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.
This commit is contained in:
Jakob Stoklund Olesen
2017-12-08 14:56:16 -08:00
parent 7d5f2f0404
commit a888b2a6f1
7 changed files with 281 additions and 6 deletions

View File

@@ -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: }

View File

@@ -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: }

View File

@@ -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: }

View File

@@ -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: }

View File

@@ -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: }

View File

@@ -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<String, fmt::Error> {
fn filecheck_text(func: &Function, domtree: &DominatorTree) -> result::Result<String, fmt::Error> {
let mut s = String::new();
write!(s, "cfg_postorder:")?;
@@ -125,5 +125,24 @@ fn filecheck_text(domtree: &DominatorTree) -> result::Result<String, fmt::Error>
}
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)
}

View File

@@ -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<Ebb, ExtraNode>,
// Scratch memory used by `compute_postorder()`.
stack: Vec<Ebb>,
}
#[derive(Default, Clone)]
struct ExtraNode {
/// First child node in the domtree.
child: PackedOption<Ebb>,
/// Next sibling node in the domtree. This linked list is ordered according to the CFG RPO.
sibling: PackedOption<Ebb>,
/// 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<Ebb>,
}
impl<'a> Iterator for ChildIter<'a> {
type Item = Ebb;
fn next(&mut self) -> Option<Ebb> {
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<A, B>(&self, a: A, b: B, layout: &Layout) -> Ordering
where
A: Into<ExpandedProgramPoint>,
B: Into<ExpandedProgramPoint>,
{
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]