diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index c675664e4d..c6e2b021ea 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -36,7 +36,7 @@ $ brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/6616d50f $ brew pin llvm ``` -If that doesn't work and you get a `brew` error `Error: Calling Installation of llvm from a GitHub commit URL is disabled! Use 'brew extract llvm' to stable tap on GitHub instead.` while trying the above solution, you can follow the steps extracting the formular into your private tap (one public version is at `sladwig/tap/llvm`). If installing LLVM still fails, it might help to run `sudo xcode-select -r` before installing again. +If that doesn't work and you get a `brew` error `Error: Calling Installation of llvm from a GitHub commit URL is disabled! Use 'brew extract llvm' to stable tap on GitHub instead.` while trying the above solution, you can follow the steps extracting the formula into your private tap (one public version is at `sladwig/tap/llvm`). If installing LLVM still fails, it might help to run `sudo xcode-select -r` before installing again. ### LLVM installation on Windows diff --git a/cli/src/lib.rs b/cli/src/lib.rs index ce97bc90f3..b84b6ed76a 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -210,6 +210,12 @@ fn build_file( "host.rs", "-o", binary_path.as_path().to_str().unwrap(), + // ensure we don't make a position-independent executable + "-C", + "link-arg=-no-pie", + // explicitly link in the c++ stdlib, for exceptions + "-C", + "link-arg=-lc++", ]) .current_dir(cwd) .spawn() diff --git a/compiler/builtins/bitcode/src/lib.rs b/compiler/builtins/bitcode/src/lib.rs index 2316d1265b..7e9031bfef 100644 --- a/compiler/builtins/bitcode/src/lib.rs +++ b/compiler/builtins/bitcode/src/lib.rs @@ -36,3 +36,12 @@ pub fn pow_int_(mut base: i64, mut exp: i64) -> i64 { acc } + +/// Adapted from Rust's core::num module, by the Rust core team, +/// licensed under the Apache License, version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 +/// +/// Thank you, Rust core team! +#[no_mangle] +pub fn is_finite_(num: f64) -> bool { + f64::is_finite(num) +} diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index a46e1c842c..a2d3088482 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -187,6 +187,26 @@ pub fn types() -> MutMap { ), ); + // addChecked : Num a, Num a -> Result (Num a) [ IntOverflow ]* + let overflow = SolvedType::TagUnion( + vec![(TagName::Global("Overflow".into()), vec![])], + Box::new(SolvedType::Wildcard), + ); + + add_type( + Symbol::NUM_ADD_CHECKED, + SolvedType::Func( + vec![num_type(flex(TVAR1)), num_type(flex(TVAR1))], + Box::new(result_type(num_type(flex(TVAR1)), overflow)), + ), + ); + + // addWrap : Int, Int -> Int + add_type( + Symbol::NUM_ADD_WRAP, + SolvedType::Func(vec![int_type(), int_type()], Box::new(int_type())), + ); + // sub or (-) : Num a, Num a -> Num a add_type( Symbol::NUM_SUB, diff --git a/compiler/builtins/src/unique.rs b/compiler/builtins/src/unique.rs index a04bc1bfdc..4c13ab1716 100644 --- a/compiler/builtins/src/unique.rs +++ b/compiler/builtins/src/unique.rs @@ -274,6 +274,26 @@ pub fn types() -> MutMap { unique_function(vec![num_type(u, num), num_type(v, num)], num_type(w, num)) }); + // addChecked : Num a, Num a -> Result (Num a) [ IntOverflow ]* + let overflow = SolvedType::TagUnion( + vec![(TagName::Global("Overflow".into()), vec![])], + Box::new(SolvedType::Wildcard), + ); + + add_type(Symbol::NUM_ADD_CHECKED, { + let_tvars! { u, v, w, num, result, star }; + unique_function( + vec![num_type(u, num), num_type(v, num)], + result_type(result, num_type(w, num), lift(star, overflow)), + ) + }); + + // addWrap : Int, Int -> Int + add_type(Symbol::NUM_ADD_WRAP, { + let_tvars! { u, v, w }; + unique_function(vec![int_type(u), int_type(v)], int_type(w)) + }); + // sub or (-) : Num a, Num a -> Num a add_type(Symbol::NUM_SUB, { let_tvars! { u, v, w, num }; diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 065664ee6c..de7074f154 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -68,6 +68,8 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap { Symbol::LIST_KEEP_IF => list_keep_if, Symbol::LIST_WALK_RIGHT => list_walk_right, Symbol::NUM_ADD => num_add, + Symbol::NUM_ADD_CHECKED => num_add_checked, + Symbol::NUM_ADD_WRAP => num_add_wrap, Symbol::NUM_SUB => num_sub, Symbol::NUM_MUL => num_mul, Symbol::NUM_GT => num_gt, @@ -238,6 +240,105 @@ fn num_add(symbol: Symbol, var_store: &mut VarStore) -> Def { num_binop(symbol, var_store, LowLevel::NumAdd) } +/// Num.add : Num a, Num a -> Num a +fn num_add_wrap(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_binop(symbol, var_store, LowLevel::NumAddWrap) +} + +/// Num.add : Num a, Num a -> Num a +fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { + let bool_var = var_store.fresh(); + let num_var_1 = var_store.fresh(); + let num_var_2 = var_store.fresh(); + let num_var_3 = var_store.fresh(); + let ret_var = var_store.fresh(); + let record_var = var_store.fresh(); + + // let arg_3 = RunLowLevel NumAddChecked arg_1 arg_2 + // + // if arg_3.b then + // # overflow + // Err Overflow + // else + // # all is well + // Ok arg_3.a + + let cont = If { + branch_var: ret_var, + cond_var: bool_var, + branches: vec![( + // if-condition + no_region( + // arg_3.b + Access { + record_var, + ext_var: var_store.fresh(), + field: "b".into(), + field_var: var_store.fresh(), + loc_expr: Box::new(no_region(Var(Symbol::ARG_3))), + }, + ), + // overflow! + no_region(tag( + "Err", + vec![tag("Overflow", Vec::new(), var_store)], + var_store, + )), + )], + final_else: Box::new( + // all is well + no_region( + // Ok arg_3.a + tag( + "Ok", + vec![ + // arg_3.a + Access { + record_var, + ext_var: var_store.fresh(), + field: "a".into(), + field_var: num_var_3, + loc_expr: Box::new(no_region(Var(Symbol::ARG_3))), + }, + ], + var_store, + ), + ), + ), + }; + + // arg_3 = RunLowLevel NumAddChecked arg_1 arg_2 + let def = crate::def::Def { + loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_3)), + loc_expr: no_region(RunLowLevel { + op: LowLevel::NumAddChecked, + args: vec![ + (num_var_1, Var(Symbol::ARG_1)), + (num_var_2, Var(Symbol::ARG_2)), + ], + ret_var: record_var, + }), + expr_var: record_var, + pattern_vars: SendMap::default(), + annotation: None, + }; + + let body = LetNonRec( + Box::new(def), + Box::new(no_region(cont)), + ret_var, + SendMap::default(), + ); + + defn( + symbol, + vec![(num_var_1, Symbol::ARG_1), (num_var_2, Symbol::ARG_2)], + var_store, + body, + ret_var, + ) +} + /// Num.sub : Num a, Num a -> Num a fn num_sub(symbol: Symbol, var_store: &mut VarStore) -> Def { num_binop(symbol, var_store, LowLevel::NumSub) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 388cd6b162..c05bb4fe6c 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -270,6 +270,12 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { LLVM_FLOOR_F64, f64_type.fn_type(&[f64_type.into()], false), ); + + add_intrinsic(module, LLVM_SADD_WITH_OVERFLOW_I64, { + let fields = [i64_type.into(), i1_type.into()]; + ctx.struct_type(&fields, false) + .fn_type(&[i64_type.into(), i64_type.into()], false) + }); } static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64"; @@ -282,6 +288,7 @@ static LLVM_COS_F64: &str = "llvm.cos.f64"; static LLVM_POW_F64: &str = "llvm.pow.f64"; static LLVM_CEILING_F64: &str = "llvm.ceil.f64"; static LLVM_FLOOR_F64: &str = "llvm.floor.f64"; +static LLVM_SADD_WITH_OVERFLOW_I64: &str = "llvm.sadd.with.overflow.i64"; fn add_intrinsic<'ctx>( module: &Module<'ctx>, @@ -940,8 +947,6 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( debug_assert!(*union_size > 1); let ptr_size = env.ptr_bytes; - let mut filler = tag_layout.stack_size(ptr_size); - let ctx = env.context; let builder = env.builder; @@ -978,16 +983,9 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( } else { field_vals.push(val); } - - filler -= field_size; } } - // TODO verify that this is required (better safe than sorry) - if filler > 0 { - field_types.push(env.context.i8_type().array_type(filler).into()); - } - // Create the struct_type let struct_type = ctx.struct_type(field_types.into_bump_slice(), false); let mut struct_val = struct_type.const_zero().into(); @@ -2108,7 +2106,7 @@ fn run_low_level<'a, 'ctx, 'env>( list_join(env, inplace, parent, list, outer_list_layout) } NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumCeiling | NumFloor - | NumToFloat => { + | NumToFloat | NumIsFinite => { debug_assert_eq!(args.len(), 1); let (arg, arg_layout) = load_symbol_and_layout(env, scope, &args[0]); @@ -2219,7 +2217,7 @@ fn run_low_level<'a, 'ctx, 'env>( } NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked - | NumDivUnchecked | NumPow | NumPowInt => { + | NumAddWrap | NumAddChecked | NumDivUnchecked | NumPow | NumPowInt => { debug_assert_eq!(args.len(), 2); let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]); @@ -2234,6 +2232,7 @@ fn run_low_level<'a, 'ctx, 'env>( match lhs_builtin { Int128 | Int64 | Int32 | Int16 | Int8 => build_int_binop( env, + parent, lhs_arg.into_int_value(), lhs_layout, rhs_arg.into_int_value(), @@ -2242,6 +2241,7 @@ fn run_low_level<'a, 'ctx, 'env>( ), Float128 | Float64 | Float32 | Float16 => build_float_binop( env, + parent, lhs_arg.into_float_value(), lhs_layout, rhs_arg.into_float_value(), @@ -2413,6 +2413,7 @@ where fn build_int_binop<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, + parent: FunctionValue<'ctx>, lhs: IntValue<'ctx>, _lhs_layout: &Layout<'a>, rhs: IntValue<'ctx>, @@ -2425,7 +2426,37 @@ fn build_int_binop<'a, 'ctx, 'env>( let bd = env.builder; match op { - NumAdd => bd.build_int_add(lhs, rhs, "add_int").into(), + NumAdd => { + let context = env.context; + let result = env + .call_intrinsic(LLVM_SADD_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]) + .into_struct_value(); + + let add_result = bd.build_extract_value(result, 0, "add_result").unwrap(); + 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_exception(env, "integer addition overflowed!"); + + bd.position_at_end(then_block); + + add_result + } + NumAddWrap => bd.build_int_add(lhs, rhs, "add_int_wrap").into(), + NumAddChecked => env.call_intrinsic(LLVM_SADD_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]), NumSub => bd.build_int_sub(lhs, rhs, "sub_int").into(), NumMul => bd.build_int_mul(lhs, rhs, "mul_int").into(), NumGt => bd.build_int_compare(SGT, lhs, rhs, "int_gt").into(), @@ -2462,6 +2493,7 @@ fn call_bitcode_fn<'a, 'ctx, 'env>( fn build_float_binop<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, + parent: FunctionValue<'ctx>, lhs: FloatValue<'ctx>, _lhs_layout: &Layout<'a>, rhs: FloatValue<'ctx>, @@ -2474,7 +2506,55 @@ fn build_float_binop<'a, 'ctx, 'env>( let bd = env.builder; match op { - NumAdd => bd.build_float_add(lhs, rhs, "add_float").into(), + NumAdd => { + let builder = env.builder; + let context = env.context; + + let result = bd.build_float_add(lhs, rhs, "add_float"); + + let is_finite = + call_bitcode_fn(NumIsFinite, env, &[result.into()], "is_finite_").into_int_value(); + + let then_block = context.append_basic_block(parent, "then_block"); + let throw_block = context.append_basic_block(parent, "throw_block"); + + builder.build_conditional_branch(is_finite, then_block, throw_block); + + builder.position_at_end(throw_block); + + throw_exception(env, "float addition overflowed!"); + + builder.position_at_end(then_block); + + result.into() + } + NumAddChecked => { + let context = env.context; + + let result = bd.build_float_add(lhs, rhs, "add_float"); + + let is_finite = + call_bitcode_fn(NumIsFinite, env, &[result.into()], "is_finite_").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(), NumMul => bd.build_float_mul(lhs, rhs, "mul_float").into(), NumGt => bd.build_float_compare(OGT, lhs, rhs, "float_gt").into(), @@ -2578,6 +2658,7 @@ fn build_float_unary_op<'a, 'ctx, 'env>( env.context.i64_type(), "num_floor", ), + NumIsFinite => call_bitcode_fn(NumIsFinite, env, &[arg.into()], "is_finite_"), _ => { unreachable!("Unrecognized int unary operation: {:?}", op); } @@ -2588,7 +2669,6 @@ fn define_global_str<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, message: &str, ) -> inkwell::values::GlobalValue<'ctx> { - let context = env.context; let module = env.module; // hash the name so we don't re-define existing messages @@ -2600,29 +2680,12 @@ fn define_global_str<'a, 'ctx, 'env>( message.hash(&mut hasher); let hash = hasher.finish(); - format!("message_{}", hash) + format!("_Error_message_{}", hash) }; match module.get_global(&name) { Some(current) => current, - None => { - let i8_type = context.i8_type(); - - // define the error message as a global constant - let message_global = - module.add_global(i8_type.array_type(message.len() as u32), None, &name); - - let mut message_bytes = Vec::with_capacity_in(message.len(), env.arena); - - for c in message.chars() { - message_bytes.push(i8_type.const_int(c as u64, false)); - } - - let const_array = i8_type.const_array(&message_bytes); - message_global.set_initializer(&const_array); - - message_global - } + None => unsafe { env.builder.build_global_string(message, name.as_str()) }, } } diff --git a/compiler/gen/src/llvm/builtins.bc b/compiler/gen/src/llvm/builtins.bc index c56125a48e..746943b393 100644 Binary files a/compiler/gen/src/llvm/builtins.bc and b/compiler/gen/src/llvm/builtins.bc differ diff --git a/compiler/gen/tests/gen_num.rs b/compiler/gen/tests/gen_num.rs index 1c9e878e1a..b468030030 100644 --- a/compiler/gen/tests/gen_num.rs +++ b/compiler/gen/tests/gen_num.rs @@ -685,4 +685,102 @@ mod gen_num { fn pow_int() { assert_evals_to!("Num.powInt 2 3", 8, i64); } + + #[test] + #[should_panic(expected = r#"Roc failed with message: "integer addition overflowed!"#)] + fn int_overflow() { + assert_evals_to!( + indoc!( + r#" + 9_223_372_036_854_775_807 + 1 + "# + ), + 0, + i64 + ); + } + + #[test] + fn int_add_checked() { + assert_evals_to!( + indoc!( + r#" + when Num.addChecked 1 2 is + Ok v -> v + _ -> -1 + "# + ), + 3, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + when Num.addChecked 9_223_372_036_854_775_807 1 is + Err Overflow -> -1 + Ok v -> v + "# + ), + -1, + i64 + ); + } + + #[test] + fn int_add_wrap() { + assert_evals_to!( + indoc!( + r#" + Num.addWrap 9_223_372_036_854_775_807 1 + "# + ), + std::i64::MIN, + i64 + ); + } + + #[test] + fn float_add_checked_pass() { + assert_evals_to!( + indoc!( + r#" + when Num.addChecked 1.0 0.0 is + Ok v -> v + Err Overflow -> -1.0 + "# + ), + 1.0, + f64 + ); + } + + #[test] + fn float_add_checked_fail() { + assert_evals_to!( + indoc!( + r#" + when Num.addChecked 1.7976931348623157e308 1.7976931348623157e308 is + Err Overflow -> -1 + Ok v -> v + "# + ), + -1.0, + f64 + ); + } + + #[test] + #[should_panic(expected = r#"Roc failed with message: "float addition overflowed!"#)] + fn float_overflow() { + assert_evals_to!( + indoc!( + r#" + 1.7976931348623157e308 + 1.7976931348623157e308 + "# + ), + 0.0, + f64 + ); + } } diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index e347d134c5..2076921ba8 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -20,6 +20,8 @@ pub enum LowLevel { ListKeepIf, ListWalkRight, NumAdd, + NumAddWrap, + NumAddChecked, NumSub, NumMul, NumGt, @@ -40,6 +42,7 @@ pub enum LowLevel { NumCeiling, NumPowInt, NumFloor, + NumIsFinite, Eq, NotEq, And, diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index e61883ff15..8566b1156e 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -644,6 +644,8 @@ define_builtins! { 39 NUM_CEILING: "ceiling" 40 NUM_POW_INT: "powInt" 41 NUM_FLOOR: "floor" + 42 NUM_ADD_WRAP: "addWrap" + 43 NUM_ADD_CHECKED: "addChecked" } 2 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index ac02b9d457..efc849f2d2 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -521,12 +521,11 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { ListKeepIf => arena.alloc_slice_copy(&[owned, irrelevant]), ListWalkRight => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]), - Eq | NotEq | And | Or | NumAdd | NumSub | NumMul | NumGt | NumGte | NumLt | NumLte - | NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt => { - arena.alloc_slice_copy(&[irrelevant, irrelevant]) - } + Eq | NotEq | And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumMul | NumGt + | NumGte | NumLt | NumLte | NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow + | NumPowInt => arena.alloc_slice_copy(&[irrelevant, irrelevant]), NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor - | NumToFloat | Not => arena.alloc_slice_copy(&[irrelevant]), + | NumToFloat | Not | NumIsFinite => arena.alloc_slice_copy(&[irrelevant]), } }