mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 11:52:19 +00:00
wasm: implement all addition operators (wrapping, panicking, saturating, and checked)
This commit is contained in:
parent
d7ac8cfcbc
commit
b1193e4134
9 changed files with 99 additions and 77 deletions
|
@ -231,7 +231,22 @@ pub const RocDec = extern struct {
|
|||
const answer = RocDec.addWithOverflow(self, other);
|
||||
|
||||
if (answer.has_overflowed) {
|
||||
@panic("TODO runtime exception for overflow!");
|
||||
roc_panic("Dec overflow", 1);
|
||||
unreachable;
|
||||
} else {
|
||||
return answer.value;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addSaturated(self: RocDec, other: RocDec) RocDec {
|
||||
const answer = RocDec.addWithOverflow(self, other);
|
||||
if (answer.has_overflowed) {
|
||||
// We can unambiguously tell which way it wrapped, because we have 129 bits including the overflow bit
|
||||
if (answer.value.num < 0) {
|
||||
return RocDec.max;
|
||||
} else {
|
||||
return RocDec.min;
|
||||
}
|
||||
} else {
|
||||
return answer.value;
|
||||
}
|
||||
|
@ -1096,12 +1111,9 @@ pub fn divC(arg1: RocDec, arg2: RocDec) callconv(.C) i128 {
|
|||
}
|
||||
|
||||
pub fn addOrPanicC(arg1: RocDec, arg2: RocDec) callconv(.C) RocDec {
|
||||
const result = arg1.addWithOverflow(arg2);
|
||||
if (result.has_overflowed) {
|
||||
const todo_why_does_this_argument_exist = 1;
|
||||
roc_panic("Dec overflow", todo_why_does_this_argument_exist);
|
||||
unreachable;
|
||||
} else {
|
||||
return result.value;
|
||||
return @call(.{ .modifier = always_inline }, RocDec.add, .{ arg1, arg2 });
|
||||
}
|
||||
|
||||
pub fn addSaturatedC(arg1: RocDec, arg2: RocDec) callconv(.C) RocDec {
|
||||
return @call(.{ .modifier = always_inline }, RocDec.addSaturated, .{ arg1, arg2 });
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ comptime {
|
|||
exportDecFn(dec.mulC, "mul_with_overflow");
|
||||
exportDecFn(dec.divC, "div");
|
||||
exportDecFn(dec.addOrPanicC, "add_or_panic");
|
||||
exportDecFn(dec.addSaturatedC, "add_saturated");
|
||||
}
|
||||
|
||||
// List Module
|
||||
|
@ -89,7 +90,6 @@ comptime {
|
|||
const num = @import("num.zig");
|
||||
|
||||
const INTEGERS = [_]type{ i8, i16, i32, i64, i128, u8, u16, u32, u64, u128 };
|
||||
const WIDEINTS = [_]type{ i16, i32, i64, i128, i256, u16, u32, u64, u128, u256 };
|
||||
const FLOATS = [_]type{ f32, f64 };
|
||||
const NUMBERS = INTEGERS ++ FLOATS;
|
||||
|
||||
|
@ -97,7 +97,7 @@ comptime {
|
|||
exportNumFn(num.bytesToU16C, "bytes_to_u16");
|
||||
exportNumFn(num.bytesToU32C, "bytes_to_u32");
|
||||
|
||||
inline for (INTEGERS) |T, i| {
|
||||
inline for (INTEGERS) |T| {
|
||||
num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow_int.");
|
||||
num.exportDivCeil(T, ROC_BUILTINS ++ "." ++ NUM ++ ".div_ceil.");
|
||||
|
||||
|
@ -106,8 +106,7 @@ comptime {
|
|||
|
||||
num.exportAddWithOverflow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_with_overflow.");
|
||||
num.exportAddOrPanic(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_or_panic.");
|
||||
const Wider = WIDEINTS[i];
|
||||
num.exportAddSaturatedInt(T, Wider, ROC_BUILTINS ++ "." ++ NUM ++ ".add_saturated.");
|
||||
num.exportAddSaturatedInt(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_saturated.");
|
||||
}
|
||||
|
||||
inline for (INTEGERS) |FROM| {
|
||||
|
|
|
@ -221,22 +221,19 @@ pub fn exportAddWithOverflow(comptime T: type, comptime name: []const u8) void {
|
|||
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||
}
|
||||
|
||||
pub fn exportAddSaturatedInt(comptime T: type, comptime Wider: type, comptime name: []const u8) void {
|
||||
pub fn exportAddSaturatedInt(comptime T: type, comptime name: []const u8) void {
|
||||
comptime var f = struct {
|
||||
fn func(self: T, other: T) callconv(.C) T {
|
||||
const self_w: Wider = self;
|
||||
const other_w: Wider = other;
|
||||
const answer = self_w + other_w;
|
||||
|
||||
const max = std.math.maxInt(T);
|
||||
const min = std.math.minInt(T);
|
||||
|
||||
if (answer > max) {
|
||||
return max;
|
||||
} else if (answer < min) {
|
||||
return min;
|
||||
const result = addWithOverflow(T, self, other);
|
||||
if (result.has_overflowed) {
|
||||
// We can unambiguously tell which way it wrapped, because we have N+1 bits including the overflow bit
|
||||
if (result.value < 0) {
|
||||
return std.math.maxInt(T);
|
||||
} else {
|
||||
return @intCast(T, answer);
|
||||
return std.math.minInt(T);
|
||||
}
|
||||
} else {
|
||||
return result.value;
|
||||
}
|
||||
}
|
||||
}.func;
|
||||
|
|
|
@ -285,6 +285,8 @@ pub const NUM_DIV_CEIL: IntrinsicName = int_intrinsic!("roc_builtins.num.div_cei
|
|||
pub const NUM_ROUND_F32: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f32");
|
||||
pub const NUM_ROUND_F64: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f64");
|
||||
|
||||
pub const NUM_ADD_OR_PANIC_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.add_or_panic");
|
||||
pub const NUM_ADD_SATURATED_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.add_saturated");
|
||||
pub const NUM_ADD_WITH_OVERFLOW_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.add_with_overflow");
|
||||
pub const NUM_ADD_WITH_OVERFLOW_FLOAT: IntrinsicName = float_intrinsic!("roc_builtins.num.add_with_overflow");
|
||||
|
||||
|
@ -373,6 +375,8 @@ pub const DEC_ADD_WITH_OVERFLOW: &str = "roc_builtins.dec.add_with_overflow";
|
|||
pub const DEC_SUB_WITH_OVERFLOW: &str = "roc_builtins.dec.sub_with_overflow";
|
||||
pub const DEC_MUL_WITH_OVERFLOW: &str = "roc_builtins.dec.mul_with_overflow";
|
||||
pub const DEC_DIV: &str = "roc_builtins.dec.div";
|
||||
pub const DEC_ADD_OR_PANIC: &str = "roc_builtins.dec.add_or_panic";
|
||||
pub const DEC_ADD_SATURATED: &str = "roc_builtins.dec.add_saturated";
|
||||
|
||||
pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic";
|
||||
pub const UTILS_ALLOCATE_WITH_REFCOUNT: &str = "roc_builtins.utils.allocate_with_refcount";
|
||||
|
|
|
@ -6931,29 +6931,7 @@ fn build_float_binop<'a, 'ctx, 'env>(
|
|||
let bd = env.builder;
|
||||
|
||||
match op {
|
||||
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(env, &[result.into()], &bitcode::NUM_IS_FINITE[float_width])
|
||||
.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()
|
||||
}
|
||||
NumAdd => bd.build_float_add(lhs, rhs, "add_float").into(),
|
||||
NumAddChecked => {
|
||||
let context = env.context;
|
||||
|
||||
|
|
|
@ -319,20 +319,24 @@ impl<'a> LowLevelCall<'a> {
|
|||
}
|
||||
|
||||
// Num
|
||||
NumAdd => {
|
||||
NumAdd => match self.ret_layout {
|
||||
Layout::Builtin(Builtin::Int(width)) => {
|
||||
self.load_args_and_call_zig(backend, &bitcode::NUM_ADD_OR_PANIC_INT[width])
|
||||
}
|
||||
Layout::Builtin(Builtin::Float(FloatWidth::F32)) => {
|
||||
self.load_args(backend);
|
||||
match CodeGenNumType::from(self.ret_layout) {
|
||||
CodeGenNumType::I32 => backend.code_builder.i32_add(),
|
||||
CodeGenNumType::I64 => backend.code_builder.i64_add(),
|
||||
CodeGenNumType::F32 => backend.code_builder.f32_add(),
|
||||
CodeGenNumType::F64 => backend.code_builder.f64_add(),
|
||||
CodeGenNumType::I128 => todo!("{:?}", self.lowlevel),
|
||||
CodeGenNumType::F128 => todo!("{:?}", self.lowlevel),
|
||||
CodeGenNumType::Decimal => {
|
||||
self.load_args_and_call_zig(backend, bitcode::DEC_ADD_WITH_OVERFLOW)
|
||||
backend.code_builder.f32_add()
|
||||
}
|
||||
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
|
||||
self.load_args(backend);
|
||||
backend.code_builder.f64_add()
|
||||
}
|
||||
Layout::Builtin(Builtin::Float(FloatWidth::F128)) => todo!("Num.add for f128"),
|
||||
Layout::Builtin(Builtin::Decimal) => {
|
||||
self.load_args_and_call_zig(backend, bitcode::DEC_ADD_OR_PANIC)
|
||||
}
|
||||
_ => panic_ret_type(),
|
||||
},
|
||||
|
||||
NumAddWrap => {
|
||||
self.load_args(backend);
|
||||
|
@ -344,24 +348,53 @@ impl<'a> LowLevelCall<'a> {
|
|||
I64 => backend.code_builder.i64_add(),
|
||||
F32 => backend.code_builder.f32_add(),
|
||||
F64 => backend.code_builder.f64_add(),
|
||||
Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_ADD_WITH_OVERFLOW),
|
||||
x => todo!("{:?} for {:?}", self.lowlevel, x),
|
||||
|
||||
I128 => self.load_args_and_call_zig(
|
||||
backend,
|
||||
&bitcode::NUM_ADD_OR_PANIC_INT[IntWidth::I128], // TODO: don't panic
|
||||
),
|
||||
|
||||
Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_ADD_OR_PANIC), // TODO: don't panic
|
||||
|
||||
F128 => todo!("f128 NumAddWrap"),
|
||||
}
|
||||
}
|
||||
NumToStr => todo!("{:?}", self.lowlevel),
|
||||
NumAddChecked => {
|
||||
let arg_layout = backend.storage.symbol_layouts[&self.arguments[0]];
|
||||
match arg_layout {
|
||||
Layout::Builtin(Builtin::Int(width)) => {
|
||||
self.load_args_and_call_zig(backend, &bitcode::NUM_ADD_WITH_OVERFLOW_INT[width])
|
||||
}
|
||||
Layout::Builtin(Builtin::Float(width)) => {
|
||||
self.load_args_and_call_zig(backend, &bitcode::NUM_ADD_WITH_OVERFLOW_FLOAT[width])
|
||||
Layout::Builtin(Builtin::Int(width)) => self.load_args_and_call_zig(
|
||||
backend,
|
||||
&bitcode::NUM_ADD_WITH_OVERFLOW_INT[width],
|
||||
),
|
||||
Layout::Builtin(Builtin::Float(width)) => self.load_args_and_call_zig(
|
||||
backend,
|
||||
&bitcode::NUM_ADD_WITH_OVERFLOW_FLOAT[width],
|
||||
),
|
||||
Layout::Builtin(Builtin::Decimal) => {
|
||||
self.load_args_and_call_zig(backend, bitcode::DEC_ADD_WITH_OVERFLOW)
|
||||
}
|
||||
x => internal_error!("NumAddChecked is not defined for {:?}", x),
|
||||
}
|
||||
}
|
||||
NumAddSaturated => todo!("{:?}", self.lowlevel),
|
||||
NumAddSaturated => match self.ret_layout {
|
||||
Layout::Builtin(Builtin::Int(width)) => {
|
||||
self.load_args_and_call_zig(backend, &bitcode::NUM_ADD_SATURATED_INT[width])
|
||||
}
|
||||
Layout::Builtin(Builtin::Float(FloatWidth::F32)) => {
|
||||
self.load_args(backend);
|
||||
backend.code_builder.f32_add()
|
||||
}
|
||||
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
|
||||
self.load_args(backend);
|
||||
backend.code_builder.f64_add()
|
||||
}
|
||||
Layout::Builtin(Builtin::Decimal) => {
|
||||
self.load_args_and_call_zig(backend, bitcode::DEC_ADD_SATURATED)
|
||||
}
|
||||
_ => panic_ret_type(),
|
||||
},
|
||||
|
||||
NumSub => {
|
||||
self.load_args(backend);
|
||||
match CodeGenNumType::from(self.ret_layout) {
|
||||
|
|
|
@ -1664,7 +1664,7 @@ fn atan() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[should_panic(expected = r#"Roc failed with message: "integer addition overflowed!"#)]
|
||||
fn int_add_overflow() {
|
||||
assert_evals_to!(
|
||||
|
@ -1753,8 +1753,7 @@ fn float_add_checked_fail() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[should_panic(expected = r#"Roc failed with message: "float addition overflowed!"#)]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn float_add_overflow() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -1762,7 +1761,7 @@ fn float_add_overflow() {
|
|||
1.7976931348623157e308 + 1.7976931348623157e308
|
||||
"#
|
||||
),
|
||||
0.0,
|
||||
f64::INFINITY,
|
||||
f64
|
||||
);
|
||||
}
|
||||
|
@ -3095,7 +3094,7 @@ fn u8_mul_greater_than_i8() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn add_saturated() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
|
|
@ -1059,7 +1059,7 @@ fn call_with_bad_record_runtime_error() {
|
|||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-wasm"))]
|
||||
#[should_panic(expected = r#"Can't create record with improper layout"#)]
|
||||
#[should_panic(expected = r#"Roc failed with message: "Can't create record with improper layout"#)]
|
||||
fn call_with_bad_record_runtime_error() {
|
||||
expect_runtime_error_panic!(indoc!(
|
||||
r#"
|
||||
|
|
|
@ -208,7 +208,7 @@ where
|
|||
match test_wrapper.call(&[]) {
|
||||
Err(e) => {
|
||||
if let Some(msg) = get_roc_panic_msg(&instance, memory) {
|
||||
Err(msg)
|
||||
Err(format!("Roc failed with message: \"{}\"", msg))
|
||||
} else {
|
||||
Err(e.to_string())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue