1 //! A Dominator Tree represented as mappings of Blocks to their immediate dominator.
2 //! Computed using Keith D. Cooper's "Simple, Fast Dominator Algorithm."
3 //! This version have been used in Cranelift for a very long time
4 //! and should be quite stable. Used as a baseline i.e. in verification.
5 
6 use crate::entity::SecondaryMap;
7 use crate::flowgraph::{BlockPredecessor, ControlFlowGraph};
8 use crate::ir::{Block, Function, Layout, ProgramPoint};
9 use crate::packed_option::PackedOption;
10 use crate::timing;
11 use crate::traversals::Dfs;
12 use alloc::vec::Vec;
13 use core::cmp::Ordering;
14 
15 /// RPO numbers are not first assigned in a contiguous way but as multiples of STRIDE, to leave
16 /// room for modifications of the dominator tree.
17 const STRIDE: u32 = 4;
18 
19 /// Dominator tree node. We keep one of these per block.
20 #[derive(Clone, Default)]
21 struct DomNode {
22     /// Number of this node in a reverse post-order traversal of the CFG, starting from 1.
23     /// This number is monotonic in the reverse postorder but not contiguous, since we leave
24     /// holes for later localized modifications of the dominator tree.
25     /// Unreachable nodes get number 0, all others are positive.
26     rpo_number: u32,
27 
28     /// The immediate dominator of this block.
29     ///
30     /// This is `None` for unreachable blocks and the entry block which doesn't have an immediate
31     /// dominator.
32     idom: PackedOption<Block>,
33 }
34 
35 /// The dominator tree for a single function.
36 pub struct SimpleDominatorTree {
37     nodes: SecondaryMap<Block, DomNode>,
38 
39     /// CFG post-order of all reachable blocks.
40     postorder: Vec<Block>,
41 
42     /// Scratch traversal state used by `compute_postorder()`.
43     dfs: Dfs,
44 
45     valid: bool,
46 }
47 
48 /// Methods for querying the dominator tree.
49 impl SimpleDominatorTree {
50     /// Is `block` reachable from the entry block?
is_reachable(&self, block: Block) -> bool51     pub fn is_reachable(&self, block: Block) -> bool {
52         self.nodes[block].rpo_number != 0
53     }
54 
55     /// Get the CFG post-order of blocks that was used to compute the dominator tree.
56     ///
57     /// Note that this post-order is not updated automatically when the CFG is modified. It is
58     /// computed from scratch and cached by `compute()`.
cfg_postorder(&self) -> &[Block]59     pub fn cfg_postorder(&self) -> &[Block] {
60         debug_assert!(self.is_valid());
61         &self.postorder
62     }
63 
64     /// Returns the immediate dominator of `block`.
65     ///
66     /// `block_a` is said to *dominate* `block_b` if all control flow paths from the function
67     /// entry to `block_b` must go through `block_a`.
68     ///
69     /// The *immediate dominator* is the dominator that is closest to `block`. All other dominators
70     /// also dominate the immediate dominator.
71     ///
72     /// This returns `None` if `block` is not reachable from the entry block, or if it is the entry block
73     /// which has no dominators.
idom(&self, block: Block) -> Option<Block>74     pub fn idom(&self, block: Block) -> Option<Block> {
75         self.nodes[block].idom.into()
76     }
77 
78     /// Compare two blocks relative to the reverse post-order.
rpo_cmp_block(&self, a: Block, b: Block) -> Ordering79     pub fn rpo_cmp_block(&self, a: Block, b: Block) -> Ordering {
80         self.nodes[a].rpo_number.cmp(&self.nodes[b].rpo_number)
81     }
82 
83     /// Compare two program points relative to a reverse post-order traversal of the control-flow
84     /// graph.
85     ///
86     /// Return `Ordering::Less` if `a` comes before `b` in the RPO.
87     ///
88     /// If `a` and `b` belong to the same block, compare their relative position in the block.
rpo_cmp<A, B>(&self, a: A, b: B, layout: &Layout) -> Ordering where A: Into<ProgramPoint>, B: Into<ProgramPoint>,89     pub fn rpo_cmp<A, B>(&self, a: A, b: B, layout: &Layout) -> Ordering
90     where
91         A: Into<ProgramPoint>,
92         B: Into<ProgramPoint>,
93     {
94         let a = a.into();
95         let b = b.into();
96         self.rpo_cmp_block(layout.pp_block(a), layout.pp_block(b))
97             .then_with(|| layout.pp_cmp(a, b))
98     }
99 
100     /// Returns `true` if `a` dominates `b`.
101     ///
102     /// This means that every control-flow path from the function entry to `b` must go through `a`.
103     ///
104     /// Dominance is ill defined for unreachable blocks. This function can always determine
105     /// dominance for instructions in the same block, but otherwise returns `false` if either block
106     /// is unreachable.
107     ///
108     /// An instruction is considered to dominate itself.
109     /// A block is also considered to dominate itself.
dominates<A, B>(&self, a: A, b: B, layout: &Layout) -> bool where A: Into<ProgramPoint>, B: Into<ProgramPoint>,110     pub fn dominates<A, B>(&self, a: A, b: B, layout: &Layout) -> bool
111     where
112         A: Into<ProgramPoint>,
113         B: Into<ProgramPoint>,
114     {
115         let a = a.into();
116         let b = b.into();
117         match a {
118             ProgramPoint::Block(block_a) => match b {
119                 ProgramPoint::Block(block_b) => self.block_dominates(block_a, block_b),
120                 ProgramPoint::Inst(inst_b) => {
121                     let block_b = layout
122                         .inst_block(inst_b)
123                         .expect("Instruction not in layout.");
124                     self.block_dominates(block_a, block_b)
125                 }
126             },
127             ProgramPoint::Inst(inst_a) => {
128                 let block_a: Block = layout
129                     .inst_block(inst_a)
130                     .expect("Instruction not in layout.");
131                 match b {
132                     ProgramPoint::Block(block_b) => {
133                         block_a != block_b && self.block_dominates(block_a, block_b)
134                     }
135                     ProgramPoint::Inst(inst_b) => {
136                         let block_b = layout
137                             .inst_block(inst_b)
138                             .expect("Instruction not in layout.");
139                         if block_a == block_b {
140                             layout.pp_cmp(a, b) != Ordering::Greater
141                         } else {
142                             self.block_dominates(block_a, block_b)
143                         }
144                     }
145                 }
146             }
147         }
148     }
149 
150     /// Returns `true` if `block_a` dominates `block_b`.
151     ///
152     /// A block is considered to dominate itself.
block_dominates(&self, block_a: Block, mut block_b: Block) -> bool153     fn block_dominates(&self, block_a: Block, mut block_b: Block) -> bool {
154         let rpo_a = self.nodes[block_a].rpo_number;
155 
156         // Run a finger up the dominator tree from b until we see a.
157         // Do nothing if b is unreachable.
158         while rpo_a < self.nodes[block_b].rpo_number {
159             let idom = match self.idom(block_b) {
160                 Some(idom) => idom,
161                 None => return false, // a is unreachable, so we climbed past the entry
162             };
163             block_b = idom;
164         }
165 
166         block_a == block_b
167     }
168 
169     /// Compute the common dominator of two basic blocks.
170     ///
171     /// Both basic blocks are assumed to be reachable.
common_dominator(&self, mut a: Block, mut b: Block) -> Block172     fn common_dominator(&self, mut a: Block, mut b: Block) -> Block {
173         loop {
174             match self.rpo_cmp_block(a, b) {
175                 Ordering::Less => {
176                     // `a` comes before `b` in the RPO. Move `b` up.
177                     let idom = self.nodes[b].idom.expect("Unreachable basic block?");
178                     b = idom;
179                 }
180                 Ordering::Greater => {
181                     // `b` comes before `a` in the RPO. Move `a` up.
182                     let idom = self.nodes[a].idom.expect("Unreachable basic block?");
183                     a = idom;
184                 }
185                 Ordering::Equal => break,
186             }
187         }
188 
189         debug_assert_eq!(a, b, "Unreachable block passed to common_dominator?");
190 
191         a
192     }
193 }
194 
195 impl SimpleDominatorTree {
196     /// Allocate a new blank dominator tree. Use `compute` to compute the dominator tree for a
197     /// function.
new() -> Self198     pub fn new() -> Self {
199         Self {
200             nodes: SecondaryMap::new(),
201             postorder: Vec::new(),
202             dfs: Dfs::new(),
203             valid: false,
204         }
205     }
206 
207     /// Allocate and compute a dominator tree.
with_function(func: &Function, cfg: &ControlFlowGraph) -> Self208     pub fn with_function(func: &Function, cfg: &ControlFlowGraph) -> Self {
209         let block_capacity = func.layout.block_capacity();
210         let mut domtree = Self {
211             nodes: SecondaryMap::with_capacity(block_capacity),
212             postorder: Vec::with_capacity(block_capacity),
213             dfs: Dfs::new(),
214             valid: false,
215         };
216         domtree.compute(func, cfg);
217         domtree
218     }
219 
220     /// Reset and compute a CFG post-order and dominator tree.
compute(&mut self, func: &Function, cfg: &ControlFlowGraph)221     pub fn compute(&mut self, func: &Function, cfg: &ControlFlowGraph) {
222         let _tt = timing::domtree();
223         debug_assert!(cfg.is_valid());
224         self.compute_postorder(func);
225         self.compute_domtree(func, cfg);
226         self.valid = true;
227     }
228 
229     /// Clear the data structures used to represent the dominator tree. This will leave the tree in
230     /// a state where `is_valid()` returns false.
clear(&mut self)231     pub fn clear(&mut self) {
232         self.nodes.clear();
233         self.postorder.clear();
234         self.valid = false;
235     }
236 
237     /// Check if the dominator tree is in a valid state.
238     ///
239     /// Note that this doesn't perform any kind of validity checks. It simply checks if the
240     /// `compute()` method has been called since the last `clear()`. It does not check that the
241     /// dominator tree is consistent with the CFG.
is_valid(&self) -> bool242     pub fn is_valid(&self) -> bool {
243         self.valid
244     }
245 
246     /// Reset all internal data structures and compute a post-order of the control flow graph.
247     ///
248     /// This leaves `rpo_number == 1` for all reachable blocks, 0 for unreachable ones.
compute_postorder(&mut self, func: &Function)249     fn compute_postorder(&mut self, func: &Function) {
250         self.clear();
251         self.nodes.resize(func.dfg.num_blocks());
252         self.postorder.extend(self.dfs.post_order_iter(func));
253     }
254 
255     /// Build a dominator tree from a control flow graph using Keith D. Cooper's
256     /// "Simple, Fast Dominator Algorithm."
compute_domtree(&mut self, func: &Function, cfg: &ControlFlowGraph)257     fn compute_domtree(&mut self, func: &Function, cfg: &ControlFlowGraph) {
258         // During this algorithm, `rpo_number` has the following values:
259         //
260         // 0: block is not reachable.
261         // 1: block is reachable, but has not yet been visited during the first pass. This is set by
262         // `compute_postorder`.
263         // 2+: block is reachable and has an assigned RPO number.
264 
265         // We'll be iterating over a reverse post-order of the CFG, skipping the entry block.
266         let (entry_block, postorder) = match self.postorder.as_slice().split_last() {
267             Some((&eb, rest)) => (eb, rest),
268             None => return,
269         };
270         debug_assert_eq!(Some(entry_block), func.layout.entry_block());
271 
272         // Do a first pass where we assign RPO numbers to all reachable nodes.
273         self.nodes[entry_block].rpo_number = 2 * STRIDE;
274         for (rpo_idx, &block) in postorder.iter().rev().enumerate() {
275             // Update the current node and give it an RPO number.
276             // The entry block got 2, the rest start at 3 by multiples of STRIDE to leave
277             // room for future dominator tree modifications.
278             //
279             // Since `compute_idom` will only look at nodes with an assigned RPO number, the
280             // function will never see an uninitialized predecessor.
281             //
282             // Due to the nature of the post-order traversal, every node we visit will have at
283             // least one predecessor that has previously been visited during this RPO.
284             self.nodes[block] = DomNode {
285                 idom: self.compute_idom(block, cfg).into(),
286                 rpo_number: (rpo_idx as u32 + 3) * STRIDE,
287             }
288         }
289 
290         // Now that we have RPO numbers for everything and initial immediate dominator estimates,
291         // iterate until convergence.
292         //
293         // If the function is free of irreducible control flow, this will exit after one iteration.
294         let mut changed = true;
295         while changed {
296             changed = false;
297             for &block in postorder.iter().rev() {
298                 let idom = self.compute_idom(block, cfg).into();
299                 if self.nodes[block].idom != idom {
300                     self.nodes[block].idom = idom;
301                     changed = true;
302                 }
303             }
304         }
305     }
306 
307     // Compute the immediate dominator for `block` using the current `idom` states for the reachable
308     // nodes.
compute_idom(&self, block: Block, cfg: &ControlFlowGraph) -> Block309     fn compute_idom(&self, block: Block, cfg: &ControlFlowGraph) -> Block {
310         // Get an iterator with just the reachable, already visited predecessors to `block`.
311         // Note that during the first pass, `rpo_number` is 1 for reachable blocks that haven't
312         // been visited yet, 0 for unreachable blocks.
313         let mut reachable_preds = cfg
314             .pred_iter(block)
315             .filter(|&BlockPredecessor { block: pred, .. }| self.nodes[pred].rpo_number > 1)
316             .map(|pred| pred.block);
317 
318         // The RPO must visit at least one predecessor before this node.
319         let mut idom = reachable_preds
320             .next()
321             .expect("block node must have one reachable predecessor");
322 
323         for pred in reachable_preds {
324             idom = self.common_dominator(idom, pred);
325         }
326 
327         idom
328     }
329 }
330 
331 #[cfg(test)]
332 mod tests {
333     use super::*;
334     use crate::cursor::{Cursor, FuncCursor};
335     use crate::ir::types::*;
336     use crate::ir::{InstBuilder, TrapCode};
337 
338     #[test]
empty()339     fn empty() {
340         let func = Function::new();
341         let cfg = ControlFlowGraph::with_function(&func);
342         debug_assert!(cfg.is_valid());
343         let dtree = SimpleDominatorTree::with_function(&func, &cfg);
344         assert_eq!(0, dtree.nodes.keys().count());
345         assert_eq!(dtree.cfg_postorder(), &[]);
346     }
347 
348     #[test]
unreachable_node()349     fn unreachable_node() {
350         let mut func = Function::new();
351         let block0 = func.dfg.make_block();
352         let v0 = func.dfg.append_block_param(block0, I32);
353         let block1 = func.dfg.make_block();
354         let block2 = func.dfg.make_block();
355         let trap_block = func.dfg.make_block();
356 
357         let mut cur = FuncCursor::new(&mut func);
358 
359         cur.insert_block(block0);
360         cur.ins().brif(v0, block2, &[], trap_block, &[]);
361 
362         cur.insert_block(trap_block);
363         cur.ins().trap(TrapCode::unwrap_user(1));
364 
365         cur.insert_block(block1);
366         let v1 = cur.ins().iconst(I32, 1);
367         let v2 = cur.ins().iadd(v0, v1);
368         cur.ins().jump(block0, &[v2.into()]);
369 
370         cur.insert_block(block2);
371         cur.ins().return_(&[v0]);
372 
373         let cfg = ControlFlowGraph::with_function(cur.func);
374         let dt = SimpleDominatorTree::with_function(cur.func, &cfg);
375 
376         // Fall-through-first, prune-at-source DFT:
377         //
378         // block0 {
379         //   brif block2 {
380         //     trap
381         //     block2 {
382         //       return
383         //     } block2
384         // } block0
385         assert_eq!(dt.cfg_postorder(), &[block2, trap_block, block0]);
386 
387         let v2_def = cur.func.dfg.value_def(v2).unwrap_inst();
388         assert!(!dt.dominates(v2_def, block0, &cur.func.layout));
389         assert!(!dt.dominates(block0, v2_def, &cur.func.layout));
390 
391         assert!(dt.dominates(block0, block0, &cur.func.layout));
392         assert!(!dt.dominates(block0, block1, &cur.func.layout));
393         assert!(dt.dominates(block0, block2, &cur.func.layout));
394         assert!(!dt.dominates(block1, block0, &cur.func.layout));
395         assert!(dt.dominates(block1, block1, &cur.func.layout));
396         assert!(!dt.dominates(block1, block2, &cur.func.layout));
397         assert!(!dt.dominates(block2, block0, &cur.func.layout));
398         assert!(!dt.dominates(block2, block1, &cur.func.layout));
399         assert!(dt.dominates(block2, block2, &cur.func.layout));
400     }
401 
402     #[test]
non_zero_entry_block()403     fn non_zero_entry_block() {
404         let mut func = Function::new();
405         let block0 = func.dfg.make_block();
406         let block1 = func.dfg.make_block();
407         let block2 = func.dfg.make_block();
408         let block3 = func.dfg.make_block();
409         let cond = func.dfg.append_block_param(block3, I32);
410 
411         let mut cur = FuncCursor::new(&mut func);
412 
413         cur.insert_block(block3);
414         let jmp_block3_block1 = cur.ins().jump(block1, &[]);
415 
416         cur.insert_block(block1);
417         let br_block1_block0_block2 = cur.ins().brif(cond, block0, &[], block2, &[]);
418 
419         cur.insert_block(block2);
420         cur.ins().jump(block0, &[]);
421 
422         cur.insert_block(block0);
423 
424         let cfg = ControlFlowGraph::with_function(cur.func);
425         let dt = SimpleDominatorTree::with_function(cur.func, &cfg);
426 
427         // Fall-through-first, prune-at-source DFT:
428         //
429         // block3 {
430         //   block3:jump block1 {
431         //     block1 {
432         //       block1:brif block0 {
433         //         block1:jump block2 {
434         //           block2 {
435         //             block2:jump block0 (seen)
436         //           } block2
437         //         } block1:jump block2
438         //         block0 {
439         //         } block0
440         //       } block1:brif block0
441         //     } block1
442         //   } block3:jump block1
443         // } block3
444 
445         assert_eq!(dt.cfg_postorder(), &[block0, block2, block1, block3]);
446 
447         assert_eq!(cur.func.layout.entry_block().unwrap(), block3);
448         assert_eq!(dt.idom(block3), None);
449         assert_eq!(dt.idom(block1).unwrap(), block3);
450         assert_eq!(dt.idom(block2).unwrap(), block1);
451         assert_eq!(dt.idom(block0).unwrap(), block1);
452 
453         assert!(dt.dominates(
454             br_block1_block0_block2,
455             br_block1_block0_block2,
456             &cur.func.layout
457         ));
458         assert!(!dt.dominates(br_block1_block0_block2, jmp_block3_block1, &cur.func.layout));
459         assert!(dt.dominates(jmp_block3_block1, br_block1_block0_block2, &cur.func.layout));
460 
461         assert_eq!(
462             dt.rpo_cmp(block3, block3, &cur.func.layout),
463             Ordering::Equal
464         );
465         assert_eq!(dt.rpo_cmp(block3, block1, &cur.func.layout), Ordering::Less);
466         assert_eq!(
467             dt.rpo_cmp(block3, jmp_block3_block1, &cur.func.layout),
468             Ordering::Less
469         );
470         assert_eq!(
471             dt.rpo_cmp(jmp_block3_block1, br_block1_block0_block2, &cur.func.layout),
472             Ordering::Less
473         );
474     }
475 
476     #[test]
backwards_layout()477     fn backwards_layout() {
478         let mut func = Function::new();
479         let block0 = func.dfg.make_block();
480         let block1 = func.dfg.make_block();
481         let block2 = func.dfg.make_block();
482 
483         let mut cur = FuncCursor::new(&mut func);
484 
485         cur.insert_block(block0);
486         let jmp02 = cur.ins().jump(block2, &[]);
487 
488         cur.insert_block(block1);
489         let trap = cur.ins().trap(TrapCode::unwrap_user(5));
490 
491         cur.insert_block(block2);
492         let jmp21 = cur.ins().jump(block1, &[]);
493 
494         let cfg = ControlFlowGraph::with_function(cur.func);
495         let dt = SimpleDominatorTree::with_function(cur.func, &cfg);
496 
497         assert_eq!(cur.func.layout.entry_block(), Some(block0));
498         assert_eq!(dt.idom(block0), None);
499         assert_eq!(dt.idom(block1), Some(block2));
500         assert_eq!(dt.idom(block2), Some(block0));
501 
502         assert!(dt.dominates(block0, block0, &cur.func.layout));
503         assert!(dt.dominates(block0, jmp02, &cur.func.layout));
504         assert!(dt.dominates(block0, block1, &cur.func.layout));
505         assert!(dt.dominates(block0, trap, &cur.func.layout));
506         assert!(dt.dominates(block0, block2, &cur.func.layout));
507         assert!(dt.dominates(block0, jmp21, &cur.func.layout));
508 
509         assert!(!dt.dominates(jmp02, block0, &cur.func.layout));
510         assert!(dt.dominates(jmp02, jmp02, &cur.func.layout));
511         assert!(dt.dominates(jmp02, block1, &cur.func.layout));
512         assert!(dt.dominates(jmp02, trap, &cur.func.layout));
513         assert!(dt.dominates(jmp02, block2, &cur.func.layout));
514         assert!(dt.dominates(jmp02, jmp21, &cur.func.layout));
515 
516         assert!(!dt.dominates(block1, block0, &cur.func.layout));
517         assert!(!dt.dominates(block1, jmp02, &cur.func.layout));
518         assert!(dt.dominates(block1, block1, &cur.func.layout));
519         assert!(dt.dominates(block1, trap, &cur.func.layout));
520         assert!(!dt.dominates(block1, block2, &cur.func.layout));
521         assert!(!dt.dominates(block1, jmp21, &cur.func.layout));
522 
523         assert!(!dt.dominates(trap, block0, &cur.func.layout));
524         assert!(!dt.dominates(trap, jmp02, &cur.func.layout));
525         assert!(!dt.dominates(trap, block1, &cur.func.layout));
526         assert!(dt.dominates(trap, trap, &cur.func.layout));
527         assert!(!dt.dominates(trap, block2, &cur.func.layout));
528         assert!(!dt.dominates(trap, jmp21, &cur.func.layout));
529 
530         assert!(!dt.dominates(block2, block0, &cur.func.layout));
531         assert!(!dt.dominates(block2, jmp02, &cur.func.layout));
532         assert!(dt.dominates(block2, block1, &cur.func.layout));
533         assert!(dt.dominates(block2, trap, &cur.func.layout));
534         assert!(dt.dominates(block2, block2, &cur.func.layout));
535         assert!(dt.dominates(block2, jmp21, &cur.func.layout));
536 
537         assert!(!dt.dominates(jmp21, block0, &cur.func.layout));
538         assert!(!dt.dominates(jmp21, jmp02, &cur.func.layout));
539         assert!(dt.dominates(jmp21, block1, &cur.func.layout));
540         assert!(dt.dominates(jmp21, trap, &cur.func.layout));
541         assert!(!dt.dominates(jmp21, block2, &cur.func.layout));
542         assert!(dt.dominates(jmp21, jmp21, &cur.func.layout));
543     }
544 
545     #[test]
insts_same_block()546     fn insts_same_block() {
547         let mut func = Function::new();
548         let block0 = func.dfg.make_block();
549 
550         let mut cur = FuncCursor::new(&mut func);
551 
552         cur.insert_block(block0);
553         let v1 = cur.ins().iconst(I32, 1);
554         let v2 = cur.ins().iadd(v1, v1);
555         let v3 = cur.ins().iadd(v2, v2);
556         cur.ins().return_(&[]);
557 
558         let cfg = ControlFlowGraph::with_function(cur.func);
559         let dt = SimpleDominatorTree::with_function(cur.func, &cfg);
560 
561         let v1_def = cur.func.dfg.value_def(v1).unwrap_inst();
562         let v2_def = cur.func.dfg.value_def(v2).unwrap_inst();
563         let v3_def = cur.func.dfg.value_def(v3).unwrap_inst();
564 
565         assert!(dt.dominates(v1_def, v2_def, &cur.func.layout));
566         assert!(dt.dominates(v2_def, v3_def, &cur.func.layout));
567         assert!(dt.dominates(v1_def, v3_def, &cur.func.layout));
568 
569         assert!(!dt.dominates(v2_def, v1_def, &cur.func.layout));
570         assert!(!dt.dominates(v3_def, v2_def, &cur.func.layout));
571         assert!(!dt.dominates(v3_def, v1_def, &cur.func.layout));
572 
573         assert!(dt.dominates(v2_def, v2_def, &cur.func.layout));
574         assert!(dt.dominates(block0, block0, &cur.func.layout));
575 
576         assert!(dt.dominates(block0, v1_def, &cur.func.layout));
577         assert!(dt.dominates(block0, v2_def, &cur.func.layout));
578         assert!(dt.dominates(block0, v3_def, &cur.func.layout));
579 
580         assert!(!dt.dominates(v1_def, block0, &cur.func.layout));
581         assert!(!dt.dominates(v2_def, block0, &cur.func.layout));
582         assert!(!dt.dominates(v3_def, block0, &cur.func.layout));
583     }
584 }
585