use crate::mir::{BasicBlock, BlockId, Function, InstKind, Terminator, ValueId};
use alloy_primitives::U256;
use smallvec::smallvec;
use solar_data_structures::map::FxHashMap;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum StorageAliasScope {
Storage,
StorageAndTransient,
}
pub(crate) fn split_edge(func: &mut Function, pred: BlockId, succ: BlockId) -> BlockId {
debug_assert_ne!(succ, func.entry_block, "the entry block has no predecessor edges to split");
let new_block = func.blocks.push(BasicBlock {
instructions: Vec::new(),
terminator: Some(Terminator::Jump(succ)),
predecessors: smallvec![pred],
});
let mut retargeted = false;
let mut retarget = |block: &mut BlockId| {
if *block == succ {
*block = new_block;
retargeted = true;
}
};
match func.blocks[pred].terminator.as_mut().expect("predecessor must have a terminator") {
Terminator::Jump(target) => retarget(target),
Terminator::Branch { then_block, else_block, .. } => {
retarget(then_block);
retarget(else_block);
}
Terminator::Switch { default, cases, .. } => {
retarget(default);
for (_, case_block) in cases {
retarget(case_block);
}
}
term => panic!("terminator `{}` has no successor edges to split", term.mnemonic()),
}
assert!(retargeted, "bb{} does not branch to bb{}", pred.index(), succ.index());
let predecessors = &mut func.blocks[succ].predecessors;
let first = predecessors
.iter()
.position(|&block| block == pred)
.expect("successor must list pred as a predecessor");
predecessors[first] = new_block;
predecessors.retain(|&mut block| block != pred);
for &inst_id in &func.blocks[succ].instructions {
if let InstKind::Phi(incoming) = &mut func.instructions[inst_id].kind {
for (block, _) in incoming.iter_mut() {
if *block == pred {
*block = new_block;
}
}
}
}
new_block
}
pub(crate) fn repair_reachability_phis(func: &mut Function) -> bool {
let mut edges = Vec::new();
for (block, bb) in func.blocks.iter_enumerated() {
if let Some(term) = &bb.terminator {
edges.push((block, term.successors()));
}
}
for block in func.blocks.iter_mut() {
block.predecessors.clear();
}
for (block, successors) in edges {
for succ in successors {
func.blocks[succ].predecessors.push(block);
}
}
let mut changed = false;
for block_id in func.blocks.indices() {
let predecessors = func.blocks[block_id].predecessors.clone();
for &inst_id in &func.blocks[block_id].instructions {
if let InstKind::Phi(incoming) = &mut func.instructions[inst_id].kind {
let len_before = incoming.len();
incoming.retain(|(pred, _)| predecessors.contains(pred));
changed |= incoming.len() != len_before;
}
}
}
changed
}
pub(crate) fn resolve_replacement(
mut value: ValueId,
replacements: &FxHashMap<ValueId, ValueId>,
) -> ValueId {
while let Some(&replacement) = replacements.get(&value) {
if replacement == value {
break;
}
value = replacement;
}
value
}
pub(crate) fn replace_inst_uses(
kind: &mut InstKind,
replacements: &FxHashMap<ValueId, ValueId>,
) -> usize {
replace_inst_operands(kind, replacements, |value, replacements| {
replacements.get(&value).copied().unwrap_or(value)
})
}
pub(crate) fn replace_inst_uses_canonicalized(
kind: &mut InstKind,
replacements: &FxHashMap<ValueId, ValueId>,
) -> usize {
replace_inst_operands(kind, replacements, resolve_replacement)
}
pub(crate) fn replace_terminator_uses(
term: &mut Terminator,
replacements: &FxHashMap<ValueId, ValueId>,
) -> usize {
replace_terminator_operands(term, replacements, |value, replacements| {
replacements.get(&value).copied().unwrap_or(value)
})
}
pub(crate) fn replace_terminator_uses_canonicalized(
term: &mut Terminator,
replacements: &FxHashMap<ValueId, ValueId>,
) -> usize {
replace_terminator_operands(term, replacements, resolve_replacement)
}
pub(crate) fn u256_to_u64(value: U256) -> Option<u64> {
value.try_into().ok()
}
pub(crate) fn ranges_overlap(a_start: u64, a_size: u64, b_start: u64, b_size: u64) -> bool {
let Some(a_end) = a_start.checked_add(a_size) else {
return true;
};
let Some(b_end) = b_start.checked_add(b_size) else {
return true;
};
a_start < b_end && b_start < a_end
}
pub(crate) fn is_memory_inst(kind: &InstKind) -> bool {
matches!(
kind,
InstKind::MLoad(_)
| InstKind::MStore(_, _)
| InstKind::MStore8(_, _)
| InstKind::MCopy(_, _, _)
| InstKind::CalldataCopy(_, _, _)
| InstKind::CodeCopy(_, _, _)
| InstKind::ReturnDataCopy(_, _, _)
| InstKind::ExtCodeCopy(_, _, _, _)
| InstKind::Keccak256(_, _)
)
}
fn replace_inst_operands(
kind: &mut InstKind,
replacements: &FxHashMap<ValueId, ValueId>,
replacement: impl Fn(ValueId, &FxHashMap<ValueId, ValueId>) -> ValueId,
) -> usize {
let mut replaced = 0;
kind.visit_operands_mut(|value| {
let new_value = replacement(*value, replacements);
if new_value != *value {
*value = new_value;
replaced += 1;
}
});
replaced
}
fn replace_terminator_operands(
term: &mut Terminator,
replacements: &FxHashMap<ValueId, ValueId>,
replacement: impl Fn(ValueId, &FxHashMap<ValueId, ValueId>) -> ValueId,
) -> usize {
let mut replaced = 0;
let mut replace = |value: &mut ValueId| {
let new_value = replacement(*value, replacements);
if new_value != *value {
*value = new_value;
replaced += 1;
}
};
match term {
Terminator::Jump(_) | Terminator::Stop | Terminator::Invalid => {}
Terminator::Branch { condition, .. } => replace(condition),
Terminator::Switch { value, cases, .. } => {
replace(value);
for (case_value, _) in cases {
replace(case_value);
}
}
Terminator::Return { values } => {
for value in values {
replace(value);
}
}
Terminator::Revert { offset, size } | Terminator::ReturnData { offset, size } => {
replace(offset);
replace(size);
}
Terminator::SelfDestruct { recipient } => replace(recipient),
}
replaced
}