sonatina_codegen/
critical_edge.rs

1use super::cfg::ControlFlowGraph;
2
3use sonatina_ir::{
4    func_cursor::{CursorLocation, FuncCursor, InsnInserter},
5    insn::InsnData,
6    Block, Function, Insn,
7};
8
9#[derive(Debug)]
10pub struct CriticalEdgeSplitter {
11    critical_edges: Vec<CriticalEdge>,
12}
13
14impl Default for CriticalEdgeSplitter {
15    fn default() -> Self {
16        Self::new()
17    }
18}
19
20impl CriticalEdgeSplitter {
21    pub fn new() -> Self {
22        Self {
23            critical_edges: Vec::default(),
24        }
25    }
26
27    pub fn run(&mut self, func: &mut Function, cfg: &mut ControlFlowGraph) {
28        self.clear();
29
30        for block in func.layout.iter_block() {
31            if let Some(last_insn) = func.layout.last_insn_of(block) {
32                self.add_critical_edges(last_insn, func, cfg);
33            }
34        }
35
36        let edges = std::mem::take(&mut self.critical_edges);
37        for edge in edges {
38            self.split_edge(edge, func, cfg);
39        }
40    }
41
42    pub fn clear(&mut self) {
43        self.critical_edges.clear();
44    }
45
46    fn add_critical_edges(&mut self, insn: Insn, func: &Function, cfg: &ControlFlowGraph) {
47        let branch_info = func.dfg.analyze_branch(insn);
48        if branch_info.dests_num() < 2 {
49            return;
50        }
51
52        for dest in branch_info.iter_dests() {
53            if cfg.pred_num_of(dest) > 1 {
54                self.critical_edges.push(CriticalEdge::new(insn, dest));
55            }
56        }
57    }
58
59    fn split_edge(&mut self, edge: CriticalEdge, func: &mut Function, cfg: &mut ControlFlowGraph) {
60        let insn = edge.insn;
61        let original_dest = edge.to;
62        let source_block = func.layout.insn_block(insn);
63
64        // Create a new block that contains only a jump insn to the destinating block of the
65        // critical edge.
66        let inserted_dest = func.dfg.make_block();
67        let jump = func.dfg.make_insn(InsnData::jump(original_dest));
68        let mut cursor = InsnInserter::new(func, CursorLocation::BlockTop(original_dest));
69        cursor.append_block(inserted_dest);
70        cursor.set_loc(CursorLocation::BlockTop(inserted_dest));
71        cursor.append_insn(jump);
72
73        // Rewrite branch destination to the new block.
74        func.dfg
75            .rewrite_branch_dest(insn, original_dest, inserted_dest);
76        self.modify_cfg(cfg, source_block, original_dest, inserted_dest);
77        self.modify_phi_blocks(func, original_dest, inserted_dest);
78    }
79
80    fn modify_phi_blocks(&self, func: &mut Function, original_dest: Block, inserted_dest: Block) {
81        for insn in func.layout.iter_insn(original_dest) {
82            if !func.dfg.is_phi(insn) {
83                continue;
84            }
85
86            for block in func.dfg.phi_blocks_mut(insn) {
87                if *block == original_dest {
88                    *block = inserted_dest;
89                }
90            }
91        }
92    }
93
94    fn modify_cfg(
95        &self,
96        cfg: &mut ControlFlowGraph,
97        source_block: Block,
98        original_dest: Block,
99        inserted_dest: Block,
100    ) {
101        cfg.remove_edge(source_block, original_dest);
102        cfg.add_edge(source_block, inserted_dest);
103        cfg.add_edge(inserted_dest, original_dest);
104    }
105}
106
107#[derive(Debug)]
108struct CriticalEdge {
109    insn: Insn,
110    to: Block,
111}
112
113impl CriticalEdge {
114    fn new(insn: Insn, to: Block) -> Self {
115        Self { insn, to }
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use sonatina_ir::{builder::test_util::*, Type};
123
124    #[test]
125    fn critical_edge_basic() {
126        let mut test_module_builder = TestModuleBuilder::new();
127        let mut builder = test_module_builder.func_builder(&[], Type::Void);
128
129        let a = builder.append_block();
130        let b = builder.append_block();
131        let c = builder.append_block();
132
133        builder.switch_to_block(a);
134        let v0 = builder.make_imm_value(1i32);
135        builder.br(v0, c, b);
136
137        builder.switch_to_block(b);
138        builder.jump(c);
139
140        builder.switch_to_block(c);
141        builder.ret(None);
142
143        builder.seal_all();
144        let func_ref = builder.finish();
145
146        let mut module = test_module_builder.build();
147        let func = &mut module.funcs[func_ref];
148        let mut cfg = ControlFlowGraph::default();
149        cfg.compute(func);
150        CriticalEdgeSplitter::new().run(func, &mut cfg);
151
152        assert_eq!(
153            dump_func(func),
154            "func public %test_func() -> void:
155    block0:
156        br 1.i32 block3 block1;
157
158    block1:
159        jump block2;
160
161    block2:
162        return;
163
164    block3:
165        jump block2;
166
167"
168        );
169
170        let mut cfg_split = ControlFlowGraph::default();
171        cfg_split.compute(func);
172        assert_eq!(cfg, cfg_split);
173    }
174
175    #[test]
176    #[allow(clippy::many_single_char_names)]
177    fn critical_edge_to_same_block() {
178        let mut test_module_builder = TestModuleBuilder::new();
179        let mut builder = test_module_builder.func_builder(&[], Type::Void);
180
181        let a = builder.append_block();
182        let b = builder.append_block();
183        let c = builder.append_block();
184        let d = builder.append_block();
185        let e = builder.append_block();
186
187        builder.switch_to_block(a);
188        let v0 = builder.make_imm_value(1i8);
189        builder.br(v0, d, b);
190
191        builder.switch_to_block(b);
192        builder.jump(c);
193
194        builder.switch_to_block(c);
195        builder.br(v0, e, d);
196
197        builder.switch_to_block(d);
198        builder.ret(None);
199
200        builder.switch_to_block(e);
201        builder.ret(None);
202
203        builder.seal_all();
204        let func_ref = builder.finish();
205
206        let mut module = test_module_builder.build();
207        let func = &mut module.funcs[func_ref];
208        let mut cfg = ControlFlowGraph::default();
209        cfg.compute(func);
210        CriticalEdgeSplitter::new().run(func, &mut cfg);
211
212        assert_eq!(
213            dump_func(func),
214            "func public %test_func() -> void:
215    block0:
216        br 1.i8 block5 block1;
217
218    block1:
219        jump block2;
220
221    block2:
222        br 1.i8 block4 block6;
223
224    block3:
225        return;
226
227    block4:
228        return;
229
230    block5:
231        jump block3;
232
233    block6:
234        jump block3;
235
236"
237        );
238
239        let mut cfg_split = ControlFlowGraph::default();
240        cfg_split.compute(func);
241        assert_eq!(cfg, cfg_split);
242    }
243
244    #[test]
245    fn critical_edge_phi() {
246        let mut test_module_builder = TestModuleBuilder::new();
247        let mut builder = test_module_builder.func_builder(&[], Type::Void);
248
249        let a = builder.append_block();
250        let b = builder.append_block();
251        let c = builder.append_block();
252
253        builder.switch_to_block(a);
254        let v1 = builder.make_imm_value(1i8);
255        builder.jump(b);
256
257        builder.switch_to_block(b);
258        let phi_value = builder.phi(&[(v1, a)]);
259        let v2 = builder.add(phi_value, v1);
260        builder.append_phi_arg(phi_value, v2, b);
261        builder.br(phi_value, c, b);
262
263        builder.switch_to_block(c);
264        builder.ret(None);
265
266        builder.seal_all();
267        let func_ref = builder.finish();
268
269        let mut module = test_module_builder.build();
270        let func = &mut module.funcs[func_ref];
271        let mut cfg = ControlFlowGraph::default();
272        cfg.compute(func);
273        CriticalEdgeSplitter::new().run(func, &mut cfg);
274
275        assert_eq!(
276            dump_func(func),
277            "func public %test_func() -> void:
278    block0:
279        jump block1;
280
281    block1:
282        v1.i8 = phi (1.i8 block0) (v2 block3);
283        v2.i8 = add v1 1.i8;
284        br v1 block2 block3;
285
286    block2:
287        return;
288
289    block3:
290        jump block1;
291
292"
293        );
294
295        let mut cfg_split = ControlFlowGraph::default();
296        cfg_split.compute(func);
297        assert_eq!(cfg, cfg_split);
298    }
299
300    #[test]
301    fn critical_edge_br_table() {
302        let mut test_module_builder = TestModuleBuilder::new();
303        let mut builder = test_module_builder.func_builder(&[], Type::Void);
304
305        let a = builder.append_block();
306        let b = builder.append_block();
307        let c = builder.append_block();
308        let d = builder.append_block();
309        let e = builder.append_block();
310
311        builder.switch_to_block(a);
312        let cond = builder.make_imm_value(true);
313        builder.br(cond, b, e);
314
315        builder.switch_to_block(b);
316        let v0 = builder.make_imm_value(0i32);
317        let v1 = builder.make_imm_value(1i32);
318        let v2 = builder.make_imm_value(2i32);
319        builder.br_table(v0, Some(c), &[(v1, d), (v2, e)]);
320
321        builder.switch_to_block(c);
322        builder.jump(b);
323
324        builder.switch_to_block(d);
325        builder.ret(None);
326
327        builder.switch_to_block(e);
328        builder.ret(None);
329
330        builder.seal_all();
331        let func_ref = builder.finish();
332
333        let mut module = test_module_builder.build();
334        let func = &mut module.funcs[func_ref];
335        let mut cfg = ControlFlowGraph::default();
336        cfg.compute(func);
337        CriticalEdgeSplitter::new().run(func, &mut cfg);
338
339        assert_eq!(
340            dump_func(func),
341            "func public %test_func() -> void:
342    block0:
343        br -1.i1 block5 block6;
344
345    block1:
346        br_table 0.i32 block2 (1.i32 block3) (2.i32 block7);
347
348    block2:
349        jump block1;
350
351    block3:
352        return;
353
354    block4:
355        return;
356
357    block5:
358        jump block1;
359
360    block6:
361        jump block4;
362
363    block7:
364        jump block4;
365
366"
367        );
368
369        let mut cfg_split = ControlFlowGraph::default();
370        cfg_split.compute(func);
371        assert_eq!(cfg, cfg_split);
372    }
373}