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