diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index f2bd4cb3c4..2097a8e195 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -2902,7 +2902,7 @@ fn run_low_level<'a, 'ctx, 'env>( match arg_builtin { Int128 | Int64 | Int32 | Int16 | Int8 => { - build_int_unary_op(env, arg.into_int_value(), arg_layout, op) + build_int_unary_op(env, arg.into_int_value(), arg_builtin, op) } Float128 | Float64 | Float32 | Float16 => { build_float_unary_op(env, arg.into_float_value(), op) @@ -3492,51 +3492,52 @@ fn build_float_binop<'a, 'ctx, 'env>( fn build_int_unary_op<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, arg: IntValue<'ctx>, - arg_layout: &Layout<'a>, + arg_layout: &Builtin<'a>, op: LowLevel, ) -> BasicValueEnum<'ctx> { use roc_module::low_level::LowLevel::*; let bd = env.builder; + let ctx = env.context; match op { NumNeg => bd.build_int_neg(arg, "negate_int").into(), NumAbs => { - // This is how libc's abs() is implemented - it uses no branching! - // - // abs = \arg -> - // shifted = arg >>> 63 - // - // (xor arg shifted) - shifted + use Builtin::*; + // integer abs overflows when applied to the minimum value of a signed type + match arg_layout { + Int128 => { + let a = i128::MIN as u64; + let b = (i128::MIN >> 64) as u64; - let ctx = env.context; - let shifted_name = "abs_shift_right"; - let shifted_alloca = { - let bits_to_shift = ((arg_layout.stack_size(env.ptr_bytes) as u64) * 8) - 1; - let shift_val = ctx.i64_type().const_int(bits_to_shift, false); - let shifted = bd.build_right_shift(arg, shift_val, true, shifted_name); - let alloca = bd.build_alloca( - basic_type_from_layout(env.arena, ctx, arg_layout, env.ptr_bytes), - "#int_abs_help", - ); - - // shifted = arg >>> 63 - bd.build_store(alloca, shifted); - - alloca - }; - - let xored_arg = bd.build_xor( - arg, - bd.build_load(shifted_alloca, shifted_name).into_int_value(), - "xor_arg_shifted", - ); - - BasicValueEnum::IntValue(bd.build_int_sub( - xored_arg, - bd.build_load(shifted_alloca, shifted_name).into_int_value(), - "sub_xored_shifted", - )) + int_abs_raise_on_overflow( + env, + arg, + ctx.i128_type().const_int_arbitrary_precision(&[b, a]), + ) + } + Int64 => int_abs_raise_on_overflow( + env, + arg, + ctx.i64_type().const_int(i64::MIN as u64, false), + ), + Int32 => int_abs_raise_on_overflow( + env, + arg, + ctx.i32_type().const_int(i32::MIN as u64, false), + ), + Int16 => int_abs_raise_on_overflow( + env, + arg, + ctx.i16_type().const_int(i16::MIN as u64, false), + ), + Int8 => int_abs_raise_on_overflow( + env, + arg, + ctx.i8_type().const_int(i8::MIN as u64, false), + ), + _ => unreachable!("not an integer type"), + } } NumToFloat => { // This is an Int, so we need to convert it. @@ -3553,6 +3554,77 @@ fn build_int_unary_op<'a, 'ctx, 'env>( } } +fn int_abs_raise_on_overflow<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + arg: IntValue<'ctx>, + min_val: IntValue<'ctx>, +) -> BasicValueEnum<'ctx> { + let builder = env.builder; + 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_exception( + env, + "integer absolute overflowed because its argument is the minimum value", + ); + + builder.position_at_end(else_block); + + int_abs_with_overflow(env, arg, Layout::Builtin(Builtin::Int64)) +} + +fn int_abs_with_overflow<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + arg: IntValue<'ctx>, + arg_layout: Layout<'a>, +) -> 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 ctx = env.context; + let shifted_name = "abs_shift_right"; + let shifted_alloca = { + let bits_to_shift = ((arg_layout.stack_size(env.ptr_bytes) as u64) * 8) - 1; + let shift_val = ctx.i64_type().const_int(bits_to_shift, false); + let shifted = bd.build_right_shift(arg, shift_val, true, shifted_name); + let alloca = bd.build_alloca( + basic_type_from_layout(env.arena, ctx, &arg_layout, env.ptr_bytes), + "#int_abs_help", + ); + + // shifted = arg >>> 63 + bd.build_store(alloca, shifted); + + alloca + }; + + let xored_arg = bd.build_xor( + arg, + bd.build_load(shifted_alloca, shifted_name).into_int_value(), + "xor_arg_shifted", + ); + + BasicValueEnum::IntValue(bd.build_int_sub( + xored_arg, + bd.build_load(shifted_alloca, shifted_name).into_int_value(), + "sub_xored_shifted", + )) +} + fn build_float_unary_op<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, arg: FloatValue<'ctx>, diff --git a/compiler/gen/tests/gen_num.rs b/compiler/gen/tests/gen_num.rs index d8aa2e2e04..4150d9b4e7 100644 --- a/compiler/gen/tests/gen_num.rs +++ b/compiler/gen/tests/gen_num.rs @@ -40,6 +40,8 @@ mod gen_num { fn f64_abs() { assert_evals_to!("Num.abs -4.7", 4.7, f64); assert_evals_to!("Num.abs 5.8", 5.8, f64); + //assert_evals_to!("Num.abs Num.maxFloat", f64::MAX, f64); + //assert_evals_to!("Num.abs Num.minFloat", -f64::MIN, f64); } #[test] @@ -52,6 +54,24 @@ mod gen_num { assert_evals_to!("Num.abs 1", 1, i64); assert_evals_to!("Num.abs 9_000_000_000_000", 9_000_000_000_000, i64); assert_evals_to!("Num.abs -9_000_000_000_000", 9_000_000_000_000, i64); + assert_evals_to!("Num.abs Num.maxInt", i64::MAX, i64); + assert_evals_to!("Num.abs (Num.minInt + 1)", -(i64::MIN + 1), i64); + } + + #[test] + #[should_panic( + expected = r#"Roc failed with message: "integer absolute overflowed because its argument is the minimum value"# + )] + fn abs_min_int_overflow() { + assert_evals_to!( + indoc!( + r#" + Num.abs Num.minInt + "# + ), + 0, + i64 + ); } #[test]