1use crate::{
23 analysis::{AffineExpr, Loop, LoopAnalyzer, ScalarEvolution},
24 mir::{
25 BlockId, Function, Immediate, InstId, InstKind, Instruction, MirType, Value, ValueId,
26 utils as mir_utils,
27 },
28 pass::FunctionPass,
29};
30use alloy_primitives::U256;
31use solar_data_structures::map::FxHashMap;
32
33#[derive(Clone, Debug, Default)]
35pub struct IndVarSimplifyStats {
36 pub pointer_phis_inserted: usize,
38 pub address_uses_replaced: usize,
40}
41
42impl IndVarSimplifyStats {
43 #[must_use]
45 pub const fn total(&self) -> usize {
46 self.pointer_phis_inserted + self.address_uses_replaced
47 }
48}
49
50#[derive(Debug, Default)]
52pub struct IndVarSimplifier {
53 stats: IndVarSimplifyStats,
54}
55
56pub struct IndVarSimplifyPass;
58
59impl FunctionPass for IndVarSimplifyPass {
60 fn name(&self) -> &str {
61 "indvar-simplify"
62 }
63
64 fn run_on_function(&mut self, func: &mut Function) -> bool {
65 IndVarSimplifier::new().run(func).total() != 0
66 }
67}
68
69#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
70struct AddressKey {
71 base: ValueId,
72 iv: ValueId,
73 scale: i128,
74 constant: i128,
75}
76
77impl IndVarSimplifier {
78 #[must_use]
80 pub fn new() -> Self {
81 Self::default()
82 }
83
84 #[must_use]
86 pub const fn stats(&self) -> &IndVarSimplifyStats {
87 &self.stats
88 }
89
90 pub fn run(&mut self, func: &mut Function) -> &IndVarSimplifyStats {
92 self.stats = IndVarSimplifyStats::default();
93
94 let mut analyzer = LoopAnalyzer::new();
95 let loop_info = analyzer.analyze(func);
96 let mut loops: Vec<_> = loop_info.loops.values().cloned().collect();
97 loops.sort_by_key(|loop_data| loop_data.header.index());
98
99 for loop_data in loops {
100 self.run_loop(func, &loop_data);
101 }
102
103 &self.stats
104 }
105
106 fn run_loop(&mut self, func: &mut Function, loop_data: &Loop) {
107 let Some(preheader) = loop_data.preheader else { return };
108 let [latch] = loop_data.back_edges.as_slice() else { return };
109 let [iv] = loop_data.induction_vars.as_slice() else { return };
110 let Some(step) = self.additive_step(func, iv.value, iv.update_inst) else {
111 return;
112 };
113
114 let scev = ScalarEvolution::analyze(func, loop_data);
115 let inst_results = func.inst_results();
116 let mut candidates: FxHashMap<AddressKey, Vec<ValueId>> = FxHashMap::default();
117
118 let mut blocks: Vec<_> = loop_data.blocks.iter().copied().collect();
119 blocks.sort_by_key(|block| block.index());
120 for block in blocks {
121 let insts = func.blocks[block].instructions.clone();
122 for inst_id in insts {
123 let Some(&value) = inst_results.get(&inst_id) else { continue };
124 if !self.is_reducible_result(func, inst_id) {
125 continue;
126 }
127 let Some(key) = self.address_key(&scev, value, iv.value) else {
128 continue;
129 };
130 let Some(delta) = key.scale.checked_mul(step) else { continue };
131 if delta <= 0 || !self.has_non_address_loop_use(func, loop_data, value) {
132 continue;
133 }
134 candidates.entry(key).or_default().push(value);
135 }
136 }
137
138 if candidates.is_empty() {
139 return;
140 }
141
142 let mut replacements = FxHashMap::default();
143 for (key, values) in candidates {
144 let Some(pointer) =
145 self.materialize_pointer_phi(func, loop_data, preheader, *latch, key)
146 else {
147 continue;
148 };
149 for value in values {
150 replacements.insert(value, pointer);
151 }
152 }
153
154 if replacements.is_empty() {
155 return;
156 }
157
158 self.stats.address_uses_replaced += self.replace_loop_uses(func, loop_data, &replacements);
159 }
160
161 fn additive_step(
162 &self,
163 func: &Function,
164 iv_value: ValueId,
165 update_inst: Option<InstId>,
166 ) -> Option<i128> {
167 let update_inst = update_inst?;
168 let InstKind::Add(a, b) = func.instructions[update_inst].kind else {
169 return None;
170 };
171 let step = if a == iv_value {
172 b
173 } else if b == iv_value {
174 a
175 } else {
176 return None;
177 };
178 self.value_i128(func, step)
179 }
180
181 fn address_key(
182 &self,
183 scev: &ScalarEvolution,
184 value: ValueId,
185 iv_value: ValueId,
186 ) -> Option<AddressKey> {
187 let AffineExpr { base, constant, terms } = scev.get(value)?.clone();
188 let base = base?;
189 let [term] = terms.as_slice() else { return None };
190 if term.value != iv_value || term.scale <= 0 {
191 return None;
192 }
193 if constant < 0 {
194 return None;
195 }
196 Some(AddressKey { base, iv: iv_value, scale: term.scale, constant })
197 }
198
199 fn materialize_pointer_phi(
200 &mut self,
201 func: &mut Function,
202 loop_data: &Loop,
203 preheader: BlockId,
204 latch: BlockId,
205 key: AddressKey,
206 ) -> Option<ValueId> {
207 let iv = loop_data.induction_vars.iter().find(|iv| iv.value == key.iv)?;
208 let init = self.value_i128(func, iv.init)?;
209 let init_offset = init.checked_mul(key.scale)?.checked_add(key.constant)?;
210 let delta = self.additive_step(func, key.iv, iv.update_inst)?.checked_mul(key.scale)?;
211 if delta <= 0 {
212 return None;
213 }
214
215 let initial = self.build_base_plus_offset(func, preheader, key.base, init_offset)?;
216 let phi_inst = func.alloc_inst(Instruction::new(
217 InstKind::Phi(vec![(preheader, initial)]),
218 Some(MirType::uint256()),
219 ));
220 let phi_value = func.alloc_value(Value::Inst(phi_inst));
221 self.insert_header_phi(func, loop_data.header, phi_inst);
222
223 let delta = self.offset_value(func, delta)?;
224 let next = self.append_inst_value(
225 func,
226 latch,
227 InstKind::Add(phi_value, delta),
228 Some(MirType::uint256()),
229 );
230 let InstKind::Phi(incoming) = &mut func.instructions[phi_inst].kind else {
231 return None;
232 };
233 incoming.push((latch, next));
234 self.stats.pointer_phis_inserted += 1;
235 Some(phi_value)
236 }
237
238 fn build_base_plus_offset(
239 &self,
240 func: &mut Function,
241 block: BlockId,
242 base: ValueId,
243 offset: i128,
244 ) -> Option<ValueId> {
245 if offset == 0 {
246 return Some(base);
247 }
248 let offset = self.offset_value(func, offset)?;
249 Some(self.append_inst_value(
250 func,
251 block,
252 InstKind::Add(base, offset),
253 Some(MirType::uint256()),
254 ))
255 }
256
257 fn offset_value(&self, func: &mut Function, offset: i128) -> Option<ValueId> {
258 if offset < 0 {
259 return None;
260 }
261 Some(func.alloc_value(Value::Immediate(Immediate::uint256(U256::from(offset as u128)))))
262 }
263
264 fn append_inst_value(
265 &self,
266 func: &mut Function,
267 block: BlockId,
268 kind: InstKind,
269 ty: Option<MirType>,
270 ) -> ValueId {
271 let inst = func.alloc_inst(Instruction::new(kind, ty));
272 func.blocks[block].instructions.push(inst);
273 func.alloc_value(Value::Inst(inst))
274 }
275
276 fn insert_header_phi(&self, func: &mut Function, header: BlockId, phi_inst: InstId) {
277 let insert_pos = func.blocks[header]
278 .instructions
279 .iter()
280 .take_while(|&&inst_id| matches!(func.instructions[inst_id].kind, InstKind::Phi(_)))
281 .count();
282 func.blocks[header].instructions.insert(insert_pos, phi_inst);
283 }
284
285 fn is_reducible_result(&self, func: &Function, inst_id: InstId) -> bool {
286 if func.instructions[inst_id].result_ty != Some(MirType::uint256()) {
287 return false;
288 }
289 matches!(
290 func.instructions[inst_id].kind,
291 InstKind::Add(_, _) | InstKind::Sub(_, _) | InstKind::Mul(_, _) | InstKind::Shl(_, _)
292 )
293 }
294
295 fn value_i128(&self, func: &Function, value: ValueId) -> Option<i128> {
296 let value = match func.value(value) {
297 Value::Immediate(imm) => imm.as_u256()?,
298 _ => return None,
299 };
300 if value <= U256::from(i128::MAX as u128) { Some(value.to::<u128>() as i128) } else { None }
301 }
302
303 fn has_non_address_loop_use(&self, func: &Function, loop_data: &Loop, value: ValueId) -> bool {
304 for &block in &loop_data.blocks {
305 for &inst_id in &func.blocks[block].instructions {
306 let kind = &func.instructions[inst_id].kind;
307 if kind.operands().contains(&value) && !Self::is_address_builder(kind) {
308 return true;
309 }
310 }
311 if func.blocks[block]
312 .terminator
313 .as_ref()
314 .is_some_and(|term| term.operands().contains(&value))
315 {
316 return true;
317 }
318 }
319 false
320 }
321
322 fn is_address_builder(kind: &InstKind) -> bool {
323 matches!(
324 kind,
325 InstKind::Add(_, _) | InstKind::Sub(_, _) | InstKind::Mul(_, _) | InstKind::Shl(_, _)
326 )
327 }
328
329 fn replace_loop_uses(
330 &self,
331 func: &mut Function,
332 loop_data: &Loop,
333 replacements: &FxHashMap<ValueId, ValueId>,
334 ) -> usize {
335 let mut replaced = 0;
336 for &block in &loop_data.blocks {
337 let insts = func.blocks[block].instructions.clone();
338 for inst_id in insts {
339 replaced += mir_utils::replace_inst_uses(
340 &mut func.instructions[inst_id].kind,
341 replacements,
342 );
343 }
344 if let Some(term) = &mut func.blocks[block].terminator {
345 replaced += mir_utils::replace_terminator_uses(term, replacements);
346 }
347 }
348 replaced
349 }
350}