Merge pull request #539 from rtfeldman/num-add-wrap-check

Addition variants
This commit is contained in:
Richard Feldman 2020-09-21 22:00:31 -04:00 committed by GitHub
commit cd5e2b6817
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 360 additions and 39 deletions

View file

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

View file

@ -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()

View file

@ -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)
}

View file

@ -187,6 +187,26 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
),
);
// 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,

View file

@ -274,6 +274,26 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
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 };

View file

@ -68,6 +68,8 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
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)

View file

@ -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()) },
}
}

Binary file not shown.

View file

@ -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
);
}
}

View file

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

View file

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

View file

@ -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]),
}
}