Port Hare's math::checked to lib/math/checked/ as a two-file module
mirroring the upstream split:
- checked.ww (ref/hare/math/checked/checked.ha): add*/sub*/mul*
returning (result, overflow) with wrapping semantics — addi/addu/
subi/subu 8-64 and muli/mulu 8-32 (22 fns).
- saturating.ww (ref/hare/math/checked/saturating.ha): sat_* clamping
to the type's range on overflow — sat_addi/addu/subi 8-64 and
sat_muli/mulu 8-32 (18 fns).
checked_test.ww drives the verbatim Hare @test vectors (crash-trick
idiom) via cross-module tuple-return destructure for the overflow fns;
wrapped by test/wcc/969_checked_run.c. Both stages emit byte-identical
asm; make test-unit green.
Three ww adaptations vs Hare, all forced by language differences, none
behavioral (documented at the sites):
- no if-as-expression -> `return if (c) X else Y` becomes if-stmt.
- no implicit integer promotion -> the mul overflow compares use an
explicit widening cast.
- sub-word arithmetic truncates only on store to a typed lvalue, so
unsigned overflow tests force the wrap through a typed `res`.
Deferred as faithful Hare-subsets (Hare splits per type; no inlining):
- size-typed *z variants: no `size` type yet (#85).
- int/uint native-width variants: ww int/uint are 64-bit, a silent
overflow-boundary width divergence.
- 64-bit muls (muli64/mulu64/powi64, sat_muli64/sat_mulu64) and the
muli/mulu dispatchers: need math::mulu64 (128-bit product).
- sat_subu8/16/32/64: need types::U*_MIN, not yet in lib/types.
Saturating sat_* reference the types limits at RUNTIME (conditional
return, not a const-initializer), which resolves cross-module today
(#88 is const-fold-only). subi64's I64_MAX/I64_MIN boundary @test vector
is omitted while #89 is open (its I64_MIN literal miscompiles on
wwstage); the saturating I64_MIN assertions use the types.I64_MIN
def-ref, which is byte-id clean.
387 lines
14 KiB
Plaintext
387 lines
14 KiB
Plaintext
package checked;
|
|
|
|
// Directory import pulls both checked.ww and saturating.ww (the two
|
|
// files of this module); a bare `import checked` would resolve only to
|
|
// the checked.ww file. Referenced as `checked.*` (last path component).
|
|
import math.checked;
|
|
import types;
|
|
|
|
// Vectors ported verbatim from ref/hare/math/checked/checked.ha @test
|
|
// blocks. The subi* boundary cases there feed types::I{8,16,32,64}_{MAX,
|
|
// MIN}; written here as the literal boundary values (the value IS the
|
|
// boundary) — a test vector, not a stand-in for the types def. Restore
|
|
// the types-symbol form once it reads cleanly cross-module (#88).
|
|
|
|
@test fn test_addi8() void = {
|
|
let (res, overflow) = checked.addi8(100, 20);
|
|
if (res != 120) { let _: i32 = 1/0; };
|
|
if (overflow) { let _: i32 = 1/0; };
|
|
let (res2, overflow2) = checked.addi8(100, 50);
|
|
if (res2 != -106) { let _: i32 = 1/0; };
|
|
if (!overflow2) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_addi16() void = {
|
|
let (res, overflow) = checked.addi16(32700, 60);
|
|
if (res != 32760) { let _: i32 = 1/0; };
|
|
if (overflow) { let _: i32 = 1/0; };
|
|
let (res2, overflow2) = checked.addi16(32700, 100);
|
|
if (res2 != -32736) { let _: i32 = 1/0; };
|
|
if (!overflow2) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_addi32() void = {
|
|
let (res, overflow) = checked.addi32(2147483600, 40);
|
|
if (res != 2147483640) { let _: i32 = 1/0; };
|
|
if (overflow) { let _: i32 = 1/0; };
|
|
let (res2, overflow2) = checked.addi32(2147483600, 100);
|
|
if (res2 != -2147483596) { let _: i32 = 1/0; };
|
|
if (!overflow2) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_addi64() void = {
|
|
let (res, overflow) = checked.addi64(9223372036854775800, 5);
|
|
if (res != 9223372036854775805) { let _: i32 = 1/0; };
|
|
if (overflow) { let _: i32 = 1/0; };
|
|
let (res2, overflow2) = checked.addi64(9223372036854775800, 10);
|
|
if (res2 != -9223372036854775806) { let _: i32 = 1/0; };
|
|
if (!overflow2) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_addu8() void = {
|
|
let (res, overflow) = checked.addu8(200u8, 50u8);
|
|
if (res != 250u8) { let _: i32 = 1/0; };
|
|
if (overflow) { let _: i32 = 1/0; };
|
|
let (res2, overflow2) = checked.addu8(200u8, 100u8);
|
|
if (res2 != 44u8) { let _: i32 = 1/0; };
|
|
if (!overflow2) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_addu16() void = {
|
|
let (res, overflow) = checked.addu16(65500u16, 30u16);
|
|
if (res != 65530u16) { let _: i32 = 1/0; };
|
|
if (overflow) { let _: i32 = 1/0; };
|
|
let (res2, overflow2) = checked.addu16(65500u16, 50u16);
|
|
if (res2 != 14u16) { let _: i32 = 1/0; };
|
|
if (!overflow2) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_addu32() void = {
|
|
let (res, overflow) = checked.addu32(4294967200u32, 90u32);
|
|
if (res != 4294967290u32) { let _: i32 = 1/0; };
|
|
if (overflow) { let _: i32 = 1/0; };
|
|
let (res2, overflow2) = checked.addu32(4294967200u32, 100u32);
|
|
if (res2 != 4u32) { let _: i32 = 1/0; };
|
|
if (!overflow2) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_addu64() void = {
|
|
let (res, overflow) = checked.addu64(18446744073709551600u64, 10u64);
|
|
if (res != 18446744073709551610u64) { let _: i32 = 1/0; };
|
|
if (overflow) { let _: i32 = 1/0; };
|
|
let (res2, overflow2) = checked.addu64(18446744073709551610u64, 50u64);
|
|
if (res2 != 44u64) { let _: i32 = 1/0; };
|
|
if (!overflow2) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_subi8() void = {
|
|
let (res, overflow) = checked.subi8(-100, 20);
|
|
if (res != -120) { let _: i32 = 1/0; };
|
|
if (overflow) { let _: i32 = 1/0; };
|
|
let (res2, overflow2) = checked.subi8(-100, 50);
|
|
if (res2 != 106) { let _: i32 = 1/0; };
|
|
if (!overflow2) { let _: i32 = 1/0; };
|
|
// I8_MAX, I8_MIN boundary
|
|
let (res3, overflow3) = checked.subi8(127, -128);
|
|
if (res3 != -1) { let _: i32 = 1/0; };
|
|
if (!overflow3) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_subi16() void = {
|
|
let (res, overflow) = checked.subi16(-32700, 60);
|
|
if (res != -32760) { let _: i32 = 1/0; };
|
|
if (overflow) { let _: i32 = 1/0; };
|
|
let (res2, overflow2) = checked.subi16(-32700, 100);
|
|
if (res2 != 32736) { let _: i32 = 1/0; };
|
|
if (!overflow2) { let _: i32 = 1/0; };
|
|
// I16_MAX, I16_MIN boundary
|
|
let (res3, overflow3) = checked.subi16(32767, -32768);
|
|
if (res3 != -1) { let _: i32 = 1/0; };
|
|
if (!overflow3) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_subi32() void = {
|
|
let (res, overflow) = checked.subi32(-2147483600, 40);
|
|
if (res != -2147483640) { let _: i32 = 1/0; };
|
|
if (overflow) { let _: i32 = 1/0; };
|
|
let (res2, overflow2) = checked.subi32(-2147483600, 100);
|
|
if (res2 != 2147483596) { let _: i32 = 1/0; };
|
|
if (!overflow2) { let _: i32 = 1/0; };
|
|
// I32_MAX, I32_MIN boundary
|
|
let (res3, overflow3) = checked.subi32(2147483647, -2147483648);
|
|
if (res3 != -1) { let _: i32 = 1/0; };
|
|
if (!overflow3) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_subi64() void = {
|
|
let (res, overflow) = checked.subi64(-9223372036854775800, 5);
|
|
if (res != -9223372036854775805) { let _: i32 = 1/0; };
|
|
if (overflow) { let _: i32 = 1/0; };
|
|
let (res2, overflow2) = checked.subi64(-9223372036854775800, 10);
|
|
if (res2 != 9223372036854775806) { let _: i32 = 1/0; };
|
|
if (!overflow2) { let _: i32 = 1/0; };
|
|
// I64_MAX, I64_MIN boundary — Hare's subi64 @test 3rd case
|
|
// subi64(I64_MAX, I64_MIN). Omitted while #89 is open: the I64_MIN
|
|
// literal -9223372036854775808 makes wwstage emit `MOVQ $-, AX`
|
|
// (digits dropped — abs(i64-min) overflow in the wwstage literal
|
|
// formatter) vs cstage's correct value, a rule-10 break. The
|
|
// i8/i16/i32 boundary cases above are unaffected; restore this one
|
|
// once #89 lands.
|
|
};
|
|
|
|
@test fn test_subu8() void = {
|
|
let (res, overflow) = checked.subu8(250u8, 50u8);
|
|
if (res != 200u8) { let _: i32 = 1/0; };
|
|
if (overflow) { let _: i32 = 1/0; };
|
|
let (res2, overflow2) = checked.subu8(44u8, 100u8);
|
|
if (res2 != 200u8) { let _: i32 = 1/0; };
|
|
if (!overflow2) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_subu16() void = {
|
|
let (res, overflow) = checked.subu16(65530u16, 30u16);
|
|
if (res != 65500u16) { let _: i32 = 1/0; };
|
|
if (overflow) { let _: i32 = 1/0; };
|
|
let (res2, overflow2) = checked.subu16(14u16, 50u16);
|
|
if (res2 != 65500u16) { let _: i32 = 1/0; };
|
|
if (!overflow2) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_subu32() void = {
|
|
let (res, overflow) = checked.subu32(4294967290u32, 90u32);
|
|
if (res != 4294967200u32) { let _: i32 = 1/0; };
|
|
if (overflow) { let _: i32 = 1/0; };
|
|
let (res2, overflow2) = checked.subu32(4u32, 100u32);
|
|
if (res2 != 4294967200u32) { let _: i32 = 1/0; };
|
|
if (!overflow2) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_subu64() void = {
|
|
let (res, overflow) = checked.subu64(18446744073709551610u64, 10u64);
|
|
if (res != 18446744073709551600u64) { let _: i32 = 1/0; };
|
|
if (overflow) { let _: i32 = 1/0; };
|
|
let (res2, overflow2) = checked.subu64(44u64, 50u64);
|
|
if (res2 != 18446744073709551610u64) { let _: i32 = 1/0; };
|
|
if (!overflow2) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_muli8() void = {
|
|
let (res, overflow) = checked.muli8(11, 11);
|
|
if (res != 121) { let _: i32 = 1/0; };
|
|
if (overflow) { let _: i32 = 1/0; };
|
|
let (res2, overflow2) = checked.muli8(12, 12);
|
|
if (res2 != -112) { let _: i32 = 1/0; };
|
|
if (!overflow2) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_muli16() void = {
|
|
let (res, overflow) = checked.muli16(181, 181);
|
|
if (res != 32761) { let _: i32 = 1/0; };
|
|
if (overflow) { let _: i32 = 1/0; };
|
|
let (res2, overflow2) = checked.muli16(182, 182);
|
|
if (res2 != -32412) { let _: i32 = 1/0; };
|
|
if (!overflow2) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_muli32() void = {
|
|
let (res, overflow) = checked.muli32(46340, 46340);
|
|
if (res != 2147395600) { let _: i32 = 1/0; };
|
|
if (overflow) { let _: i32 = 1/0; };
|
|
let (res2, overflow2) = checked.muli32(46341, 46341);
|
|
if (res2 != -2147479015) { let _: i32 = 1/0; };
|
|
if (!overflow2) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_mulu8() void = {
|
|
let (res, overflow) = checked.mulu8(15u8, 15u8);
|
|
if (res != 225u8) { let _: i32 = 1/0; };
|
|
if (overflow) { let _: i32 = 1/0; };
|
|
let (res2, overflow2) = checked.mulu8(16u8, 16u8);
|
|
if (res2 != 0u8) { let _: i32 = 1/0; };
|
|
if (!overflow2) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_mulu16() void = {
|
|
let (res, overflow) = checked.mulu16(255u16, 255u16);
|
|
if (res != 65025u16) { let _: i32 = 1/0; };
|
|
if (overflow) { let _: i32 = 1/0; };
|
|
let (res2, overflow2) = checked.mulu16(256u16, 256u16);
|
|
if (res2 != 0u16) { let _: i32 = 1/0; };
|
|
if (!overflow2) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_mulu32() void = {
|
|
let (res, overflow) = checked.mulu32(65535u32, 65535u32);
|
|
if (res != 4294836225u32) { let _: i32 = 1/0; };
|
|
if (overflow) { let _: i32 = 1/0; };
|
|
let (res2, overflow2) = checked.mulu32(65536u32, 65536u32);
|
|
if (res2 != 0u32) { let _: i32 = 1/0; };
|
|
if (!overflow2) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
// Saturating vectors from ref/hare/math/checked/saturating.ha @test
|
|
// blocks. Clamp cases compare to the types limit (as Hare does); the
|
|
// I64_MIN comparisons use types.I64_MIN rather than the literal
|
|
// -9223372036854775808 (which the wwstage formatter miscompiles, #89).
|
|
|
|
@test fn test_sat_addi8() void = {
|
|
if (checked.sat_addi8(100, 20) != 120) { let _: i32 = 1/0; };
|
|
if (checked.sat_addi8(100, 50) != types.I8_MAX) { let _: i32 = 1/0; };
|
|
if (checked.sat_addi8(-100, -50) != types.I8_MIN) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_sat_addi16() void = {
|
|
if (checked.sat_addi16(32700, 60) != 32760) { let _: i32 = 1/0; };
|
|
if (checked.sat_addi16(32700, 100) != types.I16_MAX) { let _: i32 = 1/0; };
|
|
if (checked.sat_addi16(-32700, -100) != types.I16_MIN) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_sat_addi32() void = {
|
|
if (checked.sat_addi32(2147483600, 40) != 2147483640) { let _: i32 = 1/0; };
|
|
if (checked.sat_addi32(2147483600, 100) != types.I32_MAX) { let _: i32 = 1/0; };
|
|
if (checked.sat_addi32(-2147483600, -100) != types.I32_MIN) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_sat_addi64() void = {
|
|
if (checked.sat_addi64(9223372036854775800, 5) != 9223372036854775805) { let _: i32 = 1/0; };
|
|
if (checked.sat_addi64(9223372036854775800, 10) != types.I64_MAX) { let _: i32 = 1/0; };
|
|
if (checked.sat_addi64(-9223372036854775800, -10) != types.I64_MIN) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_sat_addu8() void = {
|
|
if (checked.sat_addu8(200u8, 50u8) != 250u8) { let _: i32 = 1/0; };
|
|
if (checked.sat_addu8(200u8, 100u8) != types.U8_MAX) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_sat_addu16() void = {
|
|
if (checked.sat_addu16(65500u16, 30u16) != 65530u16) { let _: i32 = 1/0; };
|
|
if (checked.sat_addu16(65500u16, 50u16) != types.U16_MAX) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_sat_addu32() void = {
|
|
if (checked.sat_addu32(4294967200u32, 90u32) != 4294967290u32) { let _: i32 = 1/0; };
|
|
if (checked.sat_addu32(4294967200u32, 100u32) != types.U32_MAX) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_sat_addu64() void = {
|
|
if (checked.sat_addu64(18446744073709551600u64, 10u64) != 18446744073709551610u64) { let _: i32 = 1/0; };
|
|
if (checked.sat_addu64(18446744073709551600u64, 50u64) != types.U64_MAX) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_sat_subi8() void = {
|
|
if (checked.sat_subi8(-100, 20) != -120) { let _: i32 = 1/0; };
|
|
if (checked.sat_subi8(-100, 50) != types.I8_MIN) { let _: i32 = 1/0; };
|
|
if (checked.sat_subi8(100, -50) != types.I8_MAX) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_sat_subi16() void = {
|
|
if (checked.sat_subi16(-32700, 60) != -32760) { let _: i32 = 1/0; };
|
|
if (checked.sat_subi16(-32700, 100) != types.I16_MIN) { let _: i32 = 1/0; };
|
|
if (checked.sat_subi16(32700, -100) != types.I16_MAX) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_sat_subi32() void = {
|
|
if (checked.sat_subi32(-2147483600, 40) != -2147483640) { let _: i32 = 1/0; };
|
|
if (checked.sat_subi32(-2147483600, 100) != types.I32_MIN) { let _: i32 = 1/0; };
|
|
if (checked.sat_subi32(2147483600, -100) != types.I32_MAX) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_sat_subi64() void = {
|
|
if (checked.sat_subi64(-9223372036854775800, 5) != -9223372036854775805) { let _: i32 = 1/0; };
|
|
if (checked.sat_subi64(-9223372036854775800, 10) != types.I64_MIN) { let _: i32 = 1/0; };
|
|
if (checked.sat_subi64(9223372036854775800, -10) != types.I64_MAX) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_sat_muli8() void = {
|
|
if (checked.sat_muli8(11, 11) != 121) { let _: i32 = 1/0; };
|
|
if (checked.sat_muli8(12, 12) != types.I8_MAX) { let _: i32 = 1/0; };
|
|
if (checked.sat_muli8(12, -12) != types.I8_MIN) { let _: i32 = 1/0; };
|
|
if (checked.sat_muli8(-12, 12) != types.I8_MIN) { let _: i32 = 1/0; };
|
|
if (checked.sat_muli8(-12, -12) != types.I8_MAX) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_sat_muli16() void = {
|
|
if (checked.sat_muli16(181, 181) != 32761) { let _: i32 = 1/0; };
|
|
if (checked.sat_muli16(182, 182) != types.I16_MAX) { let _: i32 = 1/0; };
|
|
if (checked.sat_muli16(182, -182) != types.I16_MIN) { let _: i32 = 1/0; };
|
|
if (checked.sat_muli16(-182, 182) != types.I16_MIN) { let _: i32 = 1/0; };
|
|
if (checked.sat_muli16(-182, -182) != types.I16_MAX) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_sat_muli32() void = {
|
|
if (checked.sat_muli32(46340, 46340) != 2147395600) { let _: i32 = 1/0; };
|
|
if (checked.sat_muli32(46341, 46341) != types.I32_MAX) { let _: i32 = 1/0; };
|
|
if (checked.sat_muli32(46341, -46341) != types.I32_MIN) { let _: i32 = 1/0; };
|
|
if (checked.sat_muli32(-46341, 46341) != types.I32_MIN) { let _: i32 = 1/0; };
|
|
if (checked.sat_muli32(-46341, -46341) != types.I32_MAX) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_sat_mulu8() void = {
|
|
if (checked.sat_mulu8(15u8, 15u8) != 225u8) { let _: i32 = 1/0; };
|
|
if (checked.sat_mulu8(16u8, 16u8) != types.U8_MAX) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_sat_mulu16() void = {
|
|
if (checked.sat_mulu16(255u16, 255u16) != 65025u16) { let _: i32 = 1/0; };
|
|
if (checked.sat_mulu16(256u16, 256u16) != types.U16_MAX) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
@test fn test_sat_mulu32() void = {
|
|
if (checked.sat_mulu32(65535u32, 65535u32) != 4294836225u32) { let _: i32 = 1/0; };
|
|
if (checked.sat_mulu32(65536u32, 65536u32) != types.U32_MAX) { let _: i32 = 1/0; };
|
|
};
|
|
|
|
export fn main() i32 = {
|
|
test_addi8();
|
|
test_addi16();
|
|
test_addi32();
|
|
test_addi64();
|
|
test_addu8();
|
|
test_addu16();
|
|
test_addu32();
|
|
test_addu64();
|
|
test_subi8();
|
|
test_subi16();
|
|
test_subi32();
|
|
test_subi64();
|
|
test_subu8();
|
|
test_subu16();
|
|
test_subu32();
|
|
test_subu64();
|
|
test_muli8();
|
|
test_muli16();
|
|
test_muli32();
|
|
test_mulu8();
|
|
test_mulu16();
|
|
test_mulu32();
|
|
test_sat_addi8();
|
|
test_sat_addi16();
|
|
test_sat_addi32();
|
|
test_sat_addi64();
|
|
test_sat_addu8();
|
|
test_sat_addu16();
|
|
test_sat_addu32();
|
|
test_sat_addu64();
|
|
test_sat_subi8();
|
|
test_sat_subi16();
|
|
test_sat_subi32();
|
|
test_sat_subi64();
|
|
test_sat_muli8();
|
|
test_sat_muli16();
|
|
test_sat_muli32();
|
|
test_sat_mulu8();
|
|
test_sat_mulu16();
|
|
test_sat_mulu32();
|
|
return 0;
|
|
};
|