test: migrate Fam11 float value tests to @test, keep ABI-conformance pins (#5-C4)

fold-2 chunk C4 (drew's Fam8-13 plan): 13 float value-row C drivers re-homed.
11 migrate to test/lang/*_test.ww @test row-tables (exact IEEE-bit asserts);
1 float-overflow reject row -> a runww //ww:error carrier. 956_tuprecv_f64
slims to a w6c_ww asserttyped pin (20 value rows -> @test; the stamp dimension
can't be a value/byte-id @test) -- mutation-proven non-vacuous (break #121
stamp -> RED 6/6 -> restore -> GREEN) + an in-test vacuity self-check.
946_structparam/structret stay whole: their SSE register-class .s-grep (SysV
ABI conformance, #165/#171a) is the genuine defect-guard, not @test-expressible.
Float was the predicted SSE-cursor byte-id hotspot -- zero fresh cs!=ww
surfaced; 951_f64cgen (cstage-only before) byte-ids clean. LANGBYTEID floor
82->93; test count 384->374 (10 deleted drivers; 956 + the 2 946 kept).
This commit is contained in:
2026-06-24 03:25:47 +09:00
parent 3ee1906497
commit 60dec4a6bf
24 changed files with 883 additions and 2711 deletions

View File

@@ -1,225 +0,0 @@
/*
* 907_f32arg_run — runtime + byte-id regression net for #143: passing an
* f32 as a function argument must spill through the stack at the f32 class
* width (MOVSS, 4-byte), not MOVSD (8-byte). cstage lowered the arg-push
* (cgen.c N_CALL push) and arg-pop (the pop-into-XMM loop) with a hardcoded
* MOVSD; wwstage already split f32/f64 via exprfloatkind (MOVSS for f32).
* The result was a cs!=ww byte-id break on EVERY f32 argument — a pure
* divergence, not a runtime miscompile (the callee reads its f32 param as
* the low 32 bits via MOVSS regardless of how the caller spilled it), but
* it blocked the cs==ww gate and therefore fold-5b (strconv f32tos, whose
* core calls math.f32bits(n) — an f32 arg).
*
* ABI-correct form is MOVSS for single precision per SysV: ref/qbe
* amd64/emit.c:524 — the slot->slot copy-through-XMM path (exactly ww's
* X0 -> 8B stack slot -> XMMn arg-spill shape) narrows the class to Ks for
* non-wide floats and emits movss (clstoa[Ks] == "ss"); movsd is the
* double (Kd) form. The fix aligns cstage UP to MOVSS via the existing
* op_for(node_isf32) helper at both the push and pop sites. wwstage is
* unchanged. This is the f32-arg-push half of the float-register family
* (#119 f32-arr-load, #122 f32-arr-store, #125 float-arr-index, #157
* tagged-float-return) — "f32 must route through the right XMM width."
*
* Each row carries BOTH dimensions (like 955 / 964 / 965):
* (a) cstage `ww build` + run, asserting the exit code (end-to-end ABI:
* the callee must read the correct f32 value out of the XMM reg).
* (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge (rule-10).
*
* The 990-997 byte-id gates are BLIND to a reintroduction here only if the
* gated selfhost tools never pass an f32 arg (they don't — bootstrap-
* NEUTRAL), so this executed-and-byte-id-checked probe is the live net.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
struct row { const char *label; const char *src; int want_exit; };
static const struct row rows[] = {
/* single f32 var arg — the minimal repro. takef32(2.5) -> 2.5,
* truncated to i32 == 2. On the bug the byte-id breaks (MOVSD push)
* but the value still rounds (MOVSD carries the low 4 bytes too). */
{ "single_var",
"package main;\n"
"fn takef32(x: f32) f32 = { return x; };\n"
"export fn main() i32 = {\n"
" let a: f32 = 2.5f32;\n"
" let r: f32 = takef32(a);\n"
" return r: i32;\n"
"};\n", 2 },
/* f32 LITERAL directly as the arg (ken's original getj(1.0f32)
* shape that surfaced #143). id(3.0f32) -> 3.0 -> 3. */
{ "literal_arg",
"package main;\n"
"fn id(x: f32) f32 = { return x; };\n"
"export fn main() i32 = {\n"
" let r: f32 = id(3.0f32);\n"
" return r: i32;\n"
"};\n", 3 },
/* multiple f32 args — each pushed/popped independently; all must
* use MOVSS on both stages. 1.5 + 2.5 + 4.0 == 8.0 (exact in f32). */
{ "multi_f32",
"package main;\n"
"fn add3(a: f32, b: f32, c: f32) f32 = { return a + b + c; };\n"
"export fn main() i32 = {\n"
" let x: f32 = 1.5f32;\n"
" let y: f32 = 2.5f32;\n"
" let z: f32 = 4.0f32;\n"
" let r: f32 = add3(x, y, z);\n"
" return r: i32;\n"
"};\n", 8 },
/* f32 + f64 MIX — the f64 arg must stay MOVSD (8-byte), the f32 args
* MOVSS (4-byte), and BOTH stages must agree per-arg. 1.5 + 10.0 +
* 0.5 == 12.0 (exact). Guards against the fix over-reaching to f64. */
{ "mixed_f32_f64",
"package main;\n"
"fn mix(a: f32, b: f64, c: f32) f64 = "
"{ return a: f64 + b + c: f64; };\n"
"export fn main() i32 = {\n"
" let p: f32 = 1.5f32;\n"
" let q: f64 = 10.0;\n"
" let s: f32 = 0.5f32;\n"
" let r: f64 = mix(p, q, s);\n"
" return r: i32;\n"
"};\n", 12 },
/* STACK-passed f32: 10 float args — 8 ride X0..X7, args 9+10 stay on
* the stack (fpidx >= 8). The push-spill still diverged on the bug;
* this exercises the fpidx>=8 leave-on-stack arm. 1.0 * 10 == 10.0. */
{ "stack_passed",
"package main;\n"
"fn ten(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32, "
"g: f32, h: f32, i: f32, j: f32) f32 = {\n"
" return a + b + c + d + e + f + g + h + i + j;\n"
"};\n"
"export fn main() i32 = {\n"
" let v: f32 = 1.0f32;\n"
" let r: f32 = ten(v, v, v, v, v, v, v, v, v, v);\n"
" return r: i32;\n"
"};\n", 10 },
{ NULL, NULL, 0 }
};
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa);
int cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char w6c[1100], w6c_ww[1100];
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
if (access(w6c_ww, X_OK) != 0) {
fprintf(stderr, "f32arg: w6c_ww missing — cannot run the "
"cs==ww byte-id gate (the whole point of this test)\n");
return 1;
}
int n = 0, fail = 0;
for (int i = 0; rows[i].src; i++, n++) {
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwf32a_%d_d_%d",
getpid(), i);
mkdir(tmpdir, 0755);
char src[128], outbin[128], rmcmd[160];
snprintf(src, sizeof src, "%s/wwf32a_%d_%d.ww",
tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wwf32a_%d_%d",
tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (f == NULL) { fail++; runwait(rmcmd); continue; }
fputs(rows[i].src, f);
fclose(f);
/* (a) cstage build + run. */
char cmd[2048];
snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s",
bin, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: cstage build failed\n",
rows[i].label);
fail++;
runwait(rmcmd);
continue;
}
int got = runwait(outbin);
if (got != rows[i].want_exit) {
fprintf(stderr, "row[%s]: cstage exit %d, want %d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
/* (b) cs==ww byte-id gate: emit .s from both stages, cmp. */
char cs_s[128], ws_s[128];
snprintf(cs_s, sizeof cs_s, "%s/wwf32a_%d_%d_cs.s",
tmpdir, getpid(), i);
snprintf(ws_s, sizeof ws_s, "%s/wwf32a_%d_%d_ww.s",
tmpdir, getpid(), i);
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c, cs_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label);
fail++; runwait(rmcmd); continue;
}
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c_ww, ws_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c_ww failed\n",
rows[i].label);
fail++; runwait(rmcmd); continue;
}
if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr,
"row[%s]: cstage/wwstage .s DIFFER (rule-10 "
"byte-id violation)\n", rows[i].label);
fail++;
}
runwait(rmcmd);
}
if (fail) {
fprintf(stderr, "%d/%d f32-arg-push tests failed\n", fail, n);
return 1;
}
printf("f32arg: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n);
return 0;
}

View File

@@ -1,235 +0,0 @@
/*
* 916_arr_float_call_index_run — runtime + byte-id net for #125: in
* the `arr[i] = v` ASSIGN path, when the element type is float and the
* INDEX sub-expression clobbers X0 (e.g. a fn-call index), the value
* was LOST pre-fix because both stages PUSHed AX (junk for floats) +
* never spilled X0 across the idx/base eval, then re-emitted MOVSS/
* MOVSD X0, (BX) using the already-clobbered X0.
*
* The bug was load-bearing only after #122 routed the value through
* X0 (pre-#122 it sat in AX and the existing PUSH AX accidentally
* protected it). Broken byte-identically between cstage and wwstage,
* so pre-existing + non-regression but exposed by #122.
*
* Fix mirrors the *p = v float deref-store at cmd/w6c/cgen.c:4187
* (the only other arr[i]= /-style float store): for float elements,
* substitute the PUSHQ AX / POPQ AX pair around the idx/base eval
* with SUBQ $8,SP + MOVSS/MOVSD X0,(SP) ... MOVSS/MOVSD (SP),X0 +
* ADDQ $8,SP. Non-float keeps PUSHQ AX (the original) so the str/
* slice 3-word pop order at the end of the branch is undisturbed.
*
* The fix changes asm shape for ALL float arr[i]= stores (its own
* scoped fold, byte-identical between stages); covered by the 990-
* 997 byte-id gates + this test's standalone w6c vs w6c_ww cmp.
*
* Rows cover:
* - f64_call_index: a[geti(1.0)] = 1.5f64 — the canonical repro,
* geti is a fn that adds 1 and returns i32 (its body clobbers X0).
* Pre-fix exit=2 (a[2] held geti's last X0=2.0 → cast i32=2);
* post-fix exit=1 (a[2] = 1.5 → cast i32 = 1).
* - f32_call_index: same shape, f32 element — pre-fix even worse
* because CVTSD2SS at #104 only touched X0; AX store would be
* garbage. Post-fix exit=1 same as f64.
* - f64_lit_index: a[2] = 1.5f64 with a LITERAL index — pre-fix
* this already worked (cgexpr of an int literal doesn't clobber
* X0). Regression guard.
* - f64_localvar_index: a[k] = 1.5f64 with k = 2 (local var) —
* pre-fix also worked (MOVQ off(BP), AX doesn't clobber X0).
* Regression guard.
* - f64_arith_index: a[k + 1] = 1.5f64 — integer arith on locals
* doesn't clobber X0 either; pre-fix worked. Regression guard.
*
* Each row carries (a) cstage `ww build` + run asserting exit code
* and (b) w6c vs w6c_ww `.s` cmp (rule-10 byte-id).
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
struct row { const char *label; const char *src; int want_exit; };
static const struct row rows[] = {
{ "f64_call_index",
"package main;\n"
"export fn geti(x: f64) i32 = {\n"
" let y: f64 = x + 1.0;\n"
" return (y: i32);\n"
"};\n"
"export fn main() i32 = {\n"
" let a: [4]f64 = [99.0, 99.0, 99.0, 99.0];\n"
" a[geti(1.0)] = 1.5f64;\n"
" let v: i32 = (a[2]: i32);\n"
" return v;\n"
"};\n", 1 },
/* Body of getj uses f64 arith → clobbers X0 via the materialise-
* literal-in-X0 sequence and the ADDSD. The call's argument is
* int (not float), so the call-arg-push goes through the integer
* convention and avoids a pre-existing cs/ww f32-arg-push MOVSD-
* vs-MOVSS divergence in pre-call arg materialisation (sibling
* bug, filed separately). Pre-fix the post-call X0 (set to the
* residue of 1.0+1.0 = 2.0) would overwrite the 1.5f32 in X0,
* yielding garbage. */
{ "f32_call_index",
"package main;\n"
"export fn getj(seed: i32) i32 = {\n"
" let y: f64 = (seed: f64) + 1.0;\n"
" return (y: i32);\n"
"};\n"
"export fn main() i32 = {\n"
" let a: [4]f32 = [99.0f32, 99.0f32, 99.0f32, 99.0f32];\n"
" a[getj(1)] = 1.5f32;\n"
" let v: i32 = (a[2]: i32);\n"
" return v;\n"
"};\n", 1 },
{ "f64_lit_index",
"package main;\n"
"export fn main() i32 = {\n"
" let a: [4]f64 = [99.0, 99.0, 99.0, 99.0];\n"
" a[2] = 1.5f64;\n"
" let v: i32 = (a[2]: i32);\n"
" return v;\n"
"};\n", 1 },
{ "f64_localvar_index",
"package main;\n"
"export fn main() i32 = {\n"
" let a: [4]f64 = [99.0, 99.0, 99.0, 99.0];\n"
" let k: i32 = 2;\n"
" a[k] = 1.5f64;\n"
" let v: i32 = (a[2]: i32);\n"
" return v;\n"
"};\n", 1 },
{ "f64_arith_index",
"package main;\n"
"export fn main() i32 = {\n"
" let a: [4]f64 = [99.0, 99.0, 99.0, 99.0];\n"
" let k: i32 = 1;\n"
" a[k + 1] = 1.5f64;\n"
" let v: i32 = (a[2]: i32);\n"
" return v;\n"
"};\n", 1 },
{ NULL, NULL, 0 }
};
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa);
int cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char w6c[1100], w6c_ww[1100];
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
if (access(w6c_ww, X_OK) != 0) {
fprintf(stderr, "arrfcidx: w6c_ww missing — cannot run "
"the cs==ww byte-id gate (the whole point of this test)\n");
return 1;
}
int n = 0, fail = 0;
for (int i = 0; rows[i].src; i++, n++) {
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwafc_%d_d_%d",
getpid(), i);
mkdir(tmpdir, 0755);
char rmcmd[160];
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
char src[128], outbin[128], cs_s[128], ws_s[128];
snprintf(src, sizeof src, "%s/wwafc_%d_%d.ww",
tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wwafc_%d_%d",
tmpdir, getpid(), i);
snprintf(cs_s, sizeof cs_s, "%s/wwafc_%d_%d_cs.s",
tmpdir, getpid(), i);
snprintf(ws_s, sizeof ws_s, "%s/wwafc_%d_%d_ww.s",
tmpdir, getpid(), i);
FILE *f = fopen(src, "wb");
if (f == NULL) { runwait(rmcmd); fail++; continue; }
fputs(rows[i].src, f);
fclose(f);
char cmd[2048];
snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s",
bin, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: cstage build failed\n",
rows[i].label);
fail++;
runwait(rmcmd);
continue;
}
int got = runwait(outbin);
if (got != rows[i].want_exit) {
fprintf(stderr, "row[%s]: cstage exit %d, want %d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c, cs_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label);
fail++; runwait(rmcmd); continue;
}
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c_ww, ws_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c_ww failed\n",
rows[i].label);
fail++; runwait(rmcmd); continue;
}
if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr,
"row[%s]: cstage/wwstage .s DIFFER (rule-10 "
"byte-id violation)\n", rows[i].label);
fail++;
}
runwait(rmcmd);
}
if (fail) {
fprintf(stderr, "%d/%d arr-float-call-idx tests failed\n", fail, n);
return 1;
}
printf("arrfcidx: %d/%d ok (cstage run + cs==ww byte-id)\n",
n, n);
return 0;
}

