diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 9c13c85001..a8d7a27913 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -384,6 +384,15 @@ pub fn types() -> MutMap { ), ); + // isMultipleOf : Int a, Int a -> Bool + add_type( + Symbol::NUM_IS_MULTIPLE_OF, + top_level_function( + vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], + Box::new(bool_type()), + ), + ); + // maxI128 : I128 add_type(Symbol::NUM_MAX_I128, i128_type()); diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 20bca2b8be..fee0598286 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -136,6 +136,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option NUM_ABS => num_abs, NUM_NEG => num_neg, NUM_REM => num_rem, + NUM_IS_MULTIPLE_OF => num_is_multiple_of, NUM_SQRT => num_sqrt, NUM_ROUND => num_round, NUM_IS_ODD => num_is_odd, @@ -271,6 +272,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap { Symbol::NUM_ABS => num_abs, Symbol::NUM_NEG => num_neg, Symbol::NUM_REM => num_rem, + Symbol::NUM_IS_MULTIPLE_OF => num_is_multiple_of, Symbol::NUM_SQRT => num_sqrt, Symbol::NUM_ROUND => num_round, Symbol::NUM_IS_ODD => num_is_odd, @@ -2611,6 +2613,11 @@ fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +/// Num.isMultipleOf : Int, Int -> Bool +fn num_is_multiple_of(symbol: Symbol, var_store: &mut VarStore) -> Def { + lowlevel_2(symbol, LowLevel::NumIsMultipleOf, var_store) +} + /// Num.neg : Num a -> Num a fn num_neg(symbol: Symbol, var_store: &mut VarStore) -> Def { let num_var = var_store.fresh(); diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 9f42b8e8d3..3ff74e4106 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -4075,8 +4075,8 @@ fn run_low_level<'a, 'ctx, 'env>( } NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked - | NumAddWrap | NumAddChecked | NumDivUnchecked | NumPow | NumPowInt | NumSubWrap - | NumSubChecked | NumMulWrap | NumMulChecked => { + | NumIsMultipleOf | NumAddWrap | NumAddChecked | NumDivUnchecked | NumPow | NumPowInt + | NumSubWrap | NumSubChecked | NumMulWrap | NumMulChecked => { debug_assert_eq!(args.len(), 2); let (lhs_arg, lhs_layout) = load_symbol_and_layout(scope, &args[0]); @@ -4778,6 +4778,44 @@ fn build_int_binop<'a, 'ctx, 'env>( NumLt => bd.build_int_compare(SLT, lhs, rhs, "int_lt").into(), NumLte => bd.build_int_compare(SLE, lhs, rhs, "int_lte").into(), NumRemUnchecked => bd.build_int_signed_rem(lhs, rhs, "rem_int").into(), + NumIsMultipleOf => { + /* this builds the following construct + + if rhs == 0 { + lhs == 0 + } else { + let rem = lhs % rhs; + rem == 0 + } + */ + let zero = rhs.get_type().const_zero(); + let condition_rhs = bd.build_int_compare(IntPredicate::EQ, rhs, zero, "is_zero_rhs"); + let condition_lhs = bd.build_int_compare(IntPredicate::EQ, lhs, zero, "is_zero_lhs"); + + let current_block = bd.get_insert_block().unwrap(); //block that we are in right now; + let else_block = env.context.append_basic_block(parent, "else"); // + let cont_block = env.context.append_basic_block(parent, "branchcont"); + + bd.build_conditional_branch(condition_rhs, cont_block, else_block); + + bd.position_at_end(else_block); + + let rem = bd.build_int_signed_rem(lhs, rhs, "int_rem"); + let condition_rem = bd.build_int_compare(IntPredicate::EQ, rem, zero, "is_zero_rem"); + + bd.build_unconditional_branch(cont_block); + + bd.position_at_end(cont_block); + + let phi = bd.build_phi(env.context.bool_type(), "branch"); + + phi.add_incoming(&[ + (&condition_lhs, current_block), + (&condition_rem, else_block), + ]); + + phi.as_basic_value() + } NumDivUnchecked => bd.build_int_signed_div(lhs, rhs, "div_int").into(), NumPowInt => call_bitcode_fn(env, &[lhs.into(), rhs.into()], &bitcode::NUM_POW_INT), NumBitwiseAnd => bd.build_and(lhs, rhs, "int_bitwise_and").into(), diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index b8a2ed31c6..a84ce73ea3 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -65,6 +65,7 @@ pub enum LowLevel { NumCompare, NumDivUnchecked, NumRemUnchecked, + NumIsMultipleOf, NumAbs, NumNeg, NumSin, diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 646ae46b36..3320c3b40b 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -856,6 +856,7 @@ define_builtins! { 95 NUM_NAT: "Nat" imported 96 NUM_INT_CAST: "intCast" 97 NUM_MAX_I128: "maxI128" + 98 NUM_IS_MULTIPLE_OF: "isMultipleOf" } 2 BOOL: "Bool" => { diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index b3785ddaa9..7c12ff969e 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -667,10 +667,9 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap | NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare - | NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt | NumBitwiseAnd - | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy | NumShiftRightZfBy => { - arena.alloc_slice_copy(&[irrelevant, irrelevant]) - } + | NumDivUnchecked | NumRemUnchecked | NumIsMultipleOf | NumPow | NumPowInt + | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy + | NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]), NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin | NumIntCast => { diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index 15a7a600fb..a306d343a5 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -1377,4 +1377,17 @@ mod gen_num { i128 ); } + + #[test] + fn is_multiple_of() { + // true + assert_evals_to!("Num.isMultipleOf 5 1", true, bool); + assert_evals_to!("Num.isMultipleOf 5 -1", true, bool); + assert_evals_to!("Num.isMultipleOf 0 0", true, bool); + assert_evals_to!("Num.isMultipleOf 0 1", true, bool); + assert_evals_to!("Num.isMultipleOf 0 -1", true, bool); + // false + assert_evals_to!("Num.isMultipleOf 5 2", false, bool); + assert_evals_to!("Num.isMultipleOf 5 0", false, bool); + } }