diff --git a/compiler/builtins/bitcode/src/dec.zig b/compiler/builtins/bitcode/src/dec.zig index 89ea505a21..4d0e813111 100644 --- a/compiler/builtins/bitcode/src/dec.zig +++ b/compiler/builtins/bitcode/src/dec.zig @@ -231,7 +231,7 @@ pub const RocDec = extern struct { const answer = RocDec.addWithOverflow(self, other); if (answer.has_overflowed) { - roc_panic("Dec overflow", 1); + roc_panic("Decimal addition overflowed!", 1); unreachable; } else { return answer.value; @@ -263,7 +263,21 @@ pub const RocDec = extern struct { const answer = RocDec.subWithOverflow(self, other); if (answer.has_overflowed) { - @panic("TODO runtime exception for overflow!"); + roc_panic("Decimal subtraction overflowed!", 1); + unreachable; + } else { + return answer.value; + } + } + + pub fn subSaturated(self: RocDec, other: RocDec) RocDec { + const answer = RocDec.subWithOverflow(self, other); + if (answer.has_overflowed) { + if (answer.value.num < 0) { + return RocDec.max; + } else { + return RocDec.min; + } } else { return answer.value; } @@ -1117,3 +1131,11 @@ pub fn addOrPanicC(arg1: RocDec, arg2: RocDec) callconv(.C) RocDec { pub fn addSaturatedC(arg1: RocDec, arg2: RocDec) callconv(.C) RocDec { return @call(.{ .modifier = always_inline }, RocDec.addSaturated, .{ arg1, arg2 }); } + +pub fn subOrPanicC(arg1: RocDec, arg2: RocDec) callconv(.C) RocDec { + return @call(.{ .modifier = always_inline }, RocDec.sub, .{ arg1, arg2 }); +} + +pub fn subSaturatedC(arg1: RocDec, arg2: RocDec) callconv(.C) RocDec { + return @call(.{ .modifier = always_inline }, RocDec.subSaturated, .{ arg1, arg2 }); +} diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index 6ca7007e14..067b6a51f6 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -17,12 +17,17 @@ comptime { exportDecFn(dec.eqC, "eq"); exportDecFn(dec.neqC, "neq"); exportDecFn(dec.negateC, "negate"); - exportDecFn(dec.addC, "add_with_overflow"); - exportDecFn(dec.subC, "sub_with_overflow"); - exportDecFn(dec.mulC, "mul_with_overflow"); exportDecFn(dec.divC, "div"); + + exportDecFn(dec.addC, "add_with_overflow"); exportDecFn(dec.addOrPanicC, "add_or_panic"); exportDecFn(dec.addSaturatedC, "add_saturated"); + + exportDecFn(dec.subC, "sub_with_overflow"); + exportDecFn(dec.subOrPanicC, "sub_or_panic"); + exportDecFn(dec.subSaturatedC, "sub_saturated"); + + exportDecFn(dec.mulC, "mul_with_overflow"); } // List Module @@ -107,6 +112,10 @@ comptime { num.exportAddWithOverflow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_with_overflow."); num.exportAddOrPanic(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_or_panic."); num.exportAddSaturatedInt(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_saturated."); + + num.exportSubWithOverflow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".sub_with_overflow."); + num.exportSubOrPanic(T, ROC_BUILTINS ++ "." ++ NUM ++ ".sub_or_panic."); + num.exportSubSaturatedInt(T, ROC_BUILTINS ++ "." ++ NUM ++ ".sub_saturated."); } inline for (INTEGERS) |FROM| { @@ -129,6 +138,7 @@ comptime { num.exportLog(T, ROC_BUILTINS ++ "." ++ NUM ++ ".log."); num.exportAddWithOverflow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".add_with_overflow."); + num.exportSubWithOverflow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".sub_with_overflow."); num.exportIsFinite(T, ROC_BUILTINS ++ "." ++ NUM ++ ".is_finite."); } diff --git a/compiler/builtins/bitcode/src/num.zig b/compiler/builtins/bitcode/src/num.zig index 63f339765c..0dcb9b2d83 100644 --- a/compiler/builtins/bitcode/src/num.zig +++ b/compiler/builtins/bitcode/src/num.zig @@ -254,3 +254,75 @@ pub fn exportAddOrPanic(comptime T: type, comptime name: []const u8) void { }.func; @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); } + +fn subWithOverflow(comptime T: type, self: T, other: T) WithOverflow(T) { + switch (@typeInfo(T)) { + .Int => { + var answer: T = undefined; + const overflowed = @subWithOverflow(T, self, other, &answer); + return .{ .value = answer, .has_overflowed = overflowed }; + }, + else => { + const answer = self - other; + const overflowed = !std.math.isFinite(answer); + return .{ .value = answer, .has_overflowed = overflowed }; + }, + } +} + +pub fn exportSubWithOverflow(comptime T: type, comptime name: []const u8) void { + comptime var f = struct { + fn func(self: T, other: T) callconv(.C) WithOverflow(T) { + return @call(.{ .modifier = always_inline }, subWithOverflow, .{ T, self, other }); + } + }.func; + @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); +} + +pub fn exportSubSaturatedInt(comptime T: type, comptime name: []const u8) void { + comptime var f = struct { + fn func(self: T, other: T) callconv(.C) T { + const result = subWithOverflow(T, self, other); + if (result.has_overflowed) { + if (result.value < 0) { + return std.math.maxInt(T); + } else { + return std.math.minInt(T); + } + } else { + return result.value; + } + } + }.func; + @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); +} + +pub fn exportSubOrPanic(comptime T: type, comptime name: []const u8) void { + comptime var f = struct { + fn func(self: T, other: T) callconv(.C) T { + const result = subWithOverflow(T, self, other); + if (result.has_overflowed) { + roc_panic("integer subtraction overflowed!", 1); + unreachable; + } else { + return result.value; + } + } + }.func; + @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); +} + +fn mulWithOverflow(comptime T: type, self: T, other: T) WithOverflow(T) { + switch (@typeInfo(T)) { + .Int => { + var answer: T = undefined; + const overflowed = @mulWithOverflow(T, self, other, &answer); + return .{ .value = answer, .has_overflowed = overflowed }; + }, + else => { + const answer = self + other; + const overflowed = !std.math.isFinite(answer); + return .{ .value = answer, .has_overflowed = overflowed }; + }, + } +} diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 64d20ad5fb..a38e1e506d 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -287,8 +287,15 @@ pub const NUM_ROUND_F64: IntrinsicName = int_intrinsic!("roc_builtins.num.round_ 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"); +pub const NUM_ADD_CHECKED_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.add_with_overflow"); +pub const NUM_ADD_CHECKED_FLOAT: IntrinsicName = + float_intrinsic!("roc_builtins.num.add_with_overflow"); + +pub const NUM_SUB_OR_PANIC_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.sub_or_panic"); +pub const NUM_SUB_SATURATED_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.sub_saturated"); +pub const NUM_SUB_CHECKED_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.sub_with_overflow"); +pub const NUM_SUB_CHECKED_FLOAT: IntrinsicName = + float_intrinsic!("roc_builtins.num.sub_with_overflow"); pub const NUM_BYTES_TO_U16: &str = "roc_builtins.num.bytes_to_u16"; pub const NUM_BYTES_TO_U32: &str = "roc_builtins.num.bytes_to_u32"; @@ -371,12 +378,14 @@ pub const DEC_FROM_F64: &str = "roc_builtins.dec.from_f64"; pub const DEC_EQ: &str = "roc_builtins.dec.eq"; pub const DEC_NEQ: &str = "roc_builtins.dec.neq"; pub const DEC_NEGATE: &str = "roc_builtins.dec.negate"; -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_WITH_OVERFLOW: &str = "roc_builtins.dec.add_with_overflow"; 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 DEC_SUB_WITH_OVERFLOW: &str = "roc_builtins.dec.sub_with_overflow"; +pub const DEC_SUB_OR_PANIC: &str = "roc_builtins.dec.sub_or_panic"; +pub const DEC_SUB_SATURATED: &str = "roc_builtins.dec.sub_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"; diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index d6975ff4db..bd2763c166 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -6960,29 +6960,7 @@ fn build_float_binop<'a, 'ctx, 'env>( struct_value.into() } NumAddWrap => unreachable!("wrapping addition is not defined on floats"), - NumSub => { - let builder = env.builder; - let context = env.context; - - let result = bd.build_float_sub(lhs, rhs, "sub_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 subtraction overflowed!"); - - builder.position_at_end(then_block); - - result.into() - } + NumSub => bd.build_float_sub(lhs, rhs, "sub_float").into(), NumSubChecked => { let context = env.context; diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index aac73e3a22..4c07281924 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -339,8 +339,8 @@ impl<'a> LowLevelCall<'a> { NumAddWrap => match self.ret_layout { Layout::Builtin(Builtin::Int(width)) => match width { IntWidth::I128 | IntWidth::U128 => { - self.load_args_and_call_zig(backend, &bitcode::NUM_ADD_OR_PANIC_INT[width]) // TODO: don't panic + self.load_args_and_call_zig(backend, &bitcode::NUM_ADD_OR_PANIC_INT[width]) } IntWidth::I64 | IntWidth::U64 => { self.load_args(backend); @@ -368,8 +368,8 @@ impl<'a> LowLevelCall<'a> { FloatWidth::F128 => todo!("Num.add for f128"), }, Layout::Builtin(Builtin::Decimal) => { - self.load_args_and_call_zig(backend, bitcode::DEC_ADD_OR_PANIC) // TODO: don't panic + self.load_args_and_call_zig(backend, bitcode::DEC_ADD_OR_PANIC) } _ => panic_ret_type(), }, @@ -378,14 +378,12 @@ impl<'a> LowLevelCall<'a> { 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_CHECKED_INT[width]) + } + Layout::Builtin(Builtin::Float(width)) => { + self.load_args_and_call_zig(backend, &bitcode::NUM_ADD_CHECKED_FLOAT[width]) + } Layout::Builtin(Builtin::Decimal) => { self.load_args_and_call_zig(backend, bitcode::DEC_ADD_WITH_OVERFLOW) } @@ -410,33 +408,97 @@ impl<'a> LowLevelCall<'a> { _ => panic_ret_type(), }, - NumSub => { - self.load_args(backend); - match CodeGenNumType::from(self.ret_layout) { - I32 => backend.code_builder.i32_sub(), - I64 => backend.code_builder.i64_sub(), - F32 => backend.code_builder.f32_sub(), - F64 => backend.code_builder.f64_sub(), - Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_SUB_WITH_OVERFLOW), - x => todo!("{:?} for {:?}", self.lowlevel, x), + NumSub => match self.ret_layout { + Layout::Builtin(Builtin::Int(width)) => { + self.load_args_and_call_zig(backend, &bitcode::NUM_SUB_OR_PANIC_INT[width]) } - } - NumSubWrap => { - self.load_args(backend); - match CodeGenNumType::from(self.ret_layout) { - I32 => { - backend.code_builder.i32_sub(); - self.wrap_i32(backend); + Layout::Builtin(Builtin::Float(width)) => match width { + FloatWidth::F32 => { + self.load_args(backend); + backend.code_builder.f32_sub() } - I64 => backend.code_builder.i64_sub(), - F32 => backend.code_builder.f32_sub(), - F64 => backend.code_builder.f64_sub(), - Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_SUB_WITH_OVERFLOW), - x => todo!("{:?} for {:?}", self.lowlevel, x), + FloatWidth::F64 => { + self.load_args(backend); + backend.code_builder.f64_sub() + } + FloatWidth::F128 => todo!("Num.sub for f128"), + }, + Layout::Builtin(Builtin::Decimal) => { + self.load_args_and_call_zig(backend, bitcode::DEC_SUB_OR_PANIC) + } + _ => panic_ret_type(), + }, + + NumSubWrap => match self.ret_layout { + Layout::Builtin(Builtin::Int(width)) => match width { + IntWidth::I128 | IntWidth::U128 => { + // TODO: don't panic + self.load_args_and_call_zig(backend, &bitcode::NUM_SUB_OR_PANIC_INT[width]) + } + IntWidth::I64 | IntWidth::U64 => { + self.load_args(backend); + backend.code_builder.i64_sub() + } + IntWidth::I32 | IntWidth::U32 => { + self.load_args(backend); + backend.code_builder.i32_sub() + } + _ => { + self.load_args(backend); + backend.code_builder.i32_sub(); + self.wrap_small_int(backend, width); + } + }, + Layout::Builtin(Builtin::Float(width)) => match width { + FloatWidth::F32 => { + self.load_args(backend); + backend.code_builder.f32_sub() + } + FloatWidth::F64 => { + self.load_args(backend); + backend.code_builder.f64_sub() + } + FloatWidth::F128 => todo!("Num.sub for f128"), + }, + Layout::Builtin(Builtin::Decimal) => { + // TODO: don't panic + self.load_args_and_call_zig(backend, bitcode::DEC_SUB_OR_PANIC) + } + _ => panic_ret_type(), + }, + NumSubChecked => { + 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_SUB_CHECKED_INT[width]) + } + Layout::Builtin(Builtin::Float(width)) => { + self.load_args_and_call_zig(backend, &bitcode::NUM_SUB_CHECKED_FLOAT[width]) + } + Layout::Builtin(Builtin::Decimal) => { + self.load_args_and_call_zig(backend, bitcode::DEC_SUB_WITH_OVERFLOW) + } + x => internal_error!("NumSubChecked is not defined for {:?}", x), } } - NumSubChecked => todo!("{:?}", self.lowlevel), - NumSubSaturated => todo!("{:?}", self.lowlevel), + NumSubSaturated => match self.ret_layout { + Layout::Builtin(Builtin::Int(width)) => { + self.load_args_and_call_zig(backend, &bitcode::NUM_SUB_SATURATED_INT[width]) + } + Layout::Builtin(Builtin::Float(FloatWidth::F32)) => { + self.load_args(backend); + backend.code_builder.f32_sub() + } + Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { + self.load_args(backend); + backend.code_builder.f64_sub() + } + Layout::Builtin(Builtin::Decimal) => { + self.load_args_and_call_zig(backend, bitcode::DEC_SUB_SATURATED) + } + _ => panic_ret_type(), + }, + NumMul => { self.load_args(backend); match CodeGenNumType::from(self.ret_layout) { diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index 32f8818e8e..b9d939ed06 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -1767,7 +1767,7 @@ fn float_add_overflow() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[should_panic(expected = r#"Roc failed with message: "integer subtraction overflowed!"#)] fn int_sub_overflow() { assert_evals_to!( @@ -1796,8 +1796,7 @@ fn int_sub_wrap() { } #[test] -#[cfg(any(feature = "gen-llvm"))] -#[should_panic(expected = r#"Roc failed with message: "float subtraction overflowed!"#)] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn float_sub_overflow() { assert_evals_to!( indoc!( @@ -1805,13 +1804,13 @@ fn float_sub_overflow() { -1.7976931348623157e308 - 1.7976931348623157e308 "# ), - 0.0, + -f64::INFINITY, f64 ); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn int_sub_checked() { assert_evals_to!( indoc!( @@ -1839,7 +1838,7 @@ fn int_sub_checked() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn float_sub_checked() { assert_evals_to!( indoc!(