1use crate::{
33 analysis::{CfgInfo, DominatorTree},
34 mir::{
35 BlockId, Function, Immediate, InstId, InstKind, Instruction, InstructionMetadata, MirType,
36 Terminator, Value, ValueId,
37 utils::{repair_reachability_phis, split_edge},
38 },
39 pass::FunctionPass,
40};
41use solar_data_structures::map::{FxHashMap, FxHashSet};
42use std::cmp::Ordering;
43
44const MAX_INSERTIONS_PER_REWRITE: usize = 2;
45
46#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
48pub struct PreStats {
49 pub expressions_eliminated: usize,
51 pub expressions_inserted: usize,
53}
54
55impl PreStats {
56 pub const fn total(self) -> usize {
58 self.expressions_eliminated + self.expressions_inserted
59 }
60}
61
62#[derive(Debug, Default)]
64pub struct PartialRedundancyEliminator {
65 stats: PreStats,
66}
67
68pub struct PrePass;
70
71impl FunctionPass for PrePass {
72 fn name(&self) -> &str {
73 "pre"
74 }
75
76 fn run_on_function(&mut self, func: &mut Function) -> bool {
77 PartialRedundancyEliminator::new().run(func).total() != 0
78 }
79}
80
81#[derive(Clone, Debug, PartialEq, Eq, Hash)]
82enum ExprKey {
83 Add(OperandKey, OperandKey),
84 Sub(OperandKey, OperandKey),
85 Mul(OperandKey, OperandKey),
86 Div(OperandKey, OperandKey),
87 SDiv(OperandKey, OperandKey),
88 Mod(OperandKey, OperandKey),
89 SMod(OperandKey, OperandKey),
90 Exp(OperandKey, OperandKey),
91 AddMod(OperandKey, OperandKey, OperandKey),
92 MulMod(OperandKey, OperandKey, OperandKey),
93 And(OperandKey, OperandKey),
94 Or(OperandKey, OperandKey),
95 Xor(OperandKey, OperandKey),
96 Not(OperandKey),
97 Shl(OperandKey, OperandKey),
98 Shr(OperandKey, OperandKey),
99 Sar(OperandKey, OperandKey),
100 Byte(OperandKey, OperandKey),
101 Lt(OperandKey, OperandKey),
102 Gt(OperandKey, OperandKey),
103 SLt(OperandKey, OperandKey),
104 SGt(OperandKey, OperandKey),
105 Eq(OperandKey, OperandKey),
106 IsZero(OperandKey),
107 Select(OperandKey, OperandKey, OperandKey),
108 SignExtend(OperandKey, OperandKey),
109}
110
111#[derive(Clone, Debug, PartialEq, Eq, Hash)]
112enum OperandKey {
113 Value(ValueId),
114 Immediate(Immediate),
115}
116
117struct PreCandidate {
118 target: BlockId,
119 inst: InstId,
120 result: ValueId,
121 result_ty: MirType,
122 metadata: InstructionMetadata,
123 incoming: Vec<(BlockId, ValueId)>,
124 insertions: Vec<(BlockId, InstKind)>,
125}
126
127impl PartialRedundancyEliminator {
128 pub fn new() -> Self {
130 Self::default()
131 }
132
133 pub const fn stats(&self) -> PreStats {
135 self.stats
136 }
137
138 pub fn run(&mut self, func: &mut Function) -> PreStats {
140 self.stats = PreStats::default();
141 repair_reachability_phis(func);
142
143 let mut inst_results = func.inst_results();
144 let mut inst_blocks = func.inst_blocks();
145
146 let mut eliminated_keys = FxHashSet::default();
147 let mut inserted_insts = FxHashSet::default();
148 let rewrite_limit = func.instructions.len().saturating_mul(2).max(64);
149 let mut rewrites = 0usize;
150
151 while rewrites < rewrite_limit {
152 let cfg = CfgInfo::new(func);
155 let batch = self.collect_candidates(
156 func,
157 cfg.dominators(),
158 &inst_results,
159 &inst_blocks,
160 &eliminated_keys,
161 &inserted_insts,
162 rewrite_limit - rewrites,
163 );
164 if batch.is_empty() {
165 break;
166 }
167 rewrites += batch.len();
168 for candidate in batch {
169 self.apply_candidate(
170 func,
171 candidate,
172 &mut inst_results,
173 &mut inst_blocks,
174 &mut eliminated_keys,
175 &mut inserted_insts,
176 );
177 }
178 repair_reachability_phis(func);
179 }
180
181 self.stats
182 }
183
184 #[allow(clippy::too_many_arguments)]
187 fn collect_candidates(
188 &self,
189 func: &Function,
190 dominators: &DominatorTree,
191 inst_results: &FxHashMap<InstId, ValueId>,
192 inst_blocks: &FxHashMap<InstId, BlockId>,
193 eliminated_keys: &FxHashSet<(ExprKey, BlockId)>,
194 inserted_insts: &FxHashSet<InstId>,
195 limit: usize,
196 ) -> Vec<PreCandidate> {
197 let mut batch = Vec::new();
198 let mut modified_blocks: FxHashSet<BlockId> = FxHashSet::default();
201 let mut eliminated_values: FxHashSet<ValueId> = FxHashSet::default();
202
203 'targets: for target in func.blocks.indices() {
204 let predecessors = func.unique_predecessors(target);
205 if predecessors.len() < 2 {
206 continue;
207 }
208
209 for &inst in &func.blocks[target].instructions {
210 if batch.len() >= limit {
211 break 'targets;
212 }
213 if inserted_insts.contains(&inst) {
216 continue;
217 }
218 let instruction = &func.instructions[inst];
219 if !Self::is_pre_expression(&instruction.kind) {
220 continue;
221 }
222 let Some(result_ty) = instruction.result_ty else {
223 continue;
224 };
225 let Some(&result) = inst_results.get(&inst) else {
226 continue;
227 };
228
229 let Some(candidate) = self.candidate_for_inst(
230 func,
231 target,
232 inst,
233 result,
234 result_ty,
235 instruction.metadata.clone(),
236 &predecessors,
237 inst_results,
238 inst_blocks,
239 dominators,
240 eliminated_keys,
241 ) else {
242 continue;
243 };
244
245 if Self::interferes_with_batch(&candidate, &modified_blocks, &eliminated_values) {
246 continue;
247 }
248 modified_blocks.insert(candidate.target);
249 modified_blocks.extend(candidate.insertions.iter().map(|(block, _)| *block));
250 eliminated_values.insert(candidate.result);
251 batch.push(candidate);
252 }
253 }
254
255 batch
256 }
257
258 fn interferes_with_batch(
262 candidate: &PreCandidate,
263 modified_blocks: &FxHashSet<BlockId>,
264 eliminated_values: &FxHashSet<ValueId>,
265 ) -> bool {
266 modified_blocks.contains(&candidate.target)
267 || candidate.insertions.iter().any(|(block, _)| modified_blocks.contains(block))
268 || candidate.incoming.iter().any(|(_, value)| eliminated_values.contains(value))
269 || candidate.insertions.iter().any(|(_, kind)| {
270 kind.operands().into_iter().any(|value| eliminated_values.contains(&value))
271 })
272 }
273
274 #[allow(clippy::too_many_arguments)]
275 fn candidate_for_inst(
276 &self,
277 func: &Function,
278 target: BlockId,
279 inst: InstId,
280 result: ValueId,
281 result_ty: MirType,
282 metadata: InstructionMetadata,
283 predecessors: &[BlockId],
284 inst_results: &FxHashMap<InstId, ValueId>,
285 inst_blocks: &FxHashMap<InstId, BlockId>,
286 dominators: &DominatorTree,
287 eliminated_keys: &FxHashSet<(ExprKey, BlockId)>,
288 ) -> Option<PreCandidate> {
289 let original = &func.instructions[inst].kind;
290 let mut incoming = Vec::with_capacity(predecessors.len());
291 let mut insertions = Vec::new();
292 let mut available = 0usize;
293
294 for &pred in predecessors {
295 let translated =
296 Self::translate_kind_for_predecessor(func, original, target, pred, inst_blocks)?;
297 if !Self::operands_available_at_end(func, &translated, pred, inst_blocks, dominators) {
298 return None;
299 }
300 let key = Self::make_expr_key(func, &translated)?;
301 if let Some(value) =
302 Self::available_value_at_end(func, dominators, pred, &key, inst_results)
303 {
304 available += 1;
305 incoming.push((pred, value));
306 continue;
307 }
308
309 if eliminated_keys.contains(&(key, pred)) {
313 return None;
314 }
315 insertions.push((pred, translated));
316 }
317
318 if insertions.len() > available
324 || insertions.len() > MAX_INSERTIONS_PER_REWRITE.max(available)
325 {
326 return None;
327 }
328
329 Some(PreCandidate { target, inst, result, result_ty, metadata, incoming, insertions })
330 }
331
332 fn apply_candidate(
333 &mut self,
334 func: &mut Function,
335 candidate: PreCandidate,
336 inst_results: &mut FxHashMap<InstId, ValueId>,
337 inst_blocks: &mut FxHashMap<InstId, BlockId>,
338 eliminated_keys: &mut FxHashSet<(ExprKey, BlockId)>,
339 inserted_insts: &mut FxHashSet<InstId>,
340 ) {
341 let PreCandidate { target, inst, result, result_ty, metadata, mut incoming, insertions } =
342 candidate;
343
344 if let Some(key) = Self::make_expr_key(func, &func.instructions[inst].kind) {
345 eliminated_keys.insert((key, target));
346 }
347
348 let fully_available = insertions.is_empty();
349 for (pred, kind) in insertions {
350 let block = match func.blocks[pred].terminator {
356 Some(Terminator::Jump(jump_target)) => {
357 debug_assert_eq!(jump_target, target);
358 pred
359 }
360 _ => split_edge(func, pred, target),
361 };
362 let new_inst = func.alloc_inst(Instruction {
363 kind,
364 result_ty: Some(result_ty),
365 metadata: metadata.clone(),
366 });
367 let value = func.alloc_value(Value::Inst(new_inst));
368 func.blocks[block].instructions.push(new_inst);
369 incoming.push((block, value));
370 inst_results.insert(new_inst, value);
371 inst_blocks.insert(new_inst, block);
372 inserted_insts.insert(new_inst);
373 self.stats.expressions_inserted += 1;
374 }
375 incoming.sort_by_key(|(block, _)| block.index());
376
377 let replacement = match incoming.first() {
381 Some(&(_, first))
382 if fully_available
383 && first != result
384 && incoming.iter().all(|&(_, value)| value == first) =>
385 {
386 first
387 }
388 _ => {
389 let phi_inst =
390 func.alloc_inst(Instruction::new(InstKind::Phi(incoming), Some(result_ty)));
391 let phi_value = func.alloc_value(Value::Inst(phi_inst));
392 let phi_count = func.blocks[target]
393 .instructions
394 .iter()
395 .take_while(|&&inst_id| {
396 matches!(func.instructions[inst_id].kind, InstKind::Phi(_))
397 })
398 .count();
399 func.blocks[target].instructions.insert(phi_count, phi_inst);
400 inst_results.insert(phi_inst, phi_value);
401 inst_blocks.insert(phi_inst, target);
402 phi_value
403 }
404 };
405
406 let replacements = FxHashMap::from_iter([(result, replacement)]);
407 func.replace_uses(&replacements);
408 func.blocks[target].instructions.retain(|&inst_id| inst_id != inst);
409 inst_results.remove(&inst);
410 inst_blocks.remove(&inst);
411 self.stats.expressions_eliminated += 1;
412 }
413
414 fn translate_kind_for_predecessor(
415 func: &Function,
416 kind: &InstKind,
417 target: BlockId,
418 pred: BlockId,
419 inst_blocks: &FxHashMap<InstId, BlockId>,
420 ) -> Option<InstKind> {
421 let mut translated = kind.clone();
422 let mut ok = true;
423 translated.visit_operands_mut(|value| {
424 if let Some(translated) =
425 Self::translate_value_for_predecessor(func, *value, target, pred, inst_blocks)
426 {
427 *value = translated;
428 } else {
429 ok = false;
430 }
431 });
432 ok.then_some(translated)
433 }
434
435 fn translate_value_for_predecessor(
436 func: &Function,
437 value: ValueId,
438 target: BlockId,
439 pred: BlockId,
440 inst_blocks: &FxHashMap<InstId, BlockId>,
441 ) -> Option<ValueId> {
442 match func.value(value) {
443 Value::Inst(inst_id)
444 if inst_blocks.get(inst_id).copied() == Some(target)
445 && matches!(func.instructions[*inst_id].kind, InstKind::Phi(_)) =>
446 {
447 let InstKind::Phi(incoming) = &func.instructions[*inst_id].kind else {
448 return None;
449 };
450 incoming.iter().find_map(|(incoming_pred, incoming_value)| {
451 (*incoming_pred == pred).then_some(*incoming_value)
452 })
453 }
454 _ => Some(value),
455 }
456 }
457
458 fn operands_available_at_end(
459 func: &Function,
460 kind: &InstKind,
461 block: BlockId,
462 inst_blocks: &FxHashMap<InstId, BlockId>,
463 dominators: &DominatorTree,
464 ) -> bool {
465 kind.operands()
466 .into_iter()
467 .all(|value| Self::value_available_at_end(func, value, block, inst_blocks, dominators))
468 }
469
470 fn value_available_at_end(
471 func: &Function,
472 value: ValueId,
473 block: BlockId,
474 inst_blocks: &FxHashMap<InstId, BlockId>,
475 dominators: &DominatorTree,
476 ) -> bool {
477 match func.value(value) {
478 Value::Immediate(_) | Value::Arg { .. } | Value::Undef(_) | Value::Error(_) => true,
479 Value::Inst(inst) => inst_blocks
480 .get(inst)
481 .is_some_and(|def_block| dominators.dominates(*def_block, block)),
482 }
483 }
484
485 fn available_value_at_end(
489 func: &Function,
490 dominators: &DominatorTree,
491 block: BlockId,
492 key: &ExprKey,
493 inst_results: &FxHashMap<InstId, ValueId>,
494 ) -> Option<ValueId> {
495 dominators
496 .self_and_dominators(block)
497 .into_iter()
498 .find_map(|block| Self::available_value_in_block(func, block, key, inst_results))
499 }
500
501 fn available_value_in_block(
502 func: &Function,
503 block: BlockId,
504 key: &ExprKey,
505 inst_results: &FxHashMap<InstId, ValueId>,
506 ) -> Option<ValueId> {
507 func.blocks[block].instructions.iter().rev().find_map(|&inst| {
508 let instruction = &func.instructions[inst];
509 if !Self::is_pre_expression(&instruction.kind) {
510 return None;
511 }
512 let candidate_key = Self::make_expr_key(func, &instruction.kind)?;
513 (candidate_key == *key).then(|| inst_results.get(&inst).copied()).flatten()
514 })
515 }
516
517 fn is_pre_expression(kind: &InstKind) -> bool {
518 matches!(
519 kind,
520 InstKind::Add(_, _)
521 | InstKind::Sub(_, _)
522 | InstKind::Mul(_, _)
523 | InstKind::Div(_, _)
524 | InstKind::SDiv(_, _)
525 | InstKind::Mod(_, _)
526 | InstKind::SMod(_, _)
527 | InstKind::Exp(_, _)
528 | InstKind::AddMod(_, _, _)
529 | InstKind::MulMod(_, _, _)
530 | InstKind::And(_, _)
531 | InstKind::Or(_, _)
532 | InstKind::Xor(_, _)
533 | InstKind::Not(_)
534 | InstKind::Shl(_, _)
535 | InstKind::Shr(_, _)
536 | InstKind::Sar(_, _)
537 | InstKind::Byte(_, _)
538 | InstKind::Lt(_, _)
539 | InstKind::Gt(_, _)
540 | InstKind::SLt(_, _)
541 | InstKind::SGt(_, _)
542 | InstKind::Eq(_, _)
543 | InstKind::IsZero(_)
544 | InstKind::Select(_, _, _)
545 | InstKind::SignExtend(_, _)
546 )
547 }
548
549 fn make_expr_key(func: &Function, kind: &InstKind) -> Option<ExprKey> {
550 let operand = |value| Self::operand_key(func, value);
551 match kind {
552 InstKind::Add(a, b) => {
553 let (a, b) = Self::ordered_pair(operand(*a), operand(*b));
554 Some(ExprKey::Add(a, b))
555 }
556 InstKind::Mul(a, b) => {
557 let (a, b) = Self::ordered_pair(operand(*a), operand(*b));
558 Some(ExprKey::Mul(a, b))
559 }
560 InstKind::And(a, b) => {
561 let (a, b) = Self::ordered_pair(operand(*a), operand(*b));
562 Some(ExprKey::And(a, b))
563 }
564 InstKind::Or(a, b) => {
565 let (a, b) = Self::ordered_pair(operand(*a), operand(*b));
566 Some(ExprKey::Or(a, b))
567 }
568 InstKind::Xor(a, b) => {
569 let (a, b) = Self::ordered_pair(operand(*a), operand(*b));
570 Some(ExprKey::Xor(a, b))
571 }
572 InstKind::Eq(a, b) => {
573 let (a, b) = Self::ordered_pair(operand(*a), operand(*b));
574 Some(ExprKey::Eq(a, b))
575 }
576 InstKind::AddMod(a, b, n) => {
577 let (a, b) = Self::ordered_pair(operand(*a), operand(*b));
578 Some(ExprKey::AddMod(a, b, operand(*n)))
579 }
580 InstKind::MulMod(a, b, n) => {
581 let (a, b) = Self::ordered_pair(operand(*a), operand(*b));
582 Some(ExprKey::MulMod(a, b, operand(*n)))
583 }
584 InstKind::Sub(a, b) => Some(ExprKey::Sub(operand(*a), operand(*b))),
585 InstKind::Div(a, b) => Some(ExprKey::Div(operand(*a), operand(*b))),
586 InstKind::SDiv(a, b) => Some(ExprKey::SDiv(operand(*a), operand(*b))),
587 InstKind::Mod(a, b) => Some(ExprKey::Mod(operand(*a), operand(*b))),
588 InstKind::SMod(a, b) => Some(ExprKey::SMod(operand(*a), operand(*b))),
589 InstKind::Exp(a, b) => Some(ExprKey::Exp(operand(*a), operand(*b))),
590 InstKind::Not(a) => Some(ExprKey::Not(operand(*a))),
591 InstKind::Shl(a, b) => Some(ExprKey::Shl(operand(*a), operand(*b))),
592 InstKind::Shr(a, b) => Some(ExprKey::Shr(operand(*a), operand(*b))),
593 InstKind::Sar(a, b) => Some(ExprKey::Sar(operand(*a), operand(*b))),
594 InstKind::Byte(a, b) => Some(ExprKey::Byte(operand(*a), operand(*b))),
595 InstKind::Lt(a, b) => Some(ExprKey::Lt(operand(*a), operand(*b))),
596 InstKind::Gt(a, b) => Some(ExprKey::Gt(operand(*a), operand(*b))),
597 InstKind::SLt(a, b) => Some(ExprKey::SLt(operand(*a), operand(*b))),
598 InstKind::SGt(a, b) => Some(ExprKey::SGt(operand(*a), operand(*b))),
599 InstKind::IsZero(a) => Some(ExprKey::IsZero(operand(*a))),
600 InstKind::Select(a, b, c) => {
601 Some(ExprKey::Select(operand(*a), operand(*b), operand(*c)))
602 }
603 InstKind::SignExtend(a, b) => Some(ExprKey::SignExtend(operand(*a), operand(*b))),
604 _ => None,
605 }
606 }
607
608 fn operand_key(func: &Function, value: ValueId) -> OperandKey {
609 match func.value(value) {
610 Value::Immediate(imm) => OperandKey::Immediate(imm.clone()),
611 _ => OperandKey::Value(value),
612 }
613 }
614
615 fn ordered_pair(a: OperandKey, b: OperandKey) -> (OperandKey, OperandKey) {
616 if Self::compare_operand_key(&a, &b) == Ordering::Greater { (b, a) } else { (a, b) }
617 }
618
619 fn compare_operand_key(a: &OperandKey, b: &OperandKey) -> Ordering {
620 match (a, b) {
621 (OperandKey::Value(a), OperandKey::Value(b)) => a.index().cmp(&b.index()),
622 (OperandKey::Value(_), OperandKey::Immediate(_)) => Ordering::Less,
623 (OperandKey::Immediate(_), OperandKey::Value(_)) => Ordering::Greater,
624 (OperandKey::Immediate(a), OperandKey::Immediate(b)) => Self::compare_immediate(a, b),
625 }
626 }
627
628 fn compare_immediate(a: &Immediate, b: &Immediate) -> Ordering {
629 let rank = |imm: &Immediate| match imm {
630 Immediate::Bool(_) => 0,
631 Immediate::UInt(_, _) => 1,
632 Immediate::Int(_, _) => 2,
633 Immediate::Address(_) => 3,
634 Immediate::FixedBytes(_, _) => 4,
635 };
636 rank(a).cmp(&rank(b)).then_with(|| match (a, b) {
637 (Immediate::Bool(a), Immediate::Bool(b)) => a.cmp(b),
638 (Immediate::UInt(a_value, a_bits), Immediate::UInt(b_value, b_bits))
639 | (Immediate::Int(a_value, a_bits), Immediate::Int(b_value, b_bits)) => {
640 a_bits.cmp(b_bits).then_with(|| a_value.cmp(b_value))
641 }
642 (Immediate::Address(a), Immediate::Address(b)) => a.cmp(b),
643 (Immediate::FixedBytes(a_value, a_len), Immediate::FixedBytes(b_value, b_len)) => {
644 a_len.cmp(b_len).then_with(|| a_value.cmp(b_value))
645 }
646 _ => Ordering::Equal,
647 })
648 }
649}