1use crate::{
9 mir::{BlockId, Function, InstId, Terminator, ValueId, utils::repair_reachability_phis},
10 pass::FunctionPass,
11 transform::DeadCodeEliminator,
12};
13use solar_data_structures::map::{FxHashMap, FxHashSet};
14
15#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
17pub struct AdceStats {
18 pub control_edges_removed: usize,
20 pub instructions_removed: usize,
22}
23
24impl AdceStats {
25 pub const fn total(self) -> usize {
27 self.control_edges_removed + self.instructions_removed
28 }
29}
30
31#[derive(Debug, Default)]
33pub struct AggressiveDeadCodeEliminator {
34 stats: AdceStats,
35}
36
37pub struct AdcePass;
39
40impl FunctionPass for AdcePass {
41 fn name(&self) -> &str {
42 "adce"
43 }
44
45 fn run_on_function(&mut self, func: &mut Function) -> bool {
46 let changed = AggressiveDeadCodeEliminator::new().run(func).total() != 0;
47 repair_reachability_phis(func);
48 changed
49 }
50}
51
52#[derive(Debug)]
53struct AdceContext {
54 inst_results: FxHashMap<InstId, ValueId>,
55 value_uses: FxHashMap<ValueId, FxHashSet<BlockId>>,
56}
57
58#[derive(Debug, Default)]
60struct TargetSearch {
61 visiting: FxHashSet<BlockId>,
63 targets: FxHashMap<BlockId, Option<BlockId>>,
65}
66
67impl AggressiveDeadCodeEliminator {
68 pub fn new() -> Self {
70 Self::default()
71 }
72
73 pub const fn stats(&self) -> AdceStats {
75 self.stats
76 }
77
78 pub fn run(&mut self, func: &mut Function) -> AdceStats {
80 self.stats = AdceStats::default();
81
82 loop {
83 let ctx = AdceContext::new(func);
84 let rewrites = self.rewrite_dead_control(func, &ctx);
85 if rewrites == 0 {
86 break;
87 }
88 self.stats.control_edges_removed += rewrites;
89 repair_reachability_phis(func);
90 }
91
92 let removed = DeadCodeEliminator::new().run_to_fixpoint(func);
93 self.stats.instructions_removed += removed;
94 self.stats
95 }
96
97 fn rewrite_dead_control(&self, func: &mut Function, ctx: &AdceContext) -> usize {
98 let mut rewrites = Vec::new();
99 let mut search = TargetSearch::default();
100 for block_id in func.blocks.indices() {
101 let Some(term) = &func.blocks[block_id].terminator else {
102 continue;
103 };
104 if !matches!(term, Terminator::Branch { .. } | Terminator::Switch { .. }) {
105 continue;
106 }
107 let Some(target) =
108 self.common_transparent_target(func, ctx, term.successors(), &mut search)
109 else {
110 continue;
111 };
112 if target == block_id || func.block_has_phi(target) {
113 continue;
114 }
115 rewrites.push((block_id, target));
116 }
117
118 for (block_id, target) in &rewrites {
119 self.rewrite_to_jump(func, *block_id, *target);
120 }
121
122 rewrites.len()
123 }
124
125 fn common_transparent_target(
126 &self,
127 func: &Function,
128 ctx: &AdceContext,
129 successors: impl IntoIterator<Item = BlockId>,
130 search: &mut TargetSearch,
131 ) -> Option<BlockId> {
132 let mut common = None;
133 for successor in successors {
134 let target = self.transparent_target(func, ctx, successor, search)?;
135 match common {
136 Some(existing) if existing != target => return None,
137 Some(_) => {}
138 None => common = Some(target),
139 }
140 }
141 common
142 }
143
144 fn transparent_target(
145 &self,
146 func: &Function,
147 ctx: &AdceContext,
148 block_id: BlockId,
149 search: &mut TargetSearch,
150 ) -> Option<BlockId> {
151 if let Some(&target) = search.targets.get(&block_id) {
152 return target;
153 }
154 if !search.visiting.insert(block_id) {
157 return None;
158 }
159 let target = self.compute_transparent_target(func, ctx, block_id, search);
160 search.visiting.remove(&block_id);
161 search.targets.insert(block_id, target);
162 target
163 }
164
165 fn compute_transparent_target(
166 &self,
167 func: &Function,
168 ctx: &AdceContext,
169 block_id: BlockId,
170 search: &mut TargetSearch,
171 ) -> Option<BlockId> {
172 if func.block_has_phi(block_id)
173 || self.block_has_effect(func, block_id)
174 || self.block_def_escapes(func, ctx, block_id)
175 {
176 return Some(block_id);
177 }
178
179 let term = func.blocks[block_id].terminator.as_ref()?;
180 match term {
181 Terminator::Jump(target) => self.transparent_target(func, ctx, *target, search),
182 Terminator::Branch { .. } | Terminator::Switch { .. } => {
183 self.common_transparent_target(func, ctx, term.successors(), search)
184 }
185 Terminator::Return { .. }
186 | Terminator::Revert { .. }
187 | Terminator::ReturnData { .. }
188 | Terminator::Stop
189 | Terminator::SelfDestruct { .. }
190 | Terminator::Invalid => Some(block_id),
191 }
192 }
193
194 fn block_has_effect(&self, func: &Function, block_id: BlockId) -> bool {
195 func.blocks[block_id]
196 .instructions
197 .iter()
198 .any(|&inst_id| func.instructions[inst_id].kind.has_side_effects())
199 }
200
201 fn block_def_escapes(&self, func: &Function, ctx: &AdceContext, block_id: BlockId) -> bool {
202 func.blocks[block_id].instructions.iter().any(|&inst_id| {
203 let Some(&value) = ctx.inst_results.get(&inst_id) else {
204 return false;
205 };
206 ctx.value_uses
207 .get(&value)
208 .is_some_and(|uses| uses.iter().any(|&use_block| use_block != block_id))
209 })
210 }
211
212 fn rewrite_to_jump(&self, func: &mut Function, block_id: BlockId, target: BlockId) {
213 let old_successors = func.blocks[block_id]
214 .terminator
215 .as_ref()
216 .map(|term| term.successors())
217 .unwrap_or_default();
218
219 for successor in old_successors {
220 func.blocks[successor].predecessors.retain(|pred| *pred != block_id);
221 }
222 if !func.blocks[target].predecessors.contains(&block_id) {
223 func.blocks[target].predecessors.push(block_id);
224 }
225
226 func.blocks[block_id].terminator = Some(Terminator::Jump(target));
227 }
228}
229
230impl AdceContext {
231 fn new(func: &Function) -> Self {
232 let inst_results = func.inst_results();
233 let value_uses = Self::value_uses(func);
234 Self { inst_results, value_uses }
235 }
236
237 fn value_uses(func: &Function) -> FxHashMap<ValueId, FxHashSet<BlockId>> {
238 let mut uses: FxHashMap<ValueId, FxHashSet<BlockId>> = FxHashMap::default();
239 for (block_id, block) in func.blocks.iter_enumerated() {
240 for &inst_id in &block.instructions {
241 for operand in func.instructions[inst_id].kind.operands() {
242 uses.entry(operand).or_default().insert(block_id);
243 }
244 }
245 if let Some(term) = &block.terminator {
246 for operand in term.operands() {
247 uses.entry(operand).or_default().insert(block_id);
248 }
249 }
250 }
251 uses
252 }
253}