1use crate::mir::{BlockId, Function, InstId, InstKind, MirType, Value, ValueId};
13use solar_data_structures::map::FxHashMap;
14
15#[derive(Clone, Debug, PartialEq, Eq)]
17pub enum CopySource {
18 Value(ValueId),
20 Temp(u32),
22}
23
24#[derive(Clone, Debug, PartialEq, Eq)]
26pub enum CopyDest {
27 Value(ValueId),
29 Temp(u32),
31}
32
33#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct ParallelCopy {
36 pub src: CopySource,
38 pub dst: CopyDest,
40 pub ty: MirType,
42}
43
44#[derive(Clone, Debug, Default)]
46pub struct BlockCopies {
47 pub copies: Vec<ParallelCopy>,
49}
50
51#[derive(Debug)]
53pub struct PhiEliminationResult {
54 pub block_copies: FxHashMap<BlockId, BlockCopies>,
56 pub phis_to_remove: Vec<(BlockId, usize)>,
58}
59
60pub struct PhiEliminator;
62
63impl PhiEliminator {
64 #[must_use]
68 pub fn analyze(func: &Function) -> PhiEliminationResult {
69 let mut block_copies: FxHashMap<BlockId, BlockCopies> = FxHashMap::default();
70 let mut phis_to_remove = Vec::new();
71
72 for (block_id, block) in func.blocks.iter_enumerated() {
74 for (inst_idx, &inst_id) in block.instructions.iter().enumerate() {
75 let inst = func.instruction(inst_id);
76
77 if let InstKind::Phi(incoming) = &inst.kind {
78 let phi_dst = find_phi_dst(func, inst_id);
80
81 if let Some(dst) = phi_dst {
82 let ty = func.value(dst).ty();
83
84 for &(pred_block, src_val) in incoming {
86 block_copies.entry(pred_block).or_default().copies.push(ParallelCopy {
87 src: CopySource::Value(src_val),
88 dst: CopyDest::Value(dst),
89 ty,
90 });
91 }
92
93 phis_to_remove.push((block_id, inst_idx));
94 }
95 }
96 }
97 }
98
99 let mut temp_counter = 0u32;
101 for copies in block_copies.values_mut() {
102 sequentialize_copies(&mut copies.copies, &mut temp_counter);
103 }
104
105 PhiEliminationResult { block_copies, phis_to_remove }
106 }
107}
108
109#[must_use]
114pub fn eliminate_phis(func: &Function) -> PhiEliminationResult {
115 PhiEliminator::analyze(func)
116}
117
118fn find_phi_dst(func: &Function, inst_id: InstId) -> Option<ValueId> {
120 for (val_id, val) in func.values.iter_enumerated() {
121 if let Value::Inst(def_inst) = val
122 && *def_inst == inst_id
123 {
124 return Some(val_id);
125 }
126 }
127 None
128}
129
130fn src_value(src: &CopySource) -> Option<ValueId> {
132 match src {
133 CopySource::Value(v) => Some(*v),
134 CopySource::Temp(_) => None,
135 }
136}
137
138fn dst_value(dst: &CopyDest) -> Option<ValueId> {
140 match dst {
141 CopyDest::Value(v) => Some(*v),
142 CopyDest::Temp(_) => None,
143 }
144}
145
146fn sequentialize_copies(copies: &mut Vec<ParallelCopy>, temp_counter: &mut u32) {
156 if copies.len() <= 1 {
157 return;
158 }
159
160 let pending: Vec<ParallelCopy> = std::mem::take(copies);
162 let mut result: Vec<ParallelCopy> = Vec::with_capacity(pending.len() + 2);
163
164 let mut writes_to: FxHashMap<ValueId, usize> = FxHashMap::default();
166 for (i, copy) in pending.iter().enumerate() {
167 if let Some(dst) = dst_value(©.dst) {
168 writes_to.insert(dst, i);
169 }
170 }
171
172 let mut blocked_by: Vec<usize> = vec![0; pending.len()];
175 for (i, copy) in pending.iter().enumerate() {
176 if let Some(src) = src_value(©.src)
177 && let Some(&writer_idx) = writes_to.get(&src)
178 && writer_idx != i
179 {
180 blocked_by[writer_idx] += 1;
181 }
182 }
183
184 let mut emitted = vec![false; pending.len()];
185
186 loop {
188 let mut made_progress = false;
189
190 for i in 0..pending.len() {
191 if emitted[i] {
192 continue;
193 }
194
195 if blocked_by[i] == 0 {
197 result.push(pending[i].clone());
198 emitted[i] = true;
199 made_progress = true;
200
201 if let Some(src) = src_value(&pending[i].src)
203 && let Some(&blocked_writer) = writes_to.get(&src)
204 && blocked_writer != i
205 && !emitted[blocked_writer]
206 {
207 blocked_by[blocked_writer] = blocked_by[blocked_writer].saturating_sub(1);
208 }
209 }
210 }
211
212 if !made_progress {
213 break_cycles(
215 &pending,
216 &mut emitted,
217 &mut blocked_by,
218 &writes_to,
219 &mut result,
220 temp_counter,
221 );
222
223 if !emitted.iter().all(|&e| e) {
225 continue;
226 }
227 break;
228 }
229
230 if emitted.iter().all(|&e| e) {
231 break;
232 }
233 }
234
235 *copies = result;
236}
237
238fn break_cycles(
246 pending: &[ParallelCopy],
247 emitted: &mut [bool],
248 blocked_by: &mut [usize],
249 writes_to: &FxHashMap<ValueId, usize>,
250 result: &mut Vec<ParallelCopy>,
251 temp_counter: &mut u32,
252) {
253 let cycle_start = pending
255 .iter()
256 .enumerate()
257 .find(|(i, _)| !emitted[*i] && blocked_by[*i] > 0)
258 .map(|(i, _)| i);
259
260 let Some(start_idx) = cycle_start else {
261 for (i, copy) in pending.iter().enumerate() {
263 if !emitted[i] {
264 result.push(copy.clone());
265 emitted[i] = true;
266 }
267 }
268 return;
269 };
270
271 let mut cycle_indices = vec![start_idx];
273 let mut current = start_idx;
274
275 while let Some(src) = src_value(&pending[current].src) {
276 if let Some(&pred_idx) = writes_to.get(&src) {
278 if emitted[pred_idx] {
279 break;
280 }
281 if pred_idx == start_idx {
282 break;
284 }
285 if cycle_indices.contains(&pred_idx) {
286 break;
288 }
289 cycle_indices.push(pred_idx);
290 current = pred_idx;
291 } else {
292 break;
294 }
295 }
296
297 let break_idx = cycle_indices[0];
299 let break_copy = &pending[break_idx];
300
301 let temp_id = *temp_counter;
303 *temp_counter += 1;
304
305 result.push(ParallelCopy {
307 src: break_copy.src.clone(),
308 dst: CopyDest::Temp(temp_id),
309 ty: break_copy.ty,
310 });
311
312 if let Some(src) = src_value(&break_copy.src)
315 && let Some(&blocked_writer) = writes_to.get(&src)
316 && blocked_writer != break_idx
317 {
318 blocked_by[blocked_writer] = blocked_by[blocked_writer].saturating_sub(1);
319 }
320
321 for &idx in &cycle_indices[1..] {
323 if !emitted[idx] && blocked_by[idx] == 0 {
324 result.push(pending[idx].clone());
325 emitted[idx] = true;
326
327 if let Some(src) = src_value(&pending[idx].src)
329 && let Some(&blocked_writer) = writes_to.get(&src)
330 && !emitted[blocked_writer]
331 {
332 blocked_by[blocked_writer] = blocked_by[blocked_writer].saturating_sub(1);
333 }
334 }
335 }
336
337 result.push(ParallelCopy {
339 src: CopySource::Temp(temp_id),
340 dst: break_copy.dst.clone(),
341 ty: break_copy.ty,
342 });
343 emitted[break_idx] = true;
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349
350 fn copy(src: usize, dst: usize) -> ParallelCopy {
351 ParallelCopy {
352 src: CopySource::Value(ValueId::from_usize(src)),
353 dst: CopyDest::Value(ValueId::from_usize(dst)),
354 ty: MirType::uint256(),
355 }
356 }
357
358 fn has_temp(copies: &[ParallelCopy]) -> bool {
359 copies.iter().any(|c| matches!(c.src, CopySource::Temp(_)))
360 || copies.iter().any(|c| matches!(c.dst, CopyDest::Temp(_)))
361 }
362
363 #[test]
364 fn test_no_cycle() {
365 let mut copies = vec![copy(0, 1), copy(2, 3)];
366 let mut temp_counter = 0;
367 sequentialize_copies(&mut copies, &mut temp_counter);
368 assert_eq!(copies.len(), 2);
369 assert!(!has_temp(&copies));
370 }
371
372 #[test]
373 fn test_chain() {
374 let mut copies = vec![copy(1, 0), copy(0, 2)];
376 let mut temp_counter = 0;
377 sequentialize_copies(&mut copies, &mut temp_counter);
378
379 let write_to_a_idx =
381 copies.iter().position(|c| matches!(c.dst, CopyDest::Value(v) if v.index() == 0));
382 let read_from_a_idx =
383 copies.iter().position(|c| matches!(c.src, CopySource::Value(v) if v.index() == 0));
384 assert!(read_from_a_idx.unwrap() < write_to_a_idx.unwrap());
385 }
386
387 #[test]
388 fn test_simple_cycle() {
389 let mut copies = vec![copy(1, 0), copy(0, 1)];
391 let mut temp_counter = 0;
392 sequentialize_copies(&mut copies, &mut temp_counter);
393
394 assert!(copies.len() >= 3, "Cycle should introduce temporary copies");
397 assert!(has_temp(&copies), "Should use temporaries for cycles");
398 assert!(temp_counter >= 1, "Should allocate at least one temp");
399 }
400
401 #[test]
402 fn test_three_way_cycle() {
403 let mut copies = vec![copy(1, 0), copy(2, 1), copy(0, 2)];
405 let mut temp_counter = 0;
406 sequentialize_copies(&mut copies, &mut temp_counter);
407
408 assert!(copies.len() >= 4, "3-way cycle should introduce temporary copies");
410 assert!(has_temp(&copies), "Should use temporaries for cycles");
411 }
412
413 #[test]
414 fn test_independent_copies() {
415 let mut copies = vec![copy(10, 0), copy(11, 1), copy(12, 2)];
417 let mut temp_counter = 0;
418 sequentialize_copies(&mut copies, &mut temp_counter);
419
420 assert_eq!(copies.len(), 3);
422 assert!(!has_temp(&copies));
423 }
424}