View File

@@ -1,233 +0,0 @@
/*
* 946_floatarr_run — runtime + byte-id net for #119: a float ARRAY
* ELEMENT load must land in X0 (MOVSS/MOVSD), not the integer register
* file (MOVQ → AX). cgindex's element-load sites ended in the integer
* loadopsz, so `let a: [3]f64 = [...]; a[0] + a[1]` loaded the element
* into AX while the consumer's ADDSD read a stale X0 → wrong sum. #119
* adds a float-element branch (MOVSS f32 / MOVSD f64 into X0) at all
* three wwstage cgindex sites (global, baselocal, fallback) and both
* cstage N_INDEX element-load sites, deriving float-ness from the same
* stamped element tinfo the esz already reads (elemisfloatc/elemisf32c
* for ident bases, typeisfloat/typeisf32(n.type_) for N_DOT/N_INDEX
* bases — never an unstamped node-stamp, dodging the #121 trap).
*
* #119 also required a wwstage exprfloatkind N_INDEX arm: the consumer
* (cgbin / cgcast) classified an indexed float operand as INTEGER and
* fell to PUSHQ/ADDQ/MOVSXD, while the cstage read the stamped operand
* type and used ADDSD/CVTTSD2SI — a rule-10 divergence the load fix
* exposed. The N_INDEX result type_ is checker-stamped (cgindex reads
* it for esz), so this is not the unstamped-N_MLET case deferred under
* #121.
*
* Each row carries (a) a cstage `ww build` + run asserting the exit
* code, and (b) a w6c vs w6c_ww `.s` cmp (rule-10 byte-id). A row whose
* want_exit is RUN_SKIP runs only the byte-id leg.
*
* #122 fixes the store-side twin the #119 commit deferred: the f32
* array-element STORE wrote AX (the raw double low-bits) instead of the
* CVTSD2SS-narrowed X0 single, so every f32 array slot read back garbage.
* Both the array-literal-init store (cgen.c:6889 / cgenstmt:949) and the
* arr[i]= index store (cgen.c:3818 / cgenexpr:4209) now route FROM X0 via
* MOVSS/MOVSD, mirroring the scalar float store. The f32 rows below now
* assert the runtime VALUE (not byte-id only) and add an arr[i]= store
* plus a [v...] repeat-fill init (a distinct cgen arm #122 also fixed).
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#define RUN_SKIP (-1)
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
struct row { const char *label; const char *src; int want_exit; };
static const struct row rows[] = {
/* f64 element load + float add: 1.5 + 2.5 == 4.0. On the bug the
* elements loaded into AX and the ADDSD read a stale X0 -> != 4.0. */
{ "f64_arith",
"package main;\n"
"export fn main() i32 = {\n"
" let a: [3]f64 = [1.5, 2.5, 9.0];\n"
" if (a[0] + a[1] != 4.0) { return 1; };\n"
" return 0;\n"
"};\n", 0 },
/* f64 element -> i32 cast: 1.5 truncates to 1. On the bug the cast
* operand classified integer (MOVSXD on AX) not CVTTSD2SI on X0. */
{ "f64_trunc",
"package main;\n"
"export fn main() i32 = {\n"
" let a: [3]f64 = [1.5, 2.5, 9.0];\n"
" return a[0]: i32;\n"
"};\n", 1 },
/* third element, non-adjacent index: a[2] == 9.0. */
{ "f64_elem2",
"package main;\n"
"export fn main() i32 = {\n"
" let a: [3]f64 = [1.5, 2.5, 9.0];\n"
" if (a[2] != 9.0) { return 1; };\n"
" return 0;\n"
"};\n", 0 },
/* f32 array-literal-init store + element load + add: suffixed
* literals so fold-1 narrows them. Pre-#122 the init store wrote
* MOVL AX (raw double low-bits) so the slots read garbage; #122
* routes the store from X0 via MOVSS, so 1.5 + 2.5 == 4.0. */
{ "f32_arith",
"package main;\n"
"export fn main() i32 = {\n"
" let b: [2]f32 = [1.5f32, 2.5f32];\n"
" if ((b[0] + b[1]): f64 != 4.0) { return 1; };\n"
" return 0;\n"
"};\n", 0 },
/* f32 arr[i]= index store (#122): assign each slot, read back.
* The index-store path popped the value to AX and wrote MOVL (raw
* double low-bits, garbage for f32); #122 stores from X0 via MOVSS.
* The [0.0f32,0.0f32] init also exercises the array-lit store. */
{ "f32_index_store",
"package main;\n"
"export fn main() i32 = {\n"
" let b: [2]f32 = [0.0f32, 0.0f32];\n"
" b[0] = 1.5f32;\n"
" b[1] = 2.5f32;\n"
" if ((b[0] + b[1]): f64 != 4.0) { return 1; };\n"
" return 0;\n"
"};\n", 0 },
/* f32 [v...] repeat-fill init store (#122): the repeat marker
* fills every slot from the last element's X0 single; pre-#122 the
* fill wrote MOVL AX (raw double low-bits) per slot so each read
* back garbage. Distinct cgen arm from the per-element list store.
* 1.5 * 3 == 4.5 (exact in IEEE). */
{ "f32_repeat_fill",
"package main;\n"
"export fn main() i32 = {\n"
" let c: [3]f32 = [1.5f32...];\n"
" if ((c[0] + c[1] + c[2]): f64 != 4.5) { return 1; };\n"
" return 0;\n"
"};\n", 0 },
{ NULL, NULL, 0 }
};
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa);
int cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char w6c[1100], w6c_ww[1100];
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
if (access(w6c_ww, X_OK) != 0) {
fprintf(stderr, "floatarr: w6c_ww missing — cannot run the "
"cs==ww byte-id gate (the whole point of this test)\n");
return 1;
}
int n = 0, fail = 0;
for (int i = 0; rows[i].src; i++, n++) {
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwfarr_%d_d_%d",
getpid(), i);
mkdir(tmpdir, 0755);
char src[128], outbin[128], cs_s[128], ws_s[128], rmcmd[160];
snprintf(src, sizeof src, "%s/wwfarr_%d_%d.ww",
tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wwfarr_%d_%d",
tmpdir, getpid(), i);
snprintf(cs_s, sizeof cs_s, "%s/wwfarr_%d_%d_cs.s",
tmpdir, getpid(), i);
snprintf(ws_s, sizeof ws_s, "%s/wwfarr_%d_%d_ww.s",
tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (f == NULL) { fail++; runwait(rmcmd); continue; }
fputs(rows[i].src, f);
fclose(f);
/* (a) cstage build + run (skipped for byte-id-only rows). */
if (rows[i].want_exit != RUN_SKIP) {
char cmd[2048];
snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s",
bin, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: cstage build failed\n",
rows[i].label);
fail++;
runwait(rmcmd);
continue;
}
int got = runwait(outbin);
if (got != rows[i].want_exit) {
fprintf(stderr, "row[%s]: cstage exit %d, want %d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
}
/* (b) cs==ww byte-id gate: emit .s from both stages, cmp. */
char cmd[2048];
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c, cs_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label);
fail++; runwait(rmcmd); continue;
}
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c_ww, ws_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c_ww failed\n",
rows[i].label);
fail++; runwait(rmcmd); continue;
}
if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr,
"row[%s]: cstage/wwstage .s DIFFER (rule-10 "
"byte-id violation)\n", rows[i].label);
fail++;
}
runwait(rmcmd);
}
if (fail) {
fprintf(stderr, "%d/%d float-array element tests failed\n",
fail, n);
return 1;
}
printf("floatarr: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n);
return 0;
}

View File

