solar_codegen/transform/
dce.rs1use crate::{
6 analysis::CfgInfo,
7 mir::{BlockId, Function, InstId, Terminator, Value, ValueId, utils::repair_reachability_phis},
8 pass::FunctionPass,
9};
10use solar_data_structures::map::{FxHashMap, FxHashSet};
11
12#[derive(Debug, Default)]
22pub struct DeadCodeEliminator {
23 pub eliminated_count: usize,
25 pub blocks_removed: usize,
27 pub unused_params: usize,
29}
30
31#[derive(Debug, Default, Clone, Copy)]
33pub struct DceStats {
34 pub dead_instructions: usize,
36 pub unreachable_blocks: usize,
38 pub unused_parameters: usize,
40}
41
42pub struct DcePass;
44
45impl FunctionPass for DcePass {
46 fn name(&self) -> &str {
47 "dce"
48 }
49
50 fn run_on_function(&mut self, func: &mut Function) -> bool {
51 let changed = DeadCodeEliminator::new().run_to_fixpoint(func) != 0;
52 repair_reachability_phis(func);
53 changed
54 }
55}
56
57impl DceStats {
58 pub fn total(&self) -> usize {
60 self.dead_instructions + self.unreachable_blocks + self.unused_parameters
61 }
62}
63
64impl DeadCodeEliminator {
65 pub fn new() -> Self {
67 Self::default()
68 }
69
70 pub fn run(&mut self, func: &mut Function) -> usize {
73 self.eliminated_count = 0;
74 self.blocks_removed = 0;
75 self.unused_params = 0;
76
77 self.eliminate_unreachable_blocks(func);
79
80 self.unused_params = self.find_unused_parameters(func).len();
82
83 let inst_to_value: FxHashMap<InstId, ValueId> = func
86 .values
87 .iter_enumerated()
88 .filter_map(
89 |(vid, val)| {
90 if let Value::Inst(iid) = val { Some((*iid, vid)) } else { None }
91 },
92 )
93 .collect();
94
95 let used_values = self.collect_used_values(func);
97
98 let dead_instructions = self.find_dead_instructions(func, &used_values, &inst_to_value);
100
101 for (block_id, inst_id) in &dead_instructions {
103 let block = func.block_mut(*block_id);
104 block.instructions.retain(|&id| id != *inst_id);
105 self.eliminated_count += 1;
106 }
107
108 self.eliminated_count
109 }
110
111 pub fn run_with_stats(&mut self, func: &mut Function) -> DceStats {
113 let eliminated = self.run(func);
114 DceStats {
115 dead_instructions: eliminated,
116 unreachable_blocks: self.blocks_removed,
117 unused_parameters: self.unused_params,
118 }
119 }
120
121 pub fn run_to_fixpoint(&mut self, func: &mut Function) -> usize {
123 let mut total_eliminated = 0;
124 loop {
125 let eliminated = self.run(func);
126 if eliminated == 0 {
127 break;
128 }
129 total_eliminated += eliminated;
130 }
131 total_eliminated
132 }
133
134 fn eliminate_unreachable_blocks(&mut self, func: &mut Function) {
136 let cfg = CfgInfo::new(func);
137 let reachable = cfg.reachable();
138
139 let unreachable: Vec<BlockId> = func
141 .blocks
142 .iter_enumerated()
143 .filter_map(|(id, _)| if !reachable.contains(&id) { Some(id) } else { None })
144 .collect();
145
146 self.blocks_removed = unreachable.len();
147
148 for block_id in &unreachable {
151 let block = func.block_mut(*block_id);
152 block.instructions.clear();
153 block.terminator = Some(Terminator::Invalid);
154 block.predecessors.clear();
155 }
156 }
157
158 pub fn find_unused_parameters(&self, func: &Function) -> Vec<u32> {
161 let mut used_args = FxHashSet::default();
163
164 for (_, block) in func.blocks.iter_enumerated() {
166 for &inst_id in &block.instructions {
167 let inst = &func.instructions[inst_id];
168 for val_id in inst.kind.operands() {
169 if let Value::Arg { index, .. } = &func.values[val_id] {
170 used_args.insert(*index);
171 }
172 }
173 }
174
175 if let Some(ref term) = block.terminator {
177 for val_id in self.get_terminator_operands(term) {
178 if let Value::Arg { index, .. } = &func.values[val_id] {
179 used_args.insert(*index);
180 }
181 }
182 }
183 }
184
185 (0..func.params.len() as u32).filter(|idx| !used_args.contains(idx)).collect()
187 }
188
189 fn get_terminator_operands(&self, term: &Terminator) -> Vec<ValueId> {
191 match term {
192 Terminator::Jump(_) | Terminator::Stop | Terminator::Invalid => vec![],
193 Terminator::Branch { condition, .. } => vec![*condition],
194 Terminator::Switch { value, cases, .. } => {
195 let mut ops = vec![*value];
196 for (case_val, _) in cases {
197 ops.push(*case_val);
198 }
199 ops
200 }
201 Terminator::Return { values } => values.to_vec(),
202 Terminator::Revert { offset, size } | Terminator::ReturnData { offset, size } => {
203 vec![*offset, *size]
204 }
205 Terminator::SelfDestruct { recipient } => vec![*recipient],
206 }
207 }
208
209 fn collect_used_values(&self, func: &Function) -> FxHashSet<ValueId> {
211 let mut used = FxHashSet::default();
212
213 for (_, block) in func.blocks.iter_enumerated() {
215 if let Some(term) = &block.terminator {
216 self.collect_terminator_uses(term, &mut used);
217 }
218 }
219
220 for (_, block) in func.blocks.iter_enumerated() {
222 for &inst_id in &block.instructions {
223 let inst = &func.instructions[inst_id];
224 for val in inst.kind.operands() {
225 used.insert(val);
226 }
227 }
228 }
229
230 used
231 }
232
233 fn collect_terminator_uses(&self, term: &Terminator, used: &mut FxHashSet<ValueId>) {
235 match term {
236 Terminator::Jump(_) | Terminator::Stop | Terminator::Invalid => {}
237 Terminator::Branch { condition, .. } => {
238 used.insert(*condition);
239 }
240 Terminator::Switch { value, cases, .. } => {
241 used.insert(*value);
242 for (case_val, _) in cases {
243 used.insert(*case_val);
244 }
245 }
246 Terminator::Return { values } => {
247 for val in values {
248 used.insert(*val);
249 }
250 }
251 Terminator::Revert { offset, size } | Terminator::ReturnData { offset, size } => {
252 used.insert(*offset);
253 used.insert(*size);
254 }
255 Terminator::SelfDestruct { recipient } => {
256 used.insert(*recipient);
257 }
258 }
259 }
260
261 fn find_dead_instructions(
263 &self,
264 func: &Function,
265 used_values: &FxHashSet<ValueId>,
266 inst_to_value: &FxHashMap<InstId, ValueId>,
267 ) -> Vec<(BlockId, InstId)> {
268 let mut dead = Vec::new();
269
270 for (block_id, block) in func.blocks.iter_enumerated() {
271 for &inst_id in &block.instructions {
272 let inst = &func.instructions[inst_id];
273
274 if inst.kind.has_side_effects() {
276 continue;
277 }
278
279 if let Some(&result) = inst_to_value.get(&inst_id)
281 && !used_values.contains(&result)
282 {
283 dead.push((block_id, inst_id));
284 }
285 }
286 }
287
288 dead
289 }
290}