use crate::codegen::cfg::HashTy;
use crate::codegen::error::CodegenError;
use crate::codegen::Builtin;
use crate::codegen::Expression;
use crate::emit::binary::Binary;
use crate::emit::soroban::{HostFunctions, SorobanTarget};
use crate::emit::ContractArgs;
use crate::emit::{TargetRuntime, Variable};
use crate::emit_context;
use crate::sema::ast;
use crate::sema::ast::CallTy;
use crate::sema::ast::{Function, Type};
use inkwell::types::{BasicTypeEnum, IntType};
use inkwell::values::{
ArrayValue, BasicMetadataValueEnum, BasicValue, BasicValueEnum, FunctionValue, IntValue,
PointerValue,
};
use solang_parser::helpers::CodeLocation;
use solang_parser::pt::{Loc, StorageType};
use num_traits::ToPrimitive;
use std::collections::HashMap;
fn unsupported_soroban<T>(loc: Loc, operation: impl Into<String>) -> T {
panic!(
"{}",
CodegenError::unsupported_soroban_operation(loc, operation)
)
}
fn runtime_helper<'a>(
bin: &Binary<'a>,
name: &str,
operation: impl Into<String>,
) -> FunctionValue<'a> {
bin.module.get_function(name).unwrap_or_else(|| {
panic!(
"{}",
CodegenError::missing_runtime_helper(name, operation, bin.ns.target)
)
})
}
fn expect_llvm_entity<T>(
value: Option<T>,
operation: impl Into<String>,
entity: impl Into<String>,
) -> T {
value.unwrap_or_else(|| panic!("{}", CodegenError::missing_llvm_entity(operation, entity)))
}
fn expect_return_value<T>(value: Option<T>, operation: impl Into<String>) -> T {
expect_llvm_entity(value, operation, "expected return value")
}
fn invalid_cfg<T>(operation: impl Into<String>, reason: impl Into<String>) -> T {
panic!("{}", CodegenError::invalid_cfg_invariant(operation, reason))
}
#[allow(unused_variables)]
impl<'a> TargetRuntime<'a> for SorobanTarget {
fn get_storage_int(
&self,
bin: &Binary<'a>,
function: FunctionValue,
slot: PointerValue<'a>,
ty: IntType<'a>,
) -> IntValue<'a> {
unsupported_soroban(Loc::Codegen, "raw storage integer loads")
}
fn storage_load(
&self,
bin: &Binary<'a>,
ty: &ast::Type,
slot: &mut IntValue<'a>,
slot_ty: Option<&ast::Type>,
function: FunctionValue<'a>,
storage_type: &Option<StorageType>,
) -> BasicValueEnum<'a> {
if let Some(Type::StorageRef(_, inner)) = slot_ty {
if let Type::Array(inner_ty, _) = inner.as_ref() {
if !is_reference_type(inner_ty) {
return get_storage_vec_subscript(bin, function, *slot);
}
}
}
let storage_type = storage_type_to_int(storage_type);
emit_context!(bin);
let slot = if slot.is_const() {
slot.as_basic_value_enum()
.into_int_value()
.const_cast(bin.context.i64_type(), false)
} else {
*slot
};
if let Type::Struct(ast::StructType::UserDefined(n)) = ty {
let field_count = &bin.ns.structs[*n].fields.len();
let struct_buffer =
soroban_get_fields_to_val_buffer(bin, function, slot, *field_count, storage_type);
return struct_buffer.as_basic_value_enum();
}
let has_data_val = call!(
HostFunctions::HasContractData.name(),
&[
slot.into(),
bin.context.i64_type().const_int(storage_type, false).into(),
]
)
.try_as_basic_value()
.left()
.unwrap()
.into_int_value();
let condition = is_val_true(bin, has_data_val);
let parent = function;
let then_bb = bin.context.append_basic_block(parent, "has_data");
let else_bb = bin.context.append_basic_block(parent, "no_data");
let merge_bb = bin.context.append_basic_block(parent, "merge");
bin.builder
.build_conditional_branch(condition, then_bb, else_bb)
.unwrap();
bin.builder.position_at_end(then_bb);
let value_from_contract = call!(
HostFunctions::GetContractData.name(),
&[
slot.into(),
bin.context.i64_type().const_int(storage_type, false).into(),
]
)
.try_as_basic_value()
.left()
.unwrap();
bin.builder.build_unconditional_branch(merge_bb).unwrap();
let then_value = value_from_contract;
bin.builder.position_at_end(else_bb);
let default_value = type_to_tagged_zero_val(bin, ty);
bin.builder.build_unconditional_branch(merge_bb).unwrap();
bin.builder.position_at_end(merge_bb);
let phi = bin
.builder
.build_phi(bin.context.i64_type(), "storage_result")
.unwrap();
phi.add_incoming(&[(&then_value, then_bb), (&default_value, else_bb)]);
phi.as_basic_value()
}
fn storage_store(
&self,
bin: &Binary<'a>,
ty: &ast::Type,
existing: bool,
slot: &mut IntValue<'a>,
slot_ty: Option<&ast::Type>,
dest: BasicValueEnum<'a>,
function: FunctionValue<'a>,
storage_type: &Option<StorageType>,
) {
if let Some(Type::StorageRef(_, inner)) = slot_ty {
if let Type::Array(inner_ty, _) = inner.as_ref() {
if !is_reference_type(inner_ty) {
return set_storage_vec_subscript(bin, function, *slot, dest.into_int_value());
}
}
}
emit_context!(bin);
let storage_type = storage_type_to_int(storage_type);
let function_value = bin
.module
.get_function(HostFunctions::PutContractData.name())
.unwrap();
let slot = if slot.is_const() {
slot.as_basic_value_enum()
.into_int_value()
.const_cast(bin.context.i64_type(), false)
} else {
*slot
};
let inner_ty = if let Type::StorageRef(mutable, inner) = ty {
inner
} else {
ty
};
if let Type::Struct(ast::StructType::UserDefined(n)) = inner_ty {
let field_count = &bin.ns.structs[*n].fields.len();
let data_ptr = bin.vector_bytes(dest);
soroban_put_fields_from_val_buffer(
bin,
function,
slot,
data_ptr,
*field_count,
storage_type,
);
return;
}
let value = bin
.builder
.build_call(
function_value,
&[
slot.into(),
dest.into(),
bin.context.i64_type().const_int(storage_type, false).into(),
],
HostFunctions::PutContractData.name(),
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
.into_int_value();
}
fn storage_delete(
&self,
bin: &Binary<'a>,
ty: &Type,
slot: &mut IntValue<'a>,
function: FunctionValue<'a>,
) {
let storage_type = storage_type_to_int(&None);
let type_int = bin.context.i64_type().const_int(storage_type, false);
let function_value = bin
.module
.get_function(HostFunctions::DeleteContractData.name())
.unwrap();
let call = bin
.builder
.build_call(
function_value,
&[slot.as_basic_value_enum().into(), type_int.into()],
"del_contract_data",
)
.unwrap();
}
fn set_storage_string(
&self,
bin: &Binary<'a>,
function: FunctionValue<'a>,
slot: PointerValue<'a>,
dest: BasicValueEnum<'a>,
) {
unsupported_soroban(Loc::Codegen, "storage string and bytes stores")
}
fn get_storage_string(
&self,
bin: &Binary<'a>,
function: FunctionValue,
slot: PointerValue<'a>,
) -> PointerValue<'a> {
unsupported_soroban(Loc::Codegen, "storage string and bytes loads")
}
fn set_storage_extfunc(
&self,
bin: &Binary<'a>,
function: FunctionValue,
slot: PointerValue,
dest: PointerValue,
dest_ty: BasicTypeEnum,
) {
unsupported_soroban(Loc::Codegen, "storage external function stores")
}
fn get_storage_extfunc(
&self,
bin: &Binary<'a>,
function: FunctionValue,
slot: PointerValue<'a>,
) -> PointerValue<'a> {
unsupported_soroban(Loc::Codegen, "storage external function loads")
}
fn get_storage_bytes_subscript(
&self,
bin: &Binary<'a>,
function: FunctionValue,
slot: IntValue<'a>,
index: IntValue<'a>,
_loc: Loc,
) -> IntValue<'a> {
emit_context!(bin);
let bytes_obj = call!(
HostFunctions::GetContractData.name(),
&[slot.into(), i64_const!(1).into()],
"bytes_load"
)
.try_as_basic_value()
.left()
.unwrap()
.into_int_value();
let idx_encoded = encode_value(index, 32, 4, bin);
let raw = call!(
HostFunctions::BytesGet.name(),
&[bytes_obj.into(), idx_encoded.into()],
"bytes_get"
)
.try_as_basic_value()
.left()
.unwrap()
.into_int_value();
let shifted = bin
.builder
.build_right_shift(raw, i64_const!(32), false, "byte_raw")
.unwrap();
bin.builder
.build_int_truncate(shifted, bin.context.i8_type(), "byte_val")
.unwrap()
}
fn set_storage_bytes_subscript(
&self,
bin: &Binary<'a>,
function: FunctionValue,
slot: IntValue<'a>,
index: IntValue<'a>,
value: IntValue<'a>,
_loc: Loc,
) {
emit_context!(bin);
let bytes_obj = call!(
HostFunctions::GetContractData.name(),
&[slot.into(), i64_const!(1).into()],
"bytes_load"
)
.try_as_basic_value()
.left()
.unwrap()
.into_int_value();
let idx_encoded = encode_value(index, 32, 4, bin);
let value = if value.get_type().get_bit_width() != 32 {
bin.builder
.build_int_z_extend(value, bin.context.i32_type(), "byte32")
.unwrap()
} else {
value
};
let val_encoded = encode_value(value, 32, 4, bin);
let new_obj = call!(
HostFunctions::BytesPut.name(),
&[bytes_obj.into(), idx_encoded.into(), val_encoded.into()],
"bytes_put"
)
.try_as_basic_value()
.left()
.unwrap()
.into_int_value();
call!(
HostFunctions::PutContractData.name(),
&[slot.into(), new_obj.into(), i64_const!(1).into()],
"bytes_store"
);
}
fn storage_subscript(
&self,
bin: &Binary<'a>,
function: FunctionValue<'a>,
ty: &Type,
slot: IntValue<'a>,
index: BasicValueEnum<'a>,
) -> IntValue<'a> {
if let Type::StorageRef(_, ty) = ty {
if let Type::Array(inner, _) = *ty.clone() {
if !is_reference_type(&inner) {
let arr = bin
.builder
.build_array_alloca(
bin.context.i64_type(),
bin.context.i64_type().const_int(2, false),
"array_subscript",
)
.unwrap();
bin.builder.build_store(arr, slot).unwrap();
let index_ptr = unsafe {
bin.builder
.build_gep(
bin.context.i64_type().array_type(1),
arr,
&[
bin.context.i64_type().const_zero(),
bin.context.i64_type().const_int(1, false),
],
"index_ptr",
)
.unwrap()
};
bin.builder.build_store(index_ptr, index).unwrap();
let arr_ptr_as_int = bin
.builder
.build_ptr_to_int(arr, bin.context.i64_type(), "array_ptr_as_int")
.unwrap();
return arr_ptr_as_int;
}
}
}
let vec_new = bin
.builder
.build_call(
bin.module
.get_function(HostFunctions::VectorNew.name())
.unwrap(),
&[],
"vec_new",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
.into_int_value();
let slot_encoded = encode_value(
if slot.get_type().get_bit_width() == 64 {
slot
} else {
bin.builder
.build_int_z_extend(slot, bin.context.i64_type(), "slot64")
.unwrap()
},
32,
4,
bin,
);
let res = bin
.builder
.build_call(
bin.module
.get_function(HostFunctions::VecPushBack.name())
.unwrap(),
&[vec_new.as_basic_value_enum().into(), slot_encoded.into()],
"push",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
.into_int_value();
let res = bin
.builder
.build_call(
bin.module
.get_function(HostFunctions::VecPushBack.name())
.unwrap(),
&[res.as_basic_value_enum().into(), index.into()],
"push",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
.into_int_value();
res
}
fn storage_push(
&self,
bin: &Binary<'a>,
function: FunctionValue<'a>,
ty: &Type,
slot: IntValue<'a>,
val: Option<BasicValueEnum<'a>>,
) -> BasicValueEnum<'a> {
unsupported_soroban(Loc::Codegen, "storage array push")
}
fn storage_pop(
&self,
bin: &Binary<'a>,
function: FunctionValue<'a>,
ty: &Type,
slot: IntValue<'a>,
load: bool,
loc: Loc,
) -> Option<BasicValueEnum<'a>> {
unsupported_soroban(loc, "storage array pop")
}
fn storage_array_length(
&self,
bin: &Binary<'a>,
_function: FunctionValue,
slot: IntValue<'a>,
elem_ty: &Type,
) -> IntValue<'a> {
if !is_reference_type(elem_ty) {
let load_storage = bin
.builder
.build_call(
bin.module
.get_function(HostFunctions::GetContractData.name())
.unwrap(),
&[
slot.into(),
bin.context.i64_type().const_int(1, false).into(), ],
"load_storage",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
.into_int_value();
let u32_val = bin
.builder
.build_call(
bin.module
.get_function(HostFunctions::VecLen.name())
.unwrap(),
&[load_storage.into()],
"vec_len",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
.into_int_value();
return bin
.builder
.build_right_shift(
u32_val,
bin.context.i64_type().const_int(32, false),
false,
"length",
)
.unwrap();
}
let storage_ty = bin.context.i64_type().const_int(1, false);
let loaded_len = bin
.builder
.build_call(
bin.module
.get_function(HostFunctions::GetContractData.name())
.unwrap(),
&[slot.into(), storage_ty.into()],
"get_len",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
.into_int_value();
bin.builder
.build_right_shift(
loaded_len,
bin.context.i64_type().const_int(8, false),
false,
"length",
)
.unwrap()
}
fn keccak256_hash(
&self,
bin: &Binary<'a>,
src: PointerValue,
length: IntValue,
dest: PointerValue,
) {
unsupported_soroban(Loc::Codegen, "keccak256 hashing")
}
fn print<'b>(&self, bin: &Binary<'b>, string: PointerValue<'b>, length: IntValue<'b>) {
let msg_pos = bin
.builder
.build_ptr_to_int(string, bin.context.i64_type(), "msg_pos")
.unwrap();
let msg_pos_encoded = encode_value(msg_pos, 32, 4, bin);
let length_encoded = encode_value(length, 32, 4, bin);
bin.builder
.build_call(
bin.module
.get_function(HostFunctions::LogFromLinearMemory.name())
.unwrap(),
&[
msg_pos_encoded.into(),
length_encoded.into(),
msg_pos_encoded.into(),
encode_value(bin.context.i64_type().const_zero(), 32, 4, bin).into(),
],
"log",
)
.unwrap();
}
fn return_empty_abi(&self, bin: &Binary) {
unsupported_soroban(Loc::Codegen, "empty ABI returns")
}
fn return_code<'b>(&self, bin: &'b Binary, ret: IntValue<'b>) {
unsupported_soroban(Loc::Codegen, "return codes")
}
fn assert_failure(&self, bin: &Binary, data: PointerValue, length: IntValue) {
bin.builder.build_unreachable().unwrap();
}
fn builtin_function(
&self,
bin: &Binary<'a>,
function: FunctionValue<'a>,
builtin_func: &Function,
args: &[BasicMetadataValueEnum<'a>],
first_arg_type: Option<BasicTypeEnum>,
) -> Option<BasicValueEnum<'a>> {
unsupported_soroban(Loc::Codegen, "target-specific builtin functions")
}
fn create_contract<'b>(
&mut self,
bin: &Binary<'b>,
function: FunctionValue<'b>,
success: Option<&mut BasicValueEnum<'b>>,
contract_no: usize,
address: PointerValue<'b>,
encoded_args: BasicValueEnum<'b>,
encoded_args_len: BasicValueEnum<'b>,
contract_args: ContractArgs<'b>,
loc: Loc,
) {
unsupported_soroban(loc, "contract construction")
}
fn external_call<'b>(
&self,
bin: &Binary<'b>,
function: FunctionValue<'b>,
success: Option<&mut BasicValueEnum<'b>>,
payload: PointerValue<'b>,
payload_len: IntValue<'b>,
address: Option<BasicValueEnum<'b>>,
contract_args: ContractArgs<'b>,
ty: CallTy,
loc: Loc,
) {
let offset = bin.context.i64_type().const_int(0, false);
let start = unsafe {
bin.builder
.build_gep(
bin.context.i64_type().array_type(1),
payload,
&[bin.context.i64_type().const_zero(), offset],
"start",
)
.unwrap()
};
let symbol = bin
.builder
.build_load(bin.context.i64_type(), start, "symbol")
.unwrap()
.into_int_value();
let args_len = bin
.builder
.build_int_unsigned_div(
payload_len,
payload_len.get_type().const_int(8, false),
"args_len",
)
.unwrap();
let args_len = bin
.builder
.build_int_sub(
args_len,
args_len.get_type().const_int(1, false),
"args_len",
)
.unwrap();
let args_len_encoded = encode_value(args_len, 32, 4, bin);
let offset = bin.context.i64_type().const_int(1, false);
let args_ptr = unsafe {
bin.builder
.build_gep(
bin.context.i64_type().array_type(1),
payload,
&[bin.context.i64_type().const_zero(), offset],
"start",
)
.unwrap()
};
let args_ptr_to_int = bin
.builder
.build_ptr_to_int(args_ptr, bin.context.i64_type(), "args_ptr")
.unwrap();
let args_ptr_encoded = encode_value(args_ptr_to_int, 32, 4, bin);
let vec_object = bin
.builder
.build_call(
runtime_helper(
bin,
HostFunctions::VectorNewFromLinearMemory.name(),
"building Soroban external call argument vector",
),
&[args_ptr_encoded.into(), args_len_encoded.into()],
"vec_object",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap_or_else(|| {
expect_return_value(None, "building Soroban external call argument vector")
})
.into_int_value();
let call_res = bin
.builder
.build_call(
runtime_helper(
bin,
HostFunctions::Call.name(),
"emitting Soroban external call",
),
&[
expect_llvm_entity(
address,
"emitting Soroban external call",
"target contract address",
)
.into(),
symbol.into(),
vec_object.into(),
],
"call",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap_or_else(|| expect_return_value(None, "emitting Soroban external call"))
.into_int_value();
let allocate_i64 = bin
.builder
.build_alloca(bin.context.i64_type(), "allocate_i64")
.unwrap();
bin.builder.build_store(allocate_i64, call_res).unwrap();
*bin.return_data.borrow_mut() = Some(allocate_i64);
}
fn value_transfer<'b>(
&self,
_bin: &Binary<'b>,
_function: FunctionValue,
_success: Option<&mut BasicValueEnum<'b>>,
_address: PointerValue<'b>,
_value: IntValue<'b>,
loc: Loc,
) {
unsupported_soroban(loc, "value transfer")
}
fn builtin<'b>(
&self,
bin: &Binary<'b>,
expr: &Expression,
vartab: &HashMap<usize, Variable<'b>>,
function: FunctionValue<'b>,
) -> BasicValueEnum<'b> {
emit_context!(bin);
match expr {
Expression::Builtin {
kind: Builtin::Timestamp,
args,
..
} => {
assert_eq!(args.len(), 0, "timestamp expects no arguments");
let function_name = HostFunctions::GetLedgerTimestamp.name();
let function_value =
runtime_helper(bin, function_name, "reading Soroban ledger timestamp");
let timestamp_val = bin
.builder
.build_call(function_value, &[], function_name)
.unwrap()
.try_as_basic_value()
.left()
.unwrap_or_else(|| {
expect_return_value(None, "reading Soroban ledger timestamp")
})
.into_int_value();
let tag = bin
.builder
.build_and(
timestamp_val,
bin.context.i64_type().const_int(0xff, false),
"timestamp_tag",
)
.unwrap();
let is_u64_small = bin
.builder
.build_int_compare(
inkwell::IntPredicate::EQ,
tag,
bin.context.i64_type().const_int(6, false), "is_u64_small",
)
.unwrap();
let value_is_small = bin
.context
.append_basic_block(function, "timestamp_value_is_small");
let value_is_object = bin
.context
.append_basic_block(function, "timestamp_value_is_object");
let value_decoded = bin
.context
.append_basic_block(function, "timestamp_value_decoded");
bin.builder
.build_conditional_branch(is_u64_small, value_is_small, value_is_object)
.unwrap();
bin.builder.position_at_end(value_is_small);
let small_value = bin
.builder
.build_right_shift(
timestamp_val,
bin.context.i64_type().const_int(8, false),
false,
"timestamp_small_value",
)
.unwrap();
bin.builder
.build_unconditional_branch(value_decoded)
.unwrap();
let small_value_block = expect_llvm_entity(
bin.builder.get_insert_block(),
"decoding Soroban ledger timestamp",
"small timestamp block",
);
bin.builder.position_at_end(value_is_object);
let decode_function_name = HostFunctions::ObjToU64.name();
let decode_function_value = runtime_helper(
bin,
decode_function_name,
"decoding Soroban ledger timestamp object",
);
let object_value = bin
.builder
.build_call(
decode_function_value,
&[timestamp_val.into()],
decode_function_name,
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap_or_else(|| {
expect_return_value(None, "decoding Soroban ledger timestamp object")
})
.into_int_value();
bin.builder
.build_unconditional_branch(value_decoded)
.unwrap();
let object_value_block = expect_llvm_entity(
bin.builder.get_insert_block(),
"decoding Soroban ledger timestamp",
"object timestamp block",
);
bin.builder.position_at_end(value_decoded);
let timestamp = bin
.builder
.build_phi(bin.context.i64_type(), "timestamp")
.unwrap();
timestamp.add_incoming(&[
(&small_value, small_value_block),
(&object_value, object_value_block),
]);
timestamp.as_basic_value()
}
Expression::Builtin {
kind: Builtin::ExtendTtl,
args,
..
} => {
assert_eq!(args.len(), 4, "extendTtl expects 4 arguments");
let slot_no = match args.first().unwrap() {
Expression::NumberLiteral { value, .. } => value,
_ => panic!(
"Expected slot_no to be of type Expression::NumberLiteral. Actual: {:?}",
args.get(1).unwrap()
),
}
.to_u64()
.unwrap();
let threshold = match args.get(1).unwrap() {
Expression::NumberLiteral { value, .. } => value,
_ => panic!(
"Expected threshold to be of type Expression::NumberLiteral. Actual: {:?}",
args.get(1).unwrap()
),
}
.to_u64()
.unwrap();
let extend_to = match args.get(2).unwrap() {
Expression::NumberLiteral { value, .. } => value,
_ => panic!(
"Expected extend_to to be of type Expression::NumberLiteral. Actual: {:?}",
args.get(2).unwrap()
),
}
.to_u64()
.unwrap();
let storage_type = match args.get(3).unwrap() {
Expression::NumberLiteral { value, .. } => value,
_ => panic!(
"Expected storage_type to be of type Expression::NumberLiteral. Actual: {:?}",
args.get(3).unwrap()
),
}
.to_u64()
.unwrap();
let threshold_u32_val = (threshold << 32) + 4;
let extend_to_u32_val = (extend_to << 32) + 4;
let function_name = HostFunctions::ExtendContractDataTtl.name();
let function_value =
runtime_helper(bin, function_name, "emitting Soroban extendTtl builtin");
let value = bin
.builder
.build_call(
function_value,
&[
bin.context.i64_type().const_int(slot_no, false).into(),
bin.context.i64_type().const_int(storage_type, false).into(),
bin.context
.i64_type()
.const_int(threshold_u32_val, false)
.into(),
bin.context
.i64_type()
.const_int(extend_to_u32_val, false)
.into(),
],
function_name,
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap_or_else(|| {
expect_return_value(None, "emitting Soroban extendTtl builtin")
})
.into_int_value();
value.into()
}
Expression::Builtin {
kind: Builtin::ExtendInstanceTtl,
args,
..
} => {
assert_eq!(args.len(), 2, "extendTtl expects 2 arguments");
let threshold = match args.first().unwrap() {
Expression::NumberLiteral { value, .. } => value,
_ => panic!(
"Expected threshold to be of type Expression::NumberLiteral. Actual: {:?}",
args.get(1).unwrap()
),
}
.to_u64()
.unwrap();
let extend_to = match args.get(1).unwrap() {
Expression::NumberLiteral { value, .. } => value,
_ => panic!(
"Expected extend_to to be of type Expression::NumberLiteral. Actual: {:?}",
args.get(2).unwrap()
),
}
.to_u64()
.unwrap();
let threshold_u32_val = (threshold << 32) + 4;
let extend_to_u32_val = (extend_to << 32) + 4;
let function_name = HostFunctions::ExtendCurrentContractInstanceAndCodeTtl.name();
let function_value = runtime_helper(
bin,
function_name,
"emitting Soroban extendInstanceTtl builtin",
);
let value = bin
.builder
.build_call(
function_value,
&[
bin.context
.i64_type()
.const_int(threshold_u32_val, false)
.into(),
bin.context
.i64_type()
.const_int(extend_to_u32_val, false)
.into(),
],
function_name,
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap_or_else(|| {
expect_return_value(None, "emitting Soroban extendInstanceTtl builtin")
})
.into_int_value();
value.into()
}
_ => unsupported_soroban(expr.loc(), "this Soroban builtin"),
}
}
fn return_data<'b>(&self, bin: &Binary<'b>, function: FunctionValue<'b>) -> PointerValue<'b> {
bin.return_data.borrow().unwrap()
}
fn value_transferred<'b>(&self, bin: &Binary<'b>) -> IntValue<'b> {
unsupported_soroban(Loc::Codegen, "transferred value reads")
}
fn selfdestruct<'b>(&self, bin: &Binary<'b>, addr: ArrayValue<'b>) {
unsupported_soroban(Loc::Codegen, "selfdestruct")
}
fn hash<'b>(
&self,
bin: &Binary<'b>,
function: FunctionValue<'b>,
hash: HashTy,
string: PointerValue<'b>,
length: IntValue<'b>,
) -> IntValue<'b> {
unsupported_soroban(Loc::Codegen, "hash builtins")
}
fn emit_event<'b>(
&self,
bin: &Binary<'b>,
_function: FunctionValue<'b>,
data: BasicValueEnum<'b>,
topics: &[BasicValueEnum<'b>],
) {
emit_context!(bin);
let mut topics_vec = call!(HostFunctions::VectorNew.name(), &[])
.try_as_basic_value()
.left()
.unwrap();
for topic in topics.iter() {
topics_vec = call!(
HostFunctions::VecPushBack.name(),
&[topics_vec.into(), (*topic).into()]
)
.try_as_basic_value()
.left()
.unwrap();
}
call!(
HostFunctions::ContractEvent.name(),
&[topics_vec.into(), data.into()]
);
}
fn return_abi_data<'b>(
&self,
bin: &Binary<'b>,
data: PointerValue<'b>,
data_len: BasicValueEnum<'b>,
) {
unsupported_soroban(Loc::Codegen, "ABI data returns")
}
}
fn storage_type_to_int(storage_type: &Option<StorageType>) -> u64 {
if let Some(storage_type) = storage_type {
match storage_type {
StorageType::Temporary(_) => 0,
StorageType::Persistent(_) => 1,
StorageType::Instance(_) => 2,
}
} else {
1
}
}
fn encode_value<'a>(
mut value: IntValue<'a>,
shift: u64,
add: u64,
bin: &Binary<'a>,
) -> IntValue<'a> {
match value.get_type().get_bit_width() {
32 =>
{
value = bin
.builder
.build_int_z_extend(value, bin.context.i64_type(), "temp")
.unwrap();
}
64 => (),
_ => invalid_cfg(
"encoding Soroban host value",
"expected a 32-bit or 64-bit integer value",
),
}
let shifted = bin
.builder
.build_left_shift(
value,
bin.context.i64_type().const_int(shift, false),
"temp",
)
.unwrap();
bin.builder
.build_int_add(
shifted,
bin.context.i64_type().const_int(add, false),
"encoded",
)
.unwrap()
}
fn load_slot_index_from_key_ptr<'a>(
bin: &Binary<'a>,
key_ptr: PointerValue<'a>,
) -> (IntValue<'a>, IntValue<'a>) {
let slot_val = bin
.builder
.build_load(bin.context.i64_type(), key_ptr, "key_slot")
.unwrap()
.into_int_value(); let index_ptr = unsafe {
bin.builder
.build_gep(
bin.context.i64_type(),
key_ptr,
&[bin.context.i64_type().const_int(1, false)],
"key_index_ptr",
)
.unwrap()
}; let index_val = bin
.builder
.build_load(bin.context.i64_type(), index_ptr, "key_index")
.unwrap()
.into_int_value(); (slot_val, index_val)
}
fn get_storage_vec_subscript<'a>(
bin: &Binary<'a>,
_function: FunctionValue<'a>,
key_vec: IntValue<'a>,
) -> BasicValueEnum<'a> {
let key_ptr = bin
.builder
.build_int_to_ptr(key_vec, bin.context.ptr_type(Default::default()), "key_ptr")
.unwrap(); let (slot_val, index_val) = load_slot_index_from_key_ptr(bin, key_ptr);
let vec_obj = bin
.builder
.build_call(
bin.module
.get_function(HostFunctions::GetContractData.name())
.unwrap(),
&[
slot_val.into(),
bin.context.i64_type().const_int(1, false).into(),
],
"load_storage",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
.into_int_value(); let index_val = encode_value(index_val, 32, 4, bin); let elem_val = bin
.builder
.build_call(
bin.module
.get_function(HostFunctions::VecGet.name())
.unwrap(),
&[vec_obj.into(), index_val.into()],
"vec_get",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
.into_int_value(); elem_val.as_basic_value_enum()
}
fn set_storage_vec_subscript<'a>(
bin: &Binary<'a>,
_function: FunctionValue<'a>,
key_vec: IntValue<'a>,
value: IntValue<'a>,
) {
let key_ptr = bin
.builder
.build_int_to_ptr(key_vec, bin.context.ptr_type(Default::default()), "key_ptr")
.unwrap(); let (slot_val, index_val) = load_slot_index_from_key_ptr(bin, key_ptr);
let index_val = encode_value(index_val, 32, 4, bin);
let vec_obj = bin
.builder
.build_call(
bin.module
.get_function(HostFunctions::GetContractData.name())
.unwrap(),
&[
slot_val.into(),
bin.context.i64_type().const_int(1, false).into(),
],
"load_storage",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
.into_int_value(); let new_vec_obj = bin
.builder
.build_call(
bin.module
.get_function(HostFunctions::VecPut.name())
.unwrap(),
&[vec_obj.into(), index_val.into(), value.into()],
"vec_put",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
.into_int_value(); let _store_storage = bin
.builder
.build_call(
bin.module
.get_function(HostFunctions::PutContractData.name())
.unwrap(),
&[
slot_val.into(),
new_vec_obj.into(),
bin.context.i64_type().const_int(1, false).into(),
],
"store_storage",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
.into_int_value(); }
fn is_val_true<'ctx>(bin: &Binary<'ctx>, val: IntValue<'ctx>) -> IntValue<'ctx> {
let tag_mask = bin.context.i64_type().const_int(0xff, false);
let tag_true = bin.context.i64_type().const_int(1, false);
let tag = bin
.builder
.build_and(val, tag_mask, "val_tag")
.expect("build_and failed");
bin.builder
.build_int_compare(inkwell::IntPredicate::EQ, tag, tag_true, "is_val_true")
.expect("build_int_compare failed")
}
pub fn type_to_tagged_zero_val<'ctx>(bin: &Binary<'ctx>, ty: &Type) -> IntValue<'ctx> {
let context = &bin.context;
let i64_type = context.i64_type();
let tag = match ty {
Type::Bool => 0, Type::Uint(32) => 4, Type::Int(32) => 5, Type::Enum(_) => 4, Type::Uint(64) => 6, Type::Int(64) => 7, Type::Uint(128) => 10, Type::Int(128) => 11, Type::Uint(256) => 12, Type::Int(256) => 13, Type::Bytes(_) => 72, Type::String => 73, Type::DynamicBytes => 72, Type::Address(_) => 77, Type::Void => 2, _ => {
2 }
};
let tag_val: u64 = tag;
i64_type.const_int(tag_val, false)
}
pub fn soroban_put_fields_from_val_buffer<'a>(
bin: &Binary<'a>,
_function: FunctionValue<'a>,
base_key_vec: IntValue<'a>,
buffer_ptr: PointerValue<'a>,
field_count: usize,
storage_type: u64,
) {
emit_context!(bin);
let i64_t = bin.context.i64_type();
for i in 0..field_count {
let byte_offset = i64_t.const_int(i as u64, false);
let field_byte_ptr = unsafe {
bin.builder
.build_gep(i64_t, buffer_ptr, &[byte_offset], "field_byte_ptr")
.unwrap()
};
let field_val_i64 = bin
.builder
.build_load(i64_t, field_byte_ptr, "field_val_i64")
.unwrap()
.into_int_value();
let idx_u32 = bin.context.i32_type().const_int(i as u64, false);
let idx_val = encode_value(idx_u32, 32, 4, bin);
let field_key_vec = bin
.builder
.build_call(
bin.module
.get_function(HostFunctions::VecPushBack.name())
.unwrap(),
&[base_key_vec.into(), idx_val.into()],
"key_push_field",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
.into_int_value();
let storage_ty_val = bin.context.i64_type().const_int(storage_type, false);
let _ = bin
.builder
.build_call(
bin.module
.get_function(HostFunctions::PutContractData.name())
.unwrap(),
&[
field_key_vec.into(),
field_val_i64.into(),
storage_ty_val.into(),
],
"put_field_from_buffer",
)
.unwrap();
}
}
pub fn soroban_get_fields_to_val_buffer<'a>(
bin: &Binary<'a>,
function: FunctionValue<'a>,
base_key_vec: IntValue<'a>,
field_count: usize,
storage_type: u64,
) -> PointerValue<'a> {
emit_context!(bin);
let size_bytes = bin
.context
.i32_type()
.const_int((field_count as u64) * 8, false);
let vec_ptr = bin
.builder
.build_call(
runtime_helper(
bin,
"soroban_malloc",
"allocating Soroban storage struct field buffer",
),
&[size_bytes.into()],
"soroban_malloc",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap_or_else(|| {
expect_return_value(None, "allocating Soroban storage struct field buffer")
})
.into_pointer_value();
let storage_ty_i64 = bin.context.i64_type().const_int(storage_type, false);
for i in 0..field_count {
let idx_u32 = bin.context.i32_type().const_int(i as u64, false);
let idx_val = encode_value(idx_u32, 32, 4, bin);
let field_key_vec = bin
.builder
.build_call(
bin.module
.get_function(HostFunctions::VecPushBack.name())
.unwrap(),
&[base_key_vec.into(), idx_val.into()],
"key_push_field",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
.into_int_value();
let has_val = bin
.builder
.build_call(
bin.module
.get_function(HostFunctions::HasContractData.name())
.unwrap(),
&[field_key_vec.into(), storage_ty_i64.into()],
"has_field",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
.into_int_value();
let cond = is_val_true(bin, has_val);
let then_bb = bin.context.append_basic_block(function, "load_field");
let else_bb = bin.context.append_basic_block(function, "skip_field");
let cont_bb = bin.context.append_basic_block(function, "cont_field");
bin.builder
.build_conditional_branch(cond, then_bb, else_bb)
.unwrap();
bin.builder.position_at_end(then_bb);
let val_i64 = bin
.builder
.build_call(
bin.module
.get_function(HostFunctions::GetContractData.name())
.unwrap(),
&[field_key_vec.into(), storage_ty_i64.into()],
"get_field",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
.into_int_value();
let idx64 = bin.context.i64_type().const_int((i) as u64, false);
let elem_ptr = unsafe {
bin.builder
.build_gep(bin.context.i64_type(), vec_ptr, &[idx64], "elem_ptr")
.unwrap()
};
bin.builder.build_store(elem_ptr, val_i64).unwrap();
bin.builder.build_unconditional_branch(cont_bb).unwrap();
bin.builder.position_at_end(else_bb);
bin.builder.build_unconditional_branch(cont_bb).unwrap();
bin.builder.position_at_end(cont_bb);
}
vec_ptr
}
fn is_reference_type(ty: &Type) -> bool {
match ty {
Type::Bool => false,
Type::Address(_) => false,
Type::Int(_) => false,
Type::Uint(_) => false,
Type::Rational => false,
Type::Bytes(_) => false,
Type::Enum(_) => false,
Type::Struct(_) => true,
Type::Array(..) => true,
Type::DynamicBytes => true,
Type::String => true,
Type::Mapping(..) => true,
Type::Contract(_) => false,
Type::InternalFunction { .. } => false,
_ => false,
}
}