@@ -1,242 +0,0 @@
/*
* 949_f9_float_run — F9 argument widen/drain ABI (float) cluster.
*
* The cgcall push/drain handling of a CONCRETE arg widened into a
* tagged-union parameter slot, where a float is involved:
*
* #30 + #48 (align-UP, wwstage-only): the cgcall DRAIN loop had no
* widen-first pop branch. A concrete arg widened into a tagged param
* was pushed as slotsize/8 GP words (tag + payload), but the drain
* classified per the ARG's source type:
* #30 — a float arg AFTER a widened (i64|void) arg: the widened
* box under-drained by one GP word, so `MOVSD (SP),X0` for
* the following f64 read the box's leftover payload word.
* #48 — the widened arg IS an f64 source: source-type=f64 hit the
* float arm, so `MOVSD (SP),X0` ate the TAG word into X0 and
* the payload landed in DI as the tag → callee fell through.
* Both are one missing branch — a widen-first GP pop placed BEFORE
* the float check, draining slotsize/8 words into the integer arg
* cursor (DI=tag, SI=payload). cstage already does this (precomputed
* widen[i]); this aligns wwstage UP and the rows pin cs==ww byte-id.
*
* REGISTER-CURSOR INVARIANT (rob): a lucky-but-wrong float shuffle can pass
* exit codes, so the run rows use values that only survive if the tag/
* payload land in the right GP regs AND the float in the right XMM:
* #30 returns 0 iff f's b (the f64 arg following the widen) == 1.5;
* #48 returns 2 iff the (i64|f64) box's tag selects the f64 arm (DI=tag).
* Both also assert cstage/wwstage .s byte-identical.
*
* NNN<950, self-contained (/tmp, no imports; #30 returns i32 rather than
* importing os, so the asm byte-id row can run w6c/w6c_ww directly).
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa), cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
#define K_RUN 0
#define K_BUILDERR 1
struct row { const char *label; const char *src; int want;
int kind; const char *experr; };
static const struct row rows[] = {
/* #30: a widened (i64|void) arg FOLLOWED by an f64 arg. Pre-fix the
* f64 drain (MOVSD (SP),X0) read the widened box's leftover payload
* word (int 7) instead of 1.5, so r != 1.5 → 1. */
{ "widen_then_float",
"package main;\n"
"fn f(a: (i64 | void), b: f64) f64 = { return b; };\n"
"export fn main() i32 = {\n"
" let r: f64 = f(7, 1.5);\n"
" if (r == 1.5) { return 0; };\n"
" return 1;\n"
"};\n", 0, K_RUN, NULL },
/* #48: the widened arg IS an f64 source (i64|f64). Pre-fix the float
* arm ate the tag word into X0 and the payload landed in DI as the
* tag, so the match fell through both arms → 9. Correct = 2 (f64). */
{ "float_source_widen",
"package main;\n"
"fn g(v: (i64 | f64)) i32 = {\n"
" match (v) {\n"
" case let n: i64 =>\n"
" return 1;\n"
" case let d: f64 =>\n"
" return 2;\n"
" };\n"
" return 9;\n"
"};\n"
"export fn main() i32 = {\n"
" let d: f64 = 3.5;\n"
" return g(d);\n"
"};\n", 2, K_RUN, NULL },
/* #49 (#263 both-stages): a RUNTIME f64 producer (mk) feeds a value
* widened into (f64|void), and the f64 arm READS the payload
* (d == 2.5). Pre-fix the widen-PUSH did `PUSHQ AX` for the payload
* while the f64 sat in X0, so d read stale bits → 1 (cs) / 3 (ww,
* the #48 pop divergence compounding). #48's arm dodged this by not
* reading the payload; this one catches the stale-AX push. Both
* stages now spill X0 → 0. (After the c4 drain fix the two stages
* already agree on the pop; this push fix closes the shared bug.) */
{ "runtime_float_widen_payload",
"package main;\n"
"type fv = (f64 | void);\n"
"fn mk(x: f64) f64 = { return x + 1.5; };\n"
"fn take(v: fv) i32 = {\n"
" match (v) {\n"
" case let d: f64 => {\n"
" if (d == 2.5) { return 0; };\n"
" return 1;\n"
" };\n"
" case void => { return 2; };\n"
" };\n"
" return 3;\n"
"};\n"
"export fn main() i32 = {\n"
" let d: f64 = mk(1.0);\n"
" return take(d);\n"
"};\n", 0, K_RUN, NULL },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[96], src[128], outbin[128], errf[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/f9f_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/f9f_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/f9f_%d_%d", tmpdir, getpid(), i);
snprintf(errf, sizeof errf, "%s/err", tmpdir);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
driver, outbin, src, errf);
int brc = runwait(cmd);
if (brc != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
if (got != r->want) {
fprintf(stderr, "row[%s]: %s exit %d, want %d\n",
r->label, driver, got, r->want);
return 1;
}
return 0;
}
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[96], cs[96], ws[96], cmd[1024];
snprintf(src, sizeof src, "/tmp/f9f_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/f9f_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/f9f_asm_%d_%d_w.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
unlink(src);
return -1;
}
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
bin, ws, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
unlink(src); unlink(cs);
return -1;
}
int rc = slurp_eq(cs, ws);
if (rc != 0)
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
r->label);
unlink(src); unlink(cs); unlink(ws);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[2080];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[2120], wdrv[2120];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
int n = (int)(sizeof rows / sizeof rows[0]);
int total = 0, fail = 0;
for (int i = 0; i < n; i++) {
total++;
if (run_driver(cdrv, &rows[i], i) != 0) fail++;
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (run_driver(wdrv, &rows[i], i) != 0) fail++;
}
for (int i = 0; i < n; i++) {
if (rows[i].kind == K_BUILDERR)
continue;
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0) fail++;
}
}
if (fail) {
fprintf(stderr, "f9_float: %d/%d checks failed\n",
fail, total);
return 1;
}
printf("f9_float: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,246 +0,0 @@
/*
* 951_f64cgen_run — runtime regression net for the two GATE-BLIND f64
* codegen bugs #96 and #97. Both stages (cstage cgen.c, wwstage
* cgenexpr.ww) emit byte-identical asm before and after the fix, so the
* 990-997 byte-id gates can NEVER catch a reintroduction — only an
* executed-and-checked runtime probe can. This file is that probe.
*
* Table-driven like 700_e2e: each row is a self-contained ww program;
* the C-side cstage `ww build` compiles it, we run the binary and assert
* the exit code. Rows that self-check return 0 on pass / a locator code
* on the first failing assertion; rows that assert value propagation
* return a computed result the harness compares to `want_exit`.
*
* cstage-only by design (mirrors 700_e2e + 969_checked_run): `ww_ww run`
* is broken (#95) and per-program wwstage byte-id is the 990-997 gates'
* job, not this file's. The fix's cs==ww symmetry is verified there.
*
* #96 — f64/f32 deref-load must MOVSD/MOVSS into X0, not MOVQ into AX.
* #97 — f64/f32 compare must consult PF (parity) for IEEE-754 NaN: with
* a NaN operand `!=` is true, the other five relops false.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return 1;
}
struct row { const char *src; int want_exit; };
static const struct row rows[] = {
/* #96 deref-load: dirty X0 with a non-x value just before the
* call so an accidental X0-retention can't mask a broken return
* load; f64 return through a bare deref must ride X0, and `*px`
* must reach X0 for the MULSD. (ken's f64gate p96_deref.) */
{ "package main;\n"
"fn deref(p: *f64) f64 = { return *p; };\n"
"export fn main() i32 = {\n"
" let x: f64 = 7.5;\n"
" let y: f64 = 1.25;\n"
" let px: *f64 = &x;\n"
" let junk: f64 = y * 2.0;\n"
" if (junk != 2.5) { return 3; };\n"
" let r: f64 = deref(px);\n"
" if (r != 7.5) { return 1; };\n"
" let q: f64 = *px * 2.0;\n"
" if (q != 15.0) { return 2; };\n"
" return 0;\n"
"};\n", 0 },
/* #96 value propagation: f64 returned through an f64-returning fn
* then truncated to i32. On the bug the value strands in AX and
* the i32 cast reads stale X0 -> wrong exit. */
{ "package main;\n"
"fn deref(p: *f64) f64 = { return *p; };\n"
"export fn main() i32 = {\n"
" let x: f64 = 42.0;\n"
" let r: f64 = deref(&x);\n"
" return r: i32;\n"
"};\n", 42 },
/* #96 arith-through-deref: `*p * 2.0` -> i32. */
{ "package main;\n"
"export fn main() i32 = {\n"
" let x: f64 = 7.5;\n"
" let p: *f64 = &x;\n"
" let q: f64 = *p * 2.0;\n"
" return q: i32;\n"
"};\n", 15 },
/* #96 f64frombits round-trip: reinterpret a u64 bit pattern as
* f64 via `*((&bits): *f64)`. The deref node is f64-typed so it
* must MOVSD into X0; on the bug it MOVQs into AX and the i32 cast
* reads stale X0. 0x4045000000000000 == 42.0. */
{ "package main;\n"
"export fn main() i32 = {\n"
" let bits: u64 = 0x4045000000000000u64;\n"
" let f: f64 = *((&bits): *f64);\n"
" return f: i32;\n"
"};\n", 42 },
/* #96 copysign-style sign transfer built from primitives: tobits/
* frombits are f64<->u64 reinterpret derefs (both float and int
* deref-loads exercised). copysign(5.0, -1.0) == -5.0. */
{ "package main;\n"
"fn tobits(f: f64) u64 = { return *((&f): *u64); };\n"
"fn frombits(b: u64) f64 = { return *((&b): *f64); };\n"
"export fn main() i32 = {\n"
" let x: f64 = 5.0;\n"
" let y: f64 = -1.0;\n"
" let mag: u64 = tobits(x) & 0x7fffffffffffffffu64;\n"
" let sgn: u64 = tobits(y) & 0x8000000000000000u64;\n"
" let r: f64 = frombits(mag | sgn);\n"
" if (r > 0.0) { return 1; };\n"
" if (r < -4.5) { if (r > -5.5) { return 0; }; };\n"
" return 2;\n"
"};\n", 0 },
/* #97 full NaN relop sweep (f64, UCOMISD). Runtime NaN via
* 0.0/0.0 through opaque fns so the checker can't const-fold it.
* isnan via self-inequality; NaN against each of the 6 relops;
* isnan(1.0)==false; ordered non-NaN rows guard for regression.
* (ken's f64gate p97_nan.) */
{ "package main;\n"
"fn zero() f64 = { return 0.0; };\n"
"fn one() f64 = { return 1.0; };\n"
"export fn main() i32 = {\n"
" let z: f64 = zero();\n"
" let nan: f64 = z / z;\n"
" let x: f64 = one();\n"
" if (!(nan != nan)) { return 1; };\n"
" if (nan == nan) { return 2; };\n"
" if (nan == x) { return 3; };\n"
" if (nan < x) { return 4; };\n"
" if (nan <= x) { return 5; };\n"
" if (nan > x) { return 6; };\n"
" if (nan >= x) { return 7; };\n"
" if (!(nan != x)) { return 8; };\n"
" if (x != x) { return 9; };\n"
" if (!(x == x)) { return 10; };\n"
" if (!(x < 2.0)) { return 11; };\n"
" if (!(x <= 1.0)) { return 12; };\n"
" if (!(2.0 > x)) { return 13; };\n"
" if (!(1.0 >= x)) { return 14; };\n"
" if (x > 2.0) { return 15; };\n"
" return 0;\n"
"};\n", 0 },
/* #97 value propagation: of the 6 relops against NaN, exactly one
* (`!=`) is true. Returns the true-count; the bug returns 3 (==,
* <, <= all wrongly fire on the unordered ZF/CF). */
{ "package main;\n"
"fn z() f64 = { return 0.0; };\n"
"export fn main() i32 = {\n"
" let nan: f64 = z() / z();\n"
" let x: f64 = 1.0;\n"
" let n: i32 = 0;\n"
" if (nan == x) { n += 1; };\n"
" if (nan != x) { n += 1; };\n"
" if (nan < x) { n += 1; };\n"
" if (nan <= x) { n += 1; };\n"
" if (nan > x) { n += 1; };\n"
" if (nan >= x) { n += 1; };\n"
" return n;\n"
"};\n", 1 },
/* #97 f32 path (UCOMISS): NaN must follow the same unordered
* rules, and ordered f64 relops with no NaN must stay correct.
* (ken's f64gate p97_f32_ordered.) */
{ "package main;\n"
"fn z32() f32 = { return 0.0; };\n"
"fn one32() f32 = { return 1.0; };\n"
"export fn main() i32 = {\n"
" let z: f32 = z32();\n"
" let nan: f32 = z / z;\n"
" let x: f32 = one32();\n"
" if (!(nan != nan)) { return 1; };\n"
" if (nan == nan) { return 2; };\n"
" if (nan < x) { return 3; };\n"
" if (nan >= x) { return 4; };\n"
" let a: f64 = 2.0;\n"
" let b: f64 = 3.0;\n"
" if (!(a < b)) { return 5; };\n"
" if (a > b) { return 6; };\n"
" if (!(a <= a)) { return 7; };\n"
" if (!(b >= a)) { return 8; };\n"
" if (!(a == 2.0)){ return 9; };\n"
" if (a != 2.0) { return 10; };\n"
" if (b < a) { return 11; };\n"
" if (!(b > a)) { return 12; };\n"
" return 0;\n"
"};\n", 0 },
/* #97 `>`/`>=` LEFT-BARE arm with runtime-built operands: the JA/
* JAE template stays unchanged by the fix, so this guards that the
* untouched arm still computes ordered greater-than correctly.
* (ken's f64gate p97_gtonly.) */
{ "package main;\n"
"fn z() f64 = { return 0.0; };\n"
"export fn main() i32 = {\n"
" let a: f64 = z() + 2.0;\n"
" let b: f64 = z() + 3.0;\n"
" if (!(b > a)) { return 1; };\n"
" if (a > b) { return 2; };\n"
" if (!(b >= a)) { return 3; };\n"
" if (!(a >= a)) { return 4; };\n"
" return 0;\n"
"};\n", 0 },
{ NULL, 0 }
};
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
int n = 0, fail = 0;
for (int i = 0; rows[i].src; i++, n++) {
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwf64_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
char src[128], outbin[128], rmcmd[160];
snprintf(src, sizeof src, "%s/wwf64_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wwf64_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (f == NULL) { runwait(rmcmd); fail++; continue; }
fputs(rows[i].src, f);
fclose(f);
char cmd[1024];
snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s",
bin, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row %d: build failed\n src: %s\n",
i, rows[i].src);
fail++;
runwait(rmcmd);
continue;
}
int got = runwait(outbin);
if (got != rows[i].want_exit) {
fprintf(stderr, "row %d: exit %d, want %d\n src: %s\n",
i, got, rows[i].want_exit, rows[i].src);
fail++;
}
runwait(rmcmd);
}
if (fail) {
fprintf(stderr, "%d/%d f64cgen tests failed\n", fail, n);
return 1;
}
printf("f64cgen: %d/%d ok\n", n, n);
return 0;
}

