mod abi_encode;
mod abi_packed;
mod bytes;
mod call;
mod checked_arith;
mod expr;
mod index;
mod stmt;
mod storage;
mod type_query;
use crate::{
IMMUTABLE_SCRATCH_BASE,
mir::{
BlockId, Function, FunctionAttributes, FunctionBuilder, FunctionId, IMMUTABLE_WORD_SIZE,
ImmutableSlot, MirType, Module, StorageSlot, ValueId,
},
};
use alloy_primitives::U256;
use solar_data_structures::{
Never,
map::{FxHashMap, FxHashSet},
};
use solar_interface::{Ident, Span, diagnostics::DiagMsg, kw, sym};
use solar_sema::{
hir::{self, ContractId, ElementaryType, FunctionId as HirFunctionId, VariableId, Visit},
ty::{Gcx, Ty, TyKind},
};
use std::ops::ControlFlow;
use self::storage::StorageLocation;
#[derive(Clone, Copy)]
pub struct LoopContext {
pub break_target: BlockId,
pub continue_target: BlockId,
}
#[derive(Clone, Copy)]
enum AbiParamSource {
ExternalCalldata,
ConstructorMemory,
}
pub struct Lowerer<'gcx> {
gcx: Gcx<'gcx>,
module: Module,
current_contract_id: Option<ContractId>,
storage_slots: FxHashMap<VariableId, u64>,
storage_locations: FxHashMap<VariableId, StorageLocation>,
next_storage_slot: u64,
next_storage_offset: u8,
immutable_slots: FxHashMap<VariableId, u32>,
next_immutable_offset: u32,
locals: FxHashMap<VariableId, ValueId>,
local_memory_slots: FxHashMap<VariableId, u64>,
next_local_memory_offset: u64,
contract_bytecodes: FxHashMap<ContractId, (Vec<u8>, usize)>,
loop_stack: Vec<LoopContext>,
assigned_vars: FxHashSet<VariableId>,
storage_ref_locals: FxHashSet<VariableId>,
inline_stack: Vec<HirFunctionId>,
hir_to_mir_functions: FxHashMap<HirFunctionId, FunctionId>,
hir_to_internal_mir_functions: FxHashMap<HirFunctionId, FunctionId>,
recursive_functions: FxHashMap<HirFunctionId, bool>,
lowering_functions: FxHashSet<HirFunctionId>,
lowering_constructor: bool,
lowering_internal_function: bool,
in_unchecked_block: bool,
current_return_tys: Vec<Ty<'gcx>>,
pub struct_storage_base_slots: FxHashMap<VariableId, u64>,
pub struct_field_offsets: FxHashMap<(hir::StructId, usize), u64>,
pub struct_field_memory_offsets: FxHashMap<(hir::StructId, usize), u64>,
}
impl<'gcx> Lowerer<'gcx> {
pub(super) fn err_value(
&self,
builder: &mut FunctionBuilder<'_>,
span: Span,
msg: impl Into<DiagMsg>,
) -> ValueId {
let guar = self.gcx.dcx().err(msg).span(span).emit();
builder.error_value(guar)
}
pub fn new(gcx: Gcx<'gcx>, name: Ident) -> Self {
if !gcx.has_typeck_results() {
gcx.dcx().emit_err(
name.span,
"tried to lower contract without typeck results; likely missing -Zcodegen",
);
}
Self {
gcx,
module: Module::new(name),
current_contract_id: None,
storage_slots: FxHashMap::default(),
storage_locations: FxHashMap::default(),
next_storage_slot: 0,
next_storage_offset: 0,
immutable_slots: FxHashMap::default(),
next_immutable_offset: 0,
locals: FxHashMap::default(),
local_memory_slots: FxHashMap::default(),
next_local_memory_offset: 0x80, contract_bytecodes: FxHashMap::default(),
loop_stack: Vec::new(),
assigned_vars: FxHashSet::default(),
storage_ref_locals: FxHashSet::default(),
inline_stack: Vec::new(),
hir_to_mir_functions: FxHashMap::default(),
hir_to_internal_mir_functions: FxHashMap::default(),
recursive_functions: FxHashMap::default(),
lowering_functions: FxHashSet::default(),
lowering_constructor: false,
lowering_internal_function: false,
in_unchecked_block: false,
current_return_tys: Vec::new(),
struct_storage_base_slots: FxHashMap::default(),
struct_field_offsets: FxHashMap::default(),
struct_field_memory_offsets: FxHashMap::default(),
}
}
pub fn push_loop(&mut self, ctx: LoopContext) {
self.loop_stack.push(ctx);
}
pub fn pop_loop(&mut self) {
self.loop_stack.pop();
}
pub fn current_loop(&self) -> Option<&LoopContext> {
self.loop_stack.last()
}
const MAX_INLINE_DEPTH: usize = 32;
const LOCAL_MEMORY_BASE: u64 = 0x80;
fn try_enter_inline(&mut self, func_id: HirFunctionId) -> bool {
if self.inline_stack.contains(&func_id) {
return false;
}
if self.inline_stack.len() >= Self::MAX_INLINE_DEPTH {
return false;
}
self.inline_stack.push(func_id);
true
}
fn exit_inline(&mut self) {
self.inline_stack.pop();
}
pub fn alloc_local_memory(&mut self, var_id: VariableId) -> u64 {
let offset = self.next_local_memory_offset;
self.next_local_memory_offset += 32; self.local_memory_slots.insert(var_id, offset);
offset
}
pub fn get_local_memory_offset(&self, var_id: &VariableId) -> Option<u64> {
self.local_memory_slots.get(var_id).copied()
}
pub fn local_memory_addr(&self, builder: &mut FunctionBuilder<'_>, offset: u64) -> ValueId {
if self.lowering_internal_function {
let header_size = 64;
let arg_size = (builder.func().params.len() as u64) * 32;
let return_size = (builder.func().returns.len() as u64) * 32;
let local_offset = offset.saturating_sub(Self::LOCAL_MEMORY_BASE);
builder.internal_frame_addr(header_size + arg_size + return_size + local_offset)
} else {
builder.imm_u64(offset)
}
}
pub fn immutable_scratch_addr(offset: u32) -> u64 {
IMMUTABLE_SCRATCH_BASE + u64::from(offset)
}
pub fn store_immutable_value(
&self,
builder: &mut FunctionBuilder<'_>,
offset: u32,
value: ValueId,
) {
let addr = builder.imm_u64(Self::immutable_scratch_addr(offset));
builder.mstore(addr, value);
}
pub fn load_immutable_value(&self, builder: &mut FunctionBuilder<'_>, offset: u32) -> ValueId {
if self.lowering_constructor {
let addr = builder.imm_u64(Self::immutable_scratch_addr(offset));
builder.mload(addr)
} else {
builder.load_immutable(offset)
}
}
pub fn register_contract_bytecode(&mut self, contract_id: ContractId, bytecode: Vec<u8>) {
let segment_idx = self.module.add_data_segment(bytecode.clone());
self.contract_bytecodes.insert(contract_id, (bytecode, segment_idx));
}
pub fn get_contract_bytecode(&self, contract_id: ContractId) -> Option<&(Vec<u8>, usize)> {
self.contract_bytecodes.get(&contract_id)
}
pub fn lower_contract(&mut self, contract_id: ContractId) {
let contract = self.gcx.hir.contract(contract_id);
self.current_contract_id = Some(contract_id);
if contract.kind == hir::ContractKind::Interface {
self.module.is_interface = true;
}
self.allocate_storage(contract_id);
let functions = self.collect_inherited_functions(contract_id);
if contract.ctor.is_none() {
self.generate_synthetic_constructor(contract_id);
}
for func_id in functions {
self.ensure_function_lowered(func_id);
}
self.current_contract_id = None;
}
fn collect_inherited_functions(&self, contract_id: ContractId) -> Vec<HirFunctionId> {
let contract = self.gcx.hir.contract(contract_id);
let linearized_bases = contract.linearized_bases;
let mut seen_selectors: FxHashSet<[u8; 4]> = FxHashSet::default();
let mut has_constructor = false;
let mut has_fallback = false;
let mut has_receive = false;
let mut functions = Vec::new();
for &base_id in linearized_bases.iter() {
let base_contract = self.gcx.hir.contract(base_id);
for func_id in base_contract.all_functions() {
let func = self.gcx.hir.function(func_id);
match func.kind {
hir::FunctionKind::Constructor => {
if base_id == contract_id && !has_constructor {
has_constructor = true;
functions.push(func_id);
}
}
hir::FunctionKind::Fallback => {
if !has_fallback {
has_fallback = true;
functions.push(func_id);
}
}
hir::FunctionKind::Receive => {
if !has_receive {
has_receive = true;
functions.push(func_id);
}
}
hir::FunctionKind::Function | hir::FunctionKind::Modifier => {
if base_id != contract_id && func.visibility == hir::Visibility::Private {
continue;
}
let is_external_abi = matches!(
func.visibility,
hir::Visibility::External | hir::Visibility::Public
);
if is_external_abi {
let selector = self.function_selector(func_id);
if seen_selectors.insert(selector) {
functions.push(func_id);
}
} else {
functions.push(func_id);
}
}
}
}
}
functions
}
fn generate_synthetic_constructor(&mut self, contract_id: ContractId) {
let contract = self.gcx.hir.contract(contract_id);
let linearized_bases = contract.linearized_bases;
let has_state_initializers = linearized_bases.iter().any(|&base_id| {
self.gcx.hir.contract(base_id).variables().any(|var_id| {
let var = self.gcx.hir.variable(var_id);
var.is_state_variable() && !var.is_constant() && var.initializer.is_some()
})
});
let has_base_constructors = linearized_bases.iter().any(|&base_id| {
base_id != contract_id && self.gcx.hir.contract(base_id).ctor.is_some()
});
if !has_state_initializers && !has_base_constructors {
return;
}
let ctor_name = Ident::new(kw::Constructor, Span::DUMMY);
let mut mir_func = Function::new(ctor_name);
mir_func.attributes = FunctionAttributes {
visibility: hir::Visibility::Public,
state_mutability: hir::StateMutability::NonPayable,
is_constructor: true,
is_fallback: false,
is_receive: false,
};
{
let mut builder = FunctionBuilder::new(&mut mir_func);
let saved_lowering_constructor = self.lowering_constructor;
let saved_lowering_internal_function = self.lowering_internal_function;
let saved_in_unchecked_block = self.in_unchecked_block;
let saved_current_return_tys = std::mem::take(&mut self.current_return_tys);
self.lowering_constructor = true;
self.lowering_internal_function = false;
self.in_unchecked_block = false;
self.lower_constructor_prelude(&mut builder, contract_id);
builder.stop();
self.lowering_constructor = saved_lowering_constructor;
self.lowering_internal_function = saved_lowering_internal_function;
self.in_unchecked_block = saved_in_unchecked_block;
self.current_return_tys = saved_current_return_tys;
}
self.module.add_function(mir_func);
}
fn allocate_storage(&mut self, contract_id: ContractId) {
let contract = self.gcx.hir.contract(contract_id);
let linearized_bases = contract.linearized_bases;
for &base_id in linearized_bases.iter().rev() {
let base_contract = self.gcx.hir.contract(base_id);
for var_id in base_contract.variables() {
if self.storage_slots.contains_key(&var_id) {
continue;
}
let var = self.gcx.hir.variable(var_id);
if var.is_state_variable() && var.is_immutable() {
let offset = self.next_immutable_offset;
self.next_immutable_offset = self
.next_immutable_offset
.checked_add(IMMUTABLE_WORD_SIZE as u32)
.expect("immutable offset overflow");
self.immutable_slots.insert(var_id, offset);
let mir_ty = self.lower_type_from_var(var);
self.module.add_immutable_slot(ImmutableSlot {
offset,
ty: mir_ty,
name: var.name,
});
} else if var.is_state_variable() && !var.is_constant() {
let var_ty = self.gcx.type_of_hir_ty(&var.ty);
let location = self.allocate_storage_location(var_ty, var.ty.span);
let base_slot = location.slot;
if matches!(var_ty.peel_refs().kind, TyKind::Struct(_)) {
self.struct_storage_base_slots.insert(var_id, base_slot);
}
self.storage_slots.insert(var_id, base_slot);
self.storage_locations.insert(var_id, location);
let mir_ty = self.lower_type_from_var(var);
self.module.add_storage_slot(StorageSlot {
slot: base_slot,
offset: location.offset,
ty: mir_ty,
name: var.name,
});
}
}
}
}
fn fixed_word_array_param_len(&self, param: &hir::Variable<'_>) -> Option<u64> {
let TyKind::Array(elem, len) = self.gcx.type_of_hir_ty(¶m.ty).peel_refs().kind else {
return None;
};
(self.abi_is_word_element(elem) && len <= U256::from(u16::MAX)).then(|| len.to::<u64>())
}
fn is_dyn_word_array_memory_param(&self, param: &hir::Variable<'_>) -> bool {
if param.data_location != Some(solar_ast::DataLocation::Memory) {
return false;
}
match self.gcx.type_of_hir_ty(¶m.ty).peel_refs().kind {
TyKind::DynArray(elem) => self.abi_is_word_element(elem),
_ => false,
}
}
pub(super) fn ensure_function_lowered(&mut self, func_id: hir::FunctionId) -> FunctionId {
if let Some(&mir_id) = self.hir_to_mir_functions.get(&func_id) {
return mir_id;
}
if self.lowering_functions.contains(&func_id) {
return self
.module
.add_function(Function::new(Ident::new(sym::_recursive_internal, Span::DUMMY)));
}
let saved_locals = std::mem::take(&mut self.locals);
let saved_local_memory_slots = std::mem::take(&mut self.local_memory_slots);
let saved_next_local_memory_offset = self.next_local_memory_offset;
let saved_assigned_vars = std::mem::take(&mut self.assigned_vars);
let saved_current_contract_id = self.current_contract_id;
let saved_lowering_constructor = self.lowering_constructor;
let saved_lowering_internal_function = self.lowering_internal_function;
let saved_in_unchecked_block = self.in_unchecked_block;
let saved_current_return_tys = std::mem::take(&mut self.current_return_tys);
self.lowering_functions.insert(func_id);
self.current_contract_id = self.gcx.hir.function(func_id).contract;
self.in_unchecked_block = false;
let mir_id = self.lower_function(func_id, false);
self.lowering_functions.remove(&func_id);
self.locals = saved_locals;
self.local_memory_slots = saved_local_memory_slots;
self.next_local_memory_offset = saved_next_local_memory_offset;
self.assigned_vars = saved_assigned_vars;
self.current_contract_id = saved_current_contract_id;
self.lowering_constructor = saved_lowering_constructor;
self.lowering_internal_function = saved_lowering_internal_function;
self.in_unchecked_block = saved_in_unchecked_block;
self.current_return_tys = saved_current_return_tys;
mir_id
}
pub(super) fn ensure_internal_mir_function(&mut self, func_id: hir::FunctionId) -> FunctionId {
if let Some(&mir_id) = self.hir_to_internal_mir_functions.get(&func_id) {
return mir_id;
}
let saved_locals = std::mem::take(&mut self.locals);
let saved_local_memory_slots = std::mem::take(&mut self.local_memory_slots);
let saved_next_local_memory_offset = self.next_local_memory_offset;
let saved_assigned_vars = std::mem::take(&mut self.assigned_vars);
let saved_current_contract_id = self.current_contract_id;
let saved_lowering_constructor = self.lowering_constructor;
let saved_lowering_internal_function = self.lowering_internal_function;
let saved_in_unchecked_block = self.in_unchecked_block;
let saved_current_return_tys = std::mem::take(&mut self.current_return_tys);
self.current_contract_id = self.gcx.hir.function(func_id).contract;
self.in_unchecked_block = false;
let mir_id = self.lower_function(func_id, true);
self.locals = saved_locals;
self.local_memory_slots = saved_local_memory_slots;
self.next_local_memory_offset = saved_next_local_memory_offset;
self.assigned_vars = saved_assigned_vars;
self.current_contract_id = saved_current_contract_id;
self.lowering_constructor = saved_lowering_constructor;
self.lowering_internal_function = saved_lowering_internal_function;
self.in_unchecked_block = saved_in_unchecked_block;
self.current_return_tys = saved_current_return_tys;
mir_id
}
fn lower_function(&mut self, func_id: hir::FunctionId, force_internal: bool) -> FunctionId {
let hir_func = self.gcx.hir.function(func_id);
let func_name = hir_func.name.unwrap_or_else(|| Ident::new(sym::_anonymous, Span::DUMMY));
let mir_id = self.module.add_function(Function::new(func_name));
if force_internal {
self.hir_to_internal_mir_functions.insert(func_id, mir_id);
} else {
self.hir_to_mir_functions.insert(func_id, mir_id);
}
let mut mir_func = Function::new(func_name);
mir_func.attributes = FunctionAttributes {
visibility: hir_func.visibility,
state_mutability: hir_func.state_mutability,
is_constructor: hir_func.kind == hir::FunctionKind::Constructor,
is_fallback: hir_func.kind == hir::FunctionKind::Fallback,
is_receive: hir_func.kind == hir::FunctionKind::Receive,
};
let is_special = mir_func.attributes.is_constructor
|| mir_func.attributes.is_receive
|| mir_func.attributes.is_fallback;
let uses_external_abi = mir_func.is_public() && !is_special && !force_internal;
let decodes_abi_params = uses_external_abi || mir_func.attributes.is_constructor;
if uses_external_abi {
mir_func.selector = Some(self.function_selector(func_id));
}
let uses_internal_frame = !uses_external_abi && !is_special;
self.locals.clear();
self.local_memory_slots.clear();
self.next_local_memory_offset = 0x80;
self.assigned_vars.clear();
self.lowering_constructor = hir_func.kind == hir::FunctionKind::Constructor;
self.lowering_internal_function = uses_internal_frame;
self.in_unchecked_block = false;
self.current_return_tys = hir_func
.returns
.iter()
.map(|&id| self.gcx.type_of_hir_ty(&self.gcx.hir.variable(id).ty))
.collect();
if let Some(body) = &hir_func.body {
self.collect_assigned_vars_block(body);
}
let external_arg_head_size = if uses_external_abi {
hir_func
.parameters
.iter()
.map(|&id| {
let param = self.gcx.hir.variable(id);
let ty = self.gcx.type_of_hir_ty(¶m.ty);
self.abi_head_size(ty)
})
.sum()
} else {
0
};
{
let mut builder = FunctionBuilder::new(&mut mir_func);
if uses_external_abi {
Self::emit_external_calldata_head_size_check(&mut builder, external_arg_head_size);
}
for ¶m_id in hir_func.parameters {
let param = self.gcx.hir.variable(param_id);
let param_ty = self.gcx.type_of_hir_ty(¶m.ty);
let ty = self.lower_type_from_var(param);
let abi_param_source = if self.lowering_constructor {
AbiParamSource::ConstructorMemory
} else {
AbiParamSource::ExternalCalldata
};
if decodes_abi_params && let TyKind::Struct(struct_id) = param_ty.peel_refs().kind {
let strukt = self.gcx.hir.strukt(struct_id);
let field_ids = strukt.fields;
let num_fields = field_ids.len();
let struct_size = (num_fields as u64) * 32;
let struct_ptr = self.allocate_memory(&mut builder, struct_size);
for (field_idx, &field_id) in field_ids.iter().enumerate() {
let arg_index = builder.func().params.len() as u64;
let field_ty = MirType::uint256();
let field_val = builder.add_param(field_ty);
let field_var = self.gcx.hir.variable(field_id);
self.emit_abi_param_validation(
&mut builder,
arg_index,
&field_var.ty,
abi_param_source,
);
let field_offset = (field_idx as u64) * 32;
if field_offset == 0 {
builder.mstore(struct_ptr, field_val);
} else {
let offset_val = builder.imm_u64(field_offset);
let field_addr = builder.add(struct_ptr, offset_val);
builder.mstore(field_addr, field_val);
}
}
self.locals.insert(param_id, struct_ptr);
} else if decodes_abi_params
&& let Some(len) = self.fixed_word_array_param_len(param)
{
let array_ptr = self.allocate_memory(&mut builder, len * 32);
let elem_hir_ty = match ¶m.ty.kind {
hir::TypeKind::Array(array) => &array.element,
_ => ¶m.ty,
};
for elem_idx in 0..len {
let arg_index = builder.func().params.len() as u64;
let elem_val = builder.add_param(MirType::uint256());
self.emit_abi_param_validation(
&mut builder,
arg_index,
elem_hir_ty,
abi_param_source,
);
if elem_idx == 0 {
builder.mstore(array_ptr, elem_val);
} else {
let offset_val = builder.imm_u64(elem_idx * 32);
let elem_addr = builder.add(array_ptr, offset_val);
builder.mstore(elem_addr, elem_val);
}
}
self.locals.insert(param_id, array_ptr);
} else if decodes_abi_params && self.is_dyn_word_array_memory_param(param) {
let head = builder.add_param(ty);
let abi_base =
builder.imm_u64(if self.lowering_constructor { 0x80 } else { 4 });
let len_pos = builder.add(abi_base, head);
let len = if self.lowering_constructor {
builder.mload(len_pos)
} else {
builder.calldataload(len_pos)
};
let word = builder.imm_u64(32);
let data_bytes = builder.mul(len, word);
let total_bytes = builder.add(data_bytes, word);
let free_ptr_addr = builder.imm_u64(0x40);
let array_ptr = builder.mload(free_ptr_addr);
let new_free_ptr = builder.add(array_ptr, total_bytes);
let free_ptr_addr = builder.imm_u64(0x40);
builder.mstore(free_ptr_addr, new_free_ptr);
builder.mstore(array_ptr, len);
let dst = builder.add(array_ptr, word);
let src = builder.add(len_pos, word);
if self.lowering_constructor {
self.mcopy(&mut builder, dst, src, data_bytes, None);
} else {
builder.calldatacopy(dst, src, data_bytes);
}
self.locals.insert(param_id, array_ptr);
} else if decodes_abi_params
&& param.data_location == Some(solar_ast::DataLocation::Memory)
&& matches!(
param_ty.peel_refs().kind,
TyKind::Elementary(ElementaryType::Bytes | ElementaryType::String)
)
{
let head = builder.add_param(ty);
let abi_base =
builder.imm_u64(if self.lowering_constructor { 0x80 } else { 4 });
let len_pos = builder.add(abi_base, head);
let len = if self.lowering_constructor {
builder.mload(len_pos)
} else {
builder.calldataload(len_pos)
};
let thirty_one = builder.imm_u64(31);
let rounded = builder.add(len, thirty_one);
let mask = builder.not(thirty_one);
let padded = builder.and(rounded, mask);
let word = builder.imm_u64(32);
let total = builder.add(padded, word);
let ptr = self.allocate_memory_dynamic(&mut builder, total);
builder.mstore(ptr, len);
let data_ptr = builder.add(ptr, word);
let src = builder.add(len_pos, word);
if self.lowering_constructor {
self.mcopy(&mut builder, data_ptr, src, len, None);
} else {
builder.calldatacopy(data_ptr, src, len);
}
self.locals.insert(param_id, ptr);
} else {
let arg_index = builder.func().params.len() as u64;
let val = builder.add_param(ty);
if decodes_abi_params {
self.emit_abi_param_validation(
&mut builder,
arg_index,
¶m.ty,
abi_param_source,
);
}
self.locals.insert(param_id, val);
}
}
for &ret_id in hir_func.returns {
let ret_var = self.gcx.hir.variable(ret_id);
let ret_ty = self.gcx.type_of_hir_ty(&ret_var.ty);
let ty = self.lower_type_from_var(ret_var);
builder.add_return(ty);
let offset = self.alloc_local_memory(ret_id);
let offset_val = self.local_memory_addr(&mut builder, offset);
if matches!(ret_ty.peel_refs().kind, TyKind::Struct(_)) {
let struct_size = self.calculate_memory_words_for_ty(ret_ty) * 32;
let struct_ptr = self.allocate_memory(&mut builder, struct_size);
builder.mstore(offset_val, struct_ptr);
} else if self.is_fixed_memory_array_type(&ret_var.ty, ret_var.data_location)
&& let Some(array_ptr) =
self.allocate_zeroed_fixed_memory_array(&mut builder, &ret_var.ty)
{
builder.mstore(offset_val, array_ptr);
} else {
let zero = builder.imm_u256(U256::ZERO);
builder.mstore(offset_val, zero);
}
}
if hir_func.kind == hir::FunctionKind::Constructor
&& let Some(contract_id) = hir_func.contract
{
self.lower_constructor_prelude(&mut builder, contract_id);
}
if let Some(body) = &hir_func.body {
self.lower_block(&mut builder, body);
}
if !builder.func().block(builder.current_block()).is_terminated() {
if builder.func().returns.is_empty() {
builder.stop();
} else {
let mut items: Vec<(ValueId, Ty<'gcx>)> = Vec::new();
for &ret_id in hir_func.returns {
let ret_var = self.gcx.hir.variable(ret_id);
let ret_val = if let Some(offset) = self.get_local_memory_offset(&ret_id) {
let offset_val = self.local_memory_addr(&mut builder, offset);
builder.mload(offset_val)
} else {
builder.imm_u256(U256::ZERO)
};
items.push((ret_val, self.gcx.type_of_hir_ty(&ret_var.ty)));
}
self.finish_external_or_internal_return(&mut builder, items, uses_external_abi);
}
}
}
self.lowering_constructor = false;
self.lowering_internal_function = false;
mir_func.internal_frame_size =
self.next_local_memory_offset.saturating_sub(Self::LOCAL_MEMORY_BASE);
if uses_external_abi && !self.current_return_tys.iter().any(|&ty| self.abi_is_dynamic(ty)) {
mir_func.external_static_return_size =
self.current_return_tys.iter().map(|&ty| self.abi_head_size(ty)).sum();
}
*self.module.function_mut(mir_id) = mir_func;
mir_id
}
fn emit_external_calldata_head_size_check(builder: &mut FunctionBuilder<'_>, head_size: u64) {
if head_size == 0 {
return;
}
let calldatasize = builder.calldatasize();
let selector_size = builder.imm_u64(4);
let payload_size = builder.sub(calldatasize, selector_size);
let required_size = builder.imm_u64(head_size);
let is_short = builder.slt(payload_size, required_size);
Self::emit_revert_if(builder, is_short);
}
fn emit_abi_param_validation(
&mut self,
builder: &mut FunctionBuilder<'_>,
arg_index: u64,
hir_ty: &hir::Type<'_>,
source: AbiParamSource,
) {
enum Validator {
Mask(U256),
SignExtend(u64),
Bool,
EnumRange(u64),
}
let mut ty = self.gcx.type_of_hir_ty(hir_ty);
if let TyKind::Udvt(underlying, _) = ty.kind {
ty = underlying;
}
let validator = match ty.kind {
TyKind::Elementary(elem) => match elem {
ElementaryType::UInt(size) => {
let bits = size.bits();
if bits >= 256 {
return;
}
Validator::Mask(U256::MAX >> (256 - usize::from(bits)))
}
ElementaryType::Int(size) => {
let bits = size.bits();
if bits >= 256 {
return;
}
Validator::SignExtend(u64::from(bits / 8) - 1)
}
ElementaryType::Address(_) => Validator::Mask(U256::MAX >> 96),
ElementaryType::Bool => Validator::Bool,
ElementaryType::FixedBytes(size) => {
let bytes = size.bytes();
if bytes >= 32 {
return;
}
Validator::Mask(U256::MAX << (256 - 8 * usize::from(bytes)))
}
_ => return,
},
TyKind::Contract(_) => Validator::Mask(U256::MAX >> 96),
TyKind::Enum(enum_id) => {
Validator::EnumRange(self.gcx.hir.enumm(enum_id).variants.len() as u64)
}
_ => return,
};
let word = match source {
AbiParamSource::ExternalCalldata => {
let offset = builder.imm_u64(4 + arg_index * 32);
builder.calldataload(offset)
}
AbiParamSource::ConstructorMemory => {
let offset = builder.imm_u64(0x80 + arg_index * 32);
builder.mload(offset)
}
};
let ok = match validator {
Validator::Mask(mask) => {
let mask = builder.imm_u256(mask);
let canonical = builder.and(word, mask);
builder.eq(word, canonical)
}
Validator::SignExtend(byte_index) => {
let byte_index = builder.imm_u64(byte_index);
let canonical = builder.signextend(byte_index, word);
builder.eq(word, canonical)
}
Validator::Bool => {
let is_zero = builder.iszero(word);
let canonical = builder.iszero(is_zero);
builder.eq(word, canonical)
}
Validator::EnumRange(count) => {
let count = builder.imm_u64(count);
builder.lt(word, count)
}
};
Self::emit_revert_unless(builder, ok);
}
fn emit_revert_unless(builder: &mut FunctionBuilder<'_>, cond: ValueId) {
let revert_block = builder.create_block();
let continue_block = builder.create_block();
builder.branch(cond, continue_block, revert_block);
builder.switch_to_block(revert_block);
let zero = builder.imm_u64(0);
builder.revert(zero, zero);
builder.switch_to_block(continue_block);
}
fn emit_revert_if(builder: &mut FunctionBuilder<'_>, cond: ValueId) {
let revert_block = builder.create_block();
let continue_block = builder.create_block();
builder.branch(cond, revert_block, continue_block);
builder.switch_to_block(revert_block);
let zero = builder.imm_u64(0);
builder.revert(zero, zero);
builder.switch_to_block(continue_block);
}
fn lower_constructor_prelude(
&mut self,
builder: &mut FunctionBuilder<'_>,
contract_id: ContractId,
) {
let contract = self.gcx.hir.contract(contract_id);
let construction_order: Vec<_> = contract
.linearized_bases
.iter()
.enumerate()
.map(|(idx, &base_id)| {
let args = idx.checked_sub(1).and_then(|arg_idx| {
contract.linearized_bases_args.get(arg_idx).and_then(|m| *m)
});
(base_id, args)
})
.collect();
for (base_id, args) in construction_order.into_iter().rev() {
let base_contract = self.gcx.hir.contract(base_id);
for var_id in base_contract.variables() {
let var = self.gcx.hir.variable(var_id);
if var.is_state_variable()
&& !var.is_constant()
&& let Some(init) = var.initializer
{
let init_val = self.lower_expr(builder, init);
if let Some(&offset) = self.immutable_slots.get(&var_id) {
self.store_immutable_value(builder, offset, init_val);
} else if let Some(&location) = self.storage_locations.get(&var_id) {
self.store_storage_location(builder, location, init_val);
}
}
}
if base_id != contract_id
&& let Some(ctor_id) = base_contract.ctor
{
self.lower_base_constructor_call(builder, ctor_id, args);
}
}
}
fn function_selector(&self, func_id: HirFunctionId) -> [u8; 4] {
self.gcx.function_selector(func_id).0
}
pub(super) fn mcopy(
&self,
builder: &mut FunctionBuilder<'_>,
dest: ValueId,
src: ValueId,
len: ValueId,
span: Option<Span>,
) {
if self.gcx.sess.opts.evm_version.has_mcopy() {
builder.mcopy(dest, src, len);
} else {
let err = self.gcx.dcx().err("codegen requires Cancun-compatible EVM for memory copy");
let err = if let Some(span) = span { err.span(span) } else { err };
err.help("compile with `--evm-version cancun` or newer").emit();
}
}
fn lower_type_from_var(&self, var: &hir::Variable<'_>) -> MirType {
self.lower_type_from_ty(self.gcx.type_of_hir_ty(&var.ty))
}
fn lower_type_from_ty(&self, ty: Ty<'gcx>) -> MirType {
match ty.peel_refs().kind {
TyKind::Elementary(elem) => match elem {
ElementaryType::Bool => MirType::Bool,
ElementaryType::Address(_) => MirType::Address,
ElementaryType::Int(bits) => MirType::Int(bits.bits()),
ElementaryType::UInt(bits) => MirType::UInt(bits.bits()),
ElementaryType::Fixed(_, _) => MirType::Int(256),
ElementaryType::UFixed(_, _) => MirType::UInt(256),
ElementaryType::FixedBytes(n) => MirType::FixedBytes(n.bytes()),
ElementaryType::String | ElementaryType::Bytes => MirType::MemPtr,
},
TyKind::Mapping(_, _) => MirType::StoragePtr,
TyKind::DynArray(_) | TyKind::Array(_, _) | TyKind::Slice(_) => MirType::MemPtr,
TyKind::Fn(_) => MirType::Function,
TyKind::Struct(_) => MirType::MemPtr,
TyKind::Enum(_) => MirType::UInt(8),
TyKind::Contract(_) | TyKind::Super(_) => MirType::Address,
TyKind::StringLiteral(_, _)
| TyKind::IntLiteral(_, _, _)
| TyKind::Tuple(_)
| TyKind::Variadic
| TyKind::Error(_, _)
| TyKind::Event(_, _)
| _ => MirType::uint256(),
}
}
#[must_use]
pub fn finish(self) -> Module {
self.module
}
fn collect_assigned_vars_block(&mut self, block: &hir::Block<'_>) {
for stmt in block.stmts {
self.collect_assigned_vars_stmt(stmt);
}
}
fn collect_assigned_vars_stmt(&mut self, stmt: &hir::Stmt<'_>) {
use hir::StmtKind;
match &stmt.kind {
StmtKind::Expr(expr) => self.collect_assigned_vars_expr(expr),
StmtKind::Block(block) | StmtKind::UncheckedBlock(block) => {
self.collect_assigned_vars_block(block)
}
StmtKind::If(cond, then_stmt, else_stmt) => {
self.collect_assigned_vars_expr(cond);
self.collect_assigned_vars_stmt(then_stmt);
if let Some(else_s) = else_stmt {
self.collect_assigned_vars_stmt(else_s);
}
}
StmtKind::Loop(block, _) => self.collect_assigned_vars_block(block),
StmtKind::Switch(switch) => {
self.collect_assigned_vars_expr(switch.selector);
for case in switch.cases {
self.collect_assigned_vars_block(&case.body);
}
}
StmtKind::Return(Some(expr)) | StmtKind::Revert(expr) | StmtKind::Emit(expr) => {
self.collect_assigned_vars_expr(expr)
}
StmtKind::Try(try_stmt) => {
self.collect_assigned_vars_expr(&try_stmt.expr);
for clause in try_stmt.clauses {
self.collect_assigned_vars_block(&clause.block);
}
}
StmtKind::AssemblyBlock(block) => self.collect_assigned_vars_block(block),
StmtKind::DeclSingle(_)
| StmtKind::DeclMulti(_, _)
| StmtKind::Return(None)
| StmtKind::Continue
| StmtKind::Break
| StmtKind::Placeholder
| StmtKind::Err(_) => {}
}
}
fn collect_assigned_vars_expr(&mut self, expr: &hir::Expr<'_>) {
use hir::ExprKind;
match &expr.kind {
ExprKind::Assign(lhs, _, rhs) => {
self.mark_assigned_var(lhs);
self.collect_assigned_vars_expr(rhs);
}
ExprKind::Binary(lhs, _, rhs) => {
self.collect_assigned_vars_expr(lhs);
self.collect_assigned_vars_expr(rhs);
}
ExprKind::Unary(op, operand) => {
use solar_ast::UnOpKind;
if matches!(
op.kind,
UnOpKind::PreInc | UnOpKind::PostInc | UnOpKind::PreDec | UnOpKind::PostDec
) {
self.mark_assigned_var(operand);
}
self.collect_assigned_vars_expr(operand);
}
ExprKind::Ternary(cond, true_val, false_val) => {
self.collect_assigned_vars_expr(cond);
self.collect_assigned_vars_expr(true_val);
self.collect_assigned_vars_expr(false_val);
}
ExprKind::Call(callee, args, _) => {
self.collect_assigned_vars_expr(callee);
for arg in args.kind.exprs() {
self.collect_assigned_vars_expr(arg);
}
}
ExprKind::Index(base, idx) => {
self.collect_assigned_vars_expr(base);
if let Some(i) = idx {
self.collect_assigned_vars_expr(i);
}
}
ExprKind::Slice(base, start, end) => {
self.collect_assigned_vars_expr(base);
if let Some(s) = start {
self.collect_assigned_vars_expr(s);
}
if let Some(e) = end {
self.collect_assigned_vars_expr(e);
}
}
ExprKind::Member(base, _) | ExprKind::YulMember(base, _) => {
self.collect_assigned_vars_expr(base)
}
ExprKind::Array(elems) => {
for elem in elems.iter() {
self.collect_assigned_vars_expr(elem);
}
}
ExprKind::Tuple(elems) => {
for elem in elems.iter().flatten() {
self.collect_assigned_vars_expr(elem);
}
}
ExprKind::Payable(inner) | ExprKind::Delete(inner) => {
self.collect_assigned_vars_expr(inner)
}
ExprKind::New(_)
| ExprKind::TypeCall(_)
| ExprKind::Lit(_)
| ExprKind::Ident(_)
| ExprKind::Type(_)
| ExprKind::Err(_) => {}
}
}
fn mark_assigned_var(&mut self, expr: &hir::Expr<'_>) {
if let Some(var_id) = self.ident_variable(expr) {
self.assigned_vars.insert(var_id);
}
}
pub fn is_var_assigned(&self, var_id: &VariableId) -> bool {
self.assigned_vars.contains(var_id)
}
pub fn has_external_call(&self, expr: &hir::Expr<'_>) -> bool {
use hir::ExprKind;
match &expr.kind {
ExprKind::Call(callee, args, _) => {
if self.is_external_call(callee) {
return true;
}
if self.has_external_call(callee) {
return true;
}
for arg in args.kind.exprs() {
if self.has_external_call(arg) {
return true;
}
}
false
}
ExprKind::Member(base, _) | ExprKind::YulMember(base, _) => {
self.has_external_call(base)
}
ExprKind::Binary(lhs, _, rhs) => {
self.has_external_call(lhs) || self.has_external_call(rhs)
}
ExprKind::Unary(_, operand) => self.has_external_call(operand),
ExprKind::Ternary(cond, true_val, false_val) => {
self.has_external_call(cond)
|| self.has_external_call(true_val)
|| self.has_external_call(false_val)
}
ExprKind::Index(base, idx) => {
self.has_external_call(base) || idx.is_some_and(|i| self.has_external_call(i))
}
ExprKind::Array(elems) => elems.iter().any(|e| self.has_external_call(e)),
ExprKind::Tuple(elems) => {
elems.iter().any(|e| e.is_some_and(|expr| self.has_external_call(expr)))
}
ExprKind::Payable(inner) | ExprKind::Delete(inner) => self.has_external_call(inner),
ExprKind::Slice(base, start, end) => {
self.has_external_call(base)
|| start.is_some_and(|s| self.has_external_call(s))
|| end.is_some_and(|e| self.has_external_call(e))
}
ExprKind::Assign(lhs, _, rhs) => {
self.has_external_call(lhs) || self.has_external_call(rhs)
}
ExprKind::New(_)
| ExprKind::TypeCall(_)
| ExprKind::Lit(_)
| ExprKind::Ident(_)
| ExprKind::Type(_)
| ExprKind::Err(_) => false,
}
}
fn is_external_call(&self, callee: &hir::Expr<'_>) -> bool {
if let hir::ExprKind::Member(base, _) = &callee.kind
&& let Some(var_id) = self.ident_variable(base)
{
let var = self.gcx.hir.variable(var_id);
if matches!(var.ty.kind, hir::TypeKind::Custom(hir::ItemId::Contract(_))) {
return true;
}
}
false
}
}
pub fn lower_contract(gcx: Gcx<'_>, contract_id: ContractId) -> Module {
lower_contract_with_bytecodes(gcx, contract_id, &FxHashMap::default())
}
pub fn contract_bytecode_dependencies(
gcx: Gcx<'_>,
contract_id: ContractId,
) -> FxHashSet<ContractId> {
let mut deps = FxHashSet::default();
BytecodeDependencyCollector { gcx, deps: &mut deps }.collect_contract(contract_id);
deps.remove(&contract_id);
deps
}
struct BytecodeDependencyCollector<'a, 'gcx> {
gcx: Gcx<'gcx>,
deps: &'a mut FxHashSet<ContractId>,
}
impl<'a, 'gcx> BytecodeDependencyCollector<'a, 'gcx> {
fn collect_contract(&mut self, contract_id: ContractId) {
let contract = self.gcx.hir.contract(contract_id);
for modifier in contract.linearized_bases_args.iter().flatten() {
let ControlFlow::Continue(()) = self.visit_modifier(modifier);
}
for &base_id in contract.linearized_bases {
let base = self.gcx.hir.contract(base_id);
for var_id in base.variables() {
let ControlFlow::Continue(()) = self.visit_nested_var(var_id);
}
for func_id in base.all_functions() {
let func = self.gcx.hir.function(func_id);
for modifier in func.modifiers {
let ControlFlow::Continue(()) = self.visit_modifier(modifier);
}
if let Some(body) = func.body {
for stmt in body.stmts {
let ControlFlow::Continue(()) = self.visit_stmt(stmt);
}
}
}
}
}
fn collect_type(&mut self, ty: &hir::Type<'gcx>) {
if let hir::TypeKind::Custom(hir::ItemId::Contract(contract_id)) = &ty.kind {
self.deps.insert(*contract_id);
}
}
}
impl<'gcx> Visit<'gcx> for BytecodeDependencyCollector<'_, 'gcx> {
type BreakValue = Never;
fn hir(&self) -> &'gcx hir::Hir<'gcx> {
&self.gcx.hir
}
fn visit_expr(&mut self, expr: &'gcx hir::Expr<'gcx>) -> ControlFlow<Self::BreakValue> {
match &expr.kind {
hir::ExprKind::New(ty) => self.collect_type(ty),
hir::ExprKind::Member(base, member)
if matches!(member.name, sym::creationCode | sym::runtimeCode) =>
{
if let hir::ExprKind::TypeCall(ty) = &base.kind {
self.collect_type(ty);
}
}
_ => {}
}
self.walk_expr(expr)
}
}
pub fn lower_contract_with_bytecodes(
gcx: Gcx<'_>,
contract_id: ContractId,
child_bytecodes: &FxHashMap<ContractId, Vec<u8>>,
) -> Module {
let contract = gcx.hir.contract(contract_id);
let mut lowerer = Lowerer::new(gcx, contract.name);
for (&child_id, bytecode) in child_bytecodes {
lowerer.register_contract_bytecode(child_id, bytecode.clone());
}
lowerer.lower_contract(contract_id);
lowerer.finish()
}