test: 952 add issubnormalf64 + normalizef64 runtime rows

Four cstage build+run rows for the fold-2a decompose step: issubnormalf64
across normals/zero/a constructed subnormal (f64frombits(1u64)), and
normalizef64 on both the normal path (n, 0) and the subnormal multiply
path (exp -52, result no longer subnormal). normalizef64's (f64, i64)
return rides the #102 16B-tuple-from-call receive; tuple-field f64s are
spilled to a let before any literal comparison to dodge the same
gate-blind XMM mis-load that holds back frexpf64 (see 952 header +
lib/math/floats.ww). Byte-id of a normalize-calling program is covered by
954's tuple-receive gate.
This commit is contained in:
2026-05-25 15:06:25 +09:00
parent 263257f2c1
commit b4752aad21

View File

@@ -1,12 +1,22 @@
/*
* 952_floats_run — runtime regression net for lib/math/floats fold-1
* (f64bits, f64frombits, isnan, isinf, signf64, absf64, copysignf64,
* ispositivef64, isnegativef64). The classify/sign/bits surface rides
* ispositivef64, isnegativef64) plus the fold-2a decompose step
* (issubnormalf64, normalizef64). The classify/sign/bits surface rides
* the f64-codegen paths fixed by #96 (deref-load -> MOVSD/X0) and #97
* (compare consults PF for NaN); both stages emit byte-identical asm, so
* the 990-997 byte-id gates can't catch a reintroduction — only an
* (compare consults PF for NaN); normalizef64 additionally rides the #102
* 16B-tuple-from-call receive (its (f64, i64) return) and the f64 multiply
* on the subnormal path. Both stages emit byte-identical asm, so the
* 990-997 byte-id gates can't catch a reintroduction — only an
* executed-and-checked runtime probe can. This file is that probe.
*
* frexpf64 is intentionally absent: its Hare-exact `n == 0f64` guard
* miscompiles (a no-decimal f64 literal in a comparison never reaches XMM;
* gate-blind, both stages identical). See the hold-back note in
* lib/math/floats.ww. The normalizef64 rows below spill tuple fields to a
* let before any f64-literal comparison, since a tuple-field f64 compared
* directly against a literal (`r.0 == 0.0`) hits the same mis-load.
*
* Table-driven like 951_f64cgen_run: each row is a self-contained ww
* program importing math; the C-side cstage `ww build -I lib` compiles
* it, we run the binary and assert the exit code. Self-checking rows
@@ -113,6 +123,55 @@ static const struct row rows[] = {
"export fn main() i32 = {\n"
" return math.absf64(-7.0): i32;\n"
"};\n", 7 },
/* issubnormalf64: normals and zero are not subnormal; the smallest
* positive f64 (bits == 1, ~5e-324) is. Built via f64frombits so the
* checker can't fold a subnormal literal (which it would flush). */
{ "package main;\n"
"import math;\n"
"export fn main() i32 = {\n"
" if (math.issubnormalf64(16.0)) { return 1; };\n"
" if (math.issubnormalf64(0.0)) { return 2; };\n"
" if (math.issubnormalf64(1.0)) { return 3; };\n"
" let s: f64 = math.f64frombits(1u64);\n"
" if (!math.issubnormalf64(s)) { return 4; };\n"
" return 0;\n"
"};\n", 0 },
/* normalizef64 on a normal value returns (n, 0). The (f64, i64) tuple
* rides the #102 16B-tuple-from-call receive; r.0 is spilled to a let
* before comparison (see header). */
{ "package main;\n"
"import math;\n"
"export fn main() i32 = {\n"
" const r = math.normalizef64(16.0);\n"
" const m: f64 = r.0;\n"
" if (m != 16.0) { return 1; };\n"
" if (r.1 != 0i64) { return 2; };\n"
" return 0;\n"
"};\n", 0 },
/* normalizef64 on a subnormal multiplies by 2^52 (f64 multiply inside a
* tuple return) and returns exp -52; the result is no longer subnormal
* and is nonzero. */
{ "package main;\n"
"import math;\n"
"export fn main() i32 = {\n"
" let s: f64 = math.f64frombits(1u64);\n"
" const r = math.normalizef64(s);\n"
" if (r.1 != -52i64) { return 1; };\n"
" const m: f64 = r.0;\n"
" if (math.issubnormalf64(m)) { return 2; };\n"
" if (m == 0.0) { return 3; };\n"
" return 0;\n"
"};\n", 0 },
/* normalizef64 value propagation: normal-path .0 truncated to i32
* (spilled to a let to dodge the tuple-field-compare mis-load; the
* cast itself reads the field fine). */
{ "package main;\n"
"import math;\n"
"export fn main() i32 = {\n"
" const r = math.normalizef64(42.0);\n"
" const m: f64 = r.0;\n"
" return m: i32;\n"
"};\n", 42 },
{ NULL, 0 }
};