diff --git a/test/wcc/952_floats_run.c b/test/wcc/952_floats_run.c index a42b0e6f..0f30eab5 100644 --- a/test/wcc/952_floats_run.c +++ b/test/wcc/952_floats_run.c @@ -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 } };