Implement a domtree sub-test.
This test verifies the computed dominator tree against annotations. Move the existing testcases into filetests/ with the new syntax.
This commit is contained in:
13
filetests/domtree/basic.cton
Normal file
13
filetests/domtree/basic.cton
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
test domtree
|
||||||
|
|
||||||
|
function test(i32) {
|
||||||
|
ebb0(v0: i32):
|
||||||
|
jump ebb1 ; dominates: ebb1
|
||||||
|
ebb1:
|
||||||
|
brz v0, ebb3 ; dominates: ebb3
|
||||||
|
jump ebb2 ; dominates: ebb2
|
||||||
|
ebb2:
|
||||||
|
jump ebb3
|
||||||
|
ebb3:
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
|
test domtree
|
||||||
|
|
||||||
function test(i32) {
|
function test(i32) {
|
||||||
ebb0(v0: i32): ; dominates(0)
|
ebb0(v0: i32):
|
||||||
brz v0, ebb1 ; dominates(1,3,4,5)
|
brz v0, ebb1 ; dominates: ebb1 ebb3 ebb4 ebb5
|
||||||
jump ebb2 ; dominates(2)
|
jump ebb2 ; dominates: ebb2
|
||||||
ebb1:
|
ebb1:
|
||||||
jump ebb3
|
jump ebb3
|
||||||
ebb2:
|
ebb2:
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
|
test domtree
|
||||||
|
|
||||||
function test(i32) {
|
function test(i32) {
|
||||||
ebb0(v0: i32): ; dominates(0)
|
ebb0(v0: i32):
|
||||||
brz v0, ebb1 ; dominates(1,6)
|
brz v0, ebb1 ; dominates: ebb1 ebb6
|
||||||
brnz v0, ebb2 ; dominates(2,9)
|
brnz v0, ebb2 ; dominates: ebb2 ebb9
|
||||||
jump ebb3 ; dominates(3)
|
jump ebb3 ; dominates: ebb3
|
||||||
ebb1:
|
ebb1:
|
||||||
jump ebb6
|
jump ebb6
|
||||||
ebb2:
|
ebb2:
|
||||||
brz v0, ebb4 ; dominates(4,7,8)
|
brz v0, ebb4 ; dominates: ebb4 ebb7 ebb8
|
||||||
jump ebb5 ; dominates(5)
|
jump ebb5 ; dominates: ebb5
|
||||||
ebb3:
|
ebb3:
|
||||||
jump ebb9
|
jump ebb9
|
||||||
ebb4:
|
ebb4:
|
||||||
33
filetests/domtree/tall-tree.cton
Normal file
33
filetests/domtree/tall-tree.cton
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
test domtree
|
||||||
|
|
||||||
|
function test(i32) {
|
||||||
|
ebb0(v0: i32):
|
||||||
|
brz v0, ebb1 ; dominates: ebb1
|
||||||
|
brnz v0, ebb2 ; dominates: ebb2 ebb5
|
||||||
|
jump ebb3 ; dominates: ebb3
|
||||||
|
ebb1:
|
||||||
|
jump ebb4 ; dominates: ebb4
|
||||||
|
ebb2:
|
||||||
|
jump ebb5
|
||||||
|
ebb3:
|
||||||
|
jump ebb5
|
||||||
|
ebb4:
|
||||||
|
brz v0, ebb6 ; dominates: ebb6 ebb10
|
||||||
|
jump ebb7 ; dominates: ebb7
|
||||||
|
ebb5:
|
||||||
|
return
|
||||||
|
ebb6:
|
||||||
|
brz v0, ebb8 ; dominates: ebb11 ebb8
|
||||||
|
brnz v0, ebb9 ; dominates: ebb9
|
||||||
|
jump ebb10
|
||||||
|
ebb7:
|
||||||
|
jump ebb10
|
||||||
|
ebb8:
|
||||||
|
jump ebb11
|
||||||
|
ebb9:
|
||||||
|
jump ebb11
|
||||||
|
ebb10:
|
||||||
|
return
|
||||||
|
ebb11:
|
||||||
|
return
|
||||||
|
}
|
||||||
41
filetests/domtree/wide-tree.cton
Normal file
41
filetests/domtree/wide-tree.cton
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
test domtree
|
||||||
|
|
||||||
|
function test(i32) {
|
||||||
|
ebb0(v0: i32):
|
||||||
|
brz v0, ebb13 ; dominates: ebb13
|
||||||
|
jump ebb1 ; dominates: ebb1
|
||||||
|
ebb1:
|
||||||
|
brz v0, ebb2 ; dominates: ebb2 ebb7
|
||||||
|
brnz v0, ebb3 ; dominates: ebb3
|
||||||
|
brz v0, ebb4 ; dominates: ebb4
|
||||||
|
brnz v0, ebb5 ; dominates: ebb5
|
||||||
|
jump ebb6 ; dominates: ebb6
|
||||||
|
ebb2:
|
||||||
|
jump ebb7
|
||||||
|
ebb3:
|
||||||
|
jump ebb7
|
||||||
|
ebb4:
|
||||||
|
jump ebb7
|
||||||
|
ebb5:
|
||||||
|
jump ebb7
|
||||||
|
ebb6:
|
||||||
|
jump ebb7
|
||||||
|
ebb7:
|
||||||
|
brnz v0, ebb8 ; dominates: ebb8 ebb12
|
||||||
|
brz v0, ebb9 ; dominates: ebb9
|
||||||
|
brnz v0, ebb10 ; dominates: ebb10
|
||||||
|
jump ebb11 ; dominates: ebb11
|
||||||
|
ebb8:
|
||||||
|
jump ebb12
|
||||||
|
ebb9:
|
||||||
|
jump ebb12
|
||||||
|
ebb10:
|
||||||
|
brz v0, ebb13
|
||||||
|
jump ebb12
|
||||||
|
ebb11:
|
||||||
|
jump ebb13
|
||||||
|
ebb12:
|
||||||
|
return
|
||||||
|
ebb13:
|
||||||
|
return
|
||||||
|
}
|
||||||
103
src/tools/filetest/domtree.rs
Normal file
103
src/tools/filetest/domtree.rs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
|
||||||
|
//! Test command for verifying dominator trees.
|
||||||
|
//!
|
||||||
|
//! The `test domtree` test command looks for annotations on instructions like this:
|
||||||
|
//!
|
||||||
|
//! jump ebb3 ; dominates: ebb3
|
||||||
|
//!
|
||||||
|
//! This annotation means that the jump instruction is expected to be the immediate dominator of
|
||||||
|
//! `ebb3`.
|
||||||
|
//!
|
||||||
|
//! We verify that the dominator tree annotations are complete and correct.
|
||||||
|
//!
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::borrow::{Borrow, Cow};
|
||||||
|
use cretonne::ir::Function;
|
||||||
|
use cretonne::ir::entities::AnyEntity;
|
||||||
|
use cretonne::cfg::ControlFlowGraph;
|
||||||
|
use cretonne::dominator_tree::DominatorTree;
|
||||||
|
use cton_reader::TestCommand;
|
||||||
|
use filetest::subtest::{SubTest, Context, Result};
|
||||||
|
use utils::match_directive;
|
||||||
|
|
||||||
|
struct TestDomtree;
|
||||||
|
|
||||||
|
pub fn subtest(parsed: &TestCommand) -> Result<Box<SubTest>> {
|
||||||
|
assert_eq!(parsed.command, "domtree");
|
||||||
|
if !parsed.options.is_empty() {
|
||||||
|
Err(format!("No options allowed on {}", parsed))
|
||||||
|
} else {
|
||||||
|
Ok(Box::new(TestDomtree))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubTest for TestDomtree {
|
||||||
|
fn name(&self) -> Cow<str> {
|
||||||
|
Cow::from("domtree")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract our own dominator tree from
|
||||||
|
fn run(&self, func: Cow<Function>, context: &Context) -> Result<()> {
|
||||||
|
let func = func.borrow();
|
||||||
|
let cfg = ControlFlowGraph::new(func);
|
||||||
|
let domtree = DominatorTree::new(&cfg);
|
||||||
|
|
||||||
|
// Build an expected domtree from the source annotations.
|
||||||
|
let mut expected = HashMap::new();
|
||||||
|
for comment in &context.details.comments {
|
||||||
|
if let Some(tail) = match_directive(comment.text, "dominates:") {
|
||||||
|
let inst = match comment.entity {
|
||||||
|
AnyEntity::Inst(inst) => inst,
|
||||||
|
_ => {
|
||||||
|
return Err(format!("annotation on non-inst {}: {}",
|
||||||
|
comment.entity,
|
||||||
|
comment.text))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for src_ebb in tail.split_whitespace() {
|
||||||
|
let ebb = match context.details.map.lookup_str(src_ebb) {
|
||||||
|
Some(AnyEntity::Ebb(ebb)) => ebb,
|
||||||
|
_ => return Err(format!("expected EBB: {}", src_ebb)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Annotations say that `inst` is the idom of `ebb`.
|
||||||
|
if expected.insert(ebb, inst).is_some() {
|
||||||
|
return Err(format!("multiple dominators for {}", src_ebb));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare to computed domtree.
|
||||||
|
match domtree.idom(ebb) {
|
||||||
|
Some((_, got_inst)) if got_inst != inst => {
|
||||||
|
return Err(format!("mismatching idoms for {}:\n\
|
||||||
|
want: {}, got: {}",
|
||||||
|
src_ebb,
|
||||||
|
inst,
|
||||||
|
got_inst));
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(format!("mismatching idoms for {}:\n\
|
||||||
|
want: {}, got: unreachable",
|
||||||
|
src_ebb,
|
||||||
|
inst));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we know that everything in `expected` is consistent with `domtree`.
|
||||||
|
// All other EBB's should be either unreachable or the entry block.
|
||||||
|
for ebb in func.layout.ebbs().skip(1).filter(|ebb| !expected.contains_key(&ebb)) {
|
||||||
|
if let Some((_, got_inst)) = domtree.idom(ebb) {
|
||||||
|
return Err(format!("mismatching idoms for renumbered {}:\n\
|
||||||
|
want: unrechable, got: {}",
|
||||||
|
ebb,
|
||||||
|
got_inst));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ use filetest::runner::TestRunner;
|
|||||||
|
|
||||||
pub mod subtest;
|
pub mod subtest;
|
||||||
mod runner;
|
mod runner;
|
||||||
|
mod domtree;
|
||||||
|
|
||||||
/// Main entry point for `cton-util test`.
|
/// Main entry point for `cton-util test`.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ pub type Result<T> = result::Result<T, String>;
|
|||||||
pub fn new(parsed: &TestCommand) -> Result<Box<SubTest>> {
|
pub fn new(parsed: &TestCommand) -> Result<Box<SubTest>> {
|
||||||
use cat;
|
use cat;
|
||||||
use print_cfg;
|
use print_cfg;
|
||||||
|
use filetest::domtree;
|
||||||
match parsed.command {
|
match parsed.command {
|
||||||
"cat" => cat::subtest(parsed),
|
"cat" => cat::subtest(parsed),
|
||||||
"print-cfg" => print_cfg::subtest(parsed),
|
"print-cfg" => print_cfg::subtest(parsed),
|
||||||
|
"domtree" => domtree::subtest(parsed),
|
||||||
_ => Err(format!("unknown test command '{}'", parsed.command)),
|
_ => Err(format!("unknown test command '{}'", parsed.command)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
extern crate cretonne;
|
|
||||||
extern crate cton_reader;
|
|
||||||
extern crate glob;
|
|
||||||
extern crate regex;
|
|
||||||
|
|
||||||
use std::env;
|
|
||||||
use glob::glob;
|
|
||||||
use regex::Regex;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Read;
|
|
||||||
use self::cretonne::ir::Ebb;
|
|
||||||
use self::cton_reader::parse_functions;
|
|
||||||
use self::cretonne::ir::function::Function;
|
|
||||||
use self::cretonne::entity_map::EntityMap;
|
|
||||||
use self::cretonne::ir::entities::NO_INST;
|
|
||||||
use self::cretonne::cfg::ControlFlowGraph;
|
|
||||||
use self::cretonne::dominator_tree::DominatorTree;
|
|
||||||
|
|
||||||
/// Construct a dominator tree from specially formatted comments in
|
|
||||||
/// cton source. Each line with a jump/branch instruction should
|
|
||||||
/// have a comment of the format: `dominates(n, ..., N)`, where each `n`
|
|
||||||
/// is the Ebb number for which this instruction is the immediate dominator.
|
|
||||||
fn dominator_tree_from_source(func: &Function, function_source: &str) -> DominatorTree {
|
|
||||||
let ebb_re = Regex::new("^[ \t]*ebb[0-9]+.*:").unwrap();
|
|
||||||
let dom_re = Regex::new("dominates\\(([0-9,]+)\\)").unwrap();
|
|
||||||
let inst_re = Regex::new("^[ \t]*[a-zA-Z0-9]+[^{}]*").unwrap();
|
|
||||||
let func_re = Regex::new("^[ \t]*function.*").unwrap();
|
|
||||||
|
|
||||||
let ebbs = func.layout.ebbs().collect::<Vec<_>>();
|
|
||||||
let mut data = EntityMap::with_capacity(ebbs.len());
|
|
||||||
|
|
||||||
if ebbs.len() < 1 {
|
|
||||||
return DominatorTree::from_data(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut ebb_offset = 0;
|
|
||||||
let mut inst_offset = 0;
|
|
||||||
|
|
||||||
let mut cur_ebb = ebbs[0];
|
|
||||||
let mut insts = func.layout.ebb_insts(ebbs[ebb_offset]).collect::<Vec<_>>();
|
|
||||||
|
|
||||||
for line in function_source.lines() {
|
|
||||||
if ebb_re.is_match(line) {
|
|
||||||
cur_ebb = ebbs[ebb_offset];
|
|
||||||
insts = func.layout.ebb_insts(cur_ebb).collect::<Vec<_>>();
|
|
||||||
ebb_offset += 1;
|
|
||||||
inst_offset = 0;
|
|
||||||
} else if inst_re.is_match(line) && !func_re.is_match(line) {
|
|
||||||
inst_offset += 1;
|
|
||||||
}
|
|
||||||
match dom_re.captures(line) {
|
|
||||||
Some(caps) => {
|
|
||||||
for s in caps.at(1).unwrap().split(",") {
|
|
||||||
let this_ebb = Ebb::with_number(s.parse::<u32>().unwrap()).unwrap();
|
|
||||||
let inst = if inst_offset == 0 {
|
|
||||||
NO_INST
|
|
||||||
} else {
|
|
||||||
insts[inst_offset - 1].clone()
|
|
||||||
};
|
|
||||||
data[this_ebb] = Some((cur_ebb.clone(), inst));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
DominatorTree::from_data(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_dominator_tree(function_source: &str) {
|
|
||||||
|
|
||||||
let func = &parse_functions(function_source).unwrap()[0];
|
|
||||||
let src_dtree = dominator_tree_from_source(&func, function_source);
|
|
||||||
|
|
||||||
let cfg = ControlFlowGraph::new(&func);
|
|
||||||
let dtree = DominatorTree::new(&cfg);
|
|
||||||
|
|
||||||
for ebb in func.layout.ebbs() {
|
|
||||||
assert_eq!(dtree.idom(ebb), src_dtree.idom(ebb));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_all() {
|
|
||||||
let testdir = format!("{}/tests/dominator_tree_testdata/*.cton",
|
|
||||||
env::current_dir().unwrap().display());
|
|
||||||
|
|
||||||
for entry in glob(&testdir).unwrap() {
|
|
||||||
let path = entry.unwrap();
|
|
||||||
println!("Testing {:?}", path);
|
|
||||||
let mut file = File::open(&path).unwrap();
|
|
||||||
let mut buffer = String::new();
|
|
||||||
file.read_to_string(&mut buffer).unwrap();
|
|
||||||
test_dominator_tree(&buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
function test(i32) {
|
|
||||||
ebb0(v0: i32): ; dominates(0)
|
|
||||||
jump ebb1 ; dominates(1)
|
|
||||||
ebb1:
|
|
||||||
brz v0, ebb3 ; dominates(3)
|
|
||||||
jump ebb2 ; dominates(2)
|
|
||||||
ebb2:
|
|
||||||
jump ebb3
|
|
||||||
ebb3:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
function test(i32) {
|
|
||||||
ebb0(v0: i32): ; dominates(0)
|
|
||||||
brz v0, ebb1 ; dominates(1)
|
|
||||||
brnz v0, ebb2 ; dominates(2,5)
|
|
||||||
jump ebb3 ; dominates(3)
|
|
||||||
ebb1:
|
|
||||||
jump ebb4 ; dominates(4)
|
|
||||||
ebb2:
|
|
||||||
jump ebb5
|
|
||||||
ebb3:
|
|
||||||
jump ebb5
|
|
||||||
ebb4:
|
|
||||||
brz v0, ebb6 ; dominates(6,10)
|
|
||||||
jump ebb7 ; dominates(7)
|
|
||||||
ebb5:
|
|
||||||
return
|
|
||||||
ebb6:
|
|
||||||
brz v0, ebb8 ; dominates(11,8)
|
|
||||||
brnz v0, ebb9 ; dominates(9)
|
|
||||||
jump ebb10
|
|
||||||
ebb7:
|
|
||||||
jump ebb10
|
|
||||||
ebb8:
|
|
||||||
jump ebb11
|
|
||||||
ebb9:
|
|
||||||
jump ebb11
|
|
||||||
ebb10:
|
|
||||||
return
|
|
||||||
ebb11:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
function test(i32) {
|
|
||||||
ebb0(v0: i32): ; dominates(0)
|
|
||||||
brz v0, ebb13 ; dominates(13)
|
|
||||||
jump ebb1 ; dominates(1)
|
|
||||||
ebb1:
|
|
||||||
brz v0, ebb2 ; dominates(2,7)
|
|
||||||
brnz v0, ebb3 ; dominates(3)
|
|
||||||
brz v0, ebb4 ; dominates(4)
|
|
||||||
brnz v0, ebb5 ; dominates(5)
|
|
||||||
jump ebb6 ; dominates(6)
|
|
||||||
ebb2:
|
|
||||||
jump ebb7
|
|
||||||
ebb3:
|
|
||||||
jump ebb7
|
|
||||||
ebb4:
|
|
||||||
jump ebb7
|
|
||||||
ebb5:
|
|
||||||
jump ebb7
|
|
||||||
ebb6:
|
|
||||||
jump ebb7
|
|
||||||
ebb7:
|
|
||||||
brnz v0, ebb8 ; dominates(8,12)
|
|
||||||
brz v0, ebb9 ; dominates(9)
|
|
||||||
brnz v0, ebb10 ; dominates(10)
|
|
||||||
jump ebb11 ; dominates(11)
|
|
||||||
ebb8:
|
|
||||||
jump ebb12
|
|
||||||
ebb9:
|
|
||||||
jump ebb12
|
|
||||||
ebb10:
|
|
||||||
brz v0, ebb13
|
|
||||||
jump ebb12
|
|
||||||
ebb11:
|
|
||||||
jump ebb13
|
|
||||||
ebb12:
|
|
||||||
return
|
|
||||||
ebb13:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -11,3 +11,29 @@ pub fn read_to_string<P: AsRef<Path>>(path: P) -> Result<String> {
|
|||||||
try!(file.read_to_string(&mut buffer));
|
try!(file.read_to_string(&mut buffer));
|
||||||
Ok(buffer)
|
Ok(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Look for a directive in a comment string.
|
||||||
|
/// The directive is of the form "foo:" and should follow the leading `;` in the comment:
|
||||||
|
///
|
||||||
|
/// ; dominates: ebb3 ebb4
|
||||||
|
///
|
||||||
|
/// Return the comment text following the directive.
|
||||||
|
pub fn match_directive<'a>(comment: &'a str, directive: &str) -> Option<&'a str> {
|
||||||
|
assert!(directive.ends_with(':'),
|
||||||
|
"Directive must include trailing colon");
|
||||||
|
let text = comment.trim_left_matches(';').trim_left();
|
||||||
|
if text.starts_with(directive) {
|
||||||
|
Some(text[directive.len()..].trim())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_match_directive() {
|
||||||
|
assert_eq!(match_directive("; foo: bar ", "foo:"), Some("bar"));
|
||||||
|
assert_eq!(match_directive(" foo:bar", "foo:"), Some("bar"));
|
||||||
|
assert_eq!(match_directive("foo:bar", "foo:"), Some("bar"));
|
||||||
|
assert_eq!(match_directive(";x foo: bar", "foo:"), None);
|
||||||
|
assert_eq!(match_directive(";;; foo: bar", "foo:"), Some("bar"));
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user