Merge remote-tracking branch 'origin/trunk' into i/2792

This commit is contained in:
Folkert 2022-05-06 20:17:53 +02:00
commit 07383e96f0
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
363 changed files with 29960 additions and 14183 deletions

View file

@ -30,6 +30,7 @@ use crate::llvm::refcounting::{
};
use bumpalo::collections::Vec;
use bumpalo::Bump;
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::basic_block::BasicBlock;
use inkwell::builder::Builder;
use inkwell::context::Context;
@ -40,7 +41,7 @@ use inkwell::memory_buffer::MemoryBuffer;
use inkwell::module::{Linkage, Module};
use inkwell::passes::{PassManager, PassManagerBuilder};
use inkwell::types::{
BasicMetadataTypeEnum, BasicType, BasicTypeEnum, FunctionType, IntType, StructType,
AnyType, BasicMetadataTypeEnum, BasicType, BasicTypeEnum, FunctionType, IntType, StructType,
};
use inkwell::values::BasicValueEnum::{self, *};
use inkwell::values::{
@ -55,6 +56,7 @@ use morphic_lib::{
use roc_builtins::bitcode::{self, FloatWidth, IntWidth, IntrinsicName};
use roc_builtins::{float_intrinsic, llvm_int_intrinsic};
use roc_collections::all::{ImMap, MutMap, MutSet};
use roc_debug_flags::{dbg_do, ROC_PRINT_LLVM_FN_VERIFICATION};
use roc_error_macros::internal_error;
use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, ModuleId, Symbol};
@ -63,16 +65,16 @@ use roc_mono::ir::{
ModifyRc, OptLevel, ProcLayout,
};
use roc_mono::layout::{Builtin, LambdaSet, Layout, LayoutIds, TagIdIntType, UnionLayout};
use roc_target::TargetInfo;
use roc_target::{PtrWidth, TargetInfo};
use target_lexicon::{Architecture, OperatingSystem, Triple};
/// This is for Inkwell's FunctionValue::verify - we want to know the verification
/// output in debug builds, but we don't want it to print to stdout in release builds!
#[cfg(debug_assertions)]
const PRINT_FN_VERIFICATION_OUTPUT: bool = true;
#[cfg(not(debug_assertions))]
const PRINT_FN_VERIFICATION_OUTPUT: bool = false;
#[inline(always)]
fn print_fn_verification_output() -> bool {
dbg_do!(ROC_PRINT_LLVM_FN_VERIFICATION, {
return true;
});
false
}
#[macro_export]
macro_rules! debug_info_init {
@ -427,6 +429,13 @@ pub fn module_from_builtins<'ctx>(
} => {
include_bytes!("../../../builtins/bitcode/builtins-i386.bc")
}
Triple {
architecture: Architecture::X86_64,
operating_system: OperatingSystem::Linux,
..
} => {
include_bytes!("../../../builtins/bitcode/builtins-x86_64.bc")
}
_ => panic!(
"The zig builtins are not currently built for this target: {:?}",
target
@ -460,7 +469,7 @@ fn add_float_intrinsic<'ctx, F>(
if let Some(_) = module.get_function(full_name) {
// zig defined this function already
} else {
add_intrinsic(module, full_name, construct_type($typ));
add_intrinsic(ctx, module, full_name, construct_type($typ));
}
};
}
@ -485,7 +494,7 @@ fn add_int_intrinsic<'ctx, F>(
if let Some(_) = module.get_function(full_name) {
// zig defined this function already
} else {
add_intrinsic(module, full_name, construct_type($typ));
add_intrinsic(ctx, module, full_name, construct_type($typ));
}
};
}
@ -507,12 +516,10 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
// List of all supported LLVM intrinsics:
//
// https://releases.llvm.org/10.0.0/docs/LangRef.html#standard-c-library-intrinsics
let f64_type = ctx.f64_type();
let i1_type = ctx.bool_type();
let i8_type = ctx.i8_type();
let i8_ptr_type = i8_type.ptr_type(AddressSpace::Generic);
let i32_type = ctx.i32_type();
let i64_type = ctx.i64_type();
let void_type = ctx.void_type();
if let Some(func) = module.get_function("__muloti4") {
@ -520,37 +527,31 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
}
add_intrinsic(
ctx,
module,
LLVM_SETJMP,
i32_type.fn_type(&[i8_ptr_type.into()], false),
);
if true {
add_intrinsic(
module,
LLVM_LONGJMP,
void_type.fn_type(&[i8_ptr_type.into()], false),
);
} else {
add_intrinsic(
module,
LLVM_LONGJMP,
void_type.fn_type(&[i8_ptr_type.into(), i32_type.into()], false),
);
}
add_intrinsic(
ctx,
module,
LLVM_LONGJMP,
void_type.fn_type(&[i8_ptr_type.into()], false),
);
add_intrinsic(
ctx,
module,
LLVM_FRAME_ADDRESS,
i8_ptr_type.fn_type(&[i32_type.into()], false),
);
add_intrinsic(module, LLVM_STACK_SAVE, i8_ptr_type.fn_type(&[], false));
add_intrinsic(
ctx,
module,
LLVM_LROUND_I64_F64,
i64_type.fn_type(&[f64_type.into()], false),
LLVM_STACK_SAVE,
i8_ptr_type.fn_type(&[], false),
);
add_float_intrinsic(ctx, module, &LLVM_LOG, |t| t.fn_type(&[t.into()], false));
@ -605,9 +606,7 @@ static LLVM_ROUND: IntrinsicName = float_intrinsic!("llvm.round");
static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64";
static LLVM_MEMSET_I32: &str = "llvm.memset.p0i8.i32";
static LLVM_LROUND_I64_F64: &str = "llvm.lround.i64.f64";
// static LLVM_FRAME_ADDRESS: &str = "llvm.frameaddress";
static LLVM_FRAME_ADDRESS: &str = "llvm.frameaddress.p0i8";
static LLVM_STACK_SAVE: &str = "llvm.stacksave";
@ -625,18 +624,17 @@ const LLVM_ADD_SATURATED: IntrinsicName = llvm_int_intrinsic!("llvm.sadd.sat", "
const LLVM_SUB_SATURATED: IntrinsicName = llvm_int_intrinsic!("llvm.ssub.sat", "llvm.usub.sat");
fn add_intrinsic<'ctx>(
context: &Context,
module: &Module<'ctx>,
intrinsic_name: &str,
fn_type: FunctionType<'ctx>,
) -> FunctionValue<'ctx> {
add_func(
context,
module,
intrinsic_name,
fn_type,
FunctionSpec::intrinsic(fn_type),
Linkage::External,
// LLVM intrinsics always use the C calling convention, because
// they are implemented in C libraries
C_CALL_CONV,
)
}
@ -809,20 +807,24 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
Bool(b) => env.context.bool_type().const_int(*b as u64, false).into(),
Byte(b) => env.context.i8_type().const_int(*b as u64, false).into(),
Str(str_literal) => {
let global = if str_literal.len() < env.small_str_bytes() as usize {
if str_literal.len() < env.small_str_bytes() as usize {
match env.small_str_bytes() {
24 => small_str_ptr_width_8(env, parent, str_literal),
12 => small_str_ptr_width_4(env, parent, str_literal),
24 => small_str_ptr_width_8(env, parent, str_literal).into(),
12 => small_str_ptr_width_4(env, parent, str_literal).into(),
_ => unreachable!("incorrect small_str_bytes"),
}
} else {
let ptr = define_global_str_literal_ptr(env, *str_literal);
let number_of_elements = env.ptr_int().const_int(str_literal.len() as u64, false);
const_str_alloca_ptr(env, parent, ptr, number_of_elements, number_of_elements)
};
let alloca =
const_str_alloca_ptr(env, parent, ptr, number_of_elements, number_of_elements);
global.into()
match env.target_info.ptr_width() {
PtrWidth::Bytes4 => env.builder.build_load(alloca, "load_const_str"),
PtrWidth::Bytes8 => alloca.into(),
}
}
}
}
}
@ -3248,32 +3250,29 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>(
// let mut argument_types = roc_function.get_type().get_param_types();
let mut argument_types = cc_argument_types;
let c_function_type = match roc_function.get_type().get_return_type() {
match roc_function.get_type().get_return_type() {
None => {
// this function already returns by-pointer
let output_type = roc_function.get_type().get_param_types().pop().unwrap();
argument_types.insert(0, output_type);
env.context
.void_type()
.fn_type(&function_arguments(env, &argument_types), false)
}
Some(return_type) => {
let output_type = return_type.ptr_type(AddressSpace::Generic);
argument_types.insert(0, output_type.into());
env.context
.void_type()
.fn_type(&function_arguments(env, &argument_types), false)
}
};
}
// This is not actually a function that returns a value but then became
// return-by-pointer do to the calling convention. Instead, here we
// explicitly are forcing the passing of values via the first parameter
// pointer, since they are generic and hence opaque to anything outside roc.
let c_function_spec = FunctionSpec::cconv(env, CCReturn::Void, None, &argument_types);
let c_function = add_func(
env.context,
env.module,
c_function_name,
c_function_type,
c_function_spec,
Linkage::External,
C_CALL_CONV,
);
let subprogram = env.new_subprogram(c_function_name);
@ -3386,20 +3385,18 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>(
let mut argument_types = cc_argument_types;
let return_type = wrapper_return_type;
let c_function_type = {
let c_function_spec = {
let output_type = return_type.ptr_type(AddressSpace::Generic);
argument_types.push(output_type.into());
env.context
.void_type()
.fn_type(&function_arguments(env, &argument_types), false)
FunctionSpec::cconv(env, CCReturn::Void, None, &argument_types)
};
let c_function = add_func(
env.context,
env.module,
c_function_name,
c_function_type,
c_function_spec,
Linkage::External,
C_CALL_CONV,
);
let subprogram = env.new_subprogram(c_function_name);
@ -3464,15 +3461,20 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>(
builder.build_return(None);
// STEP 3: build a {} -> u64 function that gives the size of the return type
let size_function_type = env.context.i64_type().fn_type(&[], false);
let size_function_spec = FunctionSpec::cconv(
env,
CCReturn::Return,
Some(env.context.i64_type().as_basic_type_enum()),
&[],
);
let size_function_name: String = format!("roc__{}_size", ident_string);
let size_function = add_func(
env.context,
env.module,
size_function_name.as_str(),
size_function_type,
size_function_spec,
Linkage::External,
C_CALL_CONV,
);
let subprogram = env.new_subprogram(&size_function_name);
@ -3504,14 +3506,14 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
let cc_return = to_cc_return(env, &return_layout);
let roc_return = RocReturn::from_layout(env, &return_layout);
let c_function_type = cc_return.to_signature(env, return_type, argument_types.as_slice());
let c_function_spec = FunctionSpec::cconv(env, cc_return, Some(return_type), &argument_types);
let c_function = add_func(
env.context,
env.module,
c_function_name,
c_function_type,
c_function_spec,
Linkage::External,
C_CALL_CONV,
);
let subprogram = env.new_subprogram(c_function_name);
@ -3622,15 +3624,20 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
);
// STEP 3: build a {} -> u64 function that gives the size of the return type
let size_function_type = env.context.i64_type().fn_type(&[], false);
let size_function_spec = FunctionSpec::cconv(
env,
CCReturn::Return,
Some(env.context.i64_type().as_basic_type_enum()),
&[],
);
let size_function_name: String = format!("roc__{}_size", ident_string);
let size_function = add_func(
env.context,
env.module,
size_function_name.as_str(),
size_function_type,
size_function_spec,
Linkage::External,
C_CALL_CONV,
);
let subprogram = env.new_subprogram(&size_function_name);
@ -3656,10 +3663,22 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
}
pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> {
let type_ = env
.context
.i8_type()
.array_type(5 * env.target_info.ptr_width() as u32);
// The size of jump_buf is platform-dependent.
// - AArch64 needs 3 machine-sized words
// - LLVM says the following about the SJLJ intrinsic:
//
// [It is] a five word buffer in which the calling context is saved.
// The front end places the frame pointer in the first word, and the
// target implementation of this intrinsic should place the destination
// address for a llvm.eh.sjlj.longjmp in the second word.
// The following three words are available for use in a target-specific manner.
//
// So, let's create a 5-word buffer.
let word_type = match env.target_info.ptr_width() {
PtrWidth::Bytes4 => env.context.i32_type(),
PtrWidth::Bytes8 => env.context.i64_type(),
};
let type_ = word_type.array_type(5);
let global = match env.module.get_global("roc_sjlj_buffer") {
Some(global) => global,
@ -3671,12 +3690,85 @@ pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValu
env.builder
.build_bitcast(
global.as_pointer_value(),
env.context.i8_type().ptr_type(AddressSpace::Generic),
env.context.i32_type().ptr_type(AddressSpace::Generic),
"cast_sjlj_buffer",
)
.into_pointer_value()
}
pub fn build_setjmp_call<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> {
let jmp_buf = get_sjlj_buffer(env);
if cfg!(target_arch = "aarch64") {
// Due to https://github.com/rtfeldman/roc/issues/2965, we use a setjmp we linked in from Zig
call_bitcode_fn(env, &[jmp_buf.into()], bitcode::UTILS_SETJMP)
} else {
// Anywhere else, use the LLVM intrinsic.
// https://llvm.org/docs/ExceptionHandling.html#llvm-eh-sjlj-setjmp
let jmp_buf_i8p_arr = env
.builder
.build_bitcast(
jmp_buf,
env.context
.i8_type()
.ptr_type(AddressSpace::Generic)
.array_type(5)
.ptr_type(AddressSpace::Generic),
"jmp_buf [5 x i8*]",
)
.into_pointer_value();
// LLVM asks us to please store the frame pointer in the first word.
let frame_address = env.call_intrinsic(
LLVM_FRAME_ADDRESS,
&[env.context.i32_type().const_zero().into()],
);
let zero = env.context.i32_type().const_zero();
let fa_index = env.context.i32_type().const_zero();
let fa = unsafe {
env.builder.build_in_bounds_gep(
jmp_buf_i8p_arr,
&[zero, fa_index],
"frame address index",
)
};
env.builder.build_store(fa, frame_address);
// LLVM says that the target implementation of the setjmp intrinsic will put the
// destination address at index 1, and that the remaining three words are for ad-hoc target
// usage. But for whatever reason, on x86, it appears we need a stacksave in those words.
let ss_index = env.context.i32_type().const_int(2, false);
let ss = unsafe {
env.builder
.build_in_bounds_gep(jmp_buf_i8p_arr, &[zero, ss_index], "name")
};
let stack_save = env.call_intrinsic(LLVM_STACK_SAVE, &[]);
env.builder.build_store(ss, stack_save);
let jmp_buf_i8p = env.builder.build_bitcast(
jmp_buf,
env.context.i8_type().ptr_type(AddressSpace::Generic),
"jmp_buf i8*",
);
env.call_intrinsic(LLVM_SETJMP, &[jmp_buf_i8p])
}
}
/// Pointer to pointer of the panic message.
pub fn get_panic_msg_ptr<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> {
let ptr_to_u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let global_name = "roc_panic_msg_ptr";
let global = env.module.get_global(global_name).unwrap_or_else(|| {
let global = env.module.add_global(ptr_to_u8_ptr, None, global_name);
global.set_initializer(&ptr_to_u8_ptr.const_zero());
global
});
global.as_pointer_value()
}
fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
@ -3695,53 +3787,7 @@ fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>(
let catch_block = context.append_basic_block(parent, "catch_block");
let cont_block = context.append_basic_block(parent, "cont_block");
let buffer = get_sjlj_buffer(env);
let cast = env
.builder
.build_bitcast(
buffer,
env.context
.i8_type()
.ptr_type(AddressSpace::Generic)
.array_type(5)
.ptr_type(AddressSpace::Generic),
"to [5 x i8*]",
)
.into_pointer_value();
let zero = env.context.i32_type().const_zero();
let index = env.context.i32_type().const_zero();
let fa = unsafe {
env.builder
.build_in_bounds_gep(cast, &[zero, index], "name")
};
let index = env.context.i32_type().const_int(2, false);
let ss = unsafe {
env.builder
.build_in_bounds_gep(cast, &[zero, index], "name")
};
let index = env.context.i32_type().const_int(3, false);
let error_msg = unsafe {
env.builder
.build_in_bounds_gep(cast, &[zero, index], "name")
};
let frame_address = env.call_intrinsic(
LLVM_FRAME_ADDRESS,
&[env.context.i32_type().const_zero().into()],
);
env.builder.build_store(fa, frame_address);
let stack_save = env.call_intrinsic(LLVM_STACK_SAVE, &[]);
env.builder.build_store(ss, stack_save);
let panicked_u32 = env.call_intrinsic(LLVM_SETJMP, &[buffer.into()]);
let panicked_u32 = build_setjmp_call(env);
let panicked_bool = env.builder.build_int_compare(
IntPredicate::NE,
panicked_u32.into_int_value(),
@ -3771,19 +3817,10 @@ fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>(
let error_msg = {
// u8**
let ptr_int_ptr = builder.build_bitcast(
error_msg,
env.context
.i8_type()
.ptr_type(AddressSpace::Generic)
.ptr_type(AddressSpace::Generic),
"cast",
);
let ptr_int_ptr = get_panic_msg_ptr(env);
// u8* again
let ptr_int = builder.build_load(ptr_int_ptr.into_pointer_value(), "ptr_int");
ptr_int
builder.build_load(ptr_int_ptr, "ptr_int")
};
let return_value = {
@ -3911,16 +3948,20 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>(
// argument_types.push(wrapper_return_type.ptr_type(AddressSpace::Generic).into());
// let wrapper_function_type = env.context.void_type().fn_type(&argument_types, false);
let wrapper_function_type =
wrapper_return_type.fn_type(&function_arguments(env, &argument_types), false);
let wrapper_function_spec = FunctionSpec::cconv(
env,
CCReturn::Return,
Some(wrapper_return_type.as_basic_type_enum()),
&argument_types,
);
// Add main to the module.
let wrapper_function = add_func(
env.context,
env.module,
wrapper_function_name,
wrapper_function_type,
wrapper_function_spec,
Linkage::External,
C_CALL_CONV,
);
let subprogram = env.new_subprogram(wrapper_function_name);
@ -4149,23 +4190,15 @@ fn build_proc_header<'a, 'ctx, 'env>(
arg_basic_types.push(arg_type);
}
let fn_type = match RocReturn::from_layout(env, &proc.ret_layout) {
RocReturn::Return => ret_type.fn_type(&function_arguments(env, &arg_basic_types), false),
RocReturn::ByPointer => {
// println!( "{:?} will return void instead of {:?}", symbol, proc.ret_layout);
arg_basic_types.push(ret_type.ptr_type(AddressSpace::Generic).into());
env.context
.void_type()
.fn_type(&function_arguments(env, &arg_basic_types), false)
}
};
let roc_return = RocReturn::from_layout(env, &proc.ret_layout);
let fn_spec = FunctionSpec::fastcc(env, roc_return, ret_type, arg_basic_types);
let fn_val = add_func(
env.context,
env.module,
fn_name.as_str(),
fn_type,
fn_spec,
Linkage::Internal,
FAST_CALL_CONV,
);
let subprogram = env.new_subprogram(&fn_name);
@ -4183,8 +4216,6 @@ fn build_proc_header<'a, 'ctx, 'env>(
}
if false {
use inkwell::attributes::{Attribute, AttributeLoc};
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
debug_assert!(kind_id > 0);
let enum_attr = env.context.create_enum_attribute(kind_id, 1);
@ -4192,8 +4223,6 @@ fn build_proc_header<'a, 'ctx, 'env>(
}
if false {
use inkwell::attributes::{Attribute, AttributeLoc};
let kind_id = Attribute::get_named_enum_kind_id("noinline");
debug_assert!(kind_id > 0);
let enum_attr = env.context.create_enum_attribute(kind_id, 1);
@ -4247,14 +4276,14 @@ pub fn build_closure_caller<'a, 'ctx, 'env>(
alias_symbol.as_str(&env.interns)
);
let function_type = context.void_type().fn_type(&argument_types, false);
let function_spec = FunctionSpec::cconv(env, CCReturn::Void, None, &argument_types);
let function_value = add_func(
env.context,
env.module,
function_name.as_str(),
function_type,
function_spec,
Linkage::External,
C_CALL_CONV,
);
// STEP 2: build function body
@ -4353,7 +4382,8 @@ fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>(
let builder = env.builder;
let context = env.context;
let size_function_type = env.context.i64_type().fn_type(&[], false);
let i64 = env.context.i64_type().as_basic_type_enum();
let size_function_spec = FunctionSpec::cconv(env, CCReturn::Return, Some(i64), &[]);
let size_function_name: String = if let Some(label) = opt_label {
format!(
"roc__{}_{}_{}_size",
@ -4370,11 +4400,11 @@ fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>(
};
let size_function = add_func(
env.context,
env.module,
size_function_name.as_str(),
size_function_type,
size_function_spec,
Linkage::External,
C_CALL_CONV,
);
let entry = context.append_basic_block(size_function, "entry");
@ -4489,7 +4519,7 @@ pub fn build_proc<'a, 'ctx, 'env>(
}
pub fn verify_fn(fn_val: FunctionValue<'_>) {
if !fn_val.verify(PRINT_FN_VERIFICATION_OUTPUT) {
if !fn_val.verify(print_fn_verification_output()) {
unsafe {
fn_val.delete();
}
@ -5638,6 +5668,15 @@ fn run_low_level<'a, 'ctx, 'env>(
update_mode,
)
}
ListIsUnique => {
// List.isUnique : List a -> Bool
debug_assert_eq!(args.len(), 1);
let list = load_symbol(scope, &args[0]);
let list = list_to_c_abi(env, list).into();
call_bitcode_fn(env, &[list], bitcode::LIST_IS_UNIQUE)
}
NumToStr => {
// Num.toStr : Num a -> Str
debug_assert_eq!(args.len(), 1);
@ -5866,6 +5905,48 @@ fn run_low_level<'a, 'ctx, 'env>(
.build_int_cast_sign_flag(arg, to, to_signed, "inc_cast")
.into()
}
NumToFloatCast => {
debug_assert_eq!(args.len(), 1);
let (arg, arg_layout) = load_symbol_and_layout(scope, &args[0]);
match arg_layout {
Layout::Builtin(Builtin::Int(width)) => {
// Converting from int to float
let int_val = arg.into_int_value();
let dest = basic_type_from_layout(env, layout).into_float_type();
if width.is_signed() {
env.builder
.build_signed_int_to_float(int_val, dest, "signed_int_to_float")
.into()
} else {
env.builder
.build_unsigned_int_to_float(int_val, dest, "unsigned_int_to_float")
.into()
}
}
Layout::Builtin(Builtin::Float(_)) => {
// Converting from float to float - e.g. F64 to F32, or vice versa
let dest = basic_type_from_layout(env, layout).into_float_type();
env.builder
.build_float_cast(arg.into_float_value(), dest, "cast_float_to_float")
.into()
}
Layout::Builtin(Builtin::Decimal) => {
todo!("Support converting Dec values to floats.");
}
other => {
unreachable!("Tried to do a float cast to non-float layout {:?}", other);
}
}
}
NumToFloatChecked => {
// NOTE: There's a NumToIntChecked implementation above,
// which could be useful to look at when implementing this.
todo!("implement checked float conversion");
}
Eq => {
debug_assert_eq!(args.len(), 2);
@ -6022,6 +6103,13 @@ fn run_low_level<'a, 'ctx, 'env>(
let key_layout = list_element_layout!(list_layout);
set_from_list(env, layout_ids, list, key_layout)
}
SetToDict => {
debug_assert_eq!(args.len(), 1);
let (set, _set_layout) = load_symbol_and_layout(scope, &args[0]);
set
}
ExpectTrue => {
debug_assert_eq!(args.len(), 1);
@ -6144,6 +6232,7 @@ fn to_cc_type_builtin<'a, 'ctx, 'env>(
}
}
#[derive(Clone, Copy)]
enum RocReturn {
/// Return as normal
Return,
@ -6184,7 +6273,7 @@ impl RocReturn {
}
}
#[derive(Debug)]
#[derive(Debug, Clone, Copy)]
pub enum CCReturn {
/// Return as normal
Return,
@ -6196,32 +6285,113 @@ pub enum CCReturn {
Void,
}
impl CCReturn {
fn to_signature<'a, 'ctx, 'env>(
&self,
#[derive(Debug, Clone, Copy)]
pub struct FunctionSpec<'ctx> {
/// The function type
pub typ: FunctionType<'ctx>,
call_conv: u32,
/// Index (0-based) of return-by-pointer parameter, if it exists.
/// We only care about this for C-call-conv functions, because this may take
/// ownership of a register due to the convention. For example, on AArch64,
/// values returned-by-pointer use the x8 register.
/// But for internal functions we don't need to worry about that and we don't
/// want the convention, since it might eat a register and cause a spill!
cconv_sret_parameter: Option<u32>,
}
impl<'ctx> FunctionSpec<'ctx> {
fn attach_attributes(&self, ctx: &Context, fn_val: FunctionValue<'ctx>) {
fn_val.set_call_conventions(self.call_conv);
if let Some(param_index) = self.cconv_sret_parameter {
// Indicate to LLVM that this argument holds the return value of the function.
let sret_attribute_id = Attribute::get_named_enum_kind_id("sret");
debug_assert!(sret_attribute_id > 0);
let ret_typ = self.typ.get_param_types()[param_index as usize];
let sret_attribute =
ctx.create_type_attribute(sret_attribute_id, ret_typ.as_any_type_enum());
fn_val.add_attribute(AttributeLoc::Param(0), sret_attribute);
}
}
/// C-calling convention
pub fn cconv<'a, 'env>(
env: &Env<'a, 'ctx, 'env>,
return_type: BasicTypeEnum<'ctx>,
cc_return: CCReturn,
return_type: Option<BasicTypeEnum<'ctx>>,
argument_types: &[BasicTypeEnum<'ctx>],
) -> FunctionType<'ctx> {
match self {
) -> FunctionSpec<'ctx> {
let (typ, opt_sret_parameter) = match cc_return {
CCReturn::ByPointer => {
// turn the output type into a pointer type. Make it the first argument to the function
let output_type = return_type.ptr_type(AddressSpace::Generic);
let output_type = return_type.unwrap().ptr_type(AddressSpace::Generic);
let mut arguments: Vec<'_, BasicTypeEnum> =
bumpalo::vec![in env.arena; output_type.into()];
arguments.extend(argument_types);
let arguments = function_arguments(env, &arguments);
env.context.void_type().fn_type(&arguments, false)
(env.context.void_type().fn_type(&arguments, false), Some(0))
}
CCReturn::Return => {
let arguments = function_arguments(env, argument_types);
return_type.fn_type(&arguments, false)
(return_type.unwrap().fn_type(&arguments, false), None)
}
CCReturn::Void => {
let arguments = function_arguments(env, argument_types);
env.context.void_type().fn_type(&arguments, false)
(env.context.void_type().fn_type(&arguments, false), None)
}
};
Self {
typ,
call_conv: C_CALL_CONV,
cconv_sret_parameter: opt_sret_parameter,
}
}
/// Fastcc calling convention
fn fastcc<'a, 'env>(
env: &Env<'a, 'ctx, 'env>,
roc_return: RocReturn,
return_type: BasicTypeEnum<'ctx>,
mut argument_types: Vec<BasicTypeEnum<'ctx>>,
) -> FunctionSpec<'ctx> {
let typ = match roc_return {
RocReturn::Return => {
return_type.fn_type(&function_arguments(env, &argument_types), false)
}
RocReturn::ByPointer => {
argument_types.push(return_type.ptr_type(AddressSpace::Generic).into());
env.context
.void_type()
.fn_type(&function_arguments(env, &argument_types), false)
}
};
Self {
typ,
call_conv: FAST_CALL_CONV,
cconv_sret_parameter: None,
}
}
pub fn known_fastcc(fn_type: FunctionType<'ctx>) -> FunctionSpec<'ctx> {
Self {
typ: fn_type,
call_conv: FAST_CALL_CONV,
cconv_sret_parameter: None,
}
}
pub fn intrinsic(fn_type: FunctionType<'ctx>) -> Self {
// LLVM intrinsics always use the C calling convention, because
// they are implemented in C libraries
Self {
typ: fn_type,
call_conv: C_CALL_CONV,
cconv_sret_parameter: None,
}
}
}
@ -6302,27 +6472,19 @@ fn build_foreign_symbol<'a, 'ctx, 'env>(
arguments.push(value);
}
let cc_type = cc_return.to_signature(env, return_type, cc_argument_types.as_slice());
let cc_type =
FunctionSpec::cconv(env, cc_return, Some(return_type), &cc_argument_types);
let cc_function = get_foreign_symbol(env, foreign.clone(), cc_type);
let fastcc_type = match roc_return {
RocReturn::Return => {
return_type.fn_type(&function_arguments(env, &fastcc_argument_types), false)
}
RocReturn::ByPointer => {
fastcc_argument_types.push(return_type.ptr_type(AddressSpace::Generic).into());
env.context
.void_type()
.fn_type(&function_arguments(env, &fastcc_argument_types), false)
}
};
let fastcc_type =
FunctionSpec::fastcc(env, roc_return, return_type, fastcc_argument_types);
let fastcc_function = add_func(
env.context,
env.module,
&fastcc_function_name,
fastcc_type,
Linkage::Internal,
FAST_CALL_CONV,
);
let old = builder.get_insert_block().unwrap();
@ -6519,10 +6681,34 @@ fn build_int_binop<'a, 'ctx, 'env>(
&LLVM_MUL_WITH_OVERFLOW[int_width],
&[lhs.into(), rhs.into()],
),
NumGt => bd.build_int_compare(SGT, lhs, rhs, "int_gt").into(),
NumGte => bd.build_int_compare(SGE, lhs, rhs, "int_gte").into(),
NumLt => bd.build_int_compare(SLT, lhs, rhs, "int_lt").into(),
NumLte => bd.build_int_compare(SLE, lhs, rhs, "int_lte").into(),
NumGt => {
if int_width.is_signed() {
bd.build_int_compare(SGT, lhs, rhs, "gt_int").into()
} else {
bd.build_int_compare(UGT, lhs, rhs, "gt_uint").into()
}
}
NumGte => {
if int_width.is_signed() {
bd.build_int_compare(SGE, lhs, rhs, "gte_int").into()
} else {
bd.build_int_compare(UGE, lhs, rhs, "gte_uint").into()
}
}
NumLt => {
if int_width.is_signed() {
bd.build_int_compare(SLT, lhs, rhs, "lt_int").into()
} else {
bd.build_int_compare(ULT, lhs, rhs, "lt_uint").into()
}
}
NumLte => {
if int_width.is_signed() {
bd.build_int_compare(SLE, lhs, rhs, "lte_int").into()
} else {
bd.build_int_compare(ULE, lhs, rhs, "lte_uint").into()
}
}
NumRemUnchecked => {
if int_width.is_signed() {
bd.build_int_signed_rem(lhs, rhs, "rem_int").into()
@ -6860,7 +7046,6 @@ fn build_float_binop<'a, 'ctx, 'env>(
NumGte => bd.build_float_compare(OGE, lhs, rhs, "float_gte").into(),
NumLt => bd.build_float_compare(OLT, lhs, rhs, "float_lt").into(),
NumLte => bd.build_float_compare(OLE, lhs, rhs, "float_lte").into(),
NumRemUnchecked => bd.build_float_rem(lhs, rhs, "rem_float").into(),
NumDivUnchecked => bd.build_float_div(lhs, rhs, "div_float").into(),
NumPow => env.call_intrinsic(&LLVM_POW[float_width], &[lhs.into(), rhs.into()]),
_ => {
@ -7426,7 +7611,7 @@ fn throw_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, message: &str) {
fn get_foreign_symbol<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
foreign_symbol: roc_module::ident::ForeignSymbol,
function_type: FunctionType<'ctx>,
function_spec: FunctionSpec<'ctx>,
) -> FunctionValue<'ctx> {
let module = env.module;
@ -7434,11 +7619,11 @@ fn get_foreign_symbol<'a, 'ctx, 'env>(
Some(gvalue) => gvalue,
None => {
let foreign_function = add_func(
env.context,
module,
foreign_symbol.as_str(),
function_type,
function_spec,
Linkage::External,
C_CALL_CONV,
);
foreign_function
@ -7450,11 +7635,11 @@ fn get_foreign_symbol<'a, 'ctx, 'env>(
/// We never want to define the same function twice in the same module!
/// The result can be bugs that are difficult to track down.
pub fn add_func<'ctx>(
ctx: &Context,
module: &Module<'ctx>,
name: &str,
typ: FunctionType<'ctx>,
spec: FunctionSpec<'ctx>,
linkage: Linkage,
call_conv: u32,
) -> FunctionValue<'ctx> {
if cfg!(debug_assertions) {
if let Some(func) = module.get_function(name) {
@ -7462,9 +7647,9 @@ pub fn add_func<'ctx>(
}
}
let fn_val = module.add_function(name, typ, Some(linkage));
let fn_val = module.add_function(name, spec.typ, Some(linkage));
fn_val.set_call_conventions(call_conv);
spec.attach_attributes(ctx, fn_val);
fn_val
}