View File

@@ -1,219 +0,0 @@
/*
* 955_f64xmm_run — runtime + byte-id regression net for #103: an f64
* value failing to materialise in XMM (X0) before an SSE op. Two faces,
* same class, both GATE-BLIND (cstage cgen.c and wwstage cgenexpr.ww
* emitted byte-identical-but-wrong asm, so the 990-997 byte-id gates
* could never catch a reintroduction — only an executed-and-checked
* runtime probe can).
*
* FACE X — a no-decimal float-typed integer literal (`0f64`, `8f64`)
* is an N_INTLIT carrying float TYPE; the integer-immediate path
* stranded it in AX, so `n == 0f64` compared a stale X0 (true for
* all n) and `(8f64 * 10.0): i32` read garbage. Fixed by routing
* the float-typed N_INTLIT through the float-constant-in-X0 emit
* (cgen.c cgexpr_float / cgenexpr.ww cgfloatbits), plus the wwstage
* exprfloatkind N_INTLIT arm so the downstream f64->i32 cast emits
* CVTTSD2SI not MOVSXD (the #101 structural-vs-stamped asymmetry).
* FACE Z — a tuple positional f64 field read (`r.0`, r:(f64,i64))
* loaded via the integer op into AX, so `r.0 == 0.0` was wrongly
* true. Fixed by a fld_isfloat branch -> MOVSD/MOVSS into X0
* (cgen.c:5910 / cgenexpr.ww tuple arm), mirroring the struct-field
* float load at cgen.c:1462,1838 (the #96 pattern).
*
* Each row carries BOTH dimensions (like 953_f64crossmod_run):
* (a) cstage `ww build` + run, asserting the exit code.
* (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge. Both
* stages are fixed identically, so this stays byte-identical
* before and after; it catches one stage being fixed without the
* other.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
struct row { const char *label; const char *src; int want_exit; };
static const struct row rows[] = {
/* FACE X false-case: g(8.0) must be 0. On the bug `n == 0f64`
* compares against a stale X0 (true for all n) -> 1. */
{ "x_cmp_false",
"package main;\n"
"fn g(n: f64) i32 = { if (n == 0f64) { return 1; }; return 0; };\n"
"export fn main() i32 = { return g(8.0); };\n", 0 },
/* FACE X true-case: g(0.0) must still be 1 — the genuine equal
* case must survive the fix. */
{ "x_cmp_true",
"package main;\n"
"fn g(n: f64) i32 = { if (n == 0f64) { return 1; }; return 0; };\n"
"export fn main() i32 = { return g(0.0); };\n", 1 },
/* FACE X arith: (8f64 * 10.0): i32 == 80. On the bug 8f64 never
* reaches X0, the MULSD reads stale X0 -> garbage. */
{ "x_arith",
"package main;\n"
"export fn main() i32 = { return (8f64 * 10.0): i32; };\n", 80 },
/* FACE X negative-lit: `-8f64` is N_UN(TK_MINUS) wrapping a float-
* typed N_INTLIT, so the wwstage exprfloatkind must recurse through
* the unary into the new N_INTLIT-float arm. g(-8.0) == -8f64 must be
* true -> 1; on the bug the literal never reaches X0, the compare is
* always false, and g(-8.0) wrongly returns 0. */
{ "x_neg_cmp_true",
"package main;\n"
"fn g(n: f64) i32 = { if (n == -8f64) { return 1; }; return 0; };\n"
"export fn main() i32 = { return g(-8.0); };\n", 1 },
/* FACE X negative-lit control: g(8.0) == -8f64 must stay false -> 0.
* Guards the negative-literal path against over-reach. */
{ "x_neg_cmp_false",
"package main;\n"
"fn g(n: f64) i32 = { if (n == -8f64) { return 1; }; return 0; };\n"
"export fn main() i32 = { return g(8.0); };\n", 0 },
/* FACE Z: tuple positional f64 field compare. r.0 == 0.0 with
* r = (8.0, 0) must be false -> 9. On the bug r.0 loads into AX
* (integer op), `== 0.0` reads stale X0 -> wrongly true -> 5. */
{ "z_tuple_field",
"package main;\n"
"fn norm(n: f64) (f64, i64) = { return (n, 0); };\n"
"export fn main() i32 = {\n"
" const r = norm(8.0);\n"
" if (r.0 == 0.0) { return 5; };\n"
" return 9;\n"
"};\n", 9 },
/* CONTROL: the let-bound spelling (`const m = r.0; m == 0.0`) was
* already correct (the let-init is float-aware) and must STAY
* correct -> 9. Guards against the FACE-Z fix over- or
* under-reaching. */
{ "z_letbound_control",
"package main;\n"
"fn norm(n: f64) (f64, i64) = { return (n, 0); };\n"
"export fn main() i32 = {\n"
" const r = norm(8.0);\n"
" const m = r.0;\n"
" if (m == 0.0) { return 5; };\n"
" return 9;\n"
"};\n", 9 },
{ NULL, NULL, 0 }
};
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa);
int cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char w6c[1100], w6c_ww[1100];
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
if (access(w6c_ww, X_OK) != 0) {
fprintf(stderr, "f64xmm: w6c_ww missing — cannot run the "
"cs==ww byte-id gate (the whole point of this test)\n");
return 1;
}
int n = 0, fail = 0;
for (int i = 0; rows[i].src; i++, n++) {
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwf64m_%d_d_%d",
getpid(), i);
mkdir(tmpdir, 0755);
char rmcmd[160];
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
char src[128], outbin[128], cs_s[128], ws_s[128];
snprintf(src, sizeof src, "%s/wwf64m_%d_%d.ww",
tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wwf64m_%d_%d",
tmpdir, getpid(), i);
snprintf(cs_s, sizeof cs_s, "%s/wwf64m_%d_%d_cs.s",
tmpdir, getpid(), i);
snprintf(ws_s, sizeof ws_s, "%s/wwf64m_%d_%d_ww.s",
tmpdir, getpid(), i);
FILE *f = fopen(src, "wb");
if (f == NULL) { fail++; runwait(rmcmd); continue; }
fputs(rows[i].src, f);
fclose(f);
/* (a) cstage build + run. */
char cmd[2048];
snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s",
bin, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: cstage build failed\n",
rows[i].label);
fail++;
runwait(rmcmd);
continue;
}
int got = runwait(outbin);
if (got != rows[i].want_exit) {
fprintf(stderr, "row[%s]: cstage exit %d, want %d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
/* (b) cs==ww byte-id gate: emit .s from both stages, cmp. */
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c, cs_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label);
fail++; runwait(rmcmd); continue;
}
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c_ww, ws_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c_ww failed\n",
rows[i].label);
fail++; runwait(rmcmd); continue;
}
if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr,
"row[%s]: cstage/wwstage .s DIFFER (rule-10 "
"byte-id violation)\n", rows[i].label);
fail++;
}
runwait(rmcmd);
}
if (fail) {
fprintf(stderr, "%d/%d f64 xmm-materialise tests failed\n",
fail, n);
return 1;
}
printf("f64xmm: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n);
return 0;
}

View File

