1use crate::cfg::Cfg;
8use llvm_ir::{BlockId, Function};
9use std::collections::HashMap;
10
11pub struct DomTree {
16 idom: Vec<Option<BlockId>>,
19}
20
21impl DomTree {
22 pub fn compute(func: &Function, cfg: &Cfg) -> Self {
25 let n = func.num_blocks();
26 if n == 0 {
27 return DomTree { idom: vec![] };
28 }
29
30 let rpo = cfg.rpo();
32 let mut rpo_idx: Vec<Option<usize>> = vec![None; n];
33 for (i, &bid) in rpo.iter().enumerate() {
34 rpo_idx[bid.0 as usize] = Some(i);
35 }
36
37 const UNDEF: usize = usize::MAX;
39 let mut idom = vec![UNDEF; rpo.len()];
40 idom[0] = 0; let mut changed = true;
43 while changed {
44 changed = false;
45 for i in 1..rpo.len() {
47 let bid = rpo[i];
48 let preds = cfg.predecessors(bid);
49
50 let new_idom_opt = preds
52 .iter()
53 .find_map(|&p| rpo_idx[p.0 as usize].filter(|&pi| idom[pi] != UNDEF));
54
55 if let Some(mut new_idom) = new_idom_opt {
56 for &p in preds {
58 let pi = match rpo_idx[p.0 as usize] {
59 Some(pi) if idom[pi] != UNDEF => pi,
60 _ => continue,
61 };
62 if pi != new_idom {
63 new_idom = Self::intersect(pi, new_idom, &idom);
64 }
65 }
66 if idom[i] != new_idom {
67 idom[i] = new_idom;
68 changed = true;
69 }
70 }
71 }
72 }
73
74 let mut result = vec![None; n];
76 for (i, &bid) in rpo.iter().enumerate() {
77 if i == 0 {
78 result[bid.0 as usize] = None; } else if idom[i] != UNDEF {
80 result[bid.0 as usize] = Some(rpo[idom[i]]);
81 }
82 }
83
84 DomTree { idom: result }
85 }
86
87 fn intersect(mut a: usize, mut b: usize, idom: &[usize]) -> usize {
89 while a != b {
90 while a > b {
91 a = idom[a];
92 }
93 while b > a {
94 b = idom[b];
95 }
96 }
97 a
98 }
99
100 pub fn idom(&self, bid: BlockId) -> Option<BlockId> {
103 self.idom[bid.0 as usize]
104 }
105
106 pub fn dominates(&self, a: BlockId, b: BlockId) -> bool {
109 if a == b {
110 return true;
111 }
112 self.strictly_dominates(a, b)
113 }
114
115 pub fn strictly_dominates(&self, a: BlockId, b: BlockId) -> bool {
118 let mut cur = b;
119 loop {
120 match self.idom[cur.0 as usize] {
121 None => return false,
122 Some(p) if p == a => return true,
123 Some(p) if p == cur => return false, Some(p) => cur = p,
125 }
126 }
127 }
128
129 pub fn dominance_frontier(&self, cfg: &Cfg) -> HashMap<BlockId, Vec<BlockId>> {
134 let mut df: HashMap<BlockId, Vec<BlockId>> = HashMap::new();
135 let n = self.idom.len();
136
137 for y_idx in 0..n {
138 let y = BlockId(y_idx as u32);
139 if self.idom[y_idx].is_none() {
145 continue;
146 }
147 let preds = cfg.predecessors(y);
148 if preds.len() < 2 {
149 continue; }
151 for &p in preds {
152 let mut runner = p;
153 while Some(runner) != self.idom[y_idx] {
154 df.entry(runner).or_default().push(y);
155 match self.idom[runner.0 as usize] {
156 Some(parent) => runner = parent,
157 None => break,
158 }
159 }
160 }
161 }
162
163 for v in df.values_mut() {
165 v.sort_unstable_by_key(|b| b.0);
166 v.dedup();
167 }
168 df
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use llvm_ir::{BasicBlock, Context, Function, InstrKind, Instruction, Linkage, ValueRef};
176
177 fn build_func(num_blocks: usize, edges: &[(usize, Vec<usize>)]) -> (Context, Function) {
178 let mut ctx = Context::new();
179 let fn_ty = ctx.mk_fn_type(ctx.void_ty, vec![], false);
180 let mut func = Function::new("test", fn_ty, vec![], Linkage::External);
181 for i in 0..num_blocks {
182 func.add_block(BasicBlock::new(format!("b{}", i)));
183 }
184 let mut has_term = vec![false; num_blocks];
185 for &(src, ref dsts) in edges {
186 has_term[src] = true;
187 let kind = match dsts.as_slice() {
188 [] => InstrKind::Unreachable,
189 [dst] => InstrKind::Br {
190 dest: BlockId(*dst as u32),
191 },
192 [t, f] => {
193 let cond = ValueRef::Constant(ctx.const_int(ctx.i1_ty, 0));
194 InstrKind::CondBr {
195 cond,
196 then_dest: BlockId(*t as u32),
197 else_dest: BlockId(*f as u32),
198 }
199 }
200 _ => panic!("max 2 successors"),
201 };
202 let iid = func.alloc_instr(Instruction {
203 name: None,
204 ty: ctx.void_ty,
205 kind,
206 });
207 func.blocks[src].set_terminator(iid);
208 }
209 for (i, &needs_term) in has_term.iter().enumerate() {
210 if !needs_term {
211 let iid = func.alloc_instr(Instruction {
212 name: None,
213 ty: ctx.void_ty,
214 kind: InstrKind::Unreachable,
215 });
216 func.blocks[i].set_terminator(iid);
217 }
218 }
219 (ctx, func)
220 }
221
222 #[test]
223 fn domtree_single_block() {
224 let (_ctx, func) = build_func(1, &[(0, vec![])]);
225 let cfg = Cfg::compute(&func);
226 let dom = DomTree::compute(&func, &cfg);
227 assert_eq!(dom.idom(BlockId(0)), None);
228 assert!(dom.dominates(BlockId(0), BlockId(0)));
229 }
230
231 #[test]
232 fn domtree_linear() {
233 let (_ctx, func) = build_func(3, &[(0, vec![1]), (1, vec![2]), (2, vec![])]);
235 let cfg = Cfg::compute(&func);
236 let dom = DomTree::compute(&func, &cfg);
237 assert_eq!(dom.idom(BlockId(0)), None);
238 assert_eq!(dom.idom(BlockId(1)), Some(BlockId(0)));
239 assert_eq!(dom.idom(BlockId(2)), Some(BlockId(1)));
240 assert!(dom.dominates(BlockId(0), BlockId(2)));
241 assert!(!dom.dominates(BlockId(2), BlockId(0)));
242 assert!(dom.strictly_dominates(BlockId(0), BlockId(1)));
243 assert!(!dom.strictly_dominates(BlockId(1), BlockId(1)));
244 }
245
246 #[test]
247 fn domtree_diamond() {
248 let (_ctx, func) = build_func(
250 4,
251 &[(0, vec![1, 2]), (1, vec![3]), (2, vec![3]), (3, vec![])],
252 );
253 let cfg = Cfg::compute(&func);
254 let dom = DomTree::compute(&func, &cfg);
255 assert_eq!(dom.idom(BlockId(3)), Some(BlockId(0)));
257 assert!(dom.dominates(BlockId(0), BlockId(3)));
258 assert!(!dom.dominates(BlockId(1), BlockId(3)));
259 assert!(!dom.dominates(BlockId(2), BlockId(3)));
260 }
261
262 #[test]
263 fn domtree_loop() {
264 let (_ctx, func) = build_func(
266 4,
267 &[(0, vec![1]), (1, vec![2]), (2, vec![1, 3]), (3, vec![])],
268 );
269 let cfg = Cfg::compute(&func);
270 let dom = DomTree::compute(&func, &cfg);
271 assert_eq!(dom.idom(BlockId(1)), Some(BlockId(0)));
272 assert_eq!(dom.idom(BlockId(2)), Some(BlockId(1)));
273 assert_eq!(dom.idom(BlockId(3)), Some(BlockId(2)));
274 assert!(dom.dominates(BlockId(1), BlockId(2)));
276 }
277
278 #[test]
279 fn dominance_frontier_diamond() {
280 let (_ctx, func) = build_func(
282 4,
283 &[(0, vec![1, 2]), (1, vec![3]), (2, vec![3]), (3, vec![])],
284 );
285 let cfg = Cfg::compute(&func);
286 let dom = DomTree::compute(&func, &cfg);
287 let df = dom.dominance_frontier(&cfg);
288 assert_eq!(
290 df.get(&BlockId(1)).map(|v| v.as_slice()),
291 Some(&[BlockId(3)][..])
292 );
293 assert_eq!(
294 df.get(&BlockId(2)).map(|v| v.as_slice()),
295 Some(&[BlockId(3)][..])
296 );
297 assert!(df.get(&BlockId(0)).map_or(true, |v| v.is_empty()));
298 assert!(df.get(&BlockId(3)).map_or(true, |v| v.is_empty()));
299 }
300
301 #[test]
302 fn dominance_frontier_loop() {
303 let (_ctx, func) = build_func(
305 4,
306 &[(0, vec![1]), (1, vec![2]), (2, vec![1, 3]), (3, vec![])],
307 );
308 let cfg = Cfg::compute(&func);
309 let dom = DomTree::compute(&func, &cfg);
310 let df = dom.dominance_frontier(&cfg);
311 assert!(df
312 .get(&BlockId(2))
313 .map_or(false, |v| v.contains(&BlockId(1))));
314 }
315
316 #[test]
317 fn dominance_frontier_unreachable_join_point() {
318 let (_ctx, func) = build_func(4, &[(0, vec![]), (1, vec![3]), (2, vec![3]), (3, vec![])]);
324 let cfg = Cfg::compute(&func);
325 let dom = DomTree::compute(&func, &cfg);
326 let df = dom.dominance_frontier(&cfg);
327 assert!(
329 df.get(&BlockId(1)).map_or(true, |v| v.is_empty()),
330 "spurious DF entry for unreachable block 1: {:?}",
331 df.get(&BlockId(1))
332 );
333 assert!(
334 df.get(&BlockId(2)).map_or(true, |v| v.is_empty()),
335 "spurious DF entry for unreachable block 2: {:?}",
336 df.get(&BlockId(2))
337 );
338 }
339}