Create new operator Num.mulSaturated

This commit is contained in:
Brian Carroll 2022-06-23 22:08:37 +01:00
parent e356f1c33f
commit 74f946b1aa
No known key found for this signature in database
GPG key ID: 9CF4E3BF9C4722C7
11 changed files with 191 additions and 68 deletions

View file

@ -82,6 +82,7 @@ interface Num
subChecked, subChecked,
subSaturated, subSaturated,
mulWrap, mulWrap,
mulSaturated,
mulChecked, mulChecked,
intCast, intCast,
bytesToU16, bytesToU16,
@ -862,7 +863,14 @@ subSaturated : Num a, Num a -> Num a
subChecked : Num a, Num a -> Result (Num a) [Overflow]* subChecked : Num a, Num a -> Result (Num a) [Overflow]*
mulWrap : Int range, Int range -> Int range mulWrap : Int range, Int range -> Int range
# mulSaturated : Num a, Num a -> Num a
## Multiply two numbers, clamping on the maximum representable number rather than
## overflowing.
##
## This is the same as [Num.mul] except for the saturating behavior if the
## addition is to overflow.
mulSaturated : Num a, Num a -> Num a
## Multiply two numbers and check for overflow. ## Multiply two numbers and check for overflow.
## ##
## This is the same as [Num.mul] except if the operation overflows, instead of ## This is the same as [Num.mul] except if the operation overflows, instead of

View file

@ -199,6 +199,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(result_type(num_type(flex(TVAR1)), overflow())), Box::new(result_type(num_type(flex(TVAR1)), overflow())),
); );
// mulSaturated : Int range, Int range -> Int range
add_top_level_function_type!(
Symbol::NUM_MUL_SATURATED,
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR1))),
);
// abs : Num a -> Num a // abs : Num a -> Num a
add_top_level_function_type!( add_top_level_function_type!(
Symbol::NUM_ABS, Symbol::NUM_ABS,

View file

@ -187,6 +187,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_SUB_SATURATED => num_sub_saturated, NUM_SUB_SATURATED => num_sub_saturated,
NUM_MUL => num_mul, NUM_MUL => num_mul,
NUM_MUL_WRAP => num_mul_wrap, NUM_MUL_WRAP => num_mul_wrap,
NUM_MUL_SATURATED => num_mul_saturated,
NUM_MUL_CHECKED => num_mul_checked, NUM_MUL_CHECKED => num_mul_checked,
NUM_GT => num_gt, NUM_GT => num_gt,
NUM_GTE => num_gte, NUM_GTE => num_gte,
@ -909,6 +910,11 @@ fn num_mul_wrap(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumMulWrap) num_binop(symbol, var_store, LowLevel::NumMulWrap)
} }
/// Num.mulSaturated : Num a, Num a -> Num a
fn num_mul_saturated(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumMulSaturated)
}
/// Num.mulChecked : Num a, Num a -> Result (Num a) [Overflow]* /// Num.mulChecked : Num a, Num a -> Result (Num a) [Overflow]*
fn num_mul_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_mul_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_overflow_checked(symbol, var_store, LowLevel::NumMulChecked) num_overflow_checked(symbol, var_store, LowLevel::NumMulChecked)

View file