@@ -1,63 +1,22 @@
/*
* 956_tuprecv_f64_run — runtime + byte-id regression net for #105 and
* the #164 (#107) multi-float extension.
* 956_tuprecv_f64_run (SLIM PIN, #5-C4) — the #121 A-narrow asserttyped stamp
* dimension for the f64 tuple-receive DESTRUCTURE bindings. The runtime VALUE
* rows + cs==ww byte-id migrated to test/lang/tuprecv_f64_test.ww; what stays
* here is the one dimension test-lang has no channel for: a wwstage-STDERR grep
* that w6c_ww emits NO `asserttyped:` diagnostic on the un-annotated float
* destructure binding (the binding must carry a checker type stamp so cgen's
* class-aware spill fires). cstage has no ww asserttyped pass, so this is
* w6c_ww-only by construction (project memory: audit the *_ww binary).
*
* #164 (#107): a multi-float tuple return (e.g. (f64,f64)) mis-routes —
* the SEND emitted every float through X0 (cgexpr clobbers X0 per
* element), so two floats collided on X0 and the receive read both from
* X0. The fix gives the tuple return a SysV SSE cursor [X0,X1] parallel
* to the integer cursor [AX,DX,CX,R8]: a float rides the next XMM on an
* INDEPENDENT counter (ref/qbe/amd64/sysv.c retr). The SEND spills each
* float to @tupfscr as it walks (X0 is clobbered by later elements) and
* reloads X0/X1 by SSE index after the integer pops; every receive site
* (single-var 16B/32B, destructure, reassign) reads the float from its
* SSE-cursor reg. SSE caps at 2 (X0,X1) — (f64,f64,f64) is over-cap, so
* it returns via sret (#10 Fold A SEND + Fold B destructure RECEIVE); the
* f64x3_recv row asserts that round-trip works in both stages.
* NON-VACUITY (drew C4 mutation gate): the grep is LIVE — pre-stamp HEAD w6c_ww
* fires `asserttyped` on the f64 binding ident (nil n.type_), and the grep
* mechanically fires on a synthetic `asserttyped:` line (self-checked below at
* startup; a broken grep FAILS the test before any row runs).
*
* #105 (original): a tuple-from-call receive corrupts the f64 word when
* the callee is BRANCHED. Covers ALL THREE receive forms, which share the
* #83 tuple_rseq cursor and all carried the same defect:
* 1. SINGLE-VAR `let r = norm(); ...r.0` (cglet 16B-tuple branch)
* 2. DESTRUCTURE `let (m,i) = norm()` (N_MLET / cgmlet+tupstore)
* 3. REASSIGN `m,i = norm()` (N_MASSIGN / cgmassign+tupstore)
*
* Root (#105, a #103-FACE-Z regression): a (f64,i64)/(i64,f64) tuple
* returns its f64 word in X0 (the SSE return reg) and its integer word
* in an integer reg (tuple_rseq AX/DX). All three receives spilled the
* f64 word via MOVQ from the integer cursor — but that reg holds GARBAGE
* (the f64 is in X0). #103-FACE-Z's field read (MOVSD slot,X0) then read
* that garbage. The fix makes every receive spill CLASS-AWARE: an f64/f32
* word spills `MOVSD/MOVSS X0, slot`, an integer word spills `MOVQ
* <reg>, slot` (as before). cstage cgen.c (3 sites) + wwstage cgenstmt.ww
* (cglet branch + the shared tupstore helper, which covers cgmlet and
* cgmassign), identically.
*
* WHY BRANCHED CALLEES: a SINGLE-return callee masks the bug via
* register coincidence — a float-literal return leaves the f64 bit
* pattern in AX (literal materialise goes through AX), so MOVQ AX,slot
* happens to store the right bits; and X0 stays live to the receive. A
* BRANCHED / multi-statement callee whose f64 word is a non-literal
* (e.g. an f64 param) has an inner CALL clobber AX, so the integer-reg
* spill stores garbage. The bug rows below therefore all use branched
* callees with an f64-param word — single-return rows are gate-blind to
* #105 (this is the gate lesson the task pins).
*
* GATE-BLIND TO BYTE-ID ALONE: both stages are symmetric-WRONG on master
* (both emit MOVQ AX,slot), so the cs==ww .s gate HOLDS on master for
* the bug rows — they diverge only at RUNTIME. Each row carries BOTH
* dimensions (modelled on 954_tuprecv_run):
* (a) cstage `ww build` + run, asserting the exit code — this is what
* catches #105 (master returns the wrong exit).
* (b) w6c vs w6c_ww `.s` cmp — guards rule-10 (both stages fixed
* identically).
*
* CONTROL rows pin that the fix touches nothing else: an all-integer
* branched 2-tuple (correct pre- and post-fix; MOVQ path untouched), the
* destructure form `let (a,b)=mk()` (cgmlet, a separate path the fix
* does not touch), a single-return (f64,i64) field read (#103 FACE-Z
* shape, correct pre- and post-fix), and a bare 0f64 compare (#103
* FACE-X shape, no tuple involved).
* Slim sibling of the C2 precedent (954 ctl_destr's asserttyped sub-dimension,
* test/lang/tuprecv_test.ww). The 6 rows are the chk_stamped subset of the
* original 956 driver; non-float / control rows carry no stamp dimension and
* live only in the @test file.
*/
#include <stdio.h>
#include <stdlib.h>
@@ -74,73 +33,9 @@ runwait(const char *cmd)
return -1;
}
struct row {
const char *label;
const char *src;
int want_exit;
int chk_stamped;
int want_compile_fail; /* #164: loud-stop rows must NOT compile */
};
struct row { const char *label; const char *src; };
static const struct row rows[] = {
/* BUG — minimal repro. norm is BRANCHED (inner issub() CALL clobbers
* AX) and its f64 word is the param n, not a literal. Pre-fix the
* receive spills MOVQ AX,slot (garbage); r.0 != 16.0 -> return 1.
* Post-fix MOVSD X0,slot -> 16.0 -> return 0. */
{ "f64_i64_br",
"package main;\n"
"fn issub(n: f64) bool = { return false; };\n"
"fn norm(n: f64) (f64, i64) = {\n"
"\tif (issub(n)) { return (n*2.0, -52); };\n"
"\treturn (n, 0);\n"
"};\n"
"export fn main() i32 = {\n"
"\tconst r = norm(16.0);\n"
"\tconst m: f64 = r.0;\n"
"\tif (m != 16.0) { return 1; };\n"
"\treturn 0;\n"
"};\n", 0 },
/* BUG — deferred read. An intervening f64 CALL clobbers X0 AFTER the
* receive; the read of r.0 must come from the SPILLED slot, not a
* stale X0. Strongest catch: proves the spill happened at receive
* time and survives X0 clobber. */
{ "f64_i64_deferred",
"package main;\n"
"fn issub(n: f64) bool = { return false; };\n"
"fn norm(n: f64) (f64, i64) = {\n"
"\tif (issub(n)) { return (n*2.0, -52); };\n"
"\treturn (n, 0);\n"
"};\n"
"fn clob(x: f64) f64 = { return x + 1.0; };\n"
"export fn main() i32 = {\n"
"\tconst r = norm(16.0);\n"
"\tconst junk: f64 = clob(3.0);\n"
"\tconst m: f64 = r.0;\n"
"\tif (m != 16.0) { return 1; };\n"
"\tif (junk != 4.0) { return 2; };\n"
"\treturn 0;\n"
"};\n", 0 },
/* BUG — order-swap (i64, f64): f64 is word1, spilled from DX pre-fix
* (garbage; the f64 is in X0). Confirms the fix is position-aware:
* word1's f64 -> MOVSD X0, slot+8; word0's i64 -> MOVQ AX, slot+0. */
{ "i64_f64_br",
"package main;\n"
"fn issub(n: f64) bool = { return false; };\n"
"fn norm(n: f64) (i64, f64) = {\n"
"\tif (issub(n)) { return (-52, n*2.0); };\n"
"\treturn (0, n);\n"
"};\n"
"export fn main() i32 = {\n"
"\tconst r = norm(16.0);\n"
"\tconst i: i64 = r.0;\n"
"\tconst m: f64 = r.1;\n"
"\tif (m != 16.0) { return 1; };\n"
"\tif (i != 0) { return 2; };\n"
"\treturn 0;\n"
"};\n", 0 },
/* BUG — DESTRUCTURE form `let (m,i)=norm()` (N_MLET / cgmlet+tupstore).
* f64 binding m is element 0 (cursor AX); pre-fix MOVQ AX,slot stores
* garbage (issub clobbered AX). Post-fix MOVSD X0,slot. m=16.0, i=0. */
{ "destr_f64_i64_br",
"package main;\n"
"fn issub(n: f64) bool = { return false; };\n"
@@ -153,10 +48,7 @@ static const struct row rows[] = {
"\tif (m != 16.0) { return 1; };\n"
"\tif (i != 0) { return 2; };\n"
"\treturn 0;\n"
"};\n", 0, 1 },
/* BUG — DESTRUCTURE order-swap `let (i,m)=norm()`, (i64,f64). f64
* binding m is element 1 (cursor DX); pre-fix MOVQ DX,slot garbage,
* post-fix MOVSD X0,slot. i=0, m=16.0. */
"};\n" },
{ "destr_i64_f64_br",
"package main;\n"
"fn issub(n: f64) bool = { return false; };\n"
@@ -169,47 +61,7 @@ static const struct row rows[] = {
"\tif (m != 16.0) { return 1; };\n"
"\tif (i != 0) { return 2; };\n"
"\treturn 0;\n"
"};\n", 0, 1 },
/* BUG — REASSIGN form `m,i = norm()` (N_MASSIGN / cgmassign+tupstore)
* into pre-declared slots. Same f64-element-from-X0 defect. m=16.0. */
{ "massign_f64_i64_br",
"package main;\n"
"fn issub(n: f64) bool = { return false; };\n"
"fn norm(n: f64) (f64, i64) = {\n"
"\tif (issub(n)) { return (n*2.0, -52); };\n"
"\treturn (n, 0);\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet m: f64 = 0.0;\n"
"\tlet i: i64 = 0;\n"
"\tm, i = norm(16.0);\n"
"\tif (m != 16.0) { return 1; };\n"
"\tif (i != 0) { return 2; };\n"
"\treturn 0;\n"
"};\n", 0 },
/* BUG — REASSIGN order-swap `i,m = norm()`, (i64,f64). f64 reassign
* target is element 1 (cursor DX). i=0, m=16.0. */
{ "massign_i64_f64_br",
"package main;\n"
"fn issub(n: f64) bool = { return false; };\n"
"fn norm(n: f64) (i64, f64) = {\n"
"\tif (issub(n)) { return (-52, n*2.0); };\n"
"\treturn (0, n);\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet i: i64 = 0;\n"
"\tlet m: f64 = 0.0;\n"
"\ti, m = norm(16.0);\n"
"\tif (m != 16.0) { return 1; };\n"
"\tif (i != 0) { return 2; };\n"
"\treturn 0;\n"
"};\n", 0 },
/* #164 (#107) HEADLINE — multi-float (f64, f64), destructure. The
* callee is BRANCHED (issub CALL clobbers X0), so on master BOTH
* elements collide on X0: cgexpr(a) leaves a in X0, cgexpr(b)
* overwrites it, and every receive read spills from X0 -> x==y==b
* (5.0). Post-fix a rides the SSE cursor X0, b rides X1; the receive
* splits them. x=3.0, y=5.0. Pre-fix: x==5.0 -> return 1. */
"};\n" },
{ "f64f64_destr_br",
"package main;\n"
"fn issub(n: f64) bool = { return false; };\n"
@@ -222,46 +74,7 @@ static const struct row rows[] = {
"\tif (x != 3.0) { return 1; };\n"
"\tif (y != 5.0) { return 2; };\n"
"\treturn 0;\n"
"};\n", 0, 1 },
/* #164 HEADLINE — multi-float (f64, f64), SINGLE-VAR whole-tuple
* receive (`let r = pair(); r.0 / r.1`, the 16B rt16 branch). Same
* X0-collision on master; post-fix r.0 from X0, r.1 from X1. */
{ "f64f64_single_br",
"package main;\n"
"fn issub(n: f64) bool = { return false; };\n"
"fn pair(a: f64, b: f64) (f64, f64) = {\n"
"\tif (issub(a)) { return (a*2.0, b*2.0); };\n"
"\treturn (a, b);\n"
"};\n"
"export fn main() i32 = {\n"
"\tconst r = pair(3.0, 5.0);\n"
"\tconst x: f64 = r.0;\n"
"\tconst y: f64 = r.1;\n"
"\tif (x != 3.0) { return 1; };\n"
"\tif (y != 5.0) { return 2; };\n"
"\treturn 0;\n"
"};\n", 0 },
/* #164 HEADLINE — multi-float (f64, f64), REASSIGN into pre-declared
* slots (N_MASSIGN / cgmassign+tupstore). x=3.0, y=5.0. */
{ "f64f64_massign_br",
"package main;\n"
"fn issub(n: f64) bool = { return false; };\n"
"fn pair(a: f64, b: f64) (f64, f64) = {\n"
"\tif (issub(a)) { return (a*2.0, b*2.0); };\n"
"\treturn (a, b);\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet x: f64 = 0.0;\n"
"\tlet y: f64 = 0.0;\n"
"\tx, y = pair(3.0, 5.0);\n"
"\tif (x != 3.0) { return 1; };\n"
"\tif (y != 5.0) { return 2; };\n"
"\treturn 0;\n"
"};\n", 0 },
/* #164 — INTERLEAVED (i64, f64, i64): kills naive position->reg. The
* two i64s ride the INTEGER cursor (AX,DX), the f64 the SSE cursor
* (X0) on an independent counter — so x=AX, z=DX, y=X0. 3-element
* destructure. x=3, y=2.0, z=7. */
"};\n" },
{ "i64_f64_i64_destr",
"package main;\n"
"fn issub(n: i64) bool = { return false; };\n"
@@ -275,11 +88,7 @@ static const struct row rows[] = {
"\tif (y != 2.0) { return 2; };\n"
"\tif (z != 7) { return 3; };\n"
"\treturn 0;\n"
"};\n", 0, 1 },
/* #164 — (f64, str): SSE + wide (24B {ptr,len,cap} header) coexist.
* The f64 rides the SSE cursor (X0); the str rides the INTEGER
* cursor (AX,DX,CX) since the float consumes no GP slot. Destructure
* form. f=4.0, s.len=5 ("hello"). */
"};\n" },
{ "f64_str_destr",
"package main;\n"
"fn issub(n: f64) bool = { return false; };\n"
@@ -292,14 +101,7 @@ static const struct row rows[] = {
"\tif (f != 4.0) { return 1; };\n"
"\tif (s.len != 5) { return 2; };\n"
"\treturn 0;\n"
"};\n", 0, 1 },
/* #164 STR-FIRST destructure (str, f64): the unification's new-
* coverage shape with the wide header in slot 0. The str rides the
* INTEGER cursor (AX,DX,CX = ptr,len,cap), the f64 the SSE cursor (X0)
* — independent counters, the float consuming no GP slot. The
* destructure path's cursor handled str-first on master too, so this
* pins the dual-cursor restructure PRESERVED it (byte-id both ways)
* AND that the f64 coexists. s.len=5 ("hello"), f=4.0. */
"};\n" },
{ "str_f64_destr",
"package main;\n"
"fn issub(n: f64) bool = { return false; };\n"
@@ -312,140 +114,12 @@ static const struct row rows[] = {
"\tif (s.len != 5) { return 1; };\n"
"\tif (f != 4.0) { return 2; };\n"
"\treturn 0;\n"
"};\n", 0, 1 },
/* #164 STR-FIRST destructure (str, i64): pure-integer str-first.
* str@AX,DX,CX then i64@R8. Correct on master too (single-cursor
* destructure already routed ptr=AX) — byte-id regression guard that
* the dual cursor left the integer str-first mapping intact. s.len=5,
* k=7. */
{ "str_i64_destr",
"package main;\n"
"fn issub(n: i64) bool = { return false; };\n"
"fn si(n: i64) (str, i64) = {\n"
"\tif (issub(n)) { return (\"x\", n*2); };\n"
"\treturn (\"hello\", n);\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet (s, k) = si(7);\n"
"\tif (s.len != 5) { return 1; };\n"
"\tif (k != 7) { return 2; };\n"
"\treturn 0;\n"
"};\n", 0 },
/* #164 STR-FIRST SINGLE-VAR (str, i64), annotated `let t: (str,i64) =
* si(); t.0/t.1`: the shape the OLD 32B single-var branch got WRONG —
* it read .ptr from DX while the send placed .ptr in AX (self-
* inconsistent), scrambling the slot so t.1 read the str.ptr word.
* DISCRIMINATES: pre-fix t.1 = a large address != 7; post-fix the
* unified dual cursor lands str@AX,DX,CX + i64@R8 so t.1=7. Both
* stages were wrong IDENTICALLY pre-fix (byte-id held, runtime broke),
* right identically post-fix. Annotated (not bare `const r=`) because
* the wwstage 32B single-var branch keys on the N_TTUPLE type node —
* a bare 32B single-var is a pre-existing stage asymmetry out of #164
* scope (16B bare single-var rows above cover the inferred path). */
{ "str_i64_single",
"package main;\n"
"fn issub(n: i64) bool = { return false; };\n"
"fn si(n: i64) (str, i64) = {\n"
"\tif (issub(n)) { return (\"x\", n*2); };\n"
"\treturn (\"hello\", n);\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet t: (str, i64) = si(7);\n"
"\tif (t.1 != 7) { return 1; };\n"
"\tif (t.0.len != 5) { return 2; };\n"
"\treturn 0;\n"
"};\n", 0 },
/* #10 OVER-CAP RECV — three f64 = 0 GP / 3 SSE exceeds the SSE return
* cap (X0,X1 only). #164 loud-stopped this at the SEND; #10 Fold A
* sret's it (callee stores X0/X1 → @sretarg at foff 0/8/16) and Fold B
* destructures it (each element MOVSD'd out of @sretscr into its
* binding), so it is now a working round-trip in BOTH stages. */
{ "f64x3_recv",
"package main;\n"
"fn tri(a: f64, b: f64, c: f64) (f64, f64, f64) = {\n"
"\treturn (a, b, c);\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet (x, y, z) = tri(1.0, 2.0, 3.0);\n"
"\tif (x != 1.0) { return 1; };\n"
"\tif (y != 2.0) { return 2; };\n"
"\tif (z != 3.0) { return 3; };\n"
"\treturn 0;\n"
"};\n", 0, 0, 0 },
/* CONTROL — all-integer branched 2-tuple. The integer-cursor MOVQ
* path is untouched by the fix (e0/e1 not float), so this is correct
* pre- and post-fix and byte-id both ways. a=5, b=7 -> 12. */
{ "ctl_int_br",
"package main;\n"
"fn issub(n: i64) bool = { return false; };\n"
"fn mk(n: i64) (i64, i64) = {\n"
"\tif (issub(n)) { return (n*2, -1); };\n"
"\treturn (n, 7);\n"
"};\n"
"export fn main() i32 = {\n"
"\tconst r = mk(5);\n"
"\tconst a: i64 = r.0;\n"
"\tconst b: i64 = r.1;\n"
"\treturn (a: i32) + (b: i32);\n"
"};\n", 12 },
/* CONTROL — destructure-with-NO-f64 `let (a,b)=mk()`. The fix now
* touches cgmlet/tupstore, but an all-integer element takes the
* unchanged MOVQ path, so this stays byte-id and correct pre- and
* post-fix — guards that the class check doesn't perturb integers.
* a=5, b=7 -> 12. */
{ "ctl_destr",
"package main;\n"
"fn issub(n: i64) bool = { return false; };\n"
"fn mk(n: i64) (i64, i64) = {\n"
"\tif (issub(n)) { return (n*2, -1); };\n"
"\treturn (n, 7);\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet (a, b) = mk(5);\n"
"\treturn (a: i32) + (b: i32);\n"
"};\n", 12 },
/* CONTROL — #103 FACE-Z single-return (f64,i64) field read. The f64
* word is a literal, so AX coincidentally holds its bits on master;
* correct pre- and post-fix (the fix changes MOVQ AX,slot -> MOVSD
* X0,slot but the run result is unchanged). f=2.5->2, i=7 -> 9. */
{ "ctl_facez_single",
"package main;\n"
"fn mk() (f64, i64) = { return (2.5, 7); };\n"
"export fn main() i32 = {\n"
"\tconst t = mk();\n"
"\tconst f: f64 = t.0;\n"
"\tconst i: i64 = t.1;\n"
"\treturn (f: i32) + (i: i32);\n"
"};\n", 9 },
/* CONTROL — #103 FACE-X bare 0f64 compare (no tuple). The fix does
* not touch the compare path; pure no-op guard. -> 0. */
{ "ctl_facex_0f64",
"package main;\n"
"export fn main() i32 = {\n"
"\tconst z: f64 = 0.0;\n"
"\tif (z != 0.0) { return 1; };\n"
"\treturn 0;\n"
"};\n", 0 },
{ NULL, NULL, 0 }
"};\n" },
{ NULL, NULL }
};
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa);
int cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
/* The asserttyped diagnostic only appears in w6c_ww (the wwstage checker audit);
* an absent w6c_ww means the gate cannot run — fail loud. */
int
main(void)
{
@@ -459,141 +133,68 @@ main(void)
bin = absbin;
}
char w6c[1100], w6c_ww[1100];
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
char w6c_ww[1100];
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
if (access(w6c_ww, X_OK) != 0) {
fprintf(stderr, "tuprecv_f64: w6c_ww missing — cannot run the "
"cs==ww byte-id gate\n");
"asserttyped stamp gate (the whole point of this pin)\n");
return 1;
}
int n = 0, fail = 0;
/* NON-VACUITY self-check: the grep must FIRE on a literal asserttyped
* line. A grep that never matches would pass every row vacuously. */
{
char probe[80], cmd[160];
snprintf(probe, sizeof probe, "/tmp/wwtupf_probe_%d.txt", getpid());
FILE *p = fopen(probe, "wb");
if (!p) { fprintf(stderr, "tuprecv_f64: probe open failed\n"); return 1; }
fputs("error: asserttyped: e.type_ nil\n", p);
fclose(p);
snprintf(cmd, sizeof cmd, "grep -q asserttyped %s", probe);
if (runwait(cmd) != 0) {
fprintf(stderr, "tuprecv_f64: grep self-check FAILED — the "
"asserttyped gate is vacuous\n");
unlink(probe);
return 1;
}
unlink(probe);
}
for (int i = 0; rows[i].src; i++, n++) {
char tmpdir[64], src[128], outbin[128], rmcmd[160];
char tmpdir[64], src[128], errf[128], rmcmd[160], cmd[2048];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwtupf_%d_d_%d",
getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/wwtupf_%d_%d.ww",
tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wwtupf_%d_%d",
tmpdir, getpid(), i);
snprintf(errf, sizeof errf, "%s/err", tmpdir);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (f == NULL) { runwait(rmcmd); fail++; continue; }
fputs(rows[i].src, f);
fclose(f);
char cmd[2048];
/* #164 LOUD-STOP rows: the SSE-cap overflow must FAIL TO COMPILE
* in BOTH stages (rule-7). Assert (a) cstage `ww build` errors,
* and (b) w6c AND w6c_ww each return non-zero — proving the
* loud-stop fires symmetrically. No .s is produced, so the
* byte-id cmp is skipped. Discriminates against master, which
* has no SSE cap and builds the (mis)compile. */
if (rows[i].want_compile_fail) {
snprintf(cmd, sizeof cmd,
"%s/ww build -o %s %s >/dev/null 2>&1",
bin, outbin, src);
if (runwait(cmd) == 0) {
fprintf(stderr, "row[%s]: cstage build SUCCEEDED, "
"want loud-stop (SSE cap)\n", rows[i].label);
fail++;
}
snprintf(cmd, sizeof cmd, "%s -o /dev/null %s 2>/dev/null",
w6c, src);
if (runwait(cmd) == 0) {
fprintf(stderr, "row[%s]: w6c emitted .s, want "
"loud-stop\n", rows[i].label);
fail++;
}
snprintf(cmd, sizeof cmd, "%s -o /dev/null %s 2>/dev/null",
w6c_ww, src);
if (runwait(cmd) == 0) {
fprintf(stderr, "row[%s]: w6c_ww emitted .s, want "
"loud-stop\n", rows[i].label);
fail++;
}
runwait(rmcmd);
continue;
}
/* (a) cstage build + run in a scratch dir. */
snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s",
bin, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: cstage build failed\n",
snprintf(cmd, sizeof cmd, "%s -o /dev/null %s 2>%s",
w6c_ww, src, errf);
runwait(cmd);
snprintf(cmd, sizeof cmd, "grep -q asserttyped %s", errf);
if (runwait(cmd) == 0) {
fprintf(stderr, "row[%s]: w6c_ww emitted asserttyped "
"(destructure float binding unstamped)\n",
rows[i].label);
fail++;
runwait(rmcmd);
continue;
}
int got = runwait(outbin);
if (got != rows[i].want_exit) {
fprintf(stderr, "row[%s]: cstage exit %d, want %d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
/* (b) cs==ww byte-id gate: emit .s from both stages, cmp. */
char cs_s[64], ws_s[64];
snprintf(cs_s, sizeof cs_s, "/tmp/wwtupf_%d_%d_cs.s",
getpid(), i);
snprintf(ws_s, sizeof ws_s, "/tmp/wwtupf_%d_%d_ww.s",
getpid(), i);
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c, cs_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label);
fail++; runwait(rmcmd); continue;
}
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c_ww, ws_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c_ww failed\n",
rows[i].label);
fail++; runwait(rmcmd); unlink(cs_s); continue;
}
if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr,
"row[%s]: cstage/wwstage .s DIFFER (rule-10 "
"byte-id violation)\n", rows[i].label);
fail++;
}
/* (c) #121 A-narrow stamp gate: the un-annotated float-
* destructure binding must now carry a checker type stamp, so
* w6c_ww emits no `asserttyped:` diagnostic. Non-vacuous —
* pre-stamp (HEAD) w6c_ww fires asserttyped on the f64 binding
* ident (nil n.type_). */
if (rows[i].chk_stamped) {
char errf[80];
snprintf(errf, sizeof errf,
"/tmp/wwtupf_%d_%d_err.txt", getpid(), i);
snprintf(cmd, sizeof cmd,
"%s -o /dev/null %s 2>%s", w6c_ww, src, errf);
runwait(cmd);
snprintf(cmd, sizeof cmd,
"grep -q asserttyped %s", errf);
if (runwait(cmd) == 0) {
fprintf(stderr, "row[%s]: w6c_ww emitted "
"asserttyped (destructure binding "
"unstamped)\n", rows[i].label);
fail++;
}
unlink(errf);
}
runwait(rmcmd); unlink(cs_s); unlink(ws_s);
runwait(rmcmd);
}
if (fail) {
fprintf(stderr, "%d/%d tuple-receive-f64 tests failed\n",
fprintf(stderr, "%d/%d tuprecv_f64 asserttyped-stamp rows failed\n",
fail, n);
return 1;
}
printf("tuprecv_f64: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n);
printf("tuprecv_f64: %d/%d ok (asserttyped stamp pin, w6c_ww)\n", n, n);
return 0;
}

View File

@@ -1,213 +0,0 @@
/*
* 964_f32lit_run — runtime + byte-id regression net for #104 fold-1: an
* f32-typed float literal must NARROW to single precision in X0 before
* the f32 consumer reads it. Both stages (cstage cgen.c cgexpr_float,
* wwstage cgenexpr.ww cgfloatbits) materialise a float literal as a
* 64-bit DOUBLE in X0 (MOVQ bits -> MOVSD). For an f32-typed literal the
* downstream MOVSS store/return then reads the LOW 4 BYTES of that
* double — garbage (0x00000000 == 0.0f for most clean values, which is
* why 0.0 coincidentally survived the bug). fold-1 appends CVTSD2SS
* X0,X0 at both literal sites (N_FLOATLIT + the float-typed N_INTLIT
* arm) when the node is f32-typed, so the value reaches X0 as a true
* single. Both stages emit byte-identical asm, so the 990-997 byte-id
* gates can NEVER catch a reintroduction — only an executed-and-checked
* runtime probe can. (951_f64cgen is GATE-BLIND here: its f32 rows only
* assert NaN ordering, never a concrete f32 value.)
*
* SCOPE: fold-1 covers literals that carry an explicit f32 TYPE — the
* `f32` suffix (`1.0f32`, `1.5f32`) and the no-decimal N_INTLIT-float
* arm (`8f32`). An UN-suffixed literal in an f32 context (`let x: f32 =
* 1.0`) stays ty_untyped_float through the checker, so the literal node
* is never f32-typed and fold-1's branch can't fire — materialised as a
* double, stored low-4-bytes -> 0.0f. Fixing that needs fold-2: the
* checker lowering untyped-float literals to their f32 context type
* (#104, both checkers). This probe therefore uses suffixed literals
* exclusively; the un-suffixed gap is tracked under #104 fold-2.
*
* Each row carries BOTH dimensions (like 955_f64xmm_run):
* (a) cstage `ww build` + run, asserting the exit code.
* (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge (rule-10).
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
struct row { const char *label; const char *src; int want_exit; };
static const struct row rows[] = {
/* The hole 951 misses: a CONCRETE non-trivial f32 value. On the
* bug `let x: f32 = 1.0f32` stores the low 4 bytes of double 1.0
* (== 0x00000000 == 0.0f), so x:f64 == 0.0 != 1.0 -> 1. */
{ "bare_value",
"package main;\n"
"export fn main() i32 = {\n"
" let x: f32 = 1.0f32;\n"
" if (x: f64 != 1.0) { return 1; };\n"
" return 0;\n"
"};\n", 0 },
/* arith on f32 literals: 1.5 + 2.5 == 4.0. On the bug both operands
* land as 0.0f -> sum 0.0 != 4.0. */
{ "arith",
"package main;\n"
"export fn main() i32 = {\n"
" let a: f32 = 1.5f32;\n"
" let b: f32 = 2.5f32;\n"
" let s: f32 = a + b;\n"
" if (s: f64 != 4.0) { return 1; };\n"
" return 0;\n"
"};\n", 0 },
/* value-propagation: f32 returned through an f32 fn + arith on an
* f32 literal, truncated to i32. 2.5 + 1.5 == 4.0 -> 4. */
{ "return_arith",
"package main;\n"
"fn g() f32 = { return 2.5f32; };\n"
"export fn main() i32 = {\n"
" let r: f32 = g() + 1.5f32;\n"
" return r: i32;\n"
"};\n", 4 },
/* the float-typed N_INTLIT arm (`8f32` — no decimal, f32 suffix).
* Same materialiser, same fold-1 branch. -> 8. */
{ "intlit_f32_arm",
"package main;\n"
"export fn main() i32 = {\n"
" let y: f32 = 8f32;\n"
" return y: i32;\n"
"};\n", 8 },
/* genuine single-rounding: 2^24 + 1 is NOT representable in f32 and
* rounds back to 2^24 (round-to-even). If the add ran in double it
* would be 16777217.0 != 16777216.0 -> 1. Proves the value is a
* true single, not the low half of a double. */
{ "single_round",
"package main;\n"
"export fn main() i32 = {\n"
" let big: f32 = 16777216.0f32;\n"
" let r: f32 = big + 1.0f32;\n"
" if (r: f64 != 16777216.0) { return 1; };\n"
" return 0;\n"
"};\n", 0 },
{ NULL, NULL, 0 }
};
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa);
int cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char w6c[1100], w6c_ww[1100];
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
if (access(w6c_ww, X_OK) != 0) {
fprintf(stderr, "f32lit: w6c_ww missing — cannot run the "
"cs==ww byte-id gate (the whole point of this test)\n");
return 1;
}
int n = 0, fail = 0;
for (int i = 0; rows[i].src; i++, n++) {
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwf32l_%d_d_%d",
getpid(), i);
mkdir(tmpdir, 0755);
char src[128], outbin[128], cs_s[128], ws_s[128], rmcmd[160];
snprintf(src, sizeof src, "%s/wwf32l_%d_%d.ww",
tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wwf32l_%d_%d",
tmpdir, getpid(), i);
snprintf(cs_s, sizeof cs_s, "%s/wwf32l_%d_%d_cs.s",
tmpdir, getpid(), i);
snprintf(ws_s, sizeof ws_s, "%s/wwf32l_%d_%d_ww.s",
tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (f == NULL) { runwait(rmcmd); fail++; continue; }
fputs(rows[i].src, f);
fclose(f);
/* (a) cstage build + run. */
char cmd[2048];
snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s",
bin, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: cstage build failed\n",
rows[i].label);
fail++;
runwait(rmcmd);
continue;
}
int got = runwait(outbin);
if (got != rows[i].want_exit) {
fprintf(stderr, "row[%s]: cstage exit %d, want %d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
/* (b) cs==ww byte-id gate: emit .s from both stages, cmp. */
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c, cs_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label);
fail++; runwait(rmcmd); continue;
}
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c_ww, ws_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c_ww failed\n",
rows[i].label);
fail++; runwait(rmcmd); continue;
}
if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr,
"row[%s]: cstage/wwstage .s DIFFER (rule-10 "
"byte-id violation)\n", rows[i].label);
fail++;
}
runwait(rmcmd);
}
if (fail) {
fprintf(stderr, "%d/%d f32 literal-materialise tests failed\n",
fail, n);
return 1;
}
printf("f32lit: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n);
return 0;
}

