From a888b2a6f1914ff40acaa055e6a44b4395b80f2a Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 8 Dec 2017 14:56:16 -0800 Subject: [PATCH] Dominator tree pre-order. Add a DominatorTreePreorder data structure which can be initialized for a DominatorTree and used for queries involving a pre-order of the dominator tree. Print out the pre-order and send it through filecheck in "test domtree" file tests. --- cranelift/filetests/domtree/basic.cton | 7 + cranelift/filetests/domtree/loops.cton | 20 +++ cranelift/filetests/domtree/loops2.cton | 26 ++- cranelift/filetests/domtree/tall-tree.cton | 15 ++ cranelift/filetests/domtree/wide-tree.cton | 17 ++ cranelift/src/filetest/domtree.rs | 25 ++- lib/cretonne/src/dominator_tree.rs | 177 ++++++++++++++++++++- 7 files changed, 281 insertions(+), 6 deletions(-) diff --git a/cranelift/filetests/domtree/basic.cton b/cranelift/filetests/domtree/basic.cton index 94f6d88cdb..37cb20d41d 100644 --- a/cranelift/filetests/domtree/basic.cton +++ b/cranelift/filetests/domtree/basic.cton @@ -16,3 +16,10 @@ function %test(i32) { ; sameln: ebb3 ; sameln: ebb1 ; sameln: ebb0 + +; check: domtree_preorder { +; nextln: ebb0: ebb1 +; nextln: ebb1: ebb3 ebb2 +; nextln: ebb3: +; nextln: ebb2: +; nextln: } diff --git a/cranelift/filetests/domtree/loops.cton b/cranelift/filetests/domtree/loops.cton index fd659ed421..70c62d4130 100644 --- a/cranelift/filetests/domtree/loops.cton +++ b/cranelift/filetests/domtree/loops.cton @@ -50,6 +50,15 @@ function %test(i32) { ; sameln: ebb1 ; sameln: ebb0 +; check: domtree_preorder { +; nextln: ebb0: ebb1 ebb2 ebb4 ebb3 ebb5 +; nextln: ebb1: +; nextln: ebb2: +; nextln: ebb4: +; nextln: ebb3: +; nextln: ebb5: +; nextln: } + function %loop2(i32) native { ebb0(v0: i32): brz v0, ebb1 ; dominates: ebb1 ebb3 ebb4 ebb5 @@ -82,3 +91,14 @@ function %loop2(i32) native { ; sameln: ebb2 ; sameln: ebb1 ; sameln: ebb0 + +; check: domtree_preorder { +; nextln: ebb0: ebb1 ebb2 ebb4 ebb3 ebb5 +; nextln: ebb1: +; nextln: ebb2: +; nextln: ebb4: ebb6 +; nextln: ebb6: ebb7 +; nextln: ebb7: +; nextln: ebb3: +; nextln: ebb5: +; nextln: } diff --git a/cranelift/filetests/domtree/loops2.cton b/cranelift/filetests/domtree/loops2.cton index 59d98119bd..029cea922a 100644 --- a/cranelift/filetests/domtree/loops2.cton +++ b/cranelift/filetests/domtree/loops2.cton @@ -1,6 +1,6 @@ test domtree -function %test(i32) { +function %loop1(i32) { ebb0(v0: i32): brz v0, ebb1 ; dominates: ebb1 ebb6 brnz v0, ebb2 ; dominates: ebb2 ebb9 @@ -30,7 +30,20 @@ function %test(i32) { return } -function %test(i32) native { +; check: domtree_preorder { +; nextln: ebb0: ebb1 ebb2 ebb6 ebb3 ebb9 +; nextln: ebb1: +; nextln: ebb2: ebb4 ebb5 ebb7 ebb8 +; nextln: ebb4: +; nextln: ebb5: +; nextln: ebb7: +; nextln: ebb8: +; nextln: ebb6: +; nextln: ebb3: +; nextln: ebb9: +; nextln: } + +function %loop2(i32) native { ebb0(v0: i32): brz v0, ebb1 ; dominates: ebb1 ebb3 ebb4 ebb5 jump ebb2 ; dominates: ebb2 @@ -55,3 +68,12 @@ function %test(i32) native { ; sameln: ebb2 ; sameln: ebb1 ; sameln: ebb0 + +; check: domtree_preorder { +; nextln: ebb0: ebb1 ebb2 ebb4 ebb3 ebb5 +; nextln: ebb1: +; nextln: ebb2: +; nextln: ebb4: +; nextln: ebb3: +; nextln: ebb5: +; nextln: } diff --git a/cranelift/filetests/domtree/tall-tree.cton b/cranelift/filetests/domtree/tall-tree.cton index 89821d0744..cad763fc36 100644 --- a/cranelift/filetests/domtree/tall-tree.cton +++ b/cranelift/filetests/domtree/tall-tree.cton @@ -31,3 +31,18 @@ function %test(i32) { ebb11: return } + +; check: domtree_preorder { +; nextln: ebb0: ebb1 ebb2 ebb3 ebb5 +; nextln: ebb1: ebb4 +; nextln: ebb4: ebb6 ebb7 ebb10 +; nextln: ebb6: ebb8 ebb9 ebb11 +; nextln: ebb8: +; nextln: ebb9: +; nextln: ebb11: +; nextln: ebb7: +; nextln: ebb10: +; nextln: ebb2: +; nextln: ebb3: +; nextln: ebb5: +; nextln: } diff --git a/cranelift/filetests/domtree/wide-tree.cton b/cranelift/filetests/domtree/wide-tree.cton index 3a9b4fff37..ae943dd7c2 100644 --- a/cranelift/filetests/domtree/wide-tree.cton +++ b/cranelift/filetests/domtree/wide-tree.cton @@ -39,3 +39,20 @@ function %test(i32) { ebb13: return } + +; check: domtree_preorder { +; nextln: ebb0: ebb13 ebb1 +; nextln: ebb13: +; nextln: ebb1: ebb2 ebb3 ebb4 ebb5 ebb6 ebb7 +; nextln: ebb2: +; nextln: ebb3: +; nextln: ebb4: +; nextln: ebb5: +; nextln: ebb6: +; nextln: ebb7: ebb8 ebb9 ebb10 ebb12 ebb11 +; nextln: ebb8: +; nextln: ebb9: +; nextln: ebb10: +; nextln: ebb12: +; nextln: ebb11: +; nextln: } diff --git a/cranelift/src/filetest/domtree.rs b/cranelift/src/filetest/domtree.rs index 97fd97bc9a..b118dbd314 100644 --- a/cranelift/src/filetest/domtree.rs +++ b/cranelift/src/filetest/domtree.rs @@ -10,7 +10,7 @@ //! We verify that the dominator tree annotations are complete and correct. //! -use cretonne::dominator_tree::DominatorTree; +use cretonne::dominator_tree::{DominatorTree, DominatorTreePreorder}; use cretonne::flowgraph::ControlFlowGraph; use cretonne::ir::Function; use cretonne::ir::entities::AnyEntity; @@ -110,13 +110,13 @@ impl SubTest for TestDomtree { } } - let text = filecheck_text(&domtree).expect("formatting error"); + let text = filecheck_text(func, &domtree).expect("formatting error"); run_filecheck(&text, context) } } // Generate some output for filecheck testing -fn filecheck_text(domtree: &DominatorTree) -> result::Result { +fn filecheck_text(func: &Function, domtree: &DominatorTree) -> result::Result { let mut s = String::new(); write!(s, "cfg_postorder:")?; @@ -125,5 +125,24 @@ fn filecheck_text(domtree: &DominatorTree) -> result::Result } writeln!(s, "")?; + // Compute and print out a pre-order of the dominator tree. + writeln!(s, "domtree_preorder {{")?; + let mut dtpo = DominatorTreePreorder::new(); + dtpo.compute(domtree, &func.layout); + let mut stack = Vec::new(); + stack.extend(func.layout.entry_block()); + while let Some(ebb) = stack.pop() { + write!(s, " {}:", ebb)?; + let i = stack.len(); + for ch in dtpo.children(ebb) { + write!(s, " {}", ch)?; + stack.push(ch); + } + writeln!(s, "")?; + // Reverse the children we just pushed so we'll pop them in order. + stack[i..].reverse(); + } + writeln!(s, "}}")?; + Ok(s) } diff --git a/lib/cretonne/src/dominator_tree.rs b/lib/cretonne/src/dominator_tree.rs index 0da024f36d..96fffc5b09 100644 --- a/lib/cretonne/src/dominator_tree.rs +++ b/lib/cretonne/src/dominator_tree.rs @@ -5,6 +5,8 @@ use flowgraph::{ControlFlowGraph, BasicBlock}; use ir::{Ebb, Inst, Function, Layout, ProgramOrder, ExpandedProgramPoint}; use ir::instructions::BranchInfo; use packed_option::PackedOption; +use std::cmp; +use std::mem; use timing; use std::cmp::Ordering; @@ -83,7 +85,6 @@ impl DominatorTree { /// Compare two EBBs relative to the reverse post-order. fn rpo_cmp_ebb(&self, a: Ebb, b: Ebb) -> Ordering { - self.nodes[a].rpo_number.cmp(&self.nodes[b].rpo_number) } @@ -491,6 +492,165 @@ impl DominatorTree { } } +/// Optional pre-order information that can be computed for a dominator tree. +/// +/// This data structure is computed from a `DominatorTree` and provides: +/// +/// - A forward traversable dominator tree through the `children()` iterator. +/// - An ordering of EBBs according to a dominator tree pre-order. +/// - Constant time dominance checks at the EBB granularity. +/// +/// The information in this auxillary data structure is not easy to update when the control flow +/// graph changes, which is why it is kept separate. +pub struct DominatorTreePreorder { + nodes: EntityMap, + + // Scratch memory used by `compute_postorder()`. + stack: Vec, +} + +#[derive(Default, Clone)] +struct ExtraNode { + /// First child node in the domtree. + child: PackedOption, + + /// Next sibling node in the domtree. This linked list is ordered according to the CFG RPO. + sibling: PackedOption, + + /// Sequence number for this node in a pre-order traversal of the dominator tree. + /// Unreachable blocks have number 0, the entry block is 1. + pre_number: u32, + + /// Maximum `pre_number` for the sub-tree of the dominator tree that is rooted at this node. + /// This is always >= `pre_number`. + pre_max: u32, +} + +/// Creating and computing the dominator tree pre-order. +impl DominatorTreePreorder { + /// Create a new blank `DominatorTreePreorder`. + pub fn new() -> DominatorTreePreorder { + DominatorTreePreorder { + nodes: EntityMap::new(), + stack: Vec::new(), + } + } + + /// Recompute this data structure to match `domtree`. + pub fn compute(&mut self, domtree: &DominatorTree, layout: &Layout) { + self.nodes.clear(); + assert_eq!(self.stack.len(), 0); + + // Step 1: Populate the child and sibling links. + // + // By following the CFG post-order and pushing to the front of the lists, we make sure that + // sibling lists are ordered according to the CFG reverse post-order. + for &ebb in domtree.cfg_postorder() { + if let Some(idom_inst) = domtree.idom(ebb) { + let idom = layout.pp_ebb(idom_inst); + let sib = mem::replace(&mut self.nodes[idom].child, ebb.into()); + self.nodes[ebb].sibling = sib; + } else { + // The only EBB without an immediate dominator is the entry. + self.stack.push(ebb); + } + } + + // Step 2. Assign pre-order numbers from a DFS of the dominator tree. + assert!(self.stack.len() <= 1); + let mut n = 0; + while let Some(ebb) = self.stack.pop() { + n += 1; + let node = &mut self.nodes[ebb]; + node.pre_number = n; + node.pre_max = n; + if let Some(n) = node.sibling.expand() { + self.stack.push(n); + } + if let Some(n) = node.child.expand() { + self.stack.push(n); + } + } + + // Step 3. Propagate the `pre_max` numbers up the tree. + // The CFG post-order is topologically ordered w.r.t. dominance so a node comes after all + // its dominator tree children. + for &ebb in domtree.cfg_postorder() { + if let Some(idom_inst) = domtree.idom(ebb) { + let idom = layout.pp_ebb(idom_inst); + let pre_max = cmp::max(self.nodes[ebb].pre_max, self.nodes[idom].pre_max); + self.nodes[idom].pre_max = pre_max; + } + } + } +} + +/// An iterator that enumerates the direct children of an EBB in the dominator tree. +pub struct ChildIter<'a> { + dtpo: &'a DominatorTreePreorder, + next: PackedOption, +} + +impl<'a> Iterator for ChildIter<'a> { + type Item = Ebb; + + fn next(&mut self) -> Option { + let n = self.next.expand(); + if let Some(ebb) = n { + self.next = self.dtpo.nodes[ebb].sibling; + } + n + } +} + +/// Query interface for the dominator tree pre-order. +impl DominatorTreePreorder { + /// Get an iterator over the direct children of `ebb` in the dominator tree. + /// + /// These are the EBB's whose immediate dominator is an instruction in `ebb`, ordered according + /// to the CFG reverse post-order. + pub fn children(&self, ebb: Ebb) -> ChildIter { + ChildIter { + dtpo: self, + next: self.nodes[ebb].child, + } + } + + /// Fast, constant time dominance check with EBB granularity. + /// + /// This computes the same result as `domtree.dominates(a, b)`, but in guaranteed fast constant + /// time. This is less general than the `DominatorTree` method because it only works with EBB + /// program points. + /// + /// An EBB is considered to dominate itself. + pub fn dominates(&self, a: Ebb, b: Ebb) -> bool { + let na = &self.nodes[a]; + let nb = &self.nodes[b]; + na.pre_number <= nb.pre_number && na.pre_max >= nb.pre_max + } + + /// Compare two EBBs according to the dominator pre-order. + pub fn pre_cmp_ebb(&self, a: Ebb, b: Ebb) -> Ordering { + self.nodes[a].pre_number.cmp(&self.nodes[b].pre_number) + } + + /// Compare two program points according to the dominator tree pre-order. + /// + /// This ordering of program points have the property that given a program point, pp, all the + /// program points dominated by pp follow immediately and contiguously after pp in the order. + pub fn pre_cmp(&self, a: A, b: B, layout: &Layout) -> Ordering + where + A: Into, + B: Into, + { + let a = a.into(); + let b = b.into(); + self.pre_cmp_ebb(layout.pp_ebb(a), layout.pp_ebb(b)).then( + layout.cmp(a, b), + ) + } +} + #[cfg(test)] mod test { use cursor::{Cursor, FuncCursor}; @@ -509,6 +669,9 @@ mod test { let dtree = DominatorTree::with_function(&func, &cfg); assert_eq!(0, dtree.nodes.keys().count()); assert_eq!(dtree.cfg_postorder(), &[]); + + let mut dtpo = DominatorTreePreorder::new(); + dtpo.compute(&dtree, &func.layout); } #[test] @@ -550,6 +713,18 @@ mod test { let v2_def = cur.func.dfg.value_def(v2).unwrap_inst(); assert!(!dt.dominates(v2_def, ebb0, &cur.func.layout)); assert!(!dt.dominates(ebb0, v2_def, &cur.func.layout)); + + let mut dtpo = DominatorTreePreorder::new(); + dtpo.compute(&dt, &cur.func.layout); + assert!(dtpo.dominates(ebb0, ebb0)); + assert!(!dtpo.dominates(ebb0, ebb1)); + assert!(dtpo.dominates(ebb0, ebb2)); + assert!(!dtpo.dominates(ebb1, ebb0)); + assert!(dtpo.dominates(ebb1, ebb1)); + assert!(!dtpo.dominates(ebb1, ebb2)); + assert!(!dtpo.dominates(ebb2, ebb0)); + assert!(!dtpo.dominates(ebb2, ebb1)); + assert!(dtpo.dominates(ebb2, ebb2)); } #[test]