1use crate::{
76 analysis::{CfgInfo, DominatorTree},
77 mir::{
78 BlockId, Function, InstId, InstKind, Instruction, InstructionMetadata, MemoryRegion,
79 MirType, StorageAlias, Terminator, Value, ValueId,
80 utils::{self as mir_utils, repair_reachability_phis},
81 },
82 pass::FunctionPass,
83};
84use alloy_primitives::U256;
85use solar_data_structures::{
86 bit_set::DenseBitSet,
87 map::{FxHashMap, FxHashSet},
88 newtype_index,
89};
90
91#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
93pub struct LoadPreStats {
94 pub loads_eliminated: usize,
96 pub loads_inserted: usize,
98}
99
100impl LoadPreStats {
101 pub const fn total(self) -> usize {
103 self.loads_eliminated + self.loads_inserted
104 }
105}
106
107#[derive(Debug, Default)]
109pub struct LoadRedundancyEliminator {
110 stats: LoadPreStats,
111}
112
113pub struct LoadPrePass;
115
116impl FunctionPass for LoadPrePass {
117 fn name(&self) -> &str {
118 "load-pre"
119 }
120
121 fn run_on_function(&mut self, func: &mut Function) -> bool {
122 LoadRedundancyEliminator::new().run(func).total() != 0
123 }
124}
125
126#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
128enum LoadKey {
129 Storage(StorageAlias),
130 Transient(StorageAlias),
131 Memory(MemAddr),
133 Keccak(MemAddr, KeccakSize),
134}
135
136#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
139struct MemAddr {
140 region: MemoryRegion,
141 base: Option<ValueId>,
142 offset: u64,
143}
144
145#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
147enum KeccakSize {
148 Const(u64),
149 Dyn(ValueId),
150}
151
152enum GenSource {
154 LoadResult,
156 Stored(ValueId),
158}
159
160newtype_index! {
161 struct KeyIdx;
162}
163
164#[derive(Clone, Debug, PartialEq, Eq)]
166struct KeySet(DenseBitSet<KeyIdx>);
167
168impl KeySet {
169 fn empty(len: usize) -> Self {
170 Self(DenseBitSet::new_empty(len))
171 }
172
173 fn full(len: usize) -> Self {
174 Self(DenseBitSet::new_filled(len))
175 }
176
177 fn insert(&mut self, idx: usize) {
178 self.0.insert(KeyIdx::from_usize(idx));
179 }
180
181 fn remove(&mut self, idx: usize) {
182 self.0.remove(KeyIdx::from_usize(idx));
183 }
184
185 fn contains(&self, idx: usize) -> bool {
186 self.0.contains(KeyIdx::from_usize(idx))
187 }
188
189 fn is_empty(&self) -> bool {
190 self.0.is_empty()
191 }
192
193 fn intersect_with(&mut self, other: &Self) {
194 self.0.intersect(&other.0);
195 }
196
197 fn subtract(&mut self, other: &Self) {
198 self.0.subtract(&other.0);
199 }
200
201 fn union_with(&mut self, other: &Self) {
202 self.0.union(&other.0);
203 }
204}
205
206struct Candidate {
209 target: BlockId,
210 key: LoadKey,
211 result_ty: MirType,
212 kind: InstKind,
213 metadata: InstructionMetadata,
214 loads: Vec<(InstId, ValueId)>,
215 incoming: Vec<(BlockId, ValueId)>,
216 insertions: Vec<BlockId>,
217}
218
219#[derive(Clone, Copy, Debug, Default)]
226struct LoadPreCostModel;
227
228#[derive(Clone, Copy, Debug, PartialEq, Eq)]
229struct LoadPreCost {
230 saved: i64,
231 inserted: i64,
232 phi: i64,
233 operands: i64,
234}
235
236impl LoadPreCost {
237 const fn is_profitable(self) -> bool {
238 self.saved > self.inserted + self.phi + self.operands
239 }
240}
241
242struct LoadPreCostInput<'a> {
243 func: &'a Function,
244 key: LoadKey,
245 kind: &'a InstKind,
246 predecessors: &'a [BlockId],
247 loads: &'a [(InstId, ValueId)],
248 insertions: &'a [BlockId],
249 loop_carried: bool,
250 needs_phi: bool,
251}
252
253impl LoadPreCostModel {
254 const MEMORY_READ: i64 = 3;
255 const STORAGE_READ: i64 = 100;
256 const TRANSIENT_READ: i64 = 100;
257 const KECCAK_BASE: i64 = 30;
258 const KECCAK_WORD: i64 = 6;
259 const NON_LOOP_PHI_EDGE_COPY: i64 = 3;
260 const CROSS_BLOCK_OPERAND: i64 = 3;
261
262 fn estimate(&self, input: LoadPreCostInput<'_>) -> LoadPreCost {
263 let read = Self::read_cost(input.key);
264 let saved = read * input.loads.len() as i64 * input.predecessors.len() as i64;
265 let inserted = read * input.insertions.len() as i64;
266 let phi = if !input.needs_phi || input.loop_carried {
267 0
268 } else {
269 Self::NON_LOOP_PHI_EDGE_COPY * input.predecessors.len() as i64
270 };
271 let operands = Self::inserted_operand_cost(input.func, input.kind, input.insertions);
272 LoadPreCost { saved, inserted, phi, operands }
273 }
274
275 const fn read_cost(key: LoadKey) -> i64 {
276 match key {
277 LoadKey::Storage(_) => Self::STORAGE_READ,
278 LoadKey::Transient(_) => Self::TRANSIENT_READ,
279 LoadKey::Memory(_) => Self::MEMORY_READ,
280 LoadKey::Keccak(_, size) => match size {
281 KeccakSize::Const(size) => {
282 Self::KECCAK_BASE + Self::KECCAK_WORD * size.div_ceil(32) as i64
283 }
284 KeccakSize::Dyn(_) => Self::KECCAK_BASE + Self::KECCAK_WORD * 2,
285 },
286 }
287 }
288
289 fn inserted_operand_cost(func: &Function, kind: &InstKind, insertions: &[BlockId]) -> i64 {
290 if insertions.is_empty() {
291 return 0;
292 }
293 let cross_block_operands = kind
294 .operands()
295 .into_iter()
296 .filter(|&value| matches!(func.value(value), Value::Inst(_) | Value::Undef(_)))
297 .count();
298 Self::CROSS_BLOCK_OPERAND * cross_block_operands as i64 * insertions.len() as i64
299 }
300}
301
302struct Analysis {
304 keys: Vec<LoadKey>,
305 key_index: FxHashMap<LoadKey, usize>,
306 reachable: FxHashSet<BlockId>,
307 kills: FxHashMap<BlockId, KeySet>,
310 ins: FxHashMap<BlockId, KeySet>,
312 outs: FxHashMap<BlockId, KeySet>,
314 reach: FxHashMap<BlockId, FxHashSet<BlockId>>,
317 dominators: DominatorTree,
318 inst_results: FxHashMap<InstId, ValueId>,
319 inst_blocks: FxHashMap<InstId, BlockId>,
320}
321
322impl Analysis {
323 fn path_kills_key(&self, from: BlockId, to: BlockId, key_idx: usize) -> bool {
326 if self.kills.is_empty() {
327 return false;
328 }
329 let Some(reachable_from) = self.reach.get(&from) else { return true };
330 for (&mid, kills) in &self.kills {
331 if mid == from || !kills.contains(key_idx) {
332 continue;
333 }
334 if reachable_from.contains(&mid)
335 && self.reach.get(&mid).is_some_and(|reach| reach.contains(&to))
336 {
337 return true;
338 }
339 }
340 false
341 }
342}
343
344struct CandidateCx<'a> {
346 analysis: &'a Analysis,
347 eliminated_keys: &'a FxHashSet<(LoadKey, BlockId)>,
348 inserted_insts: &'a FxHashSet<InstId>,
349 locate_cache: FxHashMap<(BlockId, usize), Option<ValueId>>,
350}
351
352impl LoadRedundancyEliminator {
353 pub fn new() -> Self {
355 Self::default()
356 }
357
358 pub const fn stats(&self) -> LoadPreStats {
360 self.stats
361 }
362
363 pub fn run(&mut self, func: &mut Function) -> LoadPreStats {
365 self.stats = LoadPreStats::default();
366 repair_reachability_phis(func);
367
368 let rewrite_limit = func.instructions.len().saturating_mul(2).max(64);
369 let mut rewrites = 0usize;
370 let mut eliminated_keys: FxHashSet<(LoadKey, BlockId)> = FxHashSet::default();
371 let mut inserted_insts: FxHashSet<InstId> = FxHashSet::default();
372
373 while rewrites < rewrite_limit {
374 let Some(analysis) = Self::compute_analysis(func) else { break };
375 let mut cx = CandidateCx {
376 analysis: &analysis,
377 eliminated_keys: &eliminated_keys,
378 inserted_insts: &inserted_insts,
379 locate_cache: FxHashMap::default(),
380 };
381 let batch = self.collect_candidates(func, &mut cx, rewrite_limit - rewrites);
382 if batch.is_empty() {
383 break;
384 }
385 rewrites += batch.len();
386 for candidate in batch {
387 self.apply_candidate(func, candidate, &mut eliminated_keys, &mut inserted_insts);
388 }
389 }
390
391 self.stats
392 }
393
394 fn compute_analysis(func: &Function) -> Option<Analysis> {
397 let mut cfg = CfgInfo::new(func);
398 let rpo = cfg.rpo();
399
400 let mut keys = Vec::new();
402 let mut key_index: FxHashMap<LoadKey, usize> = FxHashMap::default();
403 for &block in rpo {
404 for &inst_id in &func.blocks[block].instructions {
405 if let Some((key, _)) = Self::gen_key_value(func, inst_id) {
406 key_index.entry(key).or_insert_with(|| {
407 keys.push(key);
408 keys.len() - 1
409 });
410 }
411 }
412 }
413 if keys.is_empty() {
414 return None;
415 }
416 let key_count = keys.len();
417
418 let mut gens = FxHashMap::default();
421 let mut kills = FxHashMap::default();
422 for &block in rpo {
423 let mut gen_set = KeySet::empty(key_count);
424 let mut kill_set = KeySet::empty(key_count);
425 for &inst_id in &func.blocks[block].instructions {
426 if func.instructions[inst_id].kind.has_side_effects() {
427 for (idx, &key) in keys.iter().enumerate() {
428 if Self::inst_kills_key(func, inst_id, key) {
429 kill_set.insert(idx);
430 gen_set.remove(idx);
431 }
432 }
433 }
434 if let Some((key, _)) = Self::gen_key_value(func, inst_id)
438 && let Some(&idx) = key_index.get(&key)
439 {
440 gen_set.insert(idx);
441 }
442 }
443 if !kill_set.is_empty() {
444 kills.insert(block, kill_set);
445 }
446 gens.insert(block, gen_set);
447 }
448
449 let mut ins: FxHashMap<BlockId, KeySet> = FxHashMap::default();
451 let mut outs: FxHashMap<BlockId, KeySet> = rpo
452 .iter()
453 .map(|&block| {
454 let out = if block == func.entry_block {
455 gens[&block].clone()
456 } else {
457 KeySet::full(key_count)
458 };
459 (block, out)
460 })
461 .collect();
462 loop {
463 let mut changed = false;
464 for &block in rpo {
465 let in_set = if block == func.entry_block {
466 KeySet::empty(key_count)
467 } else {
468 let mut acc: Option<KeySet> = None;
469 for &pred in &func.blocks[block].predecessors {
470 let Some(out) = outs.get(&pred) else { continue };
473 match &mut acc {
474 Some(acc) => acc.intersect_with(out),
475 None => acc = Some(out.clone()),
476 }
477 }
478 acc.unwrap_or_else(|| KeySet::empty(key_count))
479 };
480 let mut out = in_set.clone();
481 if let Some(kill) = kills.get(&block) {
482 out.subtract(kill);
483 }
484 out.union_with(&gens[&block]);
485 ins.insert(block, in_set);
486 if outs.get(&block) != Some(&out) {
487 outs.insert(block, out);
488 changed = true;
489 }
490 }
491 if !changed {
492 break;
493 }
494 }
495
496 let reach = if kills.is_empty() {
497 FxHashMap::default()
498 } else {
499 cfg.transitive_reachability().clone()
500 };
501
502 Some(Analysis {
503 keys,
504 key_index,
505 reachable: cfg.reachable().clone(),
506 kills,
507 ins,
508 outs,
509 reach,
510 dominators: cfg.dominators().clone(),
511 inst_results: func.inst_results(),
512 inst_blocks: func.inst_blocks(),
513 })
514 }
515
516 fn collect_candidates(
519 &self,
520 func: &Function,
521 cx: &mut CandidateCx<'_>,
522 limit: usize,
523 ) -> Vec<Candidate> {
524 let mut batch = Vec::new();
525 let mut modified_blocks: FxHashSet<BlockId> = FxHashSet::default();
528 let mut eliminated_values: FxHashSet<ValueId> = FxHashSet::default();
529
530 'targets: for target in func.blocks.indices() {
531 if !cx.analysis.reachable.contains(&target) {
532 continue;
533 }
534 let predecessors = func.unique_predecessors(target);
535 if predecessors.len() < 2
536 || predecessors.iter().any(|pred| !cx.analysis.reachable.contains(pred))
537 {
538 continue;
539 }
540
541 for (inst, key_idx) in Self::first_loads(func, cx.analysis, target) {
542 if batch.len() >= limit {
543 break 'targets;
544 }
545 if cx.inserted_insts.contains(&inst) {
547 continue;
548 }
549 let Some(candidate) =
550 self.candidate_for_load(func, cx, target, inst, key_idx, &predecessors)
551 else {
552 continue;
553 };
554 if Self::interferes_with_batch(&candidate, &modified_blocks, &eliminated_values) {
555 continue;
556 }
557 modified_blocks.insert(candidate.target);
558 modified_blocks.extend(candidate.insertions.iter().copied());
559 eliminated_values.extend(candidate.loads.iter().map(|&(_, value)| value));
560 batch.push(candidate);
561 }
562 }
563
564 batch
565 }
566
567 fn interferes_with_batch(
571 candidate: &Candidate,
572 modified_blocks: &FxHashSet<BlockId>,
573 eliminated_values: &FxHashSet<ValueId>,
574 ) -> bool {
575 modified_blocks.contains(&candidate.target)
576 || candidate.insertions.iter().any(|block| modified_blocks.contains(block))
577 || candidate.incoming.iter().any(|(_, value)| eliminated_values.contains(value))
578 || candidate.loads.iter().any(|&(_, value)| eliminated_values.contains(&value))
579 || (!candidate.insertions.is_empty()
580 && candidate
581 .kind
582 .operands()
583 .into_iter()
584 .any(|value| eliminated_values.contains(&value)))
585 }
586
587 fn first_loads(func: &Function, analysis: &Analysis, target: BlockId) -> Vec<(InstId, usize)> {
595 let key_count = analysis.keys.len();
596 let mut blocked = KeySet::empty(key_count);
597 let mut taken: FxHashSet<usize> = FxHashSet::default();
598 let mut found = Vec::new();
599
600 for &inst_id in &func.blocks[target].instructions {
601 if let Some((key, GenSource::LoadResult)) = Self::gen_key_value(func, inst_id) {
602 if let Some(&idx) = analysis.key_index.get(&key)
603 && !blocked.contains(idx)
604 && taken.insert(idx)
605 {
606 found.push((inst_id, idx));
607 }
608 continue;
609 }
610 let kind = &func.instructions[inst_id].kind;
611 match kind {
612 InstKind::Gas => break,
615 InstKind::MSize => {
616 for (idx, key) in analysis.keys.iter().enumerate() {
617 if matches!(key, LoadKey::Memory(_) | LoadKey::Keccak(_, _)) {
618 blocked.insert(idx);
619 }
620 }
621 }
622 _ if kind.has_side_effects() => {
623 for (idx, &key) in analysis.keys.iter().enumerate() {
627 if !blocked.contains(idx) && Self::inst_kills_key(func, inst_id, key) {
628 blocked.insert(idx);
629 }
630 }
631 }
632 _ => {}
633 }
634 }
635
636 found
637 }
638
639 fn same_key_loads_in_target(
640 func: &Function,
641 analysis: &Analysis,
642 target: BlockId,
643 first_inst: InstId,
644 key: LoadKey,
645 ) -> Vec<(InstId, ValueId)> {
646 let mut loads = Vec::new();
647 let mut past_first = false;
648
649 for &inst_id in &func.blocks[target].instructions {
650 if inst_id == first_inst {
651 past_first = true;
652 }
653 if !past_first {
654 continue;
655 }
656
657 if inst_id != first_inst {
658 let kind = &func.instructions[inst_id].kind;
659 if matches!(kind, InstKind::Gas) {
660 break;
661 }
662 if matches!(kind, InstKind::MSize)
663 && matches!(key, LoadKey::Memory(_) | LoadKey::Keccak(_, _))
664 {
665 break;
666 }
667 if kind.has_side_effects() && Self::inst_kills_key(func, inst_id, key) {
668 break;
669 }
670 }
671
672 if let Some((load_key, GenSource::LoadResult)) = Self::gen_key_value(func, inst_id)
673 && load_key == key
674 && let Some(&value) = analysis.inst_results.get(&inst_id)
675 {
676 loads.push((inst_id, value));
677 }
678 }
679
680 loads
681 }
682
683 fn candidate_for_load(
684 &self,
685 func: &Function,
686 cx: &mut CandidateCx<'_>,
687 target: BlockId,
688 inst: InstId,
689 key_idx: usize,
690 predecessors: &[BlockId],
691 ) -> Option<Candidate> {
692 let instruction = &func.instructions[inst];
693 let result = *cx.analysis.inst_results.get(&inst)?;
694 let result_ty = instruction.result_ty?;
695 let key = cx.analysis.keys[key_idx];
696 let loads = Self::same_key_loads_in_target(func, cx.analysis, target, inst, key);
697 if loads.is_empty() {
698 return None;
699 }
700
701 let mut incoming = Vec::with_capacity(predecessors.len());
702 let mut insertions = Vec::new();
703 for &pred in predecessors {
704 if cx.analysis.outs.get(&pred).is_some_and(|out| out.contains(key_idx))
705 && let Some(value) = self.locate_value(func, cx, pred, key_idx)
706 {
707 incoming.push((pred, value));
708 continue;
709 }
710 if !Self::can_insert_on_edge(func, pred, target) {
713 return None;
714 }
715 if cx.eliminated_keys.contains(&(key, pred)) {
718 return None;
719 }
720 if !Self::operands_dominate_block(func, &instruction.kind, pred, cx.analysis) {
721 return None;
722 }
723 insertions.push(pred);
724 }
725
726 let loop_carried =
727 Self::is_loop_carried_rewrite(&cx.analysis.dominators, target, &incoming, &insertions);
728 let needs_phi = !insertions.is_empty()
729 || incoming.first().is_none_or(|&(_, first)| {
730 first == result || incoming.iter().any(|&(_, value)| value != first)
731 });
732 let model = LoadPreCostModel;
733 let cost = model.estimate(LoadPreCostInput {
734 func,
735 key,
736 kind: &instruction.kind,
737 predecessors,
738 loads: &loads,
739 insertions: &insertions,
740 loop_carried,
741 needs_phi,
742 });
743 if !cost.is_profitable() {
744 return None;
745 }
746
747 if !insertions.is_empty() {
748 let loop_insertion = incoming
751 .iter()
752 .all(|&(pred, _)| cx.analysis.dominators.dominates(target, pred))
753 && insertions.iter().all(|&pred| !cx.analysis.dominators.dominates(target, pred));
754 let diamond_insertion = Self::is_non_cyclic_diamond_insertion(
755 &cx.analysis.dominators,
756 target,
757 &incoming,
758 &insertions,
759 );
760 if !(loop_insertion || diamond_insertion) || insertions.len() > incoming.len() {
761 return None;
762 }
763 }
764
765 Some(Candidate {
766 target,
767 key,
768 result_ty,
769 kind: instruction.kind.clone(),
770 metadata: instruction.metadata.clone(),
771 loads,
772 incoming,
773 insertions,
774 })
775 }
776
777 fn is_loop_carried_rewrite(
778 dominators: &DominatorTree,
779 target: BlockId,
780 incoming: &[(BlockId, ValueId)],
781 insertions: &[BlockId],
782 ) -> bool {
783 let has_backedge_incoming =
784 incoming.iter().any(|&(pred, _)| dominators.dominates(target, pred));
785 let has_entry_edge = incoming.iter().any(|&(pred, _)| !dominators.dominates(target, pred))
786 || !insertions.is_empty();
787 has_backedge_incoming
788 && has_entry_edge
789 && insertions.iter().all(|&pred| !dominators.dominates(target, pred))
790 }
791
792 fn is_non_cyclic_diamond_insertion(
793 dominators: &DominatorTree,
794 target: BlockId,
795 incoming: &[(BlockId, ValueId)],
796 insertions: &[BlockId],
797 ) -> bool {
798 !incoming.is_empty()
799 && !insertions.is_empty()
800 && incoming.iter().all(|&(pred, _)| !dominators.dominates(target, pred))
801 && insertions.iter().all(|&pred| !dominators.dominates(target, pred))
802 }
803
804 fn locate_value(
807 &self,
808 func: &Function,
809 cx: &mut CandidateCx<'_>,
810 block: BlockId,
811 key_idx: usize,
812 ) -> Option<ValueId> {
813 if let Some(&cached) = cx.locate_cache.get(&(block, key_idx)) {
814 return cached;
815 }
816 let located = self.locate_value_uncached(func, cx, block, key_idx);
817 cx.locate_cache.insert((block, key_idx), located);
818 located
819 }
820
821 fn locate_value_uncached(
822 &self,
823 func: &Function,
824 cx: &mut CandidateCx<'_>,
825 block: BlockId,
826 key_idx: usize,
827 ) -> Option<ValueId> {
828 let key = cx.analysis.keys[key_idx];
829 for &inst_id in func.blocks[block].instructions.iter().rev() {
830 if let Some((gen_key, source)) = Self::gen_key_value(func, inst_id)
831 && gen_key == key
832 {
833 return match source {
836 GenSource::LoadResult => cx.analysis.inst_results.get(&inst_id).copied(),
837 GenSource::Stored(value) => Some(value),
838 };
839 }
840 if Self::inst_kills_key(func, inst_id, key) {
841 return None;
842 }
843 }
844
845 if !cx.analysis.ins.get(&block).is_some_and(|in_set| in_set.contains(key_idx)) {
849 return None;
850 }
851 let idom = cx.analysis.dominators.idom(block)?;
852 if idom == block {
853 return None;
854 }
855 if cx.analysis.path_kills_key(idom, block, key_idx) {
859 return None;
860 }
861 self.locate_value(func, cx, idom, key_idx)
862 }
863
864 fn apply_candidate(
865 &mut self,
866 func: &mut Function,
867 candidate: Candidate,
868 eliminated_keys: &mut FxHashSet<(LoadKey, BlockId)>,
869 inserted_insts: &mut FxHashSet<InstId>,
870 ) {
871 let Candidate { target, key, result_ty, kind, metadata, loads, mut incoming, insertions } =
872 candidate;
873
874 eliminated_keys.insert((key, target));
875
876 let fully_available = insertions.is_empty();
877 for block in insertions {
878 let new_inst = func.alloc_inst(Instruction {
879 kind: kind.clone(),
880 result_ty: Some(result_ty),
881 metadata: metadata.clone(),
882 });
883 let value = func.alloc_value(Value::Inst(new_inst));
884 func.blocks[block].instructions.push(new_inst);
885 incoming.push((block, value));
886 inserted_insts.insert(new_inst);
887 self.stats.loads_inserted += 1;
888 }
889 incoming.sort_by_key(|(block, _)| block.index());
890
891 let first_result = loads[0].1;
895 let replacement = match incoming.first() {
896 Some(&(_, first))
897 if fully_available
898 && first != first_result
899 && incoming.iter().all(|&(_, value)| value == first) =>
900 {
901 first
902 }
903 _ => {
904 let phi_inst =
905 func.alloc_inst(Instruction::new(InstKind::Phi(incoming), Some(result_ty)));
906 let phi_value = func.alloc_value(Value::Inst(phi_inst));
907 let phi_count = func.blocks[target]
908 .instructions
909 .iter()
910 .take_while(|&&inst_id| {
911 matches!(func.instructions[inst_id].kind, InstKind::Phi(_))
912 })
913 .count();
914 func.blocks[target].instructions.insert(phi_count, phi_inst);
915 phi_value
916 }
917 };
918
919 for &(_, load_result) in &loads {
920 Self::replace_uses(func, load_result, replacement);
921 }
922 let load_insts: FxHashSet<InstId> = loads.iter().map(|&(inst_id, _)| inst_id).collect();
923 func.blocks[target].instructions.retain(|&inst_id| !load_insts.contains(&inst_id));
924 self.stats.loads_eliminated += loads.len();
925 }
926
927 fn gen_key_value(func: &Function, inst_id: InstId) -> Option<(LoadKey, GenSource)> {
931 match func.instructions[inst_id].kind {
932 InstKind::SLoad(slot) => {
933 Some((LoadKey::Storage(func.storage_alias(inst_id, slot)), GenSource::LoadResult))
934 }
935 InstKind::SStore(slot, value) => Some((
936 LoadKey::Storage(func.storage_alias(inst_id, slot)),
937 GenSource::Stored(value),
938 )),
939 InstKind::TLoad(slot) => {
940 Some((LoadKey::Transient(func.storage_alias(inst_id, slot)), GenSource::LoadResult))
941 }
942 InstKind::TStore(slot, value) => Some((
943 LoadKey::Transient(func.storage_alias(inst_id, slot)),
944 GenSource::Stored(value),
945 )),
946 InstKind::MLoad(addr) => Self::mem_addr(func, inst_id, addr)
947 .map(|addr| (LoadKey::Memory(addr), GenSource::LoadResult)),
948 InstKind::MStore(addr, value) => Self::mem_addr(func, inst_id, addr)
949 .map(|addr| (LoadKey::Memory(addr), GenSource::Stored(value))),
950 InstKind::Keccak256(offset, size) => {
951 let addr = Self::mem_addr(func, inst_id, offset)?;
952 let size = match func.value_u64(size) {
953 Some(size) => KeccakSize::Const(size),
954 None => KeccakSize::Dyn(size),
955 };
956 Some((LoadKey::Keccak(addr, size), GenSource::LoadResult))
957 }
958 _ => None,
959 }
960 }
961
962 fn inst_kills_key(func: &Function, inst_id: InstId, key: LoadKey) -> bool {
964 let kind = &func.instructions[inst_id].kind;
965 match key {
966 LoadKey::Storage(alias) => match *kind {
967 InstKind::SStore(slot, _) => func.storage_alias(inst_id, slot).may_alias(alias),
968 _ => kind.may_mutate_storage(),
971 },
972 LoadKey::Transient(alias) => match *kind {
973 InstKind::TStore(slot, _) => func.storage_alias(inst_id, slot).may_alias(alias),
974 _ => kind.may_mutate_transient_storage(),
975 },
976 LoadKey::Memory(addr) => Self::memory_write_clobbers(func, inst_id, addr, Some(32)),
977 LoadKey::Keccak(addr, size) => {
978 let size = match size {
979 KeccakSize::Const(size) => Some(size),
980 KeccakSize::Dyn(_) => None,
981 };
982 Self::memory_write_clobbers(func, inst_id, addr, size)
983 }
984 }
985 }
986
987 fn memory_write_clobbers(
991 func: &Function,
992 inst_id: InstId,
993 read: MemAddr,
994 read_size: Option<u64>,
995 ) -> bool {
996 let kind = &func.instructions[inst_id].kind;
997 let (dest, write_size) = match *kind {
998 InstKind::MStore(dest, _) => (dest, Some(32)),
999 InstKind::MStore8(dest, _) => (dest, Some(1)),
1000 InstKind::MCopy(dest, _, size)
1001 | InstKind::CalldataCopy(dest, _, size)
1002 | InstKind::CodeCopy(dest, _, size)
1003 | InstKind::ReturnDataCopy(dest, _, size) => (dest, func.value_u64(size)),
1004 InstKind::ExtCodeCopy(_, dest, _, size) => (dest, func.value_u64(size)),
1005 _ => return kind.may_mutate_memory(),
1008 };
1009
1010 let write_region = func.instructions[inst_id]
1011 .metadata
1012 .memory_region()
1013 .unwrap_or_else(|| func.memory_region_for_addr(dest));
1014 if read.region != MemoryRegion::Unknown
1015 && write_region != MemoryRegion::Unknown
1016 && read.region != write_region
1017 {
1018 return false;
1019 }
1020 let (write_base, write_offset) = Self::memory_addr_base_offset(func, dest);
1021 if read.base != write_base {
1022 return true;
1023 }
1024 let (Some(read_size), Some(write_offset), Some(write_size)) =
1025 (read_size, write_offset, write_size)
1026 else {
1027 return true;
1028 };
1029 mir_utils::ranges_overlap(read.offset, read_size, write_offset, write_size)
1030 }
1031
1032 fn mem_addr(func: &Function, inst_id: InstId, addr: ValueId) -> Option<MemAddr> {
1033 let region = func.instructions[inst_id]
1034 .metadata
1035 .memory_region()
1036 .unwrap_or_else(|| func.memory_region_for_addr(addr));
1037 let (base, offset) = Self::memory_addr_base_offset(func, addr);
1038 Some(MemAddr { region, base, offset: offset? })
1039 }
1040
1041 fn memory_addr_base_offset(func: &Function, addr: ValueId) -> (Option<ValueId>, Option<u64>) {
1042 if let Some((base, offset)) = Self::offset_chain(func, addr, 0) {
1043 if let Some(offset) = mir_utils::u256_to_u64(offset) {
1044 return (Some(base), Some(offset));
1045 }
1046 return (Some(addr), Some(0));
1047 }
1048 match func.value(addr) {
1049 Value::Immediate(imm) => (None, imm.as_u256().and_then(mir_utils::u256_to_u64)),
1050 Value::Arg { .. } | Value::Inst(_) | Value::Undef(_) | Value::Error(_) => {
1051 (Some(addr), Some(0))
1052 }
1053 }
1054 }
1055
1056 fn offset_chain(func: &Function, value: ValueId, depth: usize) -> Option<(ValueId, U256)> {
1060 if depth >= 4 {
1061 return None;
1062 }
1063 match func.value(value) {
1064 Value::Immediate(_) => None,
1065 Value::Arg { .. } | Value::Undef(_) | Value::Error(_) => Some((value, U256::ZERO)),
1066 Value::Inst(inst_id) => match func.instructions[*inst_id].kind {
1067 InstKind::Add(a, b) => {
1068 if let Some(offset) = func.value_u256(b) {
1069 let (base, existing) = Self::offset_chain(func, a, depth + 1)?;
1070 Some((base, existing.wrapping_add(offset)))
1071 } else if let Some(offset) = func.value_u256(a) {
1072 let (base, existing) = Self::offset_chain(func, b, depth + 1)?;
1073 Some((base, existing.wrapping_add(offset)))
1074 } else {
1075 Some((value, U256::ZERO))
1076 }
1077 }
1078 InstKind::Sub(a, b) => {
1079 let offset = func.value_u256(b)?;
1080 let (base, existing) = Self::offset_chain(func, a, depth + 1)?;
1081 Some((base, existing.wrapping_sub(offset)))
1082 }
1083 _ => Some((value, U256::ZERO)),
1084 },
1085 }
1086 }
1087
1088 fn can_insert_on_edge(func: &Function, pred: BlockId, target: BlockId) -> bool {
1091 matches!(func.blocks[pred].terminator, Some(Terminator::Jump(jump_target)) if jump_target == target)
1092 }
1093
1094 fn operands_dominate_block(
1095 func: &Function,
1096 kind: &InstKind,
1097 block: BlockId,
1098 analysis: &Analysis,
1099 ) -> bool {
1100 kind.operands().into_iter().all(|value| match func.value(value) {
1101 Value::Immediate(_) | Value::Arg { .. } | Value::Undef(_) | Value::Error(_) => true,
1102 Value::Inst(inst_id) => analysis
1103 .inst_blocks
1104 .get(inst_id)
1105 .is_some_and(|def_block| analysis.dominators.dominates(*def_block, block)),
1106 })
1107 }
1108
1109 fn replace_uses(func: &mut Function, from: ValueId, to: ValueId) {
1112 for inst in func.instructions.iter_mut() {
1113 let mut changed = false;
1114 inst.kind.visit_operands_mut(|value| {
1115 if *value == from {
1116 *value = to;
1117 changed = true;
1118 }
1119 });
1120 if !changed {
1121 continue;
1122 }
1123 if mir_utils::is_memory_inst(&inst.kind) {
1125 inst.metadata.set_memory_region(None);
1126 }
1127 if matches!(
1128 inst.kind,
1129 InstKind::SLoad(_)
1130 | InstKind::SStore(_, _)
1131 | InstKind::TLoad(_)
1132 | InstKind::TStore(_, _)
1133 ) {
1134 inst.metadata.set_storage_alias(None);
1135 }
1136 }
1137
1138 for block in func.blocks.iter_mut() {
1139 if let Some(term) = &mut block.terminator {
1140 Self::replace_terminator_uses(term, from, to);
1141 }
1142 }
1143 }
1144
1145 fn replace_terminator_uses(term: &mut Terminator, from: ValueId, to: ValueId) {
1146 let replace = |value: &mut ValueId| {
1147 if *value == from {
1148 *value = to;
1149 }
1150 };
1151
1152 match term {
1153 Terminator::Jump(_) | Terminator::Stop | Terminator::Invalid => {}
1154 Terminator::Branch { condition, .. } => replace(condition),
1155 Terminator::Switch { value, cases, .. } => {
1156 replace(value);
1157 for (case, _) in cases {
1158 replace(case);
1159 }
1160 }
1161 Terminator::Return { values } => {
1162 for value in values {
1163 replace(value);
1164 }
1165 }
1166 Terminator::Revert { offset, size } | Terminator::ReturnData { offset, size } => {
1167 replace(offset);
1168 replace(size);
1169 }
1170 Terminator::SelfDestruct { recipient } => replace(recipient),
1171 }
1172 }
1173}