View File

@@ -1,215 +0,0 @@
/*
* 965_f32stamp_run — runtime + byte-id regression net for #104 fold-2: an
* UN-suffixed float literal in an f32 context (`let x: f32 = 1.0`, `return
* 1.0` from an f32 fn) must be stamped f32 by the checker so fold-1's cgen
* narrow (CVTSD2SS at the literal materialise site) fires. Without the stamp
* the literal stays ty_untyped_float, materialises as a 64-bit double, and
* the f32 consumer reads the LOW 4 BYTES of that double — 0x00000000 == 0.0f
* for clean values (`1.0` -> 0.0f, so `x: f64 != 1.0` trips). fold-1 (964)
* only covered SUFFIXED literals (`1.0f32`); the un-suffixed common case was
* its documented hole, closed here.
*
* Both checkers stamp the literal: cstage cmd/wcc/check.c coerce_floatlit (at
* clet + cstmt N_RETURN), wwstage selfhost/cmd/wcc/check.ww coercefloatlit (in
* resolvewalk's post-order N_LET / N_RETURN handler — placed AFTER the child
* re-walk so the post-order exprtype re-stamp doesn't undo it). Both stages
* emit byte-identical asm, so the 990-997 byte-id gates can NEVER catch a
* reintroduction — only an executed-and-checked runtime probe can.
*
* SCOPE (#104 fold-2): the stamp fires at let-init and return ONLY. binop
* (`1.0 + x_f32`), unary minus (`-1.0`), assign, call-arg, and struct-field
* are DEFERRED to #120 — the wwstage cgen's exprfloatkind (cgenutil.ww)
* hardcodes a float literal to f64 and picks f32 off the operands, not the
* node stamp, so a stamped literal in those positions does not narrow in
* wwstage (cs would emit ADDSS, ww ADDSD — a byte-id break). This probe
* therefore uses bare let-init / return literals exclusively.
*
* Each row carries BOTH dimensions (like 955 / 964):
* (a) cstage `ww build` + run, asserting the exit code.
* (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge (rule-10).
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
struct row { const char *label; const char *src; int want_exit; };
static const struct row rows[] = {
/* The hole 964 leaves: a bare (UN-suffixed) f32-context literal. On
* the bug `let x: f32 = 1.0` stores the low 4 bytes of double 1.0
* (== 0x00000000 == 0.0f), so x:f64 == 0.0 != 1.0 -> 1. */
{ "let_one",
"package main;\n"
"export fn main() i32 = {\n"
" let x: f32 = 1.0;\n"
" if (x: f64 != 1.0) { return 1; };\n"
" return 0;\n"
"};\n", 0 },
/* a bare decimal literal truncated through i32: 8.0 -> 8. On the bug
* the f32 slot holds 0.0f -> 0. */
{ "let_decimal",
"package main;\n"
"export fn main() i32 = {\n"
" let y: f32 = 8.0;\n"
" return y: i32;\n"
"};\n", 8 },
/* a fractional value (exactly representable): 0.5. Bug -> 0.0f, so
* p:f64 == 0.0 != 0.5 -> 1. */
{ "let_frac",
"package main;\n"
"export fn main() i32 = {\n"
" let p: f32 = 0.5;\n"
" if (p: f64 != 0.5) { return 1; };\n"
" return 0;\n"
"};\n", 0 },
/* return context: an f32 fn returning a bare literal, truncated to
* i32 at the call site. 2.0 -> 2. On the bug the X0 single is the
* low half of double 2.0 (== 0.0f) -> 0. */
{ "return_bare",
"package main;\n"
"fn g() f32 = { return 2.0; };\n"
"export fn main() i32 = {\n"
" return g(): i32;\n"
"};\n", 2 },
/* return feeding a let, both un-suffixed: the literal narrows in the
* fn return, the let-init binds the (already-f32) call value. 4.0 ->
* 4. */
{ "return_then_let",
"package main;\n"
"fn h() f32 = { return 4.0; };\n"
"export fn main() i32 = {\n"
" let r: f32 = h();\n"
" if (r: f64 != 4.0) { return 1; };\n"
" return r: i32;\n"
"};\n", 4 },
{ NULL, NULL, 0 }
};
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa);
int cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char w6c[1100], w6c_ww[1100];
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
if (access(w6c_ww, X_OK) != 0) {
fprintf(stderr, "f32stamp: w6c_ww missing — cannot run the "
"cs==ww byte-id gate (the whole point of this test)\n");
return 1;
}
int n = 0, fail = 0;
for (int i = 0; rows[i].src; i++, n++) {
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwf32s_%d_d_%d",
getpid(), i);
mkdir(tmpdir, 0755);
char rmcmd[160];
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
char src[128];
snprintf(src, sizeof src, "%s/wwf32s_%d_%d.ww",
tmpdir, getpid(), i);
FILE *f = fopen(src, "wb");
if (f == NULL) { fail++; runwait(rmcmd); continue; }
fputs(rows[i].src, f);
fclose(f);
/* (a) cstage build + run. */
char outbin[128];
snprintf(outbin, sizeof outbin, "%s/wwf32s_%d_%d",
tmpdir, getpid(), i);
char cmd[2048];
snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s",
bin, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: cstage build failed\n",
rows[i].label);
fail++;
runwait(rmcmd);
continue;
}
int got = runwait(outbin);
if (got != rows[i].want_exit) {
fprintf(stderr, "row[%s]: cstage exit %d, want %d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
/* (b) cs==ww byte-id gate: emit .s from both stages, cmp. */
char cs_s[128], ws_s[128];
snprintf(cs_s, sizeof cs_s, "%s/wwf32s_%d_%d_cs.s",
tmpdir, getpid(), i);
snprintf(ws_s, sizeof ws_s, "%s/wwf32s_%d_%d_ww.s",
tmpdir, getpid(), i);
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c, cs_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label);
fail++; runwait(rmcmd); continue;
}
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c_ww, ws_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c_ww failed\n",
rows[i].label);
fail++; runwait(rmcmd); continue;
}
if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr,
"row[%s]: cstage/wwstage .s DIFFER (rule-10 "
"byte-id violation)\n", rows[i].label);
fail++;
}
runwait(rmcmd);
}
if (fail) {
fprintf(stderr, "%d/%d f32 un-suffixed stamp tests failed\n",
fail, n);
return 1;
}
printf("f32stamp: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n);
return 0;
}

