rucc_opt/dom.rs
1//! Dominance, which is the question of what every path has to go through.
2//!
3//! Design: `spec/optimizer/06-cfg-and-dominators.md`.
4//!
5//! Block `a` dominates block `b` when every path from the entry to `b` goes through `a`. That
6//! is what makes it safe to say a value defined in `a` is available in `b`, so almost every
7//! transformation that moves code asks this and the ones that do not are asking the reverse
8//! question, which is post-dominance: whether every path from `b` to the exit goes through `a`.
9//!
10//! One algorithm answers both, the iterative one of Cooper, Harvey and Kennedy, "A Simple, Fast
11//! Dominance Algorithm" (2001). GCC uses Lengauer and Tarjan, which is asymptotically better
12//! and, on graphs the size of a function, slower. That is the argument of the paper and it is
13//! why the verifier already uses this algorithm. There is no ET-forest here and no incremental
14//! update: a change to the shape of a function throws the tree away and the next request builds
15//! a new one. Section 6.3 of the design says why, and section 6.7 says what measurement would
16//! change it, which is the analyses taking more than three percent of `-O2` time.
17//!
18//! The one thing to be careful about is that this is the analysis most likely to be quietly
19//! wrong, because a tree that is stale rather than absent produces a miscompilation rather than
20//! a crash.
21
22use rucc_ir::Block;
23
24use crate::cfg::Cfg;
25
26/// A node of whichever graph is being walked, which is a block number for the function's own
27/// graph and a block number or the added exit for the reverse of it.
28type Node = u32;
29
30/// No such node, for an immediate dominator that has not been worked out and for a node the
31/// root does not reach.
32const NONE: Node = Node::MAX;
33
34/// A graph the dominator computation can walk.
35///
36/// Two implement it, the function's graph and the reverse of the function's graph, and having
37/// them behind one trait is what stops post-dominance from being a second copy of the algorithm
38/// with the arrows turned around by hand. Static dispatch throughout, so the reverse case pays
39/// nothing for the abstraction and the forward case does not copy the adjacency lists to
40/// renumber them.
41trait Graph {
42 /// How many nodes there are, which is the length of every array indexed by node.
43 fn nodes(&self) -> usize;
44
45 /// Where the walk starts, which is the entry going forwards and the exit going backwards.
46 fn root(&self) -> Node;
47
48 /// Where control arrives at this node from.
49 fn preds(&self, node: Node) -> impl Iterator<Item = Node>;
50
51 /// Where control goes from this node.
52 fn succs(&self, node: Node) -> impl Iterator<Item = Node>;
53}
54
55/// A dominator tree over a graph whose nodes are numbered from zero.
56#[derive(Clone, Debug)]
57struct Tree {
58 /// The immediate dominator of each node, the root's own number for the root, and [`NONE`]
59 /// for a node the root does not reach.
60 idom: Vec<Node>,
61 /// The nodes each node immediately dominates.
62 children: Vec<Vec<Node>>,
63 /// When a depth first walk of the tree entered each node.
64 enter: Vec<u32>,
65 /// One past the largest number handed out anywhere in a node's subtree.
66 leave: Vec<u32>,
67 /// Where the walk started.
68 root: Node,
69}
70
71impl Tree {
72 /// Builds the tree, which is a postorder walk, a fixed point over it, and a second walk to
73 /// number the result.
74 fn new(graph: &impl Graph) -> Self {
75 let nodes = graph.nodes();
76 let root = graph.root();
77
78 // Postorder by an explicit stack. The recursive form runs out of stack on a function
79 // that is one long chain of blocks, and a ten thousand statement function is a real
80 // thing that people write and that generated code writes more of.
81 let mut order: Vec<Node> = Vec::new();
82 let mut seen = vec![false; nodes];
83 seen[root as usize] = true;
84 let mut stack = vec![(root, graph.succs(root))];
85 while let Some((node, mut walk)) = stack.pop() {
86 // Out of the `match` scrutinee, because the closure borrows the seen set and the
87 // arm below writes to it.
88 let step = walk.find(|&next| !seen[next as usize]);
89 match step {
90 Some(next) => {
91 stack.push((node, walk));
92 seen[next as usize] = true;
93 stack.push((next, graph.succs(next)));
94 }
95 None => order.push(node),
96 }
97 }
98
99 // Reverse postorder is the order this fixed point settles fastest in, because every
100 // node other than a loop header is reached after a predecessor that already has an
101 // answer. It converges in one sweep over a reducible graph and in two over the ones a
102 // `goto` into a loop body produces, which is the entire special case irreducibility
103 // needs here.
104 let rpo: Vec<Node> = order.iter().rev().copied().collect();
105 let mut rank = vec![NONE; nodes];
106 for (index, &node) in rpo.iter().enumerate() {
107 rank[node as usize] = index as u32;
108 }
109 let mut preds: Vec<Vec<u32>> = vec![Vec::new(); rpo.len()];
110 for (index, &node) in rpo.iter().enumerate() {
111 for pred in graph.preds(node) {
112 if rank[pred as usize] != NONE {
113 preds[index].push(rank[pred as usize]);
114 }
115 }
116 }
117
118 // The root dominates itself and everything else starts with no answer, which is what
119 // the sentinel is. A predecessor with no answer yet is skipped rather than met, because
120 // meeting with nothing is not the same as meeting with the root.
121 let mut idom_by_rank = vec![NONE; rpo.len()];
122 if !rpo.is_empty() {
123 idom_by_rank[0] = 0;
124 }
125 let mut changed = true;
126 while changed {
127 changed = false;
128 for index in 1..rpo.len() {
129 let mut new = NONE;
130 for &pred in &preds[index] {
131 if idom_by_rank[pred as usize] == NONE {
132 continue;
133 }
134 new = if new == NONE { pred } else { meet(&idom_by_rank, new, pred) };
135 }
136 if new != NONE && idom_by_rank[index] != new {
137 idom_by_rank[index] = new;
138 changed = true;
139 }
140 }
141 }
142
143 let mut idom = vec![NONE; nodes];
144 let mut children: Vec<Vec<Node>> = vec![Vec::new(); nodes];
145 for (index, &node) in rpo.iter().enumerate() {
146 let parent = rpo[idom_by_rank[index] as usize];
147 idom[node as usize] = parent;
148 if node != root {
149 children[parent as usize].push(node);
150 }
151 }
152
153 // The numbering that makes a query two comparisons instead of a walk up the tree. The
154 // walk is fine when the caller asks once per value definition and it is not fine for
155 // GVN or code motion, which ask per pair inside a loop. This is GCC's answer, from
156 // `compute_dom_fast_query`, and it costs one linear pass over a tree that is already
157 // built.
158 let mut enter = vec![0; nodes];
159 let mut leave = vec![0; nodes];
160 let mut time = 0;
161 let mut stack = vec![(root, 0usize)];
162 enter[root as usize] = time;
163 time += 1;
164 while let Some((node, next)) = stack.pop() {
165 match children[node as usize].get(next) {
166 Some(&child) => {
167 stack.push((node, next + 1));
168 enter[child as usize] = time;
169 time += 1;
170 stack.push((child, 0));
171 }
172 None => leave[node as usize] = time,
173 }
174 }
175
176 Self { idom, children, enter, leave, root }
177 }
178
179 /// Whether the root reaches this node at all.
180 ///
181 /// A node number the tree has no room for is one of a function with no blocks, and the
182 /// answer is the same: nothing reaches it.
183 fn reached(&self, node: Node) -> bool {
184 self.idom.get(node as usize).is_some_and(|&parent| parent != NONE)
185 }
186
187 /// Whether every path from the root to `node` goes through `of`.
188 ///
189 /// False when either end is unreachable, which is the one place this differs from the
190 /// verifier's copy of the same algorithm. The verifier calls an unreachable block
191 /// vacuously dominated so that it is reported once, for being unreachable, rather than
192 /// again for every value it uses. An optimizer must not: "defined in a block that dominates
193 /// this one" and "defined in a block control reaches" are different questions, and a pass
194 /// that answers the first when it meant the second will move a use of a value into a place
195 /// the value does not exist.
196 fn dominates(&self, of: Node, node: Node) -> bool {
197 if !self.reached(of) || !self.reached(node) {
198 return false;
199 }
200 let (enter, leave) = (self.enter[of as usize], self.leave[of as usize]);
201 enter <= self.enter[node as usize] && self.enter[node as usize] < leave
202 }
203
204 /// The nearest node that dominates both, which is the root at worst.
205 fn common(&self, a: Node, b: Node) -> Option<Node> {
206 if !self.reached(a) || !self.reached(b) {
207 return None;
208 }
209 let mut walk = a;
210 while !self.dominates(walk, b) {
211 walk = self.idom[walk as usize];
212 }
213 Some(walk)
214 }
215}
216
217/// The nearest node dominating both, walking the two chains towards the root by rank.
218///
219/// Ranks are reverse postorder positions, so the larger number is the deeper node and stepping
220/// the deeper one towards the root is what makes the two meet.
221fn meet(idom: &[u32], mut a: u32, mut b: u32) -> u32 {
222 while a != b {
223 while a > b {
224 a = idom[a as usize];
225 }
226 while b > a {
227 b = idom[b as usize];
228 }
229 }
230 a
231}
232
233/// The function's own graph, walked forwards.
234struct Forward<'a>(&'a Cfg);
235
236impl Graph for Forward<'_> {
237 fn nodes(&self) -> usize {
238 self.0.capacity()
239 }
240
241 fn root(&self) -> Node {
242 self.0.entry().map_or(0, |block| block.index() as Node)
243 }
244
245 fn preds(&self, node: Node) -> impl Iterator<Item = Node> {
246 self.0.predecessors(Block::from_usize(node as usize)).iter().map(|b| b.index() as Node)
247 }
248
249 fn succs(&self, node: Node) -> impl Iterator<Item = Node> {
250 self.0.successors(Block::from_usize(node as usize)).iter().map(|b| b.index() as Node)
251 }
252}
253
254/// Which block every path from the entry has to pass through to reach another.
255#[derive(Clone, Debug)]
256pub struct Dominators {
257 tree: Tree,
258}
259
260impl Dominators {
261 /// Builds the tree from the graph.
262 ///
263 /// A function with no blocks gives a tree that reaches nothing, and every query against it
264 /// answers no, which is what a caller handed a declaration should see.
265 #[must_use]
266 pub fn new(cfg: &Cfg) -> Self {
267 if cfg.entry().is_none() {
268 return Self { tree: Tree::empty() };
269 }
270 Self { tree: Tree::new(&Forward(cfg)) }
271 }
272
273 /// Whether every path from the entry to `block` goes through `of`.
274 ///
275 /// A block dominates itself. Both ends have to be blocks control reaches, and an
276 /// unreachable one dominates nothing and is dominated by nothing.
277 #[must_use]
278 pub fn dominates(&self, of: Block, block: Block) -> bool {
279 self.tree.dominates(of.index() as Node, block.index() as Node)
280 }
281
282 /// The same, without a block dominating itself.
283 #[must_use]
284 pub fn strictly_dominates(&self, of: Block, block: Block) -> bool {
285 of != block && self.dominates(of, block)
286 }
287
288 /// The nearest block that dominates this one and is not it.
289 ///
290 /// `None` for the entry, which has no dominator above it, and for a block control does not
291 /// reach.
292 #[must_use]
293 pub fn immediate_dominator(&self, block: Block) -> Option<Block> {
294 let node = block.index() as Node;
295 if !self.tree.reached(node) || node == self.tree.root {
296 return None;
297 }
298 Some(Block::from_usize(self.tree.idom[node as usize] as usize))
299 }
300
301 /// The blocks whose immediate dominator is this one.
302 ///
303 /// Walking these from the entry is how a pass visits a function in dominator tree order,
304 /// which is the order that has every definition in hand before any use of it.
305 pub fn children(&self, block: Block) -> impl Iterator<Item = Block> + use<'_> {
306 self.tree
307 .children
308 .get(block.index())
309 .map_or(&[][..], Vec::as_slice)
310 .iter()
311 .map(|&node| Block::from_usize(node as usize))
312 }
313
314 /// The nearest block that dominates both, which is the entry at worst.
315 ///
316 /// `None` when either block is one control does not reach.
317 #[must_use]
318 pub fn nearest_common_dominator(&self, a: Block, b: Block) -> Option<Block> {
319 self.tree
320 .common(a.index() as Node, b.index() as Node)
321 .map(|node| Block::from_usize(node as usize))
322 }
323}
324
325/// The function's graph with every arrow turned around, and an exit for them all to end at.
326///
327/// The exit is a node this adds, numbered one past the last block, because the real graph has
328/// as many blocks with no successors as it has `return` statements and a dominator computation
329/// wants one root. Every block with no successors gets an edge to it, and so does every
330/// infinite loop, through [`Reverse::connect`].
331struct Reverse {
332 /// Where control comes from, by node, which is where it goes in the real graph.
333 succs: Vec<Vec<Node>>,
334 /// Where control goes, by node, which is where it comes from in the real graph.
335 preds: Vec<Vec<Node>>,
336 /// The added node, numbered one past the last block.
337 exit: Node,
338}
339
340impl Graph for Reverse {
341 fn nodes(&self) -> usize {
342 self.succs.len()
343 }
344
345 fn root(&self) -> Node {
346 self.exit
347 }
348
349 fn preds(&self, node: Node) -> impl Iterator<Item = Node> {
350 self.preds[node as usize].iter().copied()
351 }
352
353 fn succs(&self, node: Node) -> impl Iterator<Item = Node> {
354 self.succs[node as usize].iter().copied()
355 }
356}
357
358impl Reverse {
359 /// Turns the graph around, keeping only the blocks control reaches.
360 fn new(cfg: &Cfg) -> Self {
361 let exit = cfg.capacity() as Node;
362 let mut succs: Vec<Vec<Node>> = vec![Vec::new(); cfg.capacity() + 1];
363 let mut preds: Vec<Vec<Node>> = vec![Vec::new(); cfg.capacity() + 1];
364 for &block in cfg.postorder() {
365 let from = block.index() as Node;
366 for &next in cfg.successors(block) {
367 succs[next.index()].push(from);
368 preds[from as usize].push(next.index() as Node);
369 }
370 if cfg.successors(block).is_empty() {
371 succs[exit as usize].push(from);
372 preds[from as usize].push(exit);
373 }
374 }
375 Self { succs, preds, exit }
376 }
377
378 /// Adds an edge to the exit from every region that has no path to one.
379 ///
380 /// `while (1) { }` has no path to the exit, so the reverse graph is disconnected and
381 /// post-dominance is undefined for everything in the loop. GCC's answer is
382 /// `connect_infinite_loops_to_exit`, which is to add a fake edge from the far end of each
383 /// such region, and this is the same thing. The edges are recorded rather than hidden, so a
384 /// pass that would treat one as a real path can ask.
385 ///
386 /// Anything reachable from a block with no path to the exit also has no path to the exit,
387 /// which is why walking forwards from one stays inside the region, and why the block this
388 /// stops at is always a sensible place to attach.
389 fn connect(&mut self, cfg: &Cfg) -> Vec<Block> {
390 let mut arrives = vec![false; self.nodes()];
391 let mut stack = vec![self.exit];
392 arrives[self.exit as usize] = true;
393 let mut fake = Vec::new();
394 let mut stamp = vec![u32::MAX; self.nodes()];
395 let mut round = 0;
396 loop {
397 while let Some(node) = stack.pop() {
398 for &next in &self.succs[node as usize] {
399 if !arrives[next as usize] {
400 arrives[next as usize] = true;
401 stack.push(next);
402 }
403 }
404 }
405 // Outermost first, so the walk to the far end starts above the loop rather than
406 // inside it, which is what makes the attachment point the same one a person would
407 // pick by hand.
408 let Some(stranded) = cfg.reverse_postorder().find(|block| !arrives[block.index()])
409 else {
410 break;
411 };
412 let end = far_end(cfg, stranded, &mut stamp, round).index() as Node;
413 round += 1;
414 self.succs[self.exit as usize].push(end);
415 self.preds[end as usize].push(self.exit);
416 fake.push(Block::from_usize(end as usize));
417 arrives[end as usize] = true;
418 stack.push(end);
419 }
420 fake
421 }
422}
423
424/// The block a forward walk from here stops at, which is the far end of the region.
425///
426/// It stops when every successor has already been stepped through, so on a loop it is the
427/// deepest block of the loop rather than the header, and on a chain it is the last block.
428fn far_end(cfg: &Cfg, from: Block, stamp: &mut [u32], round: u32) -> Block {
429 let mut block = from;
430 loop {
431 stamp[block.index()] = round;
432 let next = cfg.successors(block).iter().copied().find(|b| stamp[b.index()] != round);
433 match next {
434 Some(next) => block = next,
435 None => return block,
436 }
437 }
438}
439
440/// Which block every path to the exit has to pass through after leaving another.
441#[derive(Clone, Debug)]
442pub struct PostDominators {
443 tree: Tree,
444 exit: Node,
445 fake: Vec<Block>,
446}
447
448impl PostDominators {
449 /// Builds the tree over the reversed graph.
450 ///
451 /// # Panics
452 ///
453 /// Panics if a block control reaches still has no path to the exit after the fake edges
454 /// have been added, which would mean the answers below were arbitrary. Section 6.8 of the
455 /// design asks for exactly this, on the grounds that a wrong answer here is worth turning
456 /// into a crash.
457 #[must_use]
458 pub fn new(cfg: &Cfg) -> Self {
459 if cfg.entry().is_none() {
460 return Self { tree: Tree::empty(), exit: 0, fake: Vec::new() };
461 }
462 let mut reverse = Reverse::new(cfg);
463 let fake = reverse.connect(cfg);
464 let exit = reverse.exit;
465 let tree = Tree::new(&reverse);
466 for &block in cfg.postorder() {
467 assert!(
468 tree.reached(block.index() as Node),
469 "a block control reaches has no path to the exit, so post-dominance is undefined"
470 );
471 }
472 Self { tree, exit, fake }
473 }
474
475 /// Whether every path from `block` to the exit goes through `of`.
476 ///
477 /// A block post-dominates itself. Both ends have to be blocks control reaches.
478 #[must_use]
479 pub fn post_dominates(&self, of: Block, block: Block) -> bool {
480 self.tree.dominates(of.index() as Node, block.index() as Node)
481 }
482
483 /// The same, without a block post-dominating itself.
484 #[must_use]
485 pub fn strictly_post_dominates(&self, of: Block, block: Block) -> bool {
486 of != block && self.post_dominates(of, block)
487 }
488
489 /// The nearest block that post-dominates this one and is not it.
490 ///
491 /// `None` when the next thing on every path out is the end of the function, and for a block
492 /// control does not reach.
493 #[must_use]
494 pub fn immediate_post_dominator(&self, block: Block) -> Option<Block> {
495 let node = block.index() as Node;
496 if !self.tree.reached(node) {
497 return None;
498 }
499 let parent = self.tree.idom[node as usize];
500 if parent == self.exit || parent == node {
501 return None;
502 }
503 Some(Block::from_usize(parent as usize))
504 }
505
506 /// The blocks an edge to the exit was invented for, because nothing led there from them.
507 ///
508 /// A block is in here when it is the far end of an infinite loop. The list is public
509 /// because a pass that reasons about paths should be able to tell that one of them was
510 /// added by this analysis and is not a path the program can take.
511 #[must_use]
512 pub fn fake_exits(&self) -> &[Block] {
513 &self.fake
514 }
515}
516
517impl Tree {
518 /// The tree of a function that has no blocks, which reaches nothing.
519 fn empty() -> Self {
520 Self {
521 idom: Vec::new(),
522 children: Vec::new(),
523 enter: Vec::new(),
524 leave: Vec::new(),
525 root: NONE,
526 }
527 }
528}
529
530#[cfg(test)]
531mod tests {
532 use rucc_ir::Block;
533
534 use crate::cfg::Cfg;
535 use crate::dom::{Dominators, PostDominators};
536 use crate::testing::{computed_goto, graph};
537
538 /// Block number `n`, spelled the way the tests read.
539 fn b(n: usize) -> Block {
540 Block::from_usize(n)
541 }
542
543 /// The immediate dominator of every block, as block numbers, `None` where there is none.
544 fn idoms(doms: &Dominators, blocks: usize) -> Vec<Option<usize>> {
545 (0..blocks).map(|n| doms.immediate_dominator(b(n)).map(|d| d.index())).collect()
546 }
547
548 #[test]
549 fn a_straight_line_is_a_chain() {
550 let func = graph(&[&[1], &[2], &[]]);
551 let doms = Dominators::new(&Cfg::new(&func));
552 assert_eq!(idoms(&doms, 3), [None, Some(0), Some(1)]);
553 assert!(doms.dominates(b(0), b(2)));
554 assert!(!doms.dominates(b(2), b(0)));
555 assert!(doms.dominates(b(1), b(1)));
556 assert!(!doms.strictly_dominates(b(1), b(1)));
557 }
558
559 #[test]
560 fn a_diamond_is_dominated_by_the_block_it_came_from() {
561 let func = graph(&[&[1, 2], &[3], &[3], &[]]);
562 let doms = Dominators::new(&Cfg::new(&func));
563 // The join is dominated by the branch and not by either arm, which is the whole point.
564 assert_eq!(idoms(&doms, 4), [None, Some(0), Some(0), Some(0)]);
565 assert!(!doms.dominates(b(1), b(3)));
566 assert_eq!(doms.nearest_common_dominator(b(1), b(2)), Some(b(0)));
567 }
568
569 #[test]
570 fn a_loop_header_dominates_its_body_and_its_latch() {
571 let func = graph(&[&[1], &[2, 3], &[1], &[]]);
572 let doms = Dominators::new(&Cfg::new(&func));
573 assert_eq!(idoms(&doms, 4), [None, Some(0), Some(1), Some(1)]);
574 assert!(doms.dominates(b(1), b(2)));
575 // The header has two predecessors, one of which it dominates. Meeting the answer from
576 // the latch with the answer from outside is what a back edge asks of the fixed point.
577 assert!(!doms.dominates(b(2), b(1)));
578 }
579
580 #[test]
581 fn neither_entry_of_an_irreducible_loop_dominates_the_other() {
582 // The classic two-entry loop. Block 0 branches into the middle of the cycle either way,
583 // so blocks 1 and 2 are each reachable without the other.
584 let func = graph(&[&[1, 2], &[2], &[1, 3], &[]]);
585 let doms = Dominators::new(&Cfg::new(&func));
586 assert_eq!(idoms(&doms, 4), [None, Some(0), Some(0), Some(2)]);
587 assert!(!doms.dominates(b(1), b(2)));
588 assert!(!doms.dominates(b(2), b(1)));
589 }
590
591 #[test]
592 fn an_unreachable_block_dominates_nothing_and_nothing_dominates_it() {
593 let func = graph(&[&[1], &[], &[2]]);
594 let doms = Dominators::new(&Cfg::new(&func));
595 assert!(!doms.dominates(b(0), b(2)));
596 assert!(!doms.dominates(b(2), b(2)));
597 assert!(doms.immediate_dominator(b(2)).is_none());
598 assert!(doms.nearest_common_dominator(b(0), b(2)).is_none());
599 }
600
601 #[test]
602 fn a_block_only_a_computed_goto_reaches_is_dominated_by_the_branch() {
603 let doms = Dominators::new(&Cfg::new(&computed_goto()));
604 assert_eq!(doms.immediate_dominator(b(2)), Some(b(1)));
605 }
606
607 #[test]
608 fn a_declaration_answers_no_to_everything() {
609 let func =
610 rucc_ir::Func::new(rucc_base::Interner::new().intern("f"), rucc_ir::Signature::new());
611 let cfg = Cfg::new(&func);
612 let doms = Dominators::new(&cfg);
613 // A declaration is an ordinary thing for a pipeline to be handed, so asking about a
614 // block it does not have answers rather than panicking.
615 assert!(!doms.dominates(b(0), b(0)));
616 assert!(doms.immediate_dominator(b(0)).is_none());
617 assert_eq!(doms.children(b(0)).count(), 0);
618 let posts = PostDominators::new(&cfg);
619 assert!(posts.fake_exits().is_empty());
620 assert!(!posts.post_dominates(b(0), b(0)));
621 }
622
623 #[test]
624 fn the_children_of_a_block_are_what_it_immediately_dominates() {
625 let func = graph(&[&[1, 2], &[3], &[3], &[]]);
626 let doms = Dominators::new(&Cfg::new(&func));
627 let mut children: Vec<usize> = doms.children(b(0)).map(|c| c.index()).collect();
628 children.sort_unstable();
629 assert_eq!(children, [1, 2, 3]);
630 assert_eq!(doms.children(b(1)).count(), 0);
631 }
632
633 #[test]
634 fn a_join_is_post_dominated_by_what_comes_after_it() {
635 let func = graph(&[&[1, 2], &[3], &[3], &[]]);
636 let posts = PostDominators::new(&Cfg::new(&func));
637 assert!(posts.post_dominates(b(3), b(0)));
638 assert!(!posts.post_dominates(b(1), b(0)));
639 assert_eq!(posts.immediate_post_dominator(b(1)), Some(b(3)));
640 // Nothing comes after the return, so the next thing on the way out is the end of the
641 // function, which is not a block.
642 assert!(posts.immediate_post_dominator(b(3)).is_none());
643 assert!(posts.fake_exits().is_empty());
644 }
645
646 #[test]
647 fn an_infinite_loop_gets_an_edge_to_the_exit_and_says_which() {
648 // Block 1 is `while (1) { }` and block 2 is the only way out of the function.
649 let func = graph(&[&[1, 2], &[1], &[]]);
650 let posts = PostDominators::new(&Cfg::new(&func));
651 let fake: Vec<usize> = posts.fake_exits().iter().map(|block| block.index()).collect();
652 assert_eq!(fake, [1]);
653 // Without the invented edge this is undefined rather than false, which is the failure
654 // the design asks to be turned into a crash.
655 assert!(!posts.post_dominates(b(2), b(0)));
656 assert!(posts.post_dominates(b(1), b(1)));
657 }
658
659 #[test]
660 fn two_infinite_loops_get_an_edge_each() {
661 let func = graph(&[&[1, 2, 3], &[1], &[2], &[]]);
662 let posts = PostDominators::new(&Cfg::new(&func));
663 let mut fake: Vec<usize> = posts.fake_exits().iter().map(|block| block.index()).collect();
664 fake.sort_unstable();
665 assert_eq!(fake, [1, 2]);
666 // Three ways out of block 0 and no block on all three, so the next thing every path
667 // shares is the end of the function.
668 assert!(posts.immediate_post_dominator(b(0)).is_none());
669 }
670}