cinrs_core/reloop.rs
1//! Recovering Rust's own control flow from the [graph](crate::cfg).
2//!
3//! The graph is a correct lowering of any `goto`, and as a flat
4//! `loop { match state { … } }` it is also an opaque one: every edge is a store
5//! and a jump back to a dispatch, and the loops the C program had are gone —
6//! which is what made SQLite's `sqlite3VdbeExec` run five times the
7//! instructions GCC's does. This module puts them back. It reads the finished
8//! graph and rebuilds a *structured* program out of three shapes, which
9//! [`codegen`](crate::codegen) emits as ordinary Rust:
10//!
11//! * **Simple** — one basic block. Its terminator becomes an `if` or a `match`,
12//! and the blocks that only *it* can reach are emitted inside the arms, so a
13//! two-way branch is `if c { … } else { … }` and a `switch` is a `match` with
14//! the case bodies in it.
15//! * **Loop** — a set of blocks with an entry that something inside jumps back
16//! to. It becomes `'l: loop { … }`: a back edge is `continue 'l` and an exit
17//! is `break 'l`. The label is the C label the head carries, where there is
18//! one.
19//! * **Sequence** — the run of shapes at one level, in an order in which every
20//! remaining jump goes *forward*. A forward jump is `break` of a labelled
21//! block that ends where its target begins, which is the same trick
22//! [`regions`](crate::regions) plays on the statement tree, and the reason
23//! nothing here needs a variable to say where control went.
24//!
25//! The algorithm is Emscripten's relooper — Alon Zakai, *Emscripten: An
26//! LLVM-to-JavaScript Compiler*, OOPSLA 2011, §3.2 — as it is also implemented
27//! in `c2rust-transpile`'s `cfg::relooper`. It was written from the description
28//! of the algorithm rather than from either implementation.
29//!
30//! # The recursion
31//!
32//! `process(entries, blocks)` builds the shapes for `blocks`, which control can
33//! only get into at one of `entries`:
34//!
35//! 1. **One entry that nothing in `blocks` jumps back to** → a Simple for it,
36//! then `process` of what its terminator reaches.
37//! 2. **Several entries** → try a Multiple: give each entry the blocks that
38//! *only it* can reach, lay those groups out one after another, and
39//! `process` the rest after them. An entry another entry can reach owns
40//! nothing and waits for the rest.
41//! 3. **Otherwise** → a Loop. Its body is the entries plus every block that can
42//! get back to one of them; what is left follows it. The back edges are then
43//! *taken out of the graph*, which is what lets the body be relooped as if
44//! its entries were entered from outside only.
45//!
46//! # Irreducible regions
47//!
48//! A `goto` into the middle of a loop, Duff's device and two loops that jump
49//! into each other's bodies all produce a cycle with two heads, which no
50//! arrangement of Rust's blocks and loops can express. Step 3 is where they
51//! land: the Loop keeps *both* heads, and once its back edges are cut, step 2
52//! splits the body into one group per head. That dispatch — and only that one —
53//! reads a state variable, which every jump to a head writes first. It is one
54//! `u32` per irreducible region rather than one per function, and the blocks
55//! outside the region never touch it.
56//!
57//! # What the shapes preserve
58//!
59//! Everything, because the graph already holds it. A `cleanup` call and a
60//! variable length array's release are *statements at the end of the block the
61//! edge leaves from* — [`cfg`](crate::cfg) puts them there — so no shape has to
62//! know about them. `switch` fallthrough is an edge from one case block to the
63//! next, and comes out as the case body falling out of its `match` arm into the
64//! code after the `match`. A `return` is a block terminator wherever it stands.
65//!
66//! # Giving up
67//!
68//! Two things keep the [state machine](crate::cfg) alive. A `switch` whose
69//! `case`s fall through one into the next gives each of them a labelled block,
70//! and past two hundred of those `rustc`'s own parser runs out of stack, where
71//! the machine's flat `match` does not. And the shapes are checked before they
72//! are handed over: if every block is not in the tree exactly once, or if some
73//! jump has nothing to break to, [`plan`] answers `None` and the state machine
74//! runs instead of something subtly wrong.
75//!
76//! GNU's computed `goto` is not one of them: [`cfg`](crate::cfg) has already
77//! made it a `switch` over the labels whose address is taken, so an
78//! interpreter's dispatch loop is a Loop around a Simple whose `match` holds
79//! the handlers — the shape the same interpreter written with a `switch` has.
80
81use std::collections::{BTreeSet, HashMap, HashSet};
82
83use crate::cfg::{BasicBlock, BlockId, Terminator};
84
85/// A set of blocks, in block order.
86type Set = BTreeSet<BlockId>;
87
88/// Where control arrives when it runs off the end of a shape, or when it
89/// `break`s or `continue`s a labelled one.
90///
91/// More than one target means an irreducible region's dispatch: which of them
92/// is entered is the value written to [`Exit::state`] on the way.
93#[derive(Clone, Debug, Default, PartialEq, Eq)]
94pub struct Exit {
95 /// The blocks control can arrive at.
96 pub targets: Vec<BlockId>,
97 /// The state variable that says which, when there is more than one.
98 pub state: Option<u32>,
99}
100
101impl Exit {
102 /// Nothing follows: control cannot leave this way.
103 pub fn nowhere() -> Self {
104 Self::default()
105 }
106
107 /// One block follows, with no dispatch in between.
108 pub fn to(target: BlockId) -> Self {
109 Self {
110 targets: vec![target],
111 state: None,
112 }
113 }
114
115 /// Which entry `target` is, if this exit reaches it.
116 pub fn index_of(&self, target: BlockId) -> Option<usize> {
117 self.targets.iter().position(|block| *block == target)
118 }
119}
120
121/// A run of shapes, one after another.
122///
123/// A shape may jump to the entry of any shape *after* it in the run, which is a
124/// `break` of a labelled block that ends where that shape begins; the last one
125/// falls out of the run.
126pub type Seq = Vec<Shape>;
127
128/// One entry of a branch, and the shapes that run it.
129#[derive(Clone, Debug)]
130pub struct Arm {
131 /// The block the branch enters.
132 pub entry: BlockId,
133 /// What runs there, up to where the arms meet again.
134 pub body: Seq,
135}
136
137/// A piece of recovered control flow.
138#[derive(Clone, Debug)]
139pub enum Shape {
140 /// One basic block: its statements, then its terminator.
141 Simple {
142 /// The block.
143 block: BlockId,
144 /// The targets of its terminator that nothing else reaches, emitted
145 /// inside the `if` or the `match` the terminator becomes. Every other
146 /// target becomes a jump.
147 arms: Vec<Arm>,
148 },
149 /// `'l: loop { … }`.
150 Loop {
151 /// Distinguishes this loop's label from the others in the function.
152 id: u32,
153 /// The C label its head carries, which the Rust label is named after.
154 name: Option<String>,
155 /// The blocks it is entered at: one for a natural loop, several for an
156 /// irreducible region.
157 entries: Vec<BlockId>,
158 /// The state variable that picks the entry, for such a region.
159 state: Option<u32>,
160 /// What runs inside. For an irreducible region the first shape is the
161 /// [dispatch](Shape::Dispatch) over `state`.
162 body: Seq,
163 },
164 /// The head of an irreducible region: a `match` on the state variable that
165 /// picks which entry this turn round the loop runs.
166 Dispatch {
167 /// The state variable it reads.
168 state: u32,
169 /// One arm per entry, in the order the state numbers them.
170 arms: Vec<Arm>,
171 },
172}
173
174impl Shape {
175 /// Where a jump to this shape arrives.
176 pub fn exit(&self) -> Exit {
177 match self {
178 Shape::Simple { block, .. } => Exit::to(*block),
179 Shape::Loop { entries, state, .. } => Exit {
180 targets: entries.clone(),
181 state: *state,
182 },
183 Shape::Dispatch { state, arms } => Exit {
184 targets: arms.iter().map(|arm| arm.entry).collect(),
185 state: Some(*state),
186 },
187 }
188 }
189}
190
191/// A whole function body, recovered.
192#[derive(Clone, Debug)]
193pub struct Plan {
194 /// The shapes of the body.
195 pub body: Seq,
196 /// How many state variables the irreducible regions need; they are
197 /// numbered from zero.
198 pub states: u32,
199}
200
201/// Recovers the structured form of a graph, or answers `None`.
202///
203/// `names` is the C label each block stands at, where it stands at one, which
204/// is what the loops are named after. `None` means the [state
205/// machine](crate::cfg) has to be used: a function whose shapes would nest
206/// deeper than `rustc` parses, or — which has not been observed, and is checked
207/// for rather than trusted — one whose shapes would not account for every block
208/// or would leave a jump with nothing to break to.
209pub fn plan(blocks: &[BasicBlock], names: &HashMap<BlockId, String>) -> Option<Plan> {
210 if blocks.is_empty() {
211 return None;
212 }
213 let mut relooper = Relooper::new(blocks, names);
214 let all: Set = (0..blocks.len() as u32).map(BlockId).collect();
215 let entries: Set = [BlockId(0)].into_iter().collect();
216 let body = relooper.process(&entries, &all);
217 let plan = Plan {
218 body,
219 states: relooper.states,
220 };
221 let mut seen = vec![0usize; blocks.len()];
222 count_blocks(&plan.body, &mut seen);
223 if seen.iter().any(|times| *times != 1) {
224 return None;
225 }
226 if nesting(&plan.body, 0) > MAX_NESTING {
227 return None;
228 }
229 let mut scopes = Vec::new();
230 resolves(blocks, &plan.body, &Exit::nowhere(), &mut scopes).then_some(plan)
231}
232
233/// How deeply the output may nest blocks and loops.
234///
235/// A `switch` whose `case`s fall through one into the next gives each of them a
236/// labelled block of its own, and `rustc`'s own parser runs out of stack
237/// somewhere past four hundred of those — which is what the same figure in
238/// [`sema`](crate::sema) is for. Past this, the [state machine](crate::cfg),
239/// whose `match` is flat however many arms it has, is the answer.
240const MAX_NESTING: usize = 200;
241
242/// How deeply the shapes of `seq` nest, given that they already stand `depth`
243/// blocks deep.
244///
245/// It stops counting past [`MAX_NESTING`], which is also what bounds its own
246/// recursion — and the recursion of everything that walks a plan that passed
247/// the check, [`codegen`](crate::codegen) included.
248fn nesting(seq: &Seq, depth: usize) -> usize {
249 if depth > MAX_NESTING {
250 return depth;
251 }
252 let last = seq.len().saturating_sub(1);
253 let mut out = depth;
254 for (index, shape) in seq.iter().enumerate() {
255 // Everything but the last shape stands inside one labelled block per
256 // shape that follows it.
257 let here = depth + (last - index);
258 out = out.max(match shape {
259 Shape::Simple { arms, .. } | Shape::Dispatch { arms, .. } => arms
260 .iter()
261 .map(|arm| nesting(&arm.body, here + 1))
262 .max()
263 .unwrap_or(here),
264 Shape::Loop { body, .. } => nesting(body, here + 1),
265 });
266 if out > MAX_NESTING {
267 return out;
268 }
269 }
270 out
271}
272
273// ---------------------------------------------------------------------------
274// the recursion
275// ---------------------------------------------------------------------------
276
277struct Relooper<'a> {
278 blocks: &'a [BasicBlock],
279 names: &'a HashMap<BlockId, String>,
280 succ: Vec<Vec<BlockId>>,
281 preds: Vec<Vec<BlockId>>,
282 /// The back edges a loop has taken out of the graph.
283 ///
284 /// A jump from inside a loop to its head is a `continue`, not an edge the
285 /// shapes inside it have to follow; hiding it is what lets the body be
286 /// relooped as though its heads were entered from outside only, and is
287 /// what turns an irreducible region into a dispatch over its heads.
288 cut: HashSet<(BlockId, BlockId)>,
289 states: u32,
290 loops: u32,
291}
292
293impl<'a> Relooper<'a> {
294 fn new(blocks: &'a [BasicBlock], names: &'a HashMap<BlockId, String>) -> Self {
295 let succ: Vec<Vec<BlockId>> = blocks
296 .iter()
297 .map(|block| {
298 let mut out = block.term.successors();
299 out.sort_unstable();
300 out.dedup();
301 out
302 })
303 .collect();
304 let mut preds: Vec<Vec<BlockId>> = vec![Vec::new(); blocks.len()];
305 for (index, targets) in succ.iter().enumerate() {
306 for target in targets {
307 preds[target.0 as usize].push(BlockId(index as u32));
308 }
309 }
310 Self {
311 blocks,
312 names,
313 succ,
314 preds,
315 cut: HashSet::new(),
316 states: 0,
317 loops: 0,
318 }
319 }
320
321 /// The successors of `block` that are still in `set` and whose edge is
322 /// still in the graph.
323 fn succ_in(&self, block: BlockId, set: &Set) -> Vec<BlockId> {
324 self.succ[block.0 as usize]
325 .iter()
326 .copied()
327 .filter(|target| set.contains(target) && !self.cut.contains(&(block, *target)))
328 .collect()
329 }
330
331 /// Whether anything still in `set` jumps to `block`.
332 fn has_pred_in(&self, block: BlockId, set: &Set) -> bool {
333 self.preds[block.0 as usize]
334 .iter()
335 .any(|from| set.contains(from) && !self.cut.contains(&(*from, block)))
336 }
337
338 /// The shapes of `blocks`, which control enters at one of `entries`.
339 ///
340 /// What each shape leaves behind is the next turn of the loop below rather
341 /// than a recursive call: a run of straight-line blocks is one Simple after
342 /// another, and a function with a thousand of them would otherwise be a
343 /// thousand frames deep, each holding a copy of the block set.
344 fn process(&mut self, entries: &Set, blocks: &Set) -> Seq {
345 let mut out = Seq::new();
346 let mut entries = entries.clone();
347 let mut blocks = blocks.clone();
348 loop {
349 entries.retain(|block| blocks.contains(block));
350 if entries.is_empty() {
351 return out;
352 }
353 entries = if entries.len() == 1 {
354 let entry = *entries.iter().next().expect("one entry");
355 if self.has_pred_in(entry, &blocks) {
356 self.make_loop(&entries, &mut blocks, &mut out)
357 } else {
358 self.make_simple(entry, &mut blocks, &mut out)
359 }
360 } else {
361 // Several entries: they may be the arms of a branch, which is a
362 // Multiple, or the heads of one tangle, which is a Loop. The
363 // Multiple is tried first — an `if` whose body is a `while` has
364 // two entries and a back edge to one of them, and is an `if`
365 // around a loop rather than a loop with a dispatch at its head.
366 match self.make_multiple(&entries, &mut blocks, &mut out) {
367 Some(next) => next,
368 None => self.make_loop(&entries, &mut blocks, &mut out),
369 }
370 };
371 }
372 }
373
374 /// One block, and what its terminator reaches.
375 fn make_simple(&mut self, entry: BlockId, blocks: &mut Set, out: &mut Seq) -> Set {
376 blocks.remove(&entry);
377 let targets: Set = self.succ_in(entry, blocks).into_iter().collect();
378 // The blocks only this terminator can reach go inside the `if` or the
379 // `match` it becomes, which is what keeps a branch a branch.
380 if self.fusable(entry, &targets)
381 && let Some((arms, next)) = self.groups(&targets, blocks)
382 {
383 out.push(Shape::Simple { block: entry, arms });
384 return next;
385 }
386 out.push(Shape::Simple {
387 block: entry,
388 arms: Vec::new(),
389 });
390 targets
391 }
392
393 /// Whether the arms of `entry`'s terminator may hold the code of the blocks
394 /// they enter.
395 ///
396 /// They may not when one block is the target of two of them — a `switch`
397 /// whose `default` is also a `case`, say — because the code would then be
398 /// generated twice.
399 fn fusable(&self, entry: BlockId, targets: &Set) -> bool {
400 if targets.len() < 2 {
401 return false;
402 }
403 let listed = self.terminator_targets(entry);
404 targets
405 .iter()
406 .all(|target| listed.iter().filter(|other| *other == target).count() <= 1)
407 }
408
409 /// The blocks a terminator names, with the repeats a `match` would have to
410 /// generate twice.
411 fn terminator_targets(&self, block: BlockId) -> Vec<BlockId> {
412 match &self.blocks[block.0 as usize].term {
413 Terminator::Switch { cases, default, .. } => {
414 let mut out: Vec<BlockId> = Vec::new();
415 for (_, target) in cases {
416 if !out.contains(target) {
417 // Two `case` labels on one block share an arm.
418 out.push(*target);
419 }
420 }
421 out.push(*default);
422 out
423 }
424 other => other.successors(),
425 }
426 }
427
428 /// Lays the entries out one after another, each with the blocks only it can
429 /// reach.
430 ///
431 /// A group jumps forwards to whatever follows the last of them, which is a
432 /// `break` of the labelled block it stands in — so laying them out flat,
433 /// rather than nesting each one, is all a Multiple is here.
434 fn make_multiple(&mut self, entries: &Set, blocks: &mut Set, out: &mut Seq) -> Option<Set> {
435 let (arms, next) = self.groups(entries, blocks)?;
436 for arm in arms {
437 out.extend(arm.body);
438 }
439 Some(next)
440 }
441
442 /// The groups of a Multiple and the entries left for what follows them, or
443 /// `None` when every entry is reachable from another and nothing can be
444 /// split off. `blocks` is left holding what the groups did not take.
445 fn groups(&mut self, entries: &Set, blocks: &mut Set) -> Option<(Vec<Arm>, Set)> {
446 let owned = self.independent_groups(entries, blocks);
447 if owned.is_empty() {
448 return None;
449 }
450 let mut consumed = Set::new();
451 for (_, group) in &owned {
452 consumed.extend(group.iter().copied());
453 }
454 for block in &consumed {
455 blocks.remove(block);
456 }
457 let mut next: Set = entries
458 .iter()
459 .copied()
460 .filter(|entry| !consumed.contains(entry))
461 .collect();
462 for block in &consumed {
463 next.extend(self.succ_in(*block, blocks));
464 }
465 let arms: Vec<Arm> = owned
466 .into_iter()
467 .map(|(entry, group)| {
468 let one: Set = [entry].into_iter().collect();
469 Arm {
470 entry,
471 body: self.process(&one, &group),
472 }
473 })
474 .collect();
475 Some((arms, next))
476 }
477
478 /// For each entry, the blocks *only* that entry can reach.
479 ///
480 /// Such a group is closed: every jump into it from inside `blocks` comes
481 /// from the group itself or from its entry, because anything else would be
482 /// a second entry the block is reachable from. That is what lets the group
483 /// be emitted on its own — inside a `match` arm, or as one run of a
484 /// sequence. An entry another entry can reach owns nothing at all and is
485 /// left for the shapes that follow.
486 fn independent_groups(&self, entries: &Set, blocks: &Set) -> Vec<(BlockId, Set)> {
487 let count = self.blocks.len();
488 let mut owner: Vec<Option<BlockId>> = vec![None; count];
489 let mut shared = vec![false; count];
490 for entry in entries {
491 owner[entry.0 as usize] = Some(*entry);
492 }
493 let mut seen = vec![false; count];
494 let mut stack: Vec<BlockId> = Vec::new();
495 for entry in entries {
496 seen.iter_mut().for_each(|flag| *flag = false);
497 stack.clear();
498 stack.extend(self.succ_in(*entry, blocks));
499 for target in &stack {
500 seen[target.0 as usize] = true;
501 }
502 while let Some(block) = stack.pop() {
503 match owner[block.0 as usize] {
504 None => owner[block.0 as usize] = Some(*entry),
505 Some(other) if other != *entry => shared[block.0 as usize] = true,
506 Some(_) => {}
507 }
508 for target in self.succ_in(block, blocks) {
509 if !seen[target.0 as usize] {
510 seen[target.0 as usize] = true;
511 stack.push(target);
512 }
513 }
514 }
515 }
516 let mut out = Vec::new();
517 for entry in entries {
518 if shared[entry.0 as usize] {
519 continue;
520 }
521 let group: Set = blocks
522 .iter()
523 .copied()
524 .filter(|block| {
525 owner[block.0 as usize] == Some(*entry) && !shared[block.0 as usize]
526 })
527 .collect();
528 out.push((*entry, group));
529 }
530 out
531 }
532
533 /// A loop over the entries and everything that can get back to one of them.
534 fn make_loop(&mut self, entries: &Set, blocks: &mut Set, out: &mut Seq) -> Set {
535 let mut inner = entries.clone();
536 inner.extend(self.reaching(entries, blocks));
537 // One way out needs no labelled block around the loop, so there is
538 // nothing to gain by widening it; more than one is where it pays.
539 if self.leaving(blocks, &inner).len() > 1 {
540 self.widen_loop(blocks, &mut inner);
541 }
542 let follow = self.leaving(blocks, &inner);
543 for block in &inner {
544 blocks.remove(block);
545 }
546 // Take the back edges out: inside the body they are `continue`s.
547 for entry in entries {
548 for from in self.preds[entry.0 as usize].clone() {
549 if inner.contains(&from) {
550 self.cut.insert((from, *entry));
551 }
552 }
553 }
554 let id = self.loops;
555 self.loops += 1;
556 let name = entries
557 .iter()
558 .find_map(|entry| self.names.get(entry))
559 .cloned();
560 let (state, entry_list, body) = if entries.len() == 1 {
561 let body = self.process(entries, &inner);
562 (None, entries.iter().copied().collect(), body)
563 } else {
564 // Every head now stands on its own — nothing inside jumps to one
565 // any more — so the body splits into one group per head, and the
566 // state variable is what says which one this turn runs.
567 let state = self.states;
568 self.states += 1;
569 let mut rest = inner.clone();
570 match self.groups(entries, &mut rest) {
571 Some((arms, next)) => {
572 let list: Vec<BlockId> = arms.iter().map(|arm| arm.entry).collect();
573 let mut body = vec![Shape::Dispatch { state, arms }];
574 body.extend(self.process(&next, &rest));
575 (Some(state), list, body)
576 }
577 // Cannot happen: cutting the back edges leaves every head
578 // without a predecessor inside. The check in `plan` turns it
579 // into the state machine rather than into wrong code.
580 None => (Some(state), entries.iter().copied().collect(), Seq::new()),
581 }
582 };
583 out.push(Shape::Loop {
584 id,
585 name,
586 entries: entry_list,
587 state,
588 body,
589 });
590 follow
591 }
592
593 /// Pulls the runs that leave the loop back into its body.
594 ///
595 /// Only the entries and what can get back to them *have* to be inside: a
596 /// `case` that ends in `goto fail` cannot reach the head, so the rule above
597 /// leaves it after the loop, and then everything before it has to stand
598 /// inside a labelled block so that the dispatch can break to it. A block
599 /// only one thing jumps to is not a place anything has to break to, so
600 /// following those chains in costs nothing and is what keeps the body of a
601 /// `switch` inside the `switch` — the difference between a `match` arm and
602 /// a labelled block per `case`. (`c2rust` calls this `heuristic_loop_body`;
603 /// the idea is the same.)
604 fn widen_loop(&self, blocks: &Set, inner: &mut Set) {
605 let mut queue: Vec<BlockId> = self.leaving(blocks, inner).into_iter().collect();
606 while let Some(mut block) = queue.pop() {
607 loop {
608 if inner.contains(&block) {
609 break;
610 }
611 let entered = self.preds[block.0 as usize]
612 .iter()
613 .filter(|from| blocks.contains(from) && !self.cut.contains(&(**from, block)))
614 .count();
615 if entered != 1 {
616 break;
617 }
618 inner.insert(block);
619 let targets = self.succ_in(block, blocks);
620 let Some((next, rest)) = targets.split_first() else {
621 break;
622 };
623 queue.extend(rest.iter().copied());
624 block = *next;
625 }
626 }
627 }
628
629 /// The blocks of `blocks` that `part` jumps to from the outside of `part`.
630 fn leaving(&self, blocks: &Set, part: &Set) -> Set {
631 let mut out = Set::new();
632 for block in part {
633 for target in self.succ_in(*block, blocks) {
634 if !part.contains(&target) {
635 out.insert(target);
636 }
637 }
638 }
639 out
640 }
641
642 /// The blocks of `set` that can get to one of `targets` without leaving it.
643 fn reaching(&self, targets: &Set, set: &Set) -> Set {
644 let mut seen = vec![false; self.blocks.len()];
645 let mut out = Set::new();
646 let mut stack: Vec<BlockId> = targets.iter().copied().collect();
647 let mut todo: Vec<BlockId> = Vec::new();
648 while let Some(block) = stack.pop() {
649 for from in &self.preds[block.0 as usize] {
650 if !set.contains(from)
651 || self.cut.contains(&(*from, block))
652 || seen[from.0 as usize]
653 {
654 continue;
655 }
656 seen[from.0 as usize] = true;
657 out.insert(*from);
658 todo.push(*from);
659 }
660 stack.append(&mut todo);
661 }
662 out
663 }
664}
665
666// ---------------------------------------------------------------------------
667// the checks
668// ---------------------------------------------------------------------------
669
670/// How many times each block stands in the tree.
671fn count_blocks(seq: &Seq, out: &mut [usize]) {
672 for shape in seq {
673 match shape {
674 Shape::Simple { block, arms } => {
675 out[block.0 as usize] += 1;
676 for arm in arms {
677 count_blocks(&arm.body, out);
678 }
679 }
680 Shape::Loop { body, .. } => count_blocks(body, out),
681 Shape::Dispatch { arms, .. } => {
682 for arm in arms {
683 count_blocks(&arm.body, out);
684 }
685 }
686 }
687 }
688}
689
690/// Whether every jump the tree still holds has somewhere to go.
691///
692/// It walks exactly as [`codegen`](crate::codegen) emits: the shapes of a
693/// sequence in order, with a labelled block open around everything before each
694/// of them, and a loop's head and follow reachable from inside it. A `false`
695/// would be a bug in this module, and answering it is what keeps such a bug
696/// from becoming generated code that jumps to the wrong place.
697fn resolves(blocks: &[BasicBlock], seq: &Seq, fall: &Exit, scopes: &mut Vec<Exit>) -> bool {
698 let exits: Vec<Exit> = seq.iter().map(Shape::exit).collect();
699 let depth = scopes.len();
700 for exit in exits.iter().skip(1).rev() {
701 scopes.push(exit.clone());
702 }
703 let mut ok = true;
704 for (index, shape) in seq.iter().enumerate() {
705 if index > 0 {
706 scopes.pop();
707 }
708 let next = exits.get(index + 1).unwrap_or(fall);
709 ok &= resolves_shape(blocks, shape, next, scopes);
710 }
711 scopes.truncate(depth);
712 ok
713}
714
715fn resolves_shape(
716 blocks: &[BasicBlock],
717 shape: &Shape,
718 fall: &Exit,
719 scopes: &mut Vec<Exit>,
720) -> bool {
721 match shape {
722 Shape::Simple { block, arms } => {
723 let mut ok = true;
724 for target in blocks[block.0 as usize].term.successors() {
725 match arms.iter().find(|arm| arm.entry == target) {
726 Some(arm) => ok &= resolves(blocks, &arm.body, fall, scopes),
727 None => {
728 ok &= fall.index_of(target).is_some()
729 || scopes.iter().any(|exit| exit.index_of(target).is_some());
730 }
731 }
732 }
733 ok
734 }
735 Shape::Loop {
736 entries,
737 state,
738 body,
739 ..
740 } => {
741 let head = Exit {
742 targets: entries.clone(),
743 state: *state,
744 };
745 scopes.push(head.clone());
746 scopes.push(fall.clone());
747 let ok = resolves(blocks, body, &head, scopes);
748 scopes.pop();
749 scopes.pop();
750 ok
751 }
752 Shape::Dispatch { arms, .. } => arms
753 .iter()
754 .all(|arm| resolves(blocks, &arm.body, fall, scopes)),
755 }
756}