sonatina_codegen/
post_domtree.rs

1//! This module contains implementation of `Post Dominator Tree`.
2
3use super::{
4    cfg::ControlFlowGraph,
5    domtree::{DFSet, DomTree},
6};
7
8use sonatina_ir::{Block, Function};
9
10#[derive(Debug)]
11pub struct PostDomTree {
12    /// Dummy entry block to calculate CDG.
13    entry: Block,
14    /// Canonical dummy exit block to calculate CDG. All blocks ends with `return` has an edge to
15    /// this block.
16    exit: Block,
17
18    /// Reverse control flow graph of the function.
19    rcfg: ControlFlowGraph,
20
21    /// Dominator tree of reverse control flow graph.
22    domtree: DomTree,
23}
24
25impl Default for PostDomTree {
26    fn default() -> Self {
27        Self {
28            entry: Block(0),
29            exit: Block(0),
30            rcfg: ControlFlowGraph::default(),
31            domtree: DomTree::default(),
32        }
33    }
34}
35
36impl PostDomTree {
37    pub fn new() -> Self {
38        Self::default()
39    }
40
41    pub fn compute(&mut self, func: &Function) {
42        self.clear();
43
44        self.rcfg.compute(func);
45        if self.rcfg.entry().is_none() {
46            return;
47        }
48        let real_entry = self.rcfg.entry().unwrap();
49
50        self.entry = Block(func.dfg.blocks.len() as u32);
51        self.exit = Block(self.entry.0 + 1);
52
53        // Add edges from dummy entry block to real entry block and dummy exit block.
54        self.rcfg.add_edge(self.entry, real_entry);
55        self.rcfg.add_edge(self.entry, self.exit);
56
57        // Add edges from real exit blocks to dummy exit block.
58        let real_exits = std::mem::take(&mut self.rcfg.exits);
59        for exit in &real_exits {
60            self.rcfg.add_edge(*exit, self.exit);
61        }
62
63        self.rcfg.reverse_edges(self.exit, &[self.entry]);
64        self.domtree.compute(&self.rcfg);
65    }
66
67    pub fn idom_of(&self, block: Block) -> Option<PDTIdom> {
68        match self.domtree.idom_of(block)? {
69            block if block == self.entry => Some(PDTIdom::DummyEntry(self.entry)),
70            block if block == self.exit => Some(PDTIdom::DummyExit(self.exit)),
71            other => Some(PDTIdom::Real(other)),
72        }
73    }
74
75    pub fn clear(&mut self) {
76        self.rcfg.clear();
77        self.domtree.clear();
78    }
79
80    /// Compute post dominance frontiers of each blocks.
81    pub fn compute_df(&self) -> PDFSet {
82        let df_set = self.domtree.compute_df(&self.rcfg);
83
84        PDFSet {
85            entry: self.entry,
86            exit: self.exit,
87            df_set,
88        }
89    }
90
91    /// Returns `true` if block is reachable from the exit blocks.
92    pub fn is_reachable(&self, block: Block) -> bool {
93        self.domtree.is_reachable(block)
94    }
95}
96
97#[derive(Debug, Clone, Copy)]
98pub enum PDTIdom {
99    DummyEntry(Block),
100    DummyExit(Block),
101    Real(Block),
102}
103
104/// Post Dominance frontiers of each blocks.
105#[derive(Debug)]
106pub struct PDFSet {
107    /// Dummy entry block of the post dominator tree.
108    entry: Block,
109
110    /// Canonical dummy exit block of the post dominator tree.
111    exit: Block,
112
113    df_set: DFSet,
114}
115
116impl PDFSet {
117    pub fn frontiers(&self, block: Block) -> impl Iterator<Item = &Block> {
118        self.df_set
119            .frontiers(block)
120            .filter(|block| **block != self.entry && **block != self.exit)
121    }
122
123    pub fn in_frontier_of(&self, block: Block, of: Block) -> bool {
124        self.df_set.in_frontier_of(block, of)
125    }
126
127    pub fn frontier_num_of(&self, of: Block) -> usize {
128        self.frontiers(of).count()
129    }
130
131    pub fn clear(&mut self) {
132        self.df_set.clear();
133    }
134}
135
136impl Default for PDFSet {
137    fn default() -> Self {
138        Self {
139            entry: Block(0),
140            exit: Block(0),
141            df_set: DFSet::default(),
142        }
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    #![allow(clippy::many_single_char_names)]
149
150    use super::*;
151    use sonatina_ir::{builder::test_util::*, Type};
152
153    fn calc_dom(func: &Function) -> (PostDomTree, PDFSet) {
154        let mut post_dom_tree = PostDomTree::new();
155        post_dom_tree.compute(func);
156        let pdf = post_dom_tree.compute_df();
157        (post_dom_tree, pdf)
158    }
159
160    fn test_pdf(pdf: &PDFSet, of: Block, frontieres: &[Block]) -> bool {
161        if pdf.frontier_num_of(of) != frontieres.len() {
162            return false;
163        }
164
165        for &block in frontieres {
166            if !pdf.in_frontier_of(block, of) {
167                return false;
168            }
169        }
170
171        true
172    }
173
174    #[test]
175    fn pd_if_else() {
176        let mut test_module_builder = TestModuleBuilder::new();
177        let mut builder = test_module_builder.func_builder(&[Type::I64], Type::Void);
178
179        let entry_block = builder.append_block();
180        let then_block = builder.append_block();
181        let else_block = builder.append_block();
182        let merge_block = builder.append_block();
183
184        let arg0 = builder.args()[0];
185
186        builder.switch_to_block(entry_block);
187        builder.br(arg0, then_block, else_block);
188
189        builder.switch_to_block(then_block);
190        let v1 = builder.make_imm_value(1i64);
191        builder.jump(merge_block);
192
193        builder.switch_to_block(else_block);
194        let v2 = builder.make_imm_value(2i64);
195        builder.jump(merge_block);
196
197        builder.switch_to_block(merge_block);
198        let v3 = builder.phi(&[(v1, then_block), (v2, else_block)]);
199        builder.add(v3, arg0);
200        builder.ret(None);
201
202        builder.seal_all();
203        let func_ref = builder.finish();
204
205        let module = test_module_builder.build();
206        let func = &module.funcs[func_ref];
207        let (post_dom_tree, pdf) = calc_dom(func);
208
209        assert!(post_dom_tree.is_reachable(entry_block));
210        assert!(post_dom_tree.is_reachable(else_block));
211        assert!(post_dom_tree.is_reachable(then_block));
212        assert!(post_dom_tree.is_reachable(merge_block));
213
214        assert!(test_pdf(&pdf, entry_block, &[]));
215        assert!(test_pdf(&pdf, then_block, &[entry_block]));
216        assert!(test_pdf(&pdf, else_block, &[entry_block]));
217        assert!(test_pdf(&pdf, merge_block, &[]));
218    }
219
220    #[test]
221    fn infinite_loop() {
222        let mut test_module_builder = TestModuleBuilder::new();
223        let mut builder = test_module_builder.func_builder(&[], Type::Void);
224
225        let a = builder.append_block();
226        builder.switch_to_block(a);
227        builder.jump(a);
228
229        builder.seal_all();
230        let func_ref = builder.finish();
231
232        let module = test_module_builder.build();
233        let func = &module.funcs[func_ref];
234        let (post_dom_tree, pdf) = calc_dom(func);
235
236        assert!(!post_dom_tree.is_reachable(a));
237        assert!(test_pdf(&pdf, a, &[]));
238    }
239
240    #[test]
241    fn test_multiple_return() {
242        let mut test_module_builder = TestModuleBuilder::new();
243        let mut builder = test_module_builder.func_builder(&[], Type::Void);
244
245        let a = builder.append_block();
246        let b = builder.append_block();
247        let c = builder.append_block();
248        let d = builder.append_block();
249        let e = builder.append_block();
250
251        builder.switch_to_block(a);
252        let v0 = builder.make_imm_value(1i8);
253        builder.br(v0, b, c);
254
255        builder.switch_to_block(b);
256        builder.ret(None);
257
258        builder.switch_to_block(c);
259        builder.br(v0, d, e);
260
261        builder.switch_to_block(d);
262        builder.ret(None);
263
264        builder.switch_to_block(e);
265        builder.ret(None);
266
267        builder.seal_all();
268        let func_ref = builder.finish();
269
270        let module = test_module_builder.build();
271        let func = &module.funcs[func_ref];
272        let (post_dom_tree, pdf) = calc_dom(func);
273
274        assert!(post_dom_tree.is_reachable(a));
275        assert!(post_dom_tree.is_reachable(b));
276        assert!(post_dom_tree.is_reachable(c));
277        assert!(post_dom_tree.is_reachable(d));
278        assert!(post_dom_tree.is_reachable(e));
279
280        assert!(test_pdf(&pdf, a, &[]));
281        assert!(test_pdf(&pdf, b, &[a]));
282        assert!(test_pdf(&pdf, c, &[a]));
283        assert!(test_pdf(&pdf, d, &[c]));
284        assert!(test_pdf(&pdf, e, &[c]));
285    }
286
287    #[test]
288    fn pd_complex() {
289        let mut test_module_builder = TestModuleBuilder::new();
290        let mut builder = test_module_builder.func_builder(&[], Type::Void);
291
292        let a = builder.append_block();
293        let b = builder.append_block();
294        let c = builder.append_block();
295        let d = builder.append_block();
296        let e = builder.append_block();
297        let f = builder.append_block();
298        let g = builder.append_block();
299        let h = builder.append_block();
300
301        builder.switch_to_block(a);
302        let v0 = builder.make_imm_value(1i8);
303        builder.br(v0, b, c);
304
305        builder.switch_to_block(b);
306        builder.jump(g);
307
308        builder.switch_to_block(c);
309        builder.br(v0, d, e);
310
311        builder.switch_to_block(d);
312        builder.jump(f);
313
314        builder.switch_to_block(e);
315        builder.jump(f);
316
317        builder.switch_to_block(f);
318        builder.jump(g);
319
320        builder.switch_to_block(g);
321        builder.br(v0, a, h);
322
323        builder.switch_to_block(h);
324        builder.ret(None);
325
326        builder.seal_all();
327        let func_ref = builder.finish();
328
329        let module = test_module_builder.build();
330        let func = &module.funcs[func_ref];
331        let (post_dom_tree, pdf) = calc_dom(func);
332
333        assert!(post_dom_tree.is_reachable(a));
334        assert!(post_dom_tree.is_reachable(b));
335        assert!(post_dom_tree.is_reachable(c));
336        assert!(post_dom_tree.is_reachable(d));
337        assert!(post_dom_tree.is_reachable(e));
338        assert!(post_dom_tree.is_reachable(f));
339        assert!(post_dom_tree.is_reachable(g));
340        assert!(post_dom_tree.is_reachable(h));
341
342        assert!(test_pdf(&pdf, a, &[g]));
343        assert!(test_pdf(&pdf, b, &[a]));
344        assert!(test_pdf(&pdf, c, &[a]));
345        assert!(test_pdf(&pdf, d, &[c]));
346        assert!(test_pdf(&pdf, e, &[c]));
347        assert!(test_pdf(&pdf, f, &[a]));
348        assert!(test_pdf(&pdf, g, &[g]));
349        assert!(test_pdf(&pdf, h, &[]));
350    }
351}