follow the C abi for effect functions the host exposes

This commit is contained in:
Folkert 2021-08-25 16:28:21 +02:00
parent 12fb36d1df
commit 8846841a95
5 changed files with 175 additions and 69 deletions

View File

@ -579,6 +579,8 @@ fn link_wasm32(
"str",
zig_str_path.to_str().unwrap(),
"--pkg-end",
// useful for debugging
// "-femit-llvm-ir=/home/folkertdev/roc/roc/examples/benchmarks/platform/host.ll",
])
.spawn()?;

View File

@ -265,6 +265,8 @@ pub fn gen_from_mono_module(
.expect("Writing .o file failed");
}
Architecture::Wasm32 => {
// Useful for debugging
// module.print_to_file(app_ll_file);
module.write_bitcode_to_path(app_o_file);
}
_ => panic!(

View File

@ -176,10 +176,22 @@ impl std::convert::TryFrom<u32> for PanicTagId {
}
impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> {
/// The integer type representing a pointer
///
/// on 64-bit systems, this is i64
/// on 32-bit systems, this is i32
pub fn ptr_int(&self) -> IntType<'ctx> {
ptr_int(self.context, self.ptr_bytes)
}
/// The integer type representing a RocList or RocStr when following the C ABI
///
/// on 64-bit systems, this is i128
/// on 32-bit systems, this is i64
pub fn str_list_int(&self) -> IntType<'ctx> {
crate::llvm::convert::str_list_int(self.context, self.ptr_bytes)
}
pub fn small_str_bytes(&self) -> u32 {
self.ptr_bytes * 2
}
@ -935,11 +947,7 @@ pub fn build_exp_call<'a, 'ctx, 'env>(
CallType::Foreign {
foreign_symbol,
ret_layout,
} => {
// we always initially invoke foreign symbols, but if there is nothing to clean up,
// we emit a normal call
build_foreign_symbol(env, scope, foreign_symbol, arguments, ret_layout)
}
} => build_foreign_symbol(env, scope, foreign_symbol, arguments, ret_layout),
}
}
@ -5180,89 +5188,171 @@ fn run_low_level<'a, 'ctx, 'env>(
}
}
fn build_foreign_symbol_return_result<'a, 'ctx, 'env>(
/// A type that is valid according to the C ABI
///
/// As an example, structs that fit inside an integer type should
/// (this does not currently happen here) be coerced to that integer type.
fn to_cc_type<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &mut Scope<'a, 'ctx>,
foreign: &roc_module::ident::ForeignSymbol,
arguments: &[Symbol],
return_type: BasicTypeEnum<'ctx>,
) -> (FunctionValue<'ctx>, &'a [BasicValueEnum<'ctx>]) {
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(arguments.len(), env.arena);
let mut arg_types = Vec::with_capacity_in(arguments.len() + 1, env.arena);
for arg in arguments.iter() {
let (value, layout) = load_symbol_and_layout(scope, arg);
arg_vals.push(value);
let arg_type = basic_type_from_layout(env, layout);
debug_assert_eq!(arg_type, value.get_type());
arg_types.push(arg_type);
layout: &Layout<'a>,
) -> BasicTypeEnum<'ctx> {
match layout {
Layout::Builtin(builtin) => to_cc_type_builtin(env, builtin),
_ => {
// TODO this is almost certainly incorrect for bigger structs
basic_type_from_layout(env, layout)
}
}
let function_type = return_type.fn_type(&arg_types, false);
let function = get_foreign_symbol(env, foreign.clone(), function_type);
(function, arg_vals.into_bump_slice())
}
fn build_foreign_symbol_write_result_into_ptr<'a, 'ctx, 'env>(
fn to_cc_type_builtin<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &mut Scope<'a, 'ctx>,
foreign: &roc_module::ident::ForeignSymbol,
arguments: &[Symbol],
return_pointer: PointerValue<'ctx>,
) -> (FunctionValue<'ctx>, &'a [BasicValueEnum<'ctx>]) {
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(arguments.len(), env.arena);
let mut arg_types = Vec::with_capacity_in(arguments.len() + 1, env.arena);
arg_vals.push(return_pointer.into());
arg_types.push(return_pointer.get_type().into());
for arg in arguments.iter() {
let (value, layout) = load_symbol_and_layout(scope, arg);
arg_vals.push(value);
let arg_type = basic_type_from_layout(env, layout);
debug_assert_eq!(arg_type, value.get_type());
arg_types.push(arg_type);
builtin: &Builtin<'a>,
) -> BasicTypeEnum<'ctx> {
match builtin {
Builtin::Int128
| Builtin::Int64
| Builtin::Int32
| Builtin::Int16
| Builtin::Int8
| Builtin::Int1
| Builtin::Usize
| Builtin::Decimal
| Builtin::Float128
| Builtin::Float64
| Builtin::Float32
| Builtin::Float16 => basic_type_from_builtin(env, builtin),
Builtin::Str | Builtin::EmptyStr | Builtin::List(_) | Builtin::EmptyList => {
env.str_list_int().into()
}
Builtin::Dict(_, _) | Builtin::Set(_) | Builtin::EmptyDict | Builtin::EmptySet => {
// TODO verify this is what actually happens
basic_type_from_builtin(env, builtin)
}
}
}
enum CCReturn {
/// Return as normal
Return,
/// require an extra argument, a pointer
/// where the result is written into
/// returns void
ByPointer,
/// The return type is zero-sized
Void,
}
/// According to the C ABI, how should we return a value with the given layout?
fn to_cc_return<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) -> CCReturn {
let return_size = layout.stack_size(env.ptr_bytes);
let pass_result_by_pointer = return_size > 2 * env.ptr_bytes;
if return_size == 0 {
CCReturn::Void
} else if pass_result_by_pointer {
CCReturn::ByPointer
} else {
CCReturn::Return
}
let function_type = env.context.void_type().fn_type(&arg_types, false);
let function = get_foreign_symbol(env, foreign.clone(), function_type);
(function, arg_vals.into_bump_slice())
}
#[allow(clippy::too_many_arguments)]
fn build_foreign_symbol<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &mut Scope<'a, 'ctx>,
foreign: &roc_module::ident::ForeignSymbol,
arguments: &[Symbol],
argument_symbols: &[Symbol],
ret_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let ret_type = basic_type_from_layout(env, ret_layout);
let return_pointer = env.builder.build_alloca(ret_type, "return_value");
let builder = env.builder;
let context = env.context;
// crude approximation of the C calling convention
let pass_result_by_pointer = ret_layout.stack_size(env.ptr_bytes) > 2 * env.ptr_bytes;
// Here we build two functions:
//
// - an C_CALL_CONV extern that will be provided by the host, e.g. `roc_fx_putLine`
// This is just a type signature that we make available to the linker,
// and can use in the wrapper
// - a FAST_CALL_CONV wrapper that we make here, e.g. `roc_fx_putLine_fastcc_wrapper`
let (function, arguments) = if pass_result_by_pointer {
build_foreign_symbol_write_result_into_ptr(env, scope, foreign, arguments, return_pointer)
} else {
build_foreign_symbol_return_result(env, scope, foreign, arguments, ret_type)
let return_type = basic_type_from_layout(env, ret_layout);
let cc_return = to_cc_return(env, ret_layout);
let mut cc_argument_types = Vec::with_capacity_in(argument_symbols.len() + 1, env.arena);
let mut fastcc_argument_types = Vec::with_capacity_in(argument_symbols.len(), env.arena);
let mut arguments = Vec::with_capacity_in(argument_symbols.len(), env.arena);
for symbol in argument_symbols {
let (value, layout) = load_symbol_and_layout(scope, symbol);
cc_argument_types.push(to_cc_type(env, layout));
let basic_type = basic_type_from_layout(env, layout);
fastcc_argument_types.push(basic_type);
arguments.push(value);
}
let cc_type = match cc_return {
CCReturn::Void => env.context.void_type().fn_type(&cc_argument_types, false),
CCReturn::ByPointer => {
cc_argument_types.push(return_type.ptr_type(AddressSpace::Generic).into());
env.context.void_type().fn_type(&cc_argument_types, false)
}
CCReturn::Return => return_type.fn_type(&cc_argument_types, false),
};
let call = env.builder.build_call(function, arguments, "tmp");
let cc_function = get_foreign_symbol(env, foreign.clone(), cc_type);
// this is a foreign function, use c calling convention
call.set_call_convention(C_CALL_CONV);
let fastcc_type = return_type.fn_type(&fastcc_argument_types, false);
call.try_as_basic_value();
let fastcc_function = add_func(
env.module,
&format!("{}_fastcc_wrapper", foreign.as_str()),
fastcc_type,
Linkage::Private,
FAST_CALL_CONV,
);
if pass_result_by_pointer {
env.builder.build_load(return_pointer, "read_result")
} else {
call.try_as_basic_value().left().unwrap()
let old = builder.get_insert_block().unwrap();
let entry = context.append_basic_block(fastcc_function, "entry");
{
builder.position_at_end(entry);
let return_pointer = env.builder.build_alloca(return_type, "return_value");
let fastcc_parameters = fastcc_function.get_params();
let mut cc_arguments = Vec::with_capacity_in(fastcc_parameters.len() + 1, env.arena);
for (param, cc_type) in fastcc_parameters.into_iter().zip(cc_argument_types.iter()) {
if param.get_type() == *cc_type {
cc_arguments.push(param);
} else {
let as_cc_type = complex_bitcast(env.builder, param, *cc_type, "to_cc_type");
cc_arguments.push(as_cc_type);
}
}
if let CCReturn::ByPointer = cc_return {
cc_arguments.push(return_pointer.into());
}
let call = env.builder.build_call(cc_function, &cc_arguments, "tmp");
call.set_call_convention(C_CALL_CONV);
let return_value = match cc_return {
CCReturn::Return => call.try_as_basic_value().left().unwrap(),
CCReturn::ByPointer => env.builder.build_load(return_pointer, "read_result"),
CCReturn::Void => return_type.const_zero(),
};
builder.build_return(Some(&return_value));
}
builder.position_at_end(old);
let call = env.builder.build_call(fastcc_function, &arguments, "tmp");
call.set_call_convention(FAST_CALL_CONV);
return call.try_as_basic_value().left().unwrap();
}
fn throw_on_overflow<'a, 'ctx, 'env>(

View File

@ -194,6 +194,20 @@ pub fn ptr_int(ctx: &Context, ptr_bytes: u32) -> IntType<'_> {
}
}
/// The int type that the C ABI turns our RocList/RocStr into
pub fn str_list_int(ctx: &Context, ptr_bytes: u32) -> IntType<'_> {
match ptr_bytes {
1 => ctx.i16_type(),
2 => ctx.i32_type(),
4 => ctx.i64_type(),
8 => ctx.i128_type(),
_ => panic!(
"Invalid target: Roc does't support compiling to {}-bit systems.",
ptr_bytes * 8
),
}
}
pub fn zig_dict_type<'a, 'ctx, 'env>(
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
) -> StructType<'ctx> {

View File

@ -143,7 +143,7 @@ pub export fn roc_fx_putInt(int: i64) i64 {
return 0;
}
pub export fn roc_fx_putLine(rocPath: str.RocStr) i64 {
export fn roc_fx_putLine(rocPath: str.RocStr) callconv(.C) void {
const stdout = std.io.getStdOut().writer();
for (rocPath.asSlice()) |char| {
@ -151,8 +151,6 @@ pub export fn roc_fx_putLine(rocPath: str.RocStr) i64 {
}
stdout.print("\n", .{}) catch unreachable;
return 0;
}
const GetInt = extern struct {