@ -5952,7 +5952,7 @@ fn run_low_level<'a, 'ctx, 'env>(
NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked
| NumIsMultipleOf | NumAddWrap | NumAddChecked | NumAddSaturated | NumDivUnchecked | NumIsMultipleOf | NumAddWrap | NumAddChecked | NumAddSaturated | NumDivUnchecked
| NumDivCeilUnchecked | NumPow | NumPowInt | NumSubWrap | NumSubChecked | NumDivCeilUnchecked | NumPow | NumPowInt | NumSubWrap | NumSubChecked
| NumSubSaturated | NumMulWrap | NumMulChecked => { | NumSubSaturated | NumMulWrap | NumMulSaturated | NumMulChecked => {
debug_assert_eq!(args.len(), 2); debug_assert_eq!(args.len(), 2);
let (lhs_arg, lhs_layout) = load_symbol_and_layout(scope, &args[0]); let (lhs_arg, lhs_layout) = load_symbol_and_layout(scope, &args[0]);
@ -6719,6 +6719,11 @@ fn build_int_binop<'a, 'ctx, 'env>(
throw_on_overflow(env, parent, result, "integer multiplication overflowed!") throw_on_overflow(env, parent, result, "integer multiplication overflowed!")
} }
NumMulWrap => bd.build_int_mul(lhs, rhs, "mul_int").into(), NumMulWrap => bd.build_int_mul(lhs, rhs, "mul_int").into(),
NumMulSaturated => call_bitcode_fn(
env,
&[lhs.into(), rhs.into()],
&bitcode::NUM_MUL_SATURATED_INT[int_width],
),
NumMulChecked => env.call_intrinsic( NumMulChecked => env.call_intrinsic(
&LLVM_MUL_WITH_OVERFLOW[int_width], &LLVM_MUL_WITH_OVERFLOW[int_width],
&[lhs.into(), rhs.into()], &[lhs.into(), rhs.into()],
@ -6988,6 +6993,7 @@ fn build_float_binop<'a, 'ctx, 'env>(
} }
NumSubWrap => unreachable!("wrapping subtraction is not defined on floats"), NumSubWrap => unreachable!("wrapping subtraction is not defined on floats"),
NumMul => bd.build_float_mul(lhs, rhs, "mul_float").into(), NumMul => bd.build_float_mul(lhs, rhs, "mul_float").into(),
NumMulSaturated => bd.build_float_mul(lhs, rhs, "mul_float").into(),
NumMulChecked => { NumMulChecked => {
let context = env.context; let context = env.context;

View file

@ -549,6 +549,24 @@ impl<'a> LowLevelCall<'a> {
} }
_ => panic_ret_type(), _ => panic_ret_type(),
}, },
NumMulSaturated => match self.ret_layout {
Layout::Builtin(Builtin::Int(width)) => {
self.load_args_and_call_zig(backend, &bitcode::NUM_MUL_SATURATED_INT[width])
}
Layout::Builtin(Builtin::Float(FloatWidth::F32)) => {
self.load_args(backend);
backend.code_builder.f32_mul()
}
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
self.load_args(backend);
backend.code_builder.f64_mul()
}
Layout::Builtin(Builtin::Decimal) => {
self.load_args_and_call_zig(backend, bitcode::DEC_MUL_SATURATED)
}
_ => panic_ret_type(),
},
NumMulChecked => { NumMulChecked => {
let arg_layout = backend.storage.symbol_layouts[&self.arguments[0]]; let arg_layout = backend.storage.symbol_layouts[&self.arguments[0]];
match arg_layout { match arg_layout {

View file

@ -78,6 +78,7 @@ pub enum LowLevel {
NumSubSaturated, NumSubSaturated,
NumMul, NumMul,
NumMulWrap, NumMulWrap,
NumMulSaturated,
NumMulChecked, NumMulChecked,
NumGt, NumGt,
NumGte, NumGte,
@ -284,6 +285,7 @@ impl LowLevelWrapperType {
Symbol::NUM_SUB_SATURATED => CanBeReplacedBy(NumSubSaturated), Symbol::NUM_SUB_SATURATED => CanBeReplacedBy(NumSubSaturated),
Symbol::NUM_MUL => CanBeReplacedBy(NumMul), Symbol::NUM_MUL => CanBeReplacedBy(NumMul),
Symbol::NUM_MUL_WRAP => CanBeReplacedBy(NumMulWrap), Symbol::NUM_MUL_WRAP => CanBeReplacedBy(NumMulWrap),
Symbol::NUM_MUL_SATURATED => CanBeReplacedBy(NumMulSaturated),
Symbol::NUM_MUL_CHECKED => WrapperIsRequired, Symbol::NUM_MUL_CHECKED => WrapperIsRequired,
Symbol::NUM_GT => CanBeReplacedBy(NumGt), Symbol::NUM_GT => CanBeReplacedBy(NumGt),
Symbol::NUM_GTE => CanBeReplacedBy(NumGte), Symbol::NUM_GTE => CanBeReplacedBy(NumGte),

View file

@ -1075,66 +1075,67 @@ define_builtins! {
77 NUM_SUB_SATURATED: "subSaturated" 77 NUM_SUB_SATURATED: "subSaturated"
78 NUM_MUL_WRAP: "mulWrap" 78 NUM_MUL_WRAP: "mulWrap"
79 NUM_MUL_CHECKED: "mulChecked" 79 NUM_MUL_CHECKED: "mulChecked"
80 NUM_INT: "Int" 80 NUM_MUL_SATURATED: "mulSaturated"
81 NUM_FRAC: "Frac" 81 NUM_INT: "Int"
82 NUM_NATURAL: "Natural" 82 NUM_FRAC: "Frac"
83 NUM_NAT: "Nat" 83 NUM_NATURAL: "Natural"
84 NUM_INT_CAST: "intCast" 84 NUM_NAT: "Nat"
85 NUM_IS_MULTIPLE_OF: "isMultipleOf" 85 NUM_INT_CAST: "intCast"
86 NUM_DECIMAL: "Decimal" 86 NUM_IS_MULTIPLE_OF: "isMultipleOf"
87 NUM_DEC: "Dec" // the Num.Dectype alias 87 NUM_DECIMAL: "Decimal"
88 NUM_BYTES_TO_U16: "bytesToU16" 88 NUM_DEC: "Dec" // the Num.Dectype alias
89 NUM_BYTES_TO_U32: "bytesToU32" 89 NUM_BYTES_TO_U16: "bytesToU16"
90 NUM_CAST_TO_NAT: "#castToNat" 90 NUM_BYTES_TO_U32: "bytesToU32"
91 NUM_DIV_CEIL: "divCeil" 91 NUM_CAST_TO_NAT: "#castToNat"
92 NUM_DIV_CEIL_CHECKED: "divCeilChecked" 92 NUM_DIV_CEIL: "divCeil"
93 NUM_TO_STR: "toStr" 93 NUM_DIV_CEIL_CHECKED: "divCeilChecked"
94 NUM_MIN_I8: "minI8" 94 NUM_TO_STR: "toStr"
95 NUM_MAX_I8: "maxI8" 95 NUM_MIN_I8: "minI8"
96 NUM_MIN_U8: "minU8" 96 NUM_MAX_I8: "maxI8"
97 NUM_MAX_U8: "maxU8" 97 NUM_MIN_U8: "minU8"
98 NUM_MIN_I16: "minI16" 98 NUM_MAX_U8: "maxU8"
99 NUM_MAX_I16: "maxI16" 99 NUM_MIN_I16: "minI16"
100 NUM_MIN_U16: "minU16" 100 NUM_MAX_I16: "maxI16"
101 NUM_MAX_U16: "maxU16" 101 NUM_MIN_U16: "minU16"
102 NUM_MIN_I32: "minI32" 102 NUM_MAX_U16: "maxU16"
103 NUM_MAX_I32: "maxI32" 103 NUM_MIN_I32: "minI32"
104 NUM_MIN_U32: "minU32" 104 NUM_MAX_I32: "maxI32"
105 NUM_MAX_U32: "maxU32" 105 NUM_MIN_U32: "minU32"
106 NUM_MIN_I64: "minI64" 106 NUM_MAX_U32: "maxU32"
107 NUM_MAX_I64: "maxI64" 107 NUM_MIN_I64: "minI64"
108 NUM_MIN_U64: "minU64" 108 NUM_MAX_I64: "maxI64"
109 NUM_MAX_U64: "maxU64" 109 NUM_MIN_U64: "minU64"
110 NUM_MIN_I128: "minI128" 110 NUM_MAX_U64: "maxU64"
111 NUM_MAX_I128: "maxI128" 111 NUM_MIN_I128: "minI128"
112 NUM_TO_I8: "toI8" 112 NUM_MAX_I128: "maxI128"
113 NUM_TO_I8_CHECKED: "toI8Checked" 113 NUM_TO_I8: "toI8"
114 NUM_TO_I16: "toI16" 114 NUM_TO_I8_CHECKED: "toI8Checked"
115 NUM_TO_I16_CHECKED: "toI16Checked" 115 NUM_TO_I16: "toI16"
116 NUM_TO_I32: "toI32" 116 NUM_TO_I16_CHECKED: "toI16Checked"
117 NUM_TO_I32_CHECKED: "toI32Checked" 117 NUM_TO_I32: "toI32"
118 NUM_TO_I64: "toI64" 118 NUM_TO_I32_CHECKED: "toI32Checked"
119 NUM_TO_I64_CHECKED: "toI64Checked" 119 NUM_TO_I64: "toI64"
120 NUM_TO_I128: "toI128" 120 NUM_TO_I64_CHECKED: "toI64Checked"
121 NUM_TO_I128_CHECKED: "toI128Checked" 121 NUM_TO_I128: "toI128"
122 NUM_TO_U8: "toU8" 122 NUM_TO_I128_CHECKED: "toI128Checked"
123 NUM_TO_U8_CHECKED: "toU8Checked" 123 NUM_TO_U8: "toU8"
124 NUM_TO_U16: "toU16" 124 NUM_TO_U8_CHECKED: "toU8Checked"
125 NUM_TO_U16_CHECKED: "toU16Checked" 125 NUM_TO_U16: "toU16"
126 NUM_TO_U32: "toU32" 126 NUM_TO_U16_CHECKED: "toU16Checked"
127 NUM_TO_U32_CHECKED: "toU32Checked" 127 NUM_TO_U32: "toU32"
128 NUM_TO_U64: "toU64" 128 NUM_TO_U32_CHECKED: "toU32Checked"
129 NUM_TO_U64_CHECKED: "toU64Checked" 129 NUM_TO_U64: "toU64"
130 NUM_TO_U128: "toU128" 130 NUM_TO_U64_CHECKED: "toU64Checked"
131 NUM_TO_U128_CHECKED: "toU128Checked" 131 NUM_TO_U128: "toU128"
132 NUM_TO_NAT: "toNat" 132 NUM_TO_U128_CHECKED: "toU128Checked"
133 NUM_TO_NAT_CHECKED: "toNatChecked" 133 NUM_TO_NAT: "toNat"
134 NUM_TO_F32: "toF32" 134 NUM_TO_NAT_CHECKED: "toNatChecked"
135 NUM_TO_F32_CHECKED: "toF32Checked" 135 NUM_TO_F32: "toF32"
136 NUM_TO_F64: "toF64" 136 NUM_TO_F32_CHECKED: "toF32Checked"
137 NUM_TO_F64_CHECKED: "toF64Checked" 137 NUM_TO_F64: "toF64"
138 NUM_MAX_F64: "maxF64" 138 NUM_TO_F64_CHECKED: "toF64Checked"
139 NUM_MIN_F64: "minF64" 139 NUM_MAX_F64: "maxF64"
140 NUM_MIN_F64: "minF64"
} }
2 BOOL: "Bool" => { 2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" // the Bool.Bool type alias 0 BOOL_BOOL: "Bool" // the Bool.Bool type alias

View file

@ -938,12 +938,11 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
Eq | NotEq => arena.alloc_slice_copy(&[borrowed, borrowed]), Eq | NotEq => arena.alloc_slice_copy(&[borrowed, borrowed]),
And | Or | NumAdd | NumAddWrap | NumAddChecked | NumAddSaturated | NumSub | NumSubWrap And | Or | NumAdd | NumAddWrap | NumAddChecked | NumAddSaturated | NumSub | NumSubWrap
| NumSubChecked | NumSubSaturated | NumMul | NumMulWrap | NumMulChecked | NumGt | NumSubChecked | NumSubSaturated | NumMul | NumMulWrap | NumMulSaturated
| NumGte | NumLt | NumLte | NumCompare | NumDivUnchecked | NumDivCeilUnchecked | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare | NumDivUnchecked
| NumRemUnchecked | NumIsMultipleOf | NumPow | NumPowInt | NumBitwiseAnd | NumDivCeilUnchecked | NumRemUnchecked | NumIsMultipleOf | NumPow | NumPowInt
| NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy | NumShiftRightZfBy => { | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy
arena.alloc_slice_copy(&[irrelevant, irrelevant]) | NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
}
NumToStr | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked NumToStr | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked
| NumRound | NumCeiling | NumFloor | NumToFrac | Not | NumIsFinite | NumAtan | NumAcos | NumRound | NumCeiling | NumFloor | NumToFrac | Not | NumIsFinite | NumAtan | NumAcos

View file

@ -165,6 +165,7 @@ enum FirstOrder {
NumSubChecked, NumSubChecked,
NumMul, NumMul,
NumMulWrap, NumMulWrap,
NumMulSaturated,
NumMulChecked, NumMulChecked,
NumGt, NumGt,
NumGte, NumGte,

View file

@ -3180,6 +3180,76 @@ fn sub_saturated() {
); );
} }
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn mul_saturated() {
assert_evals_to!(
indoc!(
r#"
x : U8
x = 20
y : U8
y = 20
Num.mulSaturated x y
"#
),
255,
u8
);
assert_evals_to!(
indoc!(
r#"
x : I8
x = -20
y : I8
y = -20
Num.mulSaturated x y
"#
),
127,
i8
);
assert_evals_to!(
indoc!(
r#"
x : I8
x = 20
y : I8
y = -20
Num.mulSaturated x y
"#
),
-128,
i8
);
assert_evals_to!(
indoc!(
r#"
x : I8
x = -20
y : I8
y = 20
Num.mulSaturated x y
"#
),
-128,
i8
);
assert_evals_to!(
indoc!(
r#"
x : I8
x = 20
y : I8
y = 20
Num.mulSaturated x y
"#
),
127,
i8
);
}
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm"))]
fn monomorphized_ints() { fn monomorphized_ints() {

View file

@ -348,6 +348,11 @@ fn num_mul_wrap() {
expect_success("Num.mulWrap Num.maxI64 2", "-2 : I64"); expect_success("Num.mulWrap Num.maxI64 2", "-2 : I64");
} }
#[test]
fn num_mul_saturated() {
expect_success("Num.mulSaturated Num.maxI64 2", "9223372036854775807 : I64");
}
#[cfg(not(feature = "wasm"))] #[cfg(not(feature = "wasm"))]
#[test] #[test]
fn num_add_checked() { fn num_add_checked() {