raise exception on Num.abs overflow

This commit is contained in:
Folkert 2020-12-25 21:26:29 +01:00
parent 2bcaf3921f
commit 51cf54e367
2 changed files with 128 additions and 36 deletions

View file

@ -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,16 +3492,101 @@ 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 => {
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;
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.
bd.build_cast(
InstructionOpcode::SIToFP,
arg,
env.context.f64_type(),
"i64_to_f64",
)
}
_ => {
unreachable!("Unrecognized int unary operation: {:?}", op);
}
}
}
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 ->
@ -3509,6 +3594,7 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
//
// (xor arg shifted) - shifted
let bd = env.builder;
let ctx = env.context;
let shifted_name = "abs_shift_right";
let shifted_alloca = {
@ -3516,7 +3602,7 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
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),
basic_type_from_layout(env.arena, ctx, &arg_layout, env.ptr_bytes),
"#int_abs_help",
);
@ -3538,20 +3624,6 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
"sub_xored_shifted",
))
}
NumToFloat => {
// This is an Int, so we need to convert it.
bd.build_cast(
InstructionOpcode::SIToFP,
arg,
env.context.f64_type(),
"i64_to_f64",
)
}
_ => {
unreachable!("Unrecognized int unary operation: {:?}", op);
}
}
}
fn build_float_unary_op<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,

View file

@ -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]