use crate::llvm::bitcode::call_bitcode_fn; use crate::llvm::build_list::{self, allocate_list, empty_polymorphic_list}; use crate::llvm::convert::{ argument_type_from_layout, basic_type_from_builtin, basic_type_from_layout, zig_str_type, }; use crate::llvm::expect::{clone_to_shared_memory, SharedMemoryPointer}; use crate::llvm::memcpy::build_memcpy; use crate::llvm::refcounting::{ build_reset, decrement_refcount_layout, increment_refcount_layout, PointerToRefcount, }; use crate::llvm::struct_::{struct_from_fields, RocStruct}; use crate::llvm::{erased, fn_ptr}; 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; use inkwell::debug_info::{ AsDIScope, DICompileUnit, DIFlagsConstants, DISubprogram, DebugInfoBuilder, }; use inkwell::memory_buffer::MemoryBuffer; use inkwell::module::{Linkage, Module}; use inkwell::passes::{PassManager, PassManagerBuilder}; use inkwell::types::{ AnyType, BasicMetadataTypeEnum, BasicType, BasicTypeEnum, FloatMathType, FunctionType, IntMathType, IntType, PointerMathType, StructType, }; use inkwell::values::{ BasicMetadataValueEnum, BasicValue, BasicValueEnum, CallSiteValue, FloatMathValue, FunctionValue, InstructionOpcode, InstructionValue, IntMathValue, IntValue, PhiValue, PointerMathValue, PointerValue, StructValue, }; use inkwell::{AddressSpace, IntPredicate}; use inkwell::{FloatPredicate, OptimizationLevel}; use morphic_lib::{ CalleeSpecVar, FuncName, FuncSpec, FuncSpecSolutions, ModSolutions, UpdateMode, UpdateModeVar, }; use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; use roc_collections::all::{MutMap, MutSet}; use roc_debug_flags::dbg_do; #[cfg(debug_assertions)] use roc_debug_flags::ROC_PRINT_LLVM_FN_VERIFICATION; use roc_error_macros::{internal_error, todo_lambda_erasure}; use roc_module::symbol::{Interns, Symbol}; use roc_mono::ir::{ BranchInfo, CallType, CrashTag, EntryPoint, GlueLayouts, HostExposedLambdaSet, HostExposedLambdaSets, ListLiteralElement, ModifyRc, OptLevel, ProcLayout, SingleEntryPoint, }; use roc_mono::layout::{ Builtin, InLayout, LambdaName, LambdaSet, Layout, LayoutIds, LayoutInterner, LayoutRepr, Niche, RawFunctionLayout, STLayoutInterner, TagIdIntType, UnionLayout, }; use roc_std::RocDec; use roc_target::{PtrWidth, TargetInfo}; use std::convert::TryInto; use std::path::Path; use target_lexicon::{Aarch64Architecture, Architecture, OperatingSystem, Triple}; use super::convert::{struct_type_from_union_layout, RocUnion}; use super::intrinsics::{ add_intrinsics, LLVM_FRAME_ADDRESS, LLVM_MEMSET_I32, LLVM_MEMSET_I64, LLVM_SETJMP, LLVM_STACK_SAVE, }; use super::lowlevel::run_higher_order_low_level; use super::scope::Scope; pub(crate) trait BuilderExt<'ctx> { fn new_build_struct_gep( &self, struct_type: StructType<'ctx>, ptr: PointerValue<'ctx>, index: u32, name: &str, ) -> PointerValue<'ctx>; fn new_build_alloca>(&self, ty: T, name: &str) -> PointerValue<'ctx>; fn new_build_store>( &self, ptr: PointerValue<'ctx>, value: V, ) -> InstructionValue<'ctx>; fn new_build_load( &self, element_type: impl BasicType<'ctx>, ptr: PointerValue<'ctx>, name: &str, ) -> BasicValueEnum<'ctx>; unsafe fn new_build_in_bounds_gep( &self, element_type: impl BasicType<'ctx>, ptr: PointerValue<'ctx>, ordered_indexes: &[IntValue<'ctx>], name: &str, ) -> PointerValue<'ctx>; fn new_build_cast, V: BasicValue<'ctx>>( &self, op: InstructionOpcode, from_value: V, to_type: T, name: &str, ) -> BasicValueEnum<'ctx>; fn new_build_bitcast(&self, val: V, ty: T, name: &str) -> BasicValueEnum<'ctx> where T: BasicType<'ctx>, V: BasicValue<'ctx>; fn new_build_pointer_cast>( &self, from: T, to: T::BaseType, name: &str, ) -> T; fn new_build_int_cast>( &self, int: T, int_type: T::BaseType, name: &str, ) -> T; fn new_build_int_cast_sign_flag>( &self, int: T, int_type: T::BaseType, is_signed: bool, name: &str, ) -> T; fn new_build_float_cast>( &self, float: T, float_type: T::BaseType, name: &str, ) -> T; fn new_build_call( &self, function: FunctionValue<'ctx>, args: &[BasicMetadataValueEnum<'ctx>], name: &str, ) -> CallSiteValue<'ctx>; fn new_build_indirect_call( &self, function_type: FunctionType<'ctx>, function_pointer: PointerValue<'ctx>, args: &[BasicMetadataValueEnum<'ctx>], name: &str, ) -> CallSiteValue<'ctx>; fn new_build_ptr_to_int>( &self, ptr: T, int_type: >::PtrConvType, name: &str, ) -> <>::PtrConvType as IntMathType<'ctx>>::ValueType; fn new_build_int_to_ptr>( &self, int: T, ptr_type: >::PtrConvType, name: &str, ) -> <>::PtrConvType as PointerMathType<'ctx>>::ValueType; fn new_build_is_null>( &self, ptr: T, name: &str, ) -> <>::PtrConvType as IntMathType<'ctx>>::ValueType; fn new_build_is_not_null>( &self, ptr: T, name: &str, ) -> <>::PtrConvType as IntMathType<'ctx>>::ValueType; fn new_build_phi>(&self, type_: T, name: &str) -> PhiValue<'ctx>; fn new_build_select, IMV: IntMathValue<'ctx>>( &self, condition: IMV, then: BV, else_: BV, name: &str, ) -> BasicValueEnum<'ctx>; fn new_build_and>(&self, lhs: T, rhs: T, name: &str) -> T; fn new_build_or>(&self, lhs: T, rhs: T, name: &str) -> T; fn new_build_xor>(&self, lhs: T, rhs: T, name: &str) -> T; fn new_build_not>(&self, value: T, name: &str) -> T; fn new_build_int_neg>(&self, value: T, name: &str) -> T; fn new_build_int_add>(&self, lhs: T, rhs: T, name: &str) -> T; fn new_build_int_sub>(&self, lhs: T, rhs: T, name: &str) -> T; fn new_build_int_mul>(&self, lhs: T, rhs: T, name: &str) -> T; fn new_build_int_signed_rem>(&self, lhs: T, rhs: T, name: &str) -> T; fn new_build_int_unsigned_rem>(&self, lhs: T, rhs: T, name: &str) -> T; fn new_build_int_signed_div>(&self, lhs: T, rhs: T, name: &str) -> T; fn new_build_int_unsigned_div>(&self, lhs: T, rhs: T, name: &str) -> T; fn new_build_int_compare>( &self, op: IntPredicate, lhs: T, rhs: T, name: &str, ) -> >::ValueType; fn new_build_float_neg>(&self, value: T, name: &str) -> T; fn new_build_float_add>(&self, lhs: T, rhs: T, name: &str) -> T; fn new_build_float_sub>(&self, lhs: T, rhs: T, name: &str) -> T; fn new_build_float_mul>(&self, lhs: T, rhs: T, name: &str) -> T; fn new_build_float_div>(&self, lhs: T, rhs: T, name: &str) -> T; fn new_build_float_compare>( &self, op: FloatPredicate, lhs: T, rhs: T, name: &str, ) -> <>::MathConvType as IntMathType<'ctx>>::ValueType; fn new_build_right_shift>( &self, lhs: T, rhs: T, sign_extend: bool, name: &str, ) -> T; fn new_build_left_shift>(&self, lhs: T, rhs: T, name: &str) -> T; fn new_build_int_z_extend>( &self, int_value: T, int_type: T::BaseType, name: &str, ) -> T; fn new_build_signed_int_to_float>( &self, int: T, float_type: >::MathConvType, name: &str, ) -> <>::MathConvType as FloatMathType<'ctx>>::ValueType; fn new_build_unsigned_int_to_float>( &self, int: T, float_type: >::MathConvType, name: &str, ) -> <>::MathConvType as FloatMathType<'ctx>>::ValueType; fn new_build_return(&self, value: Option<&dyn BasicValue<'ctx>>) -> InstructionValue<'ctx>; fn new_build_switch( &self, value: IntValue<'ctx>, else_block: BasicBlock<'ctx>, cases: &[(IntValue<'ctx>, BasicBlock<'ctx>)], ) -> InstructionValue<'ctx>; fn new_build_conditional_branch( &self, comparison: IntValue<'ctx>, then_block: BasicBlock<'ctx>, else_block: BasicBlock<'ctx>, ) -> InstructionValue<'ctx>; fn new_build_unconditional_branch( &self, destination_block: BasicBlock<'ctx>, ) -> InstructionValue<'ctx>; fn new_build_unreachable(&self) -> InstructionValue<'ctx>; fn new_build_free(&self, ptr: PointerValue<'ctx>) -> InstructionValue<'ctx>; } impl<'ctx> BuilderExt<'ctx> for Builder<'ctx> { fn new_build_struct_gep( &self, struct_type: StructType<'ctx>, ptr: PointerValue<'ctx>, index: u32, name: &str, ) -> PointerValue<'ctx> { // debug_assert_eq!( // ptr.get_type().get_element_type().into_struct_type(), // struct_type // ); self.build_struct_gep(struct_type, ptr, index, name) .unwrap() } fn new_build_alloca>(&self, ty: T, name: &str) -> PointerValue<'ctx> { self.build_alloca(ty, name).unwrap() } fn new_build_store>( &self, ptr: PointerValue<'ctx>, value: V, ) -> InstructionValue<'ctx> { self.build_store(ptr, value).unwrap() } fn new_build_load( &self, element_type: impl BasicType<'ctx>, ptr: PointerValue<'ctx>, name: &str, ) -> BasicValueEnum<'ctx> { // debug_assert_eq!( // ptr.get_type().get_element_type(), // element_type.as_any_type_enum() // ); self.build_load(element_type, ptr, name).unwrap() } unsafe fn new_build_in_bounds_gep( &self, element_type: impl BasicType<'ctx>, ptr: PointerValue<'ctx>, ordered_indexes: &[IntValue<'ctx>], name: &str, ) -> PointerValue<'ctx> { // debug_assert_eq!( // ptr.get_type().get_element_type(), // element_type.as_any_type_enum() // ); self.build_in_bounds_gep(element_type, ptr, ordered_indexes, name) .unwrap() } fn new_build_cast, V: BasicValue<'ctx>>( &self, op: InstructionOpcode, from_value: V, to_type: T, name: &str, ) -> BasicValueEnum<'ctx> { self.build_cast(op, from_value, to_type, name).unwrap() } fn new_build_bitcast(&self, val: V, ty: T, name: &str) -> BasicValueEnum<'ctx> where T: BasicType<'ctx>, V: BasicValue<'ctx>, { self.build_bitcast(val, ty, name).unwrap() } fn new_build_pointer_cast>( &self, from: T, to: T::BaseType, name: &str, ) -> T { self.build_pointer_cast(from, to, name).unwrap() } fn new_build_int_cast>( &self, int: T, int_type: T::BaseType, name: &str, ) -> T { self.build_int_cast(int, int_type, name).unwrap() } fn new_build_int_cast_sign_flag>( &self, int: T, int_type: T::BaseType, is_signed: bool, name: &str, ) -> T { self.build_int_cast_sign_flag(int, int_type, is_signed, name) .unwrap() } fn new_build_float_cast>( &self, float: T, float_type: T::BaseType, name: &str, ) -> T { self.build_float_cast(float, float_type, name).unwrap() } fn new_build_call( &self, function: FunctionValue<'ctx>, args: &[BasicMetadataValueEnum<'ctx>], name: &str, ) -> CallSiteValue<'ctx> { self.build_call(function, args, name).unwrap() } fn new_build_indirect_call( &self, function_type: FunctionType<'ctx>, function_pointer: PointerValue<'ctx>, args: &[BasicMetadataValueEnum<'ctx>], name: &str, ) -> CallSiteValue<'ctx> { self.build_indirect_call(function_type, function_pointer, args, name) .unwrap() } fn new_build_ptr_to_int>( &self, ptr: T, int_type: >::PtrConvType, name: &str, ) -> <>::PtrConvType as IntMathType<'ctx>>::ValueType { self.build_ptr_to_int(ptr, int_type, name).unwrap() } fn new_build_int_to_ptr>( &self, int: T, ptr_type: >::PtrConvType, name: &str, ) -> <>::PtrConvType as PointerMathType<'ctx>>::ValueType { self.build_int_to_ptr(int, ptr_type, name).unwrap() } fn new_build_is_null>( &self, ptr: T, name: &str, ) -> <>::PtrConvType as IntMathType<'ctx>>::ValueType { self.build_is_null(ptr, name).unwrap() } fn new_build_is_not_null>( &self, ptr: T, name: &str, ) -> <>::PtrConvType as IntMathType<'ctx>>::ValueType { self.build_is_not_null(ptr, name).unwrap() } fn new_build_phi>(&self, type_: T, name: &str) -> PhiValue<'ctx> { self.build_phi(type_, name).unwrap() } fn new_build_select, IMV: IntMathValue<'ctx>>( &self, condition: IMV, then: BV, else_: BV, name: &str, ) -> BasicValueEnum<'ctx> { self.build_select(condition, then, else_, name).unwrap() } fn new_build_and>(&self, lhs: T, rhs: T, name: &str) -> T { self.build_and(lhs, rhs, name).unwrap() } fn new_build_or>(&self, lhs: T, rhs: T, name: &str) -> T { self.build_or(lhs, rhs, name).unwrap() } fn new_build_xor>(&self, lhs: T, rhs: T, name: &str) -> T { self.build_xor(lhs, rhs, name).unwrap() } fn new_build_not>(&self, value: T, name: &str) -> T { self.build_not(value, name).unwrap() } fn new_build_int_neg>(&self, value: T, name: &str) -> T { self.build_int_neg(value, name).unwrap() } fn new_build_int_add>(&self, lhs: T, rhs: T, name: &str) -> T { self.build_int_add(lhs, rhs, name).unwrap() } fn new_build_int_sub>(&self, lhs: T, rhs: T, name: &str) -> T { self.build_int_sub(lhs, rhs, name).unwrap() } fn new_build_int_mul>(&self, lhs: T, rhs: T, name: &str) -> T { self.build_int_mul(lhs, rhs, name).unwrap() } fn new_build_int_signed_rem>(&self, lhs: T, rhs: T, name: &str) -> T { self.build_int_signed_rem(lhs, rhs, name).unwrap() } fn new_build_int_unsigned_rem>(&self, lhs: T, rhs: T, name: &str) -> T { self.build_int_unsigned_rem(lhs, rhs, name).unwrap() } fn new_build_int_signed_div>(&self, lhs: T, rhs: T, name: &str) -> T { self.build_int_signed_div(lhs, rhs, name).unwrap() } fn new_build_int_unsigned_div>(&self, lhs: T, rhs: T, name: &str) -> T { self.build_int_unsigned_div(lhs, rhs, name).unwrap() } fn new_build_int_compare>( &self, op: IntPredicate, lhs: T, rhs: T, name: &str, ) -> >::ValueType { self.build_int_compare(op, lhs, rhs, name).unwrap() } fn new_build_float_neg>(&self, value: T, name: &str) -> T { self.build_float_neg(value, name).unwrap() } fn new_build_float_add>(&self, lhs: T, rhs: T, name: &str) -> T { self.build_float_add(lhs, rhs, name).unwrap() } fn new_build_float_sub>(&self, lhs: T, rhs: T, name: &str) -> T { self.build_float_sub(lhs, rhs, name).unwrap() } fn new_build_float_mul>(&self, lhs: T, rhs: T, name: &str) -> T { self.build_float_mul(lhs, rhs, name).unwrap() } fn new_build_float_div>(&self, lhs: T, rhs: T, name: &str) -> T { self.build_float_div(lhs, rhs, name).unwrap() } fn new_build_float_compare>( &self, op: FloatPredicate, lhs: T, rhs: T, name: &str, ) -> <>::MathConvType as IntMathType<'ctx>>::ValueType { self.build_float_compare(op, lhs, rhs, name).unwrap() } fn new_build_right_shift>( &self, lhs: T, rhs: T, sign_extend: bool, name: &str, ) -> T { self.build_right_shift(lhs, rhs, sign_extend, name).unwrap() } fn new_build_left_shift>(&self, lhs: T, rhs: T, name: &str) -> T { self.build_left_shift(lhs, rhs, name).unwrap() } fn new_build_int_z_extend>( &self, int_value: T, int_type: T::BaseType, name: &str, ) -> T { self.build_int_z_extend(int_value, int_type, name).unwrap() } fn new_build_signed_int_to_float>( &self, int: T, float_type: >::MathConvType, name: &str, ) -> <>::MathConvType as FloatMathType<'ctx>>::ValueType { self.build_signed_int_to_float(int, float_type, name) .unwrap() } fn new_build_unsigned_int_to_float>( &self, int: T, float_type: >::MathConvType, name: &str, ) -> <>::MathConvType as FloatMathType<'ctx>>::ValueType { self.build_unsigned_int_to_float(int, float_type, name) .unwrap() } fn new_build_return(&self, value: Option<&dyn BasicValue<'ctx>>) -> InstructionValue<'ctx> { self.build_return(value).unwrap() } fn new_build_switch( &self, value: IntValue<'ctx>, else_block: BasicBlock<'ctx>, cases: &[(IntValue<'ctx>, BasicBlock<'ctx>)], ) -> InstructionValue<'ctx> { self.build_switch(value, else_block, cases).unwrap() } fn new_build_conditional_branch( &self, comparison: IntValue<'ctx>, then_block: BasicBlock<'ctx>, else_block: BasicBlock<'ctx>, ) -> InstructionValue<'ctx> { self.build_conditional_branch(comparison, then_block, else_block) .unwrap() } fn new_build_unconditional_branch( &self, destination_block: BasicBlock<'ctx>, ) -> InstructionValue<'ctx> { self.build_unconditional_branch(destination_block).unwrap() } fn new_build_unreachable(&self) -> InstructionValue<'ctx> { self.build_unreachable().unwrap() } fn new_build_free(&self, ptr: PointerValue<'ctx>) -> InstructionValue<'ctx> { self.build_free(ptr).unwrap() } } #[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 { ($env:expr, $function_value:expr) => {{ use inkwell::debug_info::AsDIScope; let func_scope = $function_value.get_subprogram().expect("subprogram"); let lexical_block = $env.dibuilder.create_lexical_block( /* scope */ func_scope.as_debug_info_scope(), /* file */ $env.compile_unit.get_file(), /* line_no */ 0, /* column_no */ 0, ); let loc = $env.dibuilder.create_debug_location( $env.context, /* line */ 0, /* column */ 0, /* current_scope */ lexical_block.as_debug_info_scope(), /* inlined_at */ None, ); $env.builder.set_current_debug_location(loc); }}; } #[derive(Debug, Clone, Copy)] pub enum LlvmBackendMode { /// Assumes primitives (roc_alloc, roc_panic, etc) are provided by the host Binary, BinaryDev, /// Creates a test wrapper around the main roc function to catch and report panics. /// Provides a testing implementation of primitives (roc_alloc, roc_panic, etc) BinaryGlue, GenTest, WasmGenTest, CliTest, } impl LlvmBackendMode { pub(crate) fn has_host(self) -> bool { match self { LlvmBackendMode::Binary => true, LlvmBackendMode::BinaryDev => true, LlvmBackendMode::BinaryGlue => false, LlvmBackendMode::GenTest => false, LlvmBackendMode::WasmGenTest => true, LlvmBackendMode::CliTest => false, } } /// In other words, catches exceptions and returns a result fn returns_roc_result(self) -> bool { match self { LlvmBackendMode::Binary => false, LlvmBackendMode::BinaryDev => false, LlvmBackendMode::BinaryGlue => false, LlvmBackendMode::GenTest => true, LlvmBackendMode::WasmGenTest => true, LlvmBackendMode::CliTest => true, } } pub(crate) fn runs_expects(self) -> bool { match self { LlvmBackendMode::Binary => false, LlvmBackendMode::BinaryDev => true, LlvmBackendMode::BinaryGlue => false, LlvmBackendMode::GenTest => false, LlvmBackendMode::WasmGenTest => false, LlvmBackendMode::CliTest => true, } } } pub struct Env<'a, 'ctx, 'env> { pub arena: &'a Bump, pub context: &'ctx Context, pub builder: &'env Builder<'ctx>, pub dibuilder: &'env DebugInfoBuilder<'ctx>, pub compile_unit: &'env DICompileUnit<'ctx>, pub module: &'ctx Module<'ctx>, pub interns: Interns, pub target_info: TargetInfo, pub mode: LlvmBackendMode, pub exposed_to_host: MutSet, } 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> { let ctx = self.context; match self.target_info.ptr_width() { roc_target::PtrWidth::Bytes4 => ctx.i32_type(), roc_target::PtrWidth::Bytes8 => ctx.i64_type(), } } /// The integer type representing twice the width of a pointer /// /// on 64-bit systems, this is i128 /// on 32-bit systems, this is i64 pub fn twice_ptr_int(&self) -> IntType<'ctx> { let ctx = self.context; match self.target_info.ptr_width() { roc_target::PtrWidth::Bytes4 => ctx.i64_type(), roc_target::PtrWidth::Bytes8 => ctx.i128_type(), } } pub fn small_str_bytes(&self) -> u32 { self.target_info.ptr_width() as u32 * 3 } pub fn build_intrinsic_call( &self, intrinsic_name: &'static str, args: &[BasicValueEnum<'ctx>], ) -> CallSiteValue<'ctx> { let fn_val = self .module .get_function(intrinsic_name) .unwrap_or_else(|| panic!("Unrecognized intrinsic function: {intrinsic_name}")); let mut arg_vals: Vec = Vec::with_capacity_in(args.len(), self.arena); for arg in args.iter() { arg_vals.push((*arg).into()); } let call = self .builder .new_build_call(fn_val, arg_vals.into_bump_slice(), "call"); call.set_call_convention(fn_val.get_call_conventions()); call } pub fn call_intrinsic( &self, intrinsic_name: &'static str, args: &[BasicValueEnum<'ctx>], ) -> BasicValueEnum<'ctx> { let call = self.build_intrinsic_call(intrinsic_name, args); call.try_as_basic_value().left().unwrap_or_else(|| { panic!("LLVM error: Invalid call by name for intrinsic {intrinsic_name}") }) } pub fn alignment_type(&self) -> IntType<'ctx> { self.context.i32_type() } pub fn alignment_const(&self, alignment: u32) -> IntValue<'ctx> { self.alignment_type().const_int(alignment as u64, false) } pub fn alignment_intvalue( &self, layout_interner: &STLayoutInterner<'a>, element_layout: InLayout<'a>, ) -> BasicValueEnum<'ctx> { let alignment = layout_interner.alignment_bytes(element_layout); let alignment_iv = self.alignment_const(alignment); alignment_iv.into() } pub fn call_alloc( &self, number_of_bytes: IntValue<'ctx>, alignment: u32, ) -> PointerValue<'ctx> { let function = self.module.get_function("roc_alloc").unwrap(); let alignment = self.alignment_const(alignment); let call = self.builder.new_build_call( function, &[number_of_bytes.into(), alignment.into()], "roc_alloc", ); call.set_call_convention(C_CALL_CONV); call.try_as_basic_value() .left() .unwrap() .into_pointer_value() // TODO check if alloc returned null; if so, runtime error for OOM! } pub fn call_dealloc(&self, ptr: PointerValue<'ctx>, alignment: u32) -> InstructionValue<'ctx> { let function = self.module.get_function("roc_dealloc").unwrap(); let alignment = self.alignment_const(alignment); let call = self.builder .new_build_call(function, &[ptr.into(), alignment.into()], "roc_dealloc"); call.set_call_convention(C_CALL_CONV); call.try_as_basic_value().right().unwrap() } pub fn call_memset( &self, bytes_ptr: PointerValue<'ctx>, filler: IntValue<'ctx>, length: IntValue<'ctx>, ) -> CallSiteValue<'ctx> { let false_val = self.context.bool_type().const_int(0, false); let intrinsic_name = match self.target_info.ptr_width() { roc_target::PtrWidth::Bytes8 => LLVM_MEMSET_I64, roc_target::PtrWidth::Bytes4 => LLVM_MEMSET_I32, }; self.build_intrinsic_call( intrinsic_name, &[ bytes_ptr.into(), filler.into(), length.into(), false_val.into(), ], ) } pub fn call_panic( &self, env: &Env<'a, 'ctx, 'env>, message: BasicValueEnum<'ctx>, tag: CrashTag, ) { let function = self.module.get_function("roc_panic").unwrap(); let tag_id = self.context.i32_type().const_int(tag as u32 as u64, false); let msg = self.string_to_arg(env, message); let call = self .builder .new_build_call(function, &[msg.into(), tag_id.into()], "roc_panic"); call.set_call_convention(C_CALL_CONV); } pub fn call_dbg( &self, env: &Env<'a, 'ctx, 'env>, location: BasicValueEnum<'ctx>, source: BasicValueEnum<'ctx>, message: BasicValueEnum<'ctx>, ) { let function = self.module.get_function("roc_dbg").unwrap(); let loc = self.string_to_arg(env, location); let src = self.string_to_arg(env, source); let msg = self.string_to_arg(env, message); let call = self.builder .new_build_call(function, &[loc.into(), src.into(), msg.into()], "roc_dbg"); call.set_call_convention(C_CALL_CONV); } fn string_to_arg( &self, env: &Env<'a, 'ctx, 'env>, string: BasicValueEnum<'ctx>, ) -> BasicValueEnum<'ctx> { match env.target_info.ptr_width() { PtrWidth::Bytes4 => { // we need to pass the string by reference, but we currently hold the value. let alloca = env .builder .new_build_alloca(string.get_type(), "alloca_string"); env.builder.new_build_store(alloca, string); alloca.into() } PtrWidth::Bytes8 => { // string is already held by reference string } } } pub fn new_debug_info(module: &Module<'ctx>) -> (DebugInfoBuilder<'ctx>, DICompileUnit<'ctx>) { module.create_debug_info_builder( true, /* language */ inkwell::debug_info::DWARFSourceLanguage::C, /* filename */ "roc_app", /* directory */ ".", /* producer */ "my llvm compiler frontend", /* is_optimized */ false, /* compiler command line flags */ "", /* runtime_ver */ 0, /* split_name */ "", /* kind */ inkwell::debug_info::DWARFEmissionKind::Full, /* dwo_id */ 0, /* split_debug_inling */ false, /* debug_info_for_profiling */ false, "", "", ) } pub fn new_subprogram(&self, function_name: &str) -> DISubprogram<'ctx> { let dibuilder = self.dibuilder; let compile_unit = self.compile_unit; let ditype = dibuilder .create_basic_type( "type_name", 0_u64, 0x00, inkwell::debug_info::DIFlags::PUBLIC, ) .unwrap(); let subroutine_type = dibuilder.create_subroutine_type( compile_unit.get_file(), /* return type */ Some(ditype.as_type()), /* parameter types */ &[], inkwell::debug_info::DIFlags::PUBLIC, ); dibuilder.create_function( /* scope */ compile_unit.get_file().as_debug_info_scope(), /* func name */ function_name, /* linkage_name */ None, /* file */ compile_unit.get_file(), /* line_no */ 0, /* DIType */ subroutine_type, /* is_local_to_unit */ true, /* is_definition */ true, /* scope_line */ 0, /* flags */ inkwell::debug_info::DIFlags::PUBLIC, /* is_optimized */ false, ) } } pub fn module_from_builtins<'ctx>( target: &target_lexicon::Triple, ctx: &'ctx Context, module_name: &str, ) -> Module<'ctx> { // In the build script for the builtins module, we compile the builtins into LLVM bitcode let bitcode_bytes: &[u8] = if target == &target_lexicon::Triple::host() { include_bytes!("../../../builtins/bitcode/zig-out/builtins-host.bc") } else { match target { Triple { architecture: Architecture::Wasm32, .. } => { include_bytes!("../../../builtins/bitcode/zig-out/builtins-wasm32.bc") } Triple { architecture: Architecture::X86_32(_), operating_system: OperatingSystem::Linux, .. } => { include_bytes!("../../../builtins/bitcode/zig-out/builtins-x86.bc") } Triple { architecture: Architecture::X86_64, operating_system: OperatingSystem::Linux, .. } => { include_bytes!("../../../builtins/bitcode/zig-out/builtins-x86_64.bc") } Triple { architecture: Architecture::Aarch64(Aarch64Architecture::Aarch64), operating_system: OperatingSystem::Linux, .. } => { include_bytes!("../../../builtins/bitcode/zig-out/builtins-aarch64.bc") } Triple { architecture: Architecture::X86_64, operating_system: OperatingSystem::Windows, .. } => { include_bytes!("../../../builtins/bitcode/zig-out/builtins-windows-x86_64.bc") } _ => panic!("The zig builtins are not currently built for this target: {target:?}"), } }; let memory_buffer = MemoryBuffer::create_from_memory_range(bitcode_bytes, module_name); let module = Module::parse_bitcode_from_buffer(&memory_buffer, ctx) .unwrap_or_else(|err| panic!("Unable to import builtins bitcode. LLVM error: {err:?}")); // Add LLVM intrinsics. add_intrinsics(ctx, &module); module } pub fn construct_optimization_passes<'a>( module: &'a Module, opt_level: OptLevel, ) -> (PassManager>, PassManager>) { let mpm = PassManager::create(()); let fpm = PassManager::create(module); // remove unused global values (e.g. those defined by zig, but unused in user code) mpm.add_global_dce_pass(); mpm.add_always_inliner_pass(); // tail-call elimination is always on fpm.add_instruction_combining_pass(); fpm.add_tail_call_elimination_pass(); let pmb = PassManagerBuilder::create(); match opt_level { OptLevel::Development | OptLevel::Normal => { pmb.set_optimization_level(OptimizationLevel::None); } OptLevel::Size => { pmb.set_optimization_level(OptimizationLevel::Default); // TODO: For some usecase, like embedded, it is useful to expose this and tune it. pmb.set_inliner_with_threshold(50); } OptLevel::Optimize => { pmb.set_optimization_level(OptimizationLevel::Aggressive); // this threshold seems to do what we want pmb.set_inliner_with_threshold(750); } } // Add optimization passes for Size and Optimize. if matches!(opt_level, OptLevel::Size | OptLevel::Optimize) { // TODO figure out which of these actually help // function passes fpm.add_cfg_simplification_pass(); mpm.add_cfg_simplification_pass(); fpm.add_jump_threading_pass(); mpm.add_jump_threading_pass(); fpm.add_memcpy_optimize_pass(); // this one is very important fpm.add_licm_pass(); // turn invoke into call // TODO: is this pass needed. It theoretically prunes unused exception handling info. // This seems unrelated to the comment above. It also seems to be missing in llvm-16. // mpm.add_prune_eh_pass(); // remove unused global values (often the `_wrapper` can be removed) mpm.add_global_dce_pass(); mpm.add_function_inlining_pass(); } pmb.populate_module_pass_manager(&mpm); pmb.populate_function_pass_manager(&fpm); fpm.initialize(); // For now, we have just one of each (mpm, fpm) } fn promote_to_main_function<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, mod_solutions: &'a ModSolutions, symbol: Symbol, top_level: ProcLayout<'a>, ) -> (&'static str, FunctionValue<'ctx>) { let it = top_level.arguments.iter().copied(); let bytes = roc_alias_analysis::func_name_bytes_help(symbol, it, Niche::NONE, top_level.result); let func_name = FuncName(&bytes); let func_solutions = mod_solutions.func_solutions(func_name).unwrap(); let mut it = func_solutions.specs(); let func_spec = it.next().unwrap(); debug_assert!( it.next().is_none(), "we expect only one specialization of this symbol" ); // NOTE fake layout; it is only used for debug prints let roc_main_fn = function_value_by_func_spec(env, FuncBorrowSpec::Some(*func_spec), symbol); let main_fn_name = "$Test.main"; // Add main to the module. let main_fn = expose_function_to_host_help_c_abi( env, layout_interner, main_fn_name, roc_main_fn, top_level.arguments, top_level.result, main_fn_name, ); (main_fn_name, main_fn) } fn promote_to_wasm_test_wrapper<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, mod_solutions: &'a ModSolutions, symbol: Symbol, top_level: ProcLayout<'a>, ) -> (&'static str, FunctionValue<'ctx>) { // generates roughly // // fn test_wrapper() -> *T { // result = roc_main(); // ptr = roc_malloc(size_of::) // *ptr = result // ret ptr; // } let main_fn_name = "test_wrapper"; let it = top_level.arguments.iter().copied(); let bytes = roc_alias_analysis::func_name_bytes_help(symbol, it, Niche::NONE, top_level.result); let func_name = FuncName(&bytes); let func_solutions = mod_solutions.func_solutions(func_name).unwrap(); let mut it = func_solutions.specs(); let func_spec = it.next().unwrap(); debug_assert!( it.next().is_none(), "we expect only one specialization of this symbol" ); // NOTE fake layout; it is only used for debug prints let roc_main_fn = function_value_by_func_spec(env, FuncBorrowSpec::Some(*func_spec), symbol); let output_type = match roc_main_fn.get_type().get_return_type() { Some(return_type) => { let output_type = return_type.ptr_type(AddressSpace::default()); output_type.into() } None => { assert_eq!(roc_main_fn.get_type().get_param_types().len(), 1); let output_type = roc_main_fn.get_type().get_param_types()[0]; output_type } }; let main_fn = { let c_function_spec = FunctionSpec::cconv(env, CCReturn::Return, Some(output_type), &[]); let c_function = add_func( env.context, env.module, main_fn_name, c_function_spec, Linkage::External, ); let subprogram = env.new_subprogram(main_fn_name); c_function.set_subprogram(subprogram); // STEP 2: build the exposed function's body let builder = env.builder; let context = env.context; let entry = context.append_basic_block(c_function, "entry"); builder.position_at_end(entry); let roc_main_fn_result = call_direct_roc_function( env, layout_interner, roc_main_fn, layout_interner.get_repr(top_level.result), &[], ); // For consistency, we always return with a heap-allocated value let (size, alignment) = layout_interner.stack_size_and_alignment(top_level.result); let number_of_bytes = env.ptr_int().const_int(size as _, false); let void_ptr = env.call_alloc(number_of_bytes, alignment); let ptr = builder.new_build_pointer_cast(void_ptr, output_type.into_pointer_type(), "cast_ptr"); store_roc_value( env, layout_interner, layout_interner.get_repr(top_level.result), ptr, roc_main_fn_result, ); builder.new_build_return(Some(&ptr)); c_function }; (main_fn_name, main_fn) } fn int_with_precision<'ctx>( env: &Env<'_, 'ctx, '_>, value: i128, int_width: IntWidth, ) -> IntValue<'ctx> { use IntWidth::*; match int_width { U128 | I128 => const_i128(env, value), U64 | I64 => env.context.i64_type().const_int(value as u64, false), U32 | I32 => env.context.i32_type().const_int(value as u64, false), U16 | I16 => env.context.i16_type().const_int(value as u64, false), U8 | I8 => env.context.i8_type().const_int(value as u64, false), } } fn float_with_precision<'ctx>( env: &Env<'_, 'ctx, '_>, value: f64, float_width: FloatWidth, ) -> BasicValueEnum<'ctx> { match float_width { FloatWidth::F64 => env.context.f64_type().const_float(value).into(), FloatWidth::F32 => env.context.f32_type().const_float(value).into(), } } pub fn build_exp_literal<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, parent: FunctionValue<'ctx>, layout: InLayout<'_>, literal: &roc_mono::ir::Literal<'a>, ) -> BasicValueEnum<'ctx> { use roc_mono::ir::Literal::*; match literal { Int(bytes) => match layout_interner.get_repr(layout) { LayoutRepr::Builtin(Builtin::Bool) => env .context .bool_type() .const_int(i128::from_ne_bytes(*bytes) as u64, false) .into(), LayoutRepr::Builtin(Builtin::Int(int_width)) => { int_with_precision(env, i128::from_ne_bytes(*bytes), int_width).into() } _ => panic!("Invalid layout for int literal = {layout:?}"), }, U128(bytes) => const_u128(env, u128::from_ne_bytes(*bytes)).into(), Float(float) => match layout_interner.get_repr(layout) { LayoutRepr::Builtin(Builtin::Float(float_width)) => { float_with_precision(env, *float, float_width) } _ => panic!("Invalid layout for float literal = {layout:?}"), }, Decimal(bytes) => { let (upper_bits, lower_bits) = RocDec::from_ne_bytes(*bytes).as_bits(); env.context .i128_type() .const_int_arbitrary_precision(&[lower_bits, upper_bits as u64]) .into() } 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) => build_string_literal(env, parent, str_literal), } } fn build_string_literal<'ctx>( env: &Env<'_, 'ctx, '_>, parent: FunctionValue<'ctx>, str_literal: &str, ) -> BasicValueEnum<'ctx> { 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).into(), 12 => small_str_ptr_width_4(env, 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); let alloca = const_str_alloca_ptr(env, parent, ptr, number_of_elements, number_of_elements); match env.target_info.ptr_width() { PtrWidth::Bytes4 => { env.builder .new_build_load(zig_str_type(env), alloca, "load_const_str") } PtrWidth::Bytes8 => alloca.into(), } } } fn const_str_alloca_ptr<'ctx>( env: &Env<'_, 'ctx, '_>, parent: FunctionValue<'ctx>, ptr: PointerValue<'ctx>, len: IntValue<'ctx>, cap: IntValue<'ctx>, ) -> PointerValue<'ctx> { let typ = zig_str_type(env); let value = typ.const_named_struct(&[ptr.into(), len.into(), cap.into()]); let alloca = create_entry_block_alloca(env, parent, typ.into(), "const_str_store"); env.builder.new_build_store(alloca, value); alloca } fn small_str_ptr_width_8<'ctx>( env: &Env<'_, 'ctx, '_>, parent: FunctionValue<'ctx>, str_literal: &str, ) -> PointerValue<'ctx> { debug_assert_eq!(env.target_info.ptr_width() as u8, 8); let mut array = [0u8; 24]; array[..str_literal.len()].copy_from_slice(str_literal.as_bytes()); array[env.small_str_bytes() as usize - 1] = str_literal.len() as u8 | roc_std::RocStr::MASK; let word1 = u64::from_ne_bytes(array[0..8].try_into().unwrap()); let word2 = u64::from_ne_bytes(array[8..16].try_into().unwrap()); let word3 = u64::from_ne_bytes(array[16..24].try_into().unwrap()); let ptr = env.ptr_int().const_int(word1, false); let len = env.ptr_int().const_int(word2, false); let cap = env.ptr_int().const_int(word3, false); let address_space = AddressSpace::default(); let ptr_type = env.context.i8_type().ptr_type(address_space); let ptr = env.builder.new_build_int_to_ptr(ptr, ptr_type, "to_u8_ptr"); const_str_alloca_ptr(env, parent, ptr, len, cap) } fn small_str_ptr_width_4<'ctx>(env: &Env<'_, 'ctx, '_>, str_literal: &str) -> StructValue<'ctx> { debug_assert_eq!(env.target_info.ptr_width() as u8, 4); let mut array = [0u8; 12]; array[..str_literal.len()].copy_from_slice(str_literal.as_bytes()); array[env.small_str_bytes() as usize - 1] = str_literal.len() as u8 | roc_std::RocStr::MASK; let word1 = u32::from_ne_bytes(array[0..4].try_into().unwrap()); let word2 = u32::from_ne_bytes(array[4..8].try_into().unwrap()); let word3 = u32::from_ne_bytes(array[8..12].try_into().unwrap()); let ptr = env.ptr_int().const_int(word1 as u64, false); let len = env.ptr_int().const_int(word2 as u64, false); let cap = env.ptr_int().const_int(word3 as u64, false); let address_space = AddressSpace::default(); let ptr_type = env.context.i8_type().ptr_type(address_space); let ptr = env.builder.new_build_int_to_ptr(ptr, ptr_type, "to_u8_ptr"); struct_from_fields( env, zig_str_type(env), [(0, ptr.into()), (1, len.into()), (2, cap.into())].into_iter(), ) } pub(crate) fn build_exp_call<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, func_spec_solutions: &FuncSpecSolutions, scope: &mut Scope<'a, 'ctx>, parent: FunctionValue<'ctx>, layout: InLayout<'a>, call: &roc_mono::ir::Call<'a>, ) -> BasicValueEnum<'ctx> { let roc_mono::ir::Call { call_type, arguments, } = call; match call_type { CallType::ByName { name, specialization_id, ret_layout, .. } => { let mut arg_tuples: Vec = Vec::with_capacity_in(arguments.len(), env.arena); for symbol in arguments.iter() { arg_tuples.push(scope.load_symbol(symbol)); } let bytes = specialization_id.to_bytes(); let callee_var = CalleeSpecVar(&bytes); let func_spec = func_spec_solutions.callee_spec(callee_var).unwrap(); roc_call_direct_with_args( env, layout_interner, *ret_layout, *name, FuncBorrowSpec::Some(func_spec), arg_tuples.into_bump_slice(), ) } CallType::ByPointer { pointer, arg_layouts, ret_layout, } => { let mut args: Vec = Vec::with_capacity_in(arguments.len(), env.arena); for symbol in arguments.iter() { args.push(scope.load_symbol(symbol)); } let pointer = scope.load_symbol(pointer).into_pointer_value(); roc_call_erased_with_args( env, layout_interner, pointer, arg_layouts, *ret_layout, args.into_bump_slice(), ) } CallType::LowLevel { op, update_mode } => { let bytes = update_mode.to_bytes(); let update_var = UpdateModeVar(&bytes); let update_mode = func_spec_solutions .update_mode(update_var) .unwrap_or(UpdateMode::Immutable); crate::llvm::lowlevel::run_low_level( env, layout_interner, layout_ids, scope, parent, layout, *op, arguments, update_mode, ) } CallType::HigherOrder(higher_order) => { let bytes = higher_order.passed_function.specialization_id.to_bytes(); let callee_var = CalleeSpecVar(&bytes); let func_spec = func_spec_solutions.callee_spec(callee_var).unwrap(); run_higher_order_low_level( env, layout_interner, layout_ids, scope, layout, func_spec, higher_order, ) } CallType::Foreign { foreign_symbol, ret_layout, } => build_foreign_symbol( env, layout_interner, scope, foreign_symbol, arguments, *ret_layout, ), } } fn struct_pointer_from_fields<'a, 'ctx, 'env, I>( env: &Env<'a, 'ctx, 'env>, layout_interner: &STLayoutInterner<'a>, struct_type: StructType<'ctx>, input_pointer: PointerValue<'ctx>, values: I, ) where I: Iterator, BasicValueEnum<'ctx>))>, { let struct_ptr = env .builder .new_build_bitcast( input_pointer, struct_type.ptr_type(AddressSpace::default()), "struct_ptr", ) .into_pointer_value(); // Insert field exprs into struct_val for (index, (field_layout, field_value)) in values { let field_ptr = env.builder.new_build_struct_gep( struct_type, struct_ptr, index as u32, "field_struct_gep", ); store_roc_value( env, layout_interner, layout_interner.get_repr(field_layout), field_ptr, field_value, ); } } pub(crate) fn build_exp_expr<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, func_spec_solutions: &FuncSpecSolutions, scope: &mut Scope<'a, 'ctx>, parent: FunctionValue<'ctx>, layout: InLayout<'a>, expr: &roc_mono::ir::Expr<'a>, ) -> BasicValueEnum<'ctx> { use roc_mono::ir::Expr::*; match expr { Literal(literal) => build_exp_literal(env, layout_interner, parent, layout, literal), NullPointer => { let basic_type = basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout)); debug_assert!(basic_type.is_pointer_type()); basic_type.into_pointer_type().const_zero().into() } Call(call) => build_exp_call( env, layout_interner, layout_ids, func_spec_solutions, scope, parent, layout, call, ), Struct(sorted_fields) => RocStruct::build( env, layout_interner, layout_interner.get_repr(layout), scope, sorted_fields, ) .into(), Tag { arguments, tag_layout: union_layout, tag_id, reuse, } => { let reuse_ptr = reuse.map(|ru| scope.load_symbol(&ru.symbol).into_pointer_value()); build_tag( env, layout_interner, scope, union_layout, *tag_id, arguments, reuse_ptr, parent, ) } FunctionPointer { lambda_name } => { let alloca = fn_ptr::build(env, *lambda_name); alloca.into() } ErasedMake { value, callee } => { let value = value.map(|sym| scope.load_symbol(&sym).into_pointer_value()); let callee = scope.load_symbol(callee).into_pointer_value(); erased::build(env, value, callee).into() } ErasedLoad { symbol, field } => { let value = scope.load_symbol(symbol).into_struct_value(); let wanted_llvm_type = basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout)) .into_pointer_type(); erased::load(env, value, *field, wanted_llvm_type).into() } Reset { symbol, update_mode, } => { let bytes = update_mode.to_bytes(); let update_var = UpdateModeVar(&bytes); let update_mode = func_spec_solutions .update_mode(update_var) .unwrap_or(UpdateMode::Immutable); let (tag_ptr, layout) = scope.load_symbol_and_layout(symbol); let tag_ptr = tag_ptr.into_pointer_value(); // reset is only generated for union values let union_layout = match layout_interner.get_repr(layout) { LayoutRepr::Union(ul) => ul, _ => unreachable!(), }; let ctx = env.context; let check_if_null = ctx.append_basic_block(parent, "check_if_null"); let check_if_unique = ctx.append_basic_block(parent, "check_if_unique"); let cont_block = ctx.append_basic_block(parent, "cont"); env.builder.new_build_unconditional_branch(check_if_null); env.builder.position_at_end(check_if_null); env.builder.new_build_conditional_branch( // have llvm optimizations clean this up if layout_interner.is_nullable(layout) { env.builder.new_build_is_null(tag_ptr, "is_tag_null") } else { env.context.bool_type().const_int(false as _, false) }, cont_block, check_if_unique, ); env.builder.position_at_end(check_if_unique); let then_block = ctx.append_basic_block(parent, "then_reset"); let else_block = ctx.append_basic_block(parent, "else_decref"); let refcount_ptr = PointerToRefcount::from_ptr_to_data( env, if union_layout.stores_tag_id_in_pointer(env.target_info) { tag_pointer_clear_tag_id(env, tag_ptr) } else { tag_ptr }, ); let is_unique = match update_mode { UpdateMode::InPlace => env.context.bool_type().const_int(1, false), UpdateMode::Immutable => refcount_ptr.is_1(env), }; env.builder .new_build_conditional_branch(is_unique, then_block, else_block); { // reset, when used on a unique reference, eagerly decrements the components of the // referenced value, and returns the location of the now-invalid cell env.builder.position_at_end(then_block); let reset_function = build_reset(env, layout_interner, layout_ids, union_layout); let call = env.builder .new_build_call(reset_function, &[tag_ptr.into()], "call_reset"); call.set_call_convention(FAST_CALL_CONV); let _ = call.try_as_basic_value(); env.builder.new_build_unconditional_branch(cont_block); } { // If reset is used on a shared, non-reusable reference, it behaves // like dec and returns NULL, which instructs reuse to behave like ctor env.builder.position_at_end(else_block); refcount_ptr.decrement(env, layout_interner, layout_interner.get_repr(layout)); env.builder.new_build_unconditional_branch(cont_block); } { env.builder.position_at_end(cont_block); let phi = env.builder.new_build_phi(tag_ptr.get_type(), "branch"); let null_ptr = tag_ptr.get_type().const_null(); phi.add_incoming(&[ (&null_ptr, check_if_null), (&tag_ptr, then_block), (&null_ptr, else_block), ]); phi.as_basic_value() } } ResetRef { symbol, update_mode, } => { let bytes = update_mode.to_bytes(); let update_var = UpdateModeVar(&bytes); let update_mode = func_spec_solutions .update_mode(update_var) .unwrap_or(UpdateMode::Immutable); let (tag_ptr, layout) = scope.load_symbol_and_layout(symbol); let tag_ptr = tag_ptr.into_pointer_value(); let ctx = env.context; let check_if_null = ctx.append_basic_block(parent, "check_if_null"); let check_if_unique = ctx.append_basic_block(parent, "check_if_unique"); let cont_block = ctx.append_basic_block(parent, "cont"); env.builder.new_build_unconditional_branch(check_if_null); env.builder.position_at_end(check_if_null); env.builder.new_build_conditional_branch( // have llvm optimizations clean this up if layout_interner.is_nullable(layout) { env.builder.new_build_is_null(tag_ptr, "is_tag_null") } else { env.context.bool_type().const_int(false as _, false) }, cont_block, check_if_unique, ); env.builder.position_at_end(check_if_unique); let not_unique_block = ctx.append_basic_block(parent, "else_decref"); // reset is only generated for union values let union_layout = match layout_interner.get_repr(layout) { LayoutRepr::Union(ul) => ul, _ => unreachable!(), }; let refcount_ptr = PointerToRefcount::from_ptr_to_data( env, if union_layout.stores_tag_id_in_pointer(env.target_info) { tag_pointer_clear_tag_id(env, tag_ptr) } else { tag_ptr }, ); let is_unique = match update_mode { UpdateMode::InPlace => env.context.bool_type().const_int(1, false), UpdateMode::Immutable => refcount_ptr.is_1(env), }; let parent_block = env.builder.get_insert_block().unwrap(); env.builder .new_build_conditional_branch(is_unique, cont_block, not_unique_block); { // If reset is used on a shared, non-reusable reference, it behaves // like dec and returns NULL, which instructs reuse to behave like ctor env.builder.position_at_end(not_unique_block); refcount_ptr.decrement(env, layout_interner, layout_interner.get_repr(layout)); env.builder.new_build_unconditional_branch(cont_block); } { env.builder.position_at_end(cont_block); let phi = env.builder.new_build_phi(tag_ptr.get_type(), "branch"); let null_ptr = tag_ptr.get_type().const_null(); phi.add_incoming(&[ (&null_ptr, check_if_null), (&tag_ptr, parent_block), (&null_ptr, not_unique_block), ]); phi.as_basic_value() } } StructAtIndex { index, structure, .. } => { let (value, layout) = scope.load_symbol_and_layout(structure); let struct_val = RocStruct::from(value); struct_val.load_at_index( env, layout_interner, layout_interner.get_repr(layout), *index, ) } EmptyArray => empty_polymorphic_list(env), Array { elem_layout, elems } => { list_literal(env, layout_interner, parent, scope, *elem_layout, elems) } RuntimeErrorFunction(_) => todo!(), UnionAtIndex { tag_id, structure, index, union_layout, } => { // cast the argument bytes into the desired shape for this tag let (argument, structure_layout) = scope.load_symbol_and_layout(structure); match union_layout { UnionLayout::NonRecursive(tag_layouts) => { debug_assert!(argument.is_pointer_value()); let field_layouts = tag_layouts[*tag_id as usize]; let struct_layout = LayoutRepr::struct_(field_layouts); let struct_type = basic_type_from_layout(env, layout_interner, struct_layout); let opaque_data_ptr = env.builder.new_build_struct_gep( basic_type_from_layout( env, layout_interner, layout_interner.get_repr(structure_layout), ) .into_struct_type(), argument.into_pointer_value(), RocUnion::TAG_DATA_INDEX, "get_opaque_data_ptr", ); let data_ptr = env.builder.new_build_pointer_cast( opaque_data_ptr, struct_type.ptr_type(AddressSpace::default()), "to_data_pointer", ); let element_ptr = env.builder.new_build_struct_gep( struct_type.into_struct_type(), data_ptr, *index as _, "get_opaque_data_ptr", ); load_roc_value( env, layout_interner, layout_interner.get_repr(field_layouts[*index as usize]), element_ptr, "load_element", ) } UnionLayout::Recursive(tag_layouts) => { debug_assert!(argument.is_pointer_value()); let field_layouts = tag_layouts[*tag_id as usize]; let ptr = tag_pointer_clear_tag_id(env, argument.into_pointer_value()); let target_loaded_type = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(layout), ); lookup_at_index_ptr( env, layout_interner, field_layouts, *index as usize, ptr, None, target_loaded_type, ) } UnionLayout::NonNullableUnwrapped(field_layouts) => { let struct_layout = LayoutRepr::struct_(field_layouts); let struct_type = basic_type_from_layout(env, layout_interner, struct_layout); let target_loaded_type = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(layout), ); lookup_at_index_ptr( env, layout_interner, field_layouts, *index as usize, argument.into_pointer_value(), Some(struct_type.into_struct_type()), target_loaded_type, ) } UnionLayout::NullableWrapped { nullable_id, other_tags, } => { debug_assert!(argument.is_pointer_value()); debug_assert_ne!(*tag_id, *nullable_id); let tag_index = if *tag_id < *nullable_id { *tag_id } else { tag_id - 1 }; let field_layouts = other_tags[tag_index as usize]; let ptr = tag_pointer_clear_tag_id(env, argument.into_pointer_value()); let target_loaded_type = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(layout), ); lookup_at_index_ptr( env, layout_interner, field_layouts, *index as usize, ptr, None, target_loaded_type, ) } UnionLayout::NullableUnwrapped { nullable_id, other_fields, } => { debug_assert!(argument.is_pointer_value()); debug_assert_ne!(*tag_id != 0, *nullable_id); let field_layouts = other_fields; let struct_layout = LayoutRepr::struct_(field_layouts); let struct_type = basic_type_from_layout(env, layout_interner, struct_layout); let target_loaded_type = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(layout), ); lookup_at_index_ptr( env, layout_interner, field_layouts, // the tag id is not stored *index as usize, argument.into_pointer_value(), Some(struct_type.into_struct_type()), target_loaded_type, ) } } } GetElementPointer { structure, indices, union_layout, .. } => { debug_assert!(indices.len() >= 2); let tag_id = indices[0]; let index = indices[1] as usize; // cast the argument bytes into the desired shape for this tag let argument = scope.load_symbol(structure); let ret_repr = layout_interner.get_repr(layout); let pointer_value = match union_layout { UnionLayout::NonRecursive(_) => unreachable!(), UnionLayout::Recursive(tag_layouts) => { debug_assert!(argument.is_pointer_value()); let field_layouts = tag_layouts[tag_id as usize]; let ptr = tag_pointer_clear_tag_id(env, argument.into_pointer_value()); let target_loaded_type = basic_type_from_layout(env, layout_interner, ret_repr); union_field_ptr_at_index( env, layout_interner, field_layouts, None, index, ptr, target_loaded_type, ) } UnionLayout::NonNullableUnwrapped(field_layouts) => { let struct_layout = LayoutRepr::struct_(field_layouts); let struct_type = basic_type_from_layout(env, layout_interner, struct_layout); let target_loaded_type = basic_type_from_layout(env, layout_interner, ret_repr); union_field_ptr_at_index( env, layout_interner, field_layouts, Some(struct_type.into_struct_type()), index, argument.into_pointer_value(), target_loaded_type, ) } UnionLayout::NullableWrapped { nullable_id, other_tags, } => { debug_assert!(argument.is_pointer_value()); debug_assert_ne!(tag_id as u16, *nullable_id); let tag_id = tag_id as u16; let tag_index = if tag_id < *nullable_id { tag_id } else { tag_id - 1 }; let field_layouts = other_tags[tag_index as usize]; let ptr = tag_pointer_clear_tag_id(env, argument.into_pointer_value()); let target_loaded_type = basic_type_from_layout(env, layout_interner, ret_repr); union_field_ptr_at_index( env, layout_interner, field_layouts, None, index, ptr, target_loaded_type, ) } UnionLayout::NullableUnwrapped { nullable_id, other_fields, } => { debug_assert!(argument.is_pointer_value()); debug_assert_ne!(tag_id != 0, *nullable_id); let field_layouts = other_fields; let struct_layout = LayoutRepr::struct_(field_layouts); let struct_type = basic_type_from_layout(env, layout_interner, struct_layout); let target_loaded_type = basic_type_from_layout(env, layout_interner, ret_repr); union_field_ptr_at_index( env, layout_interner, field_layouts, Some(struct_type.into_struct_type()), // the tag id is not stored index, argument.into_pointer_value(), target_loaded_type, ) } }; pointer_value.into() } GetTagId { structure, union_layout, } => { // cast the argument bytes into the desired shape for this tag let (argument, _structure_layout) = scope.load_symbol_and_layout(structure); get_tag_id(env, layout_interner, parent, union_layout, argument).into() } Alloca { initializer, element_layout, } => { let element_type = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(*element_layout), ); let ptr = entry_block_alloca_zerofill(env, element_type, "stack_value"); if let Some(initializer) = initializer { env.builder .new_build_store(ptr, scope.load_symbol(initializer)); } ptr.into() } } } fn build_wrapped_tag<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, scope: &Scope<'a, 'ctx>, union_layout: &UnionLayout<'a>, tag_id: u8, arguments: &[Symbol], tag_field_layouts: &[InLayout<'a>], tags: &[&[InLayout<'a>]], reuse_allocation: Option>, parent: FunctionValue<'ctx>, ) -> BasicValueEnum<'ctx> { let builder = env.builder; let tag_id_layout = union_layout.tag_id_layout(); let (field_types, field_values) = build_tag_fields(env, layout_interner, scope, tag_field_layouts, arguments); let union_struct_type = struct_type_from_union_layout(env, layout_interner, union_layout); // Create the struct_type let raw_data_ptr = allocate_tag( env, layout_interner, parent, reuse_allocation, union_layout, tags, ); let struct_type = env.context.struct_type(&field_types, false); if union_layout.stores_tag_id_as_data(env.target_info) { let tag_id_ptr = builder.new_build_struct_gep( union_struct_type, raw_data_ptr, RocUnion::TAG_ID_INDEX, "tag_id_index", ); let tag_id_type = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(tag_id_layout), ) .into_int_type(); env.builder .new_build_store(tag_id_ptr, tag_id_type.const_int(tag_id as u64, false)); let opaque_struct_ptr = builder.new_build_struct_gep( union_struct_type, raw_data_ptr, RocUnion::TAG_DATA_INDEX, "tag_data_index", ); struct_pointer_from_fields( env, layout_interner, struct_type, opaque_struct_ptr, field_values.into_iter().enumerate(), ); raw_data_ptr.into() } else { struct_pointer_from_fields( env, layout_interner, struct_type, raw_data_ptr, field_values.into_iter().enumerate(), ); tag_pointer_set_tag_id(env, tag_id, raw_data_ptr).into() } } pub fn entry_block_alloca_zerofill<'ctx>( env: &Env<'_, 'ctx, '_>, basic_type: BasicTypeEnum<'ctx>, name: &str, ) -> PointerValue<'ctx> { let parent = env .builder .get_insert_block() .unwrap() .get_parent() .unwrap(); create_entry_block_alloca(env, parent, basic_type, name) } fn build_tag_field_value<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, value: BasicValueEnum<'ctx>, tag_field_layout: InLayout<'a>, ) -> BasicValueEnum<'ctx> { if let LayoutRepr::RecursivePointer(_) = layout_interner.get_repr(tag_field_layout) { debug_assert!(value.is_pointer_value()); // we store recursive pointers as `i64*` env.builder .new_build_pointer_cast( value.into_pointer_value(), env.context.i64_type().ptr_type(AddressSpace::default()), "cast_recursive_pointer", ) .into() } else if layout_interner.is_passed_by_reference(tag_field_layout) { debug_assert!(value.is_pointer_value()); // NOTE: we rely on this being passed to `store_roc_value` so that // the value is memcpy'd value } else { // this check fails for recursive tag unions, but can be helpful while debugging // debug_assert_eq!(tag_field_layout, val_layout); value } } fn build_tag_fields<'a, 'r, 'ctx, 'env>( env: &'r Env<'a, 'ctx, 'env>, layout_interner: &'r STLayoutInterner<'a>, scope: &'r Scope<'a, 'ctx>, fields: &[InLayout<'a>], arguments: &[Symbol], ) -> ( std::vec::Vec>, std::vec::Vec<(InLayout<'a>, BasicValueEnum<'ctx>)>, ) { debug_assert_eq!(fields.len(), arguments.len()); let capacity = fields.len(); let mut field_types = std::vec::Vec::with_capacity(capacity); let mut field_values = std::vec::Vec::with_capacity(capacity); for (field_symbol, tag_field_layout) in arguments.iter().zip(fields.iter()) { let field_type = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(*tag_field_layout), ); field_types.push(field_type); let raw_value: BasicValueEnum<'ctx> = scope.load_symbol(field_symbol); let field_value = build_tag_field_value(env, layout_interner, raw_value, *tag_field_layout); field_values.push((*tag_field_layout, field_value)); } (field_types, field_values) } fn build_tag<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, scope: &Scope<'a, 'ctx>, union_layout: &UnionLayout<'a>, tag_id: TagIdIntType, arguments: &[Symbol], reuse_allocation: Option>, parent: FunctionValue<'ctx>, ) -> BasicValueEnum<'ctx> { let union_size = union_layout.number_of_tags(); match union_layout { UnionLayout::NonRecursive(tags) => { debug_assert!(union_size > 1); let data_layout_repr = LayoutRepr::Struct(tags[tag_id as usize]); let data = RocStruct::build(env, layout_interner, data_layout_repr, scope, arguments); let roc_union = RocUnion::tagged_from_slices(layout_interner, env.context, tags); let tag_alloca = env .builder .new_build_alloca(roc_union.struct_type(), "tag_alloca"); roc_union.write_struct_data( env, layout_interner, tag_alloca, data, data_layout_repr, Some(tag_id as _), ); tag_alloca.into() } UnionLayout::Recursive(tags) => { debug_assert!(union_size > 1); let tag_field_layouts = &tags[tag_id as usize]; build_wrapped_tag( env, layout_interner, scope, union_layout, tag_id as _, arguments, tag_field_layouts, tags, reuse_allocation, parent, ) } UnionLayout::NullableWrapped { nullable_id, other_tags: tags, } => { let tag_field_layouts = { use std::cmp::Ordering::*; match tag_id.cmp(&(*nullable_id as _)) { Equal => { let layout = LayoutRepr::Union(*union_layout); return basic_type_from_layout(env, layout_interner, layout) .into_pointer_type() .const_null() .into(); } Less => &tags[tag_id as usize], Greater => &tags[tag_id as usize - 1], } }; build_wrapped_tag( env, layout_interner, scope, union_layout, tag_id as _, arguments, tag_field_layouts, tags, reuse_allocation, parent, ) } UnionLayout::NonNullableUnwrapped(fields) => { debug_assert_eq!(union_size, 1); debug_assert_eq!(tag_id, 0); debug_assert_eq!(arguments.len(), fields.len()); let (field_types, field_values) = build_tag_fields(env, layout_interner, scope, fields, arguments); // Create the struct_type let data_ptr = reserve_with_refcount_union_as_block_of_memory( env, layout_interner, *union_layout, &[fields], ); let struct_type = env .context .struct_type(env.arena.alloc_slice_fill_iter(field_types), false); struct_pointer_from_fields( env, layout_interner, struct_type, data_ptr, field_values.into_iter().enumerate(), ); data_ptr.into() } UnionLayout::NullableUnwrapped { nullable_id, other_fields, } => { let roc_union = RocUnion::untagged_from_slices(layout_interner, env.context, &[other_fields]); if tag_id == *nullable_id as u16 { let output_type = roc_union.struct_type().ptr_type(AddressSpace::default()); return output_type.const_null().into(); } // this tag id is not the nullable one. For the type to be recursive, the other // constructor must have at least one argument! debug_assert!(!arguments.is_empty()); debug_assert!(union_size == 2); // Create the struct_type let data_ptr = allocate_tag( env, layout_interner, parent, reuse_allocation, union_layout, &[other_fields], ); let data_layout_repr = LayoutRepr::Struct(other_fields); let data = RocStruct::build(env, layout_interner, data_layout_repr, scope, arguments); roc_union.write_struct_data( env, layout_interner, data_ptr, data, data_layout_repr, None, ); data_ptr.into() } } } fn tag_pointer_set_tag_id<'ctx>( env: &Env<'_, 'ctx, '_>, tag_id: u8, pointer: PointerValue<'ctx>, ) -> PointerValue<'ctx> { // we only have 3 bits, so can encode only 0..7 (or on 32-bit targets, 2 bits to encode 0..3) debug_assert!((tag_id as u32) < env.target_info.ptr_width() as u32); let tag_id_intval = env.ptr_int().const_int(tag_id as u64, false); let cast_pointer = env.builder.new_build_pointer_cast( pointer, env.context.i8_type().ptr_type(AddressSpace::default()), "cast_to_i8_ptr", ); // NOTE: assumes the lower bits of `cast_pointer` are all 0 let indexed_pointer = unsafe { env.builder.new_build_in_bounds_gep( env.context.i8_type(), cast_pointer, &[tag_id_intval], "indexed_pointer", ) }; env.builder .new_build_pointer_cast(indexed_pointer, pointer.get_type(), "cast_from_i8_ptr") } pub fn tag_pointer_tag_id_bits_and_mask(target_info: TargetInfo) -> (u64, u64) { match target_info.ptr_width() { roc_target::PtrWidth::Bytes8 => (3, 0b0000_0111), roc_target::PtrWidth::Bytes4 => (2, 0b0000_0011), } } pub fn tag_pointer_read_tag_id<'ctx>( env: &Env<'_, 'ctx, '_>, pointer: PointerValue<'ctx>, ) -> IntValue<'ctx> { let (_, mask) = tag_pointer_tag_id_bits_and_mask(env.target_info); let ptr_int = env.ptr_int(); let as_int = env.builder.new_build_ptr_to_int(pointer, ptr_int, "to_int"); let mask_intval = env.ptr_int().const_int(mask, false); let masked = env.builder.new_build_and(as_int, mask_intval, "mask"); env.builder .new_build_int_cast_sign_flag(masked, env.context.i8_type(), false, "to_u8") } pub fn tag_pointer_clear_tag_id<'ctx>( env: &Env<'_, 'ctx, '_>, pointer: PointerValue<'ctx>, ) -> PointerValue<'ctx> { let (_, tag_id_bits_mask) = tag_pointer_tag_id_bits_and_mask(env.target_info); let as_int = env .builder .new_build_ptr_to_int(pointer, env.ptr_int(), "to_int"); let mask = env.ptr_int().const_int(tag_id_bits_mask, false); let current_tag_id = env.builder.new_build_and(as_int, mask, "masked"); let index = env.builder.new_build_int_neg(current_tag_id, "index"); let cast_pointer = env.builder.new_build_pointer_cast( pointer, env.context.i8_type().ptr_type(AddressSpace::default()), "cast_to_i8_ptr", ); let indexed_pointer = unsafe { env.builder.new_build_in_bounds_gep( env.context.i8_type(), cast_pointer, &[index], "new_ptr", ) }; env.builder .new_build_pointer_cast(indexed_pointer, pointer.get_type(), "cast_from_i8_ptr") } fn allocate_tag<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, parent: FunctionValue<'ctx>, reuse_allocation: Option>, union_layout: &UnionLayout<'a>, tags: &[&[InLayout<'a>]], ) -> PointerValue<'ctx> { match reuse_allocation { Some(ptr) => { // check if its a null pointer let is_null_ptr = env.builder.new_build_is_null(ptr, "is_null_ptr"); let ctx = env.context; let then_block = ctx.append_basic_block(parent, "then_allocate_fresh"); let else_block = ctx.append_basic_block(parent, "else_reuse"); let cont_block = ctx.append_basic_block(parent, "cont"); env.builder .new_build_conditional_branch(is_null_ptr, then_block, else_block); let raw_ptr = { env.builder.position_at_end(then_block); let raw_ptr = reserve_with_refcount_union_as_block_of_memory( env, layout_interner, *union_layout, tags, ); env.builder.new_build_unconditional_branch(cont_block); raw_ptr }; let reuse_ptr = { env.builder.position_at_end(else_block); let cleared = tag_pointer_clear_tag_id(env, ptr); env.builder.new_build_unconditional_branch(cont_block); cleared }; { env.builder.position_at_end(cont_block); let phi = env.builder.new_build_phi(raw_ptr.get_type(), "branch"); phi.add_incoming(&[(&raw_ptr, then_block), (&reuse_ptr, else_block)]); phi.as_basic_value().into_pointer_value() } } None => reserve_with_refcount_union_as_block_of_memory( env, layout_interner, *union_layout, tags, ), } } pub fn get_tag_id<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, parent: FunctionValue<'ctx>, union_layout: &UnionLayout<'a>, argument: BasicValueEnum<'ctx>, ) -> IntValue<'ctx> { let builder = env.builder; let tag_id_layout = union_layout.tag_id_layout(); let tag_id_int_type = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(tag_id_layout), ) .into_int_type(); match union_layout { UnionLayout::NonRecursive(_) => { debug_assert!(argument.is_pointer_value(), "{argument:?}"); let argument_ptr = argument.into_pointer_value(); get_tag_id_wrapped(env, layout_interner, *union_layout, argument_ptr) } UnionLayout::Recursive(_) => { let argument_ptr = argument.into_pointer_value(); if union_layout.stores_tag_id_as_data(env.target_info) { get_tag_id_wrapped(env, layout_interner, *union_layout, argument_ptr) } else { tag_pointer_read_tag_id(env, argument_ptr) } } UnionLayout::NonNullableUnwrapped(_) => tag_id_int_type.const_zero(), UnionLayout::NullableWrapped { nullable_id, .. } => { let argument_ptr = argument.into_pointer_value(); let is_null = env.builder.new_build_is_null(argument_ptr, "is_null"); let ctx = env.context; let then_block = ctx.append_basic_block(parent, "then"); let else_block = ctx.append_basic_block(parent, "else"); let cont_block = ctx.append_basic_block(parent, "cont"); let result = builder.new_build_alloca(tag_id_int_type, "result"); env.builder .new_build_conditional_branch(is_null, then_block, else_block); { env.builder.position_at_end(then_block); let tag_id = tag_id_int_type.const_int(*nullable_id as u64, false); env.builder.new_build_store(result, tag_id); env.builder.new_build_unconditional_branch(cont_block); } { env.builder.position_at_end(else_block); let tag_id = if union_layout.stores_tag_id_as_data(env.target_info) { get_tag_id_wrapped(env, layout_interner, *union_layout, argument_ptr) } else { tag_pointer_read_tag_id(env, argument_ptr) }; env.builder.new_build_store(result, tag_id); env.builder.new_build_unconditional_branch(cont_block); } env.builder.position_at_end(cont_block); env.builder .new_build_load(tag_id_int_type, result, "load_result") .into_int_value() } UnionLayout::NullableUnwrapped { nullable_id, .. } => { let argument_ptr = argument.into_pointer_value(); let is_null = env.builder.new_build_is_null(argument_ptr, "is_null"); let then_value = tag_id_int_type.const_int(*nullable_id as u64, false); let else_value = tag_id_int_type.const_int(!*nullable_id as u64, false); env.builder .new_build_select(is_null, then_value, else_value, "select_tag_id") .into_int_value() } } } fn lookup_at_index_ptr<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, field_layouts: &[InLayout<'a>], index: usize, value: PointerValue<'ctx>, struct_type: Option>, target_loaded_type: BasicTypeEnum<'ctx>, ) -> BasicValueEnum<'ctx> { let elem_ptr = union_field_ptr_at_index_help( env, layout_interner, field_layouts, struct_type, index, value, ); let field_layout = field_layouts[index]; let result = load_roc_value( env, layout_interner, layout_interner.get_repr(field_layout), elem_ptr, "load_at_index_ptr_old", ); // A recursive pointer in the loaded structure is stored as a `i64*`, but the loaded layout // might want a more precise structure. As such, cast it to the refined type if needed. cast_if_necessary_for_opaque_recursive_pointers(env.builder, result, target_loaded_type) } fn union_field_ptr_at_index_help<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, field_layouts: &'a [InLayout<'a>], opt_struct_type: Option>, index: usize, value: PointerValue<'ctx>, ) -> PointerValue<'ctx> { let builder = env.builder; let struct_type = match opt_struct_type { Some(st) => st, None => { let struct_layout = LayoutRepr::struct_(field_layouts); basic_type_from_layout(env, layout_interner, struct_layout).into_struct_type() } }; let data_ptr = env.builder.new_build_pointer_cast( value, struct_type.ptr_type(AddressSpace::default()), "cast_lookup_at_index_ptr", ); builder.new_build_struct_gep( struct_type, data_ptr, index as u32, "at_index_struct_gep_data", ) } fn union_field_ptr_at_index<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, field_layouts: &'a [InLayout<'a>], opt_struct_type: Option>, index: usize, value: PointerValue<'ctx>, target_loaded_type: BasicTypeEnum<'ctx>, ) -> PointerValue<'ctx> { let result = union_field_ptr_at_index_help( env, layout_interner, field_layouts, opt_struct_type, index, value, ); // A recursive pointer in the loaded structure is stored as a `i64*`, but the loaded layout // might want a more precise structure. As such, cast it to the refined type if needed. let from_value: BasicValueEnum = result.into(); let to_type: BasicTypeEnum = target_loaded_type; cast_if_necessary_for_opaque_recursive_pointers(env.builder, from_value, to_type) .into_pointer_value() } pub fn reserve_with_refcount<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, layout: InLayout<'a>, ) -> PointerValue<'ctx> { let stack_size = layout_interner.stack_size(layout); let alignment_bytes = layout_interner.alignment_bytes(layout); let basic_type = basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout)); reserve_with_refcount_help(env, basic_type, stack_size, alignment_bytes) } fn reserve_with_refcount_union_as_block_of_memory<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, union_layout: UnionLayout<'a>, fields: &[&[InLayout<'a>]], ) -> PointerValue<'ctx> { let ptr_bytes = env.target_info; let roc_union = if union_layout.stores_tag_id_as_data(ptr_bytes) { RocUnion::tagged_from_slices(layout_interner, env.context, fields) } else { RocUnion::untagged_from_slices(layout_interner, env.context, fields) }; reserve_with_refcount_help( env, roc_union.struct_type(), roc_union.tag_width(), roc_union.tag_alignment(), ) } fn reserve_with_refcount_help<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, basic_type: impl BasicType<'ctx>, stack_size: u32, alignment_bytes: u32, ) -> PointerValue<'ctx> { let len_type = env.ptr_int(); let value_bytes_intvalue = len_type.const_int(stack_size as u64, false); allocate_with_refcount_help(env, basic_type, alignment_bytes, value_bytes_intvalue) } pub fn allocate_with_refcount<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, layout: InLayout<'a>, value: BasicValueEnum<'ctx>, ) -> PointerValue<'ctx> { let data_ptr = reserve_with_refcount(env, layout_interner, layout); // store the value in the pointer env.builder.new_build_store(data_ptr, value); data_ptr } pub fn allocate_with_refcount_help<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, value_type: impl BasicType<'ctx>, alignment_bytes: u32, number_of_data_bytes: IntValue<'ctx>, ) -> PointerValue<'ctx> { let ptr = call_bitcode_fn( env, &[ number_of_data_bytes.into(), env.alignment_const(alignment_bytes).into(), ], roc_builtins::bitcode::UTILS_ALLOCATE_WITH_REFCOUNT, ) .into_pointer_value(); let ptr_type = value_type.ptr_type(AddressSpace::default()); env.builder .new_build_pointer_cast(ptr, ptr_type, "alloc_cast_to_desired") } fn list_literal<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, parent: FunctionValue<'ctx>, scope: &Scope<'a, 'ctx>, element_layout: InLayout<'a>, elems: &[ListLiteralElement], ) -> BasicValueEnum<'ctx> { let ctx = env.context; let builder = env.builder; let element_type = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(element_layout), ); let list_length = elems.len(); let list_length_intval = env.ptr_int().const_int(list_length as _, false); // TODO re-enable, currently causes morphic segfaults because it tries to update // constants in-place... // if element_type.is_int_type() { if false { let element_type = element_type.into_int_type(); let element_width = layout_interner.stack_size(element_layout); let size = list_length * element_width as usize; let alignment = layout_interner .alignment_bytes(element_layout) .max(env.target_info.ptr_width() as u32); let mut is_all_constant = true; let zero_elements = (env.target_info.ptr_width() as u8 as f64 / element_width as f64).ceil() as usize; // runtime-evaluated elements let mut runtime_evaluated_elements = Vec::with_capacity_in(list_length, env.arena); // set up a global that contains all the literal elements of the array // any variables or expressions are represented as `undef` let global = { let mut global_elements = Vec::with_capacity_in(list_length, env.arena); // Add zero bytes that represent the refcount // // - if all elements are const, then we store the whole list as a constant. // It then needs a refcount before the first element. // - but if the list is not all constants, then we will just copy the constant values, // and we do not need that refcount at the start // // In the latter case, we won't store the zeros in the globals // (we slice them off again below) for _ in 0..zero_elements { global_elements.push(element_type.const_zero()); } // Copy the elements from the list literal into the array for (index, element) in elems.iter().enumerate() { match element { ListLiteralElement::Literal(literal) => { let val = build_exp_literal( env, layout_interner, parent, element_layout, literal, ); global_elements.push(val.into_int_value()); } ListLiteralElement::Symbol(symbol) => { let val = scope.load_symbol(symbol); // here we'd like to furthermore check for intval.is_const(). // if all elements are const for LLVM, we could make the array a constant. // BUT morphic does not know about this, and could allow us to modify that // array in-place. That would cause a segfault. So, we'll have to find // constants ourselves and cannot lean on LLVM here. is_all_constant = false; runtime_evaluated_elements.push((index, val)); global_elements.push(element_type.get_undef()); } }; } let const_elements = if is_all_constant { global_elements.into_bump_slice() } else { &global_elements[zero_elements..] }; // use None for the address space (e.g. Const does not work) let typ = element_type.array_type(const_elements.len() as u32); let global = env.module.add_global(typ, None, "roc__list_literal"); global.set_constant(true); global.set_alignment(alignment); global.set_unnamed_addr(true); global.set_linkage(inkwell::module::Linkage::Private); global.set_initializer(&element_type.const_array(const_elements)); global.as_pointer_value() }; if is_all_constant { // all elements are constants, so we can use the memory in the constants section directly // here we make a pointer to the first actual element (skipping the 0 bytes that // represent the refcount) let zero = env.ptr_int().const_zero(); let offset = env.ptr_int().const_int(zero_elements as _, false); let ptr = unsafe { env.builder.new_build_in_bounds_gep( element_type, global, &[zero, offset], "first_element_pointer", ) }; super::build_list::store_list(env, ptr, list_length_intval).into() } else { // some of our elements are non-constant, so we must allocate space on the heap let ptr = allocate_list(env, layout_interner, element_layout, list_length_intval); // then, copy the relevant segment from the constant section into the heap env.builder .build_memcpy( ptr, alignment, global, alignment, env.ptr_int().const_int(size as _, false), ) .unwrap(); // then replace the `undef`s with the values that we evaluate at runtime for (index, val) in runtime_evaluated_elements { let index_val = ctx.i64_type().const_int(index as u64, false); let elem_ptr = unsafe { builder.new_build_in_bounds_gep(element_type, ptr, &[index_val], "index") }; builder.new_build_store(elem_ptr, val); } super::build_list::store_list(env, ptr, list_length_intval).into() } } else { let ptr = allocate_list(env, layout_interner, element_layout, list_length_intval); // Copy the elements from the list literal into the array for (index, element) in elems.iter().enumerate() { let val = match element { ListLiteralElement::Literal(literal) => { build_exp_literal(env, layout_interner, parent, element_layout, literal) } ListLiteralElement::Symbol(symbol) => scope.load_symbol(symbol), }; let index_val = ctx.i64_type().const_int(index as u64, false); let elem_ptr = unsafe { builder.new_build_in_bounds_gep(element_type, ptr, &[index_val], "index") }; store_roc_value( env, layout_interner, layout_interner.get_repr(element_layout), elem_ptr, val, ); } super::build_list::store_list(env, ptr, list_length_intval).into() } } pub fn load_roc_value<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, layout: LayoutRepr<'a>, source: PointerValue<'ctx>, name: &str, ) -> BasicValueEnum<'ctx> { let basic_type = basic_type_from_layout(env, layout_interner, layout); if layout.is_passed_by_reference(layout_interner) { let alloca = entry_block_alloca_zerofill(env, basic_type, name); store_roc_value(env, layout_interner, layout, alloca, source.into()); alloca.into() } else { env.builder.new_build_load(basic_type, source, name) } } pub fn use_roc_value<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, layout: LayoutRepr<'a>, source: BasicValueEnum<'ctx>, name: &str, ) -> BasicValueEnum<'ctx> { if layout.is_passed_by_reference(layout_interner) { let alloca = entry_block_alloca_zerofill( env, basic_type_from_layout(env, layout_interner, layout), name, ); env.builder.new_build_store(alloca, source); alloca.into() } else { source } } pub fn store_roc_value_opaque<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, layout: InLayout<'a>, opaque_destination: PointerValue<'ctx>, value: BasicValueEnum<'ctx>, ) { let target_type = basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout)) .ptr_type(AddressSpace::default()); let destination = env.builder.new_build_pointer_cast( opaque_destination, target_type, "store_roc_value_opaque", ); store_roc_value( env, layout_interner, layout_interner.get_repr(layout), destination, value, ) } pub fn store_roc_value<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, layout: LayoutRepr<'a>, destination: PointerValue<'ctx>, value: BasicValueEnum<'ctx>, ) { if layout.is_passed_by_reference(layout_interner) { debug_assert!(value.is_pointer_value()); build_memcpy( env, layout_interner, layout, destination, value.into_pointer_value(), ); } else { env.builder.new_build_store(destination, value); } } pub(crate) fn build_exp_stmt<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, func_spec_solutions: &FuncSpecSolutions, scope: &mut Scope<'a, 'ctx>, parent: FunctionValue<'ctx>, stmt: &roc_mono::ir::Stmt<'a>, ) -> BasicValueEnum<'ctx> { use roc_mono::ir::Stmt::*; match stmt { Let(first_symbol, first_expr, first_layout, mut cont) => { let mut queue = Vec::new_in(env.arena); queue.push((first_symbol, first_expr, first_layout)); while let Let(symbol, expr, layout, new_cont) = cont { queue.push((symbol, expr, layout)); cont = new_cont; } let mut stack = Vec::with_capacity_in(queue.len(), env.arena); for (symbol, expr, layout) in queue { debug_assert!(!matches!( layout_interner.get_repr(*layout), LayoutRepr::RecursivePointer(_) )); let val = build_exp_expr( env, layout_interner, layout_ids, func_spec_solutions, scope, parent, *layout, expr, ); // Make a new scope which includes the binding we just encountered. // This should be done *after* compiling the bound expr, since any // recursive (in the LetRec sense) bindings should already have // been extracted as procedures. Nothing in here should need to // access itself! // scope = scope.clone(); scope.insert(*symbol, *layout, val); stack.push(*symbol); } let result = build_exp_stmt( env, layout_interner, layout_ids, func_spec_solutions, scope, parent, cont, ); for symbol in stack { scope.remove(&symbol); } result } Ret(symbol) => { let (value, layout) = scope.load_symbol_and_layout(symbol); build_return( env, layout_interner, layout_interner.get_repr(layout), value, parent, ); env.context.i8_type().const_zero().into() } Switch { branches, default_branch, ret_layout, cond_layout, cond_symbol, } => { let ret_type = basic_type_from_layout(env, layout_interner, layout_interner.get_repr(*ret_layout)); let switch_args = SwitchArgsIr { cond_layout: *cond_layout, cond_symbol: *cond_symbol, branches, default_branch: default_branch.1, ret_type, }; build_switch_ir( env, layout_interner, layout_ids, func_spec_solutions, scope, parent, switch_args, ) } Join { id, parameters, remainder, body: continuation, } => { let builder = env.builder; let context = env.context; // create new block let cont_block = context.append_basic_block(parent, "joinpointcont"); let mut joinpoint_args = std::vec::Vec::with_capacity(parameters.len()); { let current = builder.get_insert_block().unwrap(); builder.position_at_end(cont_block); for param in parameters.iter() { let basic_type = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(param.layout), ); let phi_type = if layout_interner.is_passed_by_reference(param.layout) { basic_type.ptr_type(AddressSpace::default()).into() } else { basic_type }; let phi_node = env.builder.new_build_phi(phi_type, "joinpointarg"); joinpoint_args.push(phi_node); } builder.position_at_end(current); } // store this join point scope.insert_join_point(*id, cont_block, joinpoint_args); // construct the blocks that may jump to this join point build_exp_stmt( env, layout_interner, layout_ids, func_spec_solutions, scope, parent, remainder, ); let phi_block = builder.get_insert_block().unwrap(); // put the cont block at the back builder.position_at_end(cont_block); // bind the values scope .bind_parameters_to_join_point(*id, parameters.iter()) .expect("join point not found, but it was inserted above"); // put the continuation in let result = build_exp_stmt( env, layout_interner, layout_ids, func_spec_solutions, scope, parent, continuation, ); // remove this join point again scope.remove_join_point(*id); cont_block.move_after(phi_block).unwrap(); result } Jump(join_point, arguments) => { let builder = env.builder; let context = env.context; let (cont_block, argument_phi_values) = scope.get_join_point(*join_point).unwrap(); let current_block = builder.get_insert_block().unwrap(); for (phi_value, argument) in argument_phi_values.iter().zip(arguments.iter()) { let (value, _) = scope.load_symbol_and_layout(argument); phi_value.add_incoming(&[(&value, current_block)]); } builder.new_build_unconditional_branch(*cont_block); // This doesn't currently do anything context.i64_type().const_zero().into() } Refcounting(modify, cont) => { use ModifyRc::*; match modify { Inc(symbol, inc_amount) => { let (value, layout) = scope.load_symbol_and_layout(symbol); if layout_interner.contains_refcounted(layout) { increment_refcount_layout( env, layout_interner, layout_ids, *inc_amount, value, layout, ); } build_exp_stmt( env, layout_interner, layout_ids, func_spec_solutions, scope, parent, cont, ) } Dec(symbol) => { let (value, layout) = scope.load_symbol_and_layout(symbol); if layout_interner.contains_refcounted(layout) { decrement_refcount_layout(env, layout_interner, layout_ids, value, layout); } build_exp_stmt( env, layout_interner, layout_ids, func_spec_solutions, scope, parent, cont, ) } DecRef(symbol) => { let (value, layout) = scope.load_symbol_and_layout(symbol); match layout_interner.runtime_representation(layout) { LayoutRepr::Builtin(Builtin::Str) => todo!(), LayoutRepr::Builtin(Builtin::List(element_layout)) => { debug_assert!(value.is_struct_value()); let element_layout = layout_interner.get_repr(element_layout); let alignment = element_layout.alignment_bytes(layout_interner); build_list::decref(env, value.into_struct_value(), alignment); } other_layout if other_layout.is_refcounted(layout_interner) => { if value.is_pointer_value() { let clear_tag_id = match other_layout { LayoutRepr::Union(union_layout) => { union_layout.stores_tag_id_in_pointer(env.target_info) } _ => false, }; let value_ptr = if clear_tag_id { tag_pointer_clear_tag_id(env, value.into_pointer_value()) } else { value.into_pointer_value() }; let then_block = env.context.append_basic_block(parent, "then"); let done_block = env.context.append_basic_block(parent, "done"); let condition = env .builder .new_build_is_not_null(value_ptr, "box_is_not_null"); env.builder.new_build_conditional_branch( condition, then_block, done_block, ); { env.builder.position_at_end(then_block); let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, value_ptr); refcount_ptr.decrement( env, layout_interner, layout_interner.get_repr(layout), ); env.builder.new_build_unconditional_branch(done_block); } env.builder.position_at_end(done_block); } else { eprint!("we're likely leaking memory; see issue #985 for details"); } } _ => { // nothing to do } } build_exp_stmt( env, layout_interner, layout_ids, func_spec_solutions, scope, parent, cont, ) } Free(symbol) => { // unconditionally deallocate the symbol let (value, layout) = scope.load_symbol_and_layout(symbol); let alignment = layout_interner.allocation_alignment_bytes(layout); debug_assert!(value.is_pointer_value()); let value = value.into_pointer_value(); let clear_tag_id = match layout_interner.runtime_representation(layout) { LayoutRepr::Union(union) => union.stores_tag_id_in_pointer(env.target_info), _ => false, }; let ptr = if clear_tag_id { tag_pointer_clear_tag_id(env, value) } else { value }; let rc_ptr = PointerToRefcount::from_ptr_to_data(env, ptr); rc_ptr.deallocate(env, alignment); build_exp_stmt( env, layout_interner, layout_ids, func_spec_solutions, scope, parent, cont, ) } } } Dbg { source_location, source, symbol, variable: _, remainder, } => { if env.mode.runs_expects() { let location = build_string_literal(env, parent, source_location); let source = build_string_literal(env, parent, source); let message = scope.load_symbol(symbol); env.call_dbg(env, location, source, message); } build_exp_stmt( env, layout_interner, layout_ids, func_spec_solutions, scope, parent, remainder, ) } Expect { condition: cond_symbol, region, lookups, variables, remainder, } => { let bd = env.builder; let context = env.context; let (cond, _cond_layout) = scope.load_symbol_and_layout(cond_symbol); let condition = bd.new_build_int_compare( IntPredicate::EQ, cond.into_int_value(), context.bool_type().const_int(1, false), "is_true", ); let then_block = context.append_basic_block(parent, "then_block"); let throw_block = context.append_basic_block(parent, "throw_block"); bd.new_build_conditional_branch(condition, then_block, throw_block); if env.mode.runs_expects() { bd.position_at_end(throw_block); match env.target_info.ptr_width() { roc_target::PtrWidth::Bytes8 => { let shared_memory = SharedMemoryPointer::get(env); clone_to_shared_memory( env, layout_interner, scope, layout_ids, &shared_memory, *cond_symbol, *region, lookups, variables, ); if let LlvmBackendMode::BinaryDev = env.mode { crate::llvm::expect::notify_parent_expect(env, &shared_memory); } bd.new_build_unconditional_branch(then_block); } roc_target::PtrWidth::Bytes4 => { // temporary WASM implementation throw_internal_exception(env, parent, "An expectation failed!"); } } } else { bd.position_at_end(throw_block); bd.new_build_unconditional_branch(then_block); } bd.position_at_end(then_block); build_exp_stmt( env, layout_interner, layout_ids, func_spec_solutions, scope, parent, remainder, ) } ExpectFx { condition: cond_symbol, region, lookups, variables, remainder, } => { let bd = env.builder; let context = env.context; let (cond, _cond_layout) = scope.load_symbol_and_layout(cond_symbol); let condition = bd.new_build_int_compare( IntPredicate::EQ, cond.into_int_value(), context.bool_type().const_int(1, false), "is_true", ); let then_block = context.append_basic_block(parent, "then_block"); let throw_block = context.append_basic_block(parent, "throw_block"); bd.new_build_conditional_branch(condition, then_block, throw_block); if env.mode.runs_expects() { bd.position_at_end(throw_block); match env.target_info.ptr_width() { roc_target::PtrWidth::Bytes8 => { let shared_memory = SharedMemoryPointer::get(env); clone_to_shared_memory( env, layout_interner, scope, layout_ids, &shared_memory, *cond_symbol, *region, lookups, variables, ); bd.new_build_unconditional_branch(then_block); } roc_target::PtrWidth::Bytes4 => { // temporary WASM implementation throw_internal_exception(env, parent, "An expectation failed!"); } } } else { bd.position_at_end(throw_block); bd.new_build_unconditional_branch(then_block); } bd.position_at_end(then_block); build_exp_stmt( env, layout_interner, layout_ids, func_spec_solutions, scope, parent, remainder, ) } Crash(sym, tag) => { throw_exception(env, scope, sym, *tag); // unused value (must return a BasicValue) let zero = env.context.i64_type().const_zero(); zero.into() } } } fn build_return<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, layout: LayoutRepr<'a>, value: BasicValueEnum<'ctx>, parent: FunctionValue<'ctx>, ) { match RocReturn::from_layout(layout_interner, layout) { RocReturn::Return => { if let Some(block) = env.builder.get_insert_block() { if block.get_terminator().is_none() { env.builder.new_build_return(Some(&value)); } } } RocReturn::ByPointer => { // we need to write our value into the final argument of the current function let parameters = parent.get_params(); let out_parameter = parameters.last().unwrap(); debug_assert!(out_parameter.is_pointer_value()); let destination = out_parameter.into_pointer_value(); if layout.is_passed_by_reference(layout_interner) { debug_assert!( value.is_pointer_value(), "{:?}: {:?}\n{:?}", parent.get_name(), value, layout ); // What we want to do here is // // let value_ptr = value.into_pointer_value(); // if value_ptr.get_first_use().is_some() { // value_ptr.replace_all_uses_with(destination); // // In other words, if the source pointer is used, // then we just subsitute the source for the input pointer, done. // // Only that does not work if the source is not written to. // A simple example is the identity function // // A slightly more complex case that will also make the above not // work is when the source pointer is only incremented, but not // written to. Then there is a first_use, but it's still invalid to // subsitute source with destination // // Hence, we explicitly memcpy source to destination, and rely on // LLVM optimizing away any inefficiencies. build_memcpy( env, layout_interner, layout, destination, value.into_pointer_value(), ); } else { env.builder.new_build_store(destination, value); } if let Some(block) = env.builder.get_insert_block() { match block.get_terminator() { None => { env.builder.new_build_return(None); } Some(terminator) => { terminator.remove_from_basic_block(); env.builder.new_build_return(None); } } } } } } fn equivalent_type_constructors(t1: &BasicTypeEnum, t2: &BasicTypeEnum) -> bool { use BasicTypeEnum::*; match (t1, t2) { (ArrayType(_), ArrayType(_)) => true, (ArrayType(_), _) => false, (FloatType(_), FloatType(_)) => true, (FloatType(_), _) => false, (IntType(_), IntType(_)) => true, (IntType(_), _) => false, (PointerType(_), PointerType(_)) => true, (PointerType(_), _) => false, (StructType(_), StructType(_)) => true, (StructType(_), _) => false, (VectorType(_), VectorType(_)) => true, (VectorType(_), _) => false, } } /// Cast a value to another value of the same size, but only if their types are not equivalent. /// This is needed to allow us to interoperate between recursive pointers in unions that are /// opaque, and well-typed. /// /// This will no longer be necessary and should be removed after we employ opaque pointers from /// LLVM. pub fn cast_if_necessary_for_opaque_recursive_pointers<'ctx>( builder: &Builder<'ctx>, from_value: BasicValueEnum<'ctx>, to_type: BasicTypeEnum<'ctx>, ) -> BasicValueEnum<'ctx> { if from_value.get_type() != to_type // Only perform the cast if the target types are transmutable. && equivalent_type_constructors(&from_value.get_type(), &to_type) { complex_bitcast( builder, from_value, to_type, "bitcast_for_opaque_recursive_pointer", ) } else { from_value } } /// Cast a value to another value of the same (or smaller?) size pub fn cast_basic_basic<'ctx>( builder: &Builder<'ctx>, from_value: BasicValueEnum<'ctx>, to_type: BasicTypeEnum<'ctx>, ) -> BasicValueEnum<'ctx> { complex_bitcast(builder, from_value, to_type, "cast_basic_basic") } pub fn complex_bitcast_struct_struct<'ctx>( builder: &Builder<'ctx>, from_value: StructValue<'ctx>, to_type: StructType<'ctx>, name: &str, ) -> StructValue<'ctx> { complex_bitcast(builder, from_value.into(), to_type.into(), name).into_struct_value() } pub fn cast_block_of_memory_to_tag<'ctx>( builder: &Builder<'ctx>, from_value: StructValue<'ctx>, to_type: BasicTypeEnum<'ctx>, ) -> StructValue<'ctx> { complex_bitcast( builder, from_value.into(), to_type, "block_of_memory_to_tag", ) .into_struct_value() } /// Cast a value to another value of the same (or smaller?) size pub fn complex_bitcast<'ctx>( builder: &Builder<'ctx>, from_value: BasicValueEnum<'ctx>, to_type: BasicTypeEnum<'ctx>, name: &str, ) -> BasicValueEnum<'ctx> { use BasicTypeEnum::*; if let (PointerType(_), PointerType(_)) = (from_value.get_type(), to_type) { // we can't use the more straightforward bitcast in all cases // it seems like a bitcast only works on integers and pointers // and crucially does not work not on arrays return builder .new_build_pointer_cast( from_value.into_pointer_value(), to_type.into_pointer_type(), name, ) .into(); } complex_bitcast_from_bigger_than_to(builder, from_value, to_type, name) } /// Check the size of the input and output types. Pretending we have more bytes at a pointer than /// we actually do can lead to faulty optimizations and weird segfaults/crashes pub fn complex_bitcast_check_size<'ctx>( env: &Env<'_, 'ctx, '_>, from_value: BasicValueEnum<'ctx>, to_type: BasicTypeEnum<'ctx>, name: &str, ) -> BasicValueEnum<'ctx> { use BasicTypeEnum::*; if let (PointerType(_), PointerType(_)) = (from_value.get_type(), to_type) { // we can't use the more straightforward bitcast in all cases // it seems like a bitcast only works on integers and pointers // and crucially does not work not on arrays return env .builder .new_build_pointer_cast( from_value.into_pointer_value(), to_type.into_pointer_type(), name, ) .into(); } let block = env.builder.get_insert_block().expect("to be in a function"); let parent = block.get_parent().expect("to be in a function"); let then_block = env.context.append_basic_block(parent, "then"); let else_block = env.context.append_basic_block(parent, "else"); let cont_block = env.context.append_basic_block(parent, "cont"); let from_size = from_value.get_type().size_of().unwrap(); let to_size = to_type.size_of().unwrap(); let condition = env.builder.new_build_int_compare( IntPredicate::UGT, from_size, to_size, "from_size >= to_size", ); env.builder .new_build_conditional_branch(condition, then_block, else_block); let then_answer = { env.builder.position_at_end(then_block); let result = complex_bitcast_from_bigger_than_to(env.builder, from_value, to_type, name); env.builder.new_build_unconditional_branch(cont_block); result }; let else_answer = { env.builder.position_at_end(else_block); let result = complex_bitcast_to_bigger_than_from(env.builder, from_value, to_type, name); env.builder.new_build_unconditional_branch(cont_block); result }; env.builder.position_at_end(cont_block); let result = env.builder.new_build_phi(then_answer.get_type(), "answer"); result.add_incoming(&[(&then_answer, then_block), (&else_answer, else_block)]); result.as_basic_value() } fn complex_bitcast_from_bigger_than_to<'ctx>( builder: &Builder<'ctx>, from_value: BasicValueEnum<'ctx>, to_type: BasicTypeEnum<'ctx>, name: &str, ) -> BasicValueEnum<'ctx> { // store the value in memory let argument_pointer = builder.new_build_alloca(from_value.get_type(), "cast_alloca"); builder.new_build_store(argument_pointer, from_value); // then read it back as a different type let to_type_pointer = builder.new_build_pointer_cast( argument_pointer, to_type.ptr_type(inkwell::AddressSpace::default()), name, ); builder.new_build_load(to_type, to_type_pointer, "cast_value") } fn complex_bitcast_to_bigger_than_from<'ctx>( builder: &Builder<'ctx>, from_value: BasicValueEnum<'ctx>, to_type: BasicTypeEnum<'ctx>, name: &str, ) -> BasicValueEnum<'ctx> { // reserve space in memory with the return type. This way, if the return type is bigger // than the input type, we don't access invalid memory when later taking a pointer to // the cast value let storage = builder.new_build_alloca(to_type, "cast_alloca"); // then cast the pointer to our desired type let from_type_pointer = builder.new_build_pointer_cast( storage, from_value .get_type() .ptr_type(inkwell::AddressSpace::default()), name, ); // store the value in memory builder.new_build_store(from_type_pointer, from_value); // then read it back as a different type builder.new_build_load(to_type, storage, "cast_value") } /// get the tag id out of a pointer to a wrapped (i.e. stores the tag id at runtime) layout fn get_tag_id_wrapped<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, union_layout: UnionLayout<'a>, from_value: PointerValue<'ctx>, ) -> IntValue<'ctx> { let union_struct_type = struct_type_from_union_layout(env, layout_interner, &union_layout); let tag_id_type = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(union_layout.tag_id_layout()), ); let tag_id_ptr = env.builder.new_build_struct_gep( union_struct_type, from_value, RocUnion::TAG_ID_INDEX, "tag_id_ptr", ); env.builder .new_build_load(tag_id_type, tag_id_ptr, "load_tag_id") .into_int_value() } pub fn get_tag_id_non_recursive<'ctx>( env: &Env<'_, 'ctx, '_>, tag: StructValue<'ctx>, ) -> IntValue<'ctx> { env.builder .build_extract_value(tag, RocUnion::TAG_ID_INDEX, "get_tag_id") .unwrap() .into_int_value() } struct SwitchArgsIr<'a, 'ctx> { pub cond_symbol: Symbol, pub cond_layout: InLayout<'a>, pub branches: &'a [(u64, BranchInfo<'a>, roc_mono::ir::Stmt<'a>)], pub default_branch: &'a roc_mono::ir::Stmt<'a>, pub ret_type: BasicTypeEnum<'ctx>, } fn const_i128<'ctx>(env: &Env<'_, 'ctx, '_>, value: i128) -> IntValue<'ctx> { // truncate the lower 64 bits let value = value as u128; let a = value as u64; // get the upper 64 bits let b = (value >> 64) as u64; env.context .i128_type() .const_int_arbitrary_precision(&[a, b]) } fn const_u128<'ctx>(env: &Env<'_, 'ctx, '_>, value: u128) -> IntValue<'ctx> { // truncate the lower 64 bits let value = value; let a = value as u64; // get the upper 64 bits let b = (value >> 64) as u64; env.context .i128_type() .const_int_arbitrary_precision(&[a, b]) } fn build_switch_ir<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, func_spec_solutions: &FuncSpecSolutions, scope: &Scope<'a, 'ctx>, parent: FunctionValue<'ctx>, switch_args: SwitchArgsIr<'a, 'ctx>, ) -> BasicValueEnum<'ctx> { let arena = env.arena; let builder = env.builder; let context = env.context; let SwitchArgsIr { branches, cond_symbol, mut cond_layout, default_branch, ret_type, .. } = switch_args; let mut copy = scope.clone(); let scope = &mut copy; let cond_symbol = &cond_symbol; let (cond_value, stored_layout) = scope.load_symbol_and_layout(cond_symbol); debug_assert_eq!( basic_type_from_layout(env, layout_interner, layout_interner.get_repr(cond_layout)), basic_type_from_layout( env, layout_interner, layout_interner.get_repr(stored_layout) ), "This switch matches on {cond_layout:?}, but the matched-on symbol {cond_symbol:?} has layout {stored_layout:?}" ); let cont_block = context.append_basic_block(parent, "cont"); // Build the condition let cond = match layout_interner.get_repr(cond_layout) { LayoutRepr::Builtin(Builtin::Float(float_width)) => { // float matches are done on the bit pattern cond_layout = Layout::float_width(float_width); let int_type = match float_width { FloatWidth::F32 => env.context.i32_type(), FloatWidth::F64 => env.context.i64_type(), }; builder .new_build_bitcast(cond_value, int_type, "") .into_int_value() } LayoutRepr::Union(variant) => { cond_layout = variant.tag_id_layout(); get_tag_id(env, layout_interner, parent, &variant, cond_value) } LayoutRepr::Builtin(_) => cond_value.into_int_value(), other => todo!("Build switch value from layout: {:?}", other), }; // Build the cases let mut incoming = Vec::with_capacity_in(branches.len(), arena); if let LayoutRepr::Builtin(Builtin::Bool) = layout_interner.get_repr(cond_layout) { match (branches, default_branch) { ([(0, _, false_branch)], true_branch) | ([(1, _, true_branch)], false_branch) => { let then_block = context.append_basic_block(parent, "then_block"); let else_block = context.append_basic_block(parent, "else_block"); builder.new_build_conditional_branch(cond, then_block, else_block); { builder.position_at_end(then_block); let branch_val = build_exp_stmt( env, layout_interner, layout_ids, func_spec_solutions, scope, parent, true_branch, ); if then_block.get_terminator().is_none() { builder.new_build_unconditional_branch(cont_block); incoming.push((branch_val, then_block)); } } { builder.position_at_end(else_block); let branch_val = build_exp_stmt( env, layout_interner, layout_ids, func_spec_solutions, scope, parent, false_branch, ); if else_block.get_terminator().is_none() { builder.new_build_unconditional_branch(cont_block); incoming.push((branch_val, else_block)); } } } _ => { unreachable!() } } } else { let default_block = context.append_basic_block(parent, "default"); let mut cases = Vec::with_capacity_in(branches.len(), arena); for (int, _, _) in branches.iter() { // Switch constants must all be same type as switch value! // e.g. this is incorrect, and will trigger a LLVM warning: // // switch i8 %apple1, label %default [ // i64 2, label %branch2 // i64 0, label %branch0 // i64 1, label %branch1 // ] // // they either need to all be i8, or i64 let condition_int_type = cond.get_type(); let int_val = if condition_int_type == context.i128_type() { const_i128(env, *int as i128) } else { condition_int_type.const_int(*int, false) }; let block = context.append_basic_block(parent, format!("branch{int}").as_str()); cases.push((int_val, block)); } builder.new_build_switch(cond, default_block, &cases); for ((_, _, branch_expr), (_, block)) in branches.iter().zip(cases) { builder.position_at_end(block); let branch_val = build_exp_stmt( env, layout_interner, layout_ids, func_spec_solutions, scope, parent, branch_expr, ); if block.get_terminator().is_none() { builder.new_build_unconditional_branch(cont_block); incoming.push((branch_val, block)); } } // The block for the conditional's default branch. builder.position_at_end(default_block); let default_val = build_exp_stmt( env, layout_interner, layout_ids, func_spec_solutions, scope, parent, default_branch, ); if default_block.get_terminator().is_none() { builder.new_build_unconditional_branch(cont_block); incoming.push((default_val, default_block)); } } // emit merge block if incoming.is_empty() { unsafe { cont_block.delete().unwrap(); } // produce unused garbage value context.i64_type().const_zero().into() } else { builder.position_at_end(cont_block); let phi = builder.new_build_phi(ret_type, "branch"); for (branch_val, block) in incoming { phi.add_incoming(&[(&Into::::into(branch_val), block)]); } phi.as_basic_value() } } /// Creates a new stack allocation instruction in the entry block of the function. pub fn create_entry_block_alloca<'ctx>( env: &Env<'_, 'ctx, '_>, parent: FunctionValue<'_>, basic_type: BasicTypeEnum<'ctx>, name: &str, ) -> PointerValue<'ctx> { let builder = env.context.create_builder(); let entry = parent.get_first_basic_block().unwrap(); match entry.get_first_instruction() { Some(first_instr) => builder.position_before(&first_instr), None => builder.position_at_end(entry), } builder.new_build_alloca(basic_type, name) } fn expose_function_to_host<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, symbol: Symbol, roc_function: FunctionValue<'ctx>, arguments: &'a [InLayout<'a>], niche: Niche<'a>, return_layout: InLayout<'a>, layout_ids: &mut LayoutIds<'a>, ) { let ident_string = symbol.as_str(&env.interns); let proc_layout = ProcLayout { arguments, result: return_layout, niche, }; let c_function_name: String = layout_ids .get_toplevel(symbol, &proc_layout) .to_exposed_symbol_string(symbol, &env.interns); expose_function_to_host_help_c_abi( env, layout_interner, ident_string, roc_function, arguments, return_layout, &c_function_name, ); } fn expose_function_to_host_help_c_abi_generic<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, roc_function: FunctionValue<'ctx>, arguments: &[InLayout<'a>], return_layout: InLayout<'a>, c_function_name: &str, ) -> FunctionValue<'ctx> { // NOTE we ingore env.mode here let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena); for layout in arguments { cc_argument_types.push(to_cc_type(env, layout_interner, *layout)); } // STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}` // let mut argument_types = roc_function.get_type().get_param_types(); let mut argument_types = cc_argument_types; 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); } Some(return_type) => { let output_type = return_type.ptr_type(AddressSpace::default()); argument_types.insert(0, output_type.into()); } } // 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_spec, Linkage::External, ); let subprogram = env.new_subprogram(c_function_name); c_function.set_subprogram(subprogram); // STEP 2: build the exposed function's body let builder = env.builder; let context = env.context; let entry = context.append_basic_block(c_function, "entry"); builder.position_at_end(entry); debug_info_init!(env, c_function); // drop the first argument, which is the pointer we write the result into let args_vector = c_function.get_params(); let mut args = args_vector.as_slice(); // drop the output parameter args = &args[1..]; let mut arguments_for_call = Vec::with_capacity_in(args.len(), env.arena); let it = args.iter().zip(roc_function.get_type().get_param_types()); for (arg, fastcc_type) in it { let arg_type = arg.get_type(); if arg_type == fastcc_type { // the C and Fast calling conventions agree arguments_for_call.push(*arg); } else { // not pretty, but seems to cover all our current cases if arg_type.is_pointer_type() && !fastcc_type.is_pointer_type() { // bitcast the ptr let fastcc_ptr = env.builder.new_build_pointer_cast( arg.into_pointer_value(), fastcc_type.ptr_type(AddressSpace::default()), "bitcast_arg", ); let loaded = env .builder .new_build_load(fastcc_type, fastcc_ptr, "load_arg"); arguments_for_call.push(loaded); } else { let as_cc_type = env.builder.new_build_pointer_cast( arg.into_pointer_value(), fastcc_type.into_pointer_type(), "to_cc_type_ptr", ); arguments_for_call.push(as_cc_type.into()); } } } let arguments_for_call = &arguments_for_call.into_bump_slice(); let call_result = if env.mode.returns_roc_result() { debug_assert_eq!(args.len(), roc_function.get_params().len()); let roc_wrapper_function = make_exception_catcher(env, layout_interner, roc_function, return_layout); debug_assert_eq!( arguments_for_call.len(), roc_wrapper_function.get_params().len() ); builder.position_at_end(entry); let wrapped_layout = roc_call_result_layout(env.arena, return_layout); call_direct_roc_function( env, layout_interner, roc_function, wrapped_layout, arguments_for_call, ) } else { call_direct_roc_function( env, layout_interner, roc_function, layout_interner.get_repr(return_layout), arguments_for_call, ) }; let output_arg_index = 0; let output_arg = c_function .get_nth_param(output_arg_index as u32) .unwrap() .into_pointer_value(); store_roc_value( env, layout_interner, layout_interner.get_repr(return_layout), output_arg, call_result, ); builder.new_build_return(None); c_function } fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, ident_string: &str, roc_function: FunctionValue<'ctx>, arguments: &[InLayout<'a>], return_layout: InLayout<'a>, c_function_name: &str, ) -> FunctionValue<'ctx> { // a tagged union to indicate to the test loader that a panic occurred. // especially when running 32-bit binaries on a 64-bit machine, there // does not seem to be a smarter solution let wrapper_return_type = roc_call_result_type( env, basic_type_from_layout( env, layout_interner, layout_interner.get_repr(return_layout), ), ); let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena); for layout in arguments { cc_argument_types.push(to_cc_type(env, layout_interner, *layout)); } // STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}` if the C abi demands it let mut argument_types = cc_argument_types; let return_type = wrapper_return_type; let c_function_spec = { let output_type = return_type.ptr_type(AddressSpace::default()); argument_types.push(output_type.into()); FunctionSpec::cconv(env, CCReturn::Void, None, &argument_types) }; let c_function = add_func( env.context, env.module, c_function_name, c_function_spec, Linkage::External, ); let subprogram = env.new_subprogram(c_function_name); c_function.set_subprogram(subprogram); // STEP 2: build the exposed function's body let builder = env.builder; let context = env.context; let entry = context.append_basic_block(c_function, "entry"); builder.position_at_end(entry); debug_info_init!(env, c_function); // drop the final argument, which is the pointer we write the result into let args_vector = c_function.get_params(); let mut args = args_vector.as_slice(); let args_length = args.len(); args = &args[..args.len() - 1]; let mut arguments_for_call = Vec::with_capacity_in(args.len(), env.arena); let it = args .iter() .zip(roc_function.get_type().get_param_types()) .zip(arguments); for ((arg, fastcc_type), layout) in it { let arg_type = arg.get_type(); if arg_type == fastcc_type { // the C and Fast calling conventions agree arguments_for_call.push(*arg); } else { match layout_interner.get_repr(*layout) { repr @ LayoutRepr::Builtin(Builtin::List(_)) => { let list_type = basic_type_from_layout(env, layout_interner, repr); let loaded = env.builder.new_build_load( list_type, arg.into_pointer_value(), "load_list_pointer", ); let cast = complex_bitcast_check_size(env, loaded, fastcc_type, "to_fastcc_type_1"); arguments_for_call.push(cast); } _ => { let cast = complex_bitcast_check_size(env, *arg, fastcc_type, "to_fastcc_type_1"); arguments_for_call.push(cast); } } } } let arguments_for_call = &arguments_for_call.into_bump_slice(); let (call_result, call_result_layout) = { let last_block = builder.get_insert_block().unwrap(); let roc_wrapper_function = make_exception_catcher(env, layout_interner, roc_function, return_layout); builder.position_at_end(last_block); let wrapper_result = roc_call_result_layout(env.arena, return_layout); let roc_value = call_direct_roc_function( env, layout_interner, roc_wrapper_function, wrapper_result, arguments_for_call, ); (roc_value, wrapper_result) }; let output_arg_index = args_length - 1; let output_arg = c_function .get_nth_param(output_arg_index as u32) .unwrap() .into_pointer_value(); store_roc_value( env, layout_interner, call_result_layout, output_arg, call_result, ); builder.new_build_return(None); // STEP 3: build a {} -> u64 function that gives the size of the return type 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__{ident_string}_size"); let size_function = add_func( env.context, env.module, size_function_name.as_str(), size_function_spec, Linkage::External, ); let subprogram = env.new_subprogram(&size_function_name); size_function.set_subprogram(subprogram); let entry = context.append_basic_block(size_function, "entry"); builder.position_at_end(entry); debug_info_init!(env, size_function); let size: BasicValueEnum = return_type.size_of().unwrap().into(); builder.new_build_return(Some(&size)); c_function } fn expose_function_to_host_help_c_abi_v2<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, roc_function: FunctionValue<'ctx>, arguments: &[InLayout<'a>], return_layout: InLayout<'a>, c_function_name: &str, ) -> FunctionValue<'ctx> { let it = arguments .iter() .map(|l| to_cc_type(env, layout_interner, *l)); let argument_types = Vec::from_iter_in(it, env.arena); let return_type = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(return_layout), ); let cc_return = to_cc_return(env, layout_interner, return_layout); let roc_return = RocReturn::from_layout(layout_interner, layout_interner.get_repr(return_layout)); 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_spec, Linkage::External, ); let c_abi_roc_str_type = env.context.struct_type( &[ env.context .i8_type() .ptr_type(AddressSpace::default()) .into(), env.ptr_int().into(), env.ptr_int().into(), ], false, ); // a temporary solution to be able to pass RocStr by-value from a host language. { let extra = match cc_return { CCReturn::Return => 0, CCReturn::ByPointer => 1, CCReturn::Void => 0, }; for (i, layout) in arguments.iter().enumerate() { if let LayoutRepr::Builtin(Builtin::Str) = layout_interner.get_repr(*layout) { // Indicate to LLVM that this argument is semantically passed by-value // even though technically (because of its size) it is passed by-reference let byval_attribute_id = Attribute::get_named_enum_kind_id("byval"); debug_assert!(byval_attribute_id > 0); // if ret_typ is a pointer type. We need the base type here. let ret_typ = c_function.get_type().get_param_types()[i + extra]; let ret_base_typ = if ret_typ.is_pointer_type() { c_abi_roc_str_type.as_any_type_enum() } else { ret_typ.as_any_type_enum() }; let byval_attribute = env .context .create_type_attribute(byval_attribute_id, ret_base_typ); c_function.add_attribute(AttributeLoc::Param((i + extra) as u32), byval_attribute); } } } let subprogram = env.new_subprogram(c_function_name); c_function.set_subprogram(subprogram); // STEP 2: build the exposed function's body let builder = env.builder; let context = env.context; let entry = context.append_basic_block(c_function, "entry"); builder.position_at_end(entry); let params = c_function.get_params(); let param_types = Vec::from_iter_in(roc_function.get_type().get_param_types(), env.arena); let (params, param_types) = match (&roc_return, &cc_return) { // Drop the "return pointer" if it exists on the roc function // and the c function does not return via pointer (RocReturn::ByPointer, CCReturn::Return) => { // Roc currently puts the return pointer at the end of the argument list. // As such, we drop the last element here instead of the first. ( ¶ms[..], ¶m_types[..param_types.len().saturating_sub(1)], ) } // Drop the return pointer the other way, if the C function returns by pointer but Roc // doesn't (RocReturn::Return, CCReturn::ByPointer) => (¶ms[1..], ¶m_types[..]), (RocReturn::ByPointer, CCReturn::ByPointer) => { // Both return by pointer but Roc puts it at the end and C puts it at the beginning ( ¶ms[1..], ¶m_types[..param_types.len().saturating_sub(1)], ) } (RocReturn::Return, CCReturn::Void) => { // the roc function returns a unit value. like `{}` or `{ { {}, {} }, {} }`. // In C, this is modelled as a function returning void (¶ms[..], ¶m_types[..]) } (RocReturn::ByPointer, CCReturn::Void) => { // the roc function returns a unit value. like `{}` or `{ { {}, {} }, {} }`. // In C, this is modelled as a function returning void ( ¶ms[..], ¶m_types[..param_types.len().saturating_sub(1)], ) } _ => (¶ms[..], ¶m_types[..]), }; debug_assert_eq!( params.len(), param_types.len(), "when exposing a function to the host, params.len() was {}, but param_types.len() was {}", params.len(), param_types.len() ); let it = params .iter() .zip(param_types) .zip(arguments) .enumerate() .map(|(i, ((arg, fastcc_type), layout))| { let arg_type = arg.get_type(); if arg_type == *fastcc_type { // the C and Fast calling conventions agree *arg } else { // not pretty, but seems to cover all our current cases if arg_type.is_pointer_type() && !fastcc_type.is_pointer_type() { // On x86_*, Modify the argument to specify it is passed by value and nonnull // Aarch*, just passes in the pointer directly. if matches!( env.target_info.architecture, roc_target::Architecture::X86_32 | roc_target::Architecture::X86_64 ) { let c_abi_type = match layout_interner.get_repr(*layout) { LayoutRepr::Builtin(Builtin::Str | Builtin::List(_)) => { c_abi_roc_str_type } _ => todo!("figure out what the C type is"), }; let byval = context.create_type_attribute( Attribute::get_named_enum_kind_id("byval"), c_abi_type.as_any_type_enum(), ); let nonnull = context.create_type_attribute( Attribute::get_named_enum_kind_id("nonnull"), c_abi_type.as_any_type_enum(), ); // C return pointer goes at the beginning of params, and we must skip it if it exists. let returns_pointer = matches!(cc_return, CCReturn::ByPointer); let param_index = i as u32 + returns_pointer as u32; c_function.add_attribute(AttributeLoc::Param(param_index), byval); c_function.add_attribute(AttributeLoc::Param(param_index), nonnull); } // bitcast the ptr let fastcc_ptr = env.builder.new_build_pointer_cast( arg.into_pointer_value(), fastcc_type.ptr_type(AddressSpace::default()), "bitcast_arg", ); env.builder .new_build_load(*fastcc_type, fastcc_ptr, "load_arg") } else { complex_bitcast_check_size(env, *arg, *fastcc_type, "to_fastcc_type_2") } } }); let arguments = Vec::from_iter_in(it, env.arena); let value = call_direct_roc_function( env, layout_interner, roc_function, layout_interner.get_repr(return_layout), arguments.as_slice(), ); match cc_return { CCReturn::Return => match roc_return { RocReturn::Return => { env.builder.new_build_return(Some(&value)); } RocReturn::ByPointer => { let loaded = env.builder.new_build_load( return_type, value.into_pointer_value(), "load_result", ); env.builder.new_build_return(Some(&loaded)); } }, CCReturn::ByPointer => { let out_ptr = c_function.get_nth_param(0).unwrap().into_pointer_value(); match roc_return { RocReturn::Return => { env.builder.new_build_store(out_ptr, value); } RocReturn::ByPointer => { // TODO: ideally, in this case, we should pass the C return pointer directly // into the call_roc_function rather than forcing an extra alloca, load, and // store! let value = env.builder.new_build_load( return_type, value.into_pointer_value(), "load_roc_result", ); env.builder.new_build_store(out_ptr, value); } } env.builder.new_build_return(None); } CCReturn::Void => { env.builder.new_build_return(None); } } c_function } fn expose_function_to_host_help_c_abi<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, ident_string: &str, roc_function: FunctionValue<'ctx>, arguments: &[InLayout<'a>], return_layout: InLayout<'a>, c_function_name: &str, ) -> FunctionValue<'ctx> { match env.mode { LlvmBackendMode::GenTest | LlvmBackendMode::WasmGenTest | LlvmBackendMode::CliTest => { return expose_function_to_host_help_c_abi_gen_test( env, layout_interner, ident_string, roc_function, arguments, return_layout, c_function_name, ) } LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev | LlvmBackendMode::BinaryGlue => {} } // a generic version that writes the result into a passed *u8 pointer expose_function_to_host_help_c_abi_generic( env, layout_interner, roc_function, arguments, return_layout, &format!("{c_function_name}_generic"), ); let c_function = expose_function_to_host_help_c_abi_v2( env, layout_interner, roc_function, arguments, return_layout, c_function_name, ); // STEP 3: build a {} -> u64 function that gives the size of the return type let size_function_spec = FunctionSpec::cconv( env, CCReturn::Return, Some(env.context.i64_type().as_basic_type_enum()), &[], ); let size_function_name: String = format!("{c_function_name}_size"); let size_function = add_func( env.context, env.module, size_function_name.as_str(), size_function_spec, Linkage::External, ); let subprogram = env.new_subprogram(&size_function_name); size_function.set_subprogram(subprogram); let entry = env.context.append_basic_block(size_function, "entry"); env.builder.position_at_end(entry); debug_info_init!(env, size_function); let return_type = match env.mode { LlvmBackendMode::GenTest | LlvmBackendMode::WasmGenTest | LlvmBackendMode::CliTest => { roc_call_result_type(env, roc_function.get_type().get_return_type().unwrap()).into() } LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev | LlvmBackendMode::BinaryGlue => { basic_type_from_layout( env, layout_interner, layout_interner.get_repr(return_layout), ) } }; let size: BasicValueEnum = return_type.size_of().unwrap().into(); env.builder.new_build_return(Some(&size)); c_function } pub fn get_sjlj_buffer<'ctx>(env: &Env<'_, 'ctx, '_>) -> PointerValue<'ctx> { // The size of jump_buf is target-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, None => env.module.add_global(type_, None, "roc_sjlj_buffer"), }; global.set_initializer(&type_.const_zero()); env.builder.new_build_pointer_cast( global.as_pointer_value(), env.context.i32_type().ptr_type(AddressSpace::default()), "cast_sjlj_buffer", ) } pub fn build_setjmp_call<'ctx>(env: &Env<'_, 'ctx, '_>) -> BasicValueEnum<'ctx> { let jmp_buf = get_sjlj_buffer(env); if cfg!(target_arch = "aarch64") { // Due to https://github.com/roc-lang/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 buf_type = env .context .i8_type() .ptr_type(AddressSpace::default()) .array_type(5); let jmp_buf_i8p_arr = env.builder.new_build_pointer_cast( jmp_buf, buf_type.ptr_type(AddressSpace::default()), "jmp_buf [5 x i8*]", ); // 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.new_build_in_bounds_gep( buf_type, jmp_buf_i8p_arr, &[zero, fa_index], "frame address index", ) }; env.builder.new_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.new_build_in_bounds_gep( buf_type, jmp_buf_i8p_arr, &[zero, ss_index], "name", ) }; let stack_save = env.call_intrinsic(LLVM_STACK_SAVE, &[]); env.builder.new_build_store(ss, stack_save); let jmp_buf_i8p = env .builder .new_build_pointer_cast( jmp_buf, env.context.i8_type().ptr_type(AddressSpace::default()), "jmp_buf i8*", ) .into(); env.call_intrinsic(LLVM_SETJMP, &[jmp_buf_i8p]) } } /// Pointer to RocStr which is the panic message. pub fn get_panic_msg_ptr<'ctx>(env: &Env<'_, 'ctx, '_>) -> PointerValue<'ctx> { let str_typ = zig_str_type(env); let global_name = "roc_panic_msg_str"; let global = env.module.get_global(global_name).unwrap_or_else(|| { let global = env.module.add_global(str_typ, None, global_name); global.set_initializer(&str_typ.const_zero()); global }); global.as_pointer_value() } /// Pointer to the panic tag. /// Only non-zero values must be written into here. pub fn get_panic_tag_ptr<'ctx>(env: &Env<'_, 'ctx, '_>) -> PointerValue<'ctx> { let i64_typ = env.context.i64_type(); let global_name = "roc_panic_msg_tag"; let global = env.module.get_global(global_name).unwrap_or_else(|| { let global = env.module.add_global(i64_typ, None, global_name); global.set_initializer(&i64_typ.const_zero()); global }); global.as_pointer_value() } fn set_jump_and_catch_long_jump<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, parent: FunctionValue<'ctx>, // The roc function to call roc_function: FunctionValue<'ctx>, roc_arguments: &[BasicValueEnum<'ctx>], roc_return_layout: InLayout<'a>, ) -> BasicValueEnum<'ctx> { let context = env.context; let builder = env.builder; let return_type = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(roc_return_layout), ); let call_result_return_conv = { let layout = roc_call_result_layout(env.arena, roc_return_layout); RocReturn::from_layout(layout_interner, layout) }; let call_result_type = roc_call_result_type(env, return_type.as_basic_type_enum()); let result_alloca = builder.new_build_alloca(call_result_type, "result"); let then_block = context.append_basic_block(parent, "then_block"); let catch_block = context.append_basic_block(parent, "catch_block"); let cont_block = context.append_basic_block(parent, "cont_block"); let panicked_u32 = build_setjmp_call(env); let panicked_bool = env.builder.new_build_int_compare( IntPredicate::NE, panicked_u32.into_int_value(), panicked_u32.get_type().into_int_type().const_zero(), "to_bool", ); env.builder .new_build_conditional_branch(panicked_bool, catch_block, then_block); // all went well { builder.position_at_end(then_block); let call_result = call_direct_roc_function( env, layout_interner, roc_function, layout_interner.get_repr(roc_return_layout), roc_arguments, ); let return_value = make_good_roc_result(env, layout_interner, roc_return_layout, call_result); builder.new_build_store(result_alloca, return_value); env.builder.new_build_unconditional_branch(cont_block); } // something went wrong { builder.position_at_end(catch_block); // RocStr* global let error_msg_ptr = get_panic_msg_ptr(env); // i64* global let error_tag_ptr = get_panic_tag_ptr(env); let return_value = { let v1 = call_result_type.const_zero(); // tag must be non-zero, indicating failure let tag = builder.new_build_load(env.context.i64_type(), error_tag_ptr, "load_panic_tag"); let v2 = builder.build_insert_value(v1, tag, 0, "set_error").unwrap(); let v3 = builder .build_insert_value(v2, error_msg_ptr, 1, "set_exception") .unwrap(); v3 }; builder.new_build_store(result_alloca, return_value); env.builder.new_build_unconditional_branch(cont_block); } env.builder.position_at_end(cont_block); match call_result_return_conv { RocReturn::Return => builder.new_build_load( call_result_type, result_alloca, "set_jump_and_catch_long_jump_load_result", ), RocReturn::ByPointer => result_alloca.into(), } } fn make_exception_catcher<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, roc_function: FunctionValue<'ctx>, return_layout: InLayout<'a>, ) -> FunctionValue<'ctx> { let wrapper_function_name = format!("{}_catcher", roc_function.get_name().to_str().unwrap()); let function_value = make_exception_catching_wrapper( env, layout_interner, roc_function, return_layout, &wrapper_function_name, ); function_value.set_linkage(Linkage::Internal); function_value } fn roc_call_result_layout<'a>(arena: &'a Bump, return_layout: InLayout<'a>) -> LayoutRepr<'a> { let elements = [Layout::U64, Layout::STR_PTR, return_layout]; LayoutRepr::struct_(arena.alloc(elements)) } // TODO: coalesce with `roc_call_result_layout`? fn roc_call_result_type<'ctx>( env: &Env<'_, 'ctx, '_>, return_type: BasicTypeEnum<'ctx>, ) -> StructType<'ctx> { env.context.struct_type( &[ env.context.i64_type().into(), zig_str_type(env).ptr_type(AddressSpace::default()).into(), return_type, ], false, ) } fn make_good_roc_result<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, return_layout: InLayout<'a>, return_value: BasicValueEnum<'ctx>, ) -> BasicValueEnum<'ctx> { let context = env.context; let builder = env.builder; let return_type = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(return_layout), ); let v1 = roc_call_result_type( env, basic_type_from_layout( env, layout_interner, layout_interner.get_repr(return_layout), ), ) .const_zero(); let v2 = builder .build_insert_value(v1, context.i64_type().const_zero(), 0, "set_no_error") .unwrap(); let v3 = if layout_interner.is_passed_by_reference(return_layout) { let loaded = env.builder.new_build_load( return_type, return_value.into_pointer_value(), "load_call_result_passed_by_ptr", ); builder .build_insert_value(v2, loaded, 2, "set_call_result") .unwrap() } else { builder .build_insert_value(v2, return_value, 2, "set_call_result") .unwrap() }; v3.into_struct_value().into() } fn make_exception_catching_wrapper<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, roc_function: FunctionValue<'ctx>, return_layout: InLayout<'a>, wrapper_function_name: &str, ) -> FunctionValue<'ctx> { // build the C calling convention wrapper let context = env.context; let builder = env.builder; // TODO: pass these, and the roc function, in directly? let wrapper_return_layout = roc_call_result_layout(env.arena, return_layout); let wrapper_return_type = roc_call_result_type( env, basic_type_from_layout( env, layout_interner, layout_interner.get_repr(return_layout), ), ); let roc_function_type = roc_function.get_type(); let argument_types = match RocReturn::from_layout(layout_interner, layout_interner.get_repr(return_layout)) { RocReturn::Return => roc_function_type.get_param_types(), RocReturn::ByPointer => { // Our fastcc passes the return pointer as the last parameter. Remove it from the // argument types used for the wrapper, since the wrapper's return type will go here // when we build the wrapper function spec below. let mut types = roc_function_type.get_param_types(); types.pop(); types } }; let wrapper_return_conv = RocReturn::from_layout(layout_interner, wrapper_return_layout); let wrapper_function_spec = FunctionSpec::fastcc( env, wrapper_return_conv, wrapper_return_type.into(), Vec::from_iter_in(argument_types, env.arena), ); // Add main to the module. let wrapper_function = add_func( env.context, env.module, wrapper_function_name, wrapper_function_spec, Linkage::External, ); let subprogram = env.new_subprogram(wrapper_function_name); wrapper_function.set_subprogram(subprogram); // The exposed main function must adhere to the C calling convention, but the wrapper can still be fastcc. wrapper_function.set_call_conventions(FAST_CALL_CONV); // invoke instead of call, so that we can catch any exceptions thrown in Roc code let roc_function_arguments = { let mut params = wrapper_function.get_params(); match wrapper_return_conv { RocReturn::Return => { /* passthrough */ } RocReturn::ByPointer => { params.pop(); } } params }; let basic_block = context.append_basic_block(wrapper_function, "entry"); builder.position_at_end(basic_block); debug_info_init!(env, wrapper_function); let wrapper_return_result = set_jump_and_catch_long_jump( env, layout_interner, wrapper_function, roc_function, &roc_function_arguments, return_layout, ); build_return( env, layout_interner, wrapper_return_layout, wrapper_return_result, wrapper_function, ); wrapper_function } pub(crate) fn build_proc_headers<'a, 'r, 'ctx>( env: &'r Env<'a, 'ctx, '_>, layout_interner: &'r STLayoutInterner<'a>, mod_solutions: &'a ModSolutions, procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>, scope: &mut Scope<'a, 'ctx>, layout_ids: &mut LayoutIds<'a>, // alias_analysis_solutions: AliasAnalysisSolutions, ) -> std::vec::Vec<( roc_mono::ir::Proc<'a>, std::vec::Vec<(&'a FuncSpecSolutions, FunctionValue<'ctx>)>, )> { // Populate Procs further and get the low-level Expr from the canonical Expr let mut headers = std::vec::Vec::with_capacity(procedures.len()); for ((symbol, layout), proc) in procedures { let name_bytes = roc_alias_analysis::func_name_bytes(&proc); let func_name = FuncName(&name_bytes); let func_solutions = mod_solutions.func_solutions(func_name).unwrap(); let it = func_solutions.specs(); let mut function_values = std::vec::Vec::with_capacity(it.size_hint().0); let is_erased = proc.is_erased; debug_assert!(!is_erased || func_solutions.specs().count() == 1); for specialization in it { let func_spec = if is_erased { FuncBorrowSpec::Erased } else { FuncBorrowSpec::Some(*specialization) }; let fn_val = build_proc_header(env, layout_interner, func_spec, symbol, &proc, layout_ids); if proc.args.is_empty() { // this is a 0-argument thunk, i.e. a top-level constant definition // it must be in-scope everywhere in the module! scope.insert_top_level_thunk(symbol, layout, fn_val); } let func_spec_solutions = func_solutions.spec(specialization).unwrap(); function_values.push((func_spec_solutions, fn_val)); } headers.push((proc, function_values)); } headers } pub fn build_procedures<'a>( env: &Env<'a, '_, '_>, layout_interner: &STLayoutInterner<'a>, opt_level: OptLevel, procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>, host_exposed_lambda_sets: HostExposedLambdaSets<'a>, entry_point: EntryPoint<'a>, debug_output_file: Option<&Path>, glue_layouts: &GlueLayouts<'a>, ) { let mod_solutions = build_procedures_help( env, layout_interner, opt_level, procedures, host_exposed_lambda_sets, entry_point, debug_output_file, ); let niche = Niche::NONE; for (symbol, top_level) in glue_layouts.getters.iter().copied() { let it = top_level.arguments.iter().copied(); let bytes = roc_alias_analysis::func_name_bytes_help(symbol, it, niche, top_level.result); let func_name = FuncName(&bytes); let func_solutions = mod_solutions.func_solutions(func_name).unwrap(); let mut it = func_solutions.specs(); let Some(func_spec) = it.next() else { // TODO this means a function was not considered host-exposed in mono continue; }; debug_assert!( it.next().is_none(), "we expect only one specialization of this symbol" ); // NOTE fake layout; it is only used for debug prints let getter_fn = function_value_by_func_spec(env, FuncBorrowSpec::Some(*func_spec), symbol); let name = getter_fn.get_name().to_str().unwrap(); let getter_name = symbol.as_str(&env.interns); // Add the getter function to the module. let _ = expose_function_to_host_help_c_abi( env, layout_interner, name, getter_fn, top_level.arguments, top_level.result, getter_name, ); } } pub fn build_wasm_test_wrapper<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, opt_level: OptLevel, procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>, entry_point: SingleEntryPoint<'a>, ) -> (&'static str, FunctionValue<'ctx>) { let mod_solutions = build_procedures_help( env, layout_interner, opt_level, procedures, vec![], EntryPoint::Single(entry_point), Some(&std::env::temp_dir().join("test.ll")), ); promote_to_wasm_test_wrapper( env, layout_interner, mod_solutions, entry_point.symbol, entry_point.layout, ) } pub fn build_procedures_return_main<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, opt_level: OptLevel, procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>, host_exposed_lambda_sets: HostExposedLambdaSets<'a>, entry_point: SingleEntryPoint<'a>, ) -> (&'static str, FunctionValue<'ctx>) { let mod_solutions = build_procedures_help( env, layout_interner, opt_level, procedures, host_exposed_lambda_sets, EntryPoint::Single(entry_point), Some(&std::env::temp_dir().join("test.ll")), ); promote_to_main_function( env, layout_interner, mod_solutions, entry_point.symbol, entry_point.layout, ) } pub fn build_procedures_expose_expects<'a>( env: &Env<'a, '_, '_>, layout_interner: &STLayoutInterner<'a>, opt_level: OptLevel, expects: &'a [Symbol], procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>, ) -> Vec<'a, &'a str> { let entry_point = EntryPoint::Expects { symbols: expects }; let mod_solutions = build_procedures_help( env, layout_interner, opt_level, procedures, vec![], entry_point, Some(&std::env::temp_dir().join("test.ll")), ); let captures_niche = Niche::NONE; let top_level = ProcLayout { arguments: &[], result: Layout::UNIT, niche: captures_niche, }; let mut expect_names = Vec::with_capacity_in(expects.len(), env.arena); for symbol in expects.iter().copied() { let it = top_level.arguments.iter().copied(); let bytes = roc_alias_analysis::func_name_bytes_help(symbol, it, captures_niche, top_level.result); let func_name = FuncName(&bytes); let func_solutions = mod_solutions.func_solutions(func_name).unwrap(); let mut it = func_solutions.specs(); let func_spec = match it.next() { Some(spec) => spec, None => panic!("no specialization for expect {symbol}"), }; debug_assert!( it.next().is_none(), "we expect only one specialization of this symbol" ); // NOTE fake layout; it is only used for debug prints let roc_main_fn = function_value_by_func_spec(env, FuncBorrowSpec::Some(*func_spec), symbol); let name = roc_main_fn.get_name().to_str().unwrap(); let expect_name = &format!("Expect_{name}"); let expect_name = env.arena.alloc_str(expect_name); expect_names.push(&*expect_name); // Add main to the module. let _ = expose_function_to_host_help_c_abi( env, layout_interner, name, roc_main_fn, top_level.arguments, top_level.result, &format!("Expect_{name}"), ); } expect_names } fn build_procedures_help<'a>( env: &Env<'a, '_, '_>, layout_interner: &STLayoutInterner<'a>, opt_level: OptLevel, procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>, host_exposed_lambda_sets: HostExposedLambdaSets<'a>, entry_point: EntryPoint<'a>, debug_output_file: Option<&Path>, ) -> &'a ModSolutions { let mut layout_ids = roc_mono::layout::LayoutIds::default(); let mut scope = Scope::default(); let it1 = procedures.iter().map(|x| x.1); let it2 = host_exposed_lambda_sets.iter().map(|(_, _, hels)| hels); let solutions = match roc_alias_analysis::spec_program( env.arena, layout_interner, opt_level, entry_point, it1, it2, ) { Err(e) => panic!("Error in alias analysis: {e}"), Ok(solutions) => solutions, }; let solutions = env.arena.alloc(solutions); let mod_solutions = solutions .mod_solutions(roc_alias_analysis::MOD_APP) .unwrap(); // Add all the Proc headers to the module. // We have to do this in a separate pass first, // because their bodies may reference each other. let headers = build_proc_headers( env, layout_interner, mod_solutions, procedures, &mut scope, &mut layout_ids, ); let (_, function_pass) = construct_optimization_passes(env.module, opt_level); for (proc, fn_vals) in headers { for (func_spec_solutions, fn_val) in fn_vals { let mut current_scope = scope.clone(); // only have top-level thunks for this proc's module in scope // this retain is not needed for correctness, but will cause less confusion when debugging let home = proc.name.name().module_id(); current_scope.retain_top_level_thunks_for_module(home); build_proc( env, layout_interner, &mut layout_ids, func_spec_solutions, scope.clone(), &proc, fn_val, ); // call finalize() before any code generation/verification env.dibuilder.finalize(); if fn_val.verify(true) { function_pass.run_on(&fn_val); } else { let mode = "NON-OPTIMIZED"; eprintln!( "\n\nFunction {:?} failed LLVM verification in {} build. Its content was:\n", fn_val.get_name().to_str().unwrap(), mode, ); fn_val.print_to_stderr(); if let Some(app_ll_file) = debug_output_file { env.module.print_to_file(app_ll_file).unwrap(); panic!( r"😱 LLVM errors when defining function {:?}; I wrote the full LLVM IR to {:?}", fn_val.get_name().to_str().unwrap(), app_ll_file, ); } else { env.module.print_to_stderr(); panic!( "The preceding code was from {:?}, which failed LLVM verification in {} build.", fn_val.get_name().to_str().unwrap(), mode, ) } } } } use LlvmBackendMode::*; match env.mode { GenTest | WasmGenTest | CliTest => { /* no host, or exposing types is not supported */ } Binary | BinaryDev | BinaryGlue => { for (proc_name, alias_name, hels) in host_exposed_lambda_sets.iter() { let ident_string = proc_name.name().as_str(&env.interns); let fn_name: String = format!("{}_{}", ident_string, hels.id.0); expose_alias_to_host( env, layout_interner, mod_solutions, &fn_name, *alias_name, hels, ) } } } mod_solutions } pub enum FuncBorrowSpec { /// This function has an specialization due to alias analysis. Some(FuncSpec), /// This function does not have a specialization due to alias analysis, /// because it is type-erased, and thus has no statically determined AA specialization. Erased, } fn func_spec_name<'a>( arena: &'a Bump, interns: &Interns, symbol: Symbol, func_spec: FuncBorrowSpec, ) -> bumpalo::collections::String<'a> { use std::fmt::Write; let mut buf = bumpalo::collections::String::with_capacity_in(1, arena); let ident_string = symbol.as_str(interns); let module_string = interns.module_ids.get_name(symbol.module_id()).unwrap(); write!(buf, "{module_string}_{ident_string}_").unwrap(); match func_spec { FuncBorrowSpec::Some(func_spec) => { for byte in func_spec.0.iter() { write!(buf, "{byte:x?}").unwrap(); } } FuncBorrowSpec::Erased => write!(buf, "erased").unwrap(), } buf } fn build_proc_header<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, func_spec: FuncBorrowSpec, symbol: Symbol, proc: &roc_mono::ir::Proc<'a>, layout_ids: &mut LayoutIds<'a>, ) -> FunctionValue<'ctx> { let args = proc.args; let arena = env.arena; let fn_name = func_spec_name(env.arena, &env.interns, symbol, func_spec); let ret_type = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(proc.ret_layout), ); let mut arg_basic_types = Vec::with_capacity_in(args.len(), arena); for (layout, _) in args.iter() { let arg_type = argument_type_from_layout(env, layout_interner, layout_interner.get_repr(*layout)); arg_basic_types.push(arg_type); } let roc_return = RocReturn::from_layout(layout_interner, layout_interner.get_repr(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_spec, Linkage::Internal, ); let subprogram = env.new_subprogram(&fn_name); fn_val.set_subprogram(subprogram); if env.exposed_to_host.contains(&symbol) { let arguments = Vec::from_iter_in(proc.args.iter().map(|(layout, _)| *layout), env.arena); expose_function_to_host( env, layout_interner, symbol, fn_val, arguments.into_bump_slice(), proc.name.niche(), proc.ret_layout, layout_ids, ); } if false { 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, 0); fn_val.add_attribute(AttributeLoc::Function, enum_attr); } if false { 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, 0); fn_val.add_attribute(AttributeLoc::Function, enum_attr); } fn_val } fn expose_alias_to_host<'a>( env: &Env<'a, '_, '_>, layout_interner: &STLayoutInterner<'a>, mod_solutions: &'a ModSolutions, fn_name: &str, alias_symbol: Symbol, hels: &HostExposedLambdaSet<'a>, ) { match hels.raw_function_layout { RawFunctionLayout::Function(arguments, closure, result) => { // define closure size and return value size, e.g. // // * roc__mainForHost_1_Update_size() -> i64 // * roc__mainForHost_1_Update_result_size() -> i64 let it = hels.proc_layout.arguments.iter().copied(); let bytes = roc_alias_analysis::func_name_bytes_help( hels.symbol, it, Niche::NONE, hels.proc_layout.result, ); let func_name = FuncName(&bytes); let func_solutions = mod_solutions.func_solutions(func_name).unwrap(); let mut it = func_solutions.specs(); let evaluator = match it.next() { Some(func_spec) => { debug_assert!( it.next().is_none(), "we expect only one specialization of this symbol" ); function_value_by_func_spec(env, FuncBorrowSpec::Some(*func_spec), hels.symbol) } None => { // morphic did not generate a specialization for this function, // therefore it must actually be unused. // An example is our closure callers panic!("morphic did not specialize {:?}", hels.symbol); } }; build_closure_caller( env, layout_interner, fn_name, evaluator, alias_symbol, arguments, result, closure, result, ) } RawFunctionLayout::ErasedFunction(..) => todo_lambda_erasure!(), RawFunctionLayout::ZeroArgumentThunk(result) => { // Define only the return value size, since this is a thunk // // * roc__mainForHost_1_Update_result_size() -> i64 let result_type = basic_type_from_layout(env, layout_interner, layout_interner.get_repr(result)); build_host_exposed_alias_size_help( env, fn_name, alias_symbol, Some("result"), result_type, ); } } } fn build_closure_caller<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, def_name: &str, evaluator: FunctionValue<'ctx>, alias_symbol: Symbol, arguments: &[InLayout<'a>], return_layout: InLayout<'a>, lambda_set: LambdaSet<'a>, result: InLayout<'a>, ) { let mut argument_types = Vec::with_capacity_in(arguments.len() + 3, env.arena); for layout in arguments { let arg_type = basic_type_from_layout(env, layout_interner, layout_interner.get_repr(*layout)); let arg_ptr_type = arg_type.ptr_type(AddressSpace::default()); argument_types.push(arg_ptr_type.into()); } let closure_argument_type = { let basic_type = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(lambda_set.runtime_representation()), ); basic_type.ptr_type(AddressSpace::default()) }; argument_types.push(closure_argument_type.into()); let context = &env.context; let builder = env.builder; let result_type = basic_type_from_layout(env, layout_interner, layout_interner.get_repr(result)); let output_type = { result_type.ptr_type(AddressSpace::default()) }; argument_types.push(output_type.into()); // STEP 1: build function header // e.g. `roc__mainForHost_0_caller` (def_name is `mainForHost_0`) let function_name = format!("roc__{def_name}_caller"); 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_spec, Linkage::External, ); // STEP 2: build function body let entry = context.append_basic_block(function_value, "entry"); builder.position_at_end(entry); let mut evaluator_arguments = function_value.get_params(); // the final parameter is the output pointer, pop it let output = evaluator_arguments.pop().unwrap().into_pointer_value(); // NOTE this may be incorrect in the long run // here we load any argument that is a pointer let closure_layout = lambda_set.runtime_representation(); let layouts_it = arguments.iter().chain(std::iter::once(&closure_layout)); for (param, layout) in evaluator_arguments.iter_mut().zip(layouts_it) { if param.is_pointer_value() && !layout_interner.is_passed_by_reference(*layout) { let basic_type = basic_type_from_layout(env, layout_interner, layout_interner.get_repr(*layout)); *param = builder.new_build_load(basic_type, param.into_pointer_value(), "load_param"); } } if env.mode.returns_roc_result() { let call_result = set_jump_and_catch_long_jump( env, layout_interner, function_value, evaluator, &evaluator_arguments, return_layout, ); builder.new_build_store(output, call_result); } else { let call_result = call_direct_roc_function( env, layout_interner, evaluator, layout_interner.get_repr(return_layout), &evaluator_arguments, ); if layout_interner.is_passed_by_reference(return_layout) { build_memcpy( env, layout_interner, layout_interner.get_repr(return_layout), output, call_result.into_pointer_value(), ); } else { builder.new_build_store(output, call_result); } }; builder.new_build_return(None); // STEP 3: build a {} -> u64 function that gives the size of the return type build_host_exposed_alias_size_help(env, def_name, alias_symbol, Some("result"), result_type); // STEP 4: build a {} -> u64 function that gives the size of the closure build_host_exposed_alias_size( env, layout_interner, def_name, alias_symbol, lambda_set.runtime_representation(), ); } fn build_host_exposed_alias_size<'a, 'r>( env: &'r Env<'a, '_, '_>, layout_interner: &'r STLayoutInterner<'a>, def_name: &str, alias_symbol: Symbol, layout: InLayout<'a>, ) { build_host_exposed_alias_size_help( env, def_name, alias_symbol, None, basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout)), ) } fn build_host_exposed_alias_size_help<'a, 'ctx>( env: &'a Env<'a, 'ctx, '_>, def_name: &str, _alias_symbol: Symbol, opt_label: Option<&str>, basic_type: BasicTypeEnum<'ctx>, ) { let builder = env.builder; let context = env.context; 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__{def_name}_{label}_size") } else { format!("roc__{def_name}_size",) }; let size_function = add_func( env.context, env.module, size_function_name.as_str(), size_function_spec, Linkage::External, ); let entry = context.append_basic_block(size_function, "entry"); builder.position_at_end(entry); let size: BasicValueEnum = basic_type.size_of().unwrap().into(); builder.new_build_return(Some(&size)); } fn build_proc<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, func_spec_solutions: &FuncSpecSolutions, mut scope: Scope<'a, 'ctx>, proc: &roc_mono::ir::Proc<'a>, fn_val: FunctionValue<'ctx>, ) { let args = proc.args; let context = &env.context; // Add a basic block for the entry point let entry = context.append_basic_block(fn_val, "entry"); let builder = env.builder; builder.position_at_end(entry); debug_info_init!(env, fn_val); // Add args to scope for (arg_val, (layout, arg_symbol)) in fn_val.get_param_iter().zip(args) { arg_val.set_name(arg_symbol.as_str(&env.interns)); scope.insert(*arg_symbol, *layout, arg_val); } let body = build_exp_stmt( env, layout_interner, layout_ids, func_spec_solutions, &mut scope, fn_val, &proc.body, ); // only add a return if codegen did not already add one if let Some(block) = builder.get_insert_block() { if block.get_terminator().is_none() { builder.new_build_return(Some(&body)); } } } pub fn verify_fn(fn_val: FunctionValue<'_>) { if !fn_val.verify(print_fn_verification_output()) { unsafe { fn_val.delete(); } panic!("Invalid generated fn_val.") } } pub(crate) fn function_value_by_func_spec<'ctx>( env: &Env<'_, 'ctx, '_>, func_spec: FuncBorrowSpec, symbol: Symbol, ) -> FunctionValue<'ctx> { let fn_name = func_spec_name(env.arena, &env.interns, symbol, func_spec); let fn_name = fn_name.as_str(); function_value_by_name_help(env, symbol, fn_name) } fn function_value_by_name_help<'ctx>( env: &Env<'_, 'ctx, '_>, symbol: Symbol, fn_name: &str, ) -> FunctionValue<'ctx> { env.module.get_function(fn_name).unwrap_or_else(|| { if symbol.is_builtin() { panic!("Unrecognized builtin function: {fn_name:?} (symbol: {symbol:?})") } else { panic!("Unrecognized non-builtin function: {fn_name:?} (symbol: {symbol:?})") } }) } #[inline(always)] fn roc_call_direct_with_args<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, result_layout: InLayout<'a>, name: LambdaName<'a>, func_spec: FuncBorrowSpec, arguments: &[BasicValueEnum<'ctx>], ) -> BasicValueEnum<'ctx> { let fn_val = function_value_by_func_spec(env, func_spec, name.name()); call_direct_roc_function( env, layout_interner, fn_val, layout_interner.get_repr(result_layout), arguments, ) } #[inline(always)] fn roc_call_erased_with_args<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, pointer: PointerValue<'ctx>, argument_layouts: &[InLayout<'a>], result_layout: InLayout<'a>, arguments: &[BasicValueEnum<'ctx>], ) -> BasicValueEnum<'ctx> { let function_type = fn_ptr::function_type(env, layout_interner, argument_layouts, result_layout); let function_ptr_type = function_type.ptr_type(AddressSpace::default()); let function_pointer = fn_ptr::cast_to_function_ptr_type(env, pointer, function_ptr_type); let build_call = |arguments: &[BasicMetadataValueEnum<'ctx>]| { env.builder .new_build_indirect_call(function_type, function_pointer, arguments, "call") }; call_roc_function_help( env, layout_interner, build_call, function_type, layout_interner.get_repr(result_layout), arguments, ) } pub(crate) fn call_direct_roc_function<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, roc_function: FunctionValue<'ctx>, result_layout: LayoutRepr<'a>, arguments: &[BasicValueEnum<'ctx>], ) -> BasicValueEnum<'ctx> { let function_type = roc_function.get_type(); let build_call = |arguments: &[BasicMetadataValueEnum<'ctx>]| { env.builder.new_build_call(roc_function, arguments, "call") }; debug_assert_eq!(roc_function.get_call_conventions(), FAST_CALL_CONV); call_roc_function_help( env, layout_interner, build_call, function_type, result_layout, arguments, ) } fn call_roc_function_help<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, build_call: impl FnOnce(&[BasicMetadataValueEnum<'ctx>]) -> CallSiteValue<'ctx>, roc_function_type: FunctionType<'ctx>, result_layout: LayoutRepr<'a>, arguments: &[BasicValueEnum<'ctx>], ) -> BasicValueEnum<'ctx> { let pass_by_pointer = roc_function_type.get_param_types().len() == arguments.len() + 1; match RocReturn::from_layout(layout_interner, result_layout) { RocReturn::ByPointer if !pass_by_pointer => { // WARNING this is a hack!! let it = arguments.iter().map(|x| (*x).into()); let mut arguments = Vec::from_iter_in(it, env.arena); arguments.pop(); let result_type = basic_type_from_layout(env, layout_interner, result_layout); let result_alloca = env.builder.new_build_alloca(result_type, "result_value"); arguments.push(result_alloca.into()); debug_assert_eq!(roc_function_type.get_param_types().len(), arguments.len()); let call = build_call(&arguments); // roc functions should have the fast calling convention call.set_call_convention(FAST_CALL_CONV); env.builder .new_build_load(result_type, result_alloca, "load_result") } RocReturn::ByPointer => { let it = arguments.iter().map(|x| (*x).into()); let mut arguments = Vec::from_iter_in(it, env.arena); let result_type = basic_type_from_layout(env, layout_interner, result_layout); let result_alloca = entry_block_alloca_zerofill(env, result_type, "result_value"); arguments.push(result_alloca.into()); debug_assert_eq!(roc_function_type.get_param_types().len(), arguments.len()); let call = build_call(&arguments); // roc functions should have the fast calling convention call.set_call_convention(FAST_CALL_CONV); if result_layout.is_passed_by_reference(layout_interner) { result_alloca.into() } else { env.builder.new_build_load( result_type, result_alloca, "return_by_pointer_load_result", ) } } RocReturn::Return => { debug_assert_eq!(roc_function_type.get_param_types().len(), arguments.len()); let it = arguments.iter().map(|x| (*x).into()); let arguments = Vec::from_iter_in(it, env.arena); let call = build_call(&arguments); // roc functions should have the fast calling convention call.set_call_convention(FAST_CALL_CONV); call.try_as_basic_value() .left() .unwrap_or_else(|| internal_error!("LLVM error: Invalid call by name",)) } } } /// Translates a target_lexicon::Triple to a LLVM calling convention u32 /// as described in https://llvm.org/doxygen/namespacellvm_1_1CallingConv.html pub fn get_call_conventions(cc: target_lexicon::CallingConvention) -> u32 { use target_lexicon::CallingConvention::*; // For now, we're returning 0 for the C calling convention on all of these. // Not sure if we should be picking something more specific! match cc { SystemV => C_CALL_CONV, WasmBasicCAbi => C_CALL_CONV, WindowsFastcall => C_CALL_CONV, AppleAarch64 => C_CALL_CONV, _ => C_CALL_CONV, } } /// Source: https://llvm.org/doxygen/namespacellvm_1_1CallingConv.html pub const C_CALL_CONV: u32 = 0; pub const FAST_CALL_CONV: u32 = 8; pub const COLD_CALL_CONV: u32 = 9; pub struct RocFunctionCall<'ctx> { pub caller: PointerValue<'ctx>, pub data: PointerValue<'ctx>, pub inc_n_data: PointerValue<'ctx>, pub data_is_owned: IntValue<'ctx>, } pub(crate) fn roc_function_call<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, transform: FunctionValue<'ctx>, closure_data: BasicValueEnum<'ctx>, lambda_set: LambdaSet<'a>, closure_data_is_owned: bool, argument_layouts: &[InLayout<'a>], result_layout: InLayout<'a>, ) -> RocFunctionCall<'ctx> { use crate::llvm::bitcode::{build_inc_n_wrapper, build_transform_caller}; let closure_data_type = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(lambda_set.runtime_representation()), ); let closure_data_ptr = env .builder .new_build_alloca(closure_data_type, "closure_data_ptr"); store_roc_value( env, layout_interner, layout_interner.get_repr(lambda_set.runtime_representation()), closure_data_ptr, closure_data, ); let stepper_caller = build_transform_caller( env, layout_interner, transform, lambda_set, argument_layouts, result_layout, ) .as_global_value() .as_pointer_value(); let inc_closure_data = build_inc_n_wrapper( env, layout_interner, layout_ids, lambda_set.runtime_representation(), ) .as_global_value() .as_pointer_value(); let closure_data_is_owned = env .context .bool_type() .const_int(closure_data_is_owned as u64, false); RocFunctionCall { caller: stepper_caller, inc_n_data: inc_closure_data, data_is_owned: closure_data_is_owned, data: closure_data_ptr, } } /// 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<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, layout: InLayout<'a>, ) -> BasicTypeEnum<'ctx> { let layout_repr = layout_interner.runtime_representation(layout); match layout_repr { LayoutRepr::Builtin(builtin) => to_cc_type_builtin(env, &builtin), LayoutRepr::Struct(_) => { let stack_type = basic_type_from_layout(env, layout_interner, layout_repr); if layout_repr.is_passed_by_reference(layout_interner) { stack_type.ptr_type(AddressSpace::default()).into() } else { stack_type } } _ => { // TODO this is almost certainly incorrect for bigger structs basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout)) } } } fn to_cc_type_builtin<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, builtin: &Builtin<'a>, ) -> BasicTypeEnum<'ctx> { match builtin { Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal => { basic_type_from_builtin(env, builtin) } Builtin::Str | Builtin::List(_) => { let address_space = AddressSpace::default(); let field_types: [BasicTypeEnum; 3] = [ env.context.i8_type().ptr_type(address_space).into(), env.ptr_int().into(), env.ptr_int().into(), ]; let struct_type = env.context.struct_type(&field_types, false); struct_type.ptr_type(address_space).into() } } } #[derive(Debug, Clone, Copy)] pub(crate) enum RocReturn { /// Return as normal Return, /// require an extra argument, a pointer /// where the result is written into returns void ByPointer, } impl RocReturn { fn roc_return_by_pointer(interner: &STLayoutInterner, layout: LayoutRepr) -> bool { layout.is_passed_by_reference(interner) } pub(crate) fn from_layout<'a>( layout_interner: &STLayoutInterner<'a>, layout: LayoutRepr<'a>, ) -> Self { if Self::roc_return_by_pointer(layout_interner, layout) { RocReturn::ByPointer } else { RocReturn::Return } } } #[derive(Debug, Clone, Copy)] pub 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, } #[derive(Debug, Clone, Copy)] pub(crate) struct FunctionSpec<'ctx> { /// The function type pub typ: FunctionType<'ctx>, call_conv: u32, /// 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_stack_return_type: Option>, } 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(stack_return_type) = self.cconv_stack_return_type { // 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 sret_attribute = ctx.create_type_attribute(sret_attribute_id, stack_return_type.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>, cc_return: CCReturn, return_type: Option>, argument_types: &[BasicTypeEnum<'ctx>], ) -> 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.unwrap().ptr_type(AddressSpace::default()); 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), Some(return_type.unwrap()), ) } CCReturn::Return => { let arguments = function_arguments(env, argument_types); (return_type.unwrap().fn_type(&arguments, false), None) } CCReturn::Void => { // NOTE: there may be a valid return type, but it is zero-sized. // for instance just `{}` or something more complex like `{ { {}, {} }, {} }` let arguments = function_arguments(env, argument_types); (env.context.void_type().fn_type(&arguments, false), None) } }; Self { typ, call_conv: C_CALL_CONV, cconv_stack_return_type: opt_sret_parameter, } } /// Fastcc calling convention pub fn fastcc<'a, 'env>( env: &Env<'a, 'ctx, 'env>, roc_return: RocReturn, return_type: BasicTypeEnum<'ctx>, mut argument_types: Vec>, ) -> 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::default()).into()); env.context .void_type() .fn_type(&function_arguments(env, &argument_types), false) } }; Self { typ, call_conv: FAST_CALL_CONV, cconv_stack_return_type: None, } } pub fn known_fastcc(fn_type: FunctionType<'ctx>) -> FunctionSpec<'ctx> { Self { typ: fn_type, call_conv: FAST_CALL_CONV, cconv_stack_return_type: 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_stack_return_type: None, } } } /// According to the C ABI, how should we return a value with the given layout? pub fn to_cc_return<'a>( env: &Env<'a, '_, '_>, layout_interner: &STLayoutInterner<'a>, layout: InLayout<'a>, ) -> CCReturn { let return_size = layout_interner.stack_size(layout); let pass_result_by_pointer = match env.target_info.operating_system { roc_target::OperatingSystem::Windows => { return_size >= 2 * env.target_info.ptr_width() as u32 } roc_target::OperatingSystem::Unix => return_size > 2 * env.target_info.ptr_width() as u32, roc_target::OperatingSystem::Wasi => return_size > 2 * env.target_info.ptr_width() as u32, }; if return_size == 0 { CCReturn::Void } else if pass_result_by_pointer { CCReturn::ByPointer } else { CCReturn::Return } } fn function_arguments<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, arguments: &[BasicTypeEnum<'ctx>], ) -> Vec<'a, BasicMetadataTypeEnum<'ctx>> { let it = arguments.iter().map(|x| (*x).into()); Vec::from_iter_in(it, env.arena) } fn build_foreign_symbol<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, scope: &mut Scope<'a, 'ctx>, foreign: &roc_module::ident::ForeignSymbol, argument_symbols: &[Symbol], ret_layout: InLayout<'a>, ) -> BasicValueEnum<'ctx> { let builder = env.builder; let context = env.context; let fastcc_function_name = format!("{}_fastcc_wrapper", foreign.as_str()); let (fastcc_function, arguments) = match env.module.get_function(fastcc_function_name.as_str()) { Some(function_value) => { let mut arguments = Vec::with_capacity_in(argument_symbols.len(), env.arena); for symbol in argument_symbols { let (value, _) = scope.load_symbol_and_layout(symbol); arguments.push(value); } (function_value, arguments) } None => { // 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 return_type = basic_type_from_layout(env, layout_interner, layout_interner.get_repr(ret_layout)); let roc_return = RocReturn::from_layout(layout_interner, layout_interner.get_repr(ret_layout)); let cc_return = to_cc_return(env, layout_interner, 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) = scope.load_symbol_and_layout(symbol); cc_argument_types.push(to_cc_type(env, layout_interner, layout)); let basic_type = argument_type_from_layout( env, layout_interner, layout_interner.get_repr(layout), ); fastcc_argument_types.push(basic_type); arguments.push(value); } 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 = 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, ); let old = builder.get_insert_block().unwrap(); let entry = context.append_basic_block(fastcc_function, "entry"); { builder.position_at_end(entry); let mut fastcc_parameters = fastcc_function.get_params(); let mut cc_arguments = Vec::with_capacity_in(fastcc_parameters.len() + 1, env.arena); let return_pointer = match roc_return { RocReturn::Return => env.builder.new_build_alloca(return_type, "return_value"), RocReturn::ByPointer => fastcc_parameters.pop().unwrap().into_pointer_value(), }; if let CCReturn::ByPointer = cc_return { cc_arguments.push(return_pointer.into()); } let it = fastcc_parameters.into_iter().zip(cc_argument_types.iter()); for (param, cc_type) in it { if param.get_type() == *cc_type { cc_arguments.push(param.into()); } else { // not pretty, but seems to cover all our current case if cc_type.is_pointer_type() && !param.get_type().is_pointer_type() { // we need to pass this value by-reference; put it into an alloca // and bitcast the reference let param_alloca = env .builder .new_build_alloca(param.get_type(), "param_alloca"); env.builder.new_build_store(param_alloca, param); let as_cc_type = env.builder.new_build_pointer_cast( param_alloca, cc_type.into_pointer_type(), "to_cc_type_ptr", ); cc_arguments.push(as_cc_type.into()); } else { // eprintln!("C type: {:?}", cc_type); // eprintln!("Fastcc type: {:?}", param.get_type()); // todo!("C <-> Fastcc interaction that we haven't seen before") let as_cc_type = env.builder.new_build_pointer_cast( param.into_pointer_value(), cc_type.into_pointer_type(), "to_cc_type_ptr", ); cc_arguments.push(as_cc_type.into()); } } } let call = env .builder .new_build_call(cc_function, &cc_arguments, "tmp"); call.set_call_convention(C_CALL_CONV); match roc_return { RocReturn::Return => { let return_value = match cc_return { CCReturn::Return => call.try_as_basic_value().left().unwrap(), CCReturn::ByPointer => env.builder.new_build_load( return_type, return_pointer, "read_result", ), CCReturn::Void => return_type.const_zero(), }; builder.new_build_return(Some(&return_value)); } RocReturn::ByPointer => { match cc_return { CCReturn::Return => { let result = call.try_as_basic_value().left().unwrap(); env.builder.new_build_store(return_pointer, result); } CCReturn::ByPointer | CCReturn::Void => { // the return value (if any) is already written to the return pointer } } builder.new_build_return(None); } } } builder.position_at_end(old); (fastcc_function, arguments) } }; call_direct_roc_function( env, layout_interner, fastcc_function, layout_interner.get_repr(ret_layout), &arguments, ) } fn define_global_str_literal_ptr<'ctx>( env: &Env<'_, 'ctx, '_>, message: &str, ) -> PointerValue<'ctx> { let global = define_global_str_literal(env, message); let ptr = env.builder.new_build_pointer_cast( global.as_pointer_value(), env.context.i8_type().ptr_type(AddressSpace::default()), "to_opaque", ); // a pointer to the first actual data (skipping over the refcount) let ptr = unsafe { env.builder.new_build_in_bounds_gep( env.context.i8_type(), ptr, &[env .ptr_int() .const_int(env.target_info.ptr_width() as u64, false)], "get_rc_ptr", ) }; ptr } fn define_global_str_literal<'ctx>( env: &Env<'_, 'ctx, '_>, message: &str, ) -> inkwell::values::GlobalValue<'ctx> { let module = env.module; // hash the name so we don't re-define existing messages let name = { use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; let mut hasher = DefaultHasher::new(); message.hash(&mut hasher); let hash = hasher.finish(); format!("_str_literal_{hash}") }; match module.get_global(&name) { Some(current) => current, None => { let size = message.bytes().len() + env.target_info.ptr_width() as usize; let mut bytes = Vec::with_capacity_in(size, env.arena); // insert NULL bytes for the refcount for _ in 0..env.target_info.ptr_width() as usize { bytes.push(env.context.i8_type().const_zero()); } // then add the data bytes for b in message.bytes() { bytes.push(env.context.i8_type().const_int(b as u64, false)); } // use None for the address space (e.g. Const does not work) let typ = env.context.i8_type().array_type(bytes.len() as u32); let global = module.add_global(typ, None, &name); global.set_initializer(&env.context.i8_type().const_array(bytes.into_bump_slice())); // mimic the `global_string` function; we cannot use it directly because it assumes // strings are NULL-terminated, which means we can't store the refcount (which is 8 // NULL bytes) global.set_constant(true); global.set_alignment(env.target_info.ptr_width() as u32); global.set_unnamed_addr(true); global.set_linkage(inkwell::module::Linkage::Private); global } } } pub(crate) fn throw_internal_exception<'ctx>( env: &Env<'_, 'ctx, '_>, parent: FunctionValue<'ctx>, message: &str, ) { let builder = env.builder; let str = build_string_literal(env, parent, message); env.call_panic(env, str, CrashTag::Roc); builder.new_build_unreachable(); } pub(crate) fn throw_exception<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, scope: &mut Scope<'a, 'ctx>, message: &Symbol, tag: CrashTag, ) { let msg_val = scope.load_symbol(message); env.call_panic(env, msg_val, tag); env.builder.new_build_unreachable(); } fn get_foreign_symbol<'ctx>( env: &Env<'_, 'ctx, '_>, foreign_symbol: roc_module::ident::ForeignSymbol, function_spec: FunctionSpec<'ctx>, ) -> FunctionValue<'ctx> { let module = env.module; match module.get_function(foreign_symbol.as_str()) { Some(gvalue) => gvalue, None => { let foreign_function = add_func( env.context, module, foreign_symbol.as_str(), function_spec, Linkage::External, ); foreign_function } } } /// Add a function to a module, after asserting that the function is unique. /// 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(crate) fn add_func<'ctx>( ctx: &Context, module: &Module<'ctx>, name: &str, spec: FunctionSpec<'ctx>, linkage: Linkage, ) -> FunctionValue<'ctx> { if cfg!(debug_assertions) { if let Some(func) = module.get_function(name) { panic!("Attempting to redefine LLVM function {name}, which was already defined in this module as:\n\n{func:#?}"); } } let fn_val = module.add_function(name, spec.typ, Some(linkage)); spec.attach_attributes(ctx, fn_val); fn_val }