mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-25 15:33:47 +00:00
1170 lines
38 KiB
Rust
1170 lines
38 KiB
Rust
/// Helpers for interacting with the zig that generates bitcode
|
|
use crate::debug_info_init;
|
|
use crate::llvm::build::{
|
|
complex_bitcast_check_size, load_roc_value, to_cc_return, CCReturn, Env, C_CALL_CONV,
|
|
FAST_CALL_CONV,
|
|
};
|
|
use crate::llvm::convert::basic_type_from_layout;
|
|
use crate::llvm::refcounting::{
|
|
decrement_refcount_layout, increment_n_refcount_layout, increment_refcount_layout,
|
|
};
|
|
use inkwell::attributes::{Attribute, AttributeLoc};
|
|
use inkwell::types::{BasicType, BasicTypeEnum, StructType};
|
|
use inkwell::values::{
|
|
BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue, IntValue, PointerValue,
|
|
StructValue,
|
|
};
|
|
use inkwell::AddressSpace;
|
|
use roc_error_macros::internal_error;
|
|
use roc_module::symbol::Symbol;
|
|
use roc_mono::layout::{
|
|
Builtin, InLayout, LambdaSet, LayoutIds, LayoutInterner, LayoutRepr, STLayoutInterner,
|
|
};
|
|
|
|
use super::build::{create_entry_block_alloca, BuilderExt};
|
|
use super::convert::{zig_list_type, zig_str_type};
|
|
use super::struct_::struct_from_fields;
|
|
|
|
pub fn call_bitcode_fn<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
args: &[BasicValueEnum<'ctx>],
|
|
fn_name: &str,
|
|
) -> BasicValueEnum<'ctx> {
|
|
let ret = call_bitcode_fn_help(env, args, fn_name)
|
|
.try_as_basic_value()
|
|
.left()
|
|
.unwrap_or_else(|| {
|
|
panic!("LLVM error: Did not get return value from bitcode function {fn_name:?}")
|
|
});
|
|
|
|
if env.target_info.operating_system == roc_target::OperatingSystem::Windows {
|
|
// On windows zig uses a vector type <2xi64> instead of a i128 value
|
|
let vec_type = env.context.i64_type().vec_type(2);
|
|
if ret.get_type() == vec_type.into() {
|
|
return env
|
|
.builder
|
|
.build_bitcast(ret, env.context.i128_type(), "return_i128")
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
ret
|
|
}
|
|
|
|
pub fn call_void_bitcode_fn<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
args: &[BasicValueEnum<'ctx>],
|
|
fn_name: &str,
|
|
) -> InstructionValue<'ctx> {
|
|
call_bitcode_fn_help(env, args, fn_name)
|
|
.try_as_basic_value()
|
|
.right()
|
|
.unwrap_or_else(|| panic!("LLVM error: Tried to call void bitcode function, but got return value from bitcode function, {fn_name:?}"))
|
|
}
|
|
|
|
fn call_bitcode_fn_help<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
args: &[BasicValueEnum<'ctx>],
|
|
fn_name: &str,
|
|
) -> CallSiteValue<'ctx> {
|
|
let it = args
|
|
.iter()
|
|
.map(|x| {
|
|
if env.target_info.operating_system == roc_target::OperatingSystem::Windows {
|
|
if x.get_type() == env.context.i128_type().into() {
|
|
let parent = env
|
|
.builder
|
|
.get_insert_block()
|
|
.and_then(|b| b.get_parent())
|
|
.unwrap();
|
|
|
|
let alloca = create_entry_block_alloca(
|
|
env,
|
|
parent,
|
|
x.get_type(),
|
|
"pass_u128_by_reference",
|
|
);
|
|
|
|
env.builder.build_store(alloca, *x).unwrap();
|
|
|
|
alloca.into()
|
|
} else {
|
|
*x
|
|
}
|
|
} else {
|
|
*x
|
|
}
|
|
})
|
|
.map(|x| (x).into());
|
|
let arguments = bumpalo::collections::Vec::from_iter_in(it, env.arena);
|
|
|
|
let fn_val = env
|
|
.module
|
|
.get_function(fn_name)
|
|
.unwrap_or_else(|| panic!("Unrecognized builtin function: {fn_name:?} - if you're working on the Roc compiler, do you need to rebuild the bitcode? See compiler/builtins/bitcode/README.md"));
|
|
|
|
let call = env
|
|
.builder
|
|
.new_build_call(fn_val, &arguments, "call_builtin");
|
|
|
|
// Attributes that we propagate from the zig builtin parameters, to the arguments we give to the
|
|
// call. It is undefined behavior in LLVM to have an attribute on a parameter, and then call
|
|
// the function where that parameter is not present. For many (e.g. nonnull) it can be inferred
|
|
// but e.g. byval and sret cannot and must be explicitly provided.
|
|
let propagate = [
|
|
Attribute::get_named_enum_kind_id("nonnull"),
|
|
Attribute::get_named_enum_kind_id("nocapture"),
|
|
Attribute::get_named_enum_kind_id("readonly"),
|
|
Attribute::get_named_enum_kind_id("noalias"),
|
|
Attribute::get_named_enum_kind_id("sret"),
|
|
Attribute::get_named_enum_kind_id("byval"),
|
|
];
|
|
|
|
for i in 0..fn_val.count_params() {
|
|
let attributes = fn_val.attributes(AttributeLoc::Param(i));
|
|
|
|
for attribute in attributes {
|
|
let kind_id = attribute.get_enum_kind_id();
|
|
|
|
if propagate.contains(&kind_id) {
|
|
call.add_attribute(AttributeLoc::Param(i), attribute)
|
|
}
|
|
}
|
|
}
|
|
|
|
call.set_call_convention(fn_val.get_call_conventions());
|
|
call
|
|
}
|
|
|
|
pub fn call_bitcode_fn_fixing_for_convention<'a, 'ctx, 'env>(
|
|
env: &Env<'a, 'ctx, 'env>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
bitcode_return_type: StructType<'ctx>,
|
|
args: &[BasicValueEnum<'ctx>],
|
|
return_layout: InLayout<'a>,
|
|
fn_name: &str,
|
|
) -> BasicValueEnum<'ctx> {
|
|
// Calling zig bitcode, so we must follow C calling conventions.
|
|
let cc_return = to_cc_return(env, layout_interner, return_layout);
|
|
match cc_return {
|
|
CCReturn::Return => {
|
|
// We'll get a return value
|
|
call_bitcode_fn(env, args, fn_name)
|
|
}
|
|
CCReturn::ByPointer => {
|
|
// We need to pass the return value by pointer.
|
|
let roc_return_type = basic_type_from_layout(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(return_layout),
|
|
);
|
|
|
|
let cc_return_type: BasicTypeEnum<'ctx> = bitcode_return_type.into();
|
|
|
|
// when we write an i128 into this (happens in NumToInt), zig expects this pointer to
|
|
// be 16-byte aligned. Not doing so is UB and will immediately fail on CI
|
|
let cc_return_value_ptr = env.builder.new_build_alloca(cc_return_type, "return_value");
|
|
cc_return_value_ptr
|
|
.as_instruction()
|
|
.unwrap()
|
|
.set_alignment(16)
|
|
.unwrap();
|
|
|
|
let fixed_args: Vec<BasicValueEnum<'ctx>> = [cc_return_value_ptr.into()]
|
|
.iter()
|
|
.chain(args)
|
|
.copied()
|
|
.collect();
|
|
call_void_bitcode_fn(env, &fixed_args, fn_name);
|
|
|
|
let cc_return_value =
|
|
env.builder
|
|
.new_build_load(cc_return_type, cc_return_value_ptr, "read_result");
|
|
if roc_return_type.size_of() == cc_return_type.size_of() {
|
|
cc_return_value
|
|
} else {
|
|
// We need to convert the C-callconv return type, which may be larger than the Roc
|
|
// return type, into the Roc return type.
|
|
complex_bitcast_check_size(
|
|
env,
|
|
cc_return_value,
|
|
roc_return_type,
|
|
"c_value_to_roc_value",
|
|
)
|
|
}
|
|
}
|
|
CCReturn::Void => {
|
|
internal_error!("Tried to call valued bitcode function, but it has no return type")
|
|
}
|
|
}
|
|
}
|
|
|
|
const ARGUMENT_SYMBOLS: [Symbol; 8] = [
|
|
Symbol::ARG_1,
|
|
Symbol::ARG_2,
|
|
Symbol::ARG_3,
|
|
Symbol::ARG_4,
|
|
Symbol::ARG_5,
|
|
Symbol::ARG_6,
|
|
Symbol::ARG_7,
|
|
Symbol::ARG_8,
|
|
];
|
|
|
|
pub(crate) fn build_transform_caller<'a, 'ctx>(
|
|
env: &Env<'a, 'ctx, '_>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
function: FunctionValue<'ctx>,
|
|
closure_data_layout: LambdaSet<'a>,
|
|
argument_layouts: &[InLayout<'a>],
|
|
result_layout: InLayout<'a>,
|
|
) -> FunctionValue<'ctx> {
|
|
let fn_name: &str = &format!(
|
|
"{}_zig_function_caller",
|
|
function.get_name().to_string_lossy()
|
|
);
|
|
|
|
match env.module.get_function(fn_name) {
|
|
Some(function_value) => function_value,
|
|
None => build_transform_caller_help(
|
|
env,
|
|
layout_interner,
|
|
function,
|
|
closure_data_layout,
|
|
argument_layouts,
|
|
result_layout,
|
|
fn_name,
|
|
),
|
|
}
|
|
}
|
|
|
|
fn build_transform_caller_help<'a, 'ctx>(
|
|
env: &Env<'a, 'ctx, '_>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
roc_function: FunctionValue<'ctx>,
|
|
closure_data_layout: LambdaSet<'a>,
|
|
argument_layouts: &[InLayout<'a>],
|
|
result_layout: InLayout<'a>,
|
|
fn_name: &str,
|
|
) -> FunctionValue<'ctx> {
|
|
debug_assert!(argument_layouts.len() <= 7);
|
|
|
|
let block = env.builder.get_insert_block().expect("to be in a function");
|
|
let di_location = env.builder.get_current_debug_location().unwrap();
|
|
|
|
let arg_type = env.context.i8_type().ptr_type(AddressSpace::default());
|
|
|
|
let function_value = crate::llvm::refcounting::build_header_help(
|
|
env,
|
|
fn_name,
|
|
env.context.void_type().into(),
|
|
&(bumpalo::vec![in env.arena; BasicTypeEnum::PointerType(arg_type); argument_layouts.len() + 2]),
|
|
);
|
|
|
|
// called from zig, must use C calling convention
|
|
function_value.set_call_conventions(C_CALL_CONV);
|
|
|
|
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
|
|
debug_assert!(kind_id > 0);
|
|
let attr = env.context.create_enum_attribute(kind_id, 0);
|
|
function_value.add_attribute(AttributeLoc::Function, attr);
|
|
|
|
let entry = env.context.append_basic_block(function_value, "entry");
|
|
env.builder.position_at_end(entry);
|
|
|
|
debug_info_init!(env, function_value);
|
|
|
|
let mut it = function_value.get_param_iter();
|
|
let closure_ptr = it.next().unwrap().into_pointer_value();
|
|
closure_ptr.set_name(Symbol::ARG_1.as_str(&env.interns));
|
|
|
|
let arguments =
|
|
bumpalo::collections::Vec::from_iter_in(it.take(argument_layouts.len()), env.arena);
|
|
|
|
for (argument, name) in arguments.iter().zip(ARGUMENT_SYMBOLS[1..].iter()) {
|
|
argument.set_name(name.as_str(&env.interns));
|
|
}
|
|
|
|
let mut arguments_cast =
|
|
bumpalo::collections::Vec::with_capacity_in(arguments.len(), env.arena);
|
|
|
|
for (argument_ptr, layout) in arguments.iter().zip(argument_layouts) {
|
|
let basic_type =
|
|
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(*layout))
|
|
.ptr_type(AddressSpace::default());
|
|
|
|
let cast_ptr = env.builder.new_build_pointer_cast(
|
|
argument_ptr.into_pointer_value(),
|
|
basic_type,
|
|
"cast_ptr_to_tag_build_transform_caller_help",
|
|
);
|
|
|
|
let argument = load_roc_value(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(*layout),
|
|
cast_ptr,
|
|
"zig_helper_load_opaque",
|
|
);
|
|
|
|
arguments_cast.push(argument);
|
|
}
|
|
|
|
match (
|
|
closure_data_layout
|
|
.is_represented(layout_interner)
|
|
.is_some(),
|
|
closure_data_layout.runtime_representation(),
|
|
) {
|
|
(false, _) => {
|
|
// the function doesn't expect a closure argument, nothing to add
|
|
}
|
|
(true, layout) => {
|
|
let closure_type =
|
|
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout))
|
|
.ptr_type(AddressSpace::default());
|
|
|
|
let closure_cast = env.builder.new_build_pointer_cast(
|
|
closure_ptr,
|
|
closure_type,
|
|
"cast_opaque_closure",
|
|
);
|
|
|
|
let closure_data = load_roc_value(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(layout),
|
|
closure_cast,
|
|
"load_closure",
|
|
);
|
|
|
|
arguments_cast.push(closure_data);
|
|
}
|
|
}
|
|
|
|
let result = crate::llvm::build::call_direct_roc_function(
|
|
env,
|
|
layout_interner,
|
|
roc_function,
|
|
layout_interner.get_repr(result_layout),
|
|
arguments_cast.as_slice(),
|
|
);
|
|
|
|
let result_u8_ptr = function_value
|
|
.get_nth_param(argument_layouts.len() as u32 + 1)
|
|
.unwrap()
|
|
.into_pointer_value();
|
|
|
|
crate::llvm::build::store_roc_value_opaque(
|
|
env,
|
|
layout_interner,
|
|
result_layout,
|
|
result_u8_ptr,
|
|
result,
|
|
);
|
|
env.builder.new_build_return(None);
|
|
|
|
env.builder.position_at_end(block);
|
|
env.builder.set_current_debug_location(di_location);
|
|
|
|
function_value
|
|
}
|
|
|
|
enum Mode {
|
|
Inc,
|
|
IncN,
|
|
Dec,
|
|
}
|
|
|
|
/// a function that accepts two arguments: the value to increment, and an amount to increment by
|
|
pub fn build_inc_n_wrapper<'a, 'ctx>(
|
|
env: &Env<'a, 'ctx, '_>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
layout_ids: &mut LayoutIds<'a>,
|
|
layout: InLayout<'a>,
|
|
) -> FunctionValue<'ctx> {
|
|
build_rc_wrapper(env, layout_interner, layout_ids, layout, Mode::IncN)
|
|
}
|
|
|
|
/// a function that accepts two arguments: the value to increment; increments by 1
|
|
pub fn build_inc_wrapper<'a, 'ctx>(
|
|
env: &Env<'a, 'ctx, '_>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
layout_ids: &mut LayoutIds<'a>,
|
|
layout: InLayout<'a>,
|
|
) -> FunctionValue<'ctx> {
|
|
build_rc_wrapper(env, layout_interner, layout_ids, layout, Mode::Inc)
|
|
}
|
|
|
|
pub fn build_dec_wrapper<'a, 'ctx>(
|
|
env: &Env<'a, 'ctx, '_>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
layout_ids: &mut LayoutIds<'a>,
|
|
layout: InLayout<'a>,
|
|
) -> FunctionValue<'ctx> {
|
|
build_rc_wrapper(env, layout_interner, layout_ids, layout, Mode::Dec)
|
|
}
|
|
|
|
fn build_rc_wrapper<'a, 'ctx>(
|
|
env: &Env<'a, 'ctx, '_>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
layout_ids: &mut LayoutIds<'a>,
|
|
layout: InLayout<'a>,
|
|
rc_operation: Mode,
|
|
) -> FunctionValue<'ctx> {
|
|
let block = env.builder.get_insert_block().expect("to be in a function");
|
|
let di_location = env.builder.get_current_debug_location().unwrap();
|
|
|
|
let symbol = Symbol::GENERIC_RC_REF;
|
|
let fn_name = layout_ids
|
|
.get(symbol, &layout_interner.get_repr(layout))
|
|
.to_symbol_string(symbol, &env.interns);
|
|
|
|
let fn_name = match rc_operation {
|
|
Mode::IncN => format!("{fn_name}_inc_n"),
|
|
Mode::Inc => format!("{fn_name}_inc"),
|
|
Mode::Dec => format!("{fn_name}_dec"),
|
|
};
|
|
|
|
let function_value = match env.module.get_function(fn_name.as_str()) {
|
|
Some(function_value) => function_value,
|
|
None => {
|
|
let arg_type = env.context.i8_type().ptr_type(AddressSpace::default());
|
|
|
|
let function_value = match rc_operation {
|
|
Mode::Inc | Mode::Dec => crate::llvm::refcounting::build_header_help(
|
|
env,
|
|
&fn_name,
|
|
env.context.void_type().into(),
|
|
&[arg_type.into()],
|
|
),
|
|
Mode::IncN => crate::llvm::refcounting::build_header_help(
|
|
env,
|
|
&fn_name,
|
|
env.context.void_type().into(),
|
|
&[arg_type.into(), env.ptr_int().into()],
|
|
),
|
|
};
|
|
|
|
// called from zig, must use C calling convention
|
|
function_value.set_call_conventions(C_CALL_CONV);
|
|
|
|
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
|
|
debug_assert!(kind_id > 0);
|
|
let attr = env.context.create_enum_attribute(kind_id, 0);
|
|
function_value.add_attribute(AttributeLoc::Function, attr);
|
|
|
|
let entry = env.context.append_basic_block(function_value, "entry");
|
|
env.builder.position_at_end(entry);
|
|
|
|
debug_info_init!(env, function_value);
|
|
|
|
let mut it = function_value.get_param_iter();
|
|
let generic_value_ptr = it.next().unwrap().into_pointer_value();
|
|
|
|
generic_value_ptr.set_name(Symbol::ARG_1.as_str(&env.interns));
|
|
|
|
let value_type =
|
|
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout));
|
|
let value_ptr_type = value_type.ptr_type(AddressSpace::default());
|
|
let value_ptr = env.builder.new_build_pointer_cast(
|
|
generic_value_ptr,
|
|
value_ptr_type,
|
|
"load_opaque",
|
|
);
|
|
|
|
// even though this looks like a `load_roc_value`, that gives segfaults in practice.
|
|
// I suspect it has something to do with the lifetime of the alloca that is created by
|
|
// `load_roc_value`
|
|
let value = if layout_interner.is_passed_by_reference(layout) {
|
|
value_ptr.into()
|
|
} else {
|
|
env.builder
|
|
.new_build_load(value_type, value_ptr, "load_opaque")
|
|
};
|
|
|
|
match rc_operation {
|
|
Mode::Inc => {
|
|
let n = 1;
|
|
increment_refcount_layout(env, layout_interner, layout_ids, n, value, layout);
|
|
}
|
|
Mode::IncN => {
|
|
let n = it.next().unwrap().into_int_value();
|
|
n.set_name(Symbol::ARG_2.as_str(&env.interns));
|
|
|
|
increment_n_refcount_layout(env, layout_interner, layout_ids, n, value, layout);
|
|
}
|
|
Mode::Dec => {
|
|
decrement_refcount_layout(env, layout_interner, layout_ids, value, layout);
|
|
}
|
|
}
|
|
|
|
env.builder.new_build_return(None);
|
|
|
|
function_value
|
|
}
|
|
};
|
|
|
|
env.builder.position_at_end(block);
|
|
env.builder.set_current_debug_location(di_location);
|
|
|
|
function_value
|
|
}
|
|
|
|
pub fn build_eq_wrapper<'a, 'ctx>(
|
|
env: &Env<'a, 'ctx, '_>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
layout_ids: &mut LayoutIds<'a>,
|
|
layout: InLayout<'a>,
|
|
) -> FunctionValue<'ctx> {
|
|
let block = env.builder.get_insert_block().expect("to be in a function");
|
|
let di_location = env.builder.get_current_debug_location().unwrap();
|
|
|
|
let symbol = Symbol::GENERIC_EQ_REF;
|
|
let fn_name = layout_ids
|
|
.get(symbol, &layout_interner.get_repr(layout))
|
|
.to_symbol_string(symbol, &env.interns);
|
|
|
|
let function_value = match env.module.get_function(fn_name.as_str()) {
|
|
Some(function_value) => function_value,
|
|
None => {
|
|
let arg_type = env.context.i8_type().ptr_type(AddressSpace::default());
|
|
|
|
let function_value = crate::llvm::refcounting::build_header_help(
|
|
env,
|
|
&fn_name,
|
|
env.context.bool_type().into(),
|
|
&[arg_type.into(), arg_type.into()],
|
|
);
|
|
|
|
// called from zig, must use C calling convention
|
|
function_value.set_call_conventions(C_CALL_CONV);
|
|
|
|
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
|
|
debug_assert!(kind_id > 0);
|
|
let attr = env.context.create_enum_attribute(kind_id, 0);
|
|
function_value.add_attribute(AttributeLoc::Function, attr);
|
|
|
|
let entry = env.context.append_basic_block(function_value, "entry");
|
|
env.builder.position_at_end(entry);
|
|
|
|
debug_info_init!(env, function_value);
|
|
|
|
let mut it = function_value.get_param_iter();
|
|
let value_ptr1 = it.next().unwrap().into_pointer_value();
|
|
let value_ptr2 = it.next().unwrap().into_pointer_value();
|
|
|
|
value_ptr1.set_name(Symbol::ARG_1.as_str(&env.interns));
|
|
value_ptr2.set_name(Symbol::ARG_2.as_str(&env.interns));
|
|
|
|
let value_type =
|
|
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout))
|
|
.ptr_type(AddressSpace::default());
|
|
|
|
let value_cast1 =
|
|
env.builder
|
|
.new_build_pointer_cast(value_ptr1, value_type, "load_opaque");
|
|
|
|
let value_cast2 =
|
|
env.builder
|
|
.new_build_pointer_cast(value_ptr2, value_type, "load_opaque");
|
|
|
|
// load_roc_value(env, *element_layout, elem_ptr, "get_elem")
|
|
let value1 = load_roc_value(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(layout),
|
|
value_cast1,
|
|
"load_opaque",
|
|
);
|
|
let value2 = load_roc_value(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(layout),
|
|
value_cast2,
|
|
"load_opaque",
|
|
);
|
|
|
|
let result = crate::llvm::compare::generic_eq(
|
|
env,
|
|
layout_interner,
|
|
layout_ids,
|
|
value1,
|
|
value2,
|
|
layout,
|
|
layout,
|
|
);
|
|
|
|
env.builder.new_build_return(Some(&result));
|
|
|
|
function_value
|
|
}
|
|
};
|
|
|
|
env.builder.position_at_end(block);
|
|
env.builder.set_current_debug_location(di_location);
|
|
|
|
function_value
|
|
}
|
|
|
|
pub fn build_compare_wrapper<'a, 'ctx>(
|
|
env: &Env<'a, 'ctx, '_>,
|
|
layout_interner: &STLayoutInterner<'a>,
|
|
layout_ids: &mut LayoutIds<'a>,
|
|
roc_function: FunctionValue<'ctx>,
|
|
closure_data_layout: LambdaSet<'a>,
|
|
layout: InLayout<'a>,
|
|
) -> FunctionValue<'ctx> {
|
|
let block = env.builder.get_insert_block().expect("to be in a function");
|
|
let di_location = env.builder.get_current_debug_location().unwrap();
|
|
|
|
let fn_name: &str = &format!(
|
|
"{}_compare_wrapper",
|
|
roc_function.get_name().to_string_lossy()
|
|
);
|
|
|
|
let function_value = match env.module.get_function(fn_name) {
|
|
Some(function_value) => function_value,
|
|
None => {
|
|
let arg_type = env.context.i8_type().ptr_type(AddressSpace::default());
|
|
|
|
let function_value = crate::llvm::refcounting::build_header_help(
|
|
env,
|
|
fn_name,
|
|
env.context.i8_type().into(),
|
|
&[arg_type.into(), arg_type.into(), arg_type.into()],
|
|
);
|
|
|
|
// called from zig, must use C calling convention
|
|
function_value.set_call_conventions(C_CALL_CONV);
|
|
|
|
// we expose this function to zig; must use c calling convention
|
|
function_value.set_call_conventions(C_CALL_CONV);
|
|
|
|
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
|
|
debug_assert!(kind_id > 0);
|
|
let attr = env.context.create_enum_attribute(kind_id, 0);
|
|
function_value.add_attribute(AttributeLoc::Function, attr);
|
|
|
|
let entry = env.context.append_basic_block(function_value, "entry");
|
|
env.builder.position_at_end(entry);
|
|
|
|
debug_info_init!(env, function_value);
|
|
|
|
let mut it = function_value.get_param_iter();
|
|
let closure_ptr = it.next().unwrap().into_pointer_value();
|
|
let value_ptr1 = it.next().unwrap().into_pointer_value();
|
|
let value_ptr2 = it.next().unwrap().into_pointer_value();
|
|
|
|
closure_ptr.set_name(Symbol::ARG_1.as_str(&env.interns));
|
|
value_ptr1.set_name(Symbol::ARG_2.as_str(&env.interns));
|
|
value_ptr2.set_name(Symbol::ARG_3.as_str(&env.interns));
|
|
|
|
let value_type =
|
|
basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout));
|
|
let value_ptr_type = value_type.ptr_type(AddressSpace::default());
|
|
|
|
let value_cast1 =
|
|
env.builder
|
|
.new_build_pointer_cast(value_ptr1, value_ptr_type, "load_opaque");
|
|
|
|
let value_cast2 =
|
|
env.builder
|
|
.new_build_pointer_cast(value_ptr2, value_ptr_type, "load_opaque");
|
|
|
|
let value1 = load_roc_value(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(layout),
|
|
value_cast1,
|
|
"load_opaque",
|
|
);
|
|
let value2 = load_roc_value(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(layout),
|
|
value_cast2,
|
|
"load_opaque",
|
|
);
|
|
|
|
increment_refcount_layout(env, layout_interner, layout_ids, 1, value1, layout);
|
|
increment_refcount_layout(env, layout_interner, layout_ids, 1, value2, layout);
|
|
|
|
let default = [value1.into(), value2.into()];
|
|
|
|
let closure_data_repr = closure_data_layout.runtime_representation();
|
|
|
|
let arguments_cast = match layout_interner.get_repr(closure_data_repr) {
|
|
LayoutRepr::Struct(&[]) => {
|
|
// nothing to add
|
|
&default
|
|
}
|
|
_ => {
|
|
let closure_type = basic_type_from_layout(
|
|
env,
|
|
layout_interner,
|
|
layout_interner.get_repr(closure_data_repr),
|
|
);
|
|
let closure_ptr_type = closure_type.ptr_type(AddressSpace::default());
|
|
|
|
let closure_cast = env.builder.new_build_pointer_cast(
|
|
closure_ptr,
|
|
closure_ptr_type,
|
|
"load_opaque",
|
|
);
|
|
|
|
let closure_data =
|
|
env.builder
|
|
.new_build_load(closure_type, closure_cast, "load_opaque");
|
|
|
|
env.arena
|
|
.alloc([value1.into(), value2.into(), closure_data.into()])
|
|
as &[_]
|
|
}
|
|
};
|
|
|
|
let call = env.builder.new_build_call(
|
|
roc_function,
|
|
arguments_cast,
|
|
"call_user_defined_compare_function",
|
|
);
|
|
|
|
let result = call.try_as_basic_value().left().unwrap();
|
|
|
|
// IMPORTANT! we call a user function, so it has the fast calling convention
|
|
call.set_call_convention(FAST_CALL_CONV);
|
|
|
|
env.builder.new_build_return(Some(&result));
|
|
|
|
function_value
|
|
}
|
|
};
|
|
|
|
env.builder.position_at_end(block);
|
|
env.builder.set_current_debug_location(di_location);
|
|
|
|
function_value
|
|
}
|
|
|
|
enum BitcodeReturnValue<'ctx> {
|
|
List(PointerValue<'ctx>),
|
|
Str(PointerValue<'ctx>),
|
|
Basic,
|
|
}
|
|
|
|
impl<'ctx> BitcodeReturnValue<'ctx> {
|
|
fn call_and_load_64bit<'a, 'env>(
|
|
&self,
|
|
env: &Env<'a, 'ctx, 'env>,
|
|
arguments: &[BasicValueEnum<'ctx>],
|
|
fn_name: &str,
|
|
) -> BasicValueEnum<'ctx> {
|
|
match self {
|
|
BitcodeReturnValue::List(result) => {
|
|
call_void_bitcode_fn(env, arguments, fn_name);
|
|
env.builder
|
|
.new_build_load(zig_list_type(env), *result, "load_list")
|
|
}
|
|
BitcodeReturnValue::Str(result) => {
|
|
call_void_bitcode_fn(env, arguments, fn_name);
|
|
|
|
// we keep a string in the alloca
|
|
(*result).into()
|
|
}
|
|
BitcodeReturnValue::Basic => call_bitcode_fn(env, arguments, fn_name),
|
|
}
|
|
}
|
|
fn call_and_load_wasm<'a, 'env>(
|
|
&self,
|
|
env: &Env<'a, 'ctx, 'env>,
|
|
arguments: &[BasicValueEnum<'ctx>],
|
|
fn_name: &str,
|
|
) -> BasicValueEnum<'ctx> {
|
|
match self {
|
|
BitcodeReturnValue::List(result) => {
|
|
call_void_bitcode_fn(env, arguments, fn_name);
|
|
env.builder
|
|
.new_build_load(zig_list_type(env), *result, "load_list")
|
|
}
|
|
BitcodeReturnValue::Str(result) => {
|
|
call_void_bitcode_fn(env, arguments, fn_name);
|
|
env.builder
|
|
.new_build_load(zig_str_type(env), *result, "load_list")
|
|
}
|
|
BitcodeReturnValue::Basic => call_bitcode_fn(env, arguments, fn_name),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) enum BitcodeReturns {
|
|
List,
|
|
Str,
|
|
Basic,
|
|
}
|
|
|
|
impl BitcodeReturns {
|
|
fn additional_arguments(&self) -> usize {
|
|
match self {
|
|
BitcodeReturns::List | BitcodeReturns::Str => 1,
|
|
BitcodeReturns::Basic => 0,
|
|
}
|
|
}
|
|
|
|
fn return_value_64bit<'a, 'ctx>(
|
|
&self,
|
|
env: &Env<'a, 'ctx, '_>,
|
|
arguments: &mut bumpalo::collections::Vec<'a, BasicValueEnum<'ctx>>,
|
|
) -> BitcodeReturnValue<'ctx> {
|
|
match self {
|
|
BitcodeReturns::List => {
|
|
let list_type = super::convert::zig_list_type(env);
|
|
|
|
let parent = env
|
|
.builder
|
|
.get_insert_block()
|
|
.and_then(|b| b.get_parent())
|
|
.unwrap();
|
|
|
|
let result =
|
|
create_entry_block_alloca(env, parent, list_type.into(), "list_alloca");
|
|
|
|
arguments.push(result.into());
|
|
|
|
BitcodeReturnValue::List(result)
|
|
}
|
|
BitcodeReturns::Str => {
|
|
let str_type = super::convert::zig_str_type(env);
|
|
|
|
let parent = env
|
|
.builder
|
|
.get_insert_block()
|
|
.and_then(|b| b.get_parent())
|
|
.unwrap();
|
|
|
|
let result = create_entry_block_alloca(env, parent, str_type.into(), "str_alloca");
|
|
|
|
arguments.push(result.into());
|
|
|
|
BitcodeReturnValue::Str(result)
|
|
}
|
|
BitcodeReturns::Basic => BitcodeReturnValue::Basic,
|
|
}
|
|
}
|
|
|
|
fn return_value_wasm<'a, 'ctx>(
|
|
&self,
|
|
env: &Env<'a, 'ctx, '_>,
|
|
arguments: &mut bumpalo::collections::Vec<'a, BasicValueEnum<'ctx>>,
|
|
) -> BitcodeReturnValue<'ctx> {
|
|
// Wasm follows 64bit despite being 32bit.
|
|
// This wrapper is just for clarity at call sites.
|
|
self.return_value_64bit(env, arguments)
|
|
}
|
|
|
|
fn call_and_load_32bit<'ctx>(
|
|
&self,
|
|
env: &Env<'_, 'ctx, '_>,
|
|
arguments: &[BasicValueEnum<'ctx>],
|
|
fn_name: &str,
|
|
) -> BasicValueEnum<'ctx> {
|
|
let value = call_bitcode_fn(env, arguments, fn_name);
|
|
|
|
match self {
|
|
BitcodeReturns::List => {
|
|
receive_zig_roc_list_32bit(env, value.into_struct_value()).into()
|
|
}
|
|
BitcodeReturns::Str => receive_zig_roc_str_32bit(env, value.into_struct_value()).into(),
|
|
BitcodeReturns::Basic => value,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn ptr_len_cap<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
value: StructValue<'ctx>,
|
|
) -> (PointerValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>) {
|
|
let ptr_and_len = env
|
|
.builder
|
|
.build_extract_value(value, 0, "get_list_cap")
|
|
.unwrap()
|
|
.into_int_value();
|
|
|
|
let upper_word = {
|
|
let shift = env.builder.new_build_right_shift(
|
|
ptr_and_len,
|
|
env.context.i64_type().const_int(32, false),
|
|
false,
|
|
"list_ptr_shift",
|
|
);
|
|
|
|
env.builder
|
|
.new_build_int_cast(shift, env.context.i32_type(), "list_ptr_int")
|
|
};
|
|
|
|
let lower_word =
|
|
env.builder
|
|
.new_build_int_cast(ptr_and_len, env.context.i32_type(), "list_len");
|
|
|
|
let len = upper_word;
|
|
|
|
let ptr = env.builder.new_build_int_to_ptr(
|
|
lower_word,
|
|
env.context.i8_type().ptr_type(AddressSpace::default()),
|
|
"list_ptr",
|
|
);
|
|
|
|
let cap = env
|
|
.builder
|
|
.build_extract_value(value, 1, "get_list_cap")
|
|
.unwrap()
|
|
.into_int_value();
|
|
|
|
(ptr, len, cap)
|
|
}
|
|
|
|
/// Converts the { i64, i32 } struct that zig returns into `list.RocList = type { i8*, i32, i32 }`
|
|
fn receive_zig_roc_list_32bit<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
value: StructValue<'ctx>,
|
|
) -> StructValue<'ctx> {
|
|
let list_type = super::convert::zig_list_type(env);
|
|
|
|
let (ptr, len, cap) = ptr_len_cap(env, value);
|
|
|
|
struct_from_fields(
|
|
env,
|
|
list_type,
|
|
[(0, ptr.into()), (1, len.into()), (2, cap.into())].into_iter(),
|
|
)
|
|
}
|
|
|
|
/// Converts the { i64, i32 } struct that zig returns into `list.RocList = type { i8*, i32, i32 }`
|
|
fn receive_zig_roc_str_32bit<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
value: StructValue<'ctx>,
|
|
) -> StructValue<'ctx> {
|
|
let str_type = super::convert::zig_str_type(env);
|
|
|
|
let (ptr, len, cap) = ptr_len_cap(env, value);
|
|
|
|
struct_from_fields(
|
|
env,
|
|
str_type,
|
|
[(0, ptr.into()), (1, len.into()), (2, cap.into())].into_iter(),
|
|
)
|
|
}
|
|
|
|
pub(crate) fn pass_list_to_zig_64bit<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
list: BasicValueEnum<'ctx>,
|
|
) -> PointerValue<'ctx> {
|
|
let parent = env
|
|
.builder
|
|
.get_insert_block()
|
|
.and_then(|b| b.get_parent())
|
|
.unwrap();
|
|
|
|
let list_type = super::convert::zig_list_type(env);
|
|
let list_alloca = create_entry_block_alloca(env, parent, list_type.into(), "list_alloca");
|
|
|
|
env.builder.new_build_store(list_alloca, list);
|
|
|
|
list_alloca
|
|
}
|
|
|
|
pub(crate) fn pass_list_to_zig_wasm<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
list: BasicValueEnum<'ctx>,
|
|
) -> PointerValue<'ctx> {
|
|
let parent = env
|
|
.builder
|
|
.get_insert_block()
|
|
.and_then(|b| b.get_parent())
|
|
.unwrap();
|
|
|
|
let list_type = super::convert::zig_list_type(env);
|
|
let list_alloca = create_entry_block_alloca(env, parent, list_type.into(), "list_alloca");
|
|
|
|
env.builder.new_build_store(list_alloca, list);
|
|
|
|
list_alloca
|
|
}
|
|
|
|
pub(crate) fn pass_string_to_zig_wasm<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
string: BasicValueEnum<'ctx>,
|
|
) -> PointerValue<'ctx> {
|
|
let parent = env
|
|
.builder
|
|
.get_insert_block()
|
|
.and_then(|b| b.get_parent())
|
|
.unwrap();
|
|
|
|
let string_type = super::convert::zig_str_type(env);
|
|
let string_alloca = create_entry_block_alloca(env, parent, string_type.into(), "string_alloca");
|
|
|
|
env.builder.new_build_store(string_alloca, string);
|
|
|
|
string_alloca
|
|
}
|
|
|
|
fn pass_string_to_zig_64bit<'ctx>(
|
|
_env: &Env<'_, 'ctx, '_>,
|
|
string: BasicValueEnum<'ctx>,
|
|
) -> PointerValue<'ctx> {
|
|
// we must pass strings by-pointer, and that is already how they are stored
|
|
string.into_pointer_value()
|
|
}
|
|
|
|
pub(crate) fn pass_list_or_string_to_zig_32bit<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
list_or_string: StructValue<'ctx>,
|
|
) -> (IntValue<'ctx>, IntValue<'ctx>) {
|
|
let ptr = env
|
|
.builder
|
|
.build_extract_value(list_or_string, Builtin::WRAPPER_PTR, "list_ptr")
|
|
.unwrap()
|
|
.into_pointer_value();
|
|
|
|
let ptr = env
|
|
.builder
|
|
.new_build_ptr_to_int(ptr, env.context.i32_type(), "ptr_to_i32");
|
|
|
|
let len = env
|
|
.builder
|
|
.build_extract_value(list_or_string, Builtin::WRAPPER_LEN, "list_len")
|
|
.unwrap()
|
|
.into_int_value();
|
|
|
|
let cap = env
|
|
.builder
|
|
.build_extract_value(list_or_string, Builtin::WRAPPER_CAPACITY, "list_cap")
|
|
.unwrap()
|
|
.into_int_value();
|
|
|
|
let int_64_type = env.context.i64_type();
|
|
let len = env
|
|
.builder
|
|
.new_build_int_z_extend(len, int_64_type, "list_len_64");
|
|
let ptr = env
|
|
.builder
|
|
.new_build_int_z_extend(ptr, int_64_type, "list_ptr_64");
|
|
|
|
let len_shift =
|
|
env.builder
|
|
.new_build_left_shift(len, int_64_type.const_int(32, false), "list_len_shift");
|
|
let ptr_len = env.builder.new_build_or(len_shift, ptr, "list_ptr_len");
|
|
|
|
(ptr_len, cap)
|
|
}
|
|
|
|
pub(crate) fn call_str_bitcode_fn<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
strings: &[BasicValueEnum<'ctx>],
|
|
other_arguments: &[BasicValueEnum<'ctx>],
|
|
returns: BitcodeReturns,
|
|
fn_name: &str,
|
|
) -> BasicValueEnum<'ctx> {
|
|
use bumpalo::collections::Vec;
|
|
use roc_target::Architecture::*;
|
|
|
|
match env.target_info.architecture {
|
|
Aarch32 | X86_32 => {
|
|
let mut arguments: Vec<BasicValueEnum> =
|
|
Vec::with_capacity_in(other_arguments.len() + 2 * strings.len(), env.arena);
|
|
|
|
for string in strings {
|
|
let (a, b) = pass_list_or_string_to_zig_32bit(env, string.into_struct_value());
|
|
arguments.push(a.into());
|
|
arguments.push(b.into());
|
|
}
|
|
|
|
arguments.extend(other_arguments);
|
|
|
|
returns.call_and_load_32bit(env, &arguments, fn_name)
|
|
}
|
|
X86_64 | Aarch64 => {
|
|
let capacity = other_arguments.len() + strings.len() + returns.additional_arguments();
|
|
let mut arguments: Vec<BasicValueEnum> = Vec::with_capacity_in(capacity, env.arena);
|
|
|
|
let return_value = returns.return_value_64bit(env, &mut arguments);
|
|
|
|
for string in strings {
|
|
arguments.push(pass_string_to_zig_64bit(env, *string).into());
|
|
}
|
|
|
|
arguments.extend(other_arguments);
|
|
|
|
return_value.call_and_load_64bit(env, &arguments, fn_name)
|
|
}
|
|
Wasm32 => {
|
|
let capacity = other_arguments.len() + strings.len() + returns.additional_arguments();
|
|
let mut arguments: Vec<BasicValueEnum> = Vec::with_capacity_in(capacity, env.arena);
|
|
|
|
let return_value = returns.return_value_wasm(env, &mut arguments);
|
|
|
|
for string in strings {
|
|
arguments.push(pass_string_to_zig_wasm(env, *string).into());
|
|
}
|
|
|
|
arguments.extend(other_arguments);
|
|
|
|
return_value.call_and_load_wasm(env, &arguments, fn_name)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn call_list_bitcode_fn<'ctx>(
|
|
env: &Env<'_, 'ctx, '_>,
|
|
lists: &[StructValue<'ctx>],
|
|
other_arguments: &[BasicValueEnum<'ctx>],
|
|
returns: BitcodeReturns,
|
|
fn_name: &str,
|
|
) -> BasicValueEnum<'ctx> {
|
|
use bumpalo::collections::Vec;
|
|
use roc_target::Architecture::*;
|
|
|
|
match env.target_info.architecture {
|
|
Aarch32 | X86_32 => {
|
|
let mut arguments: Vec<BasicValueEnum> =
|
|
Vec::with_capacity_in(other_arguments.len() + 2 * lists.len(), env.arena);
|
|
|
|
for list in lists {
|
|
let (a, b) = pass_list_or_string_to_zig_32bit(env, *list);
|
|
arguments.push(a.into());
|
|
arguments.push(b.into());
|
|
}
|
|
|
|
arguments.extend(other_arguments);
|
|
|
|
returns.call_and_load_32bit(env, &arguments, fn_name)
|
|
}
|
|
X86_64 | Aarch64 => {
|
|
let capacity = other_arguments.len() + lists.len() + returns.additional_arguments();
|
|
let mut arguments: Vec<BasicValueEnum> = Vec::with_capacity_in(capacity, env.arena);
|
|
|
|
let return_value = returns.return_value_64bit(env, &mut arguments);
|
|
|
|
for list in lists {
|
|
arguments.push(pass_list_to_zig_64bit(env, (*list).into()).into());
|
|
}
|
|
|
|
arguments.extend(other_arguments);
|
|
|
|
return_value.call_and_load_64bit(env, &arguments, fn_name)
|
|
}
|
|
Wasm32 => {
|
|
let capacity = other_arguments.len() + lists.len() + returns.additional_arguments();
|
|
let mut arguments: Vec<BasicValueEnum> = Vec::with_capacity_in(capacity, env.arena);
|
|
|
|
let return_value = returns.return_value_wasm(env, &mut arguments);
|
|
|
|
for list in lists {
|
|
arguments.push(pass_list_to_zig_wasm(env, (*list).into()).into());
|
|
}
|
|
|
|
arguments.extend(other_arguments);
|
|
|
|
return_value.call_and_load_wasm(env, &arguments, fn_name)
|
|
}
|
|
}
|
|
}
|