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.
170 lines
4.3 KiB
Plaintext
170 lines
4.3 KiB
Plaintext
// math/checked (saturating) — clamp-on-overflow arithmetic. Ported from
|
|
// Hare's math::checked saturating siblings
|
|
// (ref/hare/math/checked/saturating.ha). Part of the same `checked`
|
|
// module as checked.ww. Each sat_* clamps to the type's range on
|
|
// overflow instead of wrapping.
|
|
//
|
|
// Three ww adaptations vs Hare, all forced by language differences, none
|
|
// behavioral:
|
|
// - no if-as-expression (N_IF is statement-only, see strings.ww), so
|
|
// Hare's `return if (c) X else Y` becomes an if-statement.
|
|
// - unsigned overflow tests rely on inline wraparound in Hare; ww
|
|
// truncates sub-word arithmetic only on store to a typed lvalue, so
|
|
// the wrap is forced through a typed `res`.
|
|
// - the mul overflow compares need an explicit widening cast (no
|
|
// implicit promotion in ww).
|
|
//
|
|
// Deferred (faithful subset, Hare splits per type): sat_subu8/16/32/64
|
|
// (reference types::U*_MIN, not yet exported by lib/types); sat_*z (no
|
|
// `size` type, #85); sat_muli64/sat_mulu64/sat_muli/sat_mulu (need
|
|
// math::mulu64); int/uint native-width sat_* (ww int is 64-bit — a width
|
|
// divergence in the overflow boundary).
|
|
|
|
package checked;
|
|
|
|
import types;
|
|
|
|
export fn sat_addi8(a: i8, b: i8) i8 = {
|
|
let res: i8 = a + b;
|
|
if (a < 0 == b < 0 && a < 0 != res < 0) {
|
|
if (res < 0) { return types.I8_MAX; };
|
|
return types.I8_MIN;
|
|
};
|
|
return res;
|
|
};
|
|
|
|
export fn sat_addi16(a: i16, b: i16) i16 = {
|
|
let res: i16 = a + b;
|
|
if (a < 0 == b < 0 && a < 0 != res < 0) {
|
|
if (res < 0) { return types.I16_MAX; };
|
|
return types.I16_MIN;
|
|
};
|
|
return res;
|
|
};
|
|
|
|
export fn sat_addi32(a: i32, b: i32) i32 = {
|
|
let res: i32 = a + b;
|
|
if (a < 0 == b < 0 && a < 0 != res < 0) {
|
|
if (res < 0) { return types.I32_MAX; };
|
|
return types.I32_MIN;
|
|
};
|
|
return res;
|
|
};
|
|
|
|
export fn sat_addi64(a: i64, b: i64) i64 = {
|
|
let res: i64 = a + b;
|
|
if (a < 0 == b < 0 && a < 0 != res < 0) {
|
|
if (res < 0) { return types.I64_MAX; };
|
|
return types.I64_MIN;
|
|
};
|
|
return res;
|
|
};
|
|
|
|
export fn sat_addu8(a: u8, b: u8) u8 = {
|
|
let res: u8 = a + b;
|
|
if (res < a) { return types.U8_MAX; };
|
|
return res;
|
|
};
|
|
|
|
export fn sat_addu16(a: u16, b: u16) u16 = {
|
|
let res: u16 = a + b;
|
|
if (res < a) { return types.U16_MAX; };
|
|
return res;
|
|
};
|
|
|
|
export fn sat_addu32(a: u32, b: u32) u32 = {
|
|
let res: u32 = a + b;
|
|
if (res < a) { return types.U32_MAX; };
|
|
return res;
|
|
};
|
|
|
|
export fn sat_addu64(a: u64, b: u64) u64 = {
|
|
let res: u64 = a + b;
|
|
if (res < a) { return types.U64_MAX; };
|
|
return res;
|
|
};
|
|
|
|
export fn sat_subi8(a: i8, b: i8) i8 = {
|
|
let res: i8 = a - b;
|
|
if (a < 0 != b < 0 && a < 0 != res < 0) {
|
|
if (res < 0) { return types.I8_MAX; };
|
|
return types.I8_MIN;
|
|
};
|
|
return res;
|
|
};
|
|
|
|
export fn sat_subi16(a: i16, b: i16) i16 = {
|
|
let res: i16 = a - b;
|
|
if (a < 0 != b < 0 && a < 0 != res < 0) {
|
|
if (res < 0) { return types.I16_MAX; };
|
|
return types.I16_MIN;
|
|
};
|
|
return res;
|
|
};
|
|
|
|
export fn sat_subi32(a: i32, b: i32) i32 = {
|
|
let res: i32 = a - b;
|
|
if (a < 0 != b < 0 && a < 0 != res < 0) {
|
|
if (res < 0) { return types.I32_MAX; };
|
|
return types.I32_MIN;
|
|
};
|
|
return res;
|
|
};
|
|
|
|
export fn sat_subi64(a: i64, b: i64) i64 = {
|
|
let res: i64 = a - b;
|
|
if (a < 0 != b < 0 && a < 0 != res < 0) {
|
|
if (res < 0) { return types.I64_MAX; };
|
|
return types.I64_MIN;
|
|
};
|
|
return res;
|
|
};
|
|
|
|
export fn sat_muli8(a: i8, b: i8) i8 = {
|
|
let fullres: int = a: int * b: int;
|
|
let res: i8 = fullres: i8;
|
|
if (res: int != fullres) {
|
|
if (res < 0) { return types.I8_MAX; };
|
|
return types.I8_MIN;
|
|
};
|
|
return res;
|
|
};
|
|
|
|
export fn sat_muli16(a: i16, b: i16) i16 = {
|
|
let fullres: int = a: int * b: int;
|
|
let res: i16 = fullres: i16;
|
|
if (res: int != fullres) {
|
|
if (res < 0) { return types.I16_MAX; };
|
|
return types.I16_MIN;
|
|
};
|
|
return res;
|
|
};
|
|
|
|
export fn sat_muli32(a: i32, b: i32) i32 = {
|
|
let fullres: i64 = a: i64 * b: i64;
|
|
let res: i32 = fullres: i32;
|
|
if (res: i64 != fullres) {
|
|
if (res < 0) { return types.I32_MAX; };
|
|
return types.I32_MIN;
|
|
};
|
|
return res;
|
|
};
|
|
|
|
export fn sat_mulu8(a: u8, b: u8) u8 = {
|
|
let res: uint = a: uint * b: uint;
|
|
if (res > (types.U8_MAX): uint) { return types.U8_MAX; };
|
|
return res: u8;
|
|
};
|
|
|
|
export fn sat_mulu16(a: u16, b: u16) u16 = {
|
|
let res: uint = a: uint * b: uint;
|
|
if (res > (types.U16_MAX): uint) { return types.U16_MAX; };
|
|
return res: u16;
|
|
};
|
|
|
|
export fn sat_mulu32(a: u32, b: u32) u32 = {
|
|
let res: u64 = a: u64 * b: u64;
|
|
if (res > (types.U32_MAX): u64) { return types.U32_MAX; };
|
|
return res: u32;
|
|
};
|