View File

@@ -1,200 +0,0 @@
/*
* 989_floatlit_run — runtime + byte-id net for #62: the two stages
* folded float LITERALS differently. cstage folds via strtod
* (correctly rounded); the wwstage lexer used a pow-10 accumulation
* fold that was 1-2 ULP off on decimal fractions, overflowed its i64
* accumulator past 19 mantissa digits, and missed DBL_MIN/DBL_MAX by
* up to 2 ULP — a pinned cs≠ww DATA divergence (989 ratchet #59.10).
* Fix: lexnum folds through strconv.stof64 (the Hare-ported
* correctly-rounded decimal engine).
*
* Fixture 1 (vectors): each row reads back a literal's IEEE bits via
* a *u64 reinterpret and compares against the C-strtod-oracle bit
* pattern; the exit code pinpoints the failing row. Carries (a)
* cstage `ww build` + run asserting exit 0 (rule-10: convergence
* targets the runtime-CORRECT side) and (b) w6c vs w6c_ww `.s` cmp
* (byte-id — the wwstage fold itself).
*
* Fixture 2 (overflow): 1.7976931348623159e308 rounds above DBL_MAX —
* strtod sets ERANGE so cstage rejects; wwstage must reject too
* (stof64 overflow → errat). Both compilers must exit non-zero.
*
* Retained divergence (task #21): a SUBNORMAL literal (e.g.
* 2.2250738585072011e-308) is rejected by cstage (glibc strtod flags
* partial underflow with ERANGE) but accepted correctly-rounded by
* wwstage (Hare stof semantics) — accept-set asymmetry only, not a
* bits divergence; pre-existing on master (old parsef64 accepted it
* with garbage bits).
*
* 9xx is full; shares the 989 prefix per the 989_sha256 precedent.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static const char *vectors_src =
"package main;\n"
"fn bits(v: f64) u64 = {\n"
" let x: f64 = v;\n"
" let p: *u64 = (&x): *u64;\n"
" return *p;\n"
"};\n"
"export fn main() i32 = {\n"
" if (bits(1.0000000000000002) != 0x3FF0000000000001u64) { return 1; };\n"
" if (bits(9007199254740993.0) != 0x4340000000000000u64) { return 2; };\n"
" if (bits(1.2345e67) != 0x4DDD4E421712C0B7u64) { return 3; };\n"
" if (bits(0.1) != 0x3FB999999999999Au64) { return 4; };\n"
" if (bits(1.1) != 0x3FF199999999999Au64) { return 5; };\n"
" if (bits(123456789012345678901234567890.0) != 0x45F8EE90FF6C373Eu64) { return 6; };\n"
" if (bits(2.2250738585072014e-308) != 0x0010000000000000u64) { return 7; };\n"
" if (bits(0.3) != 0x3FD3333333333333u64) { return 8; };\n"
" if (bits(3.141592653589793) != 0x400921FB54442D18u64) { return 9; };\n"
" if (bits(1.7976931348623157e308) != 0x7FEFFFFFFFFFFFFFu64) { return 10; };\n"
" if (bits(1.7976931348623158e308) != 0x7FEFFFFFFFFFFFFFu64) { return 11; };\n"
" if (bits(7.2057594037927933e16) != 0x4370000000000000u64) { return 12; };\n"
" if (bits(1000000000000000000000.0) != 0x444B1AE4D6E2EF50u64) { return 13; };\n"
" if (bits(1_000.5) != 0x408F440000000000u64) { return 14; };\n"
" if (bits(1.00000000000000011102230246251565404236316680908203125) != 0x3FF0000000000000u64) { return 15; };\n"
" if (bits(1.00000000000000011102230246251565404236316680908203126) != 0x3FF0000000000001u64) { return 16; };\n"
" if (bits(4503599627370497.5) != 0x4330000000000002u64) { return 17; };\n"
" if (bits(0.5) != 0x3FE0000000000000u64) { return 18; };\n"
" if (bits(1.0e308) != 0x7FE1CCF385EBC8A0u64) { return 19; };\n"
" if (bits(2.225073858507202e-308) != 0x0010000000000001u64) { return 20; };\n"
" return 0;\n"
"};\n";
static const char *overflow_src =
"package main;\n"
"export fn main() i32 = {\n"
" let a: f64 = 1.7976931348623159e308;\n"
" return 0;\n"
"};\n";
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa);
int cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
static int
writesrc(const char *path, const char *src)
{
FILE *f = fopen(path, "wb");
if (f == NULL) return -1;
fputs(src, f);
fclose(f);
return 0;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char w6c[1100], w6c_ww[1100];
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
if (access(w6c_ww, X_OK) != 0) {
fprintf(stderr, "floatlit: w6c_ww missing — cannot run "
"the cs==ww byte-id gate (the whole point of this test)\n");
return 1;
}
int fail = 0;
char src[128], outbin[128], cs_s[128], ws_s[128], cmd[2048];
char tmpdir[64], rmcmd[160];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwflit_%d_d", getpid());
mkdir(tmpdir, 0755);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
/* fixture 1: vectors — cstage build+run, then byte-id */
snprintf(src, sizeof src, "%s/wwflit_%d.ww", tmpdir, getpid());
if (writesrc(src, vectors_src) != 0) { runwait(rmcmd); return 1; }
snprintf(outbin, sizeof outbin, "%s/wwflit_%d", tmpdir, getpid());
snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s", bin, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "floatlit: cstage build failed\n");
fail++;
} else {
int got = runwait(outbin);
if (got != 0) {
fprintf(stderr, "floatlit: vector row %d has wrong "
"bits at runtime (cstage)\n", got);
fail++;
}
}
snprintf(cs_s, sizeof cs_s, "%s/wwflit_%d_cs.s", tmpdir, getpid());
snprintf(ws_s, sizeof ws_s, "%s/wwflit_%d_ww.s", tmpdir, getpid());
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, cs_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "floatlit: w6c failed on vectors\n");
fail++;
} else {
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c_ww, ws_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "floatlit: w6c_ww failed on vectors\n");
fail++;
} else if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr, "floatlit: cstage/wwstage .s DIFFER "
"(rule-10 byte-id violation)\n");
fail++;
}
}
/* fixture 2: overflow literal — BOTH stages must reject */
snprintf(src, sizeof src, "%s/wwflit_%d_ovf.ww", tmpdir, getpid());
if (writesrc(src, overflow_src) != 0) { runwait(rmcmd); return 1; }
snprintf(cmd, sizeof cmd, "%s %s >/dev/null 2>&1", w6c, src);
if (runwait(cmd) == 0) {
fprintf(stderr, "floatlit: w6c ACCEPTED overflow literal\n");
fail++;
}
snprintf(cmd, sizeof cmd, "%s %s >/dev/null 2>&1", w6c_ww, src);
if (runwait(cmd) == 0) {
fprintf(stderr, "floatlit: w6c_ww ACCEPTED overflow literal\n");
fail++;
}
runwait(rmcmd);
if (fail) {
fprintf(stderr, "floatlit: %d check(s) failed\n", fail);
return 1;
}
printf("floatlit: vectors byte-id + runtime-correct, "
"overflow rejected by both stages\n");
return 0;
}

