sonatina_codegen/optim/
adce.rs

1//! This module contains a solver for `Aggressive Dead code elimination (ADCE)`.
2
3use cranelift_entity::SecondaryMap;
4use std::collections::BTreeSet;
5
6use crate::post_domtree::{PDFSet, PDTIdom, PostDomTree};
7
8use sonatina_ir::{
9    func_cursor::{CursorLocation, FuncCursor, InsnInserter},
10    insn::InsnData,
11    Block, Function, Insn,
12};
13
14pub struct AdceSolver {
15    live_insns: SecondaryMap<Insn, bool>,
16    live_blocks: SecondaryMap<Block, bool>,
17    empty_blocks: BTreeSet<Block>,
18    post_domtree: PostDomTree,
19    worklist: Vec<Insn>,
20}
21
22impl AdceSolver {
23    pub fn new() -> Self {
24        Self {
25            live_insns: SecondaryMap::default(),
26            live_blocks: SecondaryMap::default(),
27            empty_blocks: BTreeSet::default(),
28            post_domtree: PostDomTree::default(),
29            worklist: Vec::default(),
30        }
31    }
32
33    pub fn clear(&mut self) {
34        self.live_insns.clear();
35        self.live_blocks.clear();
36        self.empty_blocks.clear();
37        self.post_domtree.clear();
38        self.worklist.clear();
39    }
40
41    pub fn run(&mut self, func: &mut Function) {
42        while self.run_dce(func) {}
43    }
44
45    /// Returns `true` if branch insn is modified while dead code elimination.
46    fn run_dce(&mut self, func: &mut Function) -> bool {
47        self.clear();
48
49        self.post_domtree.compute(func);
50        let pdf_set = self.post_domtree.compute_df();
51
52        // TODO: We should remove this restriction.
53        // ref: <https://reviews.llvm.org/D35851>
54        if self.has_infinite_loop(func) {
55            return false;
56        }
57
58        for block in func.layout.iter_block() {
59            for insn in func.layout.iter_insn(block) {
60                if func.dfg.has_side_effect(insn) {
61                    self.mark_insn(func, insn);
62                }
63            }
64        }
65
66        while let Some(insn) = self.worklist.pop() {
67            self.mark_by_insn(func, insn, &pdf_set);
68        }
69
70        self.eliminate_dead_code(func)
71    }
72
73    fn has_infinite_loop(&self, func: &Function) -> bool {
74        for block in func.layout.iter_block() {
75            if !self.post_domtree.is_reachable(block) {
76                return true;
77            }
78        }
79
80        false
81    }
82
83    fn mark_insn(&mut self, func: &Function, insn: Insn) {
84        let mut mark_insn = |insn, block| {
85            if !self.does_insn_live(insn) {
86                self.live_insns[insn] = true;
87                self.worklist.push(insn);
88                self.mark_block(block);
89                true
90            } else {
91                false
92            }
93        };
94
95        let insn_block = func.layout.insn_block(insn);
96        if mark_insn(insn, insn_block) {
97            let last_insn = func.layout.last_insn_of(insn_block).unwrap();
98            mark_insn(last_insn, insn_block);
99        }
100    }
101
102    fn mark_block(&mut self, block: Block) {
103        self.live_blocks[block] = true;
104    }
105
106    fn does_insn_live(&self, insn: Insn) -> bool {
107        self.live_insns[insn]
108    }
109
110    fn does_block_live(&self, block: Block) -> bool {
111        self.live_blocks[block]
112    }
113
114    fn mark_by_insn(&mut self, func: &Function, insn: Insn, pdf_set: &PDFSet) {
115        for &value in func.dfg.insn_args(insn) {
116            if let Some(value_insn) = func.dfg.value_insn(value) {
117                self.mark_insn(func, value_insn);
118            }
119        }
120
121        let insn_block = func.layout.insn_block(insn);
122        for &block in pdf_set.frontiers(insn_block) {
123            if let Some(last_insn) = func.layout.last_insn_of(block) {
124                self.mark_insn(func, last_insn)
125            }
126        }
127    }
128
129    fn eliminate_dead_code(&mut self, func: &mut Function) -> bool {
130        let entry = if let Some(entry) = func.layout.entry_block() {
131            entry
132        } else {
133            return false;
134        };
135
136        let mut inserter = InsnInserter::new(func, CursorLocation::BlockTop(entry));
137        loop {
138            match inserter.loc() {
139                CursorLocation::At(insn) => {
140                    if self.does_insn_live(insn) {
141                        inserter.proceed();
142                    } else {
143                        inserter.remove_insn()
144                    }
145                }
146
147                CursorLocation::BlockTop(block) => {
148                    if self.does_block_live(block) {
149                        inserter.proceed()
150                    } else {
151                        inserter.remove_block()
152                    }
153                }
154
155                CursorLocation::BlockBottom(_) => {
156                    inserter.proceed();
157                }
158
159                CursorLocation::NoWhere => break,
160            }
161        }
162
163        // Modify branch insns to remove unreachable edges.
164        inserter.set_to_entry();
165        let mut br_insn_modified = false;
166        while let Some(block) = inserter.block() {
167            br_insn_modified |= self.modify_branch(&mut inserter, block);
168            inserter.proceed_block();
169        }
170
171        br_insn_modified
172    }
173
174    fn living_post_dom(&self, mut block: Block) -> Option<Block> {
175        loop {
176            let idom = self.post_domtree.idom_of(block)?;
177            match idom {
178                PDTIdom::Real(block) if self.does_block_live(block) => return Some(block),
179                PDTIdom::Real(post_dom) => block = post_dom,
180                PDTIdom::DummyExit(_) | PDTIdom::DummyEntry(_) => return None,
181            }
182        }
183    }
184
185    /// Returns `true` if branch insn is modified.
186    fn modify_branch(&self, inserter: &mut InsnInserter, block: Block) -> bool {
187        let last_insn = match inserter.func().layout.last_insn_of(block) {
188            Some(insn) => insn,
189            None => return false,
190        };
191        inserter.set_loc(CursorLocation::At(last_insn));
192
193        let dests: Vec<_> = inserter
194            .func()
195            .dfg
196            .analyze_branch(last_insn)
197            .iter_dests()
198            .collect();
199
200        let mut changed = false;
201        for dest in dests {
202            if self.does_block_live(dest) {
203                continue;
204            }
205
206            match self.living_post_dom(dest) {
207                // If the destination is dead but its post dominator is living, then change the
208                // destination to the post dominator.
209                Some(postdom) => inserter
210                    .func_mut()
211                    .dfg
212                    .rewrite_branch_dest(last_insn, dest, postdom),
213
214                // If the block doesn't have post dominator, then remove the dest.
215                None => {
216                    inserter.func_mut().dfg.remove_branch_dest(last_insn, dest);
217                }
218            }
219
220            changed = true;
221        }
222
223        // Turn branch insn to `jump` if all dests is the same.
224        let branch_info = inserter.func().dfg.analyze_branch(last_insn);
225        if branch_info.dests_num() > 1 {
226            let mut branch_dests = branch_info.iter_dests();
227            let first_dest = branch_dests.next().unwrap();
228            if branch_dests.all(|dest| dest == first_dest) {
229                changed = true;
230                inserter.replace(InsnData::jump(first_dest));
231            }
232        }
233
234        changed
235    }
236}
237
238impl Default for AdceSolver {
239    fn default() -> Self {
240        Self::new()
241    }
242}