use crate::codegen::cfg::HashTy;
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::pt::{Loc, StorageType};
use num_traits::ToPrimitive;
use std::collections::HashMap;
#[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> {
todo!()
}
fn storage_load(
&self,
bin: &Binary<'a>,
ty: &ast::Type,
slot: &mut IntValue<'a>,
function: FunctionValue<'a>,
storage_type: &Option<StorageType>,
) -> BasicValueEnum<'a> {
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
};
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>,
dest: BasicValueEnum<'a>,
function: FunctionValue<'a>,
storage_type: &Option<StorageType>,
) {
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 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>,
) {
unimplemented!()
}
fn set_storage_string(
&self,
bin: &Binary<'a>,
function: FunctionValue<'a>,
slot: PointerValue<'a>,
dest: BasicValueEnum<'a>,
) {
unimplemented!()
}
fn get_storage_string(
&self,
bin: &Binary<'a>,
function: FunctionValue,
slot: PointerValue<'a>,
) -> PointerValue<'a> {
unimplemented!()
}
fn set_storage_extfunc(
&self,
bin: &Binary<'a>,
function: FunctionValue,
slot: PointerValue,
dest: PointerValue,
dest_ty: BasicTypeEnum,
) {
unimplemented!()
}
fn get_storage_extfunc(
&self,
bin: &Binary<'a>,
function: FunctionValue,
slot: PointerValue<'a>,
) -> PointerValue<'a> {
unimplemented!()
}
fn get_storage_bytes_subscript(
&self,
bin: &Binary<'a>,
function: FunctionValue,
slot: IntValue<'a>,
index: IntValue<'a>,
loc: Loc,
) -> IntValue<'a> {
unimplemented!()
}
fn set_storage_bytes_subscript(
&self,
bin: &Binary<'a>,
function: FunctionValue,
slot: IntValue<'a>,
index: IntValue<'a>,
value: IntValue<'a>,
loc: Loc,
) {
unimplemented!()
}
fn storage_subscript(
&self,
bin: &Binary<'a>,
function: FunctionValue<'a>,
ty: &Type,
slot: IntValue<'a>,
index: BasicValueEnum<'a>,
) -> IntValue<'a> {
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 = if slot.is_const() {
slot.as_basic_value_enum()
.into_int_value()
.const_cast(bin.context.i64_type(), false)
} else {
slot
};
let res = bin
.builder
.build_call(
bin.module
.get_function(HostFunctions::VecPushBack.name())
.unwrap(),
&[vec_new.as_basic_value_enum().into(), slot.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> {
unimplemented!()
}
fn storage_pop(
&self,
bin: &Binary<'a>,
function: FunctionValue<'a>,
ty: &Type,
slot: IntValue<'a>,
load: bool,
loc: Loc,
) -> Option<BasicValueEnum<'a>> {
unimplemented!()
}
fn storage_array_length(
&self,
_bin: &Binary<'a>,
_function: FunctionValue,
_slot: IntValue<'a>,
_elem_ty: &Type,
) -> IntValue<'a> {
unimplemented!()
}
fn keccak256_hash(
&self,
bin: &Binary<'a>,
src: PointerValue,
length: IntValue,
dest: PointerValue,
) {
unimplemented!()
}
fn print(&self, bin: &Binary, string: PointerValue, length: IntValue) {
if string.is_const() && length.is_const() {
let msg_pos = bin
.builder
.build_ptr_to_int(string, bin.context.i64_type(), "msg_pos")
.unwrap();
let length = length.const_cast(bin.context.i64_type(), false);
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();
} else {
todo!("Dynamic String printing is not yet supported")
}
}
fn return_empty_abi(&self, bin: &Binary) {
unimplemented!()
}
fn return_code<'b>(&self, bin: &'b Binary, ret: IntValue<'b>) {
unimplemented!()
}
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>> {
unimplemented!()
}
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,
) {
unimplemented!()
}
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,
bin.context.i64_type().const_int(8, false),
"args_len",
)
.unwrap();
let args_len = bin
.builder
.build_int_sub(
args_len,
bin.context.i64_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(
bin.module
.get_function(HostFunctions::VectorNewFromLinearMemory.name())
.unwrap(),
&[args_ptr_encoded.into(), args_len_encoded.into()],
"vec_object",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
.into_int_value();
let call_res = bin
.builder
.build_call(
bin.module.get_function(HostFunctions::Call.name()).unwrap(),
&[address.unwrap().into(), symbol.into(), vec_object.into()],
"call",
)
.unwrap()
.try_as_basic_value()
.left()
.unwrap()
.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,
) {
unimplemented!()
}
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::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 = bin.module.get_function(function_name).unwrap();
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()
.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 = bin.module.get_function(function_name).unwrap();
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()
.into_int_value();
value.into()
}
_ => unimplemented!("unsupported 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> {
unimplemented!()
}
fn selfdestruct<'b>(&self, bin: &Binary<'b>, addr: ArrayValue<'b>) {
unimplemented!()
}
fn hash<'b>(
&self,
bin: &Binary<'b>,
function: FunctionValue<'b>,
hash: HashTy,
string: PointerValue<'b>,
length: IntValue<'b>,
) -> IntValue<'b> {
unimplemented!()
}
fn emit_event<'b>(
&self,
bin: &Binary<'b>,
function: FunctionValue<'b>,
data: BasicValueEnum<'b>,
topics: &[BasicValueEnum<'b>],
) {
unimplemented!()
}
fn return_abi_data<'b>(
&self,
bin: &Binary<'b>,
data: PointerValue<'b>,
data_len: BasicValueEnum<'b>,
) {
unimplemented!()
}
}
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>(value: IntValue<'a>, shift: u64, add: u64, bin: &'a Binary) -> IntValue<'a> {
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 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::Uint(64) => 6, Type::Int(64) => 7, Type::Uint(128) => 10, Type::Int(128) => 11, Type::Uint(256) => 12, Type::Int(256) => 13, Type::String => 73, Type::Address(_) => 77, Type::Void => 2, _ => {
2 }
};
let tag_val: u64 = tag;
i64_type.const_int(tag_val, false)
}