a proper fix for llvm wasm checked arithmetic

This commit is contained in:
Folkert 2023-09-15 14:53:00 +02:00
parent fd7a7ba1e6
commit e850f94d05
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
4 changed files with 95 additions and 22 deletions

View file

@ -1154,6 +1154,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
build_int_binop(
env,
layout_interner,
parent,
int_width,
lhs_arg.into_int_value(),
@ -1183,6 +1184,7 @@ pub(crate) fn run_low_level<'a, 'ctx>(
build_int_binop(
env,
layout_interner,
parent,
int_width,
lhs_arg.into_int_value(),
@ -1429,6 +1431,7 @@ fn intwidth_from_layout(layout: InLayout) -> IntWidth {
fn build_int_binop<'ctx>(
env: &Env<'_, 'ctx, '_>,
layout_interner: &STLayoutInterner<'_>,
parent: FunctionValue<'ctx>,
int_width: IntWidth,
lhs: IntValue<'ctx>,
@ -1452,10 +1455,23 @@ fn build_int_binop<'ctx>(
throw_on_overflow(env, parent, result, "integer addition overflowed!")
}
NumAddWrap => bd.build_int_add(lhs, rhs, "add_int_wrap").into(),
NumAddChecked => env.call_intrinsic(
&LLVM_ADD_WITH_OVERFLOW[int_width],
&[lhs.into(), rhs.into()],
),
NumAddChecked => {
let with_overflow = env.call_intrinsic(
&LLVM_ADD_WITH_OVERFLOW[int_width],
&[lhs.into(), rhs.into()],
);
let layout = Layout::from_int_width(int_width);
let layout_repr = LayoutRepr::Struct(env.arena.alloc([layout, Layout::BOOL]));
use_roc_value(
env,
layout_interner,
layout_repr,
with_overflow,
"num_add_with_overflow",
)
}
NumAddSaturated => {
env.call_intrinsic(&LLVM_ADD_SATURATED[int_width], &[lhs.into(), rhs.into()])
}
@ -1470,10 +1486,23 @@ fn build_int_binop<'ctx>(
throw_on_overflow(env, parent, result, "integer subtraction overflowed!")
}
NumSubWrap => bd.build_int_sub(lhs, rhs, "sub_int").into(),
NumSubChecked => env.call_intrinsic(
&LLVM_SUB_WITH_OVERFLOW[int_width],
&[lhs.into(), rhs.into()],
),
NumSubChecked => {
let with_overflow = env.call_intrinsic(
&LLVM_SUB_WITH_OVERFLOW[int_width],
&[lhs.into(), rhs.into()],
);
let layout = Layout::from_int_width(int_width);
let layout_repr = LayoutRepr::Struct(env.arena.alloc([layout, Layout::BOOL]));
use_roc_value(
env,
layout_interner,
layout_repr,
with_overflow,
"num_sub_with_overflow",
)
}
NumSubSaturated => {
env.call_intrinsic(&LLVM_SUB_SATURATED[int_width], &[lhs.into(), rhs.into()])
}
@ -1493,10 +1522,23 @@ fn build_int_binop<'ctx>(
&[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()],
),
NumMulChecked => {
let with_overflow = env.call_intrinsic(
&LLVM_MUL_WITH_OVERFLOW[int_width],
&[lhs.into(), rhs.into()],
);
let layout = Layout::from_int_width(int_width);
let layout_repr = LayoutRepr::Struct(env.arena.alloc([layout, Layout::BOOL]));
use_roc_value(
env,
layout_interner,
layout_repr,
with_overflow,
"num_mul_with_overflow",
)
}
NumGt => {
if int_width.is_signed() {
bd.build_int_compare(SGT, lhs, rhs, "gt_int").into()
@ -1671,6 +1713,7 @@ pub fn build_num_binop<'a, 'ctx>(
match lhs_builtin {
Int(int_width) => build_int_binop(
env,
layout_interner,
parent,
int_width,
lhs_arg.into_int_value(),
@ -2088,10 +2131,10 @@ pub(crate) fn dec_binop_with_unchecked<'ctx>(
/// Zig returns a nominal `WithOverflow(Dec)` struct (see [zig_with_overflow_roc_dec]),
/// but the Roc side may flatten the overflow struct. LLVM does not admit comparisons
/// between the two representations, so always cast to the Roc representation.
fn change_with_overflow_dec_to_roc_type<'a, 'ctx>(
fn change_with_overflow_to_roc_type<'a, 'ctx>(
env: &Env<'a, 'ctx, '_>,
layout_interner: &STLayoutInterner<'a>,
val: StructValue<'ctx>,
val: impl BasicValue<'ctx>,
return_layout: InLayout<'a>,
) -> BasicValueEnum<'ctx> {
let return_type = convert::basic_type_from_layout(
@ -2099,7 +2142,8 @@ fn change_with_overflow_dec_to_roc_type<'a, 'ctx>(
layout_interner,
layout_interner.get_repr(return_layout),
);
let casted = cast_basic_basic(env.builder, val.into(), return_type);
let casted = cast_basic_basic(env.builder, val.as_basic_value_enum(), return_type);
use_roc_value(
env,
layout_interner,
@ -2123,15 +2167,15 @@ fn build_dec_binop<'a, 'ctx>(
match op {
NumAddChecked => {
let val = dec_binop_with_overflow(env, bitcode::DEC_ADD_WITH_OVERFLOW, lhs, rhs);
change_with_overflow_dec_to_roc_type(env, layout_interner, val, return_layout)
change_with_overflow_to_roc_type(env, layout_interner, val, return_layout)
}
NumSubChecked => {
let val = dec_binop_with_overflow(env, bitcode::DEC_SUB_WITH_OVERFLOW, lhs, rhs);
change_with_overflow_dec_to_roc_type(env, layout_interner, val, return_layout)
change_with_overflow_to_roc_type(env, layout_interner, val, return_layout)
}
NumMulChecked => {
let val = dec_binop_with_overflow(env, bitcode::DEC_MUL_WITH_OVERFLOW, lhs, rhs);
change_with_overflow_dec_to_roc_type(env, layout_interner, val, return_layout)
change_with_overflow_to_roc_type(env, layout_interner, val, return_layout)
}
NumAdd => build_dec_binop_throw_on_overflow(
env,

View file

@ -2534,6 +2534,21 @@ impl<'a> Layout<'a> {
}
}
pub const fn from_int_width(int_width: IntWidth) -> InLayout<'static> {
match int_width {
IntWidth::U8 => Layout::U8,
IntWidth::U16 => Layout::U16,
IntWidth::U32 => Layout::U32,
IntWidth::U64 => Layout::U64,
IntWidth::U128 => Layout::U128,
IntWidth::I8 => Layout::I8,
IntWidth::I16 => Layout::I16,
IntWidth::I32 => Layout::I32,
IntWidth::I64 => Layout::I64,
IntWidth::I128 => Layout::I128,
}
}
fn layout_from_ranged_number(
env: &mut Env<'a, '_>,
range: NumericRange,

View file

@ -73,10 +73,7 @@ impl TargetInfo {
// This is a reasonable default for most architectures. We want to pass large values by
// reference because it's more efficient than copying them around on the stack, and puts
// less pressure on CPU registers.
// TODO: change this back to `self.ptr_size() * 4`. We make some assumptions about how
// return valus are passed somewhere that make that not quite work for the llvm wasm backend.
8 * 4
self.ptr_size() * 4
}
pub const fn ptr_alignment_bytes(&self) -> usize {

View file

@ -3995,6 +3995,23 @@ fn mul_checked_u128() {
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn add_checked_u128() {
assert_evals_to!(
indoc!(
r#"
x : Result U128 [ Overflow ]
x = Num.addChecked 5u128 2u128
x
"#
),
RocResult::ok(5u128 + 2u128),
RocResult<u128, ()>
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn num_min() {