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,
subSaturated,
mulWrap,
mulSaturated,
mulChecked,
intCast,
bytesToU16,
@ -862,7 +863,14 @@ subSaturated : Num a, Num a -> Num a
subChecked : Num a, Num a -> Result (Num a) [Overflow]*
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.
##
## 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())),
);
// 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
add_top_level_function_type!(
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_MUL => num_mul,
NUM_MUL_WRAP => num_mul_wrap,
NUM_MUL_SATURATED => num_mul_saturated,
NUM_MUL_CHECKED => num_mul_checked,
NUM_GT => num_gt,
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.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]*
fn num_mul_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
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
| NumIsMultipleOf | NumAddWrap | NumAddChecked | NumAddSaturated | NumDivUnchecked
| NumDivCeilUnchecked | NumPow | NumPowInt | NumSubWrap | NumSubChecked
| NumSubSaturated | NumMulWrap | NumMulChecked => {
| NumSubSaturated | NumMulWrap | NumMulSaturated | NumMulChecked => {
debug_assert_eq!(args.len(), 2);
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!")
}
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(
&LLVM_MUL_WITH_OVERFLOW[int_width],
&[lhs.into(), rhs.into()],
@ -6988,6 +6993,7 @@ fn build_float_binop<'a, 'ctx, 'env>(
}
NumSubWrap => unreachable!("wrapping subtraction is not defined on floats"),
NumMul => bd.build_float_mul(lhs, rhs, "mul_float").into(),
NumMulSaturated => bd.build_float_mul(lhs, rhs, "mul_float").into(),
NumMulChecked => {
let context = env.context;

View file

@ -549,6 +549,24 @@ impl<'a> LowLevelCall<'a> {
}
_ => 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 => {
let arg_layout = backend.storage.symbol_layouts[&self.arguments[0]];
match arg_layout {

View file

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

View file

@ -1075,66 +1075,67 @@ define_builtins! {
77 NUM_SUB_SATURATED: "subSaturated"
78 NUM_MUL_WRAP: "mulWrap"
79 NUM_MUL_CHECKED: "mulChecked"
80 NUM_INT: "Int"
81 NUM_FRAC: "Frac"
82 NUM_NATURAL: "Natural"
83 NUM_NAT: "Nat"
84 NUM_INT_CAST: "intCast"
85 NUM_IS_MULTIPLE_OF: "isMultipleOf"
86 NUM_DECIMAL: "Decimal"
87 NUM_DEC: "Dec" // the Num.Dectype alias
88 NUM_BYTES_TO_U16: "bytesToU16"
89 NUM_BYTES_TO_U32: "bytesToU32"
90 NUM_CAST_TO_NAT: "#castToNat"
91 NUM_DIV_CEIL: "divCeil"
92 NUM_DIV_CEIL_CHECKED: "divCeilChecked"
93 NUM_TO_STR: "toStr"
94 NUM_MIN_I8: "minI8"
95 NUM_MAX_I8: "maxI8"
96 NUM_MIN_U8: "minU8"
97 NUM_MAX_U8: "maxU8"
98 NUM_MIN_I16: "minI16"
99 NUM_MAX_I16: "maxI16"
100 NUM_MIN_U16: "minU16"
101 NUM_MAX_U16: "maxU16"
102 NUM_MIN_I32: "minI32"
103 NUM_MAX_I32: "maxI32"
104 NUM_MIN_U32: "minU32"
105 NUM_MAX_U32: "maxU32"
106 NUM_MIN_I64: "minI64"
107 NUM_MAX_I64: "maxI64"
108 NUM_MIN_U64: "minU64"
109 NUM_MAX_U64: "maxU64"
110 NUM_MIN_I128: "minI128"
111 NUM_MAX_I128: "maxI128"
112 NUM_TO_I8: "toI8"
113 NUM_TO_I8_CHECKED: "toI8Checked"
114 NUM_TO_I16: "toI16"
115 NUM_TO_I16_CHECKED: "toI16Checked"
116 NUM_TO_I32: "toI32"
117 NUM_TO_I32_CHECKED: "toI32Checked"
118 NUM_TO_I64: "toI64"
119 NUM_TO_I64_CHECKED: "toI64Checked"
120 NUM_TO_I128: "toI128"
121 NUM_TO_I128_CHECKED: "toI128Checked"
122 NUM_TO_U8: "toU8"
123 NUM_TO_U8_CHECKED: "toU8Checked"
124 NUM_TO_U16: "toU16"
125 NUM_TO_U16_CHECKED: "toU16Checked"
126 NUM_TO_U32: "toU32"
127 NUM_TO_U32_CHECKED: "toU32Checked"
128 NUM_TO_U64: "toU64"
129 NUM_TO_U64_CHECKED: "toU64Checked"
130 NUM_TO_U128: "toU128"
131 NUM_TO_U128_CHECKED: "toU128Checked"
132 NUM_TO_NAT: "toNat"
133 NUM_TO_NAT_CHECKED: "toNatChecked"
134 NUM_TO_F32: "toF32"
135 NUM_TO_F32_CHECKED: "toF32Checked"
136 NUM_TO_F64: "toF64"
137 NUM_TO_F64_CHECKED: "toF64Checked"
138 NUM_MAX_F64: "maxF64"
139 NUM_MIN_F64: "minF64"
80 NUM_MUL_SATURATED: "mulSaturated"
81 NUM_INT: "Int"
82 NUM_FRAC: "Frac"
83 NUM_NATURAL: "Natural"
84 NUM_NAT: "Nat"
85 NUM_INT_CAST: "intCast"
86 NUM_IS_MULTIPLE_OF: "isMultipleOf"
87 NUM_DECIMAL: "Decimal"
88 NUM_DEC: "Dec" // the Num.Dectype alias
89 NUM_BYTES_TO_U16: "bytesToU16"
90 NUM_BYTES_TO_U32: "bytesToU32"
91 NUM_CAST_TO_NAT: "#castToNat"
92 NUM_DIV_CEIL: "divCeil"
93 NUM_DIV_CEIL_CHECKED: "divCeilChecked"
94 NUM_TO_STR: "toStr"
95 NUM_MIN_I8: "minI8"
96 NUM_MAX_I8: "maxI8"
97 NUM_MIN_U8: "minU8"
98 NUM_MAX_U8: "maxU8"
99 NUM_MIN_I16: "minI16"
100 NUM_MAX_I16: "maxI16"
101 NUM_MIN_U16: "minU16"
102 NUM_MAX_U16: "maxU16"
103 NUM_MIN_I32: "minI32"
104 NUM_MAX_I32: "maxI32"
105 NUM_MIN_U32: "minU32"
106 NUM_MAX_U32: "maxU32"
107 NUM_MIN_I64: "minI64"
108 NUM_MAX_I64: "maxI64"
109 NUM_MIN_U64: "minU64"
110 NUM_MAX_U64: "maxU64"
111 NUM_MIN_I128: "minI128"
112 NUM_MAX_I128: "maxI128"
113 NUM_TO_I8: "toI8"
114 NUM_TO_I8_CHECKED: "toI8Checked"
115 NUM_TO_I16: "toI16"
116 NUM_TO_I16_CHECKED: "toI16Checked"
117 NUM_TO_I32: "toI32"
118 NUM_TO_I32_CHECKED: "toI32Checked"
119 NUM_TO_I64: "toI64"
120 NUM_TO_I64_CHECKED: "toI64Checked"
121 NUM_TO_I128: "toI128"
122 NUM_TO_I128_CHECKED: "toI128Checked"
123 NUM_TO_U8: "toU8"
124 NUM_TO_U8_CHECKED: "toU8Checked"
125 NUM_TO_U16: "toU16"
126 NUM_TO_U16_CHECKED: "toU16Checked"
127 NUM_TO_U32: "toU32"
128 NUM_TO_U32_CHECKED: "toU32Checked"
129 NUM_TO_U64: "toU64"
130 NUM_TO_U64_CHECKED: "toU64Checked"
131 NUM_TO_U128: "toU128"
132 NUM_TO_U128_CHECKED: "toU128Checked"
133 NUM_TO_NAT: "toNat"
134 NUM_TO_NAT_CHECKED: "toNatChecked"
135 NUM_TO_F32: "toF32"
136 NUM_TO_F32_CHECKED: "toF32Checked"
137 NUM_TO_F64: "toF64"
138 NUM_TO_F64_CHECKED: "toF64Checked"
139 NUM_MAX_F64: "maxF64"
140 NUM_MIN_F64: "minF64"
}
2 BOOL: "Bool" => {
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]),
And | Or | NumAdd | NumAddWrap | NumAddChecked | NumAddSaturated | NumSub | NumSubWrap
| NumSubChecked | NumSubSaturated | NumMul | NumMulWrap | NumMulChecked | NumGt
| NumGte | NumLt | NumLte | NumCompare | NumDivUnchecked | NumDivCeilUnchecked
| NumRemUnchecked | NumIsMultipleOf | NumPow | NumPowInt | NumBitwiseAnd
| NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy | NumShiftRightZfBy => {
arena.alloc_slice_copy(&[irrelevant, irrelevant])
}
| NumSubChecked | NumSubSaturated | NumMul | NumMulWrap | NumMulSaturated
| NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare | NumDivUnchecked
| NumDivCeilUnchecked | NumRemUnchecked | NumIsMultipleOf | NumPow | NumPowInt
| NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy
| NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
NumToStr | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked
| NumRound | NumCeiling | NumFloor | NumToFrac | Not | NumIsFinite | NumAtan | NumAcos

View file

@ -165,6 +165,7 @@ enum FirstOrder {
NumSubChecked,
NumMul,
NumMulWrap,
NumMulSaturated,
NumMulChecked,
NumGt,
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]
#[cfg(any(feature = "gen-llvm"))]
fn monomorphized_ints() {

View file

@ -348,6 +348,11 @@ fn num_mul_wrap() {
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"))]
#[test]
fn num_add_checked() {