use crate::codegen::{
cfg::{ControlFlowGraph, Instr, InternalCallTy, ReturnCode},
revert::PanicCode,
Expression,
};
use crate::emit::binary::Binary;
use crate::emit::cfg::{create_block, BasicBlock, Work};
use crate::emit::expression::expression;
use crate::emit::{ContractArgs, TargetRuntime, Variable};
use crate::sema::ast::{Contract, ExternalCallAccounts, RetrieveType, Type};
use crate::Target;
use inkwell::types::BasicType;
use inkwell::values::{
BasicMetadataValueEnum, BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue,
};
use inkwell::{AddressSpace, IntPredicate};
use num_traits::ToPrimitive;
use solang_parser::pt::CodeLocation;
use std::collections::{HashMap, VecDeque};
use super::expression::expression_to_slice;
pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
target: &mut T,
ins: &Instr,
bin: &Binary<'a>,
w: &mut Work<'a>,
function: FunctionValue<'a>,
cfg: &ControlFlowGraph,
work: &mut VecDeque<Work<'a>>,
blocks: &mut HashMap<usize, BasicBlock<'a>>,
contract: &Contract,
) {
match ins {
Instr::Nop => (),
Instr::Return { value } if value.is_empty() && bin.ns.target != Target::Soroban => {
bin.builder
.build_return(Some(&bin.return_values[&ReturnCode::Success]))
.unwrap();
}
Instr::Return { value } if bin.ns.target != Target::Soroban => {
let returns_offset = cfg.params.len();
for (i, val) in value.iter().enumerate() {
let arg = function.get_nth_param((returns_offset + i) as u32).unwrap();
let retval = expression(target, bin, val, &w.vars, function);
bin.builder
.build_store(arg.into_pointer_value(), retval)
.unwrap();
}
bin.builder
.build_return(Some(&bin.return_values[&ReturnCode::Success]))
.unwrap();
}
Instr::Return { value } => match value.iter().next() {
Some(val) => {
let retval = expression(target, bin, val, &w.vars, function);
bin.builder.build_return(Some(&retval)).unwrap();
}
None => {
bin.builder.build_return(None).unwrap();
}
},
Instr::Set { res, expr, .. } => {
if let Expression::Undefined { ty: expr_type } = expr {
if let Some(default_expr) = expr_type.default(bin.ns) {
w.vars.get_mut(res).unwrap().value =
expression(target, bin, &default_expr, &w.vars, function);
}
} else {
w.vars.get_mut(res).unwrap().value =
expression(target, bin, expr, &w.vars, function);
}
}
Instr::Branch { block: dest } => {
let pos = bin.builder.get_insert_block().unwrap();
let bb = add_or_retrieve_block(*dest, pos, bin, function, blocks, work, w, cfg);
bin.builder.position_at_end(pos);
bin.builder.build_unconditional_branch(bb).unwrap();
}
Instr::Store { dest, data } => {
let value_ref = expression(target, bin, data, &w.vars, function);
let dest_ref = expression(target, bin, dest, &w.vars, function).into_pointer_value();
bin.builder.build_store(dest_ref, value_ref).unwrap();
}
Instr::BranchCond {
cond,
true_block: true_,
false_block: false_,
} => {
let cond = expression(target, bin, cond, &w.vars, function);
let pos = bin.builder.get_insert_block().unwrap();
let bb_true = add_or_retrieve_block(*true_, pos, bin, function, blocks, work, w, cfg);
let bb_false = add_or_retrieve_block(*false_, pos, bin, function, blocks, work, w, cfg);
bin.builder.position_at_end(pos);
bin.builder
.build_conditional_branch(cond.into_int_value(), bb_true, bb_false)
.unwrap();
}
Instr::LoadStorage {
res,
ty,
storage,
storage_type,
} => {
let mut slot = expression(target, bin, storage, &w.vars, function).into_int_value();
w.vars.get_mut(res).unwrap().value =
target.storage_load(bin, ty, &mut slot, function, storage_type);
}
Instr::ClearStorage { ty, storage } => {
let mut slot = expression(target, bin, storage, &w.vars, function).into_int_value();
target.storage_delete(bin, ty, &mut slot, function);
}
Instr::SetStorage {
ty,
value,
storage,
storage_type,
} => {
let value = expression(target, bin, value, &w.vars, function);
let mut slot = expression(target, bin, storage, &w.vars, function).into_int_value();
target.storage_store(bin, ty, true, &mut slot, value, function, storage_type);
}
Instr::SetStorageBytes {
storage,
value,
offset,
} => {
let index_loc = offset.loc();
let value = expression(target, bin, value, &w.vars, function);
let slot = expression(target, bin, storage, &w.vars, function).into_int_value();
let offset = expression(target, bin, offset, &w.vars, function).into_int_value();
target.set_storage_bytes_subscript(
bin,
function,
slot,
offset,
value.into_int_value(),
index_loc,
);
}
Instr::PushStorage {
res,
ty,
storage,
value,
} => {
let val = value
.as_ref()
.map(|expr| expression(target, bin, expr, &w.vars, function));
let slot = expression(target, bin, storage, &w.vars, function).into_int_value();
w.vars.get_mut(res).unwrap().value = target.storage_push(bin, function, ty, slot, val);
}
Instr::PopStorage { res, ty, storage } => {
let loc = storage.loc();
let slot = expression(target, bin, storage, &w.vars, function).into_int_value();
let value = target.storage_pop(bin, function, ty, slot, res.is_some(), loc);
if let Some(res) = res {
w.vars.get_mut(res).unwrap().value = value.unwrap();
}
}
Instr::PushMemory {
res,
ty,
array,
value,
} => {
let arr = w.vars[array].value;
let llvm_ty = bin.llvm_type(ty);
let elem_ty = ty.array_elem();
let llvm_elem_ty = bin.llvm_field_ty(&elem_ty);
let elem_size = llvm_elem_ty
.size_of()
.unwrap()
.const_cast(bin.context.i32_type(), false);
let len = bin.vector_len(arr);
let new_len = bin
.builder
.build_int_add(len, bin.context.i32_type().const_int(1, false), "")
.unwrap();
let vec_size = bin
.module
.get_struct_type("struct.vector")
.unwrap()
.size_of()
.unwrap()
.const_cast(bin.context.i32_type(), false);
let size = bin.builder.build_int_mul(elem_size, new_len, "").unwrap();
let size = bin.builder.build_int_add(size, vec_size, "").unwrap();
let new = bin
.builder
.build_call(
bin.module.get_function("__realloc").unwrap(),
&[arr.into(), size.into()],
"",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
.into_pointer_value();
w.vars.get_mut(array).unwrap().value = new.into();
let slot_ptr = unsafe {
bin.builder
.build_gep(
llvm_ty,
new,
&[
bin.context.i32_type().const_zero(),
bin.context.i32_type().const_int(2, false),
bin.builder.build_int_mul(len, elem_size, "").unwrap(),
],
"data",
)
.unwrap()
};
let value = expression(target, bin, value, &w.vars, function);
let value = if elem_ty.is_fixed_reference_type(bin.ns) {
w.vars.get_mut(res).unwrap().value = slot_ptr.into();
let load_ty = bin.llvm_type(&elem_ty);
bin.builder
.build_load(load_ty, value.into_pointer_value(), "elem")
.unwrap()
} else {
w.vars.get_mut(res).unwrap().value = value;
value
};
bin.builder.build_store(slot_ptr, value).unwrap();
let len_ptr = unsafe {
bin.builder
.build_gep(
llvm_ty,
new,
&[
bin.context.i32_type().const_zero(),
bin.context.i32_type().const_zero(),
],
"len",
)
.unwrap()
};
bin.builder.build_store(len_ptr, new_len).unwrap();
let size_ptr = unsafe {
bin.builder
.build_gep(
llvm_ty,
new,
&[
bin.context.i32_type().const_zero(),
bin.context.i32_type().const_int(1, false),
],
"size",
)
.unwrap()
};
bin.builder.build_store(size_ptr, new_len).unwrap();
}
Instr::PopMemory {
res,
ty,
array,
loc,
} => {
let a = w.vars[array].value.into_pointer_value();
let len = unsafe {
bin.builder
.build_gep(
bin.module.get_struct_type("struct.vector").unwrap(),
a,
&[
bin.context.i32_type().const_zero(),
bin.context.i32_type().const_zero(),
],
"a_len",
)
.unwrap()
};
let len = bin
.builder
.build_load(bin.context.i32_type(), len, "a_len")
.unwrap()
.into_int_value();
let is_array_empty = bin
.builder
.build_int_compare(
IntPredicate::EQ,
len,
bin.context.i32_type().const_zero(),
"is_array_empty",
)
.unwrap();
let error = bin.context.append_basic_block(function, "error");
let pop = bin.context.append_basic_block(function, "pop");
bin.builder
.build_conditional_branch(is_array_empty, error, pop)
.unwrap();
bin.builder.position_at_end(error);
bin.log_runtime_error(target, "pop from empty array".to_string(), Some(*loc));
let (revert_out, revert_out_len) = bin.panic_data_const(PanicCode::EmptyArrayPop);
target.assert_failure(bin, revert_out, revert_out_len);
bin.builder.position_at_end(pop);
let llvm_ty = bin.llvm_type(ty);
let elem_ty = ty.array_elem();
let llvm_elem_ty = bin.llvm_field_ty(&elem_ty);
let elem_size = llvm_elem_ty
.size_of()
.unwrap()
.const_cast(bin.context.i32_type(), false);
let new_len = bin
.builder
.build_int_sub(len, bin.context.i32_type().const_int(1, false), "")
.unwrap();
let vec_size = bin
.module
.get_struct_type("struct.vector")
.unwrap()
.size_of()
.unwrap()
.const_cast(bin.context.i32_type(), false);
let size = bin.builder.build_int_mul(elem_size, new_len, "").unwrap();
let size = bin.builder.build_int_add(size, vec_size, "").unwrap();
let slot_ptr = unsafe {
bin.builder
.build_gep(
bin.module.get_struct_type("struct.vector").unwrap(),
a,
&[
bin.context.i32_type().const_zero(),
bin.context.i32_type().const_int(2, false),
bin.builder.build_int_mul(new_len, elem_size, "").unwrap(),
],
"data",
)
.unwrap()
};
if elem_ty.is_fixed_reference_type(bin.ns) {
w.vars.get_mut(res).unwrap().value = slot_ptr.into();
} else {
let ret_val = bin
.builder
.build_load(bin.llvm_type(&elem_ty), slot_ptr, "")
.unwrap();
w.vars.get_mut(res).unwrap().value = ret_val;
}
let new = bin
.builder
.build_call(
bin.module.get_function("__realloc").unwrap(),
&[a.into(), size.into()],
"",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
.into_pointer_value();
w.vars.get_mut(array).unwrap().value = new.into();
let len_ptr = unsafe {
bin.builder
.build_gep(
llvm_ty,
new,
&[
bin.context.i32_type().const_zero(),
bin.context.i32_type().const_zero(),
],
"len",
)
.unwrap()
};
bin.builder.build_store(len_ptr, new_len).unwrap();
let size_ptr = unsafe {
bin.builder
.build_gep(
llvm_ty,
new,
&[
bin.context.i32_type().const_zero(),
bin.context.i32_type().const_int(1, false),
],
"size",
)
.unwrap()
};
bin.builder.build_store(size_ptr, new_len).unwrap();
}
Instr::AssertFailure { encoded_args: None } => {
target.assert_failure(
bin,
bin.context
.i8_type()
.ptr_type(AddressSpace::default())
.const_null(),
bin.context.i32_type().const_zero(),
);
}
Instr::AssertFailure {
encoded_args: Some(expr),
} => {
let data = expression(target, bin, expr, &w.vars, function);
let vector_bytes = bin.vector_bytes(data);
let len = bin.vector_len(data);
target.assert_failure(bin, vector_bytes, len);
}
Instr::Print { expr } => {
let expr = expression(target, bin, expr, &w.vars, function);
target.print(bin, bin.vector_bytes(expr), bin.vector_len(expr));
}
Instr::Call {
res,
call: InternalCallTy::Static { cfg_no },
args,
..
} => {
let f = &contract.cfg[*cfg_no];
let mut parms = args
.iter()
.map(|p| expression(target, bin, p, &w.vars, function).into())
.collect::<Vec<BasicMetadataValueEnum>>();
if !res.is_empty() && bin.ns.target != Target::Soroban {
for v in f.returns.iter() {
parms.push(if bin.ns.target == Target::Solana {
bin.build_alloca(function, bin.llvm_var_ty(&v.ty), v.name_as_str())
.into()
} else {
bin.builder
.build_alloca(bin.llvm_var_ty(&v.ty), v.name_as_str())
.unwrap()
.into()
});
}
}
if let Some(parameters) = bin.parameters {
parms.push(parameters.into());
}
let ret = bin
.builder
.build_call(bin.functions[cfg_no], &parms, "")
.unwrap()
.try_as_basic_value()
.left();
if bin.ns.target != Target::Soroban {
let success = bin
.builder
.build_int_compare(
IntPredicate::EQ,
ret.unwrap().into_int_value(),
bin.return_values[&ReturnCode::Success],
"success",
)
.unwrap();
let success_block = bin.context.append_basic_block(function, "success");
let bail_block = bin.context.append_basic_block(function, "bail");
bin.builder
.build_conditional_branch(success, success_block, bail_block)
.unwrap();
bin.builder.position_at_end(bail_block);
bin.builder.build_return(Some(&ret.unwrap())).unwrap();
bin.builder.position_at_end(success_block);
if !res.is_empty() {
for (i, v) in f.returns.iter().enumerate() {
let load_ty = bin.llvm_var_ty(&v.ty);
let val = bin
.builder
.build_load(
load_ty,
parms[args.len() + i].into_pointer_value(),
v.name_as_str(),
)
.unwrap();
let dest = w.vars[&res[i]].value;
if dest.is_pointer_value()
&& !(v.ty.is_reference_type(bin.ns)
|| matches!(v.ty, Type::ExternalFunction { .. }))
{
bin.builder
.build_store(dest.into_pointer_value(), val)
.unwrap();
} else {
w.vars.get_mut(&res[i]).unwrap().value = val;
}
}
}
} else if let Some(value) = ret {
w.vars.get_mut(&res[0]).unwrap().value = value;
}
}
Instr::Call {
res,
call: InternalCallTy::Builtin { ast_func_no },
args,
..
} => {
let mut parms = args
.iter()
.map(|p| expression(target, bin, p, &w.vars, function).into())
.collect::<Vec<BasicMetadataValueEnum>>();
let callee = &bin.ns.functions[*ast_func_no];
if !res.is_empty() {
for v in callee.returns.iter() {
parms.push(if bin.ns.target == Target::Solana {
bin.build_alloca(function, bin.llvm_var_ty(&v.ty), v.name_as_str())
.into()
} else {
bin.builder
.build_alloca(bin.llvm_var_ty(&v.ty), v.name_as_str())
.unwrap()
.into()
});
}
}
if let Some(ret) = target.builtin_function(
bin,
function,
callee,
&parms,
args.first().map(|arg| bin.llvm_type(&arg.ty())),
) {
let success = bin
.builder
.build_int_compare(
IntPredicate::EQ,
ret.into_int_value(),
bin.return_values[&ReturnCode::Success],
"success",
)
.unwrap();
let success_block = bin.context.append_basic_block(function, "success");
let bail_block = bin.context.append_basic_block(function, "bail");
bin.builder
.build_conditional_branch(success, success_block, bail_block)
.unwrap();
bin.builder.position_at_end(bail_block);
bin.builder.build_return(Some(&ret)).unwrap();
bin.builder.position_at_end(success_block);
}
if !res.is_empty() {
for (i, v) in callee.returns.iter().enumerate() {
let load_ty = if v.ty.is_reference_type(bin.ns) {
bin.llvm_type(&v.ty)
.ptr_type(AddressSpace::default())
.as_basic_type_enum()
} else {
bin.llvm_type(&v.ty)
};
let val = bin
.builder
.build_load(
load_ty,
parms[args.len() + i].into_pointer_value(),
v.name_as_str(),
)
.unwrap();
let dest = w.vars[&res[i]].value;
if dest.is_pointer_value()
&& !(v.ty.is_reference_type(bin.ns)
|| matches!(v.ty, Type::ExternalFunction { .. }))
{
bin.builder
.build_store(dest.into_pointer_value(), val)
.unwrap();
} else {
w.vars.get_mut(&res[i]).unwrap().value = val;
}
}
}
}
Instr::Call {
res,
call: InternalCallTy::Dynamic(call_expr),
args,
..
} => {
let ty = call_expr.ty();
let (llvm_func, returns) = if let Type::InternalFunction {
params, returns, ..
} = ty.deref_any()
{
(bin.function_type(params, returns), returns)
} else {
panic!("should be Type::InternalFunction type");
};
let mut parms = args
.iter()
.map(|p| expression(target, bin, p, &w.vars, function).into())
.collect::<Vec<BasicMetadataValueEnum>>();
if !res.is_empty() {
for ty in returns.iter() {
parms.push(bin.build_alloca(function, bin.llvm_var_ty(ty), "").into());
}
}
if let Some(parameters) = bin.parameters {
parms.push(parameters.into());
}
let callable =
expression(target, bin, call_expr, &w.vars, function).into_pointer_value();
let ptr_ok = bin.context.append_basic_block(function, "fn_ptr_ok");
let ptr_nil_block = bin.context.append_basic_block(function, "fn_ptr_nil");
let nil_ptr = bin
.context
.i8_type()
.ptr_type(AddressSpace::default())
.const_null();
let is_ptr_nil = bin
.builder
.build_int_compare(IntPredicate::EQ, nil_ptr, callable, "check_nil_ptr")
.unwrap();
bin.builder
.build_conditional_branch(is_ptr_nil, ptr_nil_block, ptr_ok)
.unwrap();
bin.builder.position_at_end(ptr_nil_block);
bin.log_runtime_error(target, "internal function uninitialized".to_string(), None);
let (revert_out, revert_out_len) =
bin.panic_data_const(PanicCode::InternalFunctionUninitialized);
target.assert_failure(bin, revert_out, revert_out_len);
bin.builder.position_at_end(ptr_ok);
let ret = bin
.builder
.build_indirect_call(llvm_func, callable, &parms, "")
.unwrap()
.try_as_basic_value()
.left()
.unwrap();
let success = bin
.builder
.build_int_compare(
IntPredicate::EQ,
ret.into_int_value(),
bin.return_values[&ReturnCode::Success],
"success",
)
.unwrap();
let success_block = bin.context.append_basic_block(function, "success");
let bail_block = bin.context.append_basic_block(function, "bail");
bin.builder
.build_conditional_branch(success, success_block, bail_block)
.unwrap();
bin.builder.position_at_end(bail_block);
bin.builder.build_return(Some(&ret)).unwrap();
bin.builder.position_at_end(success_block);
if !res.is_empty() {
for (i, ty) in returns.iter().enumerate() {
let load_ty = bin.llvm_var_ty(ty);
let val = bin
.builder
.build_load(load_ty, parms[args.len() + i].into_pointer_value(), "")
.unwrap();
let dest = w.vars[&res[i]].value;
if dest.is_pointer_value() && !ty.is_reference_type(bin.ns) {
bin.builder
.build_store(dest.into_pointer_value(), val)
.unwrap();
} else {
w.vars.get_mut(&res[i]).unwrap().value = val;
}
}
}
}
Instr::Call {
res,
call: InternalCallTy::HostFunction { name },
args,
..
} => {
let parms = args
.iter()
.map(|p| expression(target, bin, p, &w.vars, function).into())
.collect::<Vec<BasicMetadataValueEnum>>();
let call = bin.module.get_function(name).unwrap();
let ret = bin
.builder
.build_call(call, &parms, "")
.unwrap()
.try_as_basic_value()
.left();
if let Some(value) = ret {
w.vars.get_mut(&res[0]).unwrap().value = value;
}
}
Instr::Constructor {
success,
res,
contract_no,
encoded_args,
value,
gas,
salt,
address,
seeds,
loc,
accounts,
constructor_no: _,
} => {
let encoded_args = expression(target, bin, encoded_args, &w.vars, function);
let encoded_args_len = bin.vector_len(encoded_args).as_basic_value_enum();
let address_stack = bin.build_alloca(function, bin.address_type(), "address");
let gas = expression(target, bin, gas, &w.vars, function).into_int_value();
let value = value
.as_ref()
.map(|v| expression(target, bin, v, &w.vars, function).into_int_value());
let salt = salt
.as_ref()
.map(|v| expression(target, bin, v, &w.vars, function).into_int_value());
let llvm_accounts = process_account_metas(target, accounts, bin, &w.vars, function);
if let Some(address) = address {
let address =
expression(target, bin, address, &w.vars, function).into_array_value();
bin.builder.build_store(address_stack, address).unwrap();
}
let seeds = if let Some(seeds) = seeds {
let len = seeds.ty().array_length().unwrap().to_u64().unwrap();
let seeds_ty = bin.llvm_type(&Type::Slice(Box::new(Type::Slice(Box::new(
Type::Bytes(1),
)))));
let output_seeds = bin.build_array_alloca(
function,
seeds_ty,
bin.context.i64_type().const_int(len, false),
"seeds",
);
if let Expression::ArrayLiteral { values, .. } = seeds {
for i in 0..len {
let val = expression(target, bin, &values[i as usize], &w.vars, function);
let seed_count = values[i as usize]
.ty()
.deref_memory()
.array_length()
.unwrap()
.to_u64()
.unwrap();
let dest = unsafe {
bin.builder
.build_gep(
seeds_ty,
output_seeds,
&[
bin.context.i32_type().const_int(i, false),
bin.context.i32_type().const_zero(),
],
"dest",
)
.unwrap()
};
bin.builder.build_store(dest, val).unwrap();
let dest = unsafe {
bin.builder
.build_gep(
seeds_ty,
output_seeds,
&[
bin.context.i32_type().const_int(i, false),
bin.context.i32_type().const_int(1, false),
],
"dest",
)
.unwrap()
};
let val = bin.context.i64_type().const_int(seed_count, false);
bin.builder.build_store(dest, val).unwrap();
}
}
Some((output_seeds, bin.context.i64_type().const_int(len, false)))
} else {
None
};
target.create_contract(
bin,
function,
success.map(|n| &mut w.vars.get_mut(&n).unwrap().value),
*contract_no,
address_stack,
encoded_args,
encoded_args_len,
ContractArgs {
program_id: None,
accounts: llvm_accounts,
gas: Some(gas),
value,
salt,
seeds,
flags: None,
},
*loc,
);
w.vars.get_mut(res).unwrap().value = bin
.builder
.build_load(bin.address_type(), address_stack, "address")
.unwrap();
}
Instr::ExternalCall {
success,
address,
payload,
value,
gas,
callty,
accounts,
seeds,
flags,
..
} => {
let loc = payload.loc();
let gas = expression(target, bin, gas, &w.vars, function).into_int_value();
let value = expression(target, bin, value, &w.vars, function).into_int_value();
let payload_ty = payload.ty();
let payload = expression(target, bin, payload, &w.vars, function);
let address = if let Some(address) = address {
let address = expression(target, bin, address, &w.vars, function);
if bin.ns.target == Target::Soroban {
Some(address)
} else {
let addr = bin.build_array_alloca(
function,
bin.context.i8_type(),
bin.context
.i32_type()
.const_int(bin.ns.address_length as u64, false),
"address",
);
bin.builder.build_store(addr, address).unwrap();
Some(addr.as_basic_value_enum())
}
} else {
None
};
let accounts = process_account_metas(target, accounts, bin, &w.vars, function);
let (payload_ptr, payload_len) = if payload_ty == Type::DynamicBytes {
(bin.vector_bytes(payload), bin.vector_len(payload))
} else {
let ptr = payload.into_pointer_value();
let len = bin.llvm_type(&payload_ty).size_of().unwrap();
(ptr, len)
};
let seeds_ty =
Type::Slice(Type::Slice(Type::Slice(Type::Bytes(1).into()).into()).into());
let seeds = seeds
.as_ref()
.map(|seeds| expression_to_slice(target, bin, seeds, &seeds_ty, &w.vars, function));
let flags = flags
.as_ref()
.map(|e| expression(target, bin, e, &w.vars, function).into_int_value());
let success = match success {
Some(n) => Some(&mut w.vars.get_mut(n).unwrap().value),
None => None,
};
target.external_call(
bin,
function,
success,
payload_ptr,
payload_len,
address,
ContractArgs {
program_id: None,
value: Some(value),
gas: Some(gas),
salt: None,
seeds,
accounts,
flags,
},
callty.clone(),
loc,
);
}
Instr::ValueTransfer {
success,
address,
value,
} => {
let loc = value.loc();
let value = expression(target, bin, value, &w.vars, function).into_int_value();
let address = expression(target, bin, address, &w.vars, function).into_array_value();
let addr = bin.build_alloca(function, bin.address_type(), "address");
bin.builder.build_store(addr, address).unwrap();
let success = match success {
Some(n) => Some(&mut w.vars.get_mut(n).unwrap().value),
None => None,
};
target.value_transfer(bin, function, success, addr, value, loc);
}
Instr::SelfDestruct { recipient } => {
let recipient =
expression(target, bin, recipient, &w.vars, function).into_array_value();
target.selfdestruct(bin, recipient);
}
Instr::EmitEvent { data, topics, .. } => {
let data = expression(target, bin, data, &w.vars, function);
let topics = topics
.iter()
.map(|a| expression(target, bin, a, &w.vars, function))
.collect::<Vec<BasicValueEnum>>();
target.emit_event(bin, function, data, &topics);
}
Instr::WriteBuffer { buf, offset, value } => {
let v = expression(target, bin, buf, &w.vars, function);
let data = bin.vector_bytes(v);
let offset = expression(target, bin, offset, &w.vars, function).into_int_value();
let emit_value = expression(target, bin, value, &w.vars, function);
if bin.ns.target == Target::Soroban {
let new_offset = bin
.builder
.build_int_unsigned_div(
offset,
bin.context.i64_type().const_int(8, false),
"new_offset",
)
.unwrap();
let start = unsafe {
bin.builder
.build_gep(
bin.context.i64_type().array_type(1),
data,
&[bin.context.i64_type().const_zero(), new_offset],
"start",
)
.unwrap()
};
bin.builder.build_store(start, emit_value).unwrap();
} else {
let start = unsafe {
bin.builder
.build_gep(bin.context.i8_type(), data, &[offset], "start")
.unwrap()
};
let is_bytes = if let Type::Bytes(n) = value.ty().unwrap_user_type(bin.ns) {
n
} else if value.ty() == Type::FunctionSelector {
bin.ns.target.selector_length()
} else {
0
};
if is_bytes > 1 {
let value_ptr = bin.build_alloca(
function,
emit_value.into_int_value().get_type(),
&format!("bytes{is_bytes}"),
);
bin.builder
.build_store(value_ptr, emit_value.into_int_value())
.unwrap();
bin.builder
.build_call(
bin.module.get_function("__leNtobeN").unwrap(),
&[
value_ptr.into(),
start.into(),
bin.context
.i32_type()
.const_int(is_bytes as u64, false)
.into(),
],
"",
)
.unwrap();
} else {
bin.builder.build_store(start, emit_value).unwrap();
}
}
}
Instr::MemCopy {
source: from,
destination: to,
bytes,
} => {
let src = if from.ty().is_dynamic_memory() {
bin.vector_bytes(expression(target, bin, from, &w.vars, function))
} else {
expression(target, bin, from, &w.vars, function).into_pointer_value()
};
let dest = if to.ty().is_dynamic_memory() {
bin.vector_bytes(expression(target, bin, to, &w.vars, function))
} else {
expression(target, bin, to, &w.vars, function).into_pointer_value()
};
let size = expression(target, bin, bytes, &w.vars, function);
bin.builder
.build_call(
bin.module.get_function("__memcpy").unwrap(),
&[dest.into(), src.into(), size.into()],
"",
)
.unwrap();
}
Instr::Switch {
cond,
cases,
default,
} => {
let pos = bin.builder.get_insert_block().unwrap();
let cond = expression(target, bin, cond, &w.vars, function);
let cases = cases
.iter()
.map(|(exp, block_no)| {
let exp = expression(target, bin, exp, &w.vars, function);
let bb =
add_or_retrieve_block(*block_no, pos, bin, function, blocks, work, w, cfg);
(exp.into_int_value(), bb)
})
.collect::<Vec<(IntValue, inkwell::basic_block::BasicBlock)>>();
let default_bb =
add_or_retrieve_block(*default, pos, bin, function, blocks, work, w, cfg);
bin.builder.position_at_end(pos);
bin.builder
.build_switch(cond.into_int_value(), default_bb, cases.as_ref())
.unwrap();
}
Instr::ReturnData { data, data_len } => {
let data = if data.ty().is_reference_type(bin.ns) {
bin.vector_bytes(expression(target, bin, data, &w.vars, function))
} else {
expression(target, bin, data, &w.vars, function).into_pointer_value()
};
let data_len = expression(target, bin, data_len, &w.vars, function);
target.return_abi_data(bin, data, data_len);
}
Instr::ReturnCode { code } => {
target.return_code(bin, bin.return_values[code]);
}
Instr::Unimplemented { .. } => unimplemented!(),
Instr::AccountAccess { .. } => {
unreachable!("Instr::AccountAccess shall never appear in the CFG")
}
}
}
fn add_or_retrieve_block<'a>(
block_no: usize,
pos: inkwell::basic_block::BasicBlock<'a>,
bin: &Binary<'a>,
function: FunctionValue<'a>,
blocks: &mut HashMap<usize, BasicBlock<'a>>,
work: &mut VecDeque<Work<'a>>,
w: &mut Work<'a>,
cfg: &ControlFlowGraph,
) -> inkwell::basic_block::BasicBlock<'a> {
if let std::collections::hash_map::Entry::Vacant(e) = blocks.entry(block_no) {
e.insert(create_block(block_no, bin, cfg, function));
work.push_back(Work {
block_no,
vars: w.vars.clone(),
});
}
let bb = blocks.get(&block_no).unwrap();
for (v, phi) in bb.phis.iter() {
phi.add_incoming(&[(&w.vars[v].value, pos)]);
}
bb.bb
}
fn process_account_metas<'a, T: TargetRuntime<'a> + ?Sized>(
target: &T,
accounts: &ExternalCallAccounts<Expression>,
bin: &Binary<'a>,
vartab: &HashMap<usize, Variable<'a>>,
function: FunctionValue<'a>,
) -> Option<(PointerValue<'a>, IntValue<'a>)> {
if let ExternalCallAccounts::Present(accounts) = accounts {
let ty = accounts.ty();
let expr = expression(target, bin, accounts, vartab, function);
if let Some(n) = ty.array_length() {
let accounts = expr.into_pointer_value();
let len = bin.context.i32_type().const_int(n.to_u64().unwrap(), false);
Some((accounts, len))
} else {
unreachable!("dynamic array not allowed for the 'accounts' parameter");
}
} else {
None
}
}