View File

@@ -1,155 +0,0 @@
/*
* 989_globfloatstructarg_run — F8-c9 (report-item #31, the EXCEPTION):
* passing a module-GLOBAL float-bearing struct by value must drain its float
* eightbytes into the SSE arg regs (X0..), not all-GP.
*
* THE BUG (cat-A silent miscompile, align-UP): cgcall's per-arg drain decides
* the SysV eightbyte class via structfloatclass, but read it from the local
* slot's tnode ONLY (`if (lc != nil) stfc = structfloatclass(lc.tnode)`). A
* module-global struct arg (lc==nil) kept stfc=0, so the float eightbytes
* drained as integers (POPQ into DI/SI) and X0 was never loaded — the callee
* read its f64 fields from the wrong registers. cstage classifies off the
* operand TYPE, so it loaded X0 and ran correct — the cat-A divergence.
*
* THIS IS THE EXCEPTION among the F8 members: the bug is a float-CLASS
* decision keyed to a local-only slot, NOT an address-routing fallback — an
* F7-flavoured stamp/type-keying fix. THE FIX: for a global ident, key
* structfloatclass off the global's declared tnode (letvartnode), the same
* classification the local path uses. align ww UP; the .s is byte-identical.
*
* Rows (build+run on cstage `ww` and wwstage `ww_ww`; rule-10 — agree+hit;
* each take() returns 0 iff every field round-trips through the call):
* row | shape | want
* ----------------+----------------------------------+-----
* float_first | gp:{f:f64,i:i64}={1.5,9}; take | 0 [#31: f→X0]
* int_first | gp:{i:i64,f:f64}={9,2.5}; take | 0 [int eb→GP, f→X0]
* both_float | gp:{a:f64,b:f64}={1.5,2.5}; take | 0 [two SSE eightbytes]
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
struct row {
const char *label;
const char *src;
int want_exit;
};
static const struct row rows[] = {
{ "float_first",
"package main;\n"
"type pair = struct { f: f64, i: i64 };\n"
"let gp: pair = pair { f = 1.5, i = 9 };\n"
"fn take(p: pair) int = { if (p.f == 1.5 && p.i == 9) { return 0; }; return 2; };\n"
"export fn main() int = { return take(gp); };\n",
0 },
{ "int_first",
"package main;\n"
"type pair = struct { i: i64, f: f64 };\n"
"let gp: pair = pair { i = 9, f = 2.5 };\n"
"fn take(p: pair) int = { if (p.i == 9 && p.f == 2.5) { return 0; }; return 2; };\n"
"export fn main() int = { return take(gp); };\n",
0 },
{ "both_float",
"package main;\n"
"type pp = struct { a: f64, b: f64 };\n"
"let gp: pp = pp { a = 1.5, b = 2.5 };\n"
"fn take(p: pp) int = { if (p.a == 1.5 && p.b == 2.5) { return 0; }; return 2; };\n"
"export fn main() int = { return take(gp); };\n",
0 },
};
/* run_build — build+run `src` via `driver`; returns the binary's exit
* code, or -1 on a build failure. */
static int
run_build(const char *driver, const struct row *r, int i)
{
char src[128], tmpdir[64], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/gfsa_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/gfsa_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/gfsa_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -2; }
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
driver, outbin, src);
int brc = runwait(cmd);
int got = -1;
if (brc == 0) got = runwait(outbin);
runwait(rmcmd);
return brc == 0 ? got : -1;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[1024], wdrv[1024];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *drv; int gated; }
drivers[] = {
{ "cstage", cdrv, 0 },
{ "wwstage", wdrv, 1 },
{ NULL, NULL, 0 },
};
int n = (int)(sizeof rows / sizeof rows[0]);
int total = 0, fail = 0;
for (int d = 0; drivers[d].name; d++) {
if (drivers[d].gated && access(drivers[d].drv, X_OK) != 0) {
fprintf(stderr, "globfloatstructarg_run: skip %s (no %s)\n",
drivers[d].name, drivers[d].drv);
continue;
}
for (int i = 0; i < n; i++) {
total++;
int got = run_build(drivers[d].drv, &rows[i], i);
if (got != rows[i].want_exit) {
fprintf(stderr, "globfloatstructarg_run[%s][%s]: "
"exit=%d want=%d\n", drivers[d].name,
rows[i].label, got, rows[i].want_exit);
fail++;
}
}
}
if (fail) {
fprintf(stderr, "globfloatstructarg_run: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("globfloatstructarg_run: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -0,0 +1,10 @@
//ww:error "bad float literal"
// 989_floatlit overflow leg: 1.7976931348623159e308 rounds above DBL_MAX —
// cstage strtod sets ERANGE, wwstage stof64 overflows; BOTH stages must reject
// (runww ERROR arm runs w6c AND w6c_ww). Migrated from 989_floatlit_run.c
// fixture 2 (#5-C4).
package main;
export fn main() i32 = {
let a: f64 = 1.7976931348623159e308;
return 0;
};