Merge branch 'trunk' into editor-ast-def

This commit is contained in:
Richard Feldman 2020-12-23 17:27:38 -05:00 committed by GitHub
commit e3fe44d054
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 390 additions and 15 deletions

View file

@ -270,6 +270,41 @@ mod repl_eval {
expect_success("Num.subWrap Num.minInt 1", "9223372036854775807 : I64");
}
#[test]
fn num_mul_wrap() {
expect_success("Num.mulWrap Num.maxInt 2", "-2 : I64");
}
#[test]
fn num_add_checked() {
expect_success("Num.addChecked 1 1", "Ok 2 : Result (Num *) [ Overflow ]*");
expect_success(
"Num.addChecked Num.maxInt 1",
"Err (Overflow) : Result I64 [ Overflow ]*",
);
}
#[test]
fn num_sub_checked() {
expect_success("Num.subChecked 1 1", "Ok 0 : Result (Num *) [ Overflow ]*");
expect_success(
"Num.subChecked Num.minInt 1",
"Err (Overflow) : Result I64 [ Overflow ]*",
);
}
#[test]
fn num_mul_checked() {
expect_success(
"Num.mulChecked 20 2",
"Ok 40 : Result (Num *) [ Overflow ]*",
);
expect_success(
"Num.mulChecked Num.maxInt 2",
"Err (Overflow) : Result I64 [ Overflow ]*",
);
}
#[test]
fn list_concat() {
expect_success(

View file

@ -75,17 +75,19 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
),
);
// addChecked : Num a, Num a -> Result (Num a) [ Overflow ]*
let overflow = SolvedType::TagUnion(
vec![(TagName::Global("Overflow".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
fn overflow() -> SolvedType {
SolvedType::TagUnion(
vec![(TagName::Global("Overflow".into()), vec![])],
Box::new(SolvedType::Wildcard),
)
}
// addChecked : Num a, Num a -> Result (Num a) [ Overflow ]*
add_type(
Symbol::NUM_ADD_CHECKED,
top_level_function(
vec![num_type(flex(TVAR1)), num_type(flex(TVAR1))],
Box::new(result_type(num_type(flex(TVAR1)), overflow.clone())),
Box::new(result_type(num_type(flex(TVAR1)), overflow())),
),
);
@ -115,7 +117,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Symbol::NUM_SUB_CHECKED,
top_level_function(
vec![num_type(flex(TVAR1)), num_type(flex(TVAR1))],
Box::new(result_type(num_type(flex(TVAR1)), overflow)),
Box::new(result_type(num_type(flex(TVAR1)), overflow())),
),
);
@ -128,6 +130,21 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
),
);
// mulWrap : Int, Int -> Int
add_type(
Symbol::NUM_MUL_WRAP,
top_level_function(vec![int_type(), int_type()], Box::new(int_type())),
);
// mulChecked : Num a, Num a -> Result (Num a) [ Overflow ]*
add_type(
Symbol::NUM_MUL_CHECKED,
top_level_function(
vec![num_type(flex(TVAR1)), num_type(flex(TVAR1))],
Box::new(result_type(num_type(flex(TVAR1)), overflow())),
),
);
// abs : Num a -> Num a
add_type(
Symbol::NUM_ABS,

View file

@ -197,6 +197,21 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
unique_function(vec![num_type(u, num), num_type(v, num)], num_type(w, num))
});
// mulWrap : Int, Int -> Int
add_type(Symbol::NUM_MUL_WRAP, {
let_tvars! { u, v, w };
unique_function(vec![int_type(u), int_type(v)], int_type(w))
});
// mulChecked : Num a, Num a -> Result (Num a) [ Overflow ]*
add_type(Symbol::NUM_MUL_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())),
)
});
// abs : Num a -> Num a
add_type(Symbol::NUM_ABS, {
let_tvars! { u, v, num };

View file

@ -85,6 +85,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_SUB_WRAP => num_sub_wrap,
NUM_SUB_CHECKED => num_sub_checked,
NUM_MUL => num_mul,
NUM_MUL_WRAP => num_mul_wrap,
NUM_MUL_CHECKED => num_mul_checked,
NUM_GT => num_gt,
NUM_GTE => num_gte,
NUM_LT => num_lt,
@ -573,6 +575,100 @@ fn num_mul(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumMul)
}
/// Num.mulWrap : Int, Int -> Int
fn num_mul_wrap(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumMulWrap)
}
/// Num.mulChecked : Num a, Num a -> Result (Num a) [ Overflow ]*
fn num_mul_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 NumMulChecked 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 NumMulChecked 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::NumMulChecked,
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);
defn(
symbol,
vec![(num_var_1, Symbol::ARG_1), (num_var_2, Symbol::ARG_2)],
var_store,
body,
ret_var,
)
}
/// Num.isGt : Num a, Num a -> Bool
fn num_gt(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_num_other_binop(symbol, var_store, LowLevel::NumGt)

View file

@ -355,6 +355,12 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
ctx.struct_type(&fields, false)
.fn_type(&[i64_type.into(), i64_type.into()], false)
});
add_intrinsic(module, LLVM_SMUL_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";
@ -369,6 +375,7 @@ static LLVM_CEILING_F64: &str = "llvm.ceil.f64";
static LLVM_FLOOR_F64: &str = "llvm.floor.f64";
pub static LLVM_SADD_WITH_OVERFLOW_I64: &str = "llvm.sadd.with.overflow.i64";
pub static LLVM_SSUB_WITH_OVERFLOW_I64: &str = "llvm.ssub.with.overflow.i64";
pub static LLVM_SMUL_WITH_OVERFLOW_I64: &str = "llvm.smul.with.overflow.i64";
fn add_intrinsic<'ctx>(
module: &Module<'ctx>,
@ -3003,7 +3010,7 @@ fn run_low_level<'a, 'ctx, 'env>(
NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked
| NumAddWrap | NumAddChecked | NumDivUnchecked | NumPow | NumPowInt | NumSubWrap
| NumSubChecked => {
| NumSubChecked | NumMulWrap | NumMulChecked => {
debug_assert_eq!(args.len(), 2);
let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]);
@ -3259,7 +3266,37 @@ fn build_int_binop<'a, 'ctx, 'env>(
}
NumSubWrap => bd.build_int_sub(lhs, rhs, "sub_int").into(),
NumSubChecked => env.call_intrinsic(LLVM_SSUB_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]),
NumMul => bd.build_int_mul(lhs, rhs, "mul_int").into(),
NumMul => {
let context = env.context;
let result = env
.call_intrinsic(LLVM_SMUL_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()])
.into_struct_value();
let mul_result = bd.build_extract_value(result, 0, "mul_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 multiplication overflowed!");
bd.position_at_end(then_block);
mul_result
}
NumMulWrap => bd.build_int_mul(lhs, rhs, "mul_int").into(),
NumMulChecked => env.call_intrinsic(LLVM_SMUL_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]),
NumGt => bd.build_int_compare(SGT, lhs, rhs, "int_gt").into(),
NumGte => bd.build_int_compare(SGE, lhs, rhs, "int_gte").into(),
NumLt => bd.build_int_compare(SLT, lhs, rhs, "int_lt").into(),
@ -3475,7 +3512,55 @@ fn build_float_binop<'a, 'ctx, 'env>(
struct_value.into()
}
NumSubWrap => unreachable!("wrapping subtraction is not defined on floats"),
NumMul => bd.build_float_mul(lhs, rhs, "mul_float").into(),
NumMul => {
let builder = env.builder;
let context = env.context;
let result = bd.build_float_mul(lhs, rhs, "mul_float");
let is_finite =
call_bitcode_fn(env, &[result.into()], &bitcode::NUM_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 multiplication overflowed!");
builder.position_at_end(then_block);
result.into()
}
NumMulChecked => {
let context = env.context;
let result = bd.build_float_mul(lhs, rhs, "mul_float");
let is_finite =
call_bitcode_fn(env, &[result.into()], &bitcode::NUM_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()
}
NumMulWrap => unreachable!("wrapping multiplication is not defined on floats"),
NumGt => bd.build_float_compare(OGT, lhs, rhs, "float_gt").into(),
NumGte => bd.build_float_compare(OGE, lhs, rhs, "float_gte").into(),
NumLt => bd.build_float_compare(OLT, lhs, rhs, "float_lt").into(),

View file

@ -778,7 +778,7 @@ mod gen_num {
indoc!(
r#"
-1.7976931348623157e308 - 1.7976931348623157e308
"#
"#
),
0.0,
f64
@ -790,12 +790,12 @@ mod gen_num {
assert_evals_to!(
indoc!(
r#"
when Num.subChecked 1 2 is
when Num.subChecked 5 2 is
Ok v -> v
_ -> -1
"#
),
-1,
3,
i64
);
@ -838,4 +838,127 @@ mod gen_num {
f64
);
}
#[test]
#[should_panic(expected = r#"Roc failed with message: "integer multiplication overflowed!"#)]
fn int_positive_mul_overflow() {
assert_evals_to!(
indoc!(
r#"
9_223_372_036_854_775_807 * 2
"#
),
0,
i64
);
}
#[test]
#[should_panic(expected = r#"Roc failed with message: "integer multiplication overflowed!"#)]
fn int_negative_mul_overflow() {
assert_evals_to!(
indoc!(
r#"
(-9_223_372_036_854_775_808) * 2
"#
),
0,
i64
);
}
#[test]
#[should_panic(expected = r#"Roc failed with message: "float multiplication overflowed!"#)]
fn float_positive_mul_overflow() {
assert_evals_to!(
indoc!(
r#"
1.7976931348623157e308 * 2
"#
),
0.0,
f64
);
}
#[test]
#[should_panic(expected = r#"Roc failed with message: "float multiplication overflowed!"#)]
fn float_negative_mul_overflow() {
assert_evals_to!(
indoc!(
r#"
-1.7976931348623157e308 * 2
"#
),
0.0,
f64
);
}
#[test]
fn int_mul_wrap() {
assert_evals_to!(
indoc!(
r#"
Num.mulWrap Num.maxInt 2
"#
),
-2,
i64
);
}
#[test]
fn int_mul_checked() {
assert_evals_to!(
indoc!(
r#"
when Num.mulChecked 20 2 is
Ok v -> v
_ -> -1
"#
),
40,
i64
);
assert_evals_to!(
indoc!(
r#"
when Num.mulChecked Num.maxInt 2 is
Err Overflow -> -1
Ok v -> v
"#
),
-1,
i64
);
}
#[test]
fn float_mul_checked() {
assert_evals_to!(
indoc!(
r#"
when Num.mulChecked 20.0 2.0 is
Ok v -> v
Err Overflow -> -1.0
"#
),
40.0,
f64
);
assert_evals_to!(
indoc!(
r#"
when Num.mulChecked 1.7976931348623157e308 2 is
Err Overflow -> -1
Ok v -> v
"#
),
-1.0,
f64
);
}
}

View file

@ -34,6 +34,8 @@ pub enum LowLevel {
NumSubWrap,
NumSubChecked,
NumMul,
NumMulWrap,
NumMulChecked,
NumGt,
NumGte,
NumLt,

View file

@ -824,6 +824,8 @@ define_builtins! {
81 NUM_BITWISE_AND: "bitwiseAnd"
82 NUM_SUB_WRAP: "subWrap"
83 NUM_SUB_CHECKED: "subChecked"
84 NUM_MUL_WRAP: "mulWrap"
85 NUM_MUL_CHECKED: "mulChecked"
}
2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias

View file

@ -540,8 +540,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListSum => arena.alloc_slice_copy(&[borrowed]),
Eq | NotEq | And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap
| NumSubChecked | NumMul | NumGt | NumGte | NumLt | NumLte | NumCompare
| NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt | NumBitwiseAnd => {
| NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte
| NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt | NumBitwiseAnd => {
arena.alloc_slice_copy(&[irrelevant, irrelevant])
}