diff --git a/filetests/domtree/basic.cton b/filetests/domtree/basic.cton new file mode 100644 index 0000000000..0b8536effd --- /dev/null +++ b/filetests/domtree/basic.cton @@ -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 +} diff --git a/src/tools/tests/dominator_tree_testdata/loops.cton b/filetests/domtree/loops.cton similarity index 63% rename from src/tools/tests/dominator_tree_testdata/loops.cton rename to filetests/domtree/loops.cton index 87d7780d7b..cdc88544cc 100644 --- a/src/tools/tests/dominator_tree_testdata/loops.cton +++ b/filetests/domtree/loops.cton @@ -1,7 +1,9 @@ +test domtree + function test(i32) { - ebb0(v0: i32): ; dominates(0) - brz v0, ebb1 ; dominates(1,3,4,5) - jump ebb2 ; dominates(2) + ebb0(v0: i32): + brz v0, ebb1 ; dominates: ebb1 ebb3 ebb4 ebb5 + jump ebb2 ; dominates: ebb2 ebb1: jump ebb3 ebb2: diff --git a/src/tools/tests/dominator_tree_testdata/loops2.cton b/filetests/domtree/loops2.cton similarity index 55% rename from src/tools/tests/dominator_tree_testdata/loops2.cton rename to filetests/domtree/loops2.cton index 452641be01..d2e3ba4f2c 100644 --- a/src/tools/tests/dominator_tree_testdata/loops2.cton +++ b/filetests/domtree/loops2.cton @@ -1,13 +1,15 @@ +test domtree + function test(i32) { - ebb0(v0: i32): ; dominates(0) - brz v0, ebb1 ; dominates(1,6) - brnz v0, ebb2 ; dominates(2,9) - jump ebb3 ; dominates(3) + ebb0(v0: i32): + brz v0, ebb1 ; dominates: ebb1 ebb6 + brnz v0, ebb2 ; dominates: ebb2 ebb9 + jump ebb3 ; dominates: ebb3 ebb1: jump ebb6 ebb2: - brz v0, ebb4 ; dominates(4,7,8) - jump ebb5 ; dominates(5) + brz v0, ebb4 ; dominates: ebb4 ebb7 ebb8 + jump ebb5 ; dominates: ebb5 ebb3: jump ebb9 ebb4: diff --git a/filetests/domtree/tall-tree.cton b/filetests/domtree/tall-tree.cton new file mode 100644 index 0000000000..3dd6f51da6 --- /dev/null +++ b/filetests/domtree/tall-tree.cton @@ -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 +} diff --git a/filetests/domtree/wide-tree.cton b/filetests/domtree/wide-tree.cton new file mode 100644 index 0000000000..f4e822cd81 --- /dev/null +++ b/filetests/domtree/wide-tree.cton @@ -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 +} diff --git a/src/tools/filetest/domtree.rs b/src/tools/filetest/domtree.rs new file mode 100644 index 0000000000..306fc433e9 --- /dev/null +++ b/src/tools/filetest/domtree.rs @@ -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> { + 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 { + Cow::from("domtree") + } + + // Extract our own dominator tree from + fn run(&self, func: Cow, 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(()) + } +} diff --git a/src/tools/filetest/mod.rs b/src/tools/filetest/mod.rs index c1019446ca..0a7bdf0bab 100644 --- a/src/tools/filetest/mod.rs +++ b/src/tools/filetest/mod.rs @@ -9,6 +9,7 @@ use filetest::runner::TestRunner; pub mod subtest; mod runner; +mod domtree; /// Main entry point for `cton-util test`. /// diff --git a/src/tools/filetest/subtest.rs b/src/tools/filetest/subtest.rs index 89a8b830e1..0359265d3b 100644 --- a/src/tools/filetest/subtest.rs +++ b/src/tools/filetest/subtest.rs @@ -12,9 +12,11 @@ pub type Result = result::Result; pub fn new(parsed: &TestCommand) -> Result> { use cat; use print_cfg; + use filetest::domtree; match parsed.command { "cat" => cat::subtest(parsed), "print-cfg" => print_cfg::subtest(parsed), + "domtree" => domtree::subtest(parsed), _ => Err(format!("unknown test command '{}'", parsed.command)), } } diff --git a/src/tools/tests/dominator_tree.rs b/src/tools/tests/dominator_tree.rs deleted file mode 100644 index 71c02dc652..0000000000 --- a/src/tools/tests/dominator_tree.rs +++ /dev/null @@ -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::>(); - 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::>(); - - 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::>(); - 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::().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); - } -} diff --git a/src/tools/tests/dominator_tree_testdata/basic.cton b/src/tools/tests/dominator_tree_testdata/basic.cton deleted file mode 100644 index c274336b79..0000000000 --- a/src/tools/tests/dominator_tree_testdata/basic.cton +++ /dev/null @@ -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 -} diff --git a/src/tools/tests/dominator_tree_testdata/tall-tree.cton b/src/tools/tests/dominator_tree_testdata/tall-tree.cton deleted file mode 100644 index d2ea0f8a9e..0000000000 --- a/src/tools/tests/dominator_tree_testdata/tall-tree.cton +++ /dev/null @@ -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 -} diff --git a/src/tools/tests/dominator_tree_testdata/wide-tree.cton b/src/tools/tests/dominator_tree_testdata/wide-tree.cton deleted file mode 100644 index 0883ef6514..0000000000 --- a/src/tools/tests/dominator_tree_testdata/wide-tree.cton +++ /dev/null @@ -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 -} diff --git a/src/tools/utils.rs b/src/tools/utils.rs index 5973f25814..f89bc46b43 100644 --- a/src/tools/utils.rs +++ b/src/tools/utils.rs @@ -11,3 +11,29 @@ pub fn read_to_string>(path: P) -> Result { try!(file.read_to_string(&mut 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")); +}