use super::{LoopContext, Lowerer};
use crate::mir::{FunctionBuilder, ValueId};
use alloy_primitives::U256;
use solar_interface::{Span, kw};
use solar_sema::{
builtins::Builtin,
hir::{self, ExprKind, StmtKind},
ty::{Ty, TyKind},
};
impl<'gcx> Lowerer<'gcx> {
pub(super) fn lower_block(
&mut self,
builder: &mut FunctionBuilder<'_>,
block: &hir::Block<'_>,
) {
let mut index = 0;
while let Some(stmt) = block.stmts.get(index) {
if let Some(args) =
self.immediate_packed_hash_return_args(stmt, block.stmts.get(index + 1))
&& self.lower_immediate_packed_hash_return(builder, &args)
{
break;
}
self.lower_stmt(builder, stmt);
if builder.func().block(builder.current_block()).terminator.is_some() {
break;
}
index += 1;
}
}
fn immediate_packed_hash_return_args(
&self,
stmt: &hir::Stmt<'_>,
next: Option<&hir::Stmt<'_>>,
) -> Option<hir::CallArgs<'gcx>> {
let StmtKind::DeclSingle(var_id) = stmt.kind else {
return None;
};
let var = self.gcx.hir.variable(var_id);
let packed_args = self.abi_encode_packed_call_args(var.initializer?)?;
let StmtKind::Return(Some(ret)) = &next?.kind else {
return None;
};
self.is_keccak_call_of_local(ret, var_id).then_some(*packed_args)
}
fn is_keccak_call_of_local(&self, expr: &hir::Expr<'_>, var_id: hir::VariableId) -> bool {
let ExprKind::Call(callee, args, _) = &expr.kind else {
return false;
};
if !matches!(self.callee_res(callee), Some(hir::Res::Builtin(Builtin::Keccak256))) {
return false;
}
let mut exprs = args.exprs();
let Some(arg) = exprs.next() else {
return false;
};
exprs.next().is_none() && self.is_local_ident(arg, var_id)
}
fn is_local_ident(&self, expr: &hir::Expr<'_>, var_id: hir::VariableId) -> bool {
self.ident_variable(expr) == Some(var_id)
}
fn lower_immediate_packed_hash_return(
&mut self,
builder: &mut FunctionBuilder<'_>,
args: &hir::CallArgs<'_>,
) -> bool {
if self.current_return_tys.len() != 1 {
return false;
}
let ty = self.current_return_tys[0];
let hash = self.lower_keccak_abi_encode_packed(builder, args);
let external = builder.func().is_public() && !self.lowering_internal_function;
self.finish_external_or_internal_return(builder, vec![(hash, ty)], external);
true
}
pub(super) fn lower_stmt(&mut self, builder: &mut FunctionBuilder<'_>, stmt: &hir::Stmt<'_>) {
match &stmt.kind {
StmtKind::DeclSingle(var_id) => {
self.lower_single_var_decl(builder, *var_id);
}
StmtKind::DeclMulti(var_ids, init) => {
self.lower_multi_var_decl(builder, var_ids, init);
}
StmtKind::Expr(expr) => {
self.lower_expr(builder, expr);
}
StmtKind::Block(block) => {
self.lower_block(builder, block);
}
StmtKind::If(cond, then_stmt, else_stmt) => {
self.lower_if(builder, cond, then_stmt, *else_stmt);
}
StmtKind::Loop(block, source) => {
self.lower_loop(builder, block, *source);
}
StmtKind::Switch(switch) => {
self.lower_switch(builder, switch);
}
StmtKind::Return(value) => {
self.lower_return(builder, *value);
}
StmtKind::Revert(expr) => {
let _ = self.lower_expr(builder, expr);
if builder.func().block(builder.current_block()).terminator.is_none() {
let zero = builder.imm_u64(0);
builder.revert(zero, zero);
}
}
StmtKind::Emit(expr) => {
self.lower_emit(builder, expr);
}
StmtKind::Try(try_stmt) => {
self.lower_try(builder, try_stmt);
}
StmtKind::Continue => {
if let Some(loop_ctx) = self.current_loop() {
builder.jump(loop_ctx.continue_target);
}
}
StmtKind::Break => {
if let Some(loop_ctx) = self.current_loop() {
builder.jump(loop_ctx.break_target);
}
}
StmtKind::Placeholder => {}
StmtKind::UncheckedBlock(block) => self.lower_unchecked_block(builder, block),
StmtKind::AssemblyBlock(block) => {
self.lower_block(builder, block);
}
StmtKind::Err(_) => {}
}
}
fn lower_unchecked_block(&mut self, builder: &mut FunctionBuilder<'_>, block: &hir::Block<'_>) {
let prev = self.in_unchecked_block;
self.in_unchecked_block = true;
self.lower_block(builder, block);
self.in_unchecked_block = prev;
}
fn lower_single_var_decl(
&mut self,
builder: &mut FunctionBuilder<'_>,
var_id: hir::VariableId,
) {
let var = self.gcx.hir.variable(var_id);
let var_ty = self.gcx.type_of_hir_ty(&var.ty);
if var.data_location == Some(solar_ast::DataLocation::Storage) {
if let Some(init) = var.initializer {
if let Some(slot) = self.lower_lvalue_slot(builder, init) {
self.locals.insert(var_id, slot);
self.storage_ref_locals.insert(var_id);
return;
}
self.gcx
.dcx()
.err("unsupported storage reference initializer")
.span(init.span)
.emit();
return;
}
let zero = builder.imm_u256(U256::ZERO);
self.locals.insert(var_id, zero);
self.storage_ref_locals.insert(var_id);
return;
}
let has_external_call = var.initializer.is_some_and(|init| self.has_external_call(init));
let is_struct_type = matches!(var_ty.peel_refs().kind, TyKind::Struct(_));
let initial_value = if let Some(init) = var.initializer {
if self.var_expects_memory_bytes_value(var) {
self.lower_expr_as_memory_bytes(builder, init)
} else {
self.lower_expr(builder, init)
}
} else if is_struct_type {
let struct_size = self.memory_struct_size(&var.ty);
let struct_ptr = self.allocate_memory(builder, struct_size);
self.zero_initialize_memory_value(builder, &var.ty, struct_ptr);
struct_ptr
} else if self.is_fixed_memory_array_type(&var.ty, var.data_location) {
self.allocate_zeroed_fixed_memory_array(builder, &var.ty)
.unwrap_or_else(|| builder.imm_u256(U256::ZERO))
} else {
builder.imm_u256(U256::ZERO)
};
let needs_local_memory =
self.is_var_assigned(&var_id) || (has_external_call && !is_struct_type);
if needs_local_memory {
let offset = self.alloc_local_memory(var_id);
let offset_val = self.local_memory_addr(builder, offset);
builder.mstore(offset_val, initial_value);
} else {
self.locals.insert(var_id, initial_value);
}
}
fn memory_struct_size(&self, ty: &hir::Type<'_>) -> u64 {
let ty = self.gcx.type_of_hir_ty(ty);
if matches!(ty.peel_refs().kind, TyKind::Struct(_)) {
self.calculate_memory_words_for_ty(ty) * 32
} else {
32
}
}
fn zero_initialize_memory_value(
&mut self,
builder: &mut FunctionBuilder<'_>,
ty: &hir::Type<'_>,
ptr: ValueId,
) {
self.zero_initialize_memory_ty(builder, self.gcx.type_of_hir_ty(ty), ptr, ty.span);
}
pub(super) fn zero_memory_field_value(
&mut self,
builder: &mut FunctionBuilder<'_>,
ty: &hir::Type<'_>,
) -> ValueId {
self.zero_memory_field_value_ty(builder, self.gcx.type_of_hir_ty(ty), ty.span)
}
pub(super) fn is_fixed_memory_array_type(
&self,
ty: &hir::Type<'_>,
loc: Option<solar_ast::DataLocation>,
) -> bool {
matches!(loc, None | Some(solar_ast::DataLocation::Memory))
&& matches!(self.gcx.type_of_hir_ty(ty).peel_refs().kind, TyKind::Array(_, _))
}
pub(super) fn fixed_memory_array_len(&self, ty: &hir::Type<'_>) -> Option<u64> {
let TyKind::Array(_, len) = self.gcx.type_of_hir_ty(ty).peel_refs().kind else {
return None;
};
u64::try_from(len).ok()
}
pub(super) fn allocate_zeroed_fixed_memory_array(
&mut self,
builder: &mut FunctionBuilder<'_>,
ty: &hir::Type<'_>,
) -> Option<ValueId> {
let array_ty = self.gcx.type_of_hir_ty(ty);
let TyKind::Array(elem_ty, _) = array_ty.peel_refs().kind else {
return None;
};
let len = self.fixed_memory_array_len(ty)?;
let alloc_size = len.checked_mul(32).unwrap_or_else(|| {
self.gcx
.dcx()
.err("fixed-size memory array is too large for codegen")
.span(ty.span)
.emit();
0
});
let ptr = self.allocate_memory(builder, alloc_size);
for i in 0..len {
let value = self.zero_memory_field_value_ty(builder, elem_ty, ty.span);
if i == 0 {
builder.mstore(ptr, value);
} else {
let offset = builder.imm_u64(i * 32);
let addr = builder.add(ptr, offset);
builder.mstore(addr, value);
}
}
Some(ptr)
}
fn zero_memory_field_value_ty(
&mut self,
builder: &mut FunctionBuilder<'_>,
ty: Ty<'gcx>,
span: Span,
) -> ValueId {
match ty.peel_refs().kind {
TyKind::Array(elem_ty, len) => {
let Some(len) = u64::try_from(len).ok() else {
return self.err_value(
builder,
span,
"fixed-size memory array is too large for codegen",
);
};
let alloc_size = len.checked_mul(32).unwrap_or_else(|| {
self.gcx
.dcx()
.err("fixed-size memory array is too large for codegen")
.span(span)
.emit();
0
});
let ptr = self.allocate_memory(builder, alloc_size);
for i in 0..len {
let value = self.zero_memory_field_value_ty(builder, elem_ty, span);
if i == 0 {
builder.mstore(ptr, value);
} else {
let offset = builder.imm_u64(i * 32);
let addr = builder.add(ptr, offset);
builder.mstore(addr, value);
}
}
ptr
}
TyKind::DynArray(_) => {
let ptr = self.allocate_memory(builder, 32);
let zero = builder.imm_u256(U256::ZERO);
builder.mstore(ptr, zero);
ptr
}
TyKind::Struct(_) => {
let ptr =
self.allocate_memory(builder, self.calculate_memory_words_for_ty(ty) * 32);
self.zero_initialize_memory_ty(builder, ty, ptr, span);
ptr
}
_ => builder.imm_u256(U256::ZERO),
}
}
fn zero_initialize_memory_ty(
&mut self,
builder: &mut FunctionBuilder<'_>,
ty: Ty<'gcx>,
ptr: ValueId,
span: Span,
) {
let TyKind::Struct(struct_id) = ty.peel_refs().kind else {
let zero = builder.imm_u256(U256::ZERO);
builder.mstore(ptr, zero);
return;
};
let field_tys = self.gcx.struct_field_types(struct_id).to_vec();
for (i, field_ty) in field_tys.into_iter().enumerate() {
let value = self.zero_memory_field_value_ty(builder, field_ty, span);
let field_offset = (i as u64) * 32;
if field_offset == 0 {
builder.mstore(ptr, value);
} else {
let offset_val = builder.imm_u64(field_offset);
let field_addr = builder.add(ptr, offset_val);
builder.mstore(field_addr, value);
}
}
}
pub(super) fn lower_multi_var_decl(
&mut self,
builder: &mut FunctionBuilder<'_>,
var_ids: &[Option<hir::VariableId>],
init: &hir::Expr<'_>,
) {
if self.is_low_level_call_expr(init) {
let success = self.lower_expr(builder, init);
for (i, var_id_opt) in var_ids.iter().enumerate() {
let Some(var_id) = var_id_opt else { continue };
let val = if i == 0 { success } else { self.materialize_returndata_bytes(builder) };
let offset = self.alloc_local_memory(*var_id);
let offset_val = self.local_memory_addr(builder, offset);
builder.mstore(offset_val, val);
}
return;
}
let first_val = self.lower_expr(builder, init);
for (i, var_id_opt) in var_ids.iter().enumerate() {
if let Some(var_id) = var_id_opt {
let val = if i == 0 {
first_val
} else {
let mem_offset = builder.imm_u64((i * 32) as u64);
builder.mload(mem_offset)
};
let offset = self.alloc_local_memory(*var_id);
let offset_val = self.local_memory_addr(builder, offset);
builder.mstore(offset_val, val);
}
}
}
fn is_low_level_call_expr(&self, expr: &hir::Expr<'_>) -> bool {
let ExprKind::Call(callee, ..) = &expr.kind else { return false };
let ExprKind::Member(base, member) = &callee.kind else { return false };
matches!(member.name, kw::Call | kw::Staticcall | kw::Delegatecall)
&& !self.is_contract_type_expr(base)
}
fn lower_if(
&mut self,
builder: &mut FunctionBuilder<'_>,
cond: &hir::Expr<'_>,
then_stmt: &hir::Stmt<'_>,
else_stmt: Option<&hir::Stmt<'_>>,
) {
let cond_val = self.lower_expr(builder, cond);
let then_block = builder.create_block();
let merge_block = builder.create_block();
let else_block = if else_stmt.is_some() { builder.create_block() } else { merge_block };
builder.branch(cond_val, then_block, else_block);
builder.switch_to_block(then_block);
self.lower_stmt(builder, then_stmt);
if !builder.func().block(builder.current_block()).is_terminated() {
builder.jump(merge_block);
}
if let Some(else_stmt) = else_stmt {
builder.switch_to_block(else_block);
self.lower_stmt(builder, else_stmt);
if !builder.func().block(builder.current_block()).is_terminated() {
builder.jump(merge_block);
}
}
builder.switch_to_block(merge_block);
}
fn lower_switch(&mut self, builder: &mut FunctionBuilder<'_>, switch: &hir::StmtSwitch<'_>) {
let selector = self.lower_expr(builder, switch.selector);
let merge_block = builder.create_block();
let mut case_blocks = Vec::new();
let mut body_blocks = Vec::new();
let mut default_block = merge_block;
for case in switch.cases {
let block = builder.create_block();
if let Some(constant) = case.constant {
let value = self.lower_literal(builder, constant);
case_blocks.push((value, block));
} else {
default_block = block;
}
body_blocks.push((case, block));
}
builder.switch(selector, default_block, case_blocks);
for (case, block) in body_blocks {
builder.switch_to_block(block);
self.lower_block(builder, &case.body);
if !builder.func().block(builder.current_block()).is_terminated() {
builder.jump(merge_block);
}
}
builder.switch_to_block(merge_block);
}
fn lower_loop(
&mut self,
builder: &mut FunctionBuilder<'_>,
block: &hir::Block<'_>,
source: hir::LoopSource,
) {
let loop_block = builder.create_block();
let exit_block = builder.create_block();
let (continue_target, is_for_with_update) = if source == hir::LoopSource::For {
if self.is_for_loop_with_update(block) {
let update_block = builder.create_block();
(update_block, true)
} else {
(loop_block, false)
}
} else {
(loop_block, false)
};
self.push_loop(LoopContext { break_target: exit_block, continue_target });
builder.jump(loop_block);
builder.switch_to_block(loop_block);
if is_for_with_update {
self.lower_for_loop_body(builder, block, continue_target, loop_block);
} else {
self.lower_block(builder, block);
if !builder.func().block(builder.current_block()).is_terminated() {
builder.jump(loop_block);
}
}
self.pop_loop();
builder.switch_to_block(exit_block);
}
fn is_for_loop_with_update(&self, block: &hir::Block<'_>) -> bool {
let stmts = block.stmts;
if stmts.len() != 1 {
return false;
}
let StmtKind::If(_, then_stmt, _) = &stmts[0].kind else {
return false;
};
let StmtKind::Block(b) = &then_stmt.kind else {
return false;
};
if b.stmts.len() < 2 {
return false;
}
matches!(b.stmts.last().map(|s| &s.kind), Some(StmtKind::Expr(_)))
}
fn lower_for_loop_body(
&mut self,
builder: &mut FunctionBuilder<'_>,
block: &hir::Block<'_>,
update_block: crate::mir::BlockId,
loop_block: crate::mir::BlockId,
) {
let stmts = block.stmts;
let StmtKind::If(cond, then_stmt, else_stmt) = &stmts[0].kind else {
self.lower_block(builder, block);
return;
};
let StmtKind::Block(then_body) = &then_stmt.kind else {
self.lower_block(builder, block);
return;
};
let then_block = builder.create_block();
let else_block = builder.create_block();
let cond_val = self.lower_expr(builder, cond);
builder.branch(cond_val, then_block, else_block);
builder.switch_to_block(then_block);
let body_stmts = &then_body.stmts[..then_body.stmts.len() - 1];
for stmt in body_stmts {
self.lower_stmt(builder, stmt);
}
if !builder.func().block(builder.current_block()).is_terminated() {
builder.jump(update_block);
}
builder.switch_to_block(update_block);
if let Some(last_stmt) = then_body.stmts.last() {
self.lower_stmt(builder, last_stmt);
}
if !builder.func().block(builder.current_block()).is_terminated() {
builder.jump(loop_block);
}
builder.switch_to_block(else_block);
if let Some(else_s) = else_stmt {
self.lower_stmt(builder, else_s);
}
}
fn lower_return(&mut self, builder: &mut FunctionBuilder<'_>, value: Option<&hir::Expr<'_>>) {
let external = builder.func().is_public() && !self.lowering_internal_function;
if external {
let items = self.gather_return_items(builder, value);
self.emit_abi_return(builder, &items);
return;
}
if let Some(expr) = value {
if let hir::ExprKind::Tuple(elements) = &expr.kind {
let ret_vals: Vec<_> = elements
.iter()
.filter_map(|elem_opt| {
elem_opt.as_ref().map(|elem| self.lower_expr(builder, elem))
})
.collect();
builder.ret(ret_vals);
} else if let Some(arity) = self.get_ternary_tuple_arity(expr) {
let _ = self.lower_expr(builder, expr);
let mut ret_vals = Vec::new();
for i in 0..arity {
let offset = builder.imm_u64(i as u64 * 32);
ret_vals.push(builder.mload(offset));
}
builder.ret(ret_vals);
} else {
let ret_val = self.lower_expr(builder, expr);
let n = builder.func().returns.len();
if n > 1 {
let mut ret_vals = Vec::with_capacity(n);
ret_vals.push(ret_val);
for i in 1..n {
let offset = builder.imm_u64((i * 32) as u64);
ret_vals.push(builder.mload(offset));
}
builder.ret(ret_vals);
} else {
builder.ret([ret_val]);
}
}
} else {
builder.ret([]);
}
}
pub(super) fn get_ternary_tuple_arity(&self, expr: &hir::Expr<'_>) -> Option<usize> {
if let hir::ExprKind::Ternary(_, then_expr, else_expr) = &expr.kind {
if let hir::ExprKind::Tuple(elements) = &then_expr.kind {
return Some(elements.len());
}
if let hir::ExprKind::Tuple(elements) = &else_expr.kind {
return Some(elements.len());
}
}
None
}
fn lower_emit(&mut self, builder: &mut FunctionBuilder<'_>, expr: &hir::Expr<'_>) {
let hir::ExprKind::Call(callee, args, _named) = &expr.kind else {
return;
};
let Some(hir::Res::Item(hir::ItemId::Event(event_id))) = self.callee_res(callee) else {
return;
};
let event = self.gcx.hir.event(event_id);
let sig = self.compute_event_signature(event);
let sig_hash = alloy_primitives::keccak256(sig.as_bytes());
let topic0 = builder.imm_u256(alloy_primitives::U256::from_be_bytes(sig_hash.0));
let mut topics = vec![topic0];
let mut data_items = Vec::new();
let mut arg_exprs = args.exprs();
for param_id in event.parameters {
let param = self.gcx.hir.variable(*param_id);
let Some(arg) = arg_exprs.next() else { continue };
let ty = self.gcx.type_of_hir_ty(¶m.ty);
if param.indexed {
if let Some(topic) = self.keccak_dynamic_bytes(builder, arg) {
topics.push(topic);
} else {
let arg_val = self.lower_return_value_for_ty(builder, arg, ty);
topics.push(arg_val);
}
} else {
let arg_val = self.lower_return_value_for_ty(builder, arg, ty);
data_items.push((arg_val, ty));
}
}
let has_dynamic_data = data_items.iter().any(|&(_, ty)| self.abi_is_dynamic(ty));
let (mem_offset, size) = if has_dynamic_data {
self.abi_encode_items_to_memory(builder, &data_items)
} else {
let mem_offset = builder.imm_u64(0);
for (i, (val, _)) in data_items.iter().enumerate() {
let offset = builder.imm_u64(i as u64 * 32);
builder.mstore(offset, *val);
}
let size = builder.imm_u64((data_items.len() * 32) as u64);
(mem_offset, size)
};
match topics.len() {
0 => builder.log0(mem_offset, size),
1 => builder.log1(mem_offset, size, topics[0]),
2 => builder.log2(mem_offset, size, topics[0], topics[1]),
3 => builder.log3(mem_offset, size, topics[0], topics[1], topics[2]),
4 => builder.log4(mem_offset, size, topics[0], topics[1], topics[2], topics[3]),
_ => {} }
}
fn compute_event_signature(&self, event: &hir::Event<'_>) -> String {
let params: Vec<String> = event
.parameters
.iter()
.map(|param_id| {
let param = self.gcx.hir.variable(*param_id);
self.type_to_abi_string(¶m.ty)
})
.collect();
format!("{}({})", event.name.name, params.join(","))
}
fn type_to_abi_string(&self, ty: &hir::Type<'_>) -> String {
match &ty.kind {
hir::TypeKind::Elementary(elem) => elem.to_abi_str().to_string(),
hir::TypeKind::Custom(item_id) => {
if let hir::ItemId::Contract(_) = item_id {
"address".to_string()
} else {
"uint256".to_string() }
}
hir::TypeKind::Array(arr) => {
let inner = self.type_to_abi_string(&arr.element);
format!("{inner}[]")
}
_ => "uint256".to_string(), }
}
fn lower_try(&mut self, builder: &mut FunctionBuilder<'_>, try_stmt: &hir::StmtTry<'_>) {
let success_block = builder.create_block();
let catch_block = builder.create_block();
let merge_block = builder.create_block();
let success = self.lower_try_call(builder, &try_stmt.expr);
builder.branch(success, success_block, catch_block);
builder.switch_to_block(success_block);
if let Some(returns_clause) = try_stmt.clauses.first() {
if !returns_clause.args.is_empty() {
panic!("codegen does not support try/catch return bindings yet");
}
self.lower_block(builder, &returns_clause.block);
}
builder.jump(merge_block);
builder.switch_to_block(catch_block);
let catch_clauses = &try_stmt.clauses[1..];
if catch_clauses.len() > 1 {
panic!("codegen does not support multiple try/catch handlers yet");
}
for clause in try_stmt.clauses.iter().skip(1) {
if clause.name.is_some() || !clause.args.is_empty() {
panic!("codegen does not support typed try/catch handlers yet");
}
self.lower_block(builder, &clause.block);
}
if try_stmt.clauses.len() <= 1 {
let zero = builder.imm_u64(0);
builder.revert(zero, zero);
} else {
builder.jump(merge_block);
}
builder.switch_to_block(merge_block);
}
fn lower_try_call(
&mut self,
builder: &mut FunctionBuilder<'_>,
expr: &hir::Expr<'_>,
) -> crate::mir::ValueId {
use hir::ExprKind;
if let ExprKind::Call(callee, args, call_opts) = &expr.kind {
if let ExprKind::Member(base, member) = &callee.kind {
return self.lower_try_member_call(
builder,
base,
*member,
args,
(*call_opts).map(|opts| opts.args),
);
}
}
let result = self.lower_expr(builder, expr);
let is_zero = builder.iszero(result);
builder.iszero(is_zero)
}
fn lower_try_member_call(
&mut self,
builder: &mut FunctionBuilder<'_>,
base: &hir::Expr<'_>,
member: solar_interface::Ident,
args: &hir::CallArgs<'_>,
call_opts: Option<&[hir::NamedArg<'_>]>,
) -> crate::mir::ValueId {
let selector = self.compute_member_selector(base, member);
let num_returns = self.get_member_function_return_count(base, member);
let num_args = args.exprs().count();
let calldata_size_bytes = 4 + num_args * 32;
let arg_vals: Vec<crate::mir::ValueId> =
args.exprs().map(|arg| self.lower_expr(builder, arg)).collect();
let addr = self.lower_expr(builder, base);
let selector_word = U256::from(selector) << 224;
let selector_val = builder.imm_u256(selector_word);
let mem_start = builder.imm_u64(0);
builder.mstore(mem_start, selector_val);
let mut arg_offset = 4u64;
for arg_val in arg_vals {
let offset = builder.imm_u64(arg_offset);
builder.mstore(offset, arg_val);
arg_offset += 32;
}
let calldata_size = builder.imm_u64(calldata_size_bytes as u64);
let args_offset = builder.imm_u64(0);
let ret_offset = builder.imm_u64(0);
let ret_size = builder.imm_u64((num_returns * 32) as u64);
let gas = builder.gas();
let value = self.extract_call_value(builder, call_opts);
builder.call(gas, addr, value, args_offset, calldata_size, ret_offset, ret_size)
}
}