use inkwell::{ types::{BasicType, IntType}, values::{ BasicValue, BasicValueEnum, FloatValue, FunctionValue, InstructionOpcode, IntValue, PointerValue, StructValue, }, AddressSpace, IntPredicate, }; use morphic_lib::{FuncSpec, UpdateMode}; use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; use roc_error_macros::internal_error; use roc_module::{low_level::LowLevel, symbol::Symbol}; use roc_mono::{ ir::HigherOrderLowLevel, layout::{ Builtin, InLayout, LambdaSet, Layout, LayoutIds, LayoutInterner, LayoutRepr, STLayoutInterner, }, list_element_layout, }; use roc_target::PtrWidth; use crate::llvm::{ bitcode::{ call_bitcode_fn, call_bitcode_fn_fixing_for_convention, call_list_bitcode_fn, call_str_bitcode_fn, call_void_bitcode_fn, pass_list_or_string_to_zig_32bit, BitcodeReturns, }, build::{ cast_basic_basic, complex_bitcast_check_size, create_entry_block_alloca, function_value_by_func_spec, load_roc_value, roc_function_call, tag_pointer_clear_tag_id, BuilderExt, RocReturn, }, build_list::{ list_append_unsafe, list_concat, list_drop_at, list_get_unsafe, list_len, list_map, list_map2, list_map3, list_map4, list_prepend, list_release_excess_capacity, list_replace_unsafe, list_reserve, list_sort_with, list_sublist, list_swap, list_symbol_to_c_abi, list_with_capacity, pass_update_mode, }, compare::{generic_eq, generic_neq}, convert::{ self, argument_type_from_layout, basic_type_from_layout, zig_num_parse_result_type, zig_to_int_checked_result_type, }, intrinsics::{ // These instrinsics do not generate calls to libc and are safe to keep. // If we find that any of them generate calls to libc on some platforms, we need to define them as zig bitcode. LLVM_ADD_SATURATED, LLVM_ADD_WITH_OVERFLOW, LLVM_MUL_WITH_OVERFLOW, LLVM_SUB_SATURATED, LLVM_SUB_WITH_OVERFLOW, }, refcounting::PointerToRefcount, }; use super::{build::Env, convert::zig_dec_type}; use super::{ build::{throw_internal_exception, use_roc_value}, convert::zig_with_overflow_roc_dec, scope::Scope, }; pub(crate) fn run_low_level<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, scope: &Scope<'a, 'ctx>, parent: FunctionValue<'ctx>, layout: InLayout<'a>, op: LowLevel, args: &[Symbol], update_mode: UpdateMode, ) -> BasicValueEnum<'ctx> { use LowLevel::*; debug_assert!(!op.is_higher_order()); macro_rules! arguments { () => {}; ($($x:ident),+ $(,)?) => { // look at that, a usage for if let ... else let [$($x),+] = match &args { [$($x),+] => { [ $(scope.load_symbol($x)),+ ] } _ => { // we could get fancier with reporting here, but this macro is used a bunch // so I want to keep the expansion small (for now) internal_error!("lowlevel operation has incorrect number of arguments!") } }; }; } macro_rules! arguments_with_layouts { () => {}; ($(($x:ident, $y:ident)),+ $(,)?) => { // look at that, a usage for if let ... else let [$(($x, $y)),+] = match &args { [$($x),+] => { [ $(scope.load_symbol_and_layout($x)),+ ] } _ => { // we could get fancier with reporting here, but this macro is used a bunch // so I want to keep the expansion small (for now) internal_error!("lowlevel operation has incorrect number of arguments!") } }; }; } match op { StrConcat => { // Str.concat : Str, Str -> Str arguments!(string1, string2); call_str_bitcode_fn( env, &[string1, string2], &[], BitcodeReturns::Str, bitcode::STR_CONCAT, ) } StrJoinWith => { // Str.joinWith : List Str, Str -> Str arguments!(list, string); match env.target_info.ptr_width() { PtrWidth::Bytes4 => { // list and string are both stored as structs on the stack on 32-bit targets call_str_bitcode_fn( env, &[list, string], &[], BitcodeReturns::Str, bitcode::STR_JOIN_WITH, ) } PtrWidth::Bytes8 => { // on 64-bit targets, strings are stored as pointers, but that is not what zig expects call_list_bitcode_fn( env, &[list.into_struct_value()], &[string], BitcodeReturns::Str, bitcode::STR_JOIN_WITH, ) } } } StrToScalars => { // Str.toScalars : Str -> List U32 arguments!(string); call_str_bitcode_fn( env, &[string], &[], BitcodeReturns::List, bitcode::STR_TO_SCALARS, ) } StrStartsWith => { // Str.startsWith : Str, Str -> Bool arguments!(string, prefix); call_str_bitcode_fn( env, &[string, prefix], &[], BitcodeReturns::Basic, bitcode::STR_STARTS_WITH, ) } StrStartsWithScalar => { // Str.startsWithScalar : Str, U32 -> Bool arguments!(string, prefix); call_str_bitcode_fn( env, &[string], &[prefix], BitcodeReturns::Basic, bitcode::STR_STARTS_WITH_SCALAR, ) } StrEndsWith => { // Str.startsWith : Str, Str -> Bool arguments!(string, prefix); call_str_bitcode_fn( env, &[string, prefix], &[], BitcodeReturns::Basic, bitcode::STR_ENDS_WITH, ) } StrToNum => { // Str.toNum : Str -> Result (Num *) {} arguments!(string); let number_layout = match layout_interner.get_repr(layout) { LayoutRepr::Struct(field_layouts) => field_layouts[0], // TODO: why is it sometimes a struct? _ => unreachable!(), }; // match on the return layout to figure out which zig builtin we need let intrinsic = match layout_interner.get_repr(number_layout) { LayoutRepr::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[int_width], LayoutRepr::Builtin(Builtin::Float(float_width)) => { &bitcode::STR_TO_FLOAT[float_width] } LayoutRepr::Builtin(Builtin::Decimal) => bitcode::DEC_FROM_STR, _ => unreachable!(), }; let result = match env.target_info.ptr_width() { PtrWidth::Bytes4 => { let zig_function = env.module.get_function(intrinsic).unwrap(); let zig_function_type = zig_function.get_type(); match zig_function_type.get_return_type() { Some(_) => call_str_bitcode_fn( env, &[string], &[], BitcodeReturns::Basic, intrinsic, ), None => { let return_type_name = match layout_interner.get_repr(number_layout) { LayoutRepr::Builtin(Builtin::Int(int_width)) => { int_width.type_name() } LayoutRepr::Builtin(Builtin::Decimal) => { // zig picks 128 for dec.RocDec "i128" } _ => unreachable!(), }; let return_type = zig_num_parse_result_type(env, return_type_name); let zig_return_alloca = create_entry_block_alloca( env, parent, return_type.into(), "str_to_num", ); let (a, b) = pass_list_or_string_to_zig_32bit(env, string.into_struct_value()); call_void_bitcode_fn( env, &[zig_return_alloca.into(), a.into(), b.into()], intrinsic, ); let roc_return_type = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(layout), ) .ptr_type(AddressSpace::default()); let roc_return_alloca = env.builder.build_pointer_cast( zig_return_alloca, roc_return_type, "cast_to_roc", ); load_roc_value( env, layout_interner, layout_interner.get_repr(layout), roc_return_alloca, "str_to_num_result", ) } } } PtrWidth::Bytes8 => { let cc_return_by_pointer = match layout_interner.get_repr(number_layout) { LayoutRepr::Builtin(Builtin::Int(int_width)) => { (int_width.stack_size() as usize > env.target_info.ptr_size()) .then_some(int_width.type_name()) } LayoutRepr::Builtin(Builtin::Decimal) => { // zig picks 128 for dec.RocDec Some("i128") } _ => None, }; if let Some(type_name) = cc_return_by_pointer { let bitcode_return_type = zig_num_parse_result_type(env, type_name); call_bitcode_fn_fixing_for_convention( env, layout_interner, bitcode_return_type, &[string], layout, intrinsic, ) } else { call_bitcode_fn(env, &[string], intrinsic) } } }; // zig passes the result as a packed integer sometimes, instead of a struct. So we cast if needed. // We check the type as expected in an argument position, since that is how we actually will use it. let expected_type = argument_type_from_layout(env, layout_interner, layout_interner.get_repr(layout)); let actual_type = result.get_type(); if expected_type != actual_type { complex_bitcast_check_size(env, result, expected_type, "str_to_num_cast") } else { result } } StrFromInt => { // Str.fromInt : Int -> Str debug_assert_eq!(args.len(), 1); let (int, int_layout) = scope.load_symbol_and_layout(&args[0]); let int = int.into_int_value(); let int_width = match layout_interner.get_repr(int_layout) { LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width, _ => unreachable!(), }; call_str_bitcode_fn( env, &[], &[int.into()], BitcodeReturns::Str, &bitcode::STR_FROM_INT[int_width], ) } StrFromFloat => { // Str.fromFloat : Float * -> Str debug_assert_eq!(args.len(), 1); let (float, float_layout) = scope.load_symbol_and_layout(&args[0]); let float_width = match layout_interner.get_repr(float_layout) { LayoutRepr::Builtin(Builtin::Float(float_width)) => float_width, _ => unreachable!(), }; call_str_bitcode_fn( env, &[], &[float], BitcodeReturns::Str, &bitcode::STR_FROM_FLOAT[float_width], ) } StrFromUtf8Range => { let result_type = env.module.get_struct_type("str.FromUtf8Result").unwrap(); let result_ptr = env .builder .build_alloca(result_type, "alloca_utf8_validate_bytes_result"); match env.target_info.ptr_width() { PtrWidth::Bytes4 => { arguments!(list, start, count); let (a, b) = pass_list_or_string_to_zig_32bit(env, list.into_struct_value()); call_void_bitcode_fn( env, &[ result_ptr.into(), a.into(), b.into(), start, count, pass_update_mode(env, update_mode), ], bitcode::STR_FROM_UTF8_RANGE, ); } PtrWidth::Bytes8 => { arguments!(_list, start, count); // we use the symbol here instead let list = args[0]; call_void_bitcode_fn( env, &[ result_ptr.into(), list_symbol_to_c_abi(env, scope, list).into(), start, count, pass_update_mode(env, update_mode), ], bitcode::STR_FROM_UTF8_RANGE, ); } } crate::llvm::build_str::decode_from_utf8_result(env, layout_interner, result_ptr) } StrToUtf8 => { // Str.fromInt : Str -> List U8 arguments!(string); call_str_bitcode_fn( env, &[string], &[], BitcodeReturns::List, bitcode::STR_TO_UTF8, ) } StrRepeat => { // Str.repeat : Str, Nat -> Str arguments!(string, count); call_str_bitcode_fn( env, &[string], &[count], BitcodeReturns::Str, bitcode::STR_REPEAT, ) } StrSplit => { // Str.split : Str, Str -> List Str arguments!(string, delimiter); call_str_bitcode_fn( env, &[string, delimiter], &[], BitcodeReturns::List, bitcode::STR_SPLIT, ) } StrIsEmpty => { // Str.isEmpty : Str -> Str arguments!(string); // the builtin will always return an u64 let length = call_str_bitcode_fn( env, &[string], &[], BitcodeReturns::Basic, bitcode::STR_NUMBER_OF_BYTES, ) .into_int_value(); // cast to the appropriate usize of the current build let byte_count = env.builder .build_int_cast_sign_flag(length, env.ptr_int(), false, "len_as_usize"); let is_zero = env.builder.build_int_compare( IntPredicate::EQ, byte_count, env.ptr_int().const_zero(), "str_len_is_zero", ); BasicValueEnum::IntValue(is_zero) } StrCountGraphemes => { // Str.countGraphemes : Str -> Nat arguments!(string); call_str_bitcode_fn( env, &[string], &[], BitcodeReturns::Basic, bitcode::STR_COUNT_GRAPEHEME_CLUSTERS, ) } StrGetScalarUnsafe => { // Str.getScalarUnsafe : Str, Nat -> { bytesParsed : Nat, scalar : U32 } arguments!(string, index); use roc_target::OperatingSystem::*; match env.target_info.operating_system { Windows => { let return_type = env.context.struct_type( &[env.ptr_int().into(), env.context.i32_type().into()], false, ); let result = env.builder.build_alloca(return_type, "result"); call_void_bitcode_fn( env, &[result.into(), string, index], bitcode::STR_GET_SCALAR_UNSAFE, ); let return_type = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(layout), ); let cast_result = env.builder.build_pointer_cast( result, return_type.ptr_type(AddressSpace::default()), "cast", ); env.builder .new_build_load(return_type, cast_result, "load_result") } Unix => { let result = call_str_bitcode_fn( env, &[string], &[index], BitcodeReturns::Basic, bitcode::STR_GET_SCALAR_UNSAFE, ); // on 32-bit targets, zig bitpacks the struct match env.target_info.ptr_width() { PtrWidth::Bytes8 => result, PtrWidth::Bytes4 => { let to = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(layout), ); complex_bitcast_check_size(env, result, to, "to_roc_record") } } } Wasi => unimplemented!(), } } StrCountUtf8Bytes => { // Str.countUtf8Bytes : Str -> Nat arguments!(string); call_str_bitcode_fn( env, &[string], &[], BitcodeReturns::Basic, bitcode::STR_COUNT_UTF8_BYTES, ) } StrGetCapacity => { // Str.capacity : Str -> Nat arguments!(string); call_bitcode_fn(env, &[string], bitcode::STR_CAPACITY) } StrSubstringUnsafe => { // Str.substringUnsafe : Str, Nat, Nat -> Str arguments!(string, start, length); call_str_bitcode_fn( env, &[string], &[start, length], BitcodeReturns::Str, bitcode::STR_SUBSTRING_UNSAFE, ) } StrReserve => { // Str.reserve : Str, Nat -> Str arguments!(string, capacity); call_str_bitcode_fn( env, &[string], &[capacity], BitcodeReturns::Str, bitcode::STR_RESERVE, ) } StrReleaseExcessCapacity => { // Str.releaseExcessCapacity: Str -> Str arguments!(string); call_str_bitcode_fn( env, &[string], &[], BitcodeReturns::Str, bitcode::STR_RELEASE_EXCESS_CAPACITY, ) } StrAppendScalar => { // Str.appendScalar : Str, U32 -> Str arguments!(string, capacity); call_str_bitcode_fn( env, &[string], &[capacity], BitcodeReturns::Str, bitcode::STR_APPEND_SCALAR, ) } StrTrim => { // Str.trim : Str -> Str arguments!(string); call_str_bitcode_fn(env, &[string], &[], BitcodeReturns::Str, bitcode::STR_TRIM) } StrTrimLeft => { // Str.trim : Str -> Str arguments!(string); call_str_bitcode_fn( env, &[string], &[], BitcodeReturns::Str, bitcode::STR_TRIM_LEFT, ) } StrTrimRight => { // Str.trim : Str -> Str arguments!(string); call_str_bitcode_fn( env, &[string], &[], BitcodeReturns::Str, bitcode::STR_TRIM_RIGHT, ) } StrWithCapacity => { // Str.withCapacity : Nat -> Str arguments!(str_len); call_str_bitcode_fn( env, &[], &[str_len], BitcodeReturns::Str, bitcode::STR_WITH_CAPACITY, ) } StrGraphemes => { // Str.graphemes : Str -> List Str arguments!(string); call_str_bitcode_fn( env, &[string], &[], BitcodeReturns::List, bitcode::STR_GRAPHEMES, ) } ListLen => { // List.len : List * -> Nat arguments!(list); list_len(env.builder, list.into_struct_value()).into() } ListGetCapacity => { // List.capacity: List a -> Nat arguments!(list); call_list_bitcode_fn( env, &[list.into_struct_value()], &[], BitcodeReturns::Basic, bitcode::LIST_CAPACITY, ) } ListWithCapacity => { // List.withCapacity : Nat -> List a arguments!(list_len); let result_layout = layout; list_with_capacity( env, layout_interner, list_len.into_int_value(), list_element_layout!(layout_interner, result_layout), ) } ListConcat => { debug_assert_eq!(args.len(), 2); let (first_list, list_layout) = scope.load_symbol_and_layout(&args[0]); let second_list = scope.load_symbol(&args[1]); let element_layout = list_element_layout!(layout_interner, list_layout); list_concat( env, layout_interner, first_list, second_list, element_layout, ) } ListAppendUnsafe => { // List.appendUnsafe : List elem, elem -> List elem debug_assert_eq!(args.len(), 2); let original_wrapper = scope.load_symbol(&args[0]).into_struct_value(); let (elem, elem_layout) = scope.load_symbol_and_layout(&args[1]); list_append_unsafe(env, layout_interner, original_wrapper, elem, elem_layout) } ListPrepend => { // List.prepend : List elem, elem -> List elem debug_assert_eq!(args.len(), 2); let original_wrapper = scope.load_symbol(&args[0]).into_struct_value(); let (elem, elem_layout) = scope.load_symbol_and_layout(&args[1]); list_prepend(env, layout_interner, original_wrapper, elem, elem_layout) } ListReserve => { // List.reserve : List elem, Nat -> List elem debug_assert_eq!(args.len(), 2); let (list, list_layout) = scope.load_symbol_and_layout(&args[0]); let element_layout = list_element_layout!(layout_interner, list_layout); let spare = scope.load_symbol(&args[1]); list_reserve( env, layout_interner, list, spare, element_layout, update_mode, ) } ListReleaseExcessCapacity => { // List.releaseExcessCapacity: List elem -> List elem debug_assert_eq!(args.len(), 1); let (list, list_layout) = scope.load_symbol_and_layout(&args[0]); let element_layout = list_element_layout!(layout_interner, list_layout); list_release_excess_capacity(env, layout_interner, list, element_layout, update_mode) } ListSwap => { // List.swap : List elem, Nat, Nat -> List elem debug_assert_eq!(args.len(), 3); let (list, list_layout) = scope.load_symbol_and_layout(&args[0]); let original_wrapper = list.into_struct_value(); let index_1 = scope.load_symbol(&args[1]); let index_2 = scope.load_symbol(&args[2]); let element_layout = list_element_layout!(layout_interner, list_layout); list_swap( env, layout_interner, original_wrapper, index_1.into_int_value(), index_2.into_int_value(), element_layout, update_mode, ) } ListSublist => { debug_assert_eq!(args.len(), 3); let (list, list_layout) = scope.load_symbol_and_layout(&args[0]); let original_wrapper = list.into_struct_value(); let start = scope.load_symbol(&args[1]); let len = scope.load_symbol(&args[2]); let element_layout = list_element_layout!(layout_interner, list_layout); list_sublist( env, layout_interner, layout_ids, original_wrapper, start.into_int_value(), len.into_int_value(), element_layout, ) } ListDropAt => { // List.dropAt : List elem, Nat -> List elem debug_assert_eq!(args.len(), 2); let (list, list_layout) = scope.load_symbol_and_layout(&args[0]); let original_wrapper = list.into_struct_value(); let count = scope.load_symbol(&args[1]); let element_layout = list_element_layout!(layout_interner, list_layout); list_drop_at( env, layout_interner, layout_ids, original_wrapper, count.into_int_value(), element_layout, ) } StrGetUnsafe => { // Str.getUnsafe : Str, Nat -> u8 arguments!(wrapper_struct, elem_index); call_str_bitcode_fn( env, &[wrapper_struct], &[elem_index], BitcodeReturns::Basic, bitcode::STR_GET_UNSAFE, ) } ListGetUnsafe => { // List.getUnsafe : List elem, Nat -> elem arguments_with_layouts!((wrapper_struct, list_layout), (element_index, _l)); list_get_unsafe( env, layout_interner, list_element_layout!(layout_interner, list_layout), element_index.into_int_value(), wrapper_struct.into_struct_value(), ) } ListReplaceUnsafe => { arguments_with_layouts!((list, _l1), (index, _l2), (element, element_layout)); list_replace_unsafe( env, layout_interner, layout_ids, list, index.into_int_value(), element, element_layout, update_mode, ) } ListIsUnique => { // List.isUnique : List a -> Bool arguments!(list); call_list_bitcode_fn( env, &[list.into_struct_value()], &[], BitcodeReturns::Basic, bitcode::LIST_IS_UNIQUE, ) } NumToStr => { // Num.toStr : Num a -> Str arguments_with_layouts!((num, num_layout)); match layout_interner.get_repr(num_layout) { LayoutRepr::Builtin(Builtin::Int(int_width)) => { let int = num.into_int_value(); call_str_bitcode_fn( env, &[], &[int.into()], BitcodeReturns::Str, &bitcode::STR_FROM_INT[int_width], ) } LayoutRepr::Builtin(Builtin::Float(_float_width)) => { let (float, float_layout) = scope.load_symbol_and_layout(&args[0]); let float_width = match layout_interner.get_repr(float_layout) { LayoutRepr::Builtin(Builtin::Float(float_width)) => float_width, _ => unreachable!(), }; call_str_bitcode_fn( env, &[], &[float], BitcodeReturns::Str, &bitcode::STR_FROM_FLOAT[float_width], ) } LayoutRepr::Builtin(Builtin::Decimal) => dec_to_str(env, num), _ => unreachable!(), } } NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumLogUnchecked | NumSin | NumCos | NumCeiling | NumFloor | NumToFrac | NumIsNan | NumIsInfinite | NumIsFinite | NumAtan | NumAcos | NumAsin | NumToIntChecked | NumCountLeadingZeroBits | NumCountTrailingZeroBits | NumCountOneBits => { arguments_with_layouts!((arg, arg_layout)); match layout_interner.get_repr(arg_layout) { LayoutRepr::Builtin(arg_builtin) => { use roc_mono::layout::Builtin::*; match arg_builtin { Int(int_width) => { let int_type = convert::int_type_from_int_width(env, int_width); build_int_unary_op( env, layout_interner, parent, arg.into_int_value(), int_width, int_type, op, layout, ) } Float(float_width) => build_float_unary_op( env, layout_interner, layout, arg.into_float_value(), op, float_width, ), _ => { unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid builtin layout: ({:?})", op, arg_layout); } } } _ => { unreachable!( "Compiler bug: tried to run numeric operation {:?} on invalid layout: {:?}", op, arg_layout ); } } } NumBytesToU16 => { arguments!(list, position); call_list_bitcode_fn( env, &[list.into_struct_value()], &[position], BitcodeReturns::Basic, bitcode::NUM_BYTES_TO_U16, ) } NumBytesToU32 => { arguments!(list, position); call_list_bitcode_fn( env, &[list.into_struct_value()], &[position], BitcodeReturns::Basic, bitcode::NUM_BYTES_TO_U32, ) } NumBytesToU64 => { arguments!(list, position); call_list_bitcode_fn( env, &[list.into_struct_value()], &[position], BitcodeReturns::Basic, bitcode::NUM_BYTES_TO_U64, ) } NumBytesToU128 => { arguments!(list, position); call_list_bitcode_fn( env, &[list.into_struct_value()], &[position], BitcodeReturns::Basic, bitcode::NUM_BYTES_TO_U128, ) } NumCompare => { arguments_with_layouts!((lhs_arg, lhs_layout), (rhs_arg, rhs_layout)); use inkwell::FloatPredicate; match ( layout_interner.get_repr(lhs_layout), layout_interner.get_repr(rhs_layout), ) { (LayoutRepr::Builtin(lhs_builtin), LayoutRepr::Builtin(rhs_builtin)) if lhs_builtin == rhs_builtin => { use roc_mono::layout::Builtin::*; let tag_eq = env.context.i8_type().const_int(0_u64, false); let tag_gt = env.context.i8_type().const_int(1_u64, false); let tag_lt = env.context.i8_type().const_int(2_u64, false); match lhs_builtin { Int(int_width) => { let are_equal = env.builder.build_int_compare( IntPredicate::EQ, lhs_arg.into_int_value(), rhs_arg.into_int_value(), "int_eq", ); let predicate = if int_width.is_signed() { IntPredicate::SLT } else { IntPredicate::ULT }; let is_less_than = env.builder.build_int_compare( predicate, lhs_arg.into_int_value(), rhs_arg.into_int_value(), "int_compare", ); let step1 = env.builder .build_select(is_less_than, tag_lt, tag_gt, "lt_or_gt"); env.builder.build_select( are_equal, tag_eq, step1.into_int_value(), "lt_or_gt", ) } Float(_) => { let are_equal = env.builder.build_float_compare( FloatPredicate::OEQ, lhs_arg.into_float_value(), rhs_arg.into_float_value(), "float_eq", ); let is_less_than = env.builder.build_float_compare( FloatPredicate::OLT, lhs_arg.into_float_value(), rhs_arg.into_float_value(), "float_compare", ); let step1 = env.builder .build_select(is_less_than, tag_lt, tag_gt, "lt_or_gt"); env.builder.build_select( are_equal, tag_eq, step1.into_int_value(), "lt_or_gt", ) } _ => { unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid builtin layout: ({:?})", op, lhs_layout); } } } _ => { unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid layouts. The 2 layouts were: ({:?}) and ({:?})", op, lhs_layout, rhs_layout); } } } NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked | NumIsMultipleOf | NumAddWrap | NumAddChecked | NumAddSaturated | NumDivFrac | NumDivTruncUnchecked | NumDivCeilUnchecked | NumPow | NumPowInt | NumSubWrap | NumSubChecked | NumSubSaturated | NumMulWrap | NumMulSaturated | NumMulChecked => { arguments_with_layouts!((lhs_arg, lhs_layout), (rhs_arg, rhs_layout)); build_num_binop( env, layout_interner, parent, lhs_arg, lhs_layout, rhs_arg, rhs_layout, layout, op, ) } NumBitwiseAnd | NumBitwiseOr | NumBitwiseXor => { arguments_with_layouts!((lhs_arg, lhs_layout), (rhs_arg, rhs_layout)); debug_assert_eq!(lhs_layout, rhs_layout); let int_width = intwidth_from_layout(lhs_layout); build_int_binop( env, parent, int_width, lhs_arg.into_int_value(), rhs_arg.into_int_value(), op, ) } NumShiftLeftBy | NumShiftRightBy | NumShiftRightZfBy => { arguments_with_layouts!((lhs_arg, lhs_layout), (rhs_arg, rhs_layout)); let int_width = intwidth_from_layout(lhs_layout); debug_assert_eq!(rhs_layout, Layout::U8); let rhs_arg = if rhs_layout != lhs_layout { // LLVM shift intrinsics expect the left and right sides to have the same type, so // here we cast up `rhs` to the lhs type. Since the rhs was checked to be a U8, // this cast isn't lossy. let rhs_arg = env.builder.build_int_cast( rhs_arg.into_int_value(), lhs_arg.get_type().into_int_type(), "cast_for_shift", ); rhs_arg.into() } else { rhs_arg }; build_int_binop( env, parent, int_width, lhs_arg.into_int_value(), rhs_arg.into_int_value(), op, ) } NumIntCast => { arguments!(arg); let to = basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout)) .into_int_type(); let to_signed = intwidth_from_layout(layout).is_signed(); env.builder .build_int_cast_sign_flag(arg.into_int_value(), to, to_signed, "inc_cast") .into() } NumToFloatCast => { arguments_with_layouts!((arg, arg_layout)); match layout_interner.get_repr(arg_layout) { LayoutRepr::Builtin(Builtin::Int(width)) => { // Converting from int to float let int_val = arg.into_int_value(); let dest = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(layout), ) .into_float_type(); if width.is_signed() { env.builder .build_signed_int_to_float(int_val, dest, "signed_int_to_float") .into() } else { env.builder .build_unsigned_int_to_float(int_val, dest, "unsigned_int_to_float") .into() } } LayoutRepr::Builtin(Builtin::Float(_)) => { // Converting from float to float - e.g. F64 to F32, or vice versa let dest = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(layout), ) .into_float_type(); env.builder .build_float_cast(arg.into_float_value(), dest, "cast_float_to_float") .into() } LayoutRepr::Builtin(Builtin::Decimal) => { todo!("Support converting Dec values to floats."); } other => { unreachable!("Tried to do a float cast to non-float layout {:?}", other); } } } NumToFloatChecked => { // NOTE: There's a NumToIntChecked implementation above, // which could be useful to look at when implementing this. todo!("implement checked float conversion"); } I128OfDec => { arguments!(dec); dec_to_i128(env, dec) } Eq => { arguments_with_layouts!((lhs_arg, lhs_layout), (rhs_arg, rhs_layout)); generic_eq( env, layout_interner, layout_ids, lhs_arg, rhs_arg, lhs_layout, rhs_layout, ) } NotEq => { arguments_with_layouts!((lhs_arg, lhs_layout), (rhs_arg, rhs_layout)); generic_neq( env, layout_interner, layout_ids, lhs_arg, rhs_arg, lhs_layout, rhs_layout, ) } And => { // The (&&) operator arguments!(lhs_arg, rhs_arg); let bool_val = env.builder.build_and( lhs_arg.into_int_value(), rhs_arg.into_int_value(), "bool_and", ); BasicValueEnum::IntValue(bool_val) } Or => { // The (||) operator arguments!(lhs_arg, rhs_arg); let bool_val = env.builder.build_or( lhs_arg.into_int_value(), rhs_arg.into_int_value(), "bool_or", ); BasicValueEnum::IntValue(bool_val) } Not => { // The (!) operator arguments!(arg); let bool_val = env.builder.build_not(arg.into_int_value(), "bool_not"); BasicValueEnum::IntValue(bool_val) } Hash => { unimplemented!() } ListMap | ListMap2 | ListMap3 | ListMap4 | ListSortWith => { unreachable!("these are higher order, and are handled elsewhere") } BoxExpr | UnboxExpr => { unreachable!("The {:?} operation is turned into mono Expr", op) } PtrCast => { arguments!(data_ptr); let target_type = basic_type_from_layout(env, layout_interner, layout_interner.get_repr(layout)) .into_pointer_type(); debug_assert!(data_ptr.is_pointer_value()); env.builder .build_pointer_cast(data_ptr.into_pointer_value(), target_type, "ptr_cast") .into() } PtrWrite | RefCountIncRcPtr | RefCountDecRcPtr | RefCountIncDataPtr | RefCountDecDataPtr => { unreachable!("Not used in LLVM backend: {:?}", op); } RefCountIsUnique => { arguments_with_layouts!((data_ptr, data_layout)); let ptr = env.builder.build_pointer_cast( data_ptr.into_pointer_value(), env.context.i8_type().ptr_type(AddressSpace::default()), "cast_to_i8_ptr", ); let value_ptr = match layout_interner.get_repr(data_layout) { LayoutRepr::Union(union_layout) if union_layout.stores_tag_id_in_pointer(env.target_info) => { tag_pointer_clear_tag_id(env, ptr) } _ => ptr, }; let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, value_ptr); BasicValueEnum::IntValue(refcount_ptr.is_1(env)) } Unreachable => { match RocReturn::from_layout(layout_interner, layout_interner.get_repr(layout)) { RocReturn::Return => { let basic_type = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(layout), ); basic_type.const_zero() } RocReturn::ByPointer => { let basic_type = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(layout), ); let ptr = env.builder.build_alloca(basic_type, "unreachable_alloca"); env.builder.build_store(ptr, basic_type.const_zero()); ptr.into() } } } DictPseudoSeed => { // Dict.pseudoSeed : {} -> u64 call_bitcode_fn(env, &[], bitcode::UTILS_DICT_PSEUDO_SEED) } } } fn intwidth_from_layout(layout: InLayout) -> IntWidth { layout.to_int_width() } fn build_int_binop<'ctx>( env: &Env<'_, 'ctx, '_>, parent: FunctionValue<'ctx>, int_width: IntWidth, lhs: IntValue<'ctx>, rhs: IntValue<'ctx>, op: LowLevel, ) -> BasicValueEnum<'ctx> { use inkwell::IntPredicate::*; use roc_module::low_level::LowLevel::*; let bd = env.builder; match op { NumAdd => { let result = env .call_intrinsic( &LLVM_ADD_WITH_OVERFLOW[int_width], &[lhs.into(), rhs.into()], ) .into_struct_value(); throw_on_overflow(env, parent, result, "integer addition overflowed!") } NumAddWrap => bd.build_int_add(lhs, rhs, "add_int_wrap").into(), NumAddChecked => env.call_intrinsic( &LLVM_ADD_WITH_OVERFLOW[int_width], &[lhs.into(), rhs.into()], ), NumAddSaturated => { env.call_intrinsic(&LLVM_ADD_SATURATED[int_width], &[lhs.into(), rhs.into()]) } NumSub => { let result = env .call_intrinsic( &LLVM_SUB_WITH_OVERFLOW[int_width], &[lhs.into(), rhs.into()], ) .into_struct_value(); throw_on_overflow(env, parent, result, "integer subtraction overflowed!") } NumSubWrap => bd.build_int_sub(lhs, rhs, "sub_int").into(), NumSubChecked => env.call_intrinsic( &LLVM_SUB_WITH_OVERFLOW[int_width], &[lhs.into(), rhs.into()], ), NumSubSaturated => { env.call_intrinsic(&LLVM_SUB_SATURATED[int_width], &[lhs.into(), rhs.into()]) } NumMul => { let result = env .call_intrinsic( &LLVM_MUL_WITH_OVERFLOW[int_width], &[lhs.into(), rhs.into()], ) .into_struct_value(); throw_on_overflow(env, parent, result, "integer multiplication overflowed!") } NumMulWrap => bd.build_int_mul(lhs, rhs, "mul_int").into(), NumMulSaturated => call_bitcode_fn( env, &[lhs.into(), rhs.into()], &bitcode::NUM_MUL_SATURATED_INT[int_width], ), NumMulChecked => env.call_intrinsic( &LLVM_MUL_WITH_OVERFLOW[int_width], &[lhs.into(), rhs.into()], ), NumGt => { if int_width.is_signed() { bd.build_int_compare(SGT, lhs, rhs, "gt_int").into() } else { bd.build_int_compare(UGT, lhs, rhs, "gt_uint").into() } } NumGte => { if int_width.is_signed() { bd.build_int_compare(SGE, lhs, rhs, "gte_int").into() } else { bd.build_int_compare(UGE, lhs, rhs, "gte_uint").into() } } NumLt => { if int_width.is_signed() { bd.build_int_compare(SLT, lhs, rhs, "lt_int").into() } else { bd.build_int_compare(ULT, lhs, rhs, "lt_uint").into() } } NumLte => { if int_width.is_signed() { bd.build_int_compare(SLE, lhs, rhs, "lte_int").into() } else { bd.build_int_compare(ULE, lhs, rhs, "lte_uint").into() } } NumRemUnchecked => { if int_width.is_signed() { bd.build_int_signed_rem(lhs, rhs, "rem_int").into() } else { bd.build_int_unsigned_rem(lhs, rhs, "rem_uint").into() } } NumIsMultipleOf => { // this builds the following construct // // if (rhs == 0 || rhs == -1) { // // lhs is a multiple of rhs iff // // // // - rhs == -1 // // - both rhs and lhs are 0 // // // // the -1 case is important for overflow reasons `isize::MIN % -1` crashes in rust // (rhs == -1) || (lhs == 0) // } else { // let rem = lhs % rhs; // rem == 0 // } // // NOTE we'd like the branches to be swapped for better branch prediction, // but llvm normalizes to the above ordering in -O3 let zero = rhs.get_type().const_zero(); let neg_1 = rhs.get_type().const_int(-1i64 as u64, false); let is_signed = int_width.is_signed(); let special_block = env.context.append_basic_block(parent, "special_block"); let default_block = env.context.append_basic_block(parent, "default_block"); let cont_block = env.context.append_basic_block(parent, "branchcont"); if is_signed { bd.build_switch( rhs, default_block, &[(zero, special_block), (neg_1, special_block)], ) } else { bd.build_switch(rhs, default_block, &[(zero, special_block)]) }; let condition_rem = { bd.position_at_end(default_block); let rem = if is_signed { bd.build_int_signed_rem(lhs, rhs, "int_rem") } else { bd.build_int_unsigned_rem(lhs, rhs, "uint_rem") }; let result = bd.build_int_compare(IntPredicate::EQ, rem, zero, "is_zero_rem"); bd.build_unconditional_branch(cont_block); result }; let condition_special = { bd.position_at_end(special_block); let is_zero = bd.build_int_compare(IntPredicate::EQ, lhs, zero, "is_zero_lhs"); let result = if is_signed { let is_neg_one = bd.build_int_compare(IntPredicate::EQ, rhs, neg_1, "is_neg_one_rhs"); bd.build_or(is_neg_one, is_zero, "cond") } else { is_zero }; bd.build_unconditional_branch(cont_block); result }; { bd.position_at_end(cont_block); let phi = bd.build_phi(env.context.bool_type(), "branch"); phi.add_incoming(&[ (&condition_rem, default_block), (&condition_special, special_block), ]); phi.as_basic_value() } } NumPowInt => call_bitcode_fn( env, &[lhs.into(), rhs.into()], &bitcode::NUM_POW_INT[int_width], ), NumDivTruncUnchecked => { if int_width.is_signed() { bd.build_int_signed_div(lhs, rhs, "div_int").into() } else { bd.build_int_unsigned_div(lhs, rhs, "div_uint").into() } } NumDivCeilUnchecked => call_bitcode_fn( env, &[lhs.into(), rhs.into()], &bitcode::NUM_DIV_CEIL[int_width], ), NumBitwiseAnd => bd.build_and(lhs, rhs, "int_bitwise_and").into(), NumBitwiseXor => bd.build_xor(lhs, rhs, "int_bitwise_xor").into(), NumBitwiseOr => bd.build_or(lhs, rhs, "int_bitwise_or").into(), NumShiftLeftBy => bd.build_left_shift(lhs, rhs, "int_shift_left").into(), NumShiftRightBy => bd .build_right_shift(lhs, rhs, true, "int_shift_right") .into(), NumShiftRightZfBy => bd .build_right_shift(lhs, rhs, false, "int_shift_right_zf") .into(), _ => { unreachable!("Unrecognized int binary operation: {:?}", op); } } } pub fn build_num_binop<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, parent: FunctionValue<'ctx>, lhs_arg: BasicValueEnum<'ctx>, lhs_layout: InLayout<'a>, rhs_arg: BasicValueEnum<'ctx>, rhs_layout: InLayout<'a>, return_layout: InLayout<'a>, op: LowLevel, ) -> BasicValueEnum<'ctx> { match ( layout_interner.get_repr(lhs_layout), layout_interner.get_repr(rhs_layout), ) { (LayoutRepr::Builtin(lhs_builtin), LayoutRepr::Builtin(rhs_builtin)) if lhs_builtin == rhs_builtin => { use roc_mono::layout::Builtin::*; match lhs_builtin { Int(int_width) => build_int_binop( env, parent, int_width, lhs_arg.into_int_value(), rhs_arg.into_int_value(), op, ), Float(float_width) => build_float_binop( env, float_width, lhs_arg.into_float_value(), rhs_arg.into_float_value(), op, ), Decimal => build_dec_binop( env, layout_interner, parent, lhs_arg, rhs_arg, return_layout, op, ), _ => { unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid builtin layout: ({:?})", op, lhs_layout); } } } _ => { unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid layouts. The 2 layouts were: ({:?}) and ({:?})", op, lhs_layout, rhs_layout); } } } fn build_float_binop<'ctx>( env: &Env<'_, 'ctx, '_>, float_width: FloatWidth, lhs: FloatValue<'ctx>, rhs: FloatValue<'ctx>, op: LowLevel, ) -> BasicValueEnum<'ctx> { use inkwell::FloatPredicate::*; use roc_module::low_level::LowLevel::*; let bd = env.builder; match op { NumAdd => bd.build_float_add(lhs, rhs, "add_float").into(), NumAddChecked => { let context = env.context; let result = bd.build_float_add(lhs, rhs, "add_float"); let is_finite = call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE[float_width]) .into_int_value(); let is_infinite = bd.build_not(is_finite, "negate"); let struct_type = context.struct_type( &[context.f64_type().into(), context.bool_type().into()], false, ); let struct_value = { let v1 = struct_type.const_zero(); let v2 = bd.build_insert_value(v1, result, 0, "set_result").unwrap(); let v3 = bd .build_insert_value(v2, is_infinite, 1, "set_is_infinite") .unwrap(); v3.into_struct_value() }; struct_value.into() } NumAddWrap => unreachable!("wrapping addition is not defined on floats"), NumSub => bd.build_float_sub(lhs, rhs, "sub_float").into(), NumSubChecked => { let context = env.context; let result = bd.build_float_sub(lhs, rhs, "sub_float"); let is_finite = call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE[float_width]) .into_int_value(); let is_infinite = bd.build_not(is_finite, "negate"); let struct_type = context.struct_type( &[context.f64_type().into(), context.bool_type().into()], false, ); let struct_value = { let v1 = struct_type.const_zero(); let v2 = bd.build_insert_value(v1, result, 0, "set_result").unwrap(); let v3 = bd .build_insert_value(v2, is_infinite, 1, "set_is_infinite") .unwrap(); v3.into_struct_value() }; struct_value.into() } NumSubWrap => unreachable!("wrapping subtraction is not defined on floats"), NumMul => bd.build_float_mul(lhs, rhs, "mul_float").into(), NumMulSaturated => bd.build_float_mul(lhs, rhs, "mul_float").into(), NumMulChecked => { let context = env.context; let result = bd.build_float_mul(lhs, rhs, "mul_float"); let is_finite = call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE[float_width]) .into_int_value(); let is_infinite = bd.build_not(is_finite, "negate"); let struct_type = context.struct_type( &[context.f64_type().into(), context.bool_type().into()], false, ); let struct_value = { let v1 = struct_type.const_zero(); let v2 = bd.build_insert_value(v1, result, 0, "set_result").unwrap(); let v3 = bd .build_insert_value(v2, is_infinite, 1, "set_is_infinite") .unwrap(); v3.into_struct_value() }; struct_value.into() } NumMulWrap => unreachable!("wrapping multiplication is not defined on floats"), NumGt => bd.build_float_compare(OGT, lhs, rhs, "float_gt").into(), NumGte => bd.build_float_compare(OGE, lhs, rhs, "float_gte").into(), NumLt => bd.build_float_compare(OLT, lhs, rhs, "float_lt").into(), NumLte => bd.build_float_compare(OLE, lhs, rhs, "float_lte").into(), NumDivFrac => bd.build_float_div(lhs, rhs, "div_float").into(), NumPow => call_bitcode_fn( env, &[lhs.into(), rhs.into()], &bitcode::NUM_POW[float_width], ), _ => { unreachable!("Unrecognized int binary operation: {:?}", op); } } } fn throw_on_overflow<'ctx>( env: &Env<'_, 'ctx, '_>, parent: FunctionValue<'ctx>, result: StructValue<'ctx>, // of the form { value: T, has_overflowed: bool } message: &str, ) -> BasicValueEnum<'ctx> { let bd = env.builder; let context = env.context; let has_overflowed = bd.build_extract_value(result, 1, "has_overflowed").unwrap(); let condition = bd.build_int_compare( IntPredicate::EQ, has_overflowed.into_int_value(), context.bool_type().const_zero(), "has_not_overflowed", ); let then_block = context.append_basic_block(parent, "then_block"); let throw_block = context.append_basic_block(parent, "throw_block"); bd.build_conditional_branch(condition, then_block, throw_block); bd.position_at_end(throw_block); throw_internal_exception(env, parent, message); bd.position_at_end(then_block); bd.build_extract_value(result, 0, "operation_result") .unwrap() } fn dec_split_into_words<'ctx>( env: &Env<'_, 'ctx, '_>, value: IntValue<'ctx>, ) -> (IntValue<'ctx>, IntValue<'ctx>) { let int_64 = env.context.i128_type().const_int(64, false); let int_64_type = env.context.i64_type(); let left_bits_i128 = env .builder .build_right_shift(value, int_64, false, "left_bits_i128"); ( env.builder.build_int_cast(value, int_64_type, ""), env.builder.build_int_cast(left_bits_i128, int_64_type, ""), ) } fn dec_alloca<'ctx>(env: &Env<'_, 'ctx, '_>, value: IntValue<'ctx>) -> PointerValue<'ctx> { let dec_type = zig_dec_type(env); let alloca = env.builder.build_alloca(dec_type, "dec_alloca"); let instruction = alloca.as_instruction_value().unwrap(); instruction.set_alignment(16).unwrap(); let ptr = env.builder.build_pointer_cast( alloca, value.get_type().ptr_type(AddressSpace::default()), "cast_to_i128_ptr", ); env.builder.build_store(ptr, value); alloca } fn dec_to_str<'ctx>(env: &Env<'_, 'ctx, '_>, dec: BasicValueEnum<'ctx>) -> BasicValueEnum<'ctx> { use roc_target::OperatingSystem::*; let dec = dec.into_int_value(); match env.target_info.operating_system { Windows => { // call_str_bitcode_fn( env, &[], &[dec_alloca(env, dec).into()], BitcodeReturns::Str, bitcode::DEC_TO_STR, ) } Unix => { let (low, high) = dec_split_into_words(env, dec); call_str_bitcode_fn( env, &[], &[low.into(), high.into()], BitcodeReturns::Str, bitcode::DEC_TO_STR, ) } Wasi => unimplemented!(), } } fn dec_to_i128<'ctx>(env: &Env<'_, 'ctx, '_>, dec: BasicValueEnum<'ctx>) -> BasicValueEnum<'ctx> { use roc_target::OperatingSystem::*; let dec = dec.into_int_value(); match env.target_info.operating_system { Windows => { // call_bitcode_fn(env, &[dec_alloca(env, dec).into()], bitcode::DEC_TO_I128) } Unix => { let (low, high) = dec_split_into_words(env, dec); call_bitcode_fn(env, &[low.into(), high.into()], bitcode::DEC_TO_I128) } Wasi => unimplemented!(), } } fn dec_binop_with_overflow<'ctx>( env: &Env<'_, 'ctx, '_>, fn_name: &str, lhs: BasicValueEnum<'ctx>, rhs: BasicValueEnum<'ctx>, ) -> StructValue<'ctx> { use roc_target::OperatingSystem::*; let lhs = lhs.into_int_value(); let rhs = rhs.into_int_value(); let return_type = zig_with_overflow_roc_dec(env); let return_alloca = env.builder.build_alloca(return_type, "return_alloca"); match env.target_info.operating_system { Windows => { call_void_bitcode_fn( env, &[ return_alloca.into(), dec_alloca(env, lhs).into(), dec_alloca(env, rhs).into(), ], fn_name, ); } Unix => { let (lhs_low, lhs_high) = dec_split_into_words(env, lhs); let (rhs_low, rhs_high) = dec_split_into_words(env, rhs); call_void_bitcode_fn( env, &[ return_alloca.into(), lhs_low.into(), lhs_high.into(), rhs_low.into(), rhs_high.into(), ], fn_name, ); } Wasi => unimplemented!(), } env.builder .new_build_load(return_type, return_alloca, "load_dec") .into_struct_value() } pub(crate) fn dec_binop_with_unchecked<'ctx>( env: &Env<'_, 'ctx, '_>, fn_name: &str, lhs: BasicValueEnum<'ctx>, rhs: BasicValueEnum<'ctx>, ) -> BasicValueEnum<'ctx> { use roc_target::OperatingSystem::*; let lhs = lhs.into_int_value(); let rhs = rhs.into_int_value(); match env.target_info.operating_system { Windows => { // windows is much nicer for us here call_bitcode_fn( env, &[dec_alloca(env, lhs).into(), dec_alloca(env, rhs).into()], fn_name, ) } Unix => { let (lhs_low, lhs_high) = dec_split_into_words(env, lhs); let (rhs_low, rhs_high) = dec_split_into_words(env, rhs); call_bitcode_fn( env, &[ lhs_low.into(), lhs_high.into(), rhs_low.into(), rhs_high.into(), ], fn_name, ) } Wasi => unimplemented!(), } } /// Zig returns a nominal `WithOverflow(Dec)` struct (see [zig_with_overflow_roc_dec]), /// but the Roc side may flatten the overflow struct. LLVM does not admit comparisons /// between the two representations, so always cast to the Roc representation. fn change_with_overflow_dec_to_roc_type<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, val: StructValue<'ctx>, return_layout: InLayout<'a>, ) -> BasicValueEnum<'ctx> { let return_type = convert::basic_type_from_layout( env, layout_interner, layout_interner.get_repr(return_layout), ); let casted = cast_basic_basic(env.builder, val.into(), return_type); use_roc_value( env, layout_interner, layout_interner.get_repr(return_layout), casted, "use_dec_with_overflow", ) } fn build_dec_binop<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, parent: FunctionValue<'ctx>, lhs: BasicValueEnum<'ctx>, rhs: BasicValueEnum<'ctx>, return_layout: InLayout<'a>, op: LowLevel, ) -> BasicValueEnum<'ctx> { use roc_module::low_level::LowLevel::*; match op { NumAddChecked => { let val = dec_binop_with_overflow(env, bitcode::DEC_ADD_WITH_OVERFLOW, lhs, rhs); change_with_overflow_dec_to_roc_type(env, layout_interner, val, return_layout) } NumSubChecked => { let val = dec_binop_with_overflow(env, bitcode::DEC_SUB_WITH_OVERFLOW, lhs, rhs); change_with_overflow_dec_to_roc_type(env, layout_interner, val, return_layout) } NumMulChecked => { let val = dec_binop_with_overflow(env, bitcode::DEC_MUL_WITH_OVERFLOW, lhs, rhs); change_with_overflow_dec_to_roc_type(env, layout_interner, val, return_layout) } NumAdd => build_dec_binop_throw_on_overflow( env, parent, bitcode::DEC_ADD_WITH_OVERFLOW, lhs, rhs, "decimal addition overflowed", ), NumSub => build_dec_binop_throw_on_overflow( env, parent, bitcode::DEC_SUB_WITH_OVERFLOW, lhs, rhs, "decimal subtraction overflowed", ), NumMul => build_dec_binop_throw_on_overflow( env, parent, bitcode::DEC_MUL_WITH_OVERFLOW, lhs, rhs, "decimal multiplication overflowed", ), NumDivFrac => dec_binop_with_unchecked(env, bitcode::DEC_DIV, lhs, rhs), _ => { unreachable!("Unrecognized int binary operation: {:?}", op); } } } fn build_dec_binop_throw_on_overflow<'ctx>( env: &Env<'_, 'ctx, '_>, parent: FunctionValue<'ctx>, operation: &str, lhs: BasicValueEnum<'ctx>, rhs: BasicValueEnum<'ctx>, message: &str, ) -> BasicValueEnum<'ctx> { let result = dec_binop_with_overflow(env, operation, lhs, rhs); let value = throw_on_overflow(env, parent, result, message).into_struct_value(); env.builder.build_extract_value(value, 0, "num").unwrap() } fn int_type_signed_min(int_type: IntType) -> IntValue { let width = int_type.get_bit_width(); debug_assert!(width <= 128); let shift = 128 - width as usize; if shift < 64 { let min = i128::MIN >> shift; let a = min as u64; let b = (min >> 64) as u64; int_type.const_int_arbitrary_precision(&[b, a]) } else { int_type.const_int((i128::MIN >> shift) as u64, false) } } fn build_int_unary_op<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_interner: &STLayoutInterner<'a>, parent: FunctionValue<'ctx>, arg: IntValue<'ctx>, arg_width: IntWidth, arg_int_type: IntType<'ctx>, op: LowLevel, return_layout: InLayout<'a>, ) -> BasicValueEnum<'ctx> { use roc_module::low_level::LowLevel::*; let bd = env.builder; match op { NumNeg => { // integer abs overflows when applied to the minimum value of a signed type int_neg_raise_on_overflow(env, arg, arg_int_type) } NumAbs => { // integer abs overflows when applied to the minimum value of a signed type int_abs_raise_on_overflow(env, arg, arg_int_type) } NumToFrac => { // This is an Int, so we need to convert it. let target_float_type = match layout_interner.get_repr(return_layout) { LayoutRepr::Builtin(Builtin::Float(float_width)) => { convert::float_type_from_float_width(env, float_width) } _ => internal_error!("There can only be floats here!"), }; bd.build_cast( InstructionOpcode::SIToFP, arg, target_float_type, "i64_to_f64", ) } NumToIntChecked => { // return_layout : Result N [OutOfBounds]* ~ { result: N, out_of_bounds: bool } let target_int_width = match layout_interner.get_repr(return_layout) { LayoutRepr::Struct(field_layouts) if field_layouts.len() == 2 => { debug_assert!(layout_interner.eq_repr(field_layouts[1], Layout::BOOL)); field_layouts[0].to_int_width() } layout => internal_error!( "There can only be a result layout here, found {:?}!", layout ), }; let arg_always_fits_in_target = (arg_width.stack_size() < target_int_width.stack_size() && ( // If the arg is unsigned, it will always fit in either a signed or unsigned // int of a larger width. !arg_width.is_signed() || // Otherwise if the arg is signed, it will always fit in a signed int of a // larger width. (target_int_width.is_signed() ) ) ) || // Or if the two types are the same, they trivially fit. arg_width == target_int_width; // How the return type needs to be stored on the stack. let return_type_stack_type = convert::basic_type_from_layout( env, layout_interner, layout_interner.get_repr(return_layout), ) .into_struct_type(); // How the return type is actually used, in the Roc calling convention. let return_type_use_type = convert::argument_type_from_layout( env, layout_interner, layout_interner.get_repr(return_layout), ); if arg_always_fits_in_target { // This is guaranteed to succeed so we can just make it an int cast and let LLVM // optimize it away. let target_int_type = convert::int_type_from_int_width(env, target_int_width); let target_int_val: BasicValueEnum<'ctx> = env .builder .build_int_cast_sign_flag( arg, target_int_type, target_int_width.is_signed(), "int_cast", ) .into(); let r = return_type_stack_type.const_zero(); let r = bd .build_insert_value(r, target_int_val, 0, "converted_int") .unwrap(); let r = bd .build_insert_value(r, env.context.bool_type().const_zero(), 1, "out_of_bounds") .unwrap(); r.into_struct_value().into() } else { let intrinsic = if !arg_width.is_signed() { // We are trying to convert from unsigned to signed/unsigned of same or lesser width, e.g. // u16 -> i16, u16 -> i8, or u16 -> u8. We only need to check that the argument // value fits in the MAX target type value. &bitcode::NUM_INT_TO_INT_CHECKING_MAX[target_int_width][arg_width] } else { // We are trying to convert from signed to signed/unsigned of same or lesser width, e.g. // i16 -> u16, i16 -> i8, or i16 -> u8. We need to check that the argument value fits in // the MAX and MIN target type. &bitcode::NUM_INT_TO_INT_CHECKING_MAX_AND_MIN[target_int_width][arg_width] }; let result = match env.target_info.ptr_width() { PtrWidth::Bytes4 => { let zig_function = env.module.get_function(intrinsic).unwrap(); let zig_function_type = zig_function.get_type(); match zig_function_type.get_return_type() { Some(_) => call_str_bitcode_fn( env, &[], &[arg.into()], BitcodeReturns::Basic, intrinsic, ), None => { let return_type = zig_to_int_checked_result_type( env, target_int_width.type_name(), ); let zig_return_alloca = create_entry_block_alloca( env, parent, return_type.into(), "num_to_int", ); call_void_bitcode_fn( env, &[zig_return_alloca.into(), arg.into()], intrinsic, ); let roc_return_type = basic_type_from_layout( env, layout_interner, layout_interner.get_repr(return_layout), ) .ptr_type(AddressSpace::default()); let roc_return_alloca = env.builder.build_pointer_cast( zig_return_alloca, roc_return_type, "cast_to_roc", ); load_roc_value( env, layout_interner, layout_interner.get_repr(return_layout), roc_return_alloca, "num_to_int", ) } } } PtrWidth::Bytes8 => { if target_int_width.stack_size() as usize > env.target_info.ptr_size() { let bitcode_return_type = zig_to_int_checked_result_type(env, target_int_width.type_name()); call_bitcode_fn_fixing_for_convention( env, layout_interner, bitcode_return_type, &[arg.into()], return_layout, intrinsic, ) } else { call_bitcode_fn(env, &[arg.into()], intrinsic) } } }; complex_bitcast_check_size(env, result, return_type_use_type, "cast_bitpacked") } } NumCountLeadingZeroBits => call_bitcode_fn( env, &[arg.into()], &bitcode::NUM_COUNT_LEADING_ZERO_BITS[arg_width], ), NumCountTrailingZeroBits => call_bitcode_fn( env, &[arg.into()], &bitcode::NUM_COUNT_TRAILING_ZERO_BITS[arg_width], ), NumCountOneBits => { call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_COUNT_ONE_BITS[arg_width]) } _ => { unreachable!("Unrecognized int unary operation: {:?}", op); } } } fn int_neg_raise_on_overflow<'ctx>( env: &Env<'_, 'ctx, '_>, arg: IntValue<'ctx>, int_type: IntType<'ctx>, ) -> BasicValueEnum<'ctx> { let builder = env.builder; let min_val = int_type_signed_min(int_type); let condition = builder.build_int_compare(IntPredicate::EQ, arg, min_val, "is_min_val"); 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"); env.builder .build_conditional_branch(condition, then_block, else_block); builder.position_at_end(then_block); throw_internal_exception( env, parent, "integer negation overflowed because its argument is the minimum value", ); builder.position_at_end(else_block); builder.build_int_neg(arg, "negate_int").into() } fn int_abs_raise_on_overflow<'ctx>( env: &Env<'_, 'ctx, '_>, arg: IntValue<'ctx>, int_type: IntType<'ctx>, ) -> BasicValueEnum<'ctx> { let builder = env.builder; let min_val = int_type_signed_min(int_type); let condition = builder.build_int_compare(IntPredicate::EQ, arg, min_val, "is_min_val"); 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"); env.builder .build_conditional_branch(condition, then_block, else_block); builder.position_at_end(then_block); throw_internal_exception( env, parent, "integer absolute overflowed because its argument is the minimum value", ); builder.position_at_end(else_block); int_abs_with_overflow(env, arg, int_type) } fn int_abs_with_overflow<'ctx>( env: &Env<'_, 'ctx, '_>, arg: IntValue<'ctx>, int_type: IntType<'ctx>, ) -> BasicValueEnum<'ctx> { // This is how libc's abs() is implemented - it uses no branching! // // abs = \arg -> // shifted = arg >>> 63 // // (xor arg shifted) - shifted let bd = env.builder; let shifted_name = "abs_shift_right"; let shifted_alloca = { let bits_to_shift = int_type.get_bit_width() as u64 - 1; let shift_val = int_type.const_int(bits_to_shift, false); let shifted = bd.build_right_shift(arg, shift_val, true, shifted_name); let alloca = bd.build_alloca(int_type, "#int_abs_help"); // shifted = arg >>> 63 bd.build_store(alloca, shifted); alloca }; let xored_arg = bd.build_xor( arg, bd.new_build_load(int_type, shifted_alloca, shifted_name) .into_int_value(), "xor_arg_shifted", ); BasicValueEnum::IntValue( bd.build_int_sub( xored_arg, bd.new_build_load(int_type, shifted_alloca, shifted_name) .into_int_value(), "sub_xored_shifted", ), ) } fn build_float_unary_op<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, layout: InLayout<'a>, arg: FloatValue<'ctx>, op: LowLevel, float_width: FloatWidth, // arg width ) -> BasicValueEnum<'ctx> { use roc_module::low_level::LowLevel::*; let bd = env.builder; // TODO: Handle different sized floats match op { NumNeg => bd.build_float_neg(arg, "negate_float").into(), NumAbs => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_FABS[float_width]), NumSqrtUnchecked => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_SQRT[float_width]), NumLogUnchecked => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_LOG[float_width]), NumToFrac => { let return_width = match layout_interner.get_repr(layout) { LayoutRepr::Builtin(Builtin::Float(return_width)) => return_width, _ => internal_error!("Layout for returning is not Float : {:?}", layout), }; match (float_width, return_width) { (FloatWidth::F32, FloatWidth::F32) => arg.into(), (FloatWidth::F32, FloatWidth::F64) => bd.build_cast( InstructionOpcode::FPExt, arg, env.context.f64_type(), "f32_to_f64", ), (FloatWidth::F64, FloatWidth::F32) => bd.build_cast( InstructionOpcode::FPTrunc, arg, env.context.f32_type(), "f64_to_f32", ), (FloatWidth::F64, FloatWidth::F64) => arg.into(), } } NumCeiling => { let int_width = match layout_interner.get_repr(layout) { LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width, _ => internal_error!("Ceiling return layout is not int: {:?}", layout), }; match float_width { FloatWidth::F32 => { call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_CEILING_F32[int_width]) } FloatWidth::F64 => { call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_CEILING_F64[int_width]) } } } NumFloor => { let int_width = match layout_interner.get_repr(layout) { LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width, _ => internal_error!("Floor return layout is not int: {:?}", layout), }; match float_width { FloatWidth::F32 => { call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_FLOOR_F32[int_width]) } FloatWidth::F64 => { call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_FLOOR_F64[int_width]) } } } NumRound => { let int_width = match layout_interner.get_repr(layout) { LayoutRepr::Builtin(Builtin::Int(int_width)) => int_width, _ => internal_error!("Round return layout is not int: {:?}", layout), }; match float_width { FloatWidth::F32 => { call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ROUND_F32[int_width]) } FloatWidth::F64 => { call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ROUND_F64[int_width]) } } } NumIsNan => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_NAN[float_width]), NumIsInfinite => { call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_INFINITE[float_width]) } NumIsFinite => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_FINITE[float_width]), // trigonometry NumSin => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_SIN[float_width]), NumCos => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_COS[float_width]), NumAtan => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ATAN[float_width]), NumAcos => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ACOS[float_width]), NumAsin => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ASIN[float_width]), _ => { unreachable!("Unrecognized int unary operation: {:?}", op); } } } pub(crate) fn run_higher_order_low_level<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, scope: &Scope<'a, 'ctx>, return_layout: InLayout<'a>, func_spec: FuncSpec, higher_order: &HigherOrderLowLevel<'a>, ) -> BasicValueEnum<'ctx> { use roc_mono::ir::PassedFunction; use roc_mono::low_level::HigherOrder::*; let HigherOrderLowLevel { op, passed_function, .. } = higher_order; let PassedFunction { argument_layouts, return_layout: result_layout, owns_captured_environment: function_owns_closure_data, name: function_name, captured_environment, .. } = *passed_function; // macros because functions cause lifetime issues related to the `env` or `layout_ids` macro_rules! function_details { () => {{ let function = function_value_by_func_spec( env, func_spec, function_name.name(), argument_layouts, function_name.niche(), return_layout, ); let (closure, closure_layout) = load_symbol_and_lambda_set(layout_interner, scope, &captured_environment); (function, closure, closure_layout) }}; } match op { ListMap { xs } => { // List.map : List before, (before -> after) -> List after let (list, list_layout) = scope.load_symbol_and_layout(xs); let (function, closure, closure_layout) = function_details!(); match ( layout_interner.get_repr(list_layout), layout_interner.get_repr(return_layout), ) { ( LayoutRepr::Builtin(Builtin::List(element_layout)), LayoutRepr::Builtin(Builtin::List(result_layout)), ) => { let argument_layouts = &[element_layout]; let roc_function_call = roc_function_call( env, layout_interner, layout_ids, function, closure, closure_layout, function_owns_closure_data, argument_layouts, result_layout, ); list_map( env, layout_interner, roc_function_call, list, element_layout, result_layout, ) } _ => unreachable!("invalid list layout"), } } ListMap2 { xs, ys } => { let (list1, list1_layout) = scope.load_symbol_and_layout(xs); let (list2, list2_layout) = scope.load_symbol_and_layout(ys); let (function, closure, closure_layout) = function_details!(); match ( layout_interner.get_repr(list1_layout), layout_interner.get_repr(list2_layout), layout_interner.get_repr(return_layout), ) { ( LayoutRepr::Builtin(Builtin::List(element1_layout)), LayoutRepr::Builtin(Builtin::List(element2_layout)), LayoutRepr::Builtin(Builtin::List(result_layout)), ) => { let argument_layouts = &[element1_layout, element2_layout]; let roc_function_call = roc_function_call( env, layout_interner, layout_ids, function, closure, closure_layout, function_owns_closure_data, argument_layouts, result_layout, ); list_map2( env, layout_interner, layout_ids, roc_function_call, list1, list2, element1_layout, element2_layout, result_layout, ) } _ => unreachable!("invalid list layout"), } } ListMap3 { xs, ys, zs } => { let (list1, list1_layout) = scope.load_symbol_and_layout(xs); let (list2, list2_layout) = scope.load_symbol_and_layout(ys); let (list3, list3_layout) = scope.load_symbol_and_layout(zs); let (function, closure, closure_layout) = function_details!(); match ( layout_interner.get_repr(list1_layout), layout_interner.get_repr(list2_layout), layout_interner.get_repr(list3_layout), layout_interner.get_repr(return_layout), ) { ( LayoutRepr::Builtin(Builtin::List(element1_layout)), LayoutRepr::Builtin(Builtin::List(element2_layout)), LayoutRepr::Builtin(Builtin::List(element3_layout)), LayoutRepr::Builtin(Builtin::List(result_layout)), ) => { let argument_layouts = &[element1_layout, element2_layout, element3_layout]; let roc_function_call = roc_function_call( env, layout_interner, layout_ids, function, closure, closure_layout, function_owns_closure_data, argument_layouts, result_layout, ); list_map3( env, layout_interner, layout_ids, roc_function_call, list1, list2, list3, element1_layout, element2_layout, element3_layout, result_layout, ) } _ => unreachable!("invalid list layout"), } } ListMap4 { xs, ys, zs, ws } => { let (list1, list1_layout) = scope.load_symbol_and_layout(xs); let (list2, list2_layout) = scope.load_symbol_and_layout(ys); let (list3, list3_layout) = scope.load_symbol_and_layout(zs); let (list4, list4_layout) = scope.load_symbol_and_layout(ws); let (function, closure, closure_layout) = function_details!(); match ( layout_interner.get_repr(list1_layout), layout_interner.get_repr(list2_layout), layout_interner.get_repr(list3_layout), layout_interner.get_repr(list4_layout), layout_interner.get_repr(return_layout), ) { ( LayoutRepr::Builtin(Builtin::List(element1_layout)), LayoutRepr::Builtin(Builtin::List(element2_layout)), LayoutRepr::Builtin(Builtin::List(element3_layout)), LayoutRepr::Builtin(Builtin::List(element4_layout)), LayoutRepr::Builtin(Builtin::List(result_layout)), ) => { let argument_layouts = &[ element1_layout, element2_layout, element3_layout, element4_layout, ]; let roc_function_call = roc_function_call( env, layout_interner, layout_ids, function, closure, closure_layout, function_owns_closure_data, argument_layouts, result_layout, ); list_map4( env, layout_interner, layout_ids, roc_function_call, list1, list2, list3, list4, element1_layout, element2_layout, element3_layout, element4_layout, result_layout, ) } _ => unreachable!("invalid list layout"), } } ListSortWith { xs } => { // List.sortWith : List a, (a, a -> Ordering) -> List a let (list, list_layout) = scope.load_symbol_and_layout(xs); let (function, closure, closure_layout) = function_details!(); match layout_interner.get_repr(list_layout) { LayoutRepr::Builtin(Builtin::List(element_layout)) => { use crate::llvm::bitcode::build_compare_wrapper; let argument_layouts = &[element_layout, element_layout]; let compare_wrapper = build_compare_wrapper( env, layout_interner, layout_ids, function, closure_layout, element_layout, ) .as_global_value() .as_pointer_value(); let roc_function_call = roc_function_call( env, layout_interner, layout_ids, function, closure, closure_layout, function_owns_closure_data, argument_layouts, result_layout, ); list_sort_with( env, layout_interner, roc_function_call, compare_wrapper, list, element_layout, ) } _ => unreachable!("invalid list layout"), } } } } fn load_symbol_and_lambda_set<'a, 'ctx>( layout_interner: &STLayoutInterner<'a>, scope: &Scope<'a, 'ctx>, symbol: &Symbol, ) -> (BasicValueEnum<'ctx>, LambdaSet<'a>) { let (ptr, layout) = scope.load_symbol_and_layout(symbol); match layout_interner.get_repr(layout) { LayoutRepr::LambdaSet(lambda_set) => (ptr, lambda_set), other => panic!("Not a lambda set: {:?}, {:?}", other, ptr), } }