cranelift_filetests/
test_domtree.rs1use crate::match_directive::match_directive;
16use crate::subtest::{run_filecheck, Context, SubTest};
17use cranelift_codegen::dominator_tree::{DominatorTree, DominatorTreePreorder};
18use cranelift_codegen::flowgraph::ControlFlowGraph;
19use cranelift_codegen::ir::entities::AnyEntity;
20use cranelift_codegen::ir::Function;
21use cranelift_reader::TestCommand;
22use std::borrow::{Borrow, Cow};
23use std::collections::HashMap;
24use std::fmt::{self, Write};
25
26struct TestDomtree;
27
28pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
29 assert_eq!(parsed.command, "domtree");
30 if !parsed.options.is_empty() {
31 anyhow::bail!("No options allowed on {}", parsed)
32 }
33 Ok(Box::new(TestDomtree))
34}
35
36impl SubTest for TestDomtree {
37 fn name(&self) -> &'static str {
38 "domtree"
39 }
40
41 fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()> {
43 let func = func.borrow();
44 let cfg = ControlFlowGraph::with_function(func);
45 let domtree = DominatorTree::with_function(func, &cfg);
46
47 let mut expected = HashMap::new();
49 for comment in &context.details.comments {
50 if let Some(tail) = match_directive(comment.text, "dominates:") {
51 let inst = match comment.entity {
52 AnyEntity::Inst(inst) => inst,
53 _ => {
54 anyhow::bail!(
55 "annotation on non-inst {}: {}",
56 comment.entity,
57 comment.text
58 );
59 }
60 };
61
62 let expected_block = match func.layout.inst_block(inst) {
63 Some(expected_block) => expected_block,
64 _ => anyhow::bail!("instruction {} is not in layout", inst),
65 };
66 for src_block in tail.split_whitespace() {
67 let block = match context.details.map.lookup_str(src_block) {
68 Some(AnyEntity::Block(block)) => block,
69 _ => anyhow::bail!("expected defined block, got {}", src_block),
70 };
71
72 if expected.insert(block, expected_block).is_some() {
74 anyhow::bail!("multiple dominators for {}", src_block);
75 }
76
77 match domtree.idom(block) {
79 Some(got_block) if got_block != expected_block => {
80 anyhow::bail!(
81 "mismatching idoms for {}:\n\
82 want: {}, got: {}",
83 src_block,
84 inst,
85 got_block
86 );
87 }
88 None => {
89 anyhow::bail!(
90 "mismatching idoms for {}:\n\
91 want: {}, got: unreachable",
92 src_block,
93 inst
94 );
95 }
96 _ => {}
97 }
98 }
99 }
100 }
101
102 for block in func
105 .layout
106 .blocks()
107 .skip(1)
108 .filter(|block| !expected.contains_key(block))
109 {
110 if let Some(got_block) = domtree.idom(block) {
111 anyhow::bail!(
112 "mismatching idoms for renumbered {}:\n\
113 want: unreachable, got: {}",
114 block,
115 got_block
116 );
117 }
118 }
119
120 let text = filecheck_text(func, &domtree).expect("formatting error");
121 run_filecheck(&text, context)
122 }
123}
124
125fn filecheck_text(func: &Function, domtree: &DominatorTree) -> Result<String, fmt::Error> {
127 let mut s = String::new();
128
129 write!(s, "cfg_postorder:")?;
130 for &block in domtree.cfg_postorder() {
131 write!(s, " {block}")?;
132 }
133 writeln!(s)?;
134
135 writeln!(s, "domtree_preorder {{")?;
137 let mut dtpo = DominatorTreePreorder::new();
138 dtpo.compute(domtree);
139 let mut stack = Vec::new();
140 stack.extend(func.layout.entry_block());
141 while let Some(block) = stack.pop() {
142 write!(s, " {block}:")?;
143 let i = stack.len();
144 for ch in dtpo.children(block) {
145 write!(s, " {ch}")?;
146 stack.push(ch);
147 }
148 writeln!(s)?;
149 stack[i..].reverse();
151 }
152 writeln!(s, "}}")?;
153
154 Ok(s)
155}