mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 06:44:46 +00:00
make Num.neg raise on overflow
This commit is contained in:
parent
f0ce8006d2
commit
59d6cdba04
2 changed files with 89 additions and 42 deletions
|
@ -9,7 +9,8 @@ use crate::llvm::build_str::{
|
||||||
};
|
};
|
||||||
use crate::llvm::compare::{build_eq, build_neq};
|
use crate::llvm::compare::{build_eq, build_neq};
|
||||||
use crate::llvm::convert::{
|
use crate::llvm::convert::{
|
||||||
basic_type_from_layout, block_of_memory, collection, get_fn_type, get_ptr_type, ptr_int,
|
basic_type_from_builtin, basic_type_from_layout, block_of_memory, collection, get_fn_type,
|
||||||
|
get_ptr_type, ptr_int,
|
||||||
};
|
};
|
||||||
use crate::llvm::refcounting::{
|
use crate::llvm::refcounting::{
|
||||||
decrement_refcount_layout, increment_refcount_layout, refcount_is_one_comparison,
|
decrement_refcount_layout, increment_refcount_layout, refcount_is_one_comparison,
|
||||||
|
@ -3489,6 +3490,33 @@ fn build_float_binop<'a, 'ctx, 'env>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn int_type_signed_min<'ctx>(int_type: IntType<'ctx>) -> IntValue<'ctx> {
|
||||||
|
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 builtin_to_int_type<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
builtin: &Builtin<'a>,
|
||||||
|
) -> IntType<'ctx> {
|
||||||
|
let result = basic_type_from_builtin(env.arena, env.context, builtin, env.ptr_bytes);
|
||||||
|
debug_assert!(result.is_int_type());
|
||||||
|
|
||||||
|
result.into_int_type()
|
||||||
|
}
|
||||||
|
|
||||||
fn build_int_unary_op<'a, 'ctx, 'env>(
|
fn build_int_unary_op<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
arg: IntValue<'ctx>,
|
arg: IntValue<'ctx>,
|
||||||
|
@ -3498,46 +3526,15 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
|
||||||
use roc_module::low_level::LowLevel::*;
|
use roc_module::low_level::LowLevel::*;
|
||||||
|
|
||||||
let bd = env.builder;
|
let bd = env.builder;
|
||||||
let ctx = env.context;
|
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
NumNeg => bd.build_int_neg(arg, "negate_int").into(),
|
NumNeg => {
|
||||||
NumAbs => {
|
|
||||||
use Builtin::*;
|
|
||||||
// integer abs overflows when applied to the minimum value of a signed type
|
// integer abs overflows when applied to the minimum value of a signed type
|
||||||
match arg_layout {
|
int_neg_raise_on_overflow(env, arg, arg_layout)
|
||||||
Int128 => {
|
}
|
||||||
let a = i128::MIN as u64;
|
NumAbs => {
|
||||||
let b = (i128::MIN >> 64) as u64;
|
// integer abs overflows when applied to the minimum value of a signed type
|
||||||
|
int_abs_raise_on_overflow(env, arg, arg_layout)
|
||||||
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 => {
|
NumToFloat => {
|
||||||
// This is an Int, so we need to convert it.
|
// This is an Int, so we need to convert it.
|
||||||
|
@ -3554,12 +3551,44 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn int_neg_raise_on_overflow<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
arg: IntValue<'ctx>,
|
||||||
|
builtin: &Builtin<'a>,
|
||||||
|
) -> BasicValueEnum<'ctx> {
|
||||||
|
let builder = env.builder;
|
||||||
|
|
||||||
|
let min_val = int_type_signed_min(builtin_to_int_type(env, builtin));
|
||||||
|
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 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<'a, 'ctx, 'env>(
|
fn int_abs_raise_on_overflow<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
arg: IntValue<'ctx>,
|
arg: IntValue<'ctx>,
|
||||||
min_val: IntValue<'ctx>,
|
builtin: &Builtin<'a>,
|
||||||
) -> BasicValueEnum<'ctx> {
|
) -> BasicValueEnum<'ctx> {
|
||||||
let builder = env.builder;
|
let builder = env.builder;
|
||||||
|
|
||||||
|
let min_val = int_type_signed_min(builtin_to_int_type(env, builtin));
|
||||||
let condition = builder.build_int_compare(IntPredicate::EQ, arg, min_val, "is_min_val");
|
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 block = env.builder.get_insert_block().expect("to be in a function");
|
||||||
|
@ -3579,13 +3608,13 @@ fn int_abs_raise_on_overflow<'a, 'ctx, 'env>(
|
||||||
|
|
||||||
builder.position_at_end(else_block);
|
builder.position_at_end(else_block);
|
||||||
|
|
||||||
int_abs_with_overflow(env, arg, Layout::Builtin(Builtin::Int64))
|
int_abs_with_overflow(env, arg, builtin)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn int_abs_with_overflow<'a, 'ctx, 'env>(
|
fn int_abs_with_overflow<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
arg: IntValue<'ctx>,
|
arg: IntValue<'ctx>,
|
||||||
arg_layout: Layout<'a>,
|
arg_layout: &Builtin<'a>,
|
||||||
) -> BasicValueEnum<'ctx> {
|
) -> BasicValueEnum<'ctx> {
|
||||||
// This is how libc's abs() is implemented - it uses no branching!
|
// This is how libc's abs() is implemented - it uses no branching!
|
||||||
//
|
//
|
||||||
|
@ -3602,7 +3631,7 @@ fn int_abs_with_overflow<'a, 'ctx, 'env>(
|
||||||
let shift_val = ctx.i64_type().const_int(bits_to_shift, false);
|
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 shifted = bd.build_right_shift(arg, shift_val, true, shifted_name);
|
||||||
let alloca = bd.build_alloca(
|
let alloca = bd.build_alloca(
|
||||||
basic_type_from_layout(env.arena, ctx, &arg_layout, env.ptr_bytes),
|
basic_type_from_builtin(env.arena, ctx, arg_layout, env.ptr_bytes),
|
||||||
"#int_abs_help",
|
"#int_abs_help",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -553,6 +553,24 @@ mod gen_num {
|
||||||
#[test]
|
#[test]
|
||||||
fn int_negate() {
|
fn int_negate() {
|
||||||
assert_evals_to!("Num.neg 123", -123, i64);
|
assert_evals_to!("Num.neg 123", -123, i64);
|
||||||
|
assert_evals_to!("Num.neg Num.maxInt", -i64::MAX, i64);
|
||||||
|
assert_evals_to!("Num.neg (Num.minInt + 1)", i64::MAX, i64);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(
|
||||||
|
expected = r#"Roc failed with message: "integer negation overflowed because its argument is the minimum value"#
|
||||||
|
)]
|
||||||
|
fn neg_min_int_overflow() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
Num.neg Num.minInt
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
i64
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue