pub(super) mod target;
use crate::codegen::{cfg::ControlFlowGraph, HostFunctions, Options};
use crate::emit::cfg::emit_cfg;
use crate::{emit::Binary, sema::ast};
use funty::Fundamental;
use inkwell::{
context::Context,
module::{Linkage, Module},
types::FunctionType,
};
use soroban_sdk::xdr::{
Limited, Limits, ScEnvMetaEntry, ScEnvMetaEntryInterfaceVersion, ScSpecEntry,
ScSpecFunctionInputV0, ScSpecFunctionV0, ScSpecTypeDef, StringM, WriteXdr,
};
const SOROBAN_ENV_INTERFACE_VERSION: ScEnvMetaEntryInterfaceVersion =
ScEnvMetaEntryInterfaceVersion {
protocol: 22,
pre_release: 0,
};
impl HostFunctions {
pub fn function_signature<'b>(&self, bin: &Binary<'b>) -> FunctionType<'b> {
let ty = bin.context.i64_type();
match self {
HostFunctions::PutContractData => bin
.context
.i64_type()
.fn_type(&[ty.into(), ty.into(), ty.into()], false),
HostFunctions::GetContractData => bin
.context
.i64_type()
.fn_type(&[ty.into(), ty.into()], false),
HostFunctions::HasContractData => bin
.context
.i64_type()
.fn_type(&[ty.into(), ty.into()], false),
HostFunctions::ExtendContractDataTtl => bin
.context
.i64_type()
.fn_type(&[ty.into(), ty.into(), ty.into(), ty.into()], false),
HostFunctions::ExtendCurrentContractInstanceAndCodeTtl => bin
.context
.i64_type()
.fn_type(&[ty.into(), ty.into()], false),
HostFunctions::LogFromLinearMemory => bin
.context
.i64_type()
.fn_type(&[ty.into(), ty.into(), ty.into(), ty.into()], false),
HostFunctions::SymbolNewFromLinearMemory => bin
.context
.i64_type()
.fn_type(&[ty.into(), ty.into()], false),
HostFunctions::VectorNew => bin.context.i64_type().fn_type(&[], false),
HostFunctions::Call => bin
.context
.i64_type()
.fn_type(&[ty.into(), ty.into(), ty.into()], false),
HostFunctions::VectorNewFromLinearMemory => bin
.context
.i64_type()
.fn_type(&[ty.into(), ty.into()], false),
HostFunctions::ObjToU64 => bin.context.i64_type().fn_type(&[ty.into()], false),
HostFunctions::ObjFromU64 => bin.context.i64_type().fn_type(&[ty.into()], false),
HostFunctions::RequireAuth => bin.context.i64_type().fn_type(&[ty.into()], false),
HostFunctions::AuthAsCurrContract => {
bin.context.i64_type().fn_type(&[ty.into()], false)
}
HostFunctions::MapNewFromLinearMemory => bin
.context
.i64_type()
.fn_type(&[ty.into(), ty.into(), ty.into()], false),
HostFunctions::MapNew => bin.context.i64_type().fn_type(&[], false),
HostFunctions::MapPut => bin
.context
.i64_type()
.fn_type(&[ty.into(), ty.into(), ty.into()], false),
HostFunctions::VecPushBack => bin
.context
.i64_type()
.fn_type(&[ty.into(), ty.into()], false),
HostFunctions::StringNewFromLinearMemory => bin
.context
.i64_type()
.fn_type(&[ty.into(), ty.into()], false),
HostFunctions::StrKeyToAddr => bin.context.i64_type().fn_type(&[ty.into()], false),
HostFunctions::GetCurrentContractAddress => bin.context.i64_type().fn_type(&[], false),
HostFunctions::ObjToI128Lo64 => bin.context.i64_type().fn_type(&[ty.into()], false),
HostFunctions::ObjToI128Hi64 => bin.context.i64_type().fn_type(&[ty.into()], false),
HostFunctions::ObjToU128Lo64 => bin.context.i64_type().fn_type(&[ty.into()], false),
HostFunctions::ObjToU128Hi64 => bin.context.i64_type().fn_type(&[ty.into()], false),
HostFunctions::ObjFromI128Pieces => bin
.context
.i64_type()
.fn_type(&[ty.into(), ty.into()], false),
HostFunctions::ObjFromU128Pieces => bin
.context
.i64_type()
.fn_type(&[ty.into(), ty.into()], false),
}
}
}
pub struct SorobanTarget;
impl SorobanTarget {
pub fn build<'a>(
context: &'a Context,
std_lib: &Module<'a>,
contract: &'a ast::Contract,
ns: &'a ast::Namespace,
opt: &'a Options,
contract_no: usize,
) -> Binary<'a> {
let filename = ns.files[contract.loc.file_no()].file_name();
let mut bin = Binary::new(
context,
ns,
&contract.id.name,
&filename,
opt,
std_lib,
None,
);
let mut export_list = Vec::new();
Self::declare_externals(&mut bin);
Self::emit_functions_with_spec(contract, &mut bin, context, contract_no, &mut export_list);
bin.internalize(export_list.as_slice());
Self::emit_env_meta_entries(context, &mut bin, opt);
bin
}
fn emit_functions_with_spec<'a>(
contract: &'a ast::Contract,
bin: &mut Binary<'a>,
context: &'a Context,
_contract_no: usize,
export_list: &mut Vec<&'a str>,
) {
let mut defines = Vec::new();
for (cfg_no, cfg) in contract.cfg.iter().enumerate() {
let ftype = bin.function_type(
&cfg.params.iter().map(|p| p.ty.clone()).collect::<Vec<_>>(),
&cfg.returns.iter().map(|p| p.ty.clone()).collect::<Vec<_>>(),
);
let linkage = if cfg.public {
let name = if cfg.name.contains("::") {
cfg.name.split("::").collect::<Vec<&str>>()[2]
} else {
&cfg.name
};
Self::emit_function_spec_entry(context, cfg, name.to_string(), bin);
export_list.push(name);
Linkage::External
} else {
Linkage::Internal
};
let func_decl = if let Some(func) = bin.module.get_function(&cfg.name) {
assert_eq!(func.get_first_basic_block(), None);
func
} else {
bin.module.add_function(&cfg.name, ftype, Some(linkage))
};
bin.functions.insert(cfg_no, func_decl);
defines.push((func_decl, cfg));
}
let init_type = context.i64_type().fn_type(&[], false);
bin.module
.add_function("storage_initializer", init_type, None);
for (func_decl, cfg) in defines {
emit_cfg(&mut SorobanTarget, bin, contract, cfg, func_decl);
}
}
fn emit_env_meta_entries<'a>(context: &'a Context, bin: &mut Binary<'a>, opt: &'a Options) {
let mut meta = Limited::new(Vec::new(), Limits::none());
let soroban_env_interface_version = opt.soroban_version;
let soroban_env_interface_version = match soroban_env_interface_version {
Some(version) => ScEnvMetaEntryInterfaceVersion {
protocol: version.as_u32(),
pre_release: 0,
},
None => SOROBAN_ENV_INTERFACE_VERSION,
};
ScEnvMetaEntry::ScEnvMetaKindInterfaceVersion(soroban_env_interface_version)
.write_xdr(&mut meta)
.expect("writing env meta interface version to xdr");
Self::add_custom_section(context, &bin.module, "contractenvmetav0", meta.inner);
}
fn emit_function_spec_entry<'a>(
context: &'a Context,
cfg: &ControlFlowGraph,
name: String,
bin: &mut Binary<'a>,
) {
if cfg.public && !cfg.is_placeholder() {
let mut spec = Limited::new(Vec::new(), Limits::none());
ScSpecEntry::FunctionV0(ScSpecFunctionV0 {
name: name
.try_into()
.unwrap_or_else(|_| panic!("function name {:?} exceeds limit", cfg.name)),
inputs: cfg
.params
.iter()
.enumerate()
.map(|(i, p)| ScSpecFunctionInputV0 {
name: p
.id
.as_ref()
.map(|id| id.to_string())
.unwrap_or_else(|| i.to_string())
.try_into()
.expect("function input name exceeds limit"),
type_: {
let ty = if let ast::Type::Ref(ty) = &p.ty {
ty.as_ref()
} else {
&p.ty
};
match ty {
ast::Type::Uint(32) => ScSpecTypeDef::U32,
ast::Type::Int(32) => ScSpecTypeDef::I32,
ast::Type::Uint(64) => ScSpecTypeDef::U64,
&ast::Type::Int(64) => ScSpecTypeDef::I64,
ast::Type::Int(128) => ScSpecTypeDef::I128,
ast::Type::Uint(128) => ScSpecTypeDef::U128,
ast::Type::Bool => ScSpecTypeDef::Bool,
ast::Type::Address(_) => ScSpecTypeDef::Address,
ast::Type::Bytes(_) => ScSpecTypeDef::Bytes,
ast::Type::String => ScSpecTypeDef::String,
_ => panic!("unsupported input type {:?}", p.ty),
}
}, doc: StringM::default(), })
.collect::<Vec<_>>()
.try_into()
.expect("function input count exceeds limit"),
outputs: cfg
.returns
.iter()
.map(|return_type| {
let ret_type = return_type.ty.clone();
let ty = if let ast::Type::Ref(ty) = ret_type {
*ty
} else {
ret_type
};
match ty {
ast::Type::Uint(32) => ScSpecTypeDef::U32,
ast::Type::Int(32) => ScSpecTypeDef::I32,
ast::Type::Uint(64) => ScSpecTypeDef::U64,
ast::Type::Int(128) => ScSpecTypeDef::I128,
ast::Type::Uint(128) => ScSpecTypeDef::U128,
ast::Type::Int(_) => ScSpecTypeDef::I32,
ast::Type::Bool => ScSpecTypeDef::Bool,
ast::Type::Address(_) => ScSpecTypeDef::Address,
ast::Type::Bytes(_) => ScSpecTypeDef::Bytes,
ast::Type::String => ScSpecTypeDef::String,
ast::Type::Void => ScSpecTypeDef::Void,
_ => panic!("unsupported return type {:?}", ty),
}
}) .collect::<Vec<_>>()
.try_into()
.expect("function output count exceeds limit"),
doc: StringM::default(), })
.write_xdr(&mut spec)
.unwrap_or_else(|_| panic!("writing spec to xdr for function {}", cfg.name));
Self::add_custom_section(context, &bin.module, "contractspecv0", spec.inner);
}
}
fn add_custom_section<'a>(
context: &'a Context,
module: &Module<'a>,
name: &'a str,
value: Vec<u8>,
) {
let value_str = unsafe {
String::from_utf8_unchecked(value)
};
module
.add_global_metadata(
"wasm.custom_sections",
&context.metadata_node(&[
context.metadata_string(name).into(),
context.metadata_string(&value_str).into(),
]),
)
.expect("adding spec as metadata");
}
fn declare_externals(bin: &mut Binary) {
let host_functions = [
HostFunctions::PutContractData,
HostFunctions::GetContractData,
HostFunctions::HasContractData,
HostFunctions::ExtendContractDataTtl,
HostFunctions::ExtendCurrentContractInstanceAndCodeTtl,
HostFunctions::LogFromLinearMemory,
HostFunctions::SymbolNewFromLinearMemory,
HostFunctions::VectorNew,
HostFunctions::Call,
HostFunctions::VectorNewFromLinearMemory,
HostFunctions::ObjToU64,
HostFunctions::ObjFromU64,
HostFunctions::PutContractData,
HostFunctions::ObjToI128Lo64,
HostFunctions::ObjToI128Hi64,
HostFunctions::ObjToU128Lo64,
HostFunctions::ObjToU128Hi64,
HostFunctions::ObjFromI128Pieces,
HostFunctions::ObjFromU128Pieces,
HostFunctions::RequireAuth,
HostFunctions::AuthAsCurrContract,
HostFunctions::MapNewFromLinearMemory,
HostFunctions::MapNew,
HostFunctions::MapPut,
HostFunctions::VecPushBack,
HostFunctions::StringNewFromLinearMemory,
HostFunctions::StrKeyToAddr,
HostFunctions::GetCurrentContractAddress,
];
for func in &host_functions {
bin.module.add_function(
func.name(),
func.function_signature(bin),
Some(Linkage::External),
);
}
}
}