use super::{
BlockId, Function, FunctionId, Immediate, InstId, InstKind, Instruction, MemoryRegion, MirType,
StorageAlias, Terminator, Value, ValueId,
};
use alloy_primitives::U256;
use smallvec::SmallVec;
pub struct FunctionBuilder<'a> {
func: &'a mut Function,
current_block: BlockId,
}
impl<'a> FunctionBuilder<'a> {
pub fn new(func: &'a mut Function) -> Self {
let entry = func.entry_block;
Self { func, current_block: entry }
}
#[must_use]
pub const fn current_block(&self) -> BlockId {
self.current_block
}
pub fn switch_to_block(&mut self, block: BlockId) {
self.current_block = block;
}
pub fn create_block(&mut self) -> BlockId {
self.func.alloc_block()
}
pub fn add_param(&mut self, ty: MirType) -> ValueId {
let index = self.func.params.len() as u32;
self.func.params.push(ty);
self.func.alloc_value(Value::Arg { index, ty })
}
pub fn add_return(&mut self, ty: MirType) {
self.func.returns.push(ty);
}
pub fn imm_u256(&mut self, value: U256) -> ValueId {
self.func.alloc_value(Value::Immediate(Immediate::uint256(value)))
}
pub fn imm_u64(&mut self, value: u64) -> ValueId {
self.imm_u256(U256::from(value))
}
pub fn imm_bool(&mut self, value: bool) -> ValueId {
self.func.alloc_value(Value::Immediate(Immediate::bool(value)))
}
pub fn undef(&mut self, ty: MirType) -> ValueId {
self.func.alloc_value(Value::Undef(ty))
}
pub fn error_value(&mut self, guar: solar_interface::diagnostics::ErrorGuaranteed) -> ValueId {
self.func.alloc_value(Value::Error(guar))
}
fn emit_inst_raw(&mut self, kind: InstKind, result_ty: Option<MirType>) -> InstId {
let mut inst = Instruction::new(kind, result_ty);
inst.metadata.set_effect(Some(inst.kind.effect_kind()));
inst.metadata.set_memory_region(self.memory_region_for_inst(&inst.kind));
inst.metadata.set_storage_alias(self.storage_alias_for_inst(&inst.kind));
let inst_id = self.func.alloc_inst(inst);
self.func.blocks[self.current_block].instructions.push(inst_id);
inst_id
}
fn emit_inst(&mut self, kind: InstKind, result_ty: Option<MirType>) -> ValueId {
debug_assert!(result_ty.is_some(), "value-producing instructions must have a result type");
let inst_id = self.emit_inst_raw(kind, result_ty);
self.func.alloc_value(Value::Inst(inst_id))
}
fn emit_void_inst(&mut self, kind: InstKind) {
self.emit_inst_raw(kind, None);
}
fn memory_region_for_inst(&self, kind: &InstKind) -> Option<MemoryRegion> {
let addr = match *kind {
InstKind::MLoad(addr)
| InstKind::MStore(addr, _)
| InstKind::MStore8(addr, _)
| InstKind::Keccak256(addr, _) => addr,
InstKind::MCopy(dest, _, _)
| InstKind::CalldataCopy(dest, _, _)
| InstKind::CodeCopy(dest, _, _)
| InstKind::ReturnDataCopy(dest, _, _)
| InstKind::ExtCodeCopy(_, dest, _, _) => dest,
_ => return None,
};
Some(self.memory_region_for_addr(addr))
}
fn memory_region_for_addr(&self, addr: ValueId) -> MemoryRegion {
match self.func.value(addr) {
Value::Immediate(imm)
if imm.as_u256().is_some_and(|value| value < U256::from(0x80)) =>
{
MemoryRegion::Scratch
}
Value::Inst(inst_id) => match self.func.instructions[*inst_id].kind {
InstKind::InternalFrameAddr(_) => MemoryRegion::InternalFrame,
InstKind::Add(lhs, rhs) if self.is_internal_frame_add(lhs, rhs) => {
MemoryRegion::InternalFrame
}
InstKind::Sub(lhs, rhs)
if self.is_internal_frame_addr(lhs) && self.is_immediate(rhs) =>
{
MemoryRegion::InternalFrame
}
_ => MemoryRegion::Unknown,
},
Value::Arg { .. } | Value::Immediate(_) | Value::Undef(_) | Value::Error(_) => {
MemoryRegion::Unknown
}
}
}
fn is_internal_frame_add(&self, lhs: ValueId, rhs: ValueId) -> bool {
(self.is_internal_frame_addr(lhs) && self.is_immediate(rhs))
|| (self.is_internal_frame_addr(rhs) && self.is_immediate(lhs))
}
fn is_internal_frame_addr(&self, value: ValueId) -> bool {
matches!(
self.func.value(value),
Value::Inst(inst_id)
if matches!(self.func.instructions[*inst_id].kind, InstKind::InternalFrameAddr(_))
)
}
fn is_immediate(&self, value: ValueId) -> bool {
matches!(self.func.value(value), Value::Immediate(_))
}
fn storage_alias_for_inst(&self, kind: &InstKind) -> Option<StorageAlias> {
match *kind {
InstKind::SLoad(slot) | InstKind::SStore(slot, _) => Some(self.storage_alias(slot)),
_ => None,
}
}
fn storage_alias(&self, slot: ValueId) -> StorageAlias {
StorageAlias::for_value(self.func, slot)
}
pub fn add(&mut self, a: ValueId, b: ValueId) -> ValueId {
self.emit_inst(InstKind::Add(a, b), Some(MirType::uint256()))
}
pub fn sub(&mut self, a: ValueId, b: ValueId) -> ValueId {
self.emit_inst(InstKind::Sub(a, b), Some(MirType::uint256()))
}
pub fn mul(&mut self, a: ValueId, b: ValueId) -> ValueId {
self.emit_inst(InstKind::Mul(a, b), Some(MirType::uint256()))
}
pub fn div(&mut self, a: ValueId, b: ValueId) -> ValueId {
self.emit_inst(InstKind::Div(a, b), Some(MirType::uint256()))
}
pub fn sdiv(&mut self, a: ValueId, b: ValueId) -> ValueId {
self.emit_inst(InstKind::SDiv(a, b), Some(MirType::int256()))
}
pub fn mod_(&mut self, a: ValueId, b: ValueId) -> ValueId {
self.emit_inst(InstKind::Mod(a, b), Some(MirType::uint256()))
}
pub fn addmod(&mut self, a: ValueId, b: ValueId, n: ValueId) -> ValueId {
self.emit_inst(InstKind::AddMod(a, b, n), Some(MirType::uint256()))
}
pub fn mulmod(&mut self, a: ValueId, b: ValueId, n: ValueId) -> ValueId {
self.emit_inst(InstKind::MulMod(a, b, n), Some(MirType::uint256()))
}
pub fn smod(&mut self, a: ValueId, b: ValueId) -> ValueId {
self.emit_inst(InstKind::SMod(a, b), Some(MirType::int256()))
}
pub fn exp(&mut self, a: ValueId, b: ValueId) -> ValueId {
self.emit_inst(InstKind::Exp(a, b), Some(MirType::uint256()))
}
pub fn and(&mut self, a: ValueId, b: ValueId) -> ValueId {
self.emit_inst(InstKind::And(a, b), Some(MirType::uint256()))
}
pub fn or(&mut self, a: ValueId, b: ValueId) -> ValueId {
self.emit_inst(InstKind::Or(a, b), Some(MirType::uint256()))
}
pub fn xor(&mut self, a: ValueId, b: ValueId) -> ValueId {
self.emit_inst(InstKind::Xor(a, b), Some(MirType::uint256()))
}
pub fn not(&mut self, a: ValueId) -> ValueId {
self.emit_inst(InstKind::Not(a), Some(MirType::uint256()))
}
pub fn shl(&mut self, shift: ValueId, value: ValueId) -> ValueId {
self.emit_inst(InstKind::Shl(shift, value), Some(MirType::uint256()))
}
pub fn shr(&mut self, shift: ValueId, value: ValueId) -> ValueId {
self.emit_inst(InstKind::Shr(shift, value), Some(MirType::uint256()))
}
pub fn sar(&mut self, shift: ValueId, value: ValueId) -> ValueId {
self.emit_inst(InstKind::Sar(shift, value), Some(MirType::int256()))
}
pub fn lt(&mut self, a: ValueId, b: ValueId) -> ValueId {
self.emit_inst(InstKind::Lt(a, b), Some(MirType::Bool))
}
pub fn gt(&mut self, a: ValueId, b: ValueId) -> ValueId {
self.emit_inst(InstKind::Gt(a, b), Some(MirType::Bool))
}
pub fn slt(&mut self, a: ValueId, b: ValueId) -> ValueId {
self.emit_inst(InstKind::SLt(a, b), Some(MirType::Bool))
}
pub fn sgt(&mut self, a: ValueId, b: ValueId) -> ValueId {
self.emit_inst(InstKind::SGt(a, b), Some(MirType::Bool))
}
pub fn eq(&mut self, a: ValueId, b: ValueId) -> ValueId {
self.emit_inst(InstKind::Eq(a, b), Some(MirType::Bool))
}
pub fn iszero(&mut self, a: ValueId) -> ValueId {
self.emit_inst(InstKind::IsZero(a), Some(MirType::Bool))
}
pub fn byte(&mut self, index: ValueId, value: ValueId) -> ValueId {
self.emit_inst(InstKind::Byte(index, value), Some(MirType::uint256()))
}
pub fn signextend(&mut self, size: ValueId, value: ValueId) -> ValueId {
self.emit_inst(InstKind::SignExtend(size, value), Some(MirType::int256()))
}
pub fn mload(&mut self, offset: ValueId) -> ValueId {
self.emit_inst(InstKind::MLoad(offset), Some(MirType::uint256()))
}
pub fn mstore(&mut self, offset: ValueId, value: ValueId) {
self.emit_void_inst(InstKind::MStore(offset, value))
}
pub fn mstore8(&mut self, offset: ValueId, value: ValueId) {
self.emit_void_inst(InstKind::MStore8(offset, value))
}
pub fn msize(&mut self) -> ValueId {
self.emit_inst(InstKind::MSize, Some(MirType::uint256()))
}
pub fn mcopy(&mut self, dest: ValueId, src: ValueId, len: ValueId) {
self.emit_void_inst(InstKind::MCopy(dest, src, len))
}
pub fn sload(&mut self, slot: ValueId) -> ValueId {
self.emit_inst(InstKind::SLoad(slot), Some(MirType::uint256()))
}
pub fn sstore(&mut self, slot: ValueId, value: ValueId) {
self.emit_void_inst(InstKind::SStore(slot, value))
}
pub fn tload(&mut self, slot: ValueId) -> ValueId {
self.emit_inst(InstKind::TLoad(slot), Some(MirType::uint256()))
}
pub fn tstore(&mut self, slot: ValueId, value: ValueId) {
self.emit_void_inst(InstKind::TStore(slot, value))
}
pub fn calldataload(&mut self, offset: ValueId) -> ValueId {
self.emit_inst(InstKind::CalldataLoad(offset), Some(MirType::uint256()))
}
pub fn calldatasize(&mut self) -> ValueId {
self.emit_inst(InstKind::CalldataSize, Some(MirType::uint256()))
}
pub fn calldatacopy(&mut self, dest: ValueId, offset: ValueId, size: ValueId) {
self.emit_void_inst(InstKind::CalldataCopy(dest, offset, size))
}
pub fn codesize(&mut self) -> ValueId {
self.emit_inst(InstKind::CodeSize, Some(MirType::uint256()))
}
pub fn extcodesize(&mut self, addr: ValueId) -> ValueId {
self.emit_inst(InstKind::ExtCodeSize(addr), Some(MirType::uint256()))
}
pub fn load_immutable(&mut self, offset: u32) -> ValueId {
self.emit_inst(InstKind::LoadImmutable(offset), Some(MirType::uint256()))
}
pub fn extcodecopy(&mut self, addr: ValueId, dest: ValueId, offset: ValueId, size: ValueId) {
self.emit_void_inst(InstKind::ExtCodeCopy(addr, dest, offset, size))
}
pub fn extcodehash(&mut self, addr: ValueId) -> ValueId {
self.emit_inst(InstKind::ExtCodeHash(addr), Some(MirType::uint256()))
}
pub fn returndatasize(&mut self) -> ValueId {
self.emit_inst(InstKind::ReturnDataSize, Some(MirType::uint256()))
}
pub fn returndatacopy(&mut self, dest: ValueId, offset: ValueId, size: ValueId) {
self.emit_void_inst(InstKind::ReturnDataCopy(dest, offset, size))
}
pub fn internal_call(
&mut self,
function: FunctionId,
args: Vec<ValueId>,
result_ty: MirType,
returns: usize,
) -> ValueId {
let returns = u32::try_from(returns).expect("too many internal call return values");
self.emit_inst(
InstKind::InternalCall { function, args: args.into(), returns },
Some(result_ty),
)
}
pub fn internal_call_void(&mut self, function: FunctionId, args: Vec<ValueId>, returns: usize) {
let returns = u32::try_from(returns).expect("too many internal call return values");
self.emit_void_inst(InstKind::InternalCall { function, args: args.into(), returns });
}
pub fn internal_frame_addr(&mut self, offset: u64) -> ValueId {
self.emit_inst(InstKind::InternalFrameAddr(offset), Some(MirType::MemPtr))
}
pub fn caller(&mut self) -> ValueId {
self.emit_inst(InstKind::Caller, Some(MirType::Address))
}
pub fn callvalue(&mut self) -> ValueId {
self.emit_inst(InstKind::CallValue, Some(MirType::uint256()))
}
pub fn origin(&mut self) -> ValueId {
self.emit_inst(InstKind::Origin, Some(MirType::Address))
}
pub fn gasprice(&mut self) -> ValueId {
self.emit_inst(InstKind::GasPrice, Some(MirType::uint256()))
}
pub fn blockhash(&mut self, block_num: ValueId) -> ValueId {
self.emit_inst(InstKind::BlockHash(block_num), Some(MirType::FixedBytes(32)))
}
pub fn coinbase(&mut self) -> ValueId {
self.emit_inst(InstKind::Coinbase, Some(MirType::Address))
}
pub fn timestamp(&mut self) -> ValueId {
self.emit_inst(InstKind::Timestamp, Some(MirType::uint256()))
}
pub fn number(&mut self) -> ValueId {
self.emit_inst(InstKind::BlockNumber, Some(MirType::uint256()))
}
pub fn prevrandao(&mut self) -> ValueId {
self.emit_inst(InstKind::PrevRandao, Some(MirType::uint256()))
}
pub fn gaslimit(&mut self) -> ValueId {
self.emit_inst(InstKind::GasLimit, Some(MirType::uint256()))
}
pub fn chainid(&mut self) -> ValueId {
self.emit_inst(InstKind::ChainId, Some(MirType::uint256()))
}
pub fn address(&mut self) -> ValueId {
self.emit_inst(InstKind::Address, Some(MirType::Address))
}
pub fn balance(&mut self, addr: ValueId) -> ValueId {
self.emit_inst(InstKind::Balance(addr), Some(MirType::uint256()))
}
pub fn selfbalance(&mut self) -> ValueId {
self.emit_inst(InstKind::SelfBalance, Some(MirType::uint256()))
}
pub fn gas(&mut self) -> ValueId {
self.emit_inst(InstKind::Gas, Some(MirType::uint256()))
}
pub fn keccak256(&mut self, offset: ValueId, size: ValueId) -> ValueId {
self.emit_inst(InstKind::Keccak256(offset, size), Some(MirType::bytes32()))
}
pub fn basefee(&mut self) -> ValueId {
self.emit_inst(InstKind::BaseFee, Some(MirType::uint256()))
}
pub fn blobbasefee(&mut self) -> ValueId {
self.emit_inst(InstKind::BlobBaseFee, Some(MirType::uint256()))
}
pub fn blobhash(&mut self, index: ValueId) -> ValueId {
self.emit_inst(InstKind::BlobHash(index), Some(MirType::FixedBytes(32)))
}
#[allow(clippy::too_many_arguments)]
pub fn call(
&mut self,
gas: ValueId,
addr: ValueId,
value: ValueId,
args_offset: ValueId,
args_size: ValueId,
ret_offset: ValueId,
ret_size: ValueId,
) -> ValueId {
self.emit_inst(
InstKind::Call { gas, addr, value, args_offset, args_size, ret_offset, ret_size },
Some(MirType::uint256()),
)
}
pub fn staticcall(
&mut self,
gas: ValueId,
addr: ValueId,
args_offset: ValueId,
args_size: ValueId,
ret_offset: ValueId,
ret_size: ValueId,
) -> ValueId {
self.emit_inst(
InstKind::StaticCall { gas, addr, args_offset, args_size, ret_offset, ret_size },
Some(MirType::uint256()),
)
}
pub fn delegatecall(
&mut self,
gas: ValueId,
addr: ValueId,
args_offset: ValueId,
args_size: ValueId,
ret_offset: ValueId,
ret_size: ValueId,
) -> ValueId {
self.emit_inst(
InstKind::DelegateCall { gas, addr, args_offset, args_size, ret_offset, ret_size },
Some(MirType::uint256()),
)
}
pub fn create(&mut self, value: ValueId, offset: ValueId, size: ValueId) -> ValueId {
self.emit_inst(InstKind::Create(value, offset, size), Some(MirType::Address))
}
pub fn create2(
&mut self,
value: ValueId,
offset: ValueId,
size: ValueId,
salt: ValueId,
) -> ValueId {
self.emit_inst(InstKind::Create2(value, offset, size, salt), Some(MirType::Address))
}
pub fn codecopy(&mut self, dest: ValueId, offset: ValueId, size: ValueId) {
self.emit_void_inst(InstKind::CodeCopy(dest, offset, size))
}
pub fn log0(&mut self, offset: ValueId, size: ValueId) {
self.emit_void_inst(InstKind::Log0(offset, size));
}
pub fn log1(&mut self, offset: ValueId, size: ValueId, topic1: ValueId) {
self.emit_void_inst(InstKind::Log1(offset, size, topic1));
}
pub fn log2(&mut self, offset: ValueId, size: ValueId, topic1: ValueId, topic2: ValueId) {
self.emit_void_inst(InstKind::Log2(offset, size, topic1, topic2));
}
pub fn log3(
&mut self,
offset: ValueId,
size: ValueId,
topic1: ValueId,
topic2: ValueId,
topic3: ValueId,
) {
self.emit_void_inst(InstKind::Log3(offset, size, topic1, topic2, topic3));
}
pub fn log4(
&mut self,
offset: ValueId,
size: ValueId,
topic1: ValueId,
topic2: ValueId,
topic3: ValueId,
topic4: ValueId,
) {
self.emit_void_inst(InstKind::Log4(offset, size, topic1, topic2, topic3, topic4));
}
pub fn select(&mut self, cond: ValueId, then_val: ValueId, else_val: ValueId) -> ValueId {
self.emit_inst(InstKind::Select(cond, then_val, else_val), Some(MirType::uint256()))
}
pub fn phi(&mut self, incoming: Vec<(BlockId, ValueId)>) -> ValueId {
self.emit_inst(InstKind::Phi(incoming), Some(MirType::uint256()))
}
pub fn add_phi_incoming(&mut self, phi: ValueId, block: BlockId, value: ValueId) {
let Value::Inst(inst_id) = *self.func.value(phi) else {
panic!("add_phi_incoming: value is not an instruction result");
};
let InstKind::Phi(incoming) = &mut self.func.instructions[inst_id].kind else {
panic!("add_phi_incoming: instruction is not a phi");
};
incoming.push((block, value));
}
pub fn jump(&mut self, target: BlockId) {
let block = &mut self.func.blocks[self.current_block];
block.terminator = Some(Terminator::Jump(target));
self.func.blocks[target].predecessors.push(self.current_block);
}
pub fn branch(&mut self, condition: ValueId, then_block: BlockId, else_block: BlockId) {
let block = &mut self.func.blocks[self.current_block];
block.terminator = Some(Terminator::Branch { condition, then_block, else_block });
self.func.blocks[then_block].predecessors.push(self.current_block);
self.func.blocks[else_block].predecessors.push(self.current_block);
}
pub fn switch(&mut self, value: ValueId, default: BlockId, cases: Vec<(ValueId, BlockId)>) {
let current = self.current_block;
self.func.blocks[current].terminator =
Some(Terminator::Switch { value, default, cases: cases.clone() });
self.func.blocks[default].predecessors.push(current);
for (_, case_block) in cases {
self.func.blocks[case_block].predecessors.push(current);
}
}
pub fn ret(&mut self, values: impl IntoIterator<Item = ValueId>) {
let values: SmallVec<[ValueId; 2]> = values.into_iter().collect();
self.func.blocks[self.current_block].terminator = Some(Terminator::Return { values });
}
pub fn revert(&mut self, offset: ValueId, size: ValueId) {
self.func.blocks[self.current_block].terminator = Some(Terminator::Revert { offset, size });
}
pub fn ret_data(&mut self, offset: ValueId, size: ValueId) {
self.func.blocks[self.current_block].terminator =
Some(Terminator::ReturnData { offset, size });
}
pub fn stop(&mut self) {
self.func.blocks[self.current_block].terminator = Some(Terminator::Stop);
}
pub fn invalid(&mut self) {
self.func.blocks[self.current_block].terminator = Some(Terminator::Invalid);
}
pub fn selfdestruct(&mut self, recipient: ValueId) {
self.func.blocks[self.current_block].terminator =
Some(Terminator::SelfDestruct { recipient });
}
#[must_use]
pub fn func(&self) -> &Function {
self.func
}
pub fn func_mut(&mut self) -> &mut Function {
self.func
}
}