Path: blob/main/cranelift/codegen/src/dominator_tree.rs
1693 views
//! A Dominator Tree represented as mappings of Blocks to their immediate dominator.12use crate::entity::SecondaryMap;3use crate::flowgraph::{BlockPredecessor, ControlFlowGraph};4use crate::ir::{Block, Function, Layout, ProgramPoint};5use crate::packed_option::PackedOption;6use crate::timing;7use alloc::vec::Vec;8use core::cmp;9use core::cmp::Ordering;10use core::mem;1112mod simple;1314pub use simple::SimpleDominatorTree;1516/// Spanning tree node, used during domtree computation.17#[derive(Clone, Default)]18struct SpanningTreeNode {19/// This node's block in function CFG.20block: PackedOption<Block>,21/// Node's ancestor in the spanning tree.22/// Gets invalidated during semi-dominator computation.23ancestor: u32,24/// The smallest semi value discovered on any semi-dominator path25/// that went through the node up till the moment.26/// Gets updated in the course of semi-dominator computation.27label: u32,28/// Semidominator value for the node.29semi: u32,30/// Immediate dominator value for the node.31/// Initialized to node's ancestor in the spanning tree.32idom: u32,33}3435/// DFS preorder number for unvisited nodes and the virtual root in the spanning tree.36const NOT_VISITED: u32 = 0;3738/// Spanning tree, in CFG preorder.39/// Node 0 is the virtual root and doesn't have a corresponding block.40/// It's not required because function's CFG in Cranelift always have41/// a singular root, but helps to avoid additional checks.42/// Numbering nodes from 0 also follows the convention in43/// `SimpleDominatorTree`.44#[derive(Clone, Default)]45struct SpanningTree {46nodes: Vec<SpanningTreeNode>,47}4849impl SpanningTree {50fn new() -> Self {51// Include the virtual root.52Self {53nodes: vec![Default::default()],54}55}5657fn with_capacity(capacity: usize) -> Self {58// Include the virtual root.59let mut nodes = Vec::with_capacity(capacity + 1);60nodes.push(Default::default());61Self { nodes }62}6364fn len(&self) -> usize {65self.nodes.len()66}6768fn reserve(&mut self, capacity: usize) {69// Virtual root should be already included.70self.nodes.reserve(capacity);71}7273fn clear(&mut self) {74self.nodes.resize(1, Default::default());75}7677/// Returns pre_number for the new node.78fn push(&mut self, ancestor: u32, block: Block) -> u32 {79// Virtual root should be already included.80debug_assert!(!self.nodes.is_empty());8182let pre_number = self.nodes.len() as u32;8384self.nodes.push(SpanningTreeNode {85block: block.into(),86ancestor,87label: pre_number,88semi: pre_number,89idom: ancestor,90});9192pre_number93}94}9596impl std::ops::Index<u32> for SpanningTree {97type Output = SpanningTreeNode;9899fn index(&self, idx: u32) -> &Self::Output {100&self.nodes[idx as usize]101}102}103104impl std::ops::IndexMut<u32> for SpanningTree {105fn index_mut(&mut self, idx: u32) -> &mut Self::Output {106&mut self.nodes[idx as usize]107}108}109110/// Traversal event to compute both preorder spanning tree111/// and postorder block list. Can't use `Dfs` from traversals.rs112/// here because of the need for parent links.113enum TraversalEvent {114Enter(u32, Block),115Exit(Block),116}117118/// Dominator tree node. We keep one of these per block.119#[derive(Clone, Default)]120struct DominatorTreeNode {121/// Immediate dominator for the block, `None` for unreachable blocks.122idom: PackedOption<Block>,123/// Preorder traversal number, zero for unreachable blocks.124pre_number: u32,125126/// First child node in the domtree.127child: PackedOption<Block>,128129/// Next sibling node in the domtree. This linked list is ordered according to the CFG RPO.130sibling: PackedOption<Block>,131132/// Sequence number for this node in a pre-order traversal of the dominator tree.133/// Unreachable blocks have number 0, the entry block is 1.134dom_pre_number: u32,135136/// Maximum `dom_pre_number` for the sub-tree of the dominator tree that is rooted at this node.137/// This is always >= `dom_pre_number`.138dom_pre_max: u32,139}140141/// The dominator tree for a single function,142/// computed using Semi-NCA algorithm.143pub struct DominatorTree {144/// DFS spanning tree.145stree: SpanningTree,146/// List of CFG blocks in postorder.147postorder: Vec<Block>,148/// Dominator tree nodes.149nodes: SecondaryMap<Block, DominatorTreeNode>,150151/// Stack for building the spanning tree.152dfs_worklist: Vec<TraversalEvent>,153/// Stack used for processing semidominator paths154/// in link-eval procedure.155eval_worklist: Vec<u32>,156157valid: bool,158}159160/// Methods for querying the dominator tree.161impl DominatorTree {162/// Is `block` reachable from the entry block?163pub fn is_reachable(&self, block: Block) -> bool {164self.nodes[block].pre_number != NOT_VISITED165}166167/// Get the CFG post-order of blocks that was used to compute the dominator tree.168///169/// Note that this post-order is not updated automatically when the CFG is modified. It is170/// computed from scratch and cached by `compute()`.171pub fn cfg_postorder(&self) -> &[Block] {172debug_assert!(self.is_valid());173&self.postorder174}175176/// Get an iterator over CFG reverse post-order of blocks used to compute the dominator tree.177///178/// Note that the post-order is not updated automatically when the CFG is modified. It is179/// computed from scratch and cached by `compute()`.180pub fn cfg_rpo(&self) -> impl Iterator<Item = &Block> {181debug_assert!(self.is_valid());182self.postorder.iter().rev()183}184185/// Returns the immediate dominator of `block`.186///187/// `block_a` is said to *dominate* `block_b` if all control flow paths from the function188/// entry to `block_b` must go through `block_a`.189///190/// The *immediate dominator* is the dominator that is closest to `block`. All other dominators191/// also dominate the immediate dominator.192///193/// This returns `None` if `block` is not reachable from the entry block, or if it is the entry block194/// which has no dominators.195pub fn idom(&self, block: Block) -> Option<Block> {196self.nodes[block].idom.into()197}198199/// Returns `true` if `a` dominates `b`.200///201/// This means that every control-flow path from the function entry to `b` must go through `a`.202///203/// Dominance is ill defined for unreachable blocks. This function can always determine204/// dominance for instructions in the same block, but otherwise returns `false` if either block205/// is unreachable.206///207/// An instruction is considered to dominate itself.208/// A block is also considered to dominate itself.209pub fn dominates<A, B>(&self, a: A, b: B, layout: &Layout) -> bool210where211A: Into<ProgramPoint>,212B: Into<ProgramPoint>,213{214let a = a.into();215let b = b.into();216match a {217ProgramPoint::Block(block_a) => match b {218ProgramPoint::Block(block_b) => self.block_dominates(block_a, block_b),219ProgramPoint::Inst(inst_b) => {220let block_b = layout221.inst_block(inst_b)222.expect("Instruction not in layout.");223self.block_dominates(block_a, block_b)224}225},226ProgramPoint::Inst(inst_a) => {227let block_a: Block = layout228.inst_block(inst_a)229.expect("Instruction not in layout.");230match b {231ProgramPoint::Block(block_b) => {232block_a != block_b && self.block_dominates(block_a, block_b)233}234ProgramPoint::Inst(inst_b) => {235let block_b = layout236.inst_block(inst_b)237.expect("Instruction not in layout.");238if block_a == block_b {239layout.pp_cmp(a, b) != Ordering::Greater240} else {241self.block_dominates(block_a, block_b)242}243}244}245}246}247}248249/// Returns `true` if `block_a` dominates `block_b`.250///251/// A block is considered to dominate itself.252/// This uses preorder numbers for O(1) constant time performance.253pub fn block_dominates(&self, block_a: Block, block_b: Block) -> bool {254let na = &self.nodes[block_a];255let nb = &self.nodes[block_b];256na.dom_pre_number <= nb.dom_pre_number && na.dom_pre_max >= nb.dom_pre_max257}258259/// Get an iterator over the direct children of `block` in the dominator tree.260///261/// These are the blocks whose immediate dominator is `block`, ordered according262/// to the CFG reverse post-order.263pub fn children(&self, block: Block) -> ChildIter<'_> {264ChildIter {265domtree: self,266next: self.nodes[block].child,267}268}269}270271impl DominatorTree {272/// Allocate a new blank dominator tree. Use `compute` to compute the dominator tree for a273/// function.274pub fn new() -> Self {275Self {276stree: SpanningTree::new(),277nodes: SecondaryMap::new(),278postorder: Vec::new(),279dfs_worklist: Vec::new(),280eval_worklist: Vec::new(),281valid: false,282}283}284285/// Allocate and compute a dominator tree.286pub fn with_function(func: &Function, cfg: &ControlFlowGraph) -> Self {287let block_capacity = func.layout.block_capacity();288let mut domtree = Self {289stree: SpanningTree::with_capacity(block_capacity),290nodes: SecondaryMap::with_capacity(block_capacity),291postorder: Vec::with_capacity(block_capacity),292dfs_worklist: Vec::new(),293eval_worklist: Vec::new(),294valid: false,295};296domtree.compute(func, cfg);297domtree298}299300/// Reset and compute a CFG post-order and dominator tree,301/// using Semi-NCA algorithm, described in the paper:302///303/// Linear-Time Algorithms for Dominators and Related Problems.304/// Loukas Georgiadis, Princeton University, November 2005.305///306/// The same algorithm is used by Julia, SpiderMonkey and LLVM,307/// the implementation is heavily inspired by them.308pub fn compute(&mut self, func: &Function, cfg: &ControlFlowGraph) {309let _tt = timing::domtree();310debug_assert!(cfg.is_valid());311312self.clear();313self.compute_spanning_tree(func);314self.compute_domtree(cfg);315self.compute_domtree_preorder();316317self.valid = true;318}319320/// Clear the data structures used to represent the dominator tree. This will leave the tree in321/// a state where `is_valid()` returns false.322pub fn clear(&mut self) {323self.stree.clear();324self.nodes.clear();325self.postorder.clear();326self.valid = false;327}328329/// Check if the dominator tree is in a valid state.330///331/// Note that this doesn't perform any kind of validity checks. It simply checks if the332/// `compute()` method has been called since the last `clear()`. It does not check that the333/// dominator tree is consistent with the CFG.334pub fn is_valid(&self) -> bool {335self.valid336}337338/// Reset all internal data structures, build spanning tree339/// and compute a post-order of the control flow graph.340fn compute_spanning_tree(&mut self, func: &Function) {341self.nodes.resize(func.dfg.num_blocks());342self.stree.reserve(func.dfg.num_blocks());343344if let Some(block) = func.layout.entry_block() {345self.dfs_worklist.push(TraversalEvent::Enter(0, block));346}347348loop {349match self.dfs_worklist.pop() {350Some(TraversalEvent::Enter(parent, block)) => {351let node = &mut self.nodes[block];352if node.pre_number != NOT_VISITED {353continue;354}355356self.dfs_worklist.push(TraversalEvent::Exit(block));357358let pre_number = self.stree.push(parent, block);359node.pre_number = pre_number;360361// Use the same traversal heuristics as in traversals.rs.362self.dfs_worklist.extend(363func.block_successors(block)364// Heuristic: chase the children in reverse. This puts365// the first successor block first in the postorder, all366// other things being equal, which tends to prioritize367// loop backedges over out-edges, putting the edge-block368// closer to the loop body and minimizing live-ranges in369// linear instruction space. This heuristic doesn't have370// any effect on the computation of dominators, and is371// purely for other consumers of the postorder we cache372// here.373.rev()374// A simple optimization: push less items to the stack.375.filter(|successor| self.nodes[*successor].pre_number == NOT_VISITED)376.map(|successor| TraversalEvent::Enter(pre_number, successor)),377);378}379Some(TraversalEvent::Exit(block)) => self.postorder.push(block),380None => break,381}382}383}384385/// Eval-link procedure from the paper.386/// For a predecessor V of node W returns V if V < W, otherwise the minimum of sdom(U),387/// where U > W and U is on a semi-dominator path for W in CFG.388/// Use path compression to bring complexity down to O(m*log(n)).389fn eval(&mut self, v: u32, last_linked: u32) -> u32 {390if self.stree[v].ancestor < last_linked {391return self.stree[v].label;392}393394// Follow semi-dominator path.395let mut root = v;396loop {397self.eval_worklist.push(root);398root = self.stree[root].ancestor;399400if self.stree[root].ancestor < last_linked {401break;402}403}404405let mut prev = root;406let root = self.stree[prev].ancestor;407408// Perform path compression. Point all ancestors to the root409// and propagate minimal sdom(U) value from ancestors to children.410while let Some(curr) = self.eval_worklist.pop() {411if self.stree[prev].label < self.stree[curr].label {412self.stree[curr].label = self.stree[prev].label;413}414415self.stree[curr].ancestor = root;416prev = curr;417}418419self.stree[v].label420}421422fn compute_domtree(&mut self, cfg: &ControlFlowGraph) {423// Compute semi-dominators.424for w in (1..self.stree.len() as u32).rev() {425let w_node = &mut self.stree[w];426let block = w_node.block.expect("Virtual root must have been excluded");427let mut semi = w_node.ancestor;428429let last_linked = w + 1;430431for pred in cfg432.pred_iter(block)433.map(|pred: BlockPredecessor| pred.block)434{435// Skip unreachable nodes.436if self.nodes[pred].pre_number == NOT_VISITED {437continue;438}439440let semi_candidate = self.eval(self.nodes[pred].pre_number, last_linked);441semi = std::cmp::min(semi, semi_candidate);442}443444let w_node = &mut self.stree[w];445w_node.label = semi;446w_node.semi = semi;447}448449// Compute immediate dominators.450for v in 1..self.stree.len() as u32 {451let semi = self.stree[v].semi;452let block = self.stree[v]453.block454.expect("Virtual root must have been excluded");455let mut idom = self.stree[v].idom;456457while idom > semi {458idom = self.stree[idom].idom;459}460461self.stree[v].idom = idom;462463self.nodes[block].idom = self.stree[idom].block;464}465}466467/// Compute dominator tree preorder information.468///469/// This populates child/sibling links and preorder numbers for fast dominance checks.470fn compute_domtree_preorder(&mut self) {471// Step 1: Populate the child and sibling links.472//473// By following the CFG post-order and pushing to the front of the lists, we make sure that474// sibling lists are ordered according to the CFG reverse post-order.475for &block in &self.postorder {476if let Some(idom) = self.idom(block) {477let sib = mem::replace(&mut self.nodes[idom].child, block.into());478self.nodes[block].sibling = sib;479} else {480// The only block without an immediate dominator is the entry.481self.dfs_worklist.push(TraversalEvent::Enter(0, block));482}483}484485// Step 2. Assign pre-order numbers from a DFS of the dominator tree.486debug_assert!(self.dfs_worklist.len() <= 1);487let mut n = 0;488while let Some(event) = self.dfs_worklist.pop() {489if let TraversalEvent::Enter(_, block) = event {490n += 1;491let node = &mut self.nodes[block];492node.dom_pre_number = n;493node.dom_pre_max = n;494if let Some(sibling) = node.sibling.expand() {495self.dfs_worklist.push(TraversalEvent::Enter(0, sibling));496}497if let Some(child) = node.child.expand() {498self.dfs_worklist.push(TraversalEvent::Enter(0, child));499}500}501}502503// Step 3. Propagate the `dom_pre_max` numbers up the tree.504// The CFG post-order is topologically ordered w.r.t. dominance so a node comes after all505// its dominator tree children.506for &block in &self.postorder {507if let Some(idom) = self.idom(block) {508let pre_max = cmp::max(self.nodes[block].dom_pre_max, self.nodes[idom].dom_pre_max);509self.nodes[idom].dom_pre_max = pre_max;510}511}512}513}514515/// An iterator that enumerates the direct children of a block in the dominator tree.516pub struct ChildIter<'a> {517domtree: &'a DominatorTree,518next: PackedOption<Block>,519}520521impl<'a> Iterator for ChildIter<'a> {522type Item = Block;523524fn next(&mut self) -> Option<Block> {525let n = self.next.expand();526if let Some(block) = n {527self.next = self.domtree.nodes[block].sibling;528}529n530}531}532533#[cfg(test)]534mod tests {535use super::*;536use crate::cursor::{Cursor, FuncCursor};537use crate::ir::types::*;538use crate::ir::{InstBuilder, TrapCode};539540#[test]541fn empty() {542let func = Function::new();543let cfg = ControlFlowGraph::with_function(&func);544debug_assert!(cfg.is_valid());545let dtree = DominatorTree::with_function(&func, &cfg);546assert_eq!(0, dtree.nodes.keys().count());547assert_eq!(dtree.cfg_postorder(), &[]);548}549550#[test]551fn unreachable_node() {552let mut func = Function::new();553let block0 = func.dfg.make_block();554let v0 = func.dfg.append_block_param(block0, I32);555let block1 = func.dfg.make_block();556let block2 = func.dfg.make_block();557let trap_block = func.dfg.make_block();558559let mut cur = FuncCursor::new(&mut func);560561cur.insert_block(block0);562cur.ins().brif(v0, block2, &[], trap_block, &[]);563564cur.insert_block(trap_block);565cur.ins().trap(TrapCode::unwrap_user(1));566567cur.insert_block(block1);568let v1 = cur.ins().iconst(I32, 1);569let v2 = cur.ins().iadd(v0, v1);570cur.ins().jump(block0, &[v2.into()]);571572cur.insert_block(block2);573cur.ins().return_(&[v0]);574575let cfg = ControlFlowGraph::with_function(cur.func);576let dt = DominatorTree::with_function(cur.func, &cfg);577578// Fall-through-first, prune-at-source DFT:579//580// block0 {581// brif block2 {582// trap583// block2 {584// return585// } block2586// } block0587assert_eq!(dt.cfg_postorder(), &[block2, trap_block, block0]);588589let v2_def = cur.func.dfg.value_def(v2).unwrap_inst();590assert!(!dt.dominates(v2_def, block0, &cur.func.layout));591assert!(!dt.dominates(block0, v2_def, &cur.func.layout));592593assert!(dt.block_dominates(block0, block0));594assert!(!dt.block_dominates(block0, block1));595assert!(dt.block_dominates(block0, block2));596assert!(!dt.block_dominates(block1, block0));597assert!(dt.block_dominates(block1, block1));598assert!(!dt.block_dominates(block1, block2));599assert!(!dt.block_dominates(block2, block0));600assert!(!dt.block_dominates(block2, block1));601assert!(dt.block_dominates(block2, block2));602}603604#[test]605fn non_zero_entry_block() {606let mut func = Function::new();607let block0 = func.dfg.make_block();608let block1 = func.dfg.make_block();609let block2 = func.dfg.make_block();610let block3 = func.dfg.make_block();611let cond = func.dfg.append_block_param(block3, I32);612613let mut cur = FuncCursor::new(&mut func);614615cur.insert_block(block3);616let jmp_block3_block1 = cur.ins().jump(block1, &[]);617618cur.insert_block(block1);619let br_block1_block0_block2 = cur.ins().brif(cond, block0, &[], block2, &[]);620621cur.insert_block(block2);622cur.ins().jump(block0, &[]);623624cur.insert_block(block0);625626let cfg = ControlFlowGraph::with_function(cur.func);627let dt = DominatorTree::with_function(cur.func, &cfg);628629// Fall-through-first, prune-at-source DFT:630//631// block3 {632// block3:jump block1 {633// block1 {634// block1:brif block0 {635// block1:jump block2 {636// block2 {637// block2:jump block0 (seen)638// } block2639// } block1:jump block2640// block0 {641// } block0642// } block1:brif block0643// } block1644// } block3:jump block1645// } block3646647assert_eq!(dt.cfg_postorder(), &[block0, block2, block1, block3]);648649assert_eq!(cur.func.layout.entry_block().unwrap(), block3);650assert_eq!(dt.idom(block3), None);651assert_eq!(dt.idom(block1).unwrap(), block3);652assert_eq!(dt.idom(block2).unwrap(), block1);653assert_eq!(dt.idom(block0).unwrap(), block1);654655assert!(dt.dominates(656br_block1_block0_block2,657br_block1_block0_block2,658&cur.func.layout659));660assert!(!dt.dominates(br_block1_block0_block2, jmp_block3_block1, &cur.func.layout));661assert!(dt.dominates(jmp_block3_block1, br_block1_block0_block2, &cur.func.layout));662}663664#[test]665fn backwards_layout() {666let mut func = Function::new();667let block0 = func.dfg.make_block();668let block1 = func.dfg.make_block();669let block2 = func.dfg.make_block();670671let mut cur = FuncCursor::new(&mut func);672673cur.insert_block(block0);674let jmp02 = cur.ins().jump(block2, &[]);675676cur.insert_block(block1);677let trap = cur.ins().trap(TrapCode::unwrap_user(5));678679cur.insert_block(block2);680let jmp21 = cur.ins().jump(block1, &[]);681682let cfg = ControlFlowGraph::with_function(cur.func);683let dt = DominatorTree::with_function(cur.func, &cfg);684685assert_eq!(cur.func.layout.entry_block(), Some(block0));686assert_eq!(dt.idom(block0), None);687assert_eq!(dt.idom(block1), Some(block2));688assert_eq!(dt.idom(block2), Some(block0));689690assert!(dt.dominates(block0, block0, &cur.func.layout));691assert!(dt.dominates(block0, jmp02, &cur.func.layout));692assert!(dt.dominates(block0, block1, &cur.func.layout));693assert!(dt.dominates(block0, trap, &cur.func.layout));694assert!(dt.dominates(block0, block2, &cur.func.layout));695assert!(dt.dominates(block0, jmp21, &cur.func.layout));696697assert!(!dt.dominates(jmp02, block0, &cur.func.layout));698assert!(dt.dominates(jmp02, jmp02, &cur.func.layout));699assert!(dt.dominates(jmp02, block1, &cur.func.layout));700assert!(dt.dominates(jmp02, trap, &cur.func.layout));701assert!(dt.dominates(jmp02, block2, &cur.func.layout));702assert!(dt.dominates(jmp02, jmp21, &cur.func.layout));703704assert!(!dt.dominates(block1, block0, &cur.func.layout));705assert!(!dt.dominates(block1, jmp02, &cur.func.layout));706assert!(dt.dominates(block1, block1, &cur.func.layout));707assert!(dt.dominates(block1, trap, &cur.func.layout));708assert!(!dt.dominates(block1, block2, &cur.func.layout));709assert!(!dt.dominates(block1, jmp21, &cur.func.layout));710711assert!(!dt.dominates(trap, block0, &cur.func.layout));712assert!(!dt.dominates(trap, jmp02, &cur.func.layout));713assert!(!dt.dominates(trap, block1, &cur.func.layout));714assert!(dt.dominates(trap, trap, &cur.func.layout));715assert!(!dt.dominates(trap, block2, &cur.func.layout));716assert!(!dt.dominates(trap, jmp21, &cur.func.layout));717718assert!(!dt.dominates(block2, block0, &cur.func.layout));719assert!(!dt.dominates(block2, jmp02, &cur.func.layout));720assert!(dt.dominates(block2, block1, &cur.func.layout));721assert!(dt.dominates(block2, trap, &cur.func.layout));722assert!(dt.dominates(block2, block2, &cur.func.layout));723assert!(dt.dominates(block2, jmp21, &cur.func.layout));724725assert!(!dt.dominates(jmp21, block0, &cur.func.layout));726assert!(!dt.dominates(jmp21, jmp02, &cur.func.layout));727assert!(dt.dominates(jmp21, block1, &cur.func.layout));728assert!(dt.dominates(jmp21, trap, &cur.func.layout));729assert!(!dt.dominates(jmp21, block2, &cur.func.layout));730assert!(dt.dominates(jmp21, jmp21, &cur.func.layout));731}732733#[test]734fn insts_same_block() {735let mut func = Function::new();736let block0 = func.dfg.make_block();737738let mut cur = FuncCursor::new(&mut func);739740cur.insert_block(block0);741let v1 = cur.ins().iconst(I32, 1);742let v2 = cur.ins().iadd(v1, v1);743let v3 = cur.ins().iadd(v2, v2);744cur.ins().return_(&[]);745746let cfg = ControlFlowGraph::with_function(cur.func);747let dt = DominatorTree::with_function(cur.func, &cfg);748749let v1_def = cur.func.dfg.value_def(v1).unwrap_inst();750let v2_def = cur.func.dfg.value_def(v2).unwrap_inst();751let v3_def = cur.func.dfg.value_def(v3).unwrap_inst();752753assert!(dt.dominates(v1_def, v2_def, &cur.func.layout));754assert!(dt.dominates(v2_def, v3_def, &cur.func.layout));755assert!(dt.dominates(v1_def, v3_def, &cur.func.layout));756757assert!(!dt.dominates(v2_def, v1_def, &cur.func.layout));758assert!(!dt.dominates(v3_def, v2_def, &cur.func.layout));759assert!(!dt.dominates(v3_def, v1_def, &cur.func.layout));760761assert!(dt.dominates(v2_def, v2_def, &cur.func.layout));762assert!(dt.dominates(block0, block0, &cur.func.layout));763764assert!(dt.dominates(block0, v1_def, &cur.func.layout));765assert!(dt.dominates(block0, v2_def, &cur.func.layout));766assert!(dt.dominates(block0, v3_def, &cur.func.layout));767768assert!(!dt.dominates(v1_def, block0, &cur.func.layout));769assert!(!dt.dominates(v2_def, block0, &cur.func.layout));770assert!(!dt.dominates(v3_def, block0, &cur.func.layout));771}772}773774775