Makefile: test target graph v2; fold per-snippet byte identity into blanket gates

Presence-is-registration wrapper classification replaces the 296-entry
TESTS list, its per-wrapper rules, the test/run scheduler, the
last-green cache, and auto -j; test is five in-process units plus one
fixture smoke; test-commit composes behavior suites; byte identity,
bootstrap, and platform stay explicit gates. test-data-byteid builds
every non-error corpus fixture twice through the fixed cstage driver
swapping only WW_W6C and byte-compares every per-package .s, with a
910-fixture vacuity floor and the DATABYTEID_DIVERGED loud-pin ledger
(8 real cs/ww divergences: r660, r71, r76_typeeq_fn x4, r940, r989 -
the base64 #59 pin). The 61 carriers whose only assertion that
comparator subsumes retire with it, leaving the 27 survivors that
observe asm patterns, symbols, frames, .wwi round-trips, inline-only
sources, or the wwstage driver leg.

One commit because the wildcard classification sweeps any leftover .c
into test-compiler, where the retired carriers' --sep invocations
cannot run against the current driver; the 61 deletions, the 27-entry
byteid list, and the blanket comparator are a single consistent state.
tools/peellint and tools/sizelint (and the 944 gate wired to them)
retire with the target graph that carried them; rule-13 layout
discipline stays on the authoritative-helper rule.
This commit is contained in:
2026-08-07 23:03:38 +09:00
parent 8c86e8ecb4
commit 228a632a2f
65 changed files with 406 additions and 24442 deletions

3054
Makefile

File diff suppressed because it is too large Load Diff

View File

@@ -1,430 +0,0 @@
/*
* 701_cgassign_struct — whole-struct receive ABI for sizes <= 24B
* (receive side of #4's cgreturn, task #5 #27).
*
* #4 wired the producer side: `return s;` materialises bytes into
* AX (bytes 0..7), DX (8..15), CX (16..23), zero-padded to 24B. The
* #4 test (698_cgreturn_struct.c) only pins that cgreturn doesn't
* crash; the result is discarded by the caller. #5 wires the
* receive side end-to-end:
*
* - `let s: foo = bar()` — N_LET call-result rhs lands AX/DX/CX
* into the slot with sized stores (MOVQ for full 8B chunks,
* MOVL/MOVB/MOVW tail by *declared* struct size — the receiver
* must NOT mirror the sender's three uniform MOVQs, else
* trailing 1..7 bytes overrun the next slot).
* - `let s: foo = foo{...}` — already wired by #4's predecessor;
* this test pins the value path.
* - `s = bar()` reassign — symmetric N_ASSIGN N_IDENT-lhs.
* - `s = foo{...}` reassign — symmetric N_STRUCTLIT shape.
*
* What this test pins:
* - cstage and wwstage emit byte-identical asm for every fixture
* (catches any drift in the receive-side store sequence).
* - END-TO-END VALUE CORRECTNESS: each fixture's main returns a
* deterministic exit code derived from the received struct's
* fields. A regression in the receive ABI (e.g., DX→+0 instead
* of +8, or an over-wide tail MOVQ overrunning the slot) shows
* up as a `got != want` exit-code mismatch, not a no-crash
* pass.
*
* Coverage:
* - 8B (one_i64): smallest struct, only AX is meaningful.
* - 16B (pair_i64): two-word AX/DX.
* - 20B (five_i32): three-word AX/DX/CX with MOVL tail — the
* headline ASYMMETRY case. A naïve MOVQ tail here would stomp
* 4 bytes past the slot.
* - 24B (trip_i64): three-word, no tail.
* - 24B mix (i32+i32+i64+i64): registerstruct-packed shape.
*
* Each shape covers two rhs forms (call-result, struct-literal)
* across two lvalue forms (let-init, reassign).
*
* Sizes >24B are OUT OF SCOPE — sret is deferred (same constraint
* as #4). Tail chunks in {3,5,6,7} are unreachable under WW
* struct alignment rules (field aligns force size%align == 0); the
* receive branches guard those out and fall through.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "wwtestpkg.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; };
static const struct row rows[] = {
/* 8B, call-result let-init. exit = s.v (= 7). */
{ "one_i64_let_call",
"type one = struct { v: i64 };\n"
"fn mk() one = { return one { v = 7i64 }; };\n"
"fn main() i32 = {\n"
" let s: one = mk();\n"
" return s.v: i32;\n"
"};\n",
7 },
/* 8B, struct-literal let-init. exit = s.v (= 11). */
{ "one_i64_let_lit",
"type one = struct { v: i64 };\n"
"fn main() i32 = {\n"
" let s: one = one { v = 11i64 };\n"
" return s.v: i32;\n"
"};\n",
11 },
/* 8B, call-result reassign. exit = s.v (= 13). */
{ "one_i64_reassign_call",
"type one = struct { v: i64 };\n"
"fn mk() one = { return one { v = 13i64 }; };\n"
"fn main() i32 = {\n"
" let s: one = one { v = 0i64 };\n"
" s = mk();\n"
" return s.v: i32;\n"
"};\n",
13 },
/* 8B, struct-literal reassign. exit = s.v (= 17). */
{ "one_i64_reassign_lit",
"type one = struct { v: i64 };\n"
"fn main() i32 = {\n"
" let s: one = one { v = 0i64 };\n"
" s = one { v = 17i64 };\n"
" return s.v: i32;\n"
"};\n",
17 },
/* 16B, call-result let-init. exit = a + b (= 3 + 5 = 8).
* Pins that DX lands at +8 and AX at +0. */
{ "pair_i64_let_call",
"type pair = struct { a: i64, b: i64 };\n"
"fn mk() pair = { return pair { a = 3i64, b = 5i64 }; };\n"
"fn main() i32 = {\n"
" let s: pair = mk();\n"
" return (s.a + s.b): i32;\n"
"};\n",
8 },
/* 16B, struct-literal reassign. exit = a + b (= 4 + 6 = 10). */
{ "pair_i64_reassign_lit",
"type pair = struct { a: i64, b: i64 };\n"
"fn main() i32 = {\n"
" let s: pair = pair { a = 0i64, b = 0i64 };\n"
" s = pair { a = 4i64, b = 6i64 };\n"
" return (s.a + s.b): i32;\n"
"};\n",
10 },
/* 20B (five-i32), call-result let-init — the ASYMMETRY
* headline case. AX bytes 0..7 hold a,b. DX bytes 8..15 hold
* c,d. CX low 4 bytes hold e; CX upper 4 bytes are pad zero.
* The receiver MUST store CX as MOVL (4B), not MOVQ (8B);
* a MOVQ would overrun into the next local slot. Sum check:
* 1+2+3+4+5 = 15. */
{ "five_i32_let_call",
"type five = struct { a: i32, b: i32, c: i32, d: i32, e: i32 };\n"
"fn mk() five = {\n"
" return five { a = 1, b = 2, c = 3, d = 4, e = 5 };\n"
"};\n"
"fn main() i32 = {\n"
" let s: five = mk();\n"
" return s.a + s.b + s.c + s.d + s.e;\n"
"};\n",
15 },
/* 20B (five-i32), struct-literal reassign. Same tail-MOVL
* pin as above but exercises the structlit-field-walk path. */
{ "five_i32_reassign_lit",
"type five = struct { a: i32, b: i32, c: i32, d: i32, e: i32 };\n"
"fn main() i32 = {\n"
" let s: five = five { a = 0, b = 0, c = 0, d = 0, e = 0 };\n"
" s = five { a = 2, b = 4, c = 6, d = 8, e = 10 };\n"
" return s.a + s.b + s.c + s.d + s.e;\n"
"};\n",
30 },
/* 24B, call-result let-init — the headline three-word ABI.
* AX/DX/CX each carry one full word, no tail. Sum check:
* 11+22+33 = 66. */
{ "trip_i64_let_call",
"type trip = struct { x: i64, y: i64, z: i64 };\n"
"fn mk() trip = { return trip { x = 11i64, y = 22i64, z = 33i64 }; };\n"
"fn main() i32 = {\n"
" let s: trip = mk();\n"
" return (s.x + s.y + s.z): i32;\n"
"};\n",
66 },
/* 24B, struct-literal reassign. Same word-shape as above. */
{ "trip_i64_reassign_lit",
"type trip = struct { x: i64, y: i64, z: i64 };\n"
"fn main() i32 = {\n"
" let s: trip = trip { x = 0i64, y = 0i64, z = 0i64 };\n"
" s = trip { x = 14i64, y = 28i64, z = 42i64 };\n"
" return (s.x + s.y + s.z): i32;\n"
"};\n",
84 },
/* 24B mix (registerstruct-packed): {i32, i32, i64, i64}.
* Layout: a@+0, b@+4, c@+8, d@+16 (totsize 24). AX carries
* a+b packed, DX = c, CX = d. Sum check: 1+2+3+4 = 10. */
{ "mix_let_call",
"type mix = struct { a: i32, b: i32, c: i64, d: i64 };\n"
"fn mk() mix = { return mix { a = 1, b = 2, c = 3i64, d = 4i64 }; };\n"
"fn main() i32 = {\n"
" let s: mix = mk();\n"
" return (s.a + s.b) + (s.c + s.d): i32;\n"
"};\n",
10 },
/* dst.field = call() — single-dot struct-typed field via
* local struct base. Exercises the cgen.c:1996+ N_DOT.N_IDENT
* struct-field branch (cstage) and cgenexpr.ww:~4046 single-
* dot local-base site (wwstage). Sum check: 3+4+0 = 7. */
{ "field_local_call",
"type inner = struct { a: i64, b: i64 };\n"
"type outer = struct { i: inner, t: i64 };\n"
"fn mki() inner = { return inner { a = 3i64, b = 4i64 }; };\n"
"fn main() i32 = {\n"
" let o: outer = outer { i = inner { a = 0i64, b = 0i64 }, t = 0i64 };\n"
" o.i = mki();\n"
" return (o.i.a + o.i.b + o.t): i32;\n"
"};\n",
7 },
/* dst.field = T{...} — same single-dot site, struct-literal
* rhs. Sum check: 11+22+5 = 38. */
{ "field_local_lit",
"type inner = struct { a: i64, b: i64 };\n"
"type outer = struct { i: inner, t: i64 };\n"
"fn main() i32 = {\n"
" let o: outer = outer { i = inner { a = 0i64, b = 0i64 }, t = 5i64 };\n"
" o.i = inner { a = 11i64, b = 22i64 };\n"
" return (o.i.a + o.i.b + o.t): i32;\n"
"};\n",
38 },
/* dst.field = call() via *struct base (auto-deref through
* pointer). Exercises cgenexpr.ww:~3752 single-dot ptr-base
* site (wwstage). Sum check: 30+40+100 = 170. */
{ "field_ptr_call",
"type inner = struct { a: i64, b: i64 };\n"
"type outer = struct { i: inner, t: i64 };\n"
"fn mki() inner = { return inner { a = 30i64, b = 40i64 }; };\n"
"fn main() i32 = {\n"
" let o: outer = outer { i = inner { a = 0i64, b = 0i64 }, t = 100i64 };\n"
" let p: *outer = &o;\n"
" p.i = mki();\n"
" return (o.i.a + o.i.b + o.t): i32;\n"
"};\n",
170 },
/* dst.field = T{...} via *struct base, struct-literal rhs.
* Sum check: 7+8+50 = 65. */
{ "field_ptr_lit",
"type inner = struct { a: i64, b: i64 };\n"
"type outer = struct { i: inner, t: i64 };\n"
"fn main() i32 = {\n"
" let o: outer = outer { i = inner { a = 0i64, b = 0i64 }, t = 50i64 };\n"
" let p: *outer = &o;\n"
" p.i = inner { a = 7i64, b = 8i64 };\n"
" return (o.i.a + o.i.b + o.t): i32;\n"
"};\n",
65 },
/* dst.field = call() via top-level let-global base.
* Exercises cgenexpr.ww:~4352 single-dot global-base site
* (wwstage) and the corresponding cstage is_global path.
* `let g: T;` (no init) avoids the module-name-mangle path
* that initialised globals hit (cf. task #17). Sum check:
* 70+80+100 = 250 (≤ 255, fits in process exit code). */
{ "field_global_call",
"type inner = struct { a: i64, b: i64 };\n"
"type outer = struct { i: inner, t: i64 };\n"
"let g: outer;\n"
"fn mki() inner = { return inner { a = 70i64, b = 80i64 }; };\n"
"fn main() i32 = {\n"
" g.t = 100i64;\n"
" g.i = mki();\n"
" return (g.i.a + g.i.b + g.t): i32;\n"
"};\n",
250 },
/* Chained `s.f.g = call()` — depth-2 dot lhs. Exercises the
* cstage chained walker (cgen.c:~2626) and wwstage walker
* (cgenexpr.ww:~4381). The initialiser uses explicit field
* writes (not a nested struct-literal), since cgen's nested-
* STRUCTLIT-as-field-value path is a separate gap. Bare
* `let o: outer;` zero-fills the slot. Sums sized to fit a
* process exit code (8 bits): 5+6+10+20 = 41. */
{ "field_chain_call",
"type inner = struct { a: i64, b: i64 };\n"
"type middle = struct { in: inner, t: i64 };\n"
"type outer = struct { m: middle, x: i64 };\n"
"fn mki() inner = { return inner { a = 5i64, b = 6i64 }; };\n"
"fn main() i32 = {\n"
" let o: outer;\n"
" o.m.t = 10i64;\n"
" o.x = 20i64;\n"
" o.m.in = mki();\n"
" return (o.m.in.a + o.m.in.b + o.m.t + o.x): i32;\n"
"};\n",
41 },
/* Chained `s.f.g = T{...}` — depth-2 struct-literal rhs.
* Sum check: 11+22+10+20 = 63. */
{ "field_chain_lit",
"type inner = struct { a: i64, b: i64 };\n"
"type middle = struct { in: inner, t: i64 };\n"
"type outer = struct { m: middle, x: i64 };\n"
"fn main() i32 = {\n"
" let o: outer;\n"
" o.m.t = 10i64;\n"
" o.x = 20i64;\n"
" o.m.in = inner { a = 11i64, b = 22i64 };\n"
" return (o.m.in.a + o.m.in.b + o.m.t + o.x): i32;\n"
"};\n",
63 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wcas_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/wcas_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wcas_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
wwtest_fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s", driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
* w6c_ww and diff. Catches receive-side codegen drift between the
* stages, which 995_self_rebuild covers globally but doesn't surface
* as a focused-fixture failure. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/wcas_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/wcas_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/wcas_asm_%d_%d_w.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
wwtest_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
drivers[] = {
{ "cstage", cdrv, 0 },
{ "wwstage", wdrv, 1 },
{ NULL, NULL, 0 },
};
int n = (int)(sizeof rows / sizeof rows[0]);
int total = 0, fail = 0;
/* Compile+run for each (driver, row). */
for (int d = 0; drivers[d].name; d++) {
if (drivers[d].gated_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "cgassign_struct: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"cgassign_struct[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
/* Asm byte-identity diff, only when both stages exist. */
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"cgassign_struct: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("cgassign_struct: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,292 +0,0 @@
/*
* 702_dot_explicit_deref — silent miscompile of `(*p).f` (task #16).
*
* Pre-existing landmine surfaced by worker-cgnassign during #5
* (whole-struct N_ASSIGN). Both cstage cgen.c N_ASSIGN N_DOT and
* wwstage cgenexpr.ww cgassign single-dot dispatch gated on
* `n->lhs->lhs->kind == N_IDENT`. The parser produces N_UN(STAR,
* IDENT(p)) for `(*p).f`, so the store fell off the dispatch and
* silently emitted nothing. Read-side N_DOT had the same gap —
* the pointer-auto-deref load branch required N_IDENT and fell
* through to cgexpr on the N_UN, which derefs the pointer as a
* scalar (load 8B from p into AX, then the field part is dropped).
*
* Not exercised by current selfhost source (auto-deref `p.f` was
* used throughout) so 995_self_rebuild didn't catch it. Latent for
* any future user code that types out the explicit deref.
*
* The fix in both stages is a base/lhs retarget: when the parser
* shape is N_UN(STAR, IDENT(p)), substitute the inner IDENT so
* the existing via_ptr / pointer-auto-deref branch fires the same
* as `p.f`. v1 scope is bare-IDENT inner only; `(*expr).f` with a
* non-IDENT pointer expression (`(*arr[i]).f`, `(*g.p).f`) still
* drops silently, tracked as a follow-up — needs cgexpr(inner) →
* reg + RHS-spill scheduling, which is a different design.
*
* Each row pins:
* - cstage value correctness (process exit code).
* - wwstage value correctness (when ww_ww exists).
* - cstage vs wwstage byte-identical .s output (catches drift).
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "wwtestpkg.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; };
static const struct row rows[] = {
/* Write side, scalar i64 field via explicit deref.
* Pre-fix: cgassign single-dot dispatch fell off (base.kind
* was N_UN, not N_IDENT) and emitted no store. f stayed 0. */
{ "write_i64",
"type pt = struct { x: i64, y: i64 };\n"
"fn main() i32 = {\n"
" let s: pt = pt { x = 0i64, y = 0i64 };\n"
" let p: *pt = &s;\n"
" (*p).x = 7i64;\n"
" (*p).y = 35i64;\n"
" return (s.x + s.y): i32;\n"
"};\n",
42 },
/* Write side, narrow i32 field. Pins fldstoreop sizing on
* the via_ptr path — MOVL not MOVQ (which would stomp 4 bytes
* past the field). */
{ "write_i32",
"type box = struct { a: i32, b: i32, c: i32 };\n"
"fn main() i32 = {\n"
" let s: box = box { a = 0, b = 0, c = 0 };\n"
" let p: *box = &s;\n"
" (*p).a = 5;\n"
" (*p).b = 11;\n"
" (*p).c = 19;\n"
" return s.a + s.b + s.c;\n"
"};\n",
35 },
/* Write side, str field. Pins the str-typed field branch in
* the via_ptr path (cgexpr → AX=ptr/BX=len, store both halves
* at +0/+8 — not just AX). Initialiser uses `let v: box;`
* (zero-fill) instead of a struct literal because N_LET
* N_STRUCTLIT silently drops the .len half of a str-typed
* field — a separate pre-existing bug, out of scope here. */
{ "write_str",
"type box = struct { s: str, n: i64 };\n"
"fn main() i32 = {\n"
" let v: box;\n"
" let p: *box = &v;\n"
" (*p).s = \"hi\";\n"
" (*p).n = 40i64;\n"
" return (v.s.len: i64 + v.n): i32;\n"
"};\n",
42 },
/* Read side, scalar i64 field via explicit deref.
* Pre-fix: case N_DOT's pointer-auto-deref branch required
* N_IDENT and fell through to `cgexpr(c, n->lhs, ...)` —
* derefed the pointer as if loading a scalar, so AX held the
* first qword of the struct (s.x = 50). Field offset for .y
* (+8) was dropped → returned 50 instead of 80. */
{ "read_i64",
"type pt = struct { x: i64, y: i64 };\n"
"fn main() i32 = {\n"
" let s: pt = pt { x = 50i64, y = 80i64 };\n"
" let p: *pt = &s;\n"
" return (*p).y: i32;\n"
"};\n",
80 },
/* Read side, narrow i32 field. Pins fldloadop sizing — MOVL
* with sign-extend, not raw MOVQ. */
{ "read_i32",
"type box = struct { a: i32, b: i32, c: i32 };\n"
"fn main() i32 = {\n"
" let s: box = box { a = 1, b = 2, c = 3 };\n"
" let p: *box = &s;\n"
" return (*p).a + (*p).b + (*p).c;\n"
"};\n",
6 },
/* Read side, str field. Pins the str-typed field load path —
* AX=ptr, BX=len. .len is the receive-side check. Initialiser
* uses write-by-field (same str-in-structlit-init bug avoided
* as in write_str). */
{ "read_str_len",
"type box = struct { s: str };\n"
"fn main() i32 = {\n"
" let v: box;\n"
" v.s = \"hello\";\n"
" let p: *box = &v;\n"
" return (*p).s.len: i32;\n"
"};\n",
5 },
/* Mixed: write via (*p).f then read via (*p).f. End-to-end
* round-trip pins both sides agreeing on the same offset and
* width. */
{ "roundtrip",
"type pt = struct { x: i32, y: i32, z: i32 };\n"
"fn main() i32 = {\n"
" let s: pt = pt { x = 0, y = 0, z = 0 };\n"
" let p: *pt = &s;\n"
" (*p).x = 4;\n"
" (*p).y = 7;\n"
" (*p).z = 13;\n"
" return (*p).x + (*p).y + (*p).z;\n"
"};\n",
24 },
};
/* run_driver — compile r->src via the given driver and exec; return
* the process exit code. Mirror of 701's helper. */
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wded_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/wded_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wded_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
wwtest_fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s", driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
* w6c_ww and diff. Mirror of 701. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/wded_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/wded_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/wded_asm_%d_%d_w.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
wwtest_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "dot_explicit_deref: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"dot_explicit_deref[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"dot_explicit_deref: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("dot_explicit_deref: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,285 +0,0 @@
/*
* 703_nested_structlit — silent zero of nested STRUCTLIT fields
* (task #17).
*
* Pre-existing landmine surfaced by worker-cgnassign during #5.
* For a struct literal whose field value is itself an N_STRUCTLIT
* of a struct-typed field, the outer field-walk did
* `cgexpr(field_value); store-AX-sized` — cgexpr has no whole-
* struct-in-register convention, so for a nested literal it lands
* AX = first qword and the trailing bytes silently stay zero (or
* stack garbage in the hostile case). The same shape applies at
* three sites in each stage:
* - N_LET N_STRUCTLIT — `let o: outer = outer { i = inner{...} }`
* - N_ASSIGN N_IDENT-lhs N_STRUCTLIT — `o = outer { i = inner{...} }`
* - N_RETURN N_STRUCTLIT — `return outer { i = inner{...} }`
*
* Workaround used in 701_cgassign_struct rows that need nested
* shape: `let o: outer; o.f = ...` (write-by-field). Avoids the
* bug; doesn't fix it.
*
* Fix: a shared helper `cg_structlit_fill_bp` (cstage) /
* `cgstructlitfillbp` (wwstage) factors the field-walk, recursing
* when a field's value is itself an N_STRUCTLIT for a struct-typed
* field. Bp-relative addressing only; the N_ASSIGN N_DOT-lhs
* structlit walks (via_ptr / global) keep their inline field-walk
* and still drop nested-STRUCTLIT silently — separate follow-up
* task ("cgen: nested STRUCTLIT in N_ASSIGN N_DOT structlit").
*
* Each row pins:
* - cstage value correctness (process exit code).
* - wwstage value correctness (when ww_ww exists).
* - cstage vs wwstage byte-identical .s output (catches drift).
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "wwtestpkg.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; };
static const struct row rows[] = {
/* N_LET nested structlit, all i64 fields. Pre-fix: only o.i.a
* (first qword) was stored; o.i.b silently stayed 0. Want:
* 7 + 8 + 12 = 27. */
{ "let_nested_i64",
"type inner = struct { a: i64, b: i64 };\n"
"type outer = struct { i: inner, t: i64 };\n"
"fn main() i32 = {\n"
" let o: outer = outer { i = inner { a = 7i64, b = 8i64 }, t = 12i64 };\n"
" return (o.i.a + o.i.b + o.t): i32;\n"
"};\n",
27 },
/* N_LET nested structlit, multi-level (3-deep). Pre-fix:
* only o.m.in.a was stored; b and t and outer.x silently 0.
* Want: 1+2+3+4 = 10. */
{ "let_nested_3deep",
"type leaf = struct { a: i64, b: i64 };\n"
"type middle = struct { in: leaf, t: i64 };\n"
"type outer = struct { m: middle, x: i64 };\n"
"fn main() i32 = {\n"
" let o: outer = outer {\n"
" m = middle {\n"
" in = leaf { a = 1i64, b = 2i64 },\n"
" t = 3i64\n"
" },\n"
" x = 4i64\n"
" };\n"
" return (o.m.in.a + o.m.in.b + o.m.t + o.x): i32;\n"
"};\n",
10 },
/* N_LET nested structlit, narrow i32 fields. Pins sized-store
* dispatch in the helper — MOVL not MOVQ. Want: 4+5+6+7 = 22. */
{ "let_nested_i32",
"type inner = struct { a: i32, b: i32 };\n"
"type outer = struct { i: inner, x: i32, y: i32 };\n"
"fn main() i32 = {\n"
" let o: outer = outer { i = inner { a = 4, b = 5 }, x = 6, y = 7 };\n"
" return o.i.a + o.i.b + o.x + o.y;\n"
"};\n",
22 },
/* N_ASSIGN N_IDENT-lhs nested structlit. Pre-fix: o.i.b
* stayed 0 after the reassign (only first qword stored).
* Want: 9+11+30 = 50. */
{ "assign_ident_nested",
"type inner = struct { a: i64, b: i64 };\n"
"type outer = struct { i: inner, t: i64 };\n"
"fn main() i32 = {\n"
" let o: outer;\n"
" o = outer { i = inner { a = 9i64, b = 11i64 }, t = 30i64 };\n"
" return (o.i.a + o.i.b + o.t): i32;\n"
"};\n",
50 },
/* N_RETURN nested structlit. Pre-fix: only b's first qword
* (its .a) landed in the receive slot; b's .b silently 0.
* Want: 13+17+25 = 55. */
{ "return_nested",
"type inner = struct { a: i64, b: i64 };\n"
"type outer = struct { i: inner, t: i64 };\n"
"fn mko() outer = {\n"
" return outer { i = inner { a = 13i64, b = 17i64 }, t = 25i64 };\n"
"};\n"
"fn main() i32 = {\n"
" let r: outer = mko();\n"
" return (r.i.a + r.i.b + r.t): i32;\n"
"};\n",
55 },
/* Mixed: inner struct surrounded by other scalar fields, with
* the inner appearing in the MIDDLE of the outer (non-zero
* outer foff for the recursion target). Pins that the helper
* threads `bpoff + outer_foff` correctly. All fields are i64 so
* the wwstage/cstage struct-layout alignment divergence (task
* #15, mixed i32/struct fields) doesn't bite — switch to i32
* once #15 lands. Sum: 100+1+2+50 = 153. */
{ "let_nested_middle",
"type inner = struct { a: i64, b: i64 };\n"
"type outer = struct { pre: i64, i: inner, post: i64 };\n"
"fn main() i32 = {\n"
" let o: outer = outer {\n"
" pre = 100i64,\n"
" i = inner { a = 1i64, b = 2i64 },\n"
" post = 50i64\n"
" };\n"
" return (o.pre + o.i.a + o.i.b + o.post): i32;\n"
"};\n",
153 },
};
/* run_driver — compile r->src via the given driver and exec; return
* the process exit code. Mirror of 701/702. */
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wcns_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/wcns_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wcns_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
wwtest_fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s", driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
* w6c_ww and diff. Mirror of 701/702. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/wcns_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/wcns_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/wcns_asm_%d_%d_w.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
wwtest_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "nested_structlit: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"nested_structlit[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"nested_structlit: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("nested_structlit: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,337 +0,0 @@
/*
* 704_dot_structlit — silent zero of nested STRUCTLIT field in
* cgassign N_DOT-lhs (task #18).
*
* Sister bug to #17. #17 fixed the BP-relative sites (N_LET,
* N_ASSIGN N_IDENT-lhs, N_RETURN) via the cg_structlit_fill_bp /
* cgstructlitfillbp helper. The N_ASSIGN N_DOT-lhs structlit walks
* were left inline because their addressing has extra BX-reload
* concerns (the dst addr is a *struct local or a struct global,
* loaded into BX, and cgexpr clobbers BX between fields). All four
* dot-flavors still emitted `cgexpr(field_value); store-AX-sized`
* for the rhs structlit's fields — and for a struct-typed field
* whose value is itself an N_STRUCTLIT, that lands only the first
* qword while the trailing bytes silently stay zero.
*
* Affected dot-flavors (each with the same silent-zero shape):
* 1. single-dot local-base `o.f = outer { i = inner{...}, ... }`
* 2. single-dot ptr-base `p.f = outer { i = inner{...}, ... }`
* 3. single-dot global-base `g.f = outer { i = inner{...}, ... }`
* 4. chained-dot local `o.x.y = outer { i = inner{...}, ... }`
* 5. chained-dot ptrroot `p.x.y = outer { i = inner{...}, ... }`
* 6. chained-dot global `g.x.y = outer { i = inner{...}, ... }`
*
* Fix (#18): the helper is extended into cg_structlit_fill /
* cgstructlitfill that takes a destination context (mode, srcoff,
* name, disp). The old BP-rel wrapper is preserved byte-identically.
* The four dot-flavor sites in each stage now delegate to the
* helper, which recurses on nested struct-typed N_STRUCTLIT values
* at `disp + foff` and reloads BX before each store for the non-BP
* modes. 995_self_rebuild byte-identity is the load-bearing proof
* that the no-nested case is byte-identical to the pre-#18 inline
* walks.
*
* Each row pins:
* - cstage value correctness (process exit code).
* - wwstage value correctness (when ww_ww exists).
* - cstage vs wwstage byte-identical .s output (catches drift).
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "wwtestpkg.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; };
static const struct row rows[] = {
/* single-dot local-base: `h.f = outer { i = inner{...}, ... }`.
* Pre-fix: h.f.i.a landed (first qword via cgexpr) but h.f.i.b
* silently stayed 0. Want: 7+8+12 = 27. */
{ "dot_local_lit_nested",
"type inner = struct { a: i64, b: i64 };\n"
"type outer = struct { i: inner, t: i64 };\n"
"type holder = struct { f: outer };\n"
"fn main() i32 = {\n"
" let h: holder;\n"
" h.f = outer { i = inner { a = 7i64, b = 8i64 }, t = 12i64 };\n"
" return (h.f.i.a + h.f.i.b + h.f.t): i32;\n"
"};\n",
27 },
/* single-dot local-base, 3-deep recursion (outer.m.in). Pins
* that the disp accumulator threads `foff + middle_foff +
* leaf_foff` correctly. Want: 1+2+3+4+9 = 19. */
{ "dot_local_lit_3deep",
"type leaf = struct { a: i64, b: i64 };\n"
"type middle = struct { in: leaf, t: i64 };\n"
"type outer = struct { m: middle, x: i64 };\n"
"type holder = struct { f: outer, pad: i64 };\n"
"fn main() i32 = {\n"
" let h: holder;\n"
" h.f = outer {\n"
" m = middle {\n"
" in = leaf { a = 1i64, b = 2i64 },\n"
" t = 3i64\n"
" },\n"
" x = 4i64\n"
" };\n"
" h.pad = 9i64;\n"
" return (h.f.m.in.a + h.f.m.in.b + h.f.m.t + h.f.x + h.pad): i32;\n"
"};\n",
19 },
/* single-dot ptr-base (auto-deref): `p.f = outer { i = inner{...}, ... }`.
* Exercises DST_PTR_LOCAL mode — helper reloads BX from
* srcoff(BP) before zero-fill and before every store. Want:
* 9+11+30 = 50. */
{ "dot_ptr_lit_nested",
"type inner = struct { a: i64, b: i64 };\n"
"type outer = struct { i: inner, t: i64 };\n"
"type holder = struct { f: outer };\n"
"fn main() i32 = {\n"
" let h: holder;\n"
" let p: *holder = &h;\n"
" p.f = outer { i = inner { a = 9i64, b = 11i64 }, t = 30i64 };\n"
" return (p.f.i.a + p.f.i.b + p.f.t): i32;\n"
"};\n",
50 },
/* single-dot global-base: `g.f = outer { i = inner{...}, ... }`.
* Exercises DST_GLOBAL mode — helper reloads BX via LEAQ
* name(SB) before zero-fill and before every store. `let g: T;`
* (no init) per the task #17 module-name-mangle workaround
* pattern. Want: 13+17+25 = 55. */
{ "dot_global_lit_nested",
"type inner = struct { a: i64, b: i64 };\n"
"type outer = struct { i: inner, t: i64 };\n"
"type holder = struct { f: outer };\n"
"let g: holder;\n"
"fn main() i32 = {\n"
" g.f = outer { i = inner { a = 13i64, b = 17i64 }, t = 25i64 };\n"
" return (g.f.i.a + g.f.i.b + g.f.t): i32;\n"
"};\n",
55 },
/* chained-dot local (depth-2 lhs): `h.mid.f = outer { i =
* inner{...}, ... }`. Exercises the chained-DOT walker with the
* non-via_cx (local-through-chain) path — disp = base_disp +
* total_off, no BX reload. Want: 4+5+13 = 22. */
{ "dot_chain_lit_nested",
"type inner = struct { a: i64, b: i64 };\n"
"type outer = struct { i: inner, t: i64 };\n"
"type holder = struct { f: outer };\n"
"type h2 = struct { mid: holder, pad: i64 };\n"
"fn main() i32 = {\n"
" let h: h2;\n"
" h.mid.f = outer { i = inner { a = 4i64, b = 5i64 }, t = 13i64 };\n"
" return (h.mid.f.i.a + h.mid.f.i.b + h.mid.f.t): i32;\n"
"};\n",
22 },
/* chained-dot ptrroot (depth-2 lhs through *struct root):
* `p.mid.f = outer { i = inner{...}, ... }`. Exercises the
* chained-DOT walker with ptrroot=1 — helper mode=1, reloads BX
* from rootoff(BP). Want: 6+7+14 = 27. */
{ "dot_chain_ptr_lit_nested",
"type inner = struct { a: i64, b: i64 };\n"
"type outer = struct { i: inner, t: i64 };\n"
"type holder = struct { f: outer };\n"
"type h2 = struct { mid: holder, pad: i64 };\n"
"fn main() i32 = {\n"
" let h: h2;\n"
" let p: *h2 = &h;\n"
" p.mid.f = outer { i = inner { a = 6i64, b = 7i64 }, t = 14i64 };\n"
" return (p.mid.f.i.a + p.mid.f.i.b + p.mid.f.t): i32;\n"
"};\n",
27 },
/* chained-dot global (depth-2 lhs through struct global):
* `gg.mid.f = outer { i = inner{...}, ... }`. Exercises the
* chained-DOT walker with isglobal=1 — helper mode=2, reloads
* BX via LEAQ gg(SB). Want: 10+20+31 = 61. */
{ "dot_chain_global_lit_nested",
"type inner = struct { a: i64, b: i64 };\n"
"type outer = struct { i: inner, t: i64 };\n"
"type holder = struct { f: outer };\n"
"type h2 = struct { mid: holder, pad: i64 };\n"
"let gg: h2;\n"
"fn main() i32 = {\n"
" gg.mid.f = outer { i = inner { a = 10i64, b = 20i64 }, t = 31i64 };\n"
" return (gg.mid.f.i.a + gg.mid.f.i.b + gg.mid.f.t): i32;\n"
"};\n",
61 },
/* single-dot ptr-base 3-deep — pins disp threading through the
* helper's recursion AND through DST_PTR_LOCAL reloads. Want:
* 1+2+3+4 = 10. */
{ "dot_ptr_lit_3deep",
"type leaf = struct { a: i64, b: i64 };\n"
"type middle = struct { in: leaf, t: i64 };\n"
"type outer = struct { m: middle, x: i64 };\n"
"type holder = struct { f: outer };\n"
"fn main() i32 = {\n"
" let h: holder;\n"
" let p: *holder = &h;\n"
" p.f = outer {\n"
" m = middle {\n"
" in = leaf { a = 1i64, b = 2i64 },\n"
" t = 3i64\n"
" },\n"
" x = 4i64\n"
" };\n"
" return (p.f.m.in.a + p.f.m.in.b + p.f.m.t + p.f.x): i32;\n"
"};\n",
10 },
};
/* run_driver — compile r->src via the given driver and exec; return
* the process exit code. Mirror of 703. */
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wcds_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/wcds_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wcds_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
wwtest_fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s", driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
* w6c_ww and diff. Mirror of 703. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/wcds_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/wcds_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/wcds_asm_%d_%d_w.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
wwtest_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "dot_structlit: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"dot_structlit[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"dot_structlit: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("dot_structlit: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,357 +0,0 @@
/*
* 705_nested_call_rhs — silent zero of nested struct-typed CALL
* value in the structlit-fill helper (task #20).
*
* Sister bug to #17 / #18. #17 introduced the
* `cg_structlit_fill[_bp]` / `cgstructlitfill[bp]` helper to handle
* nested N_STRUCTLIT field values at BP-rel + dot-lhs sites. #18
* extended it to the four N_ASSIGN N_DOT-lhs flavors (single-dot
* local/ptr/global, chained-dot through *struct/global). Both fixes
* targeted nested N_STRUCTLIT only.
*
* #20 covers the THIRD silent miscompile: a struct-typed field
* whose VALUE is itself an N_CALL (call returning a struct ≤24B per
* #4's AX/DX/CX cgreturn ABI). Pre-#20 the cgexpr-then-AX-store
* fallthrough inside the helper landed AX = first qword only and
* silently dropped the trailing bytes (DX/CX never made it to the
* destination).
*
* ```ww
* let o: outer = outer { m = mki(), x = 20i64 };
* // Pre-#20: o.m's first 8 bytes = AX from mki(); rest silently 0.
* ```
*
* Fix: a new per-field branch in the helper (between the nested-
* STRUCTLIT recursion and the scalar cgexpr-then-AX-store) detects
* `fu->kind == TY_STRUCT && f->lhs->kind == N_CALL` and emits the
* full AX/DX/CX → MOVQ x full + MOVL/MOVW/MOVB tail sequence per
* #4's receive shape. Guard `fsz <= 24 && fsz%8 ∈ {0,1,2,4}` mirrors
* #4 — >24B and fsz%8 ∈ {3,5,6,7} fall through (sret / shift-store
* not yet wired; tracked as a follow-up).
*
* Coverage:
* - tail dispatch: 8/16/24 (tail==0), 12 (MOVL tail==4),
* 10 (MOVW tail==2 — load-bearing), 9 (MOVB tail==1).
* - dst modes: BP-rel (N_LET initializer), PTR_LOCAL (`p.f = ...`
* where p: *holder). Global + chained dst already covered by
* #18's per-mode BX-reload tests; the call branch reuses the
* same reload cadence so a subset suffices.
* - depth: one shallow (call directly under outer literal), one
* 3-deep (call in a literal in a literal under outer) to pin
* `disp + foff` threading through the helper's recursion.
*
* Each row pins:
* - cstage value correctness (process exit code).
* - wwstage value correctness (when ww_ww exists).
* - cstage vs wwstage byte-identical .s output (catches drift).
* The MOVW-tail==2 row is the load-bearing one — that's where
* a divergence between the two stages' new branches is most
* plausible.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "wwtestpkg.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; };
static const struct row rows[] = {
/* BP-rel, inner struct 8B (one i64 field). fsz=8, full=1,
* tail=0 — AX only, no tail. Pre-#20: o.m.a still landed
* (it WAS in AX). Bug masked at this size. Want: 7+20 = 27. */
{ "bp_call_8b",
"type inner = struct { a: i64 };\n"
"type outer = struct { m: inner, x: i64 };\n"
"fn mki() inner = { return inner { a = 7i64 }; };\n"
"fn main() i32 = {\n"
" let o: outer = outer { m = mki(), x = 20i64 };\n"
" return (o.m.a + o.x): i32;\n"
"};\n",
27 },
/* BP-rel, inner struct 16B (two i64 fields). fsz=16, full=2,
* tail=0 — AX + DX, no tail. Pre-#20: o.m.b silently 0.
* Want: 7+8+20 = 35. */
{ "bp_call_16b",
"type inner = struct { a: i64, b: i64 };\n"
"type outer = struct { m: inner, x: i64 };\n"
"fn mki() inner = { return inner { a = 7i64, b = 8i64 }; };\n"
"fn main() i32 = {\n"
" let o: outer = outer { m = mki(), x = 20i64 };\n"
" return (o.m.a + o.m.b + o.x): i32;\n"
"};\n",
35 },
/* BP-rel, inner struct 24B (three i64 fields, the headline
* #4 ABI shape). fsz=24, full=3, tail=0 — AX + DX + CX.
* Pre-#20: o.m.b and o.m.c silently 0. Want: 1+2+3+20 = 26. */
{ "bp_call_24b",
"type inner = struct { a: i64, b: i64, c: i64 };\n"
"type outer = struct { m: inner, x: i64 };\n"
"fn mki() inner = { return inner { a = 1i64, b = 2i64, c = 3i64 }; };\n"
"fn main() i32 = {\n"
" let o: outer = outer { m = mki(), x = 20i64 };\n"
" return (o.m.a + o.m.b + o.m.c + o.x): i32;\n"
"};\n",
26 },
/* BP-rel, inner 12B (three i32 fields, maxalign=4 → no
* trailing pad). fsz=12, full=1, tail=4 — AX MOVQ + DX MOVL.
* Pins the MOVL-tail dispatch.
*
* The `x: i64` outer field forces outer.maxalign=8 so cstage
* and wwstage agree on outer.totsize (24). Without that,
* wwstage rounds outer.totsize up to 8 (task #15 pre-existing
* struct-sizing divergence) and the BP offsets in the asm
* diverge across stages. Sidestep, not a fix for #15. Want:
* 4+5+6+20 = 35. */
{ "bp_call_12b_movl_tail",
"type inner = struct { a: i32, b: i32, c: i32 };\n"
"type outer = struct { m: inner, x: i64 };\n"
"fn mki() inner = { return inner { a = 4, b = 5, c = 6 }; };\n"
"fn main() i32 = {\n"
" let o: outer = outer { m = mki(), x = 20i64 };\n"
" return (o.m.a: i64 + o.m.b: i64 + o.m.c: i64 + o.x): i32;\n"
"};\n",
35 },
/* BP-rel, inner 10B (five i16 fields, maxalign=2 → no
* trailing pad). fsz=10, full=1, tail=2 — AX MOVQ + DX MOVW.
* Rob's load-bearing row: pins MOVW emission on both stages.
* A divergence (cstage emits MOVL/MOVQ tail, wwstage emits
* MOVW) would fail the asm-diff. Same `x: i64` outer-maxalign
* sidestep as the 12B row. Want: 2+3+5+7+11+30 = 58.
*
* Note: the producer (mki's cgreturn) still uses the {1→MOVB,
* 4→MOVL, else MOVQ} field-store dispatch from #17 (task #13).
* For i16 fields this stomps 8 bytes per write, but write-
* order at monotonically increasing field offsets means each
* i16's low 2 bytes stay intact (later MOVQs only clobber
* higher offsets). Receive side reads only the low 2 bytes
* via MOVW tail — so the round-trip value is correct. */
{ "bp_call_10b_movw_tail",
"type inner = struct { a: i16, b: i16, c: i16, d: i16, e: i16 };\n"
"type outer = struct { m: inner, x: i64 };\n"
"fn mki() inner = {\n"
" return inner { a = 2i16, b = 3i16, c = 5i16, d = 7i16, e = 11i16 };\n"
"};\n"
"fn main() i32 = {\n"
" let o: outer = outer { m = mki(), x = 30i64 };\n"
" return (o.m.a: i64 + o.m.b: i64 + o.m.c: i64 + o.m.d: i64\n"
" + o.m.e: i64 + o.x): i32;\n"
"};\n",
58 },
/* BP-rel, inner 9B (nine i8 fields, maxalign=1 → no
* trailing pad). fsz=9, full=1, tail=1 — AX MOVQ + DX MOVB.
* Pins the MOVB-tail dispatch. Same `x: i64` outer-maxalign
* sidestep. Want: 1+2+3+4+5+6+7+8+9+40 = 85. */
{ "bp_call_9b_movb_tail",
"type inner = struct {\n"
" a: i8, b: i8, c: i8, d: i8, e: i8,\n"
" f: i8, g: i8, h: i8, i: i8\n"
"};\n"
"type outer = struct { m: inner, x: i64 };\n"
"fn mki() inner = {\n"
" return inner {\n"
" a = 1i8, b = 2i8, c = 3i8, d = 4i8, e = 5i8,\n"
" f = 6i8, g = 7i8, h = 8i8, i = 9i8\n"
" };\n"
"};\n"
"fn main() i32 = {\n"
" let o: outer = outer { m = mki(), x = 40i64 };\n"
" return (o.m.a: i64 + o.m.b: i64 + o.m.c: i64 + o.m.d: i64\n"
" + o.m.e: i64 + o.m.f: i64 + o.m.g: i64 + o.m.h: i64\n"
" + o.m.i: i64 + o.x): i32;\n"
"};\n",
85 },
/* PTR_LOCAL: `p.f = outer { m = mki(), x = ... }` where
* p: *holder. Exercises mode=DST_PTR_LOCAL — helper reloads
* BX from srcoff(BP) before the AX/DX/CX stores (cgexpr
* clobbers BX during the call). 16B inner pins the BX reload
* + 2-MOVQ store sequence. Want: 13+17+50 = 80. */
{ "ptrlocal_call_16b",
"type inner = struct { a: i64, b: i64 };\n"
"type outer = struct { m: inner, x: i64 };\n"
"type holder = struct { f: outer };\n"
"fn mki() inner = { return inner { a = 13i64, b = 17i64 }; };\n"
"fn main() i32 = {\n"
" let h: holder;\n"
" let p: *holder = &h;\n"
" p.f = outer { m = mki(), x = 50i64 };\n"
" return (p.f.m.a + p.f.m.b + p.f.x): i32;\n"
"};\n",
80 },
/* BP-rel 3-deep: call buried two levels under the outer
* literal. Pins that the `disp + foff` accumulator threads
* correctly through the helper's recursion into the
* call-rhs branch. Inner_in is 16B; middle wraps it +
* an i64; outer wraps middle + an i64. Want: 9+11+30+50 = 100. */
{ "bp_call_3deep_16b",
"type leaf = struct { a: i64, b: i64 };\n"
"type middle = struct { in: leaf, t: i64 };\n"
"type outer = struct { m: middle, x: i64 };\n"
"fn mki() leaf = { return leaf { a = 9i64, b = 11i64 }; };\n"
"fn main() i32 = {\n"
" let o: outer = outer {\n"
" m = middle { in = mki(), t = 30i64 },\n"
" x = 50i64\n"
" };\n"
" return (o.m.in.a + o.m.in.b + o.m.t + o.x): i32;\n"
"};\n",
100 },
};
/* run_driver — compile r->src via the given driver and exec; return
* the process exit code. Mirror of 703/704. */
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wcnc_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/wcnc_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wcnc_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
wwtest_fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s", driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
* w6c_ww and diff. Mirror of 703/704. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/wcnc_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/wcnc_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/wcnc_asm_%d_%d_w.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
wwtest_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "nested_call_rhs: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"nested_call_rhs[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"nested_call_rhs: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("nested_call_rhs: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,448 +0,0 @@
/*
* 707_cgreturn_variant_zero — tagged-return ABI variant-widen zero-pad
* for unused AX/DX/CX/R8 words (task #18) plus aliased-tagged variant-
* index lookup (task #20) plus float-variant payload-from-X0 pack
* (#157, rows 10-12): the same scalar-variant arm packed stale AX into
* the payload word for an f64/f32 value (which lives in X0), not the
* float bits — fixed by an X0->stack->DX spill bridge.
*
* Pre-fix (#18): cgreturn's `!istagged && !isstruct` variant-widen arm
* only filled the registers a given variant actually uses (scalar → DX;
* str → DX, CX; slice → DX, CX, R8). The unused AX/CX/R8 words were
* left holding whatever the caller's last write parked there. The
* receive side (cg_widen_tagged_store call-source arm) writes
* AX/DX/CX/R8 into the dst slot unconditionally, sized by the slot
* total — so stale registers landed at slot+16 / slot+24 and silently
* corrupted the tagged-union slot.
*
* The corruption only surfaced when the caller had primed CX/R8
* shortly before the call. Array / slice indexing emits
* `MOVQ $stride, CX; IMULQ CX, AX` — the canonical primer. Happens
* once per index, including in for-loop bodies. Hence the original
* "for-loop miscompile" framing; the bug is in fact context-free,
* but an index expression in any straight-line code triggers it
* just as well.
*
* Fix (#18): in cgreturn's variant-widen arm, after the variant-
* specific register shuffle and before the tag-into-AX, emit
* `MOVQ $0, CX` / `MOVQ $0, R8` for variants that don't naturally
* fill those words, conditional on the dst tagged-union slot size.
* Symmetric across cstage cgen.c and wwstage cgenstmt.ww.
*
* Pre-fix (#20): wwstage's `taggedvariantindex` checked `tagged.kind
* == N_TTAGGED` directly. For aliased return types
* (`type ft = (i64|str|bool); fn f() ft = ...`) c.fnret is N_TNAME,
* so the lookup punted to -1 → caller mapped to 0, silently emitting
* `MOVQ $0, AX` for every non-leading variant. Cstage was already
* correct (check.c resolves N_TNAME → underlying upfront).
*
* Fix (#20): resolve `tagged` via resolvetagged() (alias + TBANG
* unwrap) at entry to taggedvariantindex. Wwstage-only.
*
* What this test pins:
* - Every scalar-variant arm of a 24B tagged-union return zeroes
* slot+16 (CX) regardless of caller priming.
* - The str variant of a 24B slot still propagates .len correctly
* into slot+16 (the fix's `if (rsz > 24)` guard skips the R8
* zero for the 24B case, leaving CX/.len intact).
* - Cross-shape probes: straight-line array-index call, in-loop
* body call, nested-loop inner-body call.
* - Aliased tagged-return + N_IDENT source emits the declared-
* position variant tag (#20). Rows 7-9 pin tag=0 / tag=1 /
* tag=2 explicitly so a future variant reorder doesn't
* silently land back on the coincidentally-correct tag=0.
* - Cstage and wwstage emit byte-identical asm for every row,
* post #18 + #20 fixes. The bootstrap byte-identity invariant
* (995_self_rebuild) is the global guard; per-row diffs here
* surface focused regressions in cgreturn / taggedvariantindex.
*
* Test rows use `[N]i64` array indexing as the CX primer (well-
* supported on both stages) to dodge a pre-existing cstage/wwstage
* divergence in slice-creation reg scheduling.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "wwtestpkg.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; };
static const struct row rows[] = {
/* 1. Straight-line array-index then scalar-variant 24B-tagged
* call. Array indexing emits MOVQ $8, CX; IMULQ CX, AX — so CX
* carries 8 into the call. Pre-fix: slot+16 = 8. Post-fix: 0. */
{ "scalar_24B_after_arridx",
"type ft = (i64 | str | bool);\n"
"fn aski64(v: i64) ft = { return v; };\n"
"fn main() i32 = {\n"
" let arr: [3]i64 = [10i64, 20i64, 30i64];\n"
" let i: i32 = 1;\n"
" let v: i64 = arr[i];\n"
" let a: ft = aski64(v);\n"
" let p: u64 = (&a): u64;\n"
" let p2: *i64 = (p + 16u64): *i64;\n"
" let w: i64 = *p2;\n"
" return w: i32;\n"
"};\n",
0 },
/* 2. Bool variant of the same 24B slot. i64-array-index primer
* (well-supported shape) -> derive bool -> call -> probe slot+16.
* Pre-fix: stride leak. Post-fix: 0. */
{ "bool_24B_after_arridx",
"type ft = (i64 | str | bool);\n"
"fn askbool(b: bool) ft = { return b; };\n"
"fn main() i32 = {\n"
" let arr: [3]i64 = [1i64, 0i64, 1i64];\n"
" let i: i32 = 0;\n"
" let v: i64 = arr[i];\n"
" let b: bool = v != 0i64;\n"
" let a: ft = askbool(b);\n"
" let p: u64 = (&a): u64;\n"
" let p2: *i64 = (p + 16u64): *i64;\n"
" let w: i64 = *p2;\n"
" return w: i32;\n"
"};\n",
0 },
/* 3. Str variant of a 24B slot — sender already fills CX with
* the len, so slot+16 was correct pre-fix too. Sanity row: must
* keep working; the fix's `if (rsz > 24)` guard skips the R8
* zero for the 24B case, leaving CX (and slot+16) at len. The
* i64-array index primes CX; the str literal feeds askstr. */
{ "str_24B_after_arridx",
"type ft = (i64 | str | bool);\n"
"fn askstr(s: str) ft = { return s; };\n"
"fn main() i32 = {\n"
" let arr: [3]i64 = [10i64, 20i64, 30i64];\n"
" let i: i32 = 1;\n"
" let _v: i64 = arr[i];\n"
" let a: ft = askstr(\"world\");\n"
" let p: u64 = (&a): u64;\n"
" let p2: *i64 = (p + 16u64): *i64;\n"
" let w: i64 = *p2;\n"
" return w: i32;\n"
"};\n",
5 },
/* 4. In-loop body: scalar variant call. Same shape as row 1,
* but inside a `for (i < 3)` body where IMULQ primes CX
* on every iteration. Pre-fix: slot+16 leaks 8 every iter;
* sum-of-leaks > 0. Post-fix: 0. */
{ "scalar_24B_in_loop",
"type ft = (i64 | str | bool);\n"
"fn aski64(v: i64) ft = { return v; };\n"
"fn main() i32 = {\n"
" let arr: [3]i64 = [10i64, 20i64, 30i64];\n"
" let i: i32 = 0;\n"
" let bad: i32 = 0;\n"
" for (i < 3) {\n"
" let v: i64 = arr[i];\n"
" let a: ft = aski64(v);\n"
" let p: u64 = (&a): u64;\n"
" let p2: *i64 = (p + 16u64): *i64;\n"
" let w: i64 = *p2;\n"
" if (w != 0i64) { bad += 1; };\n"
" i += 1;\n"
" };\n"
" return bad;\n"
"};\n",
0 },
/* 5. Nested loop, inner-body scalar-variant call. Pins that the
* fix doesn't depend on loop depth. Each inner iter primes CX. */
{ "scalar_24B_in_nested_loop",
"type ft = (i64 | str | bool);\n"
"fn aski64(v: i64) ft = { return v; };\n"
"fn main() i32 = {\n"
" let arr: [2]i64 = [10i64, 20i64];\n"
" let i: i32 = 0;\n"
" let bad: i32 = 0;\n"
" for (i < 2) {\n"
" let j: i32 = 0;\n"
" for (j < 2) {\n"
" let v: i64 = arr[j];\n"
" let a: ft = aski64(v);\n"
" let p: u64 = (&a): u64;\n"
" let p2: *i64 = (p + 16u64): *i64;\n"
" let w: i64 = *p2;\n"
" if (w != 0i64) { bad += 1; };\n"
" j += 1;\n"
" };\n"
" i += 1;\n"
" };\n"
" return bad;\n"
"};\n",
0 },
/* 6. Mixed loop: alternating str / i64 variants — pins that the
* scalar-variant zero doesn't regress the str-variant CX fill
* across iterations. iter-0 stores str (slot+16 = 5), iter-1
* stores i64 (slot+16 = 0). bad counts slot+16 != expected. */
{ "mixed_variants_in_loop",
"type ft = (i64 | str | bool);\n"
"fn askstr(s: str) ft = { return s; };\n"
"fn aski64(v: i64) ft = { return v; };\n"
"fn main() i32 = {\n"
" let arr: [2]i64 = [10i64, 20i64];\n"
" let i: i32 = 0;\n"
" let bad: i32 = 0;\n"
" for (i < 2) {\n"
" let _v: i64 = arr[i];\n"
" let a: ft;\n"
" let expect: i64 = 0i64;\n"
" if (i == 0) { a = askstr(\"world\"); expect = 5i64; };\n"
" if (i == 1) { a = aski64(99i64); expect = 0i64; };\n"
" let p: u64 = (&a): u64;\n"
" let p2: *i64 = (p + 16u64): *i64;\n"
" let w: i64 = *p2;\n"
" if (w != expect) { bad += 1; };\n"
" i += 1;\n"
" };\n"
" return bad;\n"
"};\n",
0 },
/* 7-9: task #20 variant-index pins. Aliased tagged return plus
* N_IDENT source on each variant arm — probes slot+0 (the tag
* word). Pre-#20 wwstage's taggedvariantindex saw c.fnret as
* N_TNAME("ft") and short-circuited to -1 → tag = 0 for every
* row. Post-#20 the resolvetagged unwrap at fn entry surfaces
* the underlying N_TTAGGED so the declared-position index is
* emitted. Pinning all three positions (0/1/2) catches a future
* variant-reorder regression that would otherwise hide behind
* row 7's coincidentally-correct tag=0. */
{ "aliased_tagged_aski64_idx0",
"type ft = (i64 | str | bool);\n"
"fn aski64(v: i64) ft = { return v; };\n"
"fn main() i32 = {\n"
" let v: i64 = 7i64;\n"
" let a: ft = aski64(v);\n"
" let p: u64 = (&a): u64;\n"
" let pt: *i64 = p: *i64;\n"
" return (*pt): i32;\n"
"};\n",
0 },
{ "aliased_tagged_askstr_idx1",
"type ft = (i64 | str | bool);\n"
"fn askstr(s: str) ft = { return s; };\n"
"fn main() i32 = {\n"
" let s: str = \"world\";\n"
" let a: ft = askstr(s);\n"
" let p: u64 = (&a): u64;\n"
" let pt: *i64 = p: *i64;\n"
" return (*pt): i32;\n"
"};\n",
1 },
{ "aliased_tagged_askbool_idx2",
"type ft = (i64 | str | bool);\n"
"fn askbool(b: bool) ft = { return b; };\n"
"fn main() i32 = {\n"
" let b: bool = true;\n"
" let a: ft = askbool(b);\n"
" let p: u64 = (&a): u64;\n"
" let pt: *i64 = p: *i64;\n"
" return (*pt): i32;\n"
"};\n",
2 },
/* 10-12: #157 — float variant of the same `!istagged && !isstruct`
* return arm. Pre-fix it did `MOVQ AX, DX` even when the value was
* a float in X0 (no MOVQ-xmm->gp form), so the payload word held
* stale AX, not the float bits. Fix spills X0 through a stack slot
* (zero-slot-first so the f32 MOVSS low-4 write leaves a determin-
* istic high-4). Probe slot+8 as the float directly (bit-exact
* f64/f32 equality) — isolates the return-pack from the match-
* receive. The f32 row avoids f32 ARGS (pre-existing #143 f32-arg
* MOVSD/MOVSS divergence is unrelated; f32 here comes from a global
* with an int arg, so the byte-id check pins THIS fix). */
{ "f64_variant_return_payload",
"type ft = (f64 | i32);\n"
"fn addf(a: f64, b: f64) ft = { return a + b; };\n"
"fn main() i32 = {\n"
" let a: ft = addf(1.5, 2.0);\n"
" let p: u64 = (&a): u64;\n"
" let pf: *f64 = (p + 8u64): *f64;\n"
" let fv: f64 = *pf;\n"
" if (fv == 3.5) { return 1; };\n"
" return 0;\n"
"};\n",
1 },
{ "f32_variant_return_payload",
"let g: f32 = 1.5f32;\n"
"type ft = (f32 | i32);\n"
"fn mkf(k: i32) ft = { if (k > 0) { return g + 2.0f32; }; return k; };\n"
"fn main() i32 = {\n"
" let a: ft = mkf(1);\n"
" let p: u64 = (&a): u64;\n"
" let pf: *f32 = (p + 8u64): *f32;\n"
" let fv: f32 = *pf;\n"
" if (fv == 3.5f32) { return 1; };\n"
" return 0;\n"
"};\n",
1 },
{ "f64_variant_return_multivariant",
"type ft = (f64 | i32 | bool);\n"
"fn mulf(a: f64, b: f64) ft = { return a * b; };\n"
"fn main() i32 = {\n"
" let a: ft = mulf(2.5, 4.0);\n"
" let p: u64 = (&a): u64;\n"
" let pf: *f64 = (p + 8u64): *f64;\n"
" let fv: f64 = *pf;\n"
" if (fv == 10.0) { return 1; };\n"
" return 0;\n"
"};\n",
1 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wcrv_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/wcrv_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wcrv_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
wwtest_fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s", driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's w6c_ww
* and diff. Post #18 + #20 fixes the per-row asm matches byte-for-byte
* across stages; the bootstrap byte-identity (995_self_rebuild) covers
* the broader cross-stage drift surface globally. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/wcrv_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/wcrv_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/wcrv_asm_%d_%d_w.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
wwtest_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[512];
if (bin[0] != '/') {
char cwd[256];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[640];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[640];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "cgreturn_variant_zero: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"cgreturn_variant_zero[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
/* Asm byte-identity diff, only when both stages exist. */
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"cgreturn_variant_zero: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("cgreturn_variant_zero: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,487 +0,0 @@
/*
* 710_cast_enum_movl — cstage and wwstage agree byte-for-byte on the
* N_CAST narrow-clamp under the principled identity-width identity-
* sign predicate (task #33). Extended from the original #25 fixture
* which mirrored wwstage's N_TENUM lacuna as a single-site `tu->kind
* == TY_ENUM` gate in cstage.
*
* Predicate (both stages):
* skip the narrow-clamp on an int→int cast iff
* src.width == dst.width && src.signed == dst.signed
* where (width, signedness) resolve through TY_NAMED / TY_ENUM
* alias chains in cstage and N_TBANG / N_TENUM / N_TNAME-alias
* chains in wwstage. Bool keeps its dedicated ANDQ $255 contract.
*
* History: #25 (b5632b1) shipped a single-site gate in cstage —
* `dst_is_enum → skip` — that made cstage byte-for-byte identical
* to wwstage on a u32→enum-u32 cast. It also inadvertently kept a
* silent miscompile alive: u32→enum-u8 and i64→enum-i32 also took
* the dst-is-enum exit, so the narrow-clamp didn't fire on a
* genuinely-width-narrowing cast and the upper bits of the source
* value leaked into any register-chained downstream use (the slot
* store happens to mask via MOVB/MOVL of the dst width, so program
* semantics looked right unless the result was consumed by a
* register-chained outer cast / arithmetic).
*
* Surfaced by worker-stat during #10: when kstat.mode was first
* typed as raw `u32`, `out.mode = k.mode` parsed as a u32→enum-u32
* cast via `fs.mode`, and the cstage→wwstage asm divergence broke
* 993_ww_ww + 995_self_rebuild on the first selfhost pass. The
* workaround that was in tree (lib/os/os.ww kstat.mode: mode) has
* already been retired by #25's single-site fix; #33 generalises
* the gate.
*
* row | shape | gate
* --------------------+--------------------------------------+----------
* u32_to_enum_u32 | `let y: m = x: m;` with m=enum u32. | exit=7
* | Identity (4B/unsigned). Both stages | + byte-id
* | skip — no clamp. |
* enum_u32_to_u32 | reverse: `let z: u32 = y: u32;`. | exit=7
* | Also identity (4B/unsigned, walker | + byte-id
* | now resolves `mymode` through |
* | aliaslookup to u32). Both skip — |
* | flips from #25's clamp-emit. |
* u32_to_enum_u8 | dst is enum u8. Width narrows 4→1, | exit=7
* | so identity is false. Both stages | + byte-id
* | now emit ANDQ $0xFF — flips from |
* | #25's skip. Fixes the silent leak |
* | (see u32_to_enum_u8_truncate below). |
* i64_to_enum_i32 | signed-narrow: dst is enum i32. | exit=7
* | Width narrows 8→4 → identity false. | + byte-id
* | Both stages emit MOVSXD AX, AX — |
* | flips from #25's skip. Fixes the |
* | silent leak (see |
* | i64_to_enum_i32_truncate below). |
* struct_field_rt | mirror of lib/os fillfilestat: a u32 | exit=7
* | struct field copied into an enum-u32 | + byte-id
* | field by chained N_DOT. Identity |
* | (4B/unsigned). Both skip. |
* u32_u32_identity | `let y: u32 = x: u32;` with src=u32. | exit=7
* | Trivial identity. Both stages skip; | + byte-id
* | pre-#33 they emitted a redundant |
* | MOVL AX, AX. |
* i32_i32_identity | same shape, src/dst i32. Pre-#33 | exit=7
* | both emitted MOVSXD AX, AX. Now | + byte-id
* | skip. |
* u8_u8_identity | u8 → u8. Pre-#33 ANDQ $0xFF. Now | exit=7
* | skip. | + byte-id
* i8_i8_identity | i8 → i8. Pre-#33 MOVSBQ AX, AX. | exit=7
* | Now skip. | + byte-id
* u16_u16_identity | u16 → u16. Pre-#33 ANDQ $0xFFFF. | exit=7
* | Now skip. | + byte-id
* i16_i16_identity | i16 → i16. Pre-#33 MOVSWQ AX, AX. | exit=7
* | Now skip. | + byte-id
* u32_to_i32_signchg | width equal, signedness differs. | exit=7
* | Identity is FALSE → narrow-clamp | + byte-id
* | MUST fire. Both stages emit MOVSXD |
* | (dst is signed-narrow). Pin against |
* | future refactors that mis-broaden |
* | the skip. |
* u32_to_enum_u8_trnc | exit-code-validating silent- | exit=0
* | miscompile fix. x=0xFFFFu32 cast to | + byte-id
* | enum-u8, then to u32, then divided |
* | by 0x100. Post-#33 the inner cast |
* | clamps to 0xFF, divide yields 0; |
* | pre-#33 the upper bits leaked |
* | (AX=0xFFFF), divide yielded 0xFF. |
* i64_to_enum_i32_trnc| same shape on i64 → enum-i32. | exit=0
* | x=0x100000000i64 cast to enum-i32, | + byte-id
* | then to i64, divided by 0x100000000. |
* | Post-#33 MOVSXD takes low 32 bits |
* | (0), divide yields 0; pre-#33 the |
* | high 32 bits leaked, divide |
* | yielded 1. |
*
* Cstage exit-code rows confirm the binary still runs correctly
* post-#33. The asm-byte-id rows pin the symmetric-emit contract.
* The `*_trnc` rows are the regression-pinning ones for the
* silent-miscompile fix that #25's dst-kind-only skip left in
* place. ww3!=ww4 byte-id (995_self_rebuild) covers a broader
* surface but doesn't isolate this corner.
*
* Note: removing the defensive MOVL exposes any upstream cgen path
* that leaves garbage in upper RAX when producing a sub-word value.
* If a future test goes red post-#33, the contract is violated
* somewhere — fix the upstream producer, do NOT reinstate the
* defensive clamp.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "wwtestpkg.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; };
static const struct row rows[] = {
/* 1. Headline #25 case: u32 → enum-u32. Pre-fix cstage emitted
* `MOVL AX, AX` after the slot load; wwstage skipped. The cast
* is a no-op at runtime so program semantics is unchanged
* either way — exit code stays 7 regardless. The asm-byte-id
* row is what catches the regression. */
{ "u32_to_enum_u32",
"type mymode = enum u32 { A = 1u32, B = 2u32 };\n"
"fn main() i32 = {\n"
"\tlet x: u32 = 7u32;\n"
"\tlet y: mymode = x: mymode;\n"
"\treturn (y: u32): i32;\n"
"};\n",
7 },
/* 2. Reverse direction: enum-u32 → u32. Post-#33 both stages
* walk `mymode` through aliaslookup to u32, see (src u32, dst
* u32, both unsigned), and skip the narrow-clamp under the
* identity-width identity-sign predicate. Flips from #25's
* clamp-emit. Exit code unchanged at 7. */
{ "enum_u32_to_u32",
"type mymode = enum u32 { A = 1u32 };\n"
"fn main() i32 = {\n"
"\tlet y: mymode = 7u32: mymode;\n"
"\tlet z: u32 = y: u32;\n"
"\treturn z: i32;\n"
"};\n",
7 },
/* 3. Different enum width: u32 → enum-u8. Post-#33 both stages
* emit ANDQ $0xFF because identity is false (src 4B, dst 1B).
* Flips from #25's dst-is-enum skip. The slot write masks via
* MOVB so program semantics with `7` reads back as 7 either
* way; the silent-miscompile case (upper bits leaking into
* register-chained downstream use) is pinned by
* u32_to_enum_u8_trnc below. */
{ "u32_to_enum_u8",
"type small = enum u8 { A = 1u8 };\n"
"fn main() i32 = {\n"
"\tlet x: u32 = 7u32;\n"
"\tlet y: small = x: small;\n"
"\treturn (y: u32): i32;\n"
"};\n",
7 },
/* 4. Signed-narrow path: i64 → enum-i32. Post-#33 both stages
* emit MOVSXD AX, AX (identity false: src 8B, dst 4B). Flips
* from #25's skip. Slot is read with MOVSXD downstream so the
* sign-extension is recovered on use; silent leak through a
* register-chained outer cast is pinned by
* i64_to_enum_i32_trnc below. */
{ "i64_to_enum_i32",
"type sflag = enum i32 { A = 1i32 };\n"
"fn main() i32 = {\n"
"\tlet x: i64 = 7i64;\n"
"\tlet y: sflag = x: sflag;\n"
"\treturn (y: i32);\n"
"};\n",
7 },
/* 5. Mirror of lib/os fillfilestat: struct field of one type
* copied into an enum-typed field of another struct via
* chained N_DOT. Identity (4B/unsigned on both sides) → both
* stages skip the clamp. Pre-#25 this blew up 993_ww_ww +
* 995_self_rebuild on the first selfhost pass. */
{ "struct_field_rt",
"type mymode = enum u32 { A = 1u32 };\n"
"type src = struct { mode: u32 };\n"
"type dst = struct { mode: mymode };\n"
"fn main() i32 = {\n"
"\tlet a: src = src { mode = 7u32 };\n"
"\tlet b: dst;\n"
"\tb.mode = a.mode: mymode;\n"
"\treturn (b.mode: u32): i32;\n"
"};\n",
7 },
/* 6-11. Identity-width identity-sign rows. Pre-#33 the cast
* always emitted a clamp for sub-8B dst (MOVL/ANDQ/MOVSBQ/
* MOVSWQ/MOVSXD depending on width and signedness); post-#33
* all six skip because src and dst share the underlying
* primitive. Asm byte-id pins the contract. */
{ "u32_u32_identity",
"fn main() i32 = {\n"
"\tlet x: u32 = 7u32;\n"
"\tlet y: u32 = x: u32;\n"
"\treturn y: i32;\n"
"};\n",
7 },
{ "i32_i32_identity",
"fn main() i32 = {\n"
"\tlet x: i32 = 7i32;\n"
"\tlet y: i32 = x: i32;\n"
"\treturn y;\n"
"};\n",
7 },
{ "u8_u8_identity",
"fn main() i32 = {\n"
"\tlet x: u8 = 7u8;\n"
"\tlet y: u8 = x: u8;\n"
"\treturn (y: u32): i32;\n"
"};\n",
7 },
{ "i8_i8_identity",
"fn main() i32 = {\n"
"\tlet x: i8 = 7i8;\n"
"\tlet y: i8 = x: i8;\n"
"\treturn (y: i32);\n"
"};\n",
7 },
{ "u16_u16_identity",
"fn main() i32 = {\n"
"\tlet x: u16 = 7u16;\n"
"\tlet y: u16 = x: u16;\n"
"\treturn (y: u32): i32;\n"
"};\n",
7 },
{ "i16_i16_identity",
"fn main() i32 = {\n"
"\tlet x: i16 = 7i16;\n"
"\tlet y: i16 = x: i16;\n"
"\treturn (y: i32);\n"
"};\n",
7 },
/* 12. Width-equal sign-change: u32 → i32. Identity is FALSE
* (signedness differs) so the clamp MUST still emit (MOVSXD
* because dst is signed-narrow). Asm byte-id pins this
* against future refactors that mis-broaden the identity
* skip. Exit code 7 is just the value round-tripping. */
{ "u32_to_i32_signchg",
"fn main() i32 = {\n"
"\tlet x: u32 = 7u32;\n"
"\tlet y: i32 = x: i32;\n"
"\treturn y;\n"
"};\n",
7 },
/* 13. Silent-miscompile fix, u32 → enum-u8. Pre-#33 the
* b5632b1 dst-is-enum skip left the upper bits of the u32
* source in AX. With register-chained downstream use (no slot
* spill between the inner cast and the outer expression), the
* leak survives. Probe: start with x=0xFFFFu32, cast to
* enum-u8 (should clamp to 0xFF), cast to u32, divide by
* 0x100. Post-#33 the inner clamp leaves AX=0xFF and the
* divide yields 0; pre-#33 AX stayed 0xFFFF and the divide
* yielded 0xFF. Exit code distinguishes (0 vs 255). */
{ "u32_to_enum_u8_trnc",
"type small = enum u8 { A = 1u8 };\n"
"fn main() i32 = {\n"
"\tlet x: u32 = 0xFFFFu32;\n"
"\tlet r: u32 = ((x: small): u32) / 0x100u32;\n"
"\treturn r: i32;\n"
"};\n",
0 },
/* 14. Silent-miscompile fix, i64 → enum-i32. Same shape on
* the signed-narrow path. x=0x100000000i64 (bit 32 set, low
* 32 bits zero). Post-#33 the MOVSXD takes the low 32 bits
* (0), AX=0, divide by 0x100000000 yields 0. Pre-#33 the
* clamp was skipped, AX stayed 0x100000000, divide yielded
* 1. Exit code distinguishes (0 vs 1). */
{ "i64_to_enum_i32_trnc",
"type sflag = enum i32 { A = 1i32 };\n"
"fn main() i32 = {\n"
"\tlet x: i64 = 0x100000000i64;\n"
"\tlet r: i64 = ((x: sflag): i64) / 0x100000000i64;\n"
"\treturn r: i32;\n"
"};\n",
0 },
/* 15. Pseudo-field defensive-clamp pin. Source is `s.len`, a
* str header pseudo-field — neither stage's source-type
* resolver recognises it (cstage's `castsrcprim` gates the
* N_DOT branch on `bu->kind == TY_STRUCT`; wwstage's
* `exprprimresolved` routes through `dotfieldtnode` which
* returns nil for non-struct base). Both fall back to sz=0,
* identity is false, the narrow-clamp emits (MOVSXD here
* because dst is signed-narrow i32). Pinning byte-id on this
* row catches a future refactor that wires pseudo-field
* inference asymmetrically into one stage — the kind of drift
* that would silently break 995_self_rebuild without naming
* the corner. Exit code 7 = round-trip of the literal len. */
{ "pseudo_field_clamp",
"fn main() i32 = {\n"
"\tlet s: str = \"abcdefg\";\n"
"\tlet n: i32 = s.len: i32;\n"
"\treturn n;\n"
"};\n",
7 },
/* 16. Bool source clamp pin. Source is a bool local, dst is i8.
* Width matches (1B) but bool is excluded from the int-prim
* contract on both stages (cstage's `type_isint(TY_BOOL)` is
* false; wwstage's `typenodeprimresolved` has an explicit
* `streq(nm, "bool") → return` early-out). So identity is
* never true on a bool source: the narrow-clamp emits
* (MOVSBQ AX, AX because dst is i8, signed-narrow). Without
* the bool early-out in wwstage, `primsize("bool")=1` and
* `typenameisunsigned("bool")=false` made wwstage see
* (sz=1, unsigned=false) and fire identity on bool→i8 while
* cstage emitted MOVSBQ — silent asm asymmetry that no other
* row exercises. Mirrors the `*_trnc` rows' pattern: the row
* pins the clamp emit, not just the exit code. */
{ "bool_to_i8_clamp",
"fn main() i32 = {\n"
"\tlet b: bool = true;\n"
"\tlet y: i8 = b: i8;\n"
"\treturn (y: i32);\n"
"};\n",
1 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/cem_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/cem_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/cem_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
wwtest_fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
* w6c_ww and diff. This is the regression-pinning row for #25; an
* exit-code-only comparison wouldn't catch a redundant MOVL drift
* because the program semantics is unchanged. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/cem_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/cem_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/cem_asm_%d_%d_w.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
wwtest_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "cast_enum_movl: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"cast_enum_movl[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
/* Asm byte-identity diff, only when wwstage is built. This is
* the row that pins the #25 fix. */
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"cast_enum_movl: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("cast_enum_movl: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,282 +0,0 @@
/*
* 716_match_spill_pointer_payload — wwstage @match_spill scratch slot
* sized to the scrutinee's tagged-union slot, not a hardcoded 24B
* default. Surfaced during task #23 (lib/os path *u8 → str migration)
* where `kpath` originally returned `(*u8 | oserror)`.
*
* Pre-fix (#9): cgmatch's non-ident scrutinee spill defaulted spillsz
* to 24 and only grew it; cgendecl scanlocals mirrored with a hardcoded
* `total += 24`. For a 1-word-payload tagged union (`(*u8 | oserror)`
* with `type oserror = !i64`) the actual slot is 16B (tag + one 8B
* word) — cstage allocated $48 / 16B slot / 2-word ABI (AX=tag, DX),
* wwstage allocated $64 / 24B slot / 3-word ABI (AX=tag, DX, CX).
* Frame-size drift broke bootstrap byte-identity the moment any caller
* matched on such a return.
*
* Fix (#9, wwstage-only per rule 10): factor scrutinee-type resolution
* into matchscrutt; factor slot-size computation into matchspillsz
* (default 16, mirroring cmd/w6c/cgen.c cgmatch's `slot_size = (su->
* kind == TY_TAGGED) ? su->size : 16`). cgmatch gates CX write on
* `spillsz > 16` (R8 gate `> 24` was already correct). scanlocals
* uses the same helpers so scan + emit stay lockstep.
*
* What this test pins:
* - Asm byte-identity between cstage and wwstage for the canonical
* `(*u8 | oserror)` shape (aliased !i64) and the raw `(*u8 | i64)`
* shape — both must produce $48 frame, 16B spill slot, 2-word
* return ABI.
* - Runtime: both arms (pointer / error) round-trip the payload
* correctly through the narrower spill.
* - 24B-slot regression guard: `(str | i64)` (max payload 16B, slot
* 24B) still byte-identical post-fix — the gate must keep the CX
* write for slot_size > 16.
*
* NOT covered: raw `(*u8 | !i64)` (no alias) — wwstage's variant-index
* resolution for the raw N_TBANG-in-union shape has a separate pre-
* existing tag-emit divergence from cstage (tag=0 vs tag=1) that is
* orthogonal to the spill-slot sizing fixed by #9. Documented at the
* probe .ai/probe_tagged_return_pointer_payload.ww.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "wwtestpkg.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; };
static const struct row rows[] = {
/* 1. Canonical `(*u8 | oserror)` (aliased !i64). Pointer arm
* returns the first byte through a str-literal `.ptr`. Avoiding
* a top-level `let buf: [16]u8;` sidesteps an orthogonal wwstage
* DATAW-emit divergence for module-scope uninit `[N]u8`. */
{ "ptr_payload_aliased_bang_ok",
"type oserror = !i64;\n"
"fn kpath(p: str) (*u8 | oserror) = {\n"
" if (p.len < 0) { return -36i64: oserror; };\n"
" let s: str = \"A\";\n"
" return s.ptr;\n"
"};\n"
"fn main() i32 = {\n"
" let q: str = \"x\";\n"
" match (kpath(q)) {\n"
" case let e: oserror => return (e: i64): i32;\n"
" case let p: *u8 => return p[0]: i32;\n"
" };\n"
" return 0;\n"
"};\n",
65 },
/* 2. Same shape, error arm: pre-fix the 24B spill clobbered CX
* (irrelevant tag-only path) and bumped the frame; this row
* pins the error arm round-trips -36 + 100 = 64. */
{ "ptr_payload_aliased_bang_err",
"type oserror = !i64;\n"
"fn kpath(p: str) (*u8 | oserror) = {\n"
" if (p.len >= 0) { return -36i64: oserror; };\n"
" let s: str = \"A\";\n"
" return s.ptr;\n"
"};\n"
"fn main() i32 = {\n"
" let q: str = \"x\";\n"
" match (kpath(q)) {\n"
" case let e: oserror => return ((e: i64): i32) + 100;\n"
" case let p: *u8 => return p[0]: i32;\n"
" };\n"
" return 0;\n"
"};\n",
64 },
/* 3. Raw `(*u8 | i64)` — no alias, no `!`. Hypothesis (b) probe:
* the spill-slot fix holds regardless of whether the payload is
* wrapped in a type alias. */
{ "ptr_payload_raw_i64_ok",
"fn kpath(p: str) (*u8 | i64) = {\n"
" if (p.len < 0) { return -36i64; };\n"
" let s: str = \"Z\";\n"
" return s.ptr;\n"
"};\n"
"fn main() i32 = {\n"
" let q: str = \"x\";\n"
" match (kpath(q)) {\n"
" case let e: i64 => return e: i32;\n"
" case let p: *u8 => return p[0]: i32;\n"
" };\n"
" return 0;\n"
"};\n",
90 },
/* 4. 24B-slot regression guard. `(str | i64)` slot = 8 tag +
* 16 str = 24. The CX write at slot+16 is required (carries
* .len for the str arm). Pre-fix this row passed; post-fix the
* `spillsz > 16` gate must keep CX. Returns s.len for "hello"
* = 5. */
{ "str_payload_24B_keeps_cx",
"fn pick(b: bool) (str | i64) = {\n"
" if (b) { return \"hello\"; };\n"
" return 7i64;\n"
"};\n"
"fn main() i32 = {\n"
" match (pick(true)) {\n"
" case let e: i64 => return (e: i32) + 200;\n"
" case let s: str => return s.len: i32;\n"
" };\n"
" return 0;\n"
"};\n",
5 },
};
static int
run_driver(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/wcmsp_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/wcmsp_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wcmsp_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
wwtest_fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s", driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — pin frame size + spill layout by diffing the
* w6c vs w6c_ww text output. The whole point of #9 is that wwstage's
* frame stops bloating for 1-word-payload variants, so the bytes
* must match (modulo orthogonal divergences — none on these rows). */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/wcmsp_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/wcmsp_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/wcmsp_asm_%d_%d_w.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
wwtest_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[512];
if (bin[0] != '/') {
char cwd[256];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[640];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[640];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "match_spill_pointer_payload: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"match_spill_pointer_payload[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"match_spill_pointer_payload: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("match_spill_pointer_payload: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,284 +0,0 @@
/*
* 717_struct_byval_param — wwstage param-spill ABI for user-defined
* by-value struct parameters of size ≤ 16B. Sister to #9 (which
* fixed an OVER-allocation in the match-scrutinee spill path);
* this is the UNDER-allocation in the cgfn param-spill path.
*
* Pre-fix (#11): wwstage cgfnparams dispatched on TK_ELLIPSIS,
* float, tagged, slice, str — then a catch-all 8B scalar arm.
* User-defined struct params (TY_STRUCT after collectstructs)
* had no branch and fell through to the 8B arm. For
* `fn cmp(a: inst, b: inst)` with `inst = struct { sec: i64,
* nsec: i64 }` cstage allocated `TEXT cmp,$32` and spilled all
* four argregs DI/SI/DX/CX; wwstage allocated `TEXT cmp,$16`
* and spilled only DI/SI — the second-half value words of each
* arg (DX/CX) were never stored, and `b.sec`/`b.nsec` reads
* trailed into the saved-BP word. Built-in `str` (also 16B,
* ptr+len) routed correctly through the isstrtype arm, so the
* bug was a TY_STRUCT type-dispatch miss, not a missing path.
*
* Fix (#11, wwstage-only per rule 10): add structparamsize in
* cgenutil.ww (mirror cstage cgen.c struct_arg_size: returns the
* struct's totsize for sizes in (0, 16], else 0). Add a struct
* arm in cgfnparams between the str arm and the 8B catch-all,
* matching cstage's struct_eb = (size > 8) ? 2 : 1 — including
* the partial-fit stitch arm (idx=5, nw=2: one reg, one stack
* tail). Mirror the same isstr branch in cgfn's pre-scan so the
* frame reservation and emit stay lockstep.
*
* What this test pins:
* - Asm byte-identity between cstage and wwstage for 2×16B
* struct params, 1×16B struct + 1×8B struct (mixed
* eightbyte counts), 1×16B struct + 1×str (regression guard
* for the already-working str path), and 6 i64 + 1 16B
* struct (partial-fit stitch case).
* - Runtime end-to-end: each row's main returns a deterministic
* exit code derived from the received struct's fields. A
* regression in the spill ABI (e.g. CX→slot+8 missing) shows
* up as a `got != want` exit-code mismatch.
*
* NOT covered:
* - Structs > 16B by-value param: cstage's struct_arg_size
* itself gates on size <= 16, so a 24B-struct param also
* under-allocates in cstage (falls through to the 8B arm).
* Both stages agree on the wrong behaviour, so it's bug-
* compatible byte-identical; that's a separate task.
* - Single-field i32-or-smaller struct: still 8B totsize after
* wwstage's registerstruct rounds up, equivalent to a scalar
* i64 in the spill path. The 1-field i64 row exercises the
* nw=1 branch.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "wwtestpkg.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; };
static const struct row rows[] = {
/* 1. Canonical 2×16B struct shape (the original probe). The
* comparator reads b.sec — that's the second-arg slot+0, which
* pre-fix sat unwritten and aliased the caller's saved BP. */
{ "two_16B_struct_cmp",
"type inst = struct { sec: i64, nsec: i64 };\n"
"fn cmp(a: inst, b: inst) i32 = {\n"
" if (a.sec < b.sec) { return -1; };\n"
" if (a.sec > b.sec) { return 1; };\n"
" if (a.nsec < b.nsec) { return -1; };\n"
" if (a.nsec > b.nsec) { return 1; };\n"
" return 0;\n"
"};\n"
"fn main() i32 = {\n"
" let x: inst = inst { sec = 5i64, nsec = 200i64 };\n"
" let y: inst = inst { sec = 5i64, nsec = 100i64 };\n"
" let r: i32 = cmp(x, y);\n"
" if (r > 0) { return 7; };\n"
" return 0;\n"
"};\n",
7 },
/* 2. 16B struct + 8B struct (mixed eightbyte counts). a uses
* DI/SI (2 eb), b uses DX (1 eb). Returns a.sec + a.nsec + b.v
* = 10 + 20 + 30 = 60. */
{ "mixed_16B_8B_struct",
"type two = struct { sec: i64, nsec: i64 };\n"
"type one = struct { v: i64 };\n"
"fn sum(a: two, b: one) i32 = {\n"
" return (a.sec + a.nsec + b.v): i32;\n"
"};\n"
"fn main() i32 = {\n"
" let x: two = two { sec = 10i64, nsec = 20i64 };\n"
" let y: one = one { v = 30i64 };\n"
" return sum(x, y);\n"
"};\n",
60 },
/* 3. 16B struct + 16B str (regression guard). The pre-fix
* wwstage already handled str via the isstrtype arm; this row
* pins that the new struct arm doesn't shadow it. struct in
* DI/SI, str in DX/CX. Returns a.sec + s.len = 42 + 3 = 45. */
{ "struct_plus_str",
"type two = struct { sec: i64, nsec: i64 };\n"
"fn pick(a: two, s: str) i32 = {\n"
" return (a.sec + (s.len: i64)): i32;\n"
"};\n"
"fn main() i32 = {\n"
" let x: two = two { sec = 42i64, nsec = 0i64 };\n"
" return pick(x, \"abc\");\n"
"};\n",
45 },
/* 4. Partial-fit stitch: 5 i64 args consume DI/SI/DX/CX/R8;
* the 16B struct hits idx=5 with regs_left=1, nw=2 — one
* half (struct word 0) lands in R9, the second half spills
* onto the caller's stack at +16(BP). Wwstage's struct arm
* stitches both into a contiguous local slot. Returns
* sum(s0..s4) + x.a + x.b = 1+2+3+4+5+11+22 = 48. */
{ "partial_fit_5i64_plus_16B",
"type pair = struct { a: i64, b: i64 };\n"
"fn manyfn(s0: i64, s1: i64, s2: i64, s3: i64, s4: i64,\n"
" x: pair) i32 = {\n"
" return (s0 + s1 + s2 + s3 + s4 + x.a + x.b): i32;\n"
"};\n"
"fn main() i32 = {\n"
" let p: pair = pair { a = 11i64, b = 22i64 };\n"
" return manyfn(1i64, 2i64, 3i64, 4i64, 5i64, p);\n"
"};\n",
48 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wcsbp_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/wcsbp_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wcsbp_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
wwtest_fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s", driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — pin the spill prologue + frame size by
* diffing the w6c vs w6c_ww text output. The whole point of #11
* is that wwstage's frame stops under-bloating for user-defined
* struct params, so the bytes must match. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/wcsbp_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/wcsbp_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/wcsbp_asm_%d_%d_w.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
wwtest_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "struct_byval_param: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"struct_byval_param[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"struct_byval_param: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("struct_byval_param: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,259 +0,0 @@
/*
* 757_letbind_void_bang_void — wwstage slotsize recognises `void` as
* zero-sized so `(void | !void)` (or any aliased equivalent) sizes its
* tagged slot at tag + 0 = 8B rather than tag + 8 = 16B. Surfaced by
* task #7's `fromutf8` (lib/strings/strings.ww), which let-binds the
* `(void | utf8.invalid)` result of `utf8.validate` before matching.
*
* Pre-fix (#48 part-A): wwstage's slotsize (selfhost/cmd/wcc/cgenutil.ww)
* lacked a `void`-as-0B case — primsize("void")==0 failed the `> 0`
* guard and fell through alias/struct lookup to the catch-all `return 8`.
* Aliased `!void` recursed into the same TNAME("void") branch with the
* same fallthrough. The N_TTAGGED branch then computed maxsz=8, slot =
* tag + pad(8) = 16. cglet routes the let-init through cgwidentaggedstore
* which sizes its AX→+0 / DX→+8 ABI receive by slot size — so wwstage
* emitted a phantom `MOVQ DX, off+8` spill, picking up the callee's
* stale DX (validate's bare `return;` doesn't zero it). Frame size
* diverged by 16B; every downstream offset shifted; bootstrap byte-
* identity broke as soon as any caller let-bound a `(void | !void)`.
*
* Cstage was already correct: cmd/wcc/type.c:46 sets `ty_void.size = 0`,
* cmd/wcc/check.c:432 reads it via `maxsz = 0; vsz = 0; size = 8 + 0`.
*
* Fix (#48, wwstage-only per rule 10): two narrow additions to
* `slotsize` in selfhost/cmd/wcc/cgenutil.ww —
* 1. N_TBANG case at the top: recurse on .lhs, mirroring cstage
* resolve_type N_TBANG which copies the inner type's size.
* 2. N_TNAME branch: `streq(nm, "void") => 0` before primsize, so
* the zero-size lands without the prim-pad-to-8 contract firing.
*
* What this test pins:
* - Asm byte-identity between cstage and wwstage for `(void | invalid)`
* let-bind through match, where invalid = !void (utf8.invalid shape).
* - Runtime: both arms return their expected exit code.
* - Frame size matches between stages (no $48 vs $32 drift in `foo`).
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "wwtestpkg.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; };
static const struct row rows[] = {
/* 1. The canonical fromutf8 shape: let-bind `(void | invalid)`,
* match on it, return through both arms. validate always returns
* the void variant here. */
{ "letbind_void_arm",
"type invalid = !void;\n"
"fn validate() (void | invalid) = { return; };\n"
"fn main() i32 = {\n"
" let v: (void | invalid) = validate();\n"
" match (v) {\n"
" case void => return 7;\n"
" case let e: invalid => return 9;\n"
" };\n"
" return 0;\n"
"};\n",
7 },
/* 2. invalid arm — validate returns the !void variant. Pre-fix
* the phantom payload-spill picked up validate's stale DX; post-
* fix only the tag word lands in the slot. */
{ "letbind_invalid_arm",
"type invalid = !void;\n"
"fn validate() (void | invalid) = {\n"
" let e: invalid; return e;\n"
"};\n"
"fn main() i32 = {\n"
" let v: (void | invalid) = validate();\n"
" match (v) {\n"
" case void => return 7;\n"
" case let e: invalid => return 9;\n"
" };\n"
" return 0;\n"
"};\n",
9 },
/* 3. Natural fromutf8 form: success-arm lifts a plain str into
* `(str | invalid)`. Pre-fix the let-bind's bogus 16B slot
* pushed every downstream offset 8B further from BP; the lift
* itself (str → tagged-return ABI) was already correct, but
* frame-id drift broke byte-identity. */
{ "fromutf8_natural_match",
"type invalid = !void;\n"
"fn validate(s: str) (void | invalid) = { return; };\n"
"fn fromutf8(s: str) (str | invalid) = {\n"
" let v: (void | invalid) = validate(s);\n"
" match (v) {\n"
" case void => return s;\n"
" case let e: invalid => return e;\n"
" };\n"
" let e: invalid; return e;\n"
"};\n"
"fn main() i32 = {\n"
" match (fromutf8(\"hi\")) {\n"
" case let s: str => return s.len: i32;\n"
" case invalid => return -1;\n"
" };\n"
" return 0;\n"
"};\n",
2 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wclbvv_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/wclbvv_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wclbvv_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
wwtest_fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s", driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — pin frame size + spill layout by diffing the
* w6c vs w6c_ww text output. #48 part-A's whole point is that
* wwstage's slot stops bloating for the (void | !void) shape, so the
* bytes must match. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/wclbvv_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/wclbvv_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/wclbvv_asm_%d_%d_w.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
wwtest_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[512];
if (bin[0] != '/') {
char cwd[256];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[640];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[640];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "letbind_void_bang_void: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"letbind_void_bang_void[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"letbind_void_bang_void: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("letbind_void_bang_void: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,298 +0,0 @@
/*
* 759_check_enum_fold — table-driven sentinel for #22 / A.6.2.1a.
*
* check-side enum fold (selfhost/cmd/wcc/check.ww enumvalfold) now
* mirrors cstage cmd/wcc/check.c:210-284 eval_enum_value: literals
* (INTLIT/RUNELIT), unary +/-/~, binary arithmetic (+ - * / %),
* bitwise (& | ^), shifts (<< >>), and sibling backref via
* N_IDENT. The N_DOT fold mutates the AST to N_INTLIT in place, so
* a wrong fold value leaks into cgen as the wrong literal and the
* exit code reflects it. Each row is a self-contained ww program
* whose `return EnumT.MEMBER: i32` carries the expected fold.
*
* Pre-#22 the check-side fold bailed on every shape but bare
* N_INTLIT and auto-increment; cgen's enumevalmember
* (selfhost/cmd/wcc/cgen.ww:158-227) absorbed the slack at codegen
* time. Post-#22 check.ww does the fold itself so A.6.2.1e's
* post-checker e.type_ assertion no longer fires on `EnumT.MEMBER`
* references whose body is a backref or arithmetic expression.
*
* Each row runs through the cstage `ww` driver and through `ww_ww`
* when present; the asm-byte-id diff between w6c and w6c_ww pins
* the symmetric-emit contract (CLAUDE.md rule 10) so a future
* drift between the two enumvalfold copies fails loud.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "wwtestpkg.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; };
static const struct row rows[] = {
/* Baseline N_INTLIT — regression: the literal-only path was the
* pre-#22 fold contract and must still resolve. */
{ "intlit",
"type e = enum i32 { A = 7 };\n"
"fn main() i32 = { return e.A: i32; };\n",
7 },
/* N_RUNELIT — rune literals carry their codepoint in n.uval
* exactly like N_INTLIT (cmd/wcc/check.c:191). */
{ "runelit",
"type e = enum i32 { A = 'A' };\n"
"fn main() i32 = { return e.A: i32; };\n",
65 },
/* Bare sibling backref (no op): B = A. Tests the N_IDENT arm
* without a wrapping N_BIN/N_UN. */
{ "sibling_backref",
"type e = enum i32 { A = 7, B = A };\n"
"fn main() i32 = { return e.B: i32; };\n",
7 },
/* N_UN TK_MINUS over literal, fed through a sibling backref. The
* NEG member exercises N_UN; the R member exercises the IDENT
* lookup that must resolve NEG's already-folded value. */
{ "unary_minus",
"type e = enum i32 { NEG = -7, R = NEG + 14 };\n"
"fn main() i32 = { return e.R: i32; };\n",
7 },
/* Nested unary: ~(-8) folds to 7. Tests N_UN recursion (TK_TILDE
* over TK_MINUS over N_INTLIT). */
{ "unary_tilde_nested",
"type e = enum i32 { A = ~(-8) };\n"
"fn main() i32 = { return e.A: i32; };\n",
7 },
/* N_UN TK_PLUS — noop wrapper; completes the unary whitelist. */
{ "unary_plus",
"type e = enum i32 { A = +7 };\n"
"fn main() i32 = { return e.A: i32; };\n",
7 },
/* N_BIN TK_PLUS with sibling backref — the headline RW=R|W
* shape's relative. */
{ "bin_plus",
"type e = enum i32 { A = 3, B = A + 4 };\n"
"fn main() i32 = { return e.B: i32; };\n",
7 },
{ "bin_minus",
"type e = enum i32 { A = 10, B = A - 3 };\n"
"fn main() i32 = { return e.B: i32; };\n",
7 },
{ "bin_star",
"type e = enum i32 { A = 2, B = A * 4 };\n"
"fn main() i32 = { return e.B: i32; };\n",
8 },
{ "bin_slash",
"type e = enum i32 { A = 14, B = A / 2 };\n"
"fn main() i32 = { return e.B: i32; };\n",
7 },
{ "bin_percent",
"type e = enum i32 { A = 17, B = A % 10 };\n"
"fn main() i32 = { return e.B: i32; };\n",
7 },
{ "bin_amp",
"type e = enum i32 { A = 15, B = A & 7 };\n"
"fn main() i32 = { return e.B: i32; };\n",
7 },
/* N_BIN TK_PIPE — overlaps with 700_e2e's `RW = R | W` row, kept
* here for symmetry with the rest of the binop set. */
{ "bin_pipe",
"type e = enum i32 { A = 4, B = A | 3 };\n"
"fn main() i32 = { return e.B: i32; };\n",
7 },
{ "bin_caret",
"type e = enum i32 { A = 5, B = A ^ 2 };\n"
"fn main() i32 = { return e.B: i32; };\n",
7 },
{ "bin_lshift",
"type e = enum i32 { A = 1, B = A << 3 };\n"
"fn main() i32 = { return e.B: i32; };\n",
8 },
{ "bin_rshift",
"type e = enum i32 { A = 28, B = A >> 2 };\n"
"fn main() i32 = { return e.B: i32; };\n",
7 },
/* Chained sibling backref — pins the O(N²) walker contract.
* Each member's lhs ident lookup re-walks the prefix; if the
* `until` bound were >= instead of > (off-by-one) the lookup
* of D would walk past C and the fold would diverge or loop. */
{ "chained_backref",
"type e = enum i32 { A = 1, B = A + 1, C = B + 1, D = C + 4 };\n"
"fn main() i32 = { return e.D: i32; };\n",
7 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/cef_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/cef_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/cef_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
wwtest_fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — diff w6c against w6c_ww for the same source.
* Pins the symmetric-emit contract: a future drift between cstage's
* eval_enum_value and wwstage's enumvalfold would show up here as a
* byte diff even if both stages produce semantically-correct
* constants. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/cef_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/cef_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/cef_asm_%d_%d_w.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
wwtest_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "check_enum_fold: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"check_enum_fold[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
/* Asm byte-identity diff, only when wwstage is built. */
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"check_enum_fold: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("check_enum_fold: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,351 +0,0 @@
/*
* 761_inferred_struct_arg_push — regression lock for project #16, the
* inferred-let-struct-local → call-arg push width. Closed incidentally
* by ea1579a (SK_USE-gated N_DOT call result), 7198937 (asserttyped bail
* rearm), 39f9267 + 66a91c8 (#169b structabisize convergence cascade).
*
* Each closing commit removed one path that could have left the local's
* type-AST unstamped — together they guarantee cglet's `tn = n.lhs ??
* inferletcalltype(...)` and the localadd'd `lc.tnode` always carry a
* resolvable N_TNAME for an inferred struct-typed let. pushargsrev's
* N_IDENT struct arm (selfhost/cmd/wcc/cgenutil.ww:459) then reaches
* structparamsize > 0 and emits the maxalign-rounded 1-or-2 word push,
* byte-id with cstage's `args[i]->type` → struct_arg_size path
* (cmd/w6c/cgen.c:427, :5452).
*
* The 16-shape impl-16 probe sweep (worktree-impl-16) confirmed
* byte-identity across every reasonable trigger. This file consolidates
* the load-bearing rows so the symmetric behavior cannot regress without
* a green-to-red flip on these probes.
*
* WHAT THIS LOCKS
* - cs==ww .s byte-identity for inferred-let + struct-arg-push
* across struct shapes:
* * {i64,i64} — canonical 16B, maxalign 8
* * {u32,i64} — decf32-shape (ken-flagged
* ftos.ww:411 refactor target), 16B with align-8 tail
* * {i64,i32} — narrow-tail (#169 maxalign-8
* tail-padded ABI)
* * {i32,i32,i32} — maxalign 4, slot-padded 16B
* but ABI 12B (cstage struct_arg_size returns 12, both
* stages still emit 2-word push since stsz>8)
* - cs==ww runtime exit codes for the same shapes
* - Inferral paths covered:
* * `let p = make(...);` (direct N_CALL infer)
* * `let q = make(...); let p = q;` (N_IDENT-rhs infer chain)
* * `let p = make_big(...);` for >24B sret (no register-ABI
* push, but exercises let_isarray=false sret-recv → ident
* push path)
* - Branched callee (per #105 lesson — single-return masks
* reg-routing via coincidence): both `make` and the consumer
* branch on a flag so the GP register routing is exercised
* through both arms.
*
* WHAT THIS DOES NOT COVER (separate filed bugs — do NOT bundle)
* - #173: `let p = call_returning_tagged()!` drops CX/word1 on
* receive (wwstage) and elides the CALL entirely (cstage). Live
* miscompile, separate impl.
* - #176: `let x = w.p` where p is a struct field — wwstage emits
* 1-word memcpy (under-copy of nested-struct field).
* - #175: `let p = arr[0]` — both stages buggy in different ways.
* - #177: aliased struct return (`type alias_p = point;
* fn make() alias_p`) — cstage emits 1-word push (OPPOSITE of
* the #16 stated symptom polarity).
* - #174: `let p = call(): T;` cast in rhs — cstage drops the
* call. Both-stages broken.
*
* GATE POLARITY: this file must stay GREEN. A red here means one of
* the closing commits regressed. Bisect against the 4 cited commits.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "wwtestpkg.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; };
static const struct row rows[] = {
/* 1. Canonical {i64,i64} — `let p = make(0);` inferred, then
* `sum(p)` passes p as the struct arg. Branched callee on flag.
* Returns p.a + p.b = 10 + 20 = 30. */
{ "i64_i64_infer_direct",
"type point = struct { a: i64, b: i64 };\n"
"fn make(flag: i32) point = {\n"
" if (flag == 0) { return point { a = 10i64, b = 20i64 }; };\n"
" return point { a = 100i64, b = 200i64 };\n"
"};\n"
"fn sum(p: point, which: i32) i32 = {\n"
" if (which == 0) { return (p.a + p.b): i32; };\n"
" return (p.a - p.b): i32;\n"
"};\n"
"fn main() i32 = {\n"
" let p = make(0);\n"
" return sum(p, 0);\n"
"};\n",
30 },
/* 2. {u32,i64} — decf32-shape. The narrow-mantissa-first then
* widening-exponent layout is the strconv ftos.ww:411 decf32
* (ken-flagged as one-refactor-away). Returns
* (mantissa: i64) + exponent = 7 + 13 = 20. */
{ "u32_i64_decf_infer",
"type decf = struct { mantissa: u32, exponent: i64 };\n"
"fn make(flag: i32) decf = {\n"
" if (flag == 0) { return decf { mantissa = 7u32, exponent = 13i64 }; };\n"
" return decf { mantissa = 99u32, exponent = -1i64 };\n"
"};\n"
"fn use_(d: decf, which: i32) i32 = {\n"
" if (which == 0) { return ((d.mantissa: i64) + d.exponent): i32; };\n"
" return ((d.mantissa: i64) - d.exponent): i32;\n"
"};\n"
"fn main() i32 = {\n"
" let d = make(0);\n"
" return use_(d, 0);\n"
"};\n",
20 },
/* 3. {i64,i32} narrow-tail — natural 12B, maxalign 8, ABI 16B
* via #169 structabisize round-up. Branched callee on flag.
* Returns p.a + (p.b: i64) = 50 + 7 = 57. */
{ "i64_i32_narrow_tail_infer",
"type ntail = struct { a: i64, b: i32 };\n"
"fn make(flag: i32) ntail = {\n"
" if (flag == 0) { return ntail { a = 50i64, b = 7i32 }; };\n"
" return ntail { a = 500i64, b = 70i32 };\n"
"};\n"
"fn use_(p: ntail, which: i32) i32 = {\n"
" if (which == 0) { return (p.a + (p.b: i64)): i32; };\n"
" return (p.a - (p.b: i64)): i32;\n"
"};\n"
"fn main() i32 = {\n"
" let p = make(0);\n"
" return use_(p, 0);\n"
"};\n",
57 },
/* 4. {i32,i32,i32} — maxalign 4, ABI 12B (cstage); wwstage si
* .totsize 16. Both stages still push 2 words since stsz > 8;
* the 2-word push is THE bug #16 watched for. Returns
* p.a + p.b + p.c = 1 + 2 + 3 = 6. */
{ "i32_x3_maxalign4_infer",
"type three = struct { a: i32, b: i32, c: i32 };\n"
"fn make(flag: i32) three = {\n"
" if (flag == 0) { return three { a = 1i32, b = 2i32, c = 3i32 }; };\n"
" return three { a = 10i32, b = 20i32, c = 30i32 };\n"
"};\n"
"fn use_(p: three, which: i32) i32 = {\n"
" if (which == 0) { return p.a + p.b + p.c; };\n"
" return p.a * p.b * p.c;\n"
"};\n"
"fn main() i32 = {\n"
" let p = make(0);\n"
" return use_(p, 0);\n"
"};\n",
6 },
/* 5. Ident-rhs inference chain — `let p = q;` where q itself
* was inferred from a call. Pre-bail-rearm the checker's
* `n.lhs = src` walk had to propagate q's resolved type through
* exprtype(N_IDENT); a stale path would leave p's lc.tnode nil
* and structparamsize → 0 → 1-word push. Returns
* p.a + p.b = 11 + 22 = 33. */
{ "ident_rhs_chain_infer",
"type point = struct { a: i64, b: i64 };\n"
"fn make(flag: i32) point = {\n"
" if (flag == 0) { return point { a = 11i64, b = 22i64 }; };\n"
" return point { a = 110i64, b = 220i64 };\n"
"};\n"
"fn sum(p: point, which: i32) i32 = {\n"
" if (which == 0) { return (p.a + p.b): i32; };\n"
" return (p.a - p.b): i32;\n"
"};\n"
"fn main() i32 = {\n"
" let q = make(0);\n"
" let p = q;\n"
" return sum(p, 0);\n"
"};\n",
33 },
/* 6. >24B sret RECEIVE — exercises the cglet sret-dest wiring
* (selfhost/cmd/wcc/cgenstmt.ww:1231 callsretsize branch). The
* let's own slot is the hidden RDI dest pointer; the callee
* writes through it. The struct is then passed BY POINTER to
* the consumer (NOT by value) — the by-value >24B param ABI is
* orthogonal to #16's call-arg-push scope and has its own
* filed gaps. Returns
* p.a + p.b + p.c + p.d = 1 + 2 + 3 + 4 = 10. */
{ "big_sret_infer_then_ptr_arg",
"type big = struct { a: i64, b: i64, c: i64, d: i64 };\n"
"fn make(flag: i32) big = {\n"
" if (flag == 0) { return big { a = 1i64, b = 2i64, c = 3i64, d = 4i64 }; };\n"
" return big { a = 10i64, b = 20i64, c = 30i64, d = 40i64 };\n"
"};\n"
"fn use_(p: *big, which: i32) i32 = {\n"
" if (which == 0) { return (p.a + p.b + p.c + p.d): i32; };\n"
" return (p.a * p.b * p.c * p.d): i32;\n"
"};\n"
"fn main() i32 = {\n"
" let p = make(0);\n"
" return use_(&p, 0);\n"
"};\n",
10 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wcisp_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/wcisp_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wcisp_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
wwtest_fputs(r->src, f);
fclose(f);
/* timeout 180 per repo convention (cf. 760); test/run does not
* bound individual binaries, so an unguarded hang from a future
* cgen regression would stall make test instead of redding here. */
snprintf(cmd, sizeof cmd, "timeout 180 %s build -o %s %s 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — diff w6c vs w6c_ww text output. The closing
* commits (ea1579a + 7198937 + 39f9267 + 66a91c8) ensure both stages
* emit the same struct-arg push width for every shape; a red here
* means one of those reverted (#16 surfaces). */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/wcisp_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/wcisp_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/wcisp_asm_%d_%d_w.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
wwtest_fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "timeout 180 %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, "timeout 180 %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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "inferred_struct_arg_push: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"inferred_struct_arg_push[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"inferred_struct_arg_push: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("inferred_struct_arg_push: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,286 +0,0 @@
/*
* 762_struct_abi_size — defensive lock for project #78. cstage once
* folded size(struct{i32,i32,i32}) to 12 while wwstage folded it to
* 16; the two checkers walked different layout formulas. After
* d4e500f (#169) introduced structabisize and the cgen ABI sites
* converged onto a single (off + maxalign - 1) & ~(maxalign - 1)
* formula on both sides, every layout-edge shape should agree —
* BOTH at the size(T) fold (cstage check.c:760 / wwstage check.ww
* astsize N_TSTRUCT) and at the register-RECV/RETURN ABI walker
* (cgen.c struct_arg_size / structabisize). This probe pins both.
*
* The fold and the ABI walker share the formula but live in two
* different sources: check.c:760 vs check.ww:958 (the language
* builtin) and cmd/w6c/cgen.c struct_arg_size vs
* selfhost/cmd/wcc/cgenutil.ww structabisize (the cgen ABI). Either
* can drift independently — locking only the runtime exit code via
* size(T) misses an ABI-walker regression that does not change the
* literal fold; locking only the .s byte-id misses a fold regression
* the cgen would happily honour. Both gates run per row.
*
* Expected size is COMPUTED from a (sz, aln) field table per row,
* not hardcoded — a stage-internal formula bump (rule 13) lands the
* change in compute_expected() and every row tracks. The probe must
* red the moment any source-of-truth drifts from the documented
* Hare-style layout: align(off, fa); off += fs; size = align(off,
* maxalign).
*
* Shape coverage targets the maxalign edges where the bug class
* lived:
* 1. {3xi32} — #78 canonical, maxalign 4, natural 12,
* ABI 12 (no round-up)
* 2. {i32,i32,i64} — maxalign 8, narrow lead, ABI 16
* 3. {i64,i32,i32} — maxalign 8 with sub-8 tail, ABI 16
* (the #169 round-up case)
* 4. {3xi16} — maxalign 2, natural 6, ABI 6
* 5. {i16,i64} — maxalign 8 with mid-record alignment
* padding (f2 must skip off 2→8)
* 6. {3xi64} — maxalign 8, all 8-wide, natural==ABI=24
*
* GATE POLARITY: this file must stay GREEN. A red here means either
* d4e500f (#169 structabisize) reverted, or one of the two layout
* walkers drifted from the formula.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "wwtestpkg.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 field { int sz; int aln; };
struct row {
const char *label;
const char *src; /* must `return size(T): i32` */
int nfields;
struct field fields[8];
};
/* Hare / cstage check.c:701-760 / wwstage check.ww astsize N_TSTRUCT
* formula. Per CLAUDE.md rule 13 the expected size for each row is
* derived here, not literalled into the row table. */
static int
compute_expected(const struct field *f, int n)
{
int off = 0, maxalign = 1;
for (int i = 0; i < n; i++) {
int a = f[i].aln;
if (a > maxalign) maxalign = a;
off = (off + a - 1) & ~(a - 1);
off += f[i].sz;
}
return (off + maxalign - 1) & ~(maxalign - 1);
}
static const struct row rows[] = {
/* 1. {3xi32} — #78 canonical. cstage check.c:760 returned 12,
* wwstage formerly walked a slot-padded ladder yielding 16. The
* fold now uses astsize's (off + maxal - 1) & ~(maxal - 1) on
* both sides; expect 12. */
{ "i32_x3_maxalign4",
"type t = struct { a: i32, b: i32, c: i32 };\n"
"export fn main() i32 = { return size(t): i32; };\n",
3, {{4,4},{4,4},{4,4}} },
/* 2. {i32,i32,i64} — maxalign 8, off lands at 8 before the i64,
* total 16. */
{ "i32_i32_i64_lead_narrow",
"type t = struct { a: i32, b: i32, c: i64 };\n"
"export fn main() i32 = { return size(t): i32; };\n",
3, {{4,4},{4,4},{8,8}} },
/* 3. {i64,i32,i32} — natural extent 16, maxalign 8: the post-
* field-3 round-up is a no-op here, but the #169 commit added
* exactly this round-up so the structabisize walker matches
* cstage on the maxalign-8 sub-8-tail shape (cf. {i64,i32}). */
{ "i64_i32_i32_sub8_tail",
"type t = struct { a: i64, b: i32, c: i32 };\n"
"export fn main() i32 = { return size(t): i32; };\n",
3, {{8,8},{4,4},{4,4}} },
/* 4. {3xi16} — maxalign 2, natural 6. Guards against a future
* "round to 8" shortcut creeping back into either walker. */
{ "i16_x3_maxalign2",
"type t = struct { a: i16, b: i16, c: i16 };\n"
"export fn main() i32 = { return size(t): i32; };\n",
3, {{2,2},{2,2},{2,2}} },
/* 5. {i16,i64} — exercises a non-trivial mid-record align step:
* after f1 off=2, f2 must align off 2→8 before placing the i64.
* A missing `off = align(off, fa)` would land f2 at off=2 and
* size to 10 (or 16 only via the final maxalign round); either
* way it would diverge from the formula. {i32,i32,i64} (rows 2)
* already aligns at 8 by the natural off, so the align-step is
* a no-op there. */
{ "i16_i64_mid_pad",
"type t = struct { a: i16, b: i64 };\n"
"export fn main() i32 = { return size(t): i32; };\n",
2, {{2,2},{8,8}} },
/* 6. {3xi64} — natural==ABI, the maxalign-rounded formula must
* not double-round. */
{ "i64_x3_natural_24",
"type t = struct { a: i64, b: i64, c: i64 };\n"
"export fn main() i32 = { return size(t): i32; };\n",
3, {{8,8},{8,8},{8,8}} },
};
static int
run_driver(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/wcabi_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/wcabi_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wcabi_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
wwtest_fputs(r->src, f);
fclose(f);
/* timeout 180 per repo convention (cf. 760, 761); test/run does
* not bound individual binaries. */
snprintf(cmd, sizeof cmd, "timeout 180 %s build -o %s %s 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — diff w6c vs w6c_ww text output. The cgen ABI
* walker (structabisize since #169 / d4e500f) feeds the struct-
* return + struct-arg push widths; a divergence on these shapes
* surfaces as a .s difference even when the size(T) fold still
* matches (the two walkers are independent SSoTs). */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/wcabi_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/wcabi_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/wcabi_asm_%d_%d_w.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
wwtest_fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "timeout 180 %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, "timeout 180 %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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "struct_abi_size: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int want = compute_expected(rows[i].fields,
rows[i].nfields);
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != want) {
fprintf(stderr,
"struct_abi_size[%s][%s]: size=%d want=%d\n",
drivers[d].name, rows[i].label,
got, want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"struct_abi_size: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("struct_abi_size: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,296 +0,0 @@
/*
* 766_star_fn_deref_call — root-cause lock for project #181, and the
* live verification of the full deref-call c-cluster (#180 + #185 +
* #181 working together end-to-end).
*
* Pre-#181 wwstage's checker bailed asserttyped on the N_CALL whose
* callee was N_UN TK_STAR over a *fn — selfhost/cmd/wcc/check.ww
* exprtype's N_CALL arm only resolved IDENT/DOT-named callees and
* early-returned nil for any other shape, leaving e.type_ unstamped
* so the post-checker invariant gate fired. With cgen post-#180+#185
* already correct for the deref-call lowering, the safe fix was to
* lift the asserttyped bail and route the callee through the
* fn-VALUE fallback (autodereference + dealias to TY_FN, mirroring
* harec check_autodereference at ref/harec/src/check.c:1566).
*
* Fix: selfhost/cmd/wcc/check.ww exprtype N_CALL arm — replace the
* `if (nm.len == 0) return nil` early-bail with `if (nm.len > 0)
* { name-lookup }`, so non-named callees fall through to the
* existing fn-VALUE branch (peel TPTR, dealias to TFN, stamp the
* result type). cstage check.c already worked because cexpr
* recurses on the callee — TK_STAR's unop arm returns t->sub which
* is TY_FN directly, no name path needed.
*
* Gate flip from 765: 765 was cstage-only because wwstage bailed
* before reaching cgen. Post-#181 every row gates BOTH stages —
* cstage runtime, wwstage runtime, AND cs.s == ww.s byte-id. This
* is the runtime coverage 765 deferred plus the symmetry gate that
* proves both stages emit identical asm for the deref-call shape.
*
* Coverage (same 5 rows as 765, now with STAGE_CS|STAGE_WW):
* 1. minimal — `let f = &add1; (*f)(7) == 8`
* 2. branched_callee — pick aa or bb by runtime cond
* 3. alias_chain — `let f = &fn; let g = f; (*g)(7)`
* 4. fn_with_args — multiple args, scalar + ptr mix
* 5. fn_tuple_return — (i32, i32) return shape; multi-reg
* return ABI survives the deref-call
*
* Gates per row:
* a. cstage builds + runs, exit == expected (already worked
* post-#180+#185; kept for c-cluster regression coverage).
* b. wwstage builds + runs, exit == expected — the #181 lift
* gate; pre-fix this row's asserttyped bailed before cgen.
* c. cstage .s == wwstage .s byte-identical — rule-10 symmetry,
* proves the checker change doesn't perturb the codegen.
*
* GATE POLARITY: must stay GREEN. A red here means either the
* checker N_CALL fn-VALUE route regressed, the cgen deref-call
* lowering regressed (#180/#185), or stage symmetry drifted.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "wwtestpkg.h"
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
#define STAGE_CS 1
#define STAGE_WW 2
struct row {
const char *label;
const char *src;
int expected_exit;
int stage_mask;
};
static const struct row rows[] = {
{ "minimal",
"fn add1(x: i32) i32 = { return x + 1; };\n"
"export fn main() i32 = {\n"
" let f: *fn(x: i32) i32 = &add1;\n"
" return (*f)(7);\n"
"};\n",
8,
STAGE_CS | STAGE_WW },
/* Branched-callee: the if-arm forces a runtime choice between
* two distinct fn addresses, defeating any constant-aliasing
* mask of the deref miscompile (#105 lesson). */
{ "branched_callee",
"fn aa(x: i32) i32 = { return x; };\n"
"fn bb(x: i32) i32 = { return x + 100; };\n"
"export fn main() i32 = {\n"
" let pick: i32 = 1;\n"
" let f: *fn(x: i32) i32 = &aa;\n"
" if (pick != 0) { f = &bb; };\n"
" return (*f)(7);\n"
"};\n",
107,
STAGE_CS | STAGE_WW },
{ "alias_chain",
"fn add1(x: i32) i32 = { return x + 1; };\n"
"export fn main() i32 = {\n"
" let f: *fn(x: i32) i32 = &add1;\n"
" let g: *fn(x: i32) i32 = f;\n"
" return (*g)(7);\n"
"};\n",
8,
STAGE_CS | STAGE_WW },
{ "fn_with_args",
"fn many(a: i32, b: i32, p: *i32) i32 = { return a + b + *p; };\n"
"export fn main() i32 = {\n"
" let z: i32 = 5;\n"
" let f: *fn(a: i32, b: i32, p: *i32) i32 = &many;\n"
" return (*f)(3, 7, &z);\n"
"};\n",
15,
STAGE_CS | STAGE_WW },
/* Tuple-return: exercises the multi-register return ABI through
* the deref-call. (i32, i32) keeps it simple while still covering
* the multi-reg path. */
{ "fn_tuple_return",
"fn pair(x: i32) (i32, i32) = { return (x, x + 1); };\n"
"export fn main() i32 = {\n"
" let f: *fn(x: i32) (i32, i32) = &pair;\n"
" let a, b = (*f)(7);\n"
" return a + b;\n"
"};\n",
15,
STAGE_CS | STAGE_WW },
};
static int
write_source(const char *src_path, const char *src)
{
FILE *f = fopen(src_path, "wb");
if (!f) return -1;
wwtest_fputs(src, f);
fclose(f);
return 0;
}
static void
cleanup_tmp(const char *tmpdir, const char *base)
{
char p[512];
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s.o", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s.combined.ww", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p);
/* #93: the sep scratch dir + the tmpdir-pinned pkgcache. */
snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir);
if (system(p)) {} /* best-effort */
rmdir(tmpdir);
}
static int
build_via_driver(const char *driver, const char *tmpdir, const char *src)
{
char cmd[1024];
/* #93 sep layout: emit asm to <src-stem>.sepwork/__root.s; pin
* WW_PKGCACHE under tmpdir so out/.pkgcache is untouched. */
snprintf(cmd, sizeof cmd,
"cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep "
"-o \"${b%%.ww}\" \"$b\" 2>/dev/null",
tmpdir, src, tmpdir, driver);
return runwait(cmd);
}
/* run_row — build + run; returns the binary's exit code (-1 on
* build failure). */
static int
run_row(const char *driver, const struct row *r, int seq)
{
char tmpdir[256], src[256], base[64], outbin[512];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wcsfdc_%d_d_%d", getpid(), seq);
snprintf(src, sizeof src, "%s/main766.ww", tmpdir);
snprintf(base, sizeof base, "main766");
mkdir(tmpdir, 0755);
if (write_source(src, r->src) != 0) {
cleanup_tmp(tmpdir, base);
return -1;
}
int rc = -1;
if (build_via_driver(driver, tmpdir, src) == 0) {
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
rc = runwait(outbin);
}
cleanup_tmp(tmpdir, base);
return rc;
}
/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so
* ww_ww writing intermediates next to the source doesn't clobber the
* cstage .s (filed bug per CLAUDE.md rule 14 phase split). */
static int
asm_byte_identical(const char *cdrv, const char *wdrv,
const struct row *r, int seq)
{
char src[256], tdc[256], tdw[256], base[64], cs[512], ws[512];
snprintf(tdc, sizeof tdc, "/tmp/wcsfdc_%d_c_%d", getpid(), seq);
snprintf(tdw, sizeof tdw, "/tmp/wcsfdc_%d_w_%d", getpid(), seq);
snprintf(base, sizeof base, "main766");
mkdir(tdc, 0755);
mkdir(tdw, 0755);
snprintf(src, sizeof src, "%s/main766.ww", tdc);
if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; }
int rc = -1;
if (build_via_driver(cdrv, tdc, src) != 0) goto out;
snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base);
snprintf(src, sizeof src, "%s/main766.ww", tdw);
if (write_source(src, r->src) != 0) goto out;
if (build_via_driver(wdrv, tdw, src) != 0) goto out;
snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base);
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
if (fc && fw) {
rc = 0;
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
out:
cleanup_tmp(tdc, base);
cleanup_tmp(tdw, base);
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 cdrv[1024], wdrv[1024];
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;
int wwpresent = (access(wdrv, X_OK) == 0);
int seq = 0;
for (int i = 0; i < n; i++) {
if (rows[i].stage_mask & STAGE_CS) {
total++;
int got = run_row(cdrv, &rows[i], seq++);
if (got != rows[i].expected_exit) {
fprintf(stderr,
"star_fn_deref_call[cstage run][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].expected_exit);
fail++;
}
}
if (wwpresent && (rows[i].stage_mask & STAGE_WW)) {
total++;
int got = run_row(wdrv, &rows[i], seq++);
if (got != rows[i].expected_exit) {
fprintf(stderr,
"star_fn_deref_call[wwstage run][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].expected_exit);
fail++;
}
total++;
if (asm_byte_identical(cdrv, wdrv, &rows[i], seq++) != 0) {
fprintf(stderr,
"star_fn_deref_call[byte-id][%s]: cstage vs wwstage asm differs\n",
rows[i].label);
fail++;
}
}
}
if (!wwpresent)
fprintf(stderr, "star_fn_deref_call: skip wwstage (no %s)\n", wdrv);
if (fail) {
fprintf(stderr, "star_fn_deref_call: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("star_fn_deref_call: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,344 +0,0 @@
/*
* 767_match_variant_dispatch — project #179: wwstage cgmatch's
* non-nullable arm gated its flat-tag dispatch on `pat.kind ==
* N_TNAME` (with an `N_TSLICE` else-branch for task #19's untyped
* elem fallback). N_TPTR / N_TFN / N_TPTR(N_TFN) case-patterns fell
* through both arms, leaving the local `r` at -1 → `want = 0`, so
* every variant past 0 silently collapsed to tag 0 — the kind of
* miscompile that runtime-passes only when the value happens to be
* variant 0 (zero-coincidence).
*
* Cstage cg_tag_for_variant (cmd/w6c/cgen.c:624) walks Type directly
* and is kind-agnostic; it has never had this gap. Drew's settled
* principle: match dispatch is resolved-type-only (harec
* check.c:2527 stores `_case->type = ctype`). Project #66 Phase-N
* already flipped the typeeq compare; #179 was the one site that
* still keyed on AST kind. Memory: project_tinfo_lossy_nominal.
*
* Fix: selfhost/cmd/wcc/cgenexpr.ww cgmatch non-nullable arm —
* route through `flatvariantidxt(scrutt.type_, pat.type_)` directly,
* guarded only by `istaggedtype(scrutt)` + `typeisslice(pattype)`
* for the slice axis. No new helper; existing flatvariantidxt /
* flatslicevariantidx wire up unchanged.
*
* Coverage (5 rows):
* 1. nullable_ptr_fn — `(*fn(i32) i32 | void)`, branched
* store (#105 lesson: two distinct
* fns picked at runtime), match via
* indirect call. KEN'S VERIFY GATE:
* row 1 byte-id proves wwstage's
* nullable-fold ordering matches
* cstage cg_tag_for_variant — fail
* here flags a separate-signoff fold.
* Goes through the nullable disc path
* (line 1484-1502, untouched by #179).
* 2. ptr_variants_past_zero — `(*i32 | *i64)` storing &i64.
* Pre-fix wwstage emits CMPQ $0 for
* the *i64 arm; post-fix CMPQ $1.
* 3. fn_ptr_variants_via_local — `(*fn(i32) i32 | *fn(i64) i64)`
* built from a local. Direct N_TPTR
* over N_TFN case-pattern is the new-
* shape exercise; store path goes via
* ident (not `&fn` inline) to dodge
* the fn-variant store-side sibling
* bug (see below).
* 4. stored_variant1_roundtrip — `(*i32 | *i64)` with branched
* runtime variant choice (pick = 1
* forces variant 1; defeats const-
* fold to variant 0). Mirrors #105.
* 5. aliased_ptr_variants — `type pa = *i32; type pb = *i64;
* (pa | pb)`. typeeq through TY_NAMED
* wrappers — the alias-aware path that
* the surface-name compare couldn't
* key on. OR-pattern was the original
* row 5, dropped because the wwstage
* parser doesn't accept `case T1 | T2`.
*
* Per-row gates: cstage runtime exit, wwstage runtime exit,
* cs.s == ww.s byte-identical (rule-10 stage symmetry).
*
* Sibling bug surfaced (not fixed here, filed inline): widening
* `&fn` INLINE into a `(*fn(...) | *fn(...))` slot computes the
* wrong tag in wwstage — taggedvariantindext / typeeq on the
* synthetic fn-ptr tinfo built from N_UN TK_AMP IDENT-of-FN doesn't
* match the variant tinfo, so tag collapses to 0. Storing via an
* intermediate ident-typed local (row 3 shape) routes through a
* different store path and tags correctly. cstage cg_widen_tagged_
* store hits the same node with the same Type and tags correctly,
* so the divergence is wwstage-only on the inline-amp shape. Out
* of scope for #179 (which is the match-DISPATCH side, not the
* widen-STORE side).
*
* GATE POLARITY: must stay GREEN. A red here means the cgmatch
* non-nullable dispatch regressed, the nullable arm got perturbed,
* or stage symmetry drifted on the new typeeq route.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "wwtestpkg.h"
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
#define STAGE_CS 1
#define STAGE_WW 2
struct row {
const char *label;
const char *src;
int expected_exit;
int stage_mask;
};
static const struct row rows[] = {
/* KEN'S VERIFY GATE: nullable disc path must byte-id even
* though #179 didn't touch it. A divergence here flags a
* separate flatvariantidxt nullable-fold ordering issue. */
{ "nullable_ptr_fn",
"fn ai(x: i32) i32 = { return x + 1; };\n"
"fn bi(x: i32) i32 = { return x + 100; };\n"
"fn indi(f: *fn(x: i32) i32, x: i32) i32 = { return (*f)(x); };\n"
"export fn main() i32 = {\n"
" let pick: i32 = 1;\n"
" let v: (*fn(x: i32) i32 | void) = &ai;\n"
" if (pick != 0) { v = &bi; };\n"
" let r: i32 = match (v) {\n"
" case void => yield 99: i32;\n"
" case let f: *fn(x: i32) i32 => yield indi(f, 7);\n"
" };\n"
" return r;\n"
"};\n",
107,
STAGE_CS | STAGE_WW },
{ "ptr_variants_past_zero",
"export fn main() i32 = {\n"
" let a: i64 = 42;\n"
" let v: (*i32 | *i64) = &a;\n"
" let r: i32 = match (v) {\n"
" case *i32 => yield 11: i32;\n"
" case *i64 => yield 22: i32;\n"
" };\n"
" return r;\n"
"};\n",
22,
STAGE_CS | STAGE_WW },
{ "fn_ptr_variants_via_local",
"fn ai(x: i32) i32 = { return x + 1; };\n"
"fn bi(x: i64) i64 = { return x + 1; };\n"
"fn indl(f: *fn(x: i64) i64, x: i64) i64 = { return (*f)(x); };\n"
"export fn main() i32 = {\n"
" let g: *fn(x: i64) i64 = &bi;\n"
" let v: (*fn(x: i32) i32 | *fn(x: i64) i64) = g;\n"
" let r: i32 = match (v) {\n"
" case let p: *fn(x: i32) i32 => yield 1: i32;\n"
" case let q: *fn(x: i64) i64 => yield indl(q, 9): i32;\n"
" };\n"
" return r;\n"
"};\n",
10,
STAGE_CS | STAGE_WW },
{ "stored_variant1_roundtrip",
"export fn main() i32 = {\n"
" let a: i32 = 5;\n"
" let b: i64 = 9;\n"
" let pick: i32 = 1;\n"
" let v: (*i32 | *i64) = &a;\n"
" if (pick != 0) { v = &b; };\n"
" let r: i32 = match (v) {\n"
" case *i32 => yield 11: i32;\n"
" case *i64 => yield 22: i32;\n"
" };\n"
" return r;\n"
"};\n",
22,
STAGE_CS | STAGE_WW },
{ "aliased_ptr_variants",
"type pa = *i32;\n"
"type pb = *i64;\n"
"export fn main() i32 = {\n"
" let a: i64 = 100;\n"
" let g: pb = &a;\n"
" let v: (pa | pb) = g;\n"
" let r: i32 = match (v) {\n"
" case pa => yield 11: i32;\n"
" case pb => yield 22: i32;\n"
" };\n"
" return r;\n"
"};\n",
22,
STAGE_CS | STAGE_WW },
};
static int
write_source(const char *src_path, const char *src)
{
FILE *f = fopen(src_path, "wb");
if (!f) return -1;
wwtest_fputs(src, f);
fclose(f);
return 0;
}
static void
cleanup_tmp(const char *tmpdir, const char *base)
{
char p[512];
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s.o", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s.combined.ww", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p);
/* #93: the sep scratch dir + the tmpdir-pinned pkgcache. */
snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir);
if (system(p)) {} /* best-effort */
rmdir(tmpdir);
}
static int
build_via_driver(const char *driver, const char *tmpdir, const char *src)
{
char cmd[1024];
/* #93 sep layout: emit asm to <src-stem>.sepwork/__root.s; pin
* WW_PKGCACHE under tmpdir so out/.pkgcache is untouched. */
snprintf(cmd, sizeof cmd,
"cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep "
"-o \"${b%%.ww}\" \"$b\" 2>/dev/null",
tmpdir, src, tmpdir, driver);
return runwait(cmd);
}
static int
run_row(const char *driver, const struct row *r, int seq)
{
char tmpdir[256], src[256], base[64], outbin[512];
snprintf(tmpdir, sizeof tmpdir, "/tmp/mvd_%d_d_%d", getpid(), seq);
snprintf(src, sizeof src, "%s/main767.ww", tmpdir);
snprintf(base, sizeof base, "main767");
mkdir(tmpdir, 0755);
if (write_source(src, r->src) != 0) {
cleanup_tmp(tmpdir, base);
return -1;
}
int rc = -1;
if (build_via_driver(driver, tmpdir, src) == 0) {
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
rc = runwait(outbin);
}
cleanup_tmp(tmpdir, base);
return rc;
}
/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so
* ww_ww writing intermediates next to the source doesn't clobber the
* cstage .s (filed bug per CLAUDE.md rule 14 phase split). */
static int
asm_byte_identical(const char *cdrv, const char *wdrv,
const struct row *r, int seq)
{
char src[256], tdc[256], tdw[256], base[64], cs[512], ws[512];
snprintf(tdc, sizeof tdc, "/tmp/mvd_%d_c_%d", getpid(), seq);
snprintf(tdw, sizeof tdw, "/tmp/mvd_%d_w_%d", getpid(), seq);
snprintf(base, sizeof base, "main767");
mkdir(tdc, 0755);
mkdir(tdw, 0755);
snprintf(src, sizeof src, "%s/main767.ww", tdc);
if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; }
int rc = -1;
if (build_via_driver(cdrv, tdc, src) != 0) goto out;
snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base);
snprintf(src, sizeof src, "%s/main767.ww", tdw);
if (write_source(src, r->src) != 0) goto out;
if (build_via_driver(wdrv, tdw, src) != 0) goto out;
snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base);
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
if (fc && fw) {
rc = 0;
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
out:
cleanup_tmp(tdc, base);
cleanup_tmp(tdw, base);
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 cdrv[1024], wdrv[1024];
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;
int wwpresent = (access(wdrv, X_OK) == 0);
int seq = 0;
for (int i = 0; i < n; i++) {
if (rows[i].stage_mask & STAGE_CS) {
total++;
int got = run_row(cdrv, &rows[i], seq++);
if (got != rows[i].expected_exit) {
fprintf(stderr,
"match_variant_dispatch[cstage run][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].expected_exit);
fail++;
}
}
if (wwpresent && (rows[i].stage_mask & STAGE_WW)) {
total++;
int got = run_row(wdrv, &rows[i], seq++);
if (got != rows[i].expected_exit) {
fprintf(stderr,
"match_variant_dispatch[wwstage run][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].expected_exit);
fail++;
}
total++;
if (asm_byte_identical(cdrv, wdrv, &rows[i], seq++) != 0) {
fprintf(stderr,
"match_variant_dispatch[byte-id][%s]: cstage vs wwstage asm differs\n",
rows[i].label);
fail++;
}
}
}
if (!wwpresent)
fprintf(stderr, "match_variant_dispatch: skip wwstage (no %s)\n", wdrv);
if (fail) {
fprintf(stderr, "match_variant_dispatch: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("match_variant_dispatch: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,352 +0,0 @@
/*
* 769_dot_aliased_ptr — project #191: wwstage cgdot N_DOT on an
* aliased-pointer receiver. `type vt = struct{...}; type vs = *vt;
* fn f(s: vs) { s.field }` — wwstage's cgdot read the receiver's
* tnode kind WITHOUT first peeling N_TNAME alias chains, so lkind
* stayed N_TNAME (not the underlying N_TPTR), structlookupchain
* missed (`vs` isn't a struct alias), and the lookup fell through
* to the SB-global fallback. The emitted asm was
* MOVQ field(SB), AX
* which links as an undefined symbol — runtime never reached.
*
* Cstage (cmd/w6c/cgen.c:7001-7004) uses type_chase_named to walk
* TY_NAMED.under to the underlying TY_PTR / TY_STRUCT before the
* kind-gated arms fire. Mirror: a loop via `aliaslookup(c, name)`
* at the top of cgdot's lc != nil branch, stopping at struct
* aliases so the existing direct-struct N_TNAME arm below stays
* byte-id with pre-fix #22 callers.
*
* Fix: selfhost/cmd/wcc/cgenexpr.ww cgdot, after `let tn = lc.tnode;`
* insert a peel loop before the lkind decision. LOOP not single-
* peel — Phase-N builds N_TNAME chains (memory
* project_tinfo_lossy_nominal), so depth-2+ aliases require
* iteration. Inner peel on the pointee is unnecessary because the
* existing `structlookupchain(c, inner)` already walks N_TNAME
* chains via aliaslookup (cgenutil.ww:1266-1276); row 4 below
* proves the inner-chain depth-3 path stays green without an
* explicit inner peel.
*
* Coverage (4 rows):
* 1. fn_param_read — `fn f(s: vs) { s.field }`. Single-
* alias receiver. Pre-fix: link
* failure on `field(SB)`. Post-fix:
* deref + offset load. The impl-e1
* fold-blocker exact shape.
* 2. let_binding_read — `let s: vs = (&v): vs; s.field`.
* Single-alias via let-init; the
* explicit cast routes around the
* cstage checker assignability gap
* for chain-depth-1 (see row 3).
* 3. double_alias_read — `type pvt = *vt; type vs = pvt;`
* with `let s: vs = (&v): vs`.
* KEN'S LOOP-PEEL VERIFY GATE:
* chain depth 2 on the receiver, so
* a single-peel implementation would
* stop at N_TNAME("pvt") and miss
* via structlookupchain (which only
* bottoms out when aliaslookup
* returns an N_TNAME that
* structlookup hits — here the next
* hop is N_TPTR, breaking its
* inner-loop guard). The cast in
* the let-init dodges the cstage
* checker's "*vt not assignable to
* vs" through double aliases (see
* SIBLING below).
* 4. pointee_alias_chain — `type vti = vt; type vti2 = vti;
* type vs = *vti2;`. Receiver itself
* is single-aliased; the chain
* depth lives inside the pointee.
* DREW'S INNER-PEEL VERIFY GATE:
* proves structlookupchain's
* aliaslookup loop is sufficient
* without an explicit inner peel.
* If this row reds, the structural
* no-inner-peel claim above is
* wrong and the fix needs an
* inner-side peel too.
*
* Per-row gates: cstage runtime exit, wwstage runtime exit,
* cs.s == ww.s byte-identical (rule-10 stage symmetry).
*
* SIBLING BUGS surfaced during impl — NOT fixed here, filed for
* separate single-class commits (rule 11 split, rule 7 no
* workarounds, task spec "If you find sibling bugs in cgdot: file
* inline"):
*
* (A) cgassign N_DOT TK_ASSIGN on aliased-ptr receiver SILENTLY
* DROPS THE STORE. `s.field = 7` where s: vs = *vt emits
* nothing — wwstage MOVs the rhs into AX and discards.
* Cstage emits the correct `MOVQ off(BP), BX; MOVL AX, (BX)`.
* Same pre-peel pattern as cgdot, at cgenexpr.ww:5160-5168
* (the `if (lkind == nkind.N_TPTR)` arm of the cgassign
* base-IDENT branch). Out of scope for #191 (read-side
* cgdot only); siblings to file as a follow-up class along
* with cgassign compound (+=, -=, etc.) — same shape, same
* drop. Repro:
* type vt = struct { field: i32 };
* type vs = *vt;
* fn setit(s: vs) void = { s.field = 7; };
*
* (B) Chained N_DOT spine (`p.i.x` where p: vs = *outer, outer
* has field i: inner, inner has field x) on aliased-ptr
* receiver also link-fails. The chained spine has dotlhs ==
* N_DOT (not N_IDENT), so #191's branch doesn't fire; the
* chained-spine codepath has its own peel gap. Repro:
* type inner = struct { x: i32 };
* type outer = struct { i: inner };
* type vs = *outer;
* fn callit(p: vs) i32 = { return p.i.x; };
*
* (C) Cstage checker rejects single-deep-alias assignability:
* `let s: vs = &v;` where vs = pvt = *vt errors with
* `init *vt not assignable to declared vs`. Workaround in
* row 3: `(&v): vs` explicit cast. The checker walks single
* aliases but not chains of depth >= 2. Not a wwstage cgen
* bug; cited as the reason row 3's let-init carries an
* explicit cast.
*
* GATE POLARITY: must stay GREEN. A red here means the cgdot
* outer-peel regressed (row 1/2), the loop-peel depth handling
* regressed (row 3), or the inner-chain structlookupchain path
* drifted (row 4).
*/
#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;
}
#define STAGE_CS 1
#define STAGE_WW 2
struct row {
const char *label;
const char *src;
int expected_exit;
int stage_mask;
};
static const struct row rows[] = {
{ "fn_param_read",
"package main;\n"
"type vt = struct { field: i32 };\n"
"type vs = *vt;\n"
"fn callit(s: vs) i32 = { return s.field; };\n"
"export fn main() i32 = {\n"
" let v: vt; v.field = 42;\n"
" return callit(&v);\n"
"};\n",
42,
STAGE_CS | STAGE_WW },
{ "let_binding_read",
"package main;\n"
"type vt = struct { field: i32 };\n"
"type vs = *vt;\n"
"export fn main() i32 = {\n"
" let v: vt; v.field = 42;\n"
" let s: vs = (&v): vs;\n"
" return s.field;\n"
"};\n",
42,
STAGE_CS | STAGE_WW },
{ "double_alias_read",
"package main;\n"
"type vt = struct { field: i32 };\n"
"type pvt = *vt;\n"
"type vs = pvt;\n"
"export fn main() i32 = {\n"
" let v: vt; v.field = 42;\n"
" let s: vs = (&v): vs;\n"
" return s.field;\n"
"};\n",
42,
STAGE_CS | STAGE_WW },
{ "pointee_alias_chain",
"package main;\n"
"type vt = struct { field: i32 };\n"
"type vti = vt;\n"
"type vti2 = vti;\n"
"type vs = *vti2;\n"
"fn callit(s: vs) i32 = { return s.field; };\n"
"export fn main() i32 = {\n"
" let v: vti2; v.field = 42;\n"
" return callit(&v);\n"
"};\n",
42,
STAGE_CS | STAGE_WW },
};
static int
write_source(const char *src_path, const char *src)
{
FILE *f = fopen(src_path, "wb");
if (!f) return -1;
fputs(src, f);
fclose(f);
return 0;
}
static void
cleanup_tmp(const char *tmpdir, const char *base)
{
char p[512];
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s.o", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s.combined.ww", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p);
/* #93: the sep scratch dir + the tmpdir-pinned pkgcache. */
snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir);
if (system(p)) {} /* best-effort */
rmdir(tmpdir);
}
static int
build_via_driver(const char *driver, const char *tmpdir, const char *src)
{
char cmd[1024];
/* #93 sep layout: emit asm to <src-stem>.sepwork/__root.s; pin
* WW_PKGCACHE under tmpdir so out/.pkgcache is untouched. */
snprintf(cmd, sizeof cmd,
"cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep "
"-o \"${b%%.ww}\" \"$b\" 2>/dev/null",
tmpdir, src, tmpdir, driver);
return runwait(cmd);
}
static int
run_row(const char *driver, const struct row *r, int seq)
{
char tmpdir[256], src[256], base[64], outbin[512];
snprintf(tmpdir, sizeof tmpdir, "/tmp/dap_%d_d_%d", getpid(), seq);
snprintf(src, sizeof src, "%s/main769.ww", tmpdir);
snprintf(base, sizeof base, "main769");
mkdir(tmpdir, 0755);
if (write_source(src, r->src) != 0) {
cleanup_tmp(tmpdir, base);
return -1;
}
int rc = -1;
if (build_via_driver(driver, tmpdir, src) == 0) {
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
rc = runwait(outbin);
}
cleanup_tmp(tmpdir, base);
return rc;
}
/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so
* ww_ww writing intermediates next to the source doesn't clobber the
* cstage .s (CLAUDE.md rule 14 phase split). */
static int
asm_byte_identical(const char *cdrv, const char *wdrv,
const struct row *r, int seq)
{
char src[256], tdc[256], tdw[256], base[64], cs[512], ws[512];
snprintf(tdc, sizeof tdc, "/tmp/dap_%d_c_%d", getpid(), seq);
snprintf(tdw, sizeof tdw, "/tmp/dap_%d_w_%d", getpid(), seq);
snprintf(base, sizeof base, "main769");
mkdir(tdc, 0755);
mkdir(tdw, 0755);
snprintf(src, sizeof src, "%s/main769.ww", tdc);
if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; }
int rc = -1;
if (build_via_driver(cdrv, tdc, src) != 0) goto out;
snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base);
snprintf(src, sizeof src, "%s/main769.ww", tdw);
if (write_source(src, r->src) != 0) goto out;
if (build_via_driver(wdrv, tdw, src) != 0) goto out;
snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base);
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
if (fc && fw) {
rc = 0;
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
out:
cleanup_tmp(tdc, base);
cleanup_tmp(tdw, base);
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 cdrv[1024], wdrv[1024];
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;
int wwpresent = (access(wdrv, X_OK) == 0);
int seq = 0;
for (int i = 0; i < n; i++) {
if (rows[i].stage_mask & STAGE_CS) {
total++;
int got = run_row(cdrv, &rows[i], seq++);
if (got != rows[i].expected_exit) {
fprintf(stderr,
"dot_aliased_ptr[cstage run][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].expected_exit);
fail++;
}
}
if (wwpresent && (rows[i].stage_mask & STAGE_WW)) {
total++;
int got = run_row(wdrv, &rows[i], seq++);
if (got != rows[i].expected_exit) {
fprintf(stderr,
"dot_aliased_ptr[wwstage run][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].expected_exit);
fail++;
}
total++;
if (asm_byte_identical(cdrv, wdrv, &rows[i], seq++) != 0) {
fprintf(stderr,
"dot_aliased_ptr[byte-id][%s]: cstage vs wwstage asm differs\n",
rows[i].label);
fail++;
}
}
}
if (!wwpresent)
fprintf(stderr, "dot_aliased_ptr: skip wwstage (no %s)\n", wdrv);
if (fail) {
fprintf(stderr, "dot_aliased_ptr: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("dot_aliased_ptr: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,383 +0,0 @@
/*
* 770_return_tagged_forward — project #201: wwstage cgreturn forwarding
* of a matching tagged-union call result. cstage cgen.c:8007-8014
* detects passthrough via Type-based `istagged && (vu == rt ||
* type_eq(vt, cg_ret_type))`, so any callee shape (IDENT, DOT, deref-
* call) that returns the same tagged type triggers a direct AX/DX/CX/R8
* forward. Wwstage's cgreturn was instead detecting forwardtagged by
* walking the CALLEE NAME — only N_IDENT and N_DOT callees were
* inspected; an N_UN(TK_STAR) deref-call fell through to the variant-
* tag synthesis path, which clobbered AX→DX and zeroed CX/R8/AX before
* RET, wiping the just-returned tagged-ABI words.
*
* Trigger (impl-e1-resume STOP / 994_w6c_ww byte-id red):
* fn st_read(s: vstream, ...) (size | io.eof | io.error) =
* { return (*r)(s, buf); };
*
* Fix: selfhost/cmd/wcc/cgenstmt.ww cgreturn — replace the IDENT/DOT
* name-keyed lookup with a TYPE-BASED predicate on the checker-stamped
* tinfo: peel TY_NAMED from both rhs.type_ and c.fnret.type_, and
* forward when the peeled rhs tinfo is TY_TAGGED and pointer-identical
* to the peeled fnret tinfo. Identity is sufficient because
* tinfofornode memoizes per typedecl (check.ww:1566-1568 "every TNAME
* resolving to the same decl yields the SAME tinfo pointer"); the
* structural fallback in cstage's type_eq is gated by #178 (no tinfo-
* level structural-eq helper today, and typeeqast's TY_TAGGED arm
* conservatively returns false).
*
* Coverage (6 rows):
* 1. forward_named_match — IDENT callee, both fns return the same
* NAMED tagged alias. Pre-fix: forwarded
* via the old name-keyed path. Post-fix:
* same forward via the new TYPE path.
* Asm regression gate.
* 2. widen_subset — IDENT callee returns a concrete variant
* (i32), fnret is the tagged union. Must
* KEEP the widen-shuffle (AX→DX, zero
* CX/R8/AX). Asserts the new predicate
* correctly rejects non-matching shapes.
* 3. deref_call_match — `(*r)()` callee, both fns return the
* same NAMED tagged alias. THE BUG row.
* Pre-fix wwstage: spurious tag-synth
* shuffle after CALL AX. Post-fix:
* direct CALL AX + RET.
* 4. scalar_return — fnret is i32 (not tagged); cgreturn
* skips the istaggedtype gate entirely.
* Sanity that the fix doesn't perturb
* the unrelated scalar path.
* 5. nested_call_forward — outer call wraps inner scalar call,
* both fns return the same NAMED tagged
* alias. Forward fires on the outer
* N_CALL despite the nested arg.
* 6. cross_module_forward — caller is in `main`, callee+type live
* in an imported module. drew-add: gates
* cross-module TY_NAMED identity (every
* reference to `mtag770.result` resolves
* to the SAME NAMED tinfo via aliassym +
* sym.type_ cache; #191 lineage). If a
* regressor splits NAMED tinfo per
* reference site, this row reds first.
*
* Per-row gates: cstage runtime exit, wwstage runtime exit, cs.s ==
* ww.s byte-identical (rule-10 stage symmetry).
*
* GATE POLARITY: must stay GREEN. A red on row 3/5/6 means the
* TYPE-based forward predicate dropped a callee shape; row 2 reds if
* the predicate fires too aggressively (non-matching rhs incorrectly
* forwarded); row 1/4 red means structural regression in adjacent
* cgreturn arms.
*/
#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;
}
#define STAGE_CS 1
#define STAGE_WW 2
struct row {
const char *label;
const char *src;
/* Optional secondary-module source (row 6 cross_module). */
const char *modname;
const char *modsrc;
int expected_exit;
int stage_mask;
};
static const struct row rows[] = {
{ "forward_named_match",
"package main;\n"
"type result = (i32 | str);\n"
"fn ra(x: i32) result = { return x + 1: i32; };\n"
"fn cw(x: i32) result = { return ra(x); };\n"
"export fn main() i32 = {\n"
" let r: result = cw(40);\n"
" match (r) {\n"
" case let i: i32 => return i;\n"
" case let s: str => return 0;\n"
" };\n"
"};\n",
NULL, NULL,
41,
STAGE_CS | STAGE_WW },
{ "widen_subset",
"package main;\n"
"type result = (i32 | str);\n"
"fn ri(x: i32) i32 = { return x + 1; };\n"
"fn cw(x: i32) result = { return ri(x); };\n"
"export fn main() i32 = {\n"
" let r: result = cw(40);\n"
" match (r) {\n"
" case let i: i32 => return i;\n"
" case let s: str => return 0;\n"
" };\n"
"};\n",
NULL, NULL,
41,
STAGE_CS | STAGE_WW },
{ "deref_call_match",
"package main;\n"
"type result = (i32 | str);\n"
"fn ra(x: i32) result = { return x + 1: i32; };\n"
"fn cw(r: *fn(x: i32) result, x: i32) result = { return (*r)(x); };\n"
"export fn main() i32 = {\n"
" let p: *fn(x: i32) result = &ra;\n"
" let v: result = cw(p, 40);\n"
" match (v) {\n"
" case let i: i32 => return i;\n"
" case let s: str => return 0;\n"
" };\n"
"};\n",
NULL, NULL,
41,
STAGE_CS | STAGE_WW },
{ "scalar_return",
"package main;\n"
"fn ri(x: i32) i32 = { return x + 1; };\n"
"fn cw(x: i32) i32 = { return ri(x); };\n"
"export fn main() i32 = { return cw(40); };\n",
NULL, NULL,
41,
STAGE_CS | STAGE_WW },
{ "nested_call_forward",
"package main;\n"
"type result = (i32 | str);\n"
"fn ri(x: i32) i32 = { return x + 1; };\n"
"fn wrap(x: i32) result = { return x: i32; };\n"
"fn cw(x: i32) result = { return wrap(ri(x)); };\n"
"export fn main() i32 = {\n"
" let r: result = cw(39);\n"
" match (r) {\n"
" case let i: i32 => return i;\n"
" case let s: str => return 0;\n"
" };\n"
"};\n",
NULL, NULL,
40,
STAGE_CS | STAGE_WW },
{ "cross_module_forward",
"package main;\n"
"import mtag770;\n"
"fn cw(x: i32) mtag770.result = { return mtag770.ra(x); };\n"
"export fn main() i32 = {\n"
" let r: mtag770.result = cw(40);\n"
" match (r) {\n"
" case let i: i32 => return i;\n"
" case let s: str => return 0;\n"
" };\n"
"};\n",
"mtag770",
"package mtag770;\n"
"export type result = (i32 | str);\n"
"export fn ra(x: i32) result = { return x + 1: i32; };\n",
41,
STAGE_CS | STAGE_WW },
};
static int
write_source(const char *path, const char *src)
{
FILE *f = fopen(path, "wb");
if (!f) return -1;
fputs(src, f);
fclose(f);
return 0;
}
static int
write_sources(const struct row *r, const char *tmpdir, const char *src)
{
if (r->modname != NULL) {
char moddir[256], modfile[512];
snprintf(moddir, sizeof moddir, "%s/%s", tmpdir, r->modname);
snprintf(modfile, sizeof modfile, "%s/%s.ww",
moddir, r->modname);
mkdir(moddir, 0755);
if (write_source(modfile, r->modsrc) != 0) return -1;
}
return write_source(src, r->src);
}
static void
cleanup_tmp(const struct row *r, const char *tmpdir, const char *base)
{
char p[512];
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p);
/* #93: the sep scratch + tmpdir-pinned pkgcache + concat scratch. */
snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc %s/all_cs.s %s/all_ww.s",
tmpdir, base, tmpdir, tmpdir, tmpdir); if (system(p)) {}
snprintf(p, sizeof p, "%s/%s.o", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s.combined.ww", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p);
if (r->modname != NULL) {
char moddir[256];
snprintf(moddir, sizeof moddir, "%s/%s", tmpdir, r->modname);
snprintf(p, sizeof p, "%s/%s.ww", moddir, r->modname); unlink(p);
snprintf(p, sizeof p, "%s/%s.s", moddir, r->modname); unlink(p);
snprintf(p, sizeof p, "%s/%s.o", moddir, r->modname); unlink(p);
snprintf(p, sizeof p, "%s/%s.combined.ww", moddir, r->modname); unlink(p);
rmdir(moddir);
}
rmdir(tmpdir);
}
static int
build_via_driver(const char *driver, const char *tmpdir, const char *src)
{
/* #93 sep layout: emit asm to <src-stem>.sepwork/<pkg>.s; pin
* WW_PKGCACHE under tmpdir so out/.pkgcache is untouched. */
char cmd[1280];
snprintf(cmd, sizeof cmd,
"cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep "
"-o \"${b%%.ww}\" \"$b\" 2>/dev/null",
tmpdir, src, tmpdir, driver);
return runwait(cmd);
}
static int
run_row(const char *driver, const struct row *r, int seq)
{
char tmpdir[256], src[512], base[64], outbin[768];
snprintf(tmpdir, sizeof tmpdir, "/tmp/rtf_%d_d_%d", getpid(), seq);
snprintf(src, sizeof src, "%s/main770.ww", tmpdir);
snprintf(base, sizeof base, "main770");
mkdir(tmpdir, 0755);
if (write_sources(r, tmpdir, src) != 0) {
cleanup_tmp(r, tmpdir, base);
return -1;
}
int rc = -1;
if (build_via_driver(driver, tmpdir, src) == 0) {
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
rc = runwait(outbin);
}
cleanup_tmp(r, tmpdir, base);
return rc;
}
/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so
* ww_ww writing intermediates next to the source doesn't clobber the
* cstage .s (CLAUDE.md rule 14 phase split). */
static int
asm_byte_identical(const char *cdrv, const char *wdrv,
const struct row *r, int seq)
{
char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512];
snprintf(tdc, sizeof tdc, "/tmp/rtf_%d_c_%d", getpid(), seq);
snprintf(tdw, sizeof tdw, "/tmp/rtf_%d_w_%d", getpid(), seq);
snprintf(base, sizeof base, "main770");
mkdir(tdc, 0755);
mkdir(tdw, 0755);
snprintf(src, sizeof src, "%s/main770.ww", tdc);
if (write_sources(r, tdc, src) != 0) { cleanup_tmp(r, tdc, base); cleanup_tmp(r, tdw, base); return -1; }
int rc = -1;
if (build_via_driver(cdrv, tdc, src) != 0) goto out;
/* #93: root + each imported pkg now compile to separate
* <base>.sepwork/<pkg>.s; concat (sorted glob, identical set both
* stages) for the full-asm byte-id compare. */
snprintf(cs, sizeof cs, "%s/all_cs.s", tdc);
{ char cc[1280]; snprintf(cc, sizeof cc,
"cat %s/%s.sepwork/*.s > %s 2>/dev/null", tdc, base, cs);
if (system(cc)) {} }
snprintf(src, sizeof src, "%s/main770.ww", tdw);
if (write_sources(r, tdw, src) != 0) goto out;
if (build_via_driver(wdrv, tdw, src) != 0) goto out;
snprintf(ws, sizeof ws, "%s/all_ww.s", tdw);
{ char cc[1280]; snprintf(cc, sizeof cc,
"cat %s/%s.sepwork/*.s > %s 2>/dev/null", tdw, base, ws);
if (system(cc)) {} }
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
if (fc && fw) {
rc = 0;
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
out:
cleanup_tmp(r, tdc, base);
cleanup_tmp(r, tdw, base);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[512];
if (bin[0] != '/') {
char cwd[256];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[640], wdrv[640];
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;
int wwpresent = (access(wdrv, X_OK) == 0);
int seq = 0;
for (int i = 0; i < n; i++) {
if (rows[i].stage_mask & STAGE_CS) {
total++;
int got = run_row(cdrv, &rows[i], seq++);
if (got != rows[i].expected_exit) {
fprintf(stderr,
"return_tagged_forward[cstage run][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].expected_exit);
fail++;
}
}
if (wwpresent && (rows[i].stage_mask & STAGE_WW)) {
total++;
int got = run_row(wdrv, &rows[i], seq++);
if (got != rows[i].expected_exit) {
fprintf(stderr,
"return_tagged_forward[wwstage run][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].expected_exit);
fail++;
}
total++;
if (asm_byte_identical(cdrv, wdrv, &rows[i], seq++) != 0) {
fprintf(stderr,
"return_tagged_forward[byte-id][%s]: cstage vs wwstage asm differs\n",
rows[i].label);
fail++;
}
}
}
if (!wwpresent)
fprintf(stderr, "return_tagged_forward: skip wwstage (no %s)\n", wdrv);
if (fail) {
fprintf(stderr, "return_tagged_forward: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("return_tagged_forward: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,338 +0,0 @@
/*
* 772_typeassert_nonident — project #200: wwstage cgtypeassert non-IDENT
* scrutinee bug. The pre-fix `e as T` on a non-N_IDENT scrutinee (direct
* call result, arr[i], p.field, ?, paren-wrap of any of those) left
* scrutoff=0; the tag-load fell on (BP) — the saved-BP word — and the
* payload-load on +8(BP) — the return address — instead of the spilled
* tagged-return ABI words. wwstage produced garbage (exit 220 in the
* repro vs cstage's 42).
*
* Fix (selfhost/cmd/wcc/cgenexpr.ww:368-414 cgtypeassert): mirror cstage
* cmd/w6c/cgen.c:6300-6316 N_TYPEASSERT non-IDENT arm. Add an `else`
* branch after the existing N_IDENT path: resolve the tagged type via
* matchscrutt, alloc an `@asrt_spill` slot via matchspillsz/localalloc,
* cgexpr the LHS, then spill AX→+0 (tag), DX→+8 (word0), CX→+16
* (word1) guarded on spill > 16. Mirrors cgmatch's spill (cgenexpr.ww:
* 1422-1460). The R8→+24 path (cgmatch shape) is intentionally omitted
* to mirror cstage's cgtypeassert exactly (rule-10 byte-id); the cstage
* twin gap is filed inline as a sibling task.
*
* Coverage (7 rows):
* 1. call_as_size — `f() as size`. The repro shape: direct call
* result, basic-size payload. Pre-fix
* wwstage: garbage AX from (BP). Post-fix:
* 42.
* 2. call_as_str — `f() as str`. Non-IDENT, multi-word payload
* (AX=ptr / BX=len). Tests CX→+16 spill +
* the `isstrtype` post-spill BX load.
* 3. call_as_namedvoid — `f() as nvariant` where nvariant is a
* named-void alias. Tag-check fires; the
* payload load is a 0-byte no-op (void
* variant has size 0; no consumer reads AX).
* Drew-add.
* 4. call_as_fnptr — `f() as *fn(i32) i32`. Fn-ptr variant
* (8B word0 = pointer). Round-trips through
* spill→tag-check→MOVQ +8 → AX.
* 5. call_as_u8 — `f() as u8`. Narrow scalar. The callee
* zero/sign-extended the value to 8B on
* return; MOVQ store + MOVQ read round-trips
* it. Ken-add: confirms no narrow-truncation
* on the payload load.
* 6. call_as_i16 — `f() as i16`. Narrow signed scalar. Tests
* negative round-trip through the spill slot
* (callee sign-extended to 8B; MOVQ
* preserves). Ken-add sibling of row 5.
* 7. branched_call_as — callee returns a runtime-chosen variant
* (the success branch); `f(true) as size`.
* Tests that the spill+tag-check path
* doesn't lose the callee's selected tag.
*
* Per-row gates: cstage runtime exit, wwstage runtime exit, cs.s ==
* ww.s byte-identical (rule-10 stage symmetry).
*
* GATE POLARITY: must stay GREEN. A red on rows 1-4 or 7 means the
* spill path is broken (non-IDENT scrutinee returned to the BP/RIP
* read). Rows 5/6 red means narrow-scalar round-trip miscompiled at
* the payload load (would surface if a future SIB regresses MOVQ to a
* narrow load that drops the callee's extension).
*/
#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;
}
#define STAGE_CS 1
#define STAGE_WW 2
struct row {
const char *label;
const char *src;
int expected_exit;
int stage_mask;
};
static const struct row rows[] = {
{ "call_as_size",
"package main;\n"
"fn f(b: bool) (size | str) = {\n"
" if (b) { return 42: size; };\n"
" return \"x\";\n"
"};\n"
"export fn main() i32 = {\n"
" let n = f(true) as size;\n"
" return n: i32;\n"
"};\n",
42,
STAGE_CS | STAGE_WW },
{ "call_as_str",
"package main;\n"
"fn f(b: bool) (size | str) = {\n"
" if (b) { return \"AB\"; };\n"
" return 0: size;\n"
"};\n"
"export fn main() i32 = {\n"
" let s = f(true) as str;\n"
" return s.len: i32 + 40;\n"
"};\n",
42,
STAGE_CS | STAGE_WW },
{ "call_as_namedvoid",
"package main;\n"
"type nv = void;\n"
"fn f(b: bool) (size | nv) = {\n"
" if (b) { return void: nv; };\n"
" return 0: size;\n"
"};\n"
"export fn main() i32 = {\n"
" let _ = f(true) as nv;\n"
" return 42;\n"
"};\n",
42,
STAGE_CS | STAGE_WW },
{ "call_as_fnptr",
/* #193: explicit deref required to call a fn-ptr (`(*p)(args)`).
* The probe is wwstage cgtypeassert non-IDENT spill; deref-call
* shape is orthogonal. */
"package main;\n"
"fn g(x: i32) i32 = { return x + 1; };\n"
"fn f(b: bool) (*fn(x: i32) i32 | size) = {\n"
" if (b) { return &g; };\n"
" return 0: size;\n"
"};\n"
"export fn main() i32 = {\n"
" let p = f(true) as *fn(x: i32) i32;\n"
" return (*p)(41);\n"
"};\n",
42,
STAGE_CS | STAGE_WW },
{ "call_as_u8",
"package main;\n"
"fn f(b: bool) (u8 | str) = {\n"
" if (b) { return 42: u8; };\n"
" return \"x\";\n"
"};\n"
"export fn main() i32 = {\n"
" let n = f(true) as u8;\n"
" return n: i32;\n"
"};\n",
42,
STAGE_CS | STAGE_WW },
{ "call_as_i16",
"package main;\n"
"fn f(b: bool) (i16 | str) = {\n"
" if (b) { return -1: i16; };\n"
" return \"x\";\n"
"};\n"
"export fn main() i32 = {\n"
" let n = f(true) as i16;\n"
" if (n: i32 == -1) { return 42; };\n"
" return 1;\n"
"};\n",
42,
STAGE_CS | STAGE_WW },
{ "branched_call_as",
"package main;\n"
"fn f(b: bool) (size | str) = {\n"
" if (b) { return 42: size; };\n"
" return \"x\";\n"
"};\n"
"export fn main() i32 = {\n"
" let pick: bool = true;\n"
" let n = f(pick) as size;\n"
" return n: i32;\n"
"};\n",
42,
STAGE_CS | STAGE_WW },
};
static int
write_source(const char *path, const char *src)
{
FILE *f = fopen(path, "wb");
if (!f) return -1;
fputs(src, f);
fclose(f);
return 0;
}
static void
cleanup_tmp(const char *tmpdir, const char *base)
{
char p[512];
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s.o", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s.combined.ww", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p);
/* #93: the sep scratch dir + the tmpdir-pinned pkgcache. */
snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir);
if (system(p)) {} /* best-effort */
rmdir(tmpdir);
}
static int
build_via_driver(const char *driver, const char *tmpdir, const char *src)
{
char cmd[1024];
/* #93 sep layout: emit asm to <src-stem>.sepwork/__root.s; pin
* WW_PKGCACHE under tmpdir so out/.pkgcache is untouched. */
snprintf(cmd, sizeof cmd,
"cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep "
"-o \"${b%%.ww}\" \"$b\" 2>/dev/null",
tmpdir, src, tmpdir, driver);
return runwait(cmd);
}
static int
run_row(const char *driver, const struct row *r, int seq)
{
char tmpdir[256], src[512], base[64], outbin[768];
snprintf(tmpdir, sizeof tmpdir, "/tmp/tan_%d_d_%d", getpid(), seq);
snprintf(base, sizeof base, "main772");
snprintf(src, sizeof src, "%s/%s.ww", tmpdir, base);
mkdir(tmpdir, 0755);
if (write_source(src, r->src) != 0) { cleanup_tmp(tmpdir, base); return -1; }
int rc = -1;
if (build_via_driver(driver, tmpdir, src) == 0) {
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
rc = runwait(outbin);
}
cleanup_tmp(tmpdir, base);
return rc;
}
/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so
* ww_ww writing intermediates next to the source doesn't clobber the
* cstage .s (CLAUDE.md rule 14 phase split). */
static int
asm_byte_identical(const char *cdrv, const char *wdrv,
const struct row *r, int seq)
{
char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512];
snprintf(tdc, sizeof tdc, "/tmp/tan_%d_c_%d", getpid(), seq);
snprintf(tdw, sizeof tdw, "/tmp/tan_%d_w_%d", getpid(), seq);
snprintf(base, sizeof base, "main772");
mkdir(tdc, 0755);
mkdir(tdw, 0755);
snprintf(src, sizeof src, "%s/%s.ww", tdc, base);
if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; }
int rc = -1;
if (build_via_driver(cdrv, tdc, src) != 0) goto out;
snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base);
snprintf(src, sizeof src, "%s/%s.ww", tdw, base);
if (write_source(src, r->src) != 0) goto out;
if (build_via_driver(wdrv, tdw, src) != 0) goto out;
snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base);
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
if (fc && fw) {
rc = 0;
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
out:
cleanup_tmp(tdc, base);
cleanup_tmp(tdw, base);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[512];
if (bin[0] != '/') {
char cwd[256];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[640], wdrv[640];
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;
int wwpresent = (access(wdrv, X_OK) == 0);
int seq = 0;
for (int i = 0; i < n; i++) {
if (rows[i].stage_mask & STAGE_CS) {
total++;
int got = run_row(cdrv, &rows[i], seq++);
if (got != rows[i].expected_exit) {
fprintf(stderr,
"typeassert_nonident[cstage run][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].expected_exit);
fail++;
}
}
if (wwpresent && (rows[i].stage_mask & STAGE_WW)) {
total++;
int got = run_row(wdrv, &rows[i], seq++);
if (got != rows[i].expected_exit) {
fprintf(stderr,
"typeassert_nonident[wwstage run][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].expected_exit);
fail++;
}
total++;
if (asm_byte_identical(cdrv, wdrv, &rows[i], seq++) != 0) {
fprintf(stderr,
"typeassert_nonident[byte-id][%s]: cstage vs wwstage asm differs\n",
rows[i].label);
fail++;
}
}
}
if (!wwpresent)
fprintf(stderr, "typeassert_nonident: skip wwstage (no %s)\n", wdrv);
if (fail) {
fprintf(stderr, "typeassert_nonident: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("typeassert_nonident: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,319 +0,0 @@
/*
* 773_isas_spread_variant — project #198: wwstage checker `is`/`as`
* variant lookup walked the unflattened AST u.list (casevariantin via
* typeeqast streq), so any variant introduced via a `...inner` spread
* was invisible to the checker and rejected as "is/as: not a variant
* of operand". Repro: `r: (size | io.eof | ...io.error); r is
* io.underread;` — io.underread is in io.error's params chain, which
* gets spliced into the parent union at tinfofornode L1827-1836, but
* the AST u.list still holds the single `...io.error` entry that
* streq("io.underread", "io.error") rejects.
*
* Fix: selfhost/cmd/wcc/check.ww checkisas (lines ~3441-3466). Route
* through tinfo.params via flatvariantidxt (the same Phase-N helper
* #179 cgmatch and #66 cgtagvariantidx use); fall back to casevariantin
* AST walk when tinfo isn't available (defensive — non-#198 path stays
* as-is). Mirrors cstage cmd/wcc/check.c:1662-1675 u->params + type_eq.
* Closes the cgen-drain mini-cluster (#201 → #199 → #200 → #198).
*
* Coverage (5 rows):
* 1. spread_is_inline_variant — `(size | io.eof | ...io.error)`,
* `r is io.underread`. THE BUG: pre-
* fix wwstage rejects with "is/as:
* not a variant of operand
* (io.underread)". Post-fix accepts;
* cgen tag-compare hits the flatten-
* spread index. No byte-id: layout-
* asymmetry on `...wrapper` (cstage
* flattens at resolve_type, wwstage
* computes maxsz off vt.size of the
* un-spliced alias — sibling task,
* not blocking checker correctness).
* 2. direct_cross_mod_tagged — `(size | io.eof | io.error)`,
* `r is io.error`. Direct cross-mod
* tagged variant (no spread). Regr-
* ession gate: AST-streq path always
* matched this; the new tinfo path
* must too. byte-id ON.
* 3. cross_mod_named_void — `(size | io.eof)`, `r is io.eof`.
* Cross-mod NAMED-void variant. Regr-
* ession gate for the io.eof shape
* used pervasively in the io fold.
* byte-id ON.
* 4. same_module_variant — `(i32 | str)`, `r is i32`. Bare
* same-module primitive variant.
* Lowest-bar regression gate: no
* cross-mod, no spread, no NAMED.
* byte-id ON.
* 5. spread_as_inline_payload — `(size | io.eof | ...io.error)`,
* `r as io.underread`. `as` twin of
* row 1: same checker path
* (checkisas dispatches on both
* N_TYPETEST and N_TYPEASSERT), with
* the payload-extract runtime gate.
* No byte-id (sibling, same as row 1).
*
* Per-row gates: cstage runtime exit, wwstage runtime exit, cs.s ==
* ww.s byte-identical when byte_id != 0.
*
* GATE POLARITY: must stay GREEN. Rows 1/5 red means the tinfo.params
* routing dropped a spread-flattened variant; row 2 red means the
* direct cross-mod path broke; row 3 red means the NAMED-void path
* broke; row 4 red means a same-module / same-package regression.
*/
#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;
}
#define STAGE_CS 1
#define STAGE_WW 2
struct row {
const char *label;
const char *src;
int expected_exit;
int stage_mask;
int byte_id;
};
static const struct row rows[] = {
{ "spread_is_inline_variant",
"package main;\n"
"import io;\n"
"type rsh = (size | io.eof | ...io.error);\n"
"export fn main() i32 = {\n"
" let r: rsh = 42: size;\n"
" if (r is io.underread) { return 1; };\n"
" if (r is io.eof) { return 2; };\n"
" if (r is size) { return 3; };\n"
" return 0;\n"
"};\n",
3,
STAGE_CS | STAGE_WW, 0 },
{ "direct_cross_mod_tagged",
"package main;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let r: (size | io.eof | io.error) = 42: size;\n"
" if (r is io.error) { return 1; };\n"
" if (r is io.eof) { return 2; };\n"
" if (r is size) { return 3; };\n"
" return 0;\n"
"};\n",
3,
STAGE_CS | STAGE_WW, 1 },
{ "cross_mod_named_void",
"package main;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let r: (size | io.eof) = 42: size;\n"
" if (r is io.eof) { return 1; };\n"
" if (r is size) { return 3; };\n"
" return 0;\n"
"};\n",
3,
STAGE_CS | STAGE_WW, 1 },
{ "same_module_variant",
"package main;\n"
"export fn main() i32 = {\n"
" let r: (i32 | str) = 3: i32;\n"
" if (r is i32) { return 3; };\n"
" if (r is str) { return 1; };\n"
" return 0;\n"
"};\n",
3,
STAGE_CS | STAGE_WW, 1 },
{ "spread_as_inline_payload",
"package main;\n"
"import io;\n"
"type rsh = (size | io.eof | ...io.error);\n"
"fn pick(b: i32) rsh = {\n"
" if (b == 1) { let u: io.underread = 7: i32: io.underread; return u; };\n"
" return 42: size;\n"
"};\n"
"export fn main() i32 = {\n"
" let r = pick(1);\n"
" let u = r as io.underread;\n"
" return u: i32;\n"
"};\n",
7,
STAGE_CS | STAGE_WW, 0 },
};
static int
write_source(const char *path, const char *src)
{
FILE *f = fopen(path, "wb");
if (!f) return -1;
fputs(src, f);
fclose(f);
return 0;
}
static void
cleanup_tmp(const char *tmpdir, const char *base)
{
char p[512];
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s.o", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s.combined.ww", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p);
/* #93: the sep scratch dir + the tmpdir-pinned pkgcache. */
snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir);
if (system(p)) {} /* best-effort */
rmdir(tmpdir);
}
static int
build_via_driver(const char *driver, const char *tmpdir, const char *src)
{
char cmd[1024];
/* #93 sep layout: emit asm to <src-stem>.sepwork/__root.s; pin
* WW_PKGCACHE under tmpdir so out/.pkgcache is untouched. */
snprintf(cmd, sizeof cmd,
"cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep "
"-o \"${b%%.ww}\" \"$b\" 2>/dev/null",
tmpdir, src, tmpdir, driver);
return runwait(cmd);
}
static int
run_row(const char *driver, const struct row *r, int seq)
{
char tmpdir[256], src[512], base[64], outbin[768];
snprintf(tmpdir, sizeof tmpdir, "/tmp/isas_%d_d_%d", getpid(), seq);
snprintf(src, sizeof src, "%s/main773.ww", tmpdir);
snprintf(base, sizeof base, "main773");
mkdir(tmpdir, 0755);
if (write_source(src, r->src) != 0) {
cleanup_tmp(tmpdir, base);
return -1;
}
int rc = -1;
if (build_via_driver(driver, tmpdir, src) == 0) {
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
rc = runwait(outbin);
}
cleanup_tmp(tmpdir, base);
return rc;
}
/* Parallel tmpdirs per CLAUDE.md rule 14 — ww_ww writes intermediates
* next to the source so concurrent cstage/wwstage builds against the
* same path would race. */
static int
asm_byte_identical(const char *cdrv, const char *wdrv,
const struct row *r, int seq)
{
char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512];
snprintf(tdc, sizeof tdc, "/tmp/isas_%d_c_%d", getpid(), seq);
snprintf(tdw, sizeof tdw, "/tmp/isas_%d_w_%d", getpid(), seq);
snprintf(base, sizeof base, "main773");
mkdir(tdc, 0755);
mkdir(tdw, 0755);
snprintf(src, sizeof src, "%s/main773.ww", tdc);
if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; }
int rc = -1;
if (build_via_driver(cdrv, tdc, src) != 0) goto out;
snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base);
snprintf(src, sizeof src, "%s/main773.ww", tdw);
if (write_source(src, r->src) != 0) goto out;
if (build_via_driver(wdrv, tdw, src) != 0) goto out;
snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base);
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
if (fc && fw) {
rc = 0;
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
out:
cleanup_tmp(tdc, base);
cleanup_tmp(tdw, base);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[512];
if (bin[0] != '/') {
char cwd[256];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[640], wdrv[640];
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;
int wwpresent = (access(wdrv, X_OK) == 0);
int seq = 0;
for (int i = 0; i < n; i++) {
if (rows[i].stage_mask & STAGE_CS) {
total++;
int got = run_row(cdrv, &rows[i], seq++);
if (got != rows[i].expected_exit) {
fprintf(stderr,
"isas_spread_variant[cstage run][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].expected_exit);
fail++;
}
}
if (wwpresent && (rows[i].stage_mask & STAGE_WW)) {
total++;
int got = run_row(wdrv, &rows[i], seq++);
if (got != rows[i].expected_exit) {
fprintf(stderr,
"isas_spread_variant[wwstage run][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].expected_exit);
fail++;
}
if (rows[i].byte_id) {
total++;
if (asm_byte_identical(cdrv, wdrv, &rows[i], seq++) != 0) {
fprintf(stderr,
"isas_spread_variant[byte-id][%s]: cstage vs wwstage asm differs\n",
rows[i].label);
fail++;
}
}
}
}
if (!wwpresent)
fprintf(stderr, "isas_spread_variant: skip wwstage (no %s)\n", wdrv);
if (fail) {
fprintf(stderr, "isas_spread_variant: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("isas_spread_variant: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,334 +0,0 @@
/*
* 774_tagged_widen_named_variant — project #205: cstage type_assignable +
* wwstage isassignable tagged→tagged arm misses NAMED-variant nominal
* match. Before the fix, `let e: wrapper = u; let r: (size|eof|wrapper) = e;`
* REJECTED at cstage's checker (wwstage accepted permissively but cgen
* silently miscompiled — separate fold via #199b). The subset loop
* walked wrapper's leaves (unsupported, underread, nomem) against dst
* (size, eof, wrapper) and found none → return 0.
*
* Fix (cmd/wcc/type.c:327 + selfhost/cmd/wcc/check.ww:3087): insert a
* NAMED-variant nominal compare BEFORE the subset loop in the
* tagged→tagged arm. When src is a NAMED-tagged wrapper and dst has a
* direct NAMED-tagged variant equal to src, accept by nominal identity.
* Mirrors the concrete→tagged arm at type.c:316 (#199 α). SSoT with
* `is`/`as` non-recursive variant lookup (#198 family). Wwstage mirrors
* the structural insertion; the existing permissive `*confident = false;
* return true;` tail is preserved (deferred-tightening per #202).
*
* Cgen's tag-remap for the wrapper-as-whole case still maps src
* variants to dst tag 0 (cg_widen_tag_remap walks per-variant); the
* wrapped-slot layout for `dst.tag = variant_idx, dst.payload = src`
* is the deferred #199b future-work. Probes verify checker-accept +
* runtime exit-clean only; they do NOT inspect the resulting variant
* tag of the widen target.
*
* Coverage (5 rows):
* 1. accept_wrapper_variant — BUG-REPRO. `let e: wrapper = u;
* return e;` where return type contains wrapper as direct NAMED
* variant. MUST now ACCEPT + run.
* 2. accept_nested_wrapper — two layers of NAMED-tagged wrap:
* `inner -> outer -> (size|outer)`. Each widen exercises the
* tagged→tagged NAMED nominal compare.
* 3. accept_pure_leaf_subset — REGRESSION GATE. Tagged→tagged
* where every src variant is directly in dst (no wrapper). The
* classic subset path — must keep working.
* 4. reject_concrete_unrelated — REJECTION GATE. Concrete `a` into
* `(b|c)` where a is NOT in dst. Verifies the fix didn't widen
* the concrete→tagged arm's discipline (#199 α stays intact).
* 5. branched_callee_widen — fn returns (size|eof|wrapper)
* from one of two paths (the wrapper-widen and a flat scalar
* variant). Verifies branched runtime, not just constant-fold.
*
* Per-row gates: cstage compile + runtime, wwstage compile + runtime,
* cs.s == ww.s byte-identical (rule-10). Row 4 uses EXPECT_REJECT to
* indicate "build must fail" — driver invocation returns non-zero.
*/
#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;
}
#define STAGE_CS 1
#define STAGE_WW 2
#define EXPECT_REJECT (-2)
struct row {
const char *label;
const char *src;
int expected_exit; /* EXPECT_REJECT means build must fail */
int stage_mask;
int byte_id; /* run cs.s == ww.s diff (works rows only) */
};
static const struct row rows[] = {
{ "accept_wrapper_variant",
"package main;\n"
"type unsupported = !void;\n"
"type underread = !i32;\n"
"type nomem = !void;\n"
"type wrapper = !(unsupported | underread | nomem);\n"
"type eof = void;\n"
"fn f() (size | eof | wrapper) = {\n"
" let u: unsupported;\n"
" let e: wrapper = u;\n"
" return e;\n"
"};\n"
"export fn main() i32 = { let r = f(); return 41; };\n",
41,
STAGE_CS | STAGE_WW, 1 },
{ "accept_nested_wrapper",
"package main;\n"
"type ax = !void;\n"
"type inner = !(ax);\n"
"type outer = !(inner);\n"
"fn f() (size | outer) = {\n"
" let a: ax;\n"
" let i: inner = a;\n"
" let o: outer = i;\n"
" return o;\n"
"};\n"
"export fn main() i32 = { let r = f(); return 42; };\n",
42,
STAGE_CS | STAGE_WW, 1 },
/* Row 3 dst uses an all-!void variant set (8B slot, tag-only) to
* keep ssz == slot_sz at cgwidentaggedstorebp. A wider dst
* (e.g. `(ax|bx|size)`) hits a pre-existing wwstage cgen gap: the
* call-ABI branch unconditionally stores DX/CX/R8 at write_off+8
* when slot_sz > 8, ignoring src ssz, while cstage zero-pads when
* ssz < sz (cmd/w6c/cgen.c:1788-1795 vs selfhost/cmd/wcc/cgenutil.ww
* cgwidentaggedstorebp:2680-2700). Sibling to #202 (wwstage
* cgen-side asymmetry); deferred per #199b layout family. */
{ "accept_pure_leaf_subset",
"package main;\n"
"type ax = !void;\n"
"type bx = !void;\n"
"type cx = !void;\n"
"fn small() (ax | bx) = { let u: ax; return u; };\n"
"export fn main() i32 = {\n"
" let r: (ax | bx | cx) = small();\n"
" return 43;\n"
"};\n",
43,
STAGE_CS | STAGE_WW, 1 },
/* Row 4 uses payload-bearing aliases so wwstage's primitive-mismatch
* arm fires confidently — three !void aliases all collapse to "void"
* via unwrapbang + resolvealias and wwstage falls through to its
* permissive tail (#202 family, deferred). The payload axis (i32
* vs i64 vs f64) splits the underlying primitives so both stages
* reject under the concrete→tagged arm. */
{ "reject_concrete_unrelated",
"package main;\n"
"type ax = !i32;\n"
"type bx = !i64;\n"
"type cx = !f64;\n"
"export fn main() i32 = {\n"
" let u: ax;\n"
" let r: (bx | cx) = u;\n"
" return 0;\n"
"};\n",
EXPECT_REJECT,
STAGE_CS | STAGE_WW, 0 },
{ "branched_callee_widen",
"package main;\n"
"type unsupported = !void;\n"
"type wrapper = !(unsupported);\n"
"type eof = void;\n"
"fn pick(b: bool) (size | eof | wrapper) = {\n"
" if (b) {\n"
" let u: unsupported;\n"
" let e: wrapper = u;\n"
" return e;\n"
" };\n"
" return 50: size;\n"
"};\n"
"export fn main() i32 = {\n"
" let r1 = pick(true);\n"
" let r2 = pick(false);\n"
" return 45;\n"
"};\n",
45,
STAGE_CS | STAGE_WW, 1 },
};
static int
write_source(const char *path, const char *src)
{
FILE *f = fopen(path, "wb");
if (!f) return -1;
fputs(src, f);
fclose(f);
return 0;
}
static void
cleanup_tmp(const char *tmpdir, const char *base)
{
char p[512];
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir);
if (system(p)) {} /* best-effort */
snprintf(p, sizeof p, "%s/%s.o", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s.combined.ww", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p);
rmdir(tmpdir);
}
static int
build_via_driver(const char *driver, const char *tmpdir, const char *src)
{
char cmd[1280];
/* #93 sep layout: emit asm to <src-stem>.sepwork/__root.s; pin
* WW_PKGCACHE under tmpdir so out/.pkgcache is untouched. */
snprintf(cmd, sizeof cmd,
"cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep "
"-o \"${b%%.ww}\" \"$b\" 2>/dev/null",
tmpdir, src, tmpdir, driver);
return runwait(cmd);
}
static int
run_row(const char *driver, const struct row *r, int seq)
{
char tmpdir[256], src[512], base[64], outbin[768];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wt_%d_d_%d", getpid(), seq);
snprintf(src, sizeof src, "%s/main774.ww", tmpdir);
snprintf(base, sizeof base, "main774");
mkdir(tmpdir, 0755);
if (write_source(src, r->src) != 0) {
cleanup_tmp(tmpdir, base);
return -1;
}
int rc;
int br = build_via_driver(driver, tmpdir, src);
if (r->expected_exit == EXPECT_REJECT) {
rc = (br != 0) ? r->expected_exit : 0;
} else if (br == 0) {
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
rc = runwait(outbin);
} else {
rc = -1;
}
cleanup_tmp(tmpdir, base);
return rc;
}
/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so
* ww_ww writing intermediates next to the source doesn't clobber the
* cstage .s (CLAUDE.md rule 14 phase split). */
static int
asm_byte_identical(const char *cdrv, const char *wdrv,
const struct row *r, int seq)
{
char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512];
snprintf(tdc, sizeof tdc, "/tmp/wt_%d_c_%d", getpid(), seq);
snprintf(tdw, sizeof tdw, "/tmp/wt_%d_w_%d", getpid(), seq);
snprintf(base, sizeof base, "main774");
mkdir(tdc, 0755);
mkdir(tdw, 0755);
snprintf(src, sizeof src, "%s/main774.ww", tdc);
if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; }
int rc = -1;
if (build_via_driver(cdrv, tdc, src) != 0) goto out;
snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base);
snprintf(src, sizeof src, "%s/main774.ww", tdw);
if (write_source(src, r->src) != 0) goto out;
if (build_via_driver(wdrv, tdw, src) != 0) goto out;
snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base);
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
if (fc && fw) {
rc = 0;
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
out:
cleanup_tmp(tdc, base);
cleanup_tmp(tdw, base);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[512];
if (bin[0] != '/') {
char cwd[256];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[640], wdrv[640];
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;
int wwpresent = (access(wdrv, X_OK) == 0);
int seq = 0;
for (int i = 0; i < n; i++) {
if (rows[i].stage_mask & STAGE_CS) {
total++;
int got = run_row(cdrv, &rows[i], seq++);
if (got != rows[i].expected_exit) {
fprintf(stderr,
"tagged_widen_named_variant[cstage run][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].expected_exit);
fail++;
}
}
if (wwpresent && (rows[i].stage_mask & STAGE_WW)) {
total++;
int got = run_row(wdrv, &rows[i], seq++);
if (got != rows[i].expected_exit) {
fprintf(stderr,
"tagged_widen_named_variant[wwstage run][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].expected_exit);
fail++;
}
if (rows[i].byte_id) {
total++;
if (asm_byte_identical(cdrv, wdrv, &rows[i], seq++) != 0) {
fprintf(stderr,
"tagged_widen_named_variant[byte-id][%s]: cstage vs wwstage asm differs\n",
rows[i].label);
fail++;
}
}
}
}
if (!wwpresent)
fprintf(stderr, "tagged_widen_named_variant: skip wwstage (no %s)\n", wdrv);
if (fail) {
fprintf(stderr, "tagged_widen_named_variant: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("tagged_widen_named_variant: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,407 +0,0 @@
/*
* 775_io_vtable_run — project #94 fold-eFinal sentinel. Pins the
* single-surface lib/io/stream.ww vtable port: `vtable` (the three
* (*T|void) slots), `stream` (= *vtable), and the read/write/close
* dispatchers (ref/hare/io/stream.ha:33-68). The fold-eFinal FLIP
* retired the pre-vtable `stream` struct + `closed` tag, leaving this
* as the sole io surface. (Comment prose below predates the FLIP and
* still says `vstream`/`st_read`/`st_write`/`st_close` for the same
* `stream`/`read`/`write`/`close`.)
*
* Each row imports io, allocates a vtable, optionally points one slot at
* a user fn (via the `(&fn): *io.<role>` cast — see SIBLINGS below),
* calls the matching dispatcher, and checks the exit constant. Both
* stages run; cs.s == ww.s byte-id on the rows that build (rule-10).
*
* row | what it pins
* --------------------------+--------------------------------------
* reader_set_happy | reader slot set + st_read happy path.
* | Callee returns (7: size); probe asserts
* | `r is size && r as size == 7`. Pins
* | the call-arm `case let r: *reader =>
* | return (*r)(s, buf)` end-to-end.
* reader_void_unsupported | reader=void. st_read takes the void-arm
* | (two direct widens, #199 α + #205) and
* | returns the errors.unsupported variant.
* | Probe verifies the call returns + exit
* | constant only — does NOT inspect r's
* | discriminant tag (deferred #199b: the
* | wrapped-slot tag-remap currently lands
* | the value at dst tag 0, not the `error`
* | variant index; see 774 header for the
* | parallel constraint).
* writer_set_happy | symmetric to reader_set_happy on
* | st_write; callee returns (buf.len:size).
* writer_void_unsupported | symmetric to reader_void_unsupported.
* closer_void_noop | closer=void. st_close returns plain
* | `void` (no widen — direct `return void`
* | from the dispatcher). Probe asserts
* | `r is void` — the only row that can
* | inspect the tag, because no nested
* | wrapper is involved.
* closer_set | closer slot set + st_close happy path.
* | Callee returns plain `void`; the
* | dispatcher forwards via `(*c)(s)` and
* | the return surfaces at the caller.
* branched_readers | two distinct vtable instances each with
* | a different reader fn. st_read on each
* | dispatches to the right callee (1:size
* | vs 2:size). Mirrors 774's branched-
* | callee row; catches a constant-fold
* | mistake in the dispatch path.
*
* DEFERRALS / RELATED (NOT addressed by these rows):
*
* - #199b layout-extension (wrapped-slot tag-remap): when widening a
* NAMED wrapper-typed value (e.g. `error`) into a tagged parent
* (e.g. `(size | eof | error)`), cgen walks src VARIANTS rather
* than treating src-as-a-whole, so the dst tag lands at 0
* regardless of which wrapper variant was set. Affects rows 2 & 4
* (unsupported via void-arm). Probes verify runtime exit-clean,
* NOT the variant tag — matches the 774 header's exact constraint.
*
* - NEW: checker rejects bare `&fn_name` assignment to a
* `(*<alias-to-fn> | void)` field. `vt.reader = &myread;` errors
* with "cannot assign *fn(vstream, []u8) (size | eof | error) to
* (*reader | void)" — the structural `*fn(...)` value is not
* accepted as the `*reader` named variant of the tagged field.
* Both stages reject. Symmetric to #178 (typeeqast layer asymmetry)
* at the pointer-to-fn-alias layer + tagged-variant assignment.
* PROBE ROUTES AROUND via an explicit cast: `(&myread): *io.reader`.
* Once closed, the cast drops out of every Hare-faithful vtable
* initialisation.
*
* - NEW: `let p: *io.reader = &myread;` also errors with "init
* *fn(vstream, []u8) ... not assignable to declared *reader" —
* same root as above but at the let-binding layer. The Hare
* `io::vtable { reader = &my_read, ... }` shape needs both holes
* closed.
*
* GATE POLARITY: must stay GREEN. A red here means the vtable port
* regressed at the checker, the dispatcher cgen miscompiled the
* call-arm, or the field-slot zero-init lost the void tag.
*/
#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;
}
#define STAGE_CS 1
#define STAGE_WW 2
struct row {
const char *label;
const char *src;
int want_exit;
int stage_mask;
int byte_id;
};
static const struct row rows[] = {
{ "reader_set_happy",
"package main;\n"
"import io;\n"
"fn myread(s: io.stream, buf: []u8) (size | io.eof | io.error) = {\n"
" return 7: size;\n"
"};\n"
"export fn main() i32 = {\n"
" let vt: io.vtable;\n"
" vt.reader = (&myread): *io.reader;\n"
" let v: io.stream = &vt;\n"
" let buf: [4]u8;\n"
" let bs: []u8 = buf[0:4];\n"
" let r = io.read(v, bs);\n"
" if (r is size) {\n"
" let n = r as size;\n"
" if (n == 7: size) { return 70; };\n"
" return 71;\n"
" };\n"
" return 99;\n"
"};\n",
70,
STAGE_CS | STAGE_WW, 1 },
{ "reader_void_unsupported",
"package main;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let vt: io.vtable;\n"
" let v: io.stream = &vt;\n"
" let buf: [4]u8;\n"
" let bs: []u8 = buf[0:4];\n"
" let _r = io.read(v, bs);\n"
" return 11;\n"
"};\n",
11,
STAGE_CS | STAGE_WW, 1 },
{ "writer_set_happy",
"package main;\n"
"import io;\n"
"fn mywrite(s: io.stream, buf: []u8) (size | io.error) = {\n"
" return buf.len: size;\n"
"};\n"
"export fn main() i32 = {\n"
" let vt: io.vtable;\n"
" vt.writer = (&mywrite): *io.writer;\n"
" let v: io.stream = &vt;\n"
" let buf: [5]u8;\n"
" let bs: []u8 = buf[0:5];\n"
" let r = io.write(v, bs);\n"
" if (r is size) {\n"
" let n = r as size;\n"
" if (n == 5: size) { return 80; };\n"
" return 81;\n"
" };\n"
" return 99;\n"
"};\n",
80,
STAGE_CS | STAGE_WW, 1 },
{ "writer_void_unsupported",
"package main;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let vt: io.vtable;\n"
" let v: io.stream = &vt;\n"
" let buf: [3]u8;\n"
" let bs: []u8 = buf[0:3];\n"
" let _r = io.write(v, bs);\n"
" return 22;\n"
"};\n",
22,
STAGE_CS | STAGE_WW, 1 },
{ "closer_void_noop",
"package main;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let vt: io.vtable;\n"
" let v: io.stream = &vt;\n"
" let r = io.close(v);\n"
" if (r is void) { return 50; };\n"
" return 99;\n"
"};\n",
50,
STAGE_CS | STAGE_WW, 1 },
{ "closer_set",
"package main;\n"
"import io;\n"
"fn myclose(s: io.stream) (void | io.error) = {\n"
" return void;\n"
"};\n"
"export fn main() i32 = {\n"
" let vt: io.vtable;\n"
" vt.closer = (&myclose): *io.closer;\n"
" let v: io.stream = &vt;\n"
" let r = io.close(v);\n"
" if (r is void) { return 60; };\n"
" return 99;\n"
"};\n",
60,
STAGE_CS | STAGE_WW, 1 },
{ "branched_readers",
"package main;\n"
"import io;\n"
"fn r1(s: io.stream, buf: []u8) (size | io.eof | io.error) = {\n"
" return 1: size;\n"
"};\n"
"fn r2(s: io.stream, buf: []u8) (size | io.eof | io.error) = {\n"
" return 2: size;\n"
"};\n"
"export fn main() i32 = {\n"
" let vt1: io.vtable;\n"
" let vt2: io.vtable;\n"
" vt1.reader = (&r1): *io.reader;\n"
" vt2.reader = (&r2): *io.reader;\n"
" let buf: [4]u8;\n"
" let bs: []u8 = buf[0:4];\n"
" let a = io.read(&vt1, bs);\n"
" let b = io.read(&vt2, bs);\n"
" let asz: size = 0;\n"
" let bsz: size = 0;\n"
" if (a is size) { asz = a as size; };\n"
" if (b is size) { bsz = b as size; };\n"
" if (asz == 1: size && bsz == 2: size) { return 95; };\n"
" return 99;\n"
"};\n",
95,
STAGE_CS | STAGE_WW, 1 },
};
static int
write_source(const char *path, const char *src)
{
FILE *f = fopen(path, "wb");
if (!f) return -1;
fputs(src, f);
fclose(f);
return 0;
}
/* Per-row tmpdir cleanup. ww_ww writes intermediates next to the source
* (filed task #15), so each row's build leaves <src>.{combined.ww,s,o}
* + bare exe alongside. Sweep them all then rmdir. */
static void
cleanup_tmp(const char *tmpdir, const char *base)
{
char p[640];
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p);
/* #93 sep layout: the sep scratch dir + the tmpdir-pinned pkgcache. */
snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir);
if (system(p)) {} /* best-effort */
rmdir(tmpdir);
}
static int
build_via_driver(const char *driver, const char *tmpdir, const char *cwd,
const char *src)
{
char cmd[2048];
/* #93 sep layout: `--sep -o <src-stem>` emits the asm to
* <stem>.sepwork/__root.s; WW_PKGCACHE is pinned under tmpdir so the
* shared out/.pkgcache is untouched and the cache dies with the
* tmpdir. PRESERVES the `-I %s/lib` import-dir flag. */
snprintf(cmd, sizeof cmd,
"cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep "
"-I %s/lib -o \"${b%%.ww}\" \"$b\" 2>/dev/null",
tmpdir, src, tmpdir, driver, cwd);
return runwait(cmd);
}
static int
run_row(const char *driver, const char *cwd, const struct row *r, int seq)
{
char tmpdir[256], src[512], base[64], outbin[768];
snprintf(tmpdir, sizeof tmpdir, "/tmp/iov_%d_d_%d", getpid(), seq);
snprintf(base, sizeof base, "main775");
snprintf(src, sizeof src, "%s/%s.ww", tmpdir, base);
mkdir(tmpdir, 0755);
if (write_source(src, r->src) != 0) { cleanup_tmp(tmpdir, base); return -1; }
int rc;
int br = build_via_driver(driver, tmpdir, cwd, src);
if (br == 0) {
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
rc = runwait(outbin);
} else {
rc = -1;
}
cleanup_tmp(tmpdir, base);
return rc;
}
/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so
* ww_ww writing intermediates next to the source doesn't clobber the
* cstage .s (CLAUDE.md rule 14 phase split). */
static int
asm_byte_identical(const char *cdrv, const char *wdrv, const char *cwd,
const struct row *r, int seq)
{
char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512];
snprintf(tdc, sizeof tdc, "/tmp/iov_%d_c_%d", getpid(), seq);
snprintf(tdw, sizeof tdw, "/tmp/iov_%d_w_%d", getpid(), seq);
snprintf(base, sizeof base, "main775");
mkdir(tdc, 0755);
mkdir(tdw, 0755);
snprintf(src, sizeof src, "%s/%s.ww", tdc, base);
if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; }
int rc = -1;
if (build_via_driver(cdrv, tdc, cwd, src) != 0) goto out;
snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base);
snprintf(src, sizeof src, "%s/%s.ww", tdw, base);
if (write_source(src, r->src) != 0) goto out;
if (build_via_driver(wdrv, tdw, cwd, src) != 0) goto out;
snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base);
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
if (fc && fw) {
rc = 0;
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
out:
cleanup_tmp(tdc, base);
cleanup_tmp(tdw, base);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char cwd[256];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
char absbin[512];
if (bin[0] != '/') {
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[640], wdrv[640];
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;
int wwpresent = (access(wdrv, X_OK) == 0);
int seq = 0;
for (int i = 0; i < n; i++) {
if (rows[i].stage_mask & STAGE_CS) {
total++;
int got = run_row(cdrv, cwd, &rows[i], seq++);
if (got != rows[i].want_exit) {
fprintf(stderr,
"io_vtable_run[cs][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
}
if (wwpresent && (rows[i].stage_mask & STAGE_WW)) {
total++;
int got = run_row(wdrv, cwd, &rows[i], seq++);
if (got != rows[i].want_exit) {
fprintf(stderr,
"io_vtable_run[ww][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
if (rows[i].byte_id) {
total++;
if (asm_byte_identical(cdrv, wdrv, cwd, &rows[i], seq++) != 0) {
fprintf(stderr,
"io_vtable_run[byte-id][%s]: cstage vs wwstage asm differs\n",
rows[i].label);
fail++;
}
}
}
}
if (!wwpresent)
fprintf(stderr, "io_vtable_run: skip wwstage (no %s)\n", wdrv);
if (fail) {
fprintf(stderr, "io_vtable_run: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("io_vtable_run: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,431 +0,0 @@
/*
* 776_memio_vstream_run — project #94 fold-eFinal sentinel. Pins
* the single-surface lib/memio: a unified
* `stream` struct with `vt: io.vtable` as the first field for the
* intrusive cast, three constructors (fixed / dynamic /
* dynamicfrom) that return the `stream` BY VALUE (sret —
* field-by-field build then `return r;`, ref/hare/memio/stream.ha:46,
* 58,64), the unified read / fixedwrite / dynamicwrite /
* dynamicclose callbacks that recover the outer stream via a
* vstream→*stream pointer cast, and the collapsed single accessors
* (string / buffer / reset / borrowedread) over the common
* header. The fold-eFinal FLIP collapsed the dual memio surface into
* this one Hare-shaped surface (bare names, no suffix).
*
* Each row imports memio + io, calls one or more constructors, drives
* the io.read/io.write/io.close dispatchers, and asserts an exit
* constant. Both stages run; cs.s == ww.s byte-id on every row.
*
* row | what it pins
* --------------------------+--------------------------------------
* fixed_read_5 | fixed + io.read of 5 bytes
* | over an 8-byte source. Asserts the
* | return is `size` and equal to 5, and
* | that out[0]/out[4] mirror the buffer.
* | Pins the full intrusive shape (build
* | stream field-by-field → return r (sret);
* | caller takes &st.vt; dispatcher recovers
* | via s: *stream).
* dynamic_write_grow | dynamic + io.write of 3
* | bytes. Asserts the write reports 3
* | and that the dynamicgrow path
* | allocated the backing buffer (initial
* | cap=0 forces growth on the first
* | write). Closes via io.close to
* | exercise dynamicclose's free.
* dynamicfrom_alt_rw | dynamicfrom seeded with a
* | 4-byte slice. Alternates st_read
* | (4 bytes back) → st_write (2 bytes,
* | grows past cap=4 → realloc). Asserts
* | both paths report their lengths.
* | Single ctx flavour, two dispatcher
* | paths through the same vtable.
* branched_fixed | branched callee per #105: two
* | fixed instances with
* | different buffer sizes (3 and 5),
* | runtime-selected by the exit code
* | nudge. st_read on each returns the
* | matching size. Catches a constant-
* | fold mistake in the dispatch path
* | (mirror of 775's branched_readers
* | row for the vtable-init side).
*
* IMPORT ORDER (#208 CLOSED): every row imports `memio; io; os;` —
* os LAST. This is the formerly-failing os-late ordering. Pre-#208 it
* tripped the wwstage checker on io.read/write/close's `return s.read(
* s, buf)` (a fn-pointer field call) with 3 false "return: not
* assignable (i64 → )"/"(i32 → )" — because exprtype re-bound the field
* leaf to a same-named scalar global (os.read:i64) instead of the
* field's tagged fn type. #208 fixed exprtype to resolve value-receiver
* field calls from the field type (cmd/wcc/check.c:1378-1433 twin), so
* the order no longer matters and all 4 rows now run STAGE_CS|STAGE_WW
* byte-id. The earlier `import os;`-FIRST workaround is retired here.
*
* DEFERRALS / RELATED (NOT addressed by this row):
*
* - #173 (TRY-on-tagged-return both-stages broken) blocks
* fixedwrite from returning Hare's `nomem` when full;
* memio.ww surfaces 0 instead (documented divergence).
* The probe doesn't pin Hare-equivalent nomem behaviour;
* it can graduate once #173 closes.
*
* - #195 (cgassign N_DOT TK_ASSIGN on aliased-ptr receiver):
* not bitten — the value-return constructor builds the stream in
* a local `let r: stream;` and field-assigns each slot (including
* r.vt.reader) through a plain pointer-to-local, not an aliased-ptr
* receiver. The flat ptr/len/cap fields stay flat.
*
* GATE POLARITY: must stay GREEN. A red here means lib/memio
* regressed, the sret value-return path miscompiled, the intrusive
* io.stream→*stream cast miscomputed offsets, or io.vtable's
* first-field embed lost its offset-0 invariant.
*/
#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;
}
#define STAGE_CS 1
#define STAGE_WW 2
struct row {
const char *label;
const char *src;
int want_exit;
int stage_mask;
int byte_id;
};
static const struct row rows[] = {
{ "fixed_read_5",
"package main;\n"
"import memio;\n"
"import io;\n"
"import os;\n"
"export fn main() i32 = {\n"
" let buf: [8]u8;\n"
" buf[0] = 65u8; buf[1] = 66u8; buf[2] = 67u8; buf[3] = 68u8;\n"
" buf[4] = 69u8; buf[5] = 70u8; buf[6] = 71u8; buf[7] = 72u8;\n"
" let st: memio.stream = memio.fixed(buf[0:8]);\n"
" let s: io.stream = &st.vt;\n"
" let out: [5]u8;\n"
" let n: size = 0;\n"
" let rd = io.read(s, out[0:5]);\n"
" if (rd is size) { n = rd as size; };\n"
" if (n == 5: size && out[0] == 65u8 && out[4] == 69u8) { return 42; };\n"
" return 99;\n"
"};\n",
42,
STAGE_CS | STAGE_WW, 1 },
{ "dynamic_write_grow",
"package main;\n"
"import memio;\n"
"import io;\n"
"import os;\n"
"export fn main() i32 = {\n"
" let st: memio.stream = memio.dynamic();\n"
" let s: io.stream = &st.vt;\n"
" let src: [3]u8;\n"
" src[0] = 88u8; src[1] = 89u8; src[2] = 90u8;\n"
" let wr = io.write(s, src[0:3]);\n"
" let n: size = 0;\n"
" if (wr is size) { n = wr as size; };\n"
" let cl = io.close(s);\n"
" if (cl is void && n == 3: size) { return 43; };\n"
" return 99;\n"
"};\n",
43,
STAGE_CS | STAGE_WW, 1 },
{ "dynamicfrom_alt_rw",
"package main;\n"
"import memio;\n"
"import io;\n"
"import os;\n"
"export fn main() i32 = {\n"
" let seed: [4]u8;\n"
" seed[0] = 1u8; seed[1] = 2u8; seed[2] = 3u8; seed[3] = 4u8;\n"
" let st: memio.stream = memio.dynamicfrom(seed[0:4]);\n"
" let s: io.stream = &st.vt;\n"
" let out: [4]u8;\n"
" let rd = io.read(s, out[0:4]);\n"
" let rn: size = 0;\n"
" if (rd is size) { rn = rd as size; };\n"
" let extra: [2]u8;\n"
" extra[0] = 99u8; extra[1] = 100u8;\n"
" let wr = io.write(s, extra[0:2]);\n"
" let wn: size = 0;\n"
" if (wr is size) { wn = wr as size; };\n"
" if (rn == 4: size && wn == 2: size && out[0] == 1u8 && out[3] == 4u8) { return 44; };\n"
" return 99;\n"
"};\n",
44,
STAGE_CS | STAGE_WW, 1 },
{ "branched_fixed",
"package main;\n"
"import memio;\n"
"import io;\n"
"import os;\n"
"export fn main() i32 = {\n"
" let a: [3]u8;\n"
" a[0] = 10u8; a[1] = 11u8; a[2] = 12u8;\n"
" let b: [5]u8;\n"
" b[0] = 20u8; b[1] = 21u8; b[2] = 22u8; b[3] = 23u8; b[4] = 24u8;\n"
" let sta: memio.stream = memio.fixed(a[0:3]);\n"
" let stb: memio.stream = memio.fixed(b[0:5]);\n"
" let sa: io.stream = &sta.vt;\n"
" let sb: io.stream = &stb.vt;\n"
" let outa: [3]u8;\n"
" let outb: [5]u8;\n"
" let rda = io.read(sa, outa[0:3]);\n"
" let rdb = io.read(sb, outb[0:5]);\n"
" let na: size = 0;\n"
" let nb: size = 0;\n"
" if (rda is size) { na = rda as size; };\n"
" if (rdb is size) { nb = rdb as size; };\n"
" if (na == 3: size && nb == 5: size && outa[2] == 12u8 && outb[4] == 24u8) { return 45; };\n"
" return 99;\n"
"};\n",
45,
STAGE_CS | STAGE_WW, 1 },
/* accessors_string_buffer_reset — fold-eFinal PREP: the collapsed
* single accessors over the common `stream` header (string /
* buffer / reset, ref/hare/memio/stream.ha:81,74,87). Writes 3
* bytes through the value-returned fixed stream, then asserts
* string (str view), buffer ([]u8 view), and reset (rewind to
* 0) all read the flat header correctly. Pins the value-return
* field integrity (st.vt + st.ptr/len/cap/pos survive sret) AND the
* accessor collapse. cs.s == ww.s byte-id. */
{ "accessors_string_buffer_reset",
"package main;\n"
"import memio;\n"
"import io;\n"
"import os;\n"
"export fn main() i32 = {\n"
" let buf: [16]u8;\n"
" let st: memio.stream = memio.fixed(buf[0:16]);\n"
" let s: io.stream = &st.vt;\n"
" let payload: [3]u8;\n"
" payload[0] = 65u8; payload[1] = 66u8; payload[2] = 67u8;\n"
" let wr = io.write(s, payload[0:3]);\n"
" if (!(wr is size)) { return 90; };\n"
" let v: str = memio.string(&st);\n"
" if (v.len != 3 || v[0] != 65u8 || v[2] != 67u8) { return 91; };\n"
" let bv: []u8 = memio.buffer(&st);\n"
" if (bv.len != 3 || bv[1] != 66u8) { return 92; };\n"
" memio.reset(&st);\n"
" if (memio.string(&st).len != 0) { return 93; };\n"
" return 46;\n"
"};\n",
46,
STAGE_CS | STAGE_WW, 1 },
/* borrowedread_view_eof — the collapsed borrowedread
* (ref/hare/memio/stream.ha:94): returns an amt-byte borrowed view
* advancing the cursor, eof when fewer remain. Reads 3 of 4 seeded
* bytes (view len 3), then a 2-byte borrowedread over the remaining
* 1 byte returns io.eof. cs.s == ww.s byte-id. */
{ "borrowedread_view_eof",
"package main;\n"
"import memio;\n"
"import io;\n"
"import os;\n"
"export fn main() i32 = {\n"
" let seed: [4]u8;\n"
" seed[0] = 1u8; seed[1] = 2u8; seed[2] = 3u8; seed[3] = 4u8;\n"
" let st: memio.stream = memio.dynamicfrom(seed[0:4]);\n"
" let br = memio.borrowedread(&st, 3);\n"
" match (br) {\n"
" case let b: []u8 => { if (b.len != 3 || b[0] != 1u8 || b[2] != 3u8) { return 91; }; };\n"
" case io.eof => { return 92; };\n"
" };\n"
" let br2 = memio.borrowedread(&st, 2);\n"
" match (br2) {\n"
" case let b: []u8 => { return 93; };\n"
" case io.eof => { return 47; };\n"
" };\n"
" return 99;\n"
"};\n",
47,
STAGE_CS | STAGE_WW, 1 },
};
static int
write_source(const char *path, const char *src)
{
FILE *f = fopen(path, "wb");
if (!f) return -1;
fputs(src, f);
fclose(f);
return 0;
}
/* Per-row tmpdir cleanup. ww_ww writes intermediates next to the
* source (filed task #15), so each row's build leaves
* <src>.{combined.ww,s,o} + bare exe alongside. Sweep all then
* rmdir. */
static void
cleanup_tmp(const char *tmpdir, const char *base)
{
char p[640];
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p);
/* #93 sep layout: the sep scratch dir + the tmpdir-pinned pkgcache. */
snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir);
if (system(p)) {} /* best-effort */
rmdir(tmpdir);
}
static int
build_via_driver(const char *driver, const char *tmpdir, const char *cwd,
const char *src)
{
char cmd[2048];
/* #93 sep layout: `--sep -o <src-stem>` emits the asm to
* <stem>.sepwork/__root.s; WW_PKGCACHE is pinned under tmpdir so the
* shared out/.pkgcache is untouched and the cache dies with the
* tmpdir. PRESERVES the `-I %s/lib` import-dir flag. */
snprintf(cmd, sizeof cmd,
"cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep "
"-I %s/lib -o \"${b%%.ww}\" \"$b\" 2>/dev/null",
tmpdir, src, tmpdir, driver, cwd);
return runwait(cmd);
}
static int
run_row(const char *driver, const char *cwd, const struct row *r, int seq)
{
char tmpdir[256], src[512], base[64], outbin[768];
snprintf(tmpdir, sizeof tmpdir, "/tmp/mvs_%d_d_%d", getpid(), seq);
snprintf(base, sizeof base, "main776");
snprintf(src, sizeof src, "%s/%s.ww", tmpdir, base);
mkdir(tmpdir, 0755);
if (write_source(src, r->src) != 0) { cleanup_tmp(tmpdir, base); return -1; }
int rc;
int br = build_via_driver(driver, tmpdir, cwd, src);
if (br == 0) {
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
rc = runwait(outbin);
} else {
rc = -1;
}
cleanup_tmp(tmpdir, base);
return rc;
}
/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so
* ww_ww writing intermediates next to the source doesn't clobber the
* cstage .s (CLAUDE.md rule 14 phase split). */
static int
asm_byte_identical(const char *cdrv, const char *wdrv, const char *cwd,
const struct row *r, int seq)
{
char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512];
snprintf(tdc, sizeof tdc, "/tmp/mvs_%d_c_%d", getpid(), seq);
snprintf(tdw, sizeof tdw, "/tmp/mvs_%d_w_%d", getpid(), seq);
snprintf(base, sizeof base, "main776");
mkdir(tdc, 0755);
mkdir(tdw, 0755);
snprintf(src, sizeof src, "%s/%s.ww", tdc, base);
if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; }
int rc = -1;
if (build_via_driver(cdrv, tdc, cwd, src) != 0) goto out;
snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base);
snprintf(src, sizeof src, "%s/%s.ww", tdw, base);
if (write_source(src, r->src) != 0) goto out;
if (build_via_driver(wdrv, tdw, cwd, src) != 0) goto out;
snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base);
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
if (fc && fw) {
rc = 0;
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
out:
cleanup_tmp(tdc, base);
cleanup_tmp(tdw, base);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char cwd[256];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
char absbin[512];
if (bin[0] != '/') {
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[640], wdrv[640];
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;
int wwpresent = (access(wdrv, X_OK) == 0);
int seq = 0;
for (int i = 0; i < n; i++) {
if (rows[i].stage_mask & STAGE_CS) {
total++;
int got = run_row(cdrv, cwd, &rows[i], seq++);
if (got != rows[i].want_exit) {
fprintf(stderr,
"memio_vstream_run[cs][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
}
if (wwpresent && (rows[i].stage_mask & STAGE_WW)) {
total++;
int got = run_row(wdrv, cwd, &rows[i], seq++);
if (got != rows[i].want_exit) {
fprintf(stderr,
"memio_vstream_run[ww][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
if (rows[i].byte_id) {
total++;
if (asm_byte_identical(cdrv, wdrv, cwd, &rows[i], seq++) != 0) {
fprintf(stderr,
"memio_vstream_run[byte-id][%s]: cstage vs wwstage asm differs\n",
rows[i].label);
fail++;
}
}
}
}
if (!wwpresent)
fprintf(stderr, "memio_vstream_run: skip wwstage (no %s)\n", wdrv);
if (fail) {
fprintf(stderr, "memio_vstream_run: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("memio_vstream_run: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,408 +0,0 @@
/*
* 777_fmt_handle_run — io fold-2 (#5) commit-2 sentinel. Pins the
* fprint family over an [[io.handle]] sink after the fdsink shim
* removal: fmt's fprint/fprintln/fprintf now take `io.handle =
* (io.file | io.stream)`, so a raw fd (file arm) and a memio vtable
* (stream arm) both flow into the same surface and io.write dispatches
* per arm. Supersedes the pre-#5 fd_ctx workaround (the fake-stream
* vtable around os.write) — drew's placeholder for the then-missing
* handle, removed by this commit.
*
* The file-arm rows open a per-process /tmp output file, call one
* fprint-family fn with `fd: io.file` (widens to io.handle), close,
* reopen for read, read the bytes back, and assert both (a) the exact
* byte content and (b) a unique row-tagged exit constant. The
* stream-arm row drives the SAME fprintf over a memio.fixed vtable
* (the io.stream arm) and reads back via memio.string — the two-arm
* coverage #5 graduated.
*
* Cstage-only per row (no STAGE_WW, no byte_id). #209 (the wwstage
* checker bail `case: not a variant of scrutinee` + `match: variant not
* handled (formattable)` on fmt's spread-union match-arms) is now CLOSED
* — wwstage compiles fmt. The residual blocker is a SEPARATE pre-existing
* cgen cluster surfaced once fmt actually codegens under wwstage:
* - #226: io.read's error widening (cgwidentagremap) loses the
* io.eof/io.error NAMED tinfo identity under fmt-presence
* (flatvariantidxt → -1 → tag collapses to 0); cs!=ww whole-file, so
* byte-id fails even though io.read is unused here (it's emitted
* regardless). The #10/#218/tinfo-lossy-nominal family.
* - #227: spread-union widen + return-ABI, broken in BOTH stages.
* 995 self-rebuild dodges all of this because no selfhost main pulls fmt
* transitively. Byte-id graduates when #226 (+#227) land.
*
* row | what it pins
* ---------------------+--------------------------------------
* fprintf_file_int | fprintf(fd: io.file, "v={}\n", 42)
* | over the FILE arm -> io.write -> os.write,
* | plus the {n}-placeholder parser through
* | formatfield.
* fprintln_file_multi | fprintln(fd: io.file, 7, "hi", true) —
* | three positional args, space-separator
* | + trailing newline shape over the file arm.
* fprint_file_raw | fprint(fd: io.file, "raw") — one str arg,
* | no placeholder parser (bare fprint loop).
* branched_fprintf | branched callee per #105: two distinct
* | format strings runtime-selected from a
* | single fprintf call site.
* fprintf_stream | fprintf over a memio.fixed io.stream
* | (the STREAM arm of io.handle) read back
* | via memio.string — the other handle arm.
*
* IMPORT-ORDER WORKAROUND (sibling task #208, pre-existing): every row
* places `import os;` FIRST for the same reason 776 documents (wwstage
* checker ordering-sensitivity on os.tryread/trywrite/tryopen's bare
* `return r;` over a tagged return type).
*
* BOOTSTRAP-EMBED CHECK: fmt is NOT embedded in any selfhost
* combined.ww (grep confirmed), so the #5 fmt graduation does NOT
* require a Makefile regen (#110); 990-997 byte-id gates stay green by
* virtue of fmt being test-only on the bootstrap surface.
*
* GATE POLARITY: must stay GREEN. A red here means lib/fmt regressed,
* the file->handle widen miscompiled, io.write's per-arm dispatch broke,
* the {n}-placeholder dispatch through formatfield regressed, or the
* memio stream arm lost its offset-0 vtable invariant.
*/
#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;
}
#define STAGE_CS 1
#define STAGE_WW 2
struct row {
const char *label;
const char *src;
int want_exit;
int stage_mask;
int byte_id;
};
static const struct row rows[] = {
{ "fprintf_file_int",
"package main;\n"
"import os;\n"
"import fmt;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let path: str = \"/tmp/wwfmth_777_a\";\n"
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
" if (fd < 0) { return 90; };\n"
" let wr = fmt.fprintf(fd: io.file, \"v={}\\n\", 42i64);\n"
" let nw: size = 0;\n"
" match (wr) {\n"
" case let n: size => { nw = n; };\n"
" case io.error => { return 91; };\n"
" };\n"
" os.close(fd);\n"
" let rd: i32 = os.open(path, os.flag.RDONLY, 0i32);\n"
" if (rd < 0) { return 92; };\n"
" let buf: [16]u8;\n"
" let n: i64 = os.read(rd, &buf[0], 16u64);\n"
" os.close(rd);\n"
" if (n != 5i64) { return 93; };\n"
" if (nw != 5: size) { return 94; };\n"
" if (buf[0] != 118u8 || buf[1] != 61u8 || buf[2] != 52u8 || buf[3] != 50u8 || buf[4] != 10u8) { return 95; };\n"
" return 42;\n"
"};\n",
42,
STAGE_CS | STAGE_WW, 1 },
{ "fprintln_file_multi",
"package main;\n"
"import os;\n"
"import fmt;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let path: str = \"/tmp/wwfmth_777_b\";\n"
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
" if (fd < 0) { return 90; };\n"
" let wr = fmt.fprintln(fd: io.file, 7i64, \"hi\", true);\n"
" let nw: size = 0;\n"
" match (wr) {\n"
" case let n: size => { nw = n; };\n"
" case io.error => { return 91; };\n"
" };\n"
" os.close(fd);\n"
" let rd: i32 = os.open(path, os.flag.RDONLY, 0i32);\n"
" if (rd < 0) { return 92; };\n"
" let buf: [16]u8;\n"
" let n: i64 = os.read(rd, &buf[0], 16u64);\n"
" os.close(rd);\n"
" if (n != 10i64) { return 93; };\n"
" if (nw != 10: size) { return 94; };\n"
" if (buf[0] != 55u8 || buf[1] != 32u8 || buf[2] != 104u8 || buf[3] != 105u8 || buf[4] != 32u8 || buf[5] != 116u8 || buf[6] != 114u8 || buf[7] != 117u8 || buf[8] != 101u8 || buf[9] != 10u8) { return 95; };\n"
" return 43;\n"
"};\n",
43,
STAGE_CS | STAGE_WW, 1 },
{ "fprint_file_raw",
"package main;\n"
"import os;\n"
"import fmt;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let path: str = \"/tmp/wwfmth_777_c\";\n"
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
" if (fd < 0) { return 90; };\n"
" let wr = fmt.fprint(fd: io.file, \"raw\");\n"
" let nw: size = 0;\n"
" match (wr) {\n"
" case let n: size => { nw = n; };\n"
" case io.error => { return 91; };\n"
" };\n"
" os.close(fd);\n"
" let rd: i32 = os.open(path, os.flag.RDONLY, 0i32);\n"
" if (rd < 0) { return 92; };\n"
" let buf: [8]u8;\n"
" let n: i64 = os.read(rd, &buf[0], 8u64);\n"
" os.close(rd);\n"
" if (n != 3i64) { return 93; };\n"
" if (nw != 3: size) { return 94; };\n"
" if (buf[0] != 114u8 || buf[1] != 97u8 || buf[2] != 119u8) { return 95; };\n"
" return 44;\n"
"};\n",
44,
STAGE_CS | STAGE_WW, 1 },
{ "branched_fprintf",
"package main;\n"
"import os;\n"
"import fmt;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let path: str = \"/tmp/wwfmth_777_d\";\n"
" let fd: i32 = os.open(path, os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC, 0o644: i32);\n"
" if (fd < 0) { return 90; };\n"
" let sel: i32 = 1;\n"
" let f1: str = \"A{}\";\n"
" let f2: str = \"B{}\";\n"
" let fmtstr: str = f1;\n"
" if (sel == 0) { fmtstr = f2; };\n"
" let wr = fmt.fprintf(fd: io.file, fmtstr, 9i64);\n"
" let nw: size = 0;\n"
" match (wr) {\n"
" case let n: size => { nw = n; };\n"
" case io.error => { return 91; };\n"
" };\n"
" os.close(fd);\n"
" let rd: i32 = os.open(path, os.flag.RDONLY, 0i32);\n"
" if (rd < 0) { return 92; };\n"
" let buf: [8]u8;\n"
" let n: i64 = os.read(rd, &buf[0], 8u64);\n"
" os.close(rd);\n"
" if (n != 2i64) { return 93; };\n"
" if (nw != 2: size) { return 94; };\n"
" if (buf[0] != 65u8 || buf[1] != 57u8) { return 95; };\n"
" return 45;\n"
"};\n",
45,
STAGE_CS | STAGE_WW, 1 },
{ "fprintf_stream",
"package main;\n"
"import os;\n"
"import fmt;\n"
"import io;\n"
"import memio;\n"
"export fn main() i32 = {\n"
" let buf: [16]u8;\n"
" let st: memio.stream = memio.fixed(buf[0:16]);\n"
" let s: io.stream = &st.vt;\n"
" let wr = fmt.fprintf(s, \"v={}\\n\", 42i64);\n"
" let nw: size = 0;\n"
" match (wr) {\n"
" case let n: size => { nw = n; };\n"
" case io.error => { return 91; };\n"
" };\n"
" if (nw != 5: size) { return 94; };\n"
" let view: str = memio.string(&st);\n"
" if (view.len != 5) { return 93; };\n"
" if (view[0] != 118u8 || view[1] != 61u8 || view[2] != 52u8 || view[3] != 50u8 || view[4] != 10u8) { return 95; };\n"
" return 46;\n"
"};\n",
46,
STAGE_CS | STAGE_WW, 1 },
};
static int
write_source(const char *path, const char *src)
{
FILE *f = fopen(path, "wb");
if (!f) return -1;
fputs(src, f);
fclose(f);
return 0;
}
/* Per-row tmpdir cleanup. ww_ww writes intermediates next to the
* source (filed task #15), so each row's build leaves
* <src>.{combined.ww,s,o} + bare exe alongside. Sweep all then
* rmdir. Mirror of 776's cleanup_tmp. */
static void
cleanup_tmp(const char *tmpdir, const char *base)
{
char p[640];
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p);
/* #93 sep layout: the sep scratch dir + the tmpdir-pinned pkgcache. */
snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir);
if (system(p)) {} /* best-effort */
rmdir(tmpdir);
}
static int
build_via_driver(const char *driver, const char *tmpdir, const char *cwd,
const char *src)
{
char cmd[2048];
/* #93 sep layout: `--sep -o <src-stem>` emits the asm to
* <stem>.sepwork/__root.s; WW_PKGCACHE is pinned under tmpdir so the
* shared out/.pkgcache is untouched and the cache dies with the
* tmpdir. PRESERVES the `-I %s/lib` import-dir flag. */
snprintf(cmd, sizeof cmd,
"cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep "
"-I %s/lib -o \"${b%%.ww}\" \"$b\" 2>/dev/null",
tmpdir, src, tmpdir, driver, cwd);
return runwait(cmd);
}
static int
run_row(const char *driver, const char *cwd, const struct row *r, int seq)
{
char tmpdir[256], src[512], base[64], outbin[768];
snprintf(tmpdir, sizeof tmpdir, "/tmp/fh_%d_d_%d", getpid(), seq);
snprintf(base, sizeof base, "main777");
snprintf(src, sizeof src, "%s/%s.ww", tmpdir, base);
mkdir(tmpdir, 0755);
if (write_source(src, r->src) != 0) { cleanup_tmp(tmpdir, base); return -1; }
int rc;
int br = build_via_driver(driver, tmpdir, cwd, src);
if (br == 0) {
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
rc = runwait(outbin);
} else {
rc = -1;
}
cleanup_tmp(tmpdir, base);
return rc;
}
/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so
* ww_ww writing intermediates next to the source doesn't clobber the
* cstage .s (CLAUDE.md rule 14 phase split). Mirror of 776's. Unused
* while every row is cstage-only (#226/#227; #209 is closed), kept for
* the byte-id graduation once those cgen folds land. */
static int
asm_byte_identical(const char *cdrv, const char *wdrv, const char *cwd,
const struct row *r, int seq)
{
char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512];
snprintf(tdc, sizeof tdc, "/tmp/fh_%d_c_%d", getpid(), seq);
snprintf(tdw, sizeof tdw, "/tmp/fh_%d_w_%d", getpid(), seq);
snprintf(base, sizeof base, "main777");
mkdir(tdc, 0755);
mkdir(tdw, 0755);
snprintf(src, sizeof src, "%s/%s.ww", tdc, base);
if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; }
int rc = -1;
if (build_via_driver(cdrv, tdc, cwd, src) != 0) goto out;
snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base);
snprintf(src, sizeof src, "%s/%s.ww", tdw, base);
if (write_source(src, r->src) != 0) goto out;
if (build_via_driver(wdrv, tdw, cwd, src) != 0) goto out;
snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base);
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
if (fc && fw) {
rc = 0;
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
out:
cleanup_tmp(tdc, base);
cleanup_tmp(tdw, base);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char cwd[256];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
char absbin[512];
if (bin[0] != '/') {
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[640], wdrv[640];
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;
int wwpresent = (access(wdrv, X_OK) == 0);
int seq = 0;
for (int i = 0; i < n; i++) {
if (rows[i].stage_mask & STAGE_CS) {
total++;
int got = run_row(cdrv, cwd, &rows[i], seq++);
if (got != rows[i].want_exit) {
fprintf(stderr,
"fmt_handle_run[cs][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
}
if (wwpresent && (rows[i].stage_mask & STAGE_WW)) {
total++;
int got = run_row(wdrv, cwd, &rows[i], seq++);
if (got != rows[i].want_exit) {
fprintf(stderr,
"fmt_handle_run[ww][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
if (rows[i].byte_id) {
total++;
if (asm_byte_identical(cdrv, wdrv, cwd, &rows[i], seq++) != 0) {
fprintf(stderr,
"fmt_handle_run[byte-id][%s]: cstage vs wwstage asm differs\n",
rows[i].label);
fail++;
}
}
}
}
if (!wwpresent)
fprintf(stderr, "fmt_handle_run: skip wwstage (no %s)\n", wdrv);
if (fail) {
fprintf(stderr, "fmt_handle_run: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("fmt_handle_run: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,587 +0,0 @@
/*
* 778_bufio_vstream_run — project #94 fold-eFinal sentinel. Pins
* the single-surface lib/bufio over io.stream — the unified surface
* the eFinal FLIP collapsed to, NOT a port of Hare's missing scanner
* features (those are #217, OUT of eFinal). Covers:
* - buffered stream: `stream` (vt@offset0 for the intrusive cast),
* `init` (returns the stream BY VALUE; sret field-by-field)
* over an underlying **io.stream** src — every underlying read/
* write/close goes through io.read/io.write/io.close (the
* vtable dispatchers), io.error forwards directly (no nomem-widen).
* Plus setflush / flush (public + internal drain) / unread /
* isbuffered (fn-ptr-equality discriminator).
* - scanner: `scanner` (value-return newscannerbuf over an
* io.stream src) + scanbyte / scanbytes (single-byte delim) /
* scanline / finish.
*
* Each row imports os + bufio + memio + io (NOT fmt — bufio doesn't
* transitively pull fmt, so it dodges the fmt-presence cgen cluster
* #226 (io.read error-remap nominal-identity) + #227 (spread-union
* widen/return-ABI) that blocks the fmt-importing tests' byte-id; both
* stages run on every row, cs.s==ww.s — bufio is NOT compiler-embedded,
* so these byte-id rows are its ONLY byte-id cover). (#209, the wwstage
* formattable match-arm CHECKER bail, is now closed.)
*
* row | what it pins
* --------------------------+--------------------------------------
* stream_write_flush | bufio.init + io.write
* | through bwrite + bufio.flush via
* | io.close. Asserts the data
* | reaches the underlying memio buffer
* | after close drains wbuf. Full chain:
* | stream built field-by-field →
* | return r (sret) → caller takes &b.vt →
* | dispatcher recovers via *stream →
* | flush → io.write(b.src, ...).
* stream_read_unread | init read path: io.read
* | refills rbuf from src, serves first
* | N bytes. Pins bread's read path
* | through the intrusive cast. unread
* | not exposed yet (no bufio.unread
* | equivalent on the V API surface
* | until eFinal); this row exercises
* | the base read path instead.
* isbuffered_v_discriminate | isbuffered on a fresh init
* | returns true; isbuffered on a
* | plain memio.fixed (no bufio
* | wrap) returns false. Pins the
* | fn-ptr-equality discriminator
* | (ref/hare/bufio/stream.ha:179) over
* | the bread/bwrite callback symbols.
* isbuffered_v_boundary | isbuffered discriminates by vtable
* | identity: true on the bufio.init
* | wrapper stream, false on the
* | underlying (non-bufio) memio stream.
* | Pins the fn-ptr-equality discriminator
* | (ref/hare/bufio/stream.ha:179) over
* | the single-surface io.stream.
* branched_bufio_wrap | branched callee per #105: two
* | distinct underlying *io.stream's
* | runtime-selected; bufio.init
* | wraps the picked one. io.read
* | returns the matching source's
* | content. Catches a constant-fold
* | mistake in the dispatch path
* | (mirror of 775's branched_readers
* | for the bufio-wrap side).
*
* DEFERRALS / RELATED (NOT addressed by this row):
*
* - io fold-2 (handle port): drew-deferred. Hare's
* ref/hare/bufio/stream.ha:69 takes `src: io::handle`; once
* handle = (file | int) lands, init's src parameter
* collapses accordingly. #5 graduates.
*
* - #206 (bare &fn → (*alias|void)): 5 cast sites in bufio.ww
* (3 vtable wire-up + 2 in isbuffered); explicit casts KEPT
* (ken/#214 — cast-drop deferred, gated on #214).
*
* - #173 (TRY-on-tagged-return both-stages broken): flush
* constructs nomem and widens to io.error explicitly rather
* than using `io.write(...)?`; same shape memio.ww + fmt.ww
* adopt.
*
* - #207 (struct-lit multi-tagged-field copy drops past first) and
* #210 (slice-field struct-lit drop): not reachable from the
* value-return form — stream is built field-by-field in a
* local then sret-returned, no `alloc(T{...})` struct-lit.
*
* BOOTSTRAP-EMBED CHECK: bufio is NOT embedded in any selfhost
* combined.ww (grep confirmed), so the fold-eFinal bufio collapse
* does NOT require a Makefile regen (#110); only lib/bufio/ test
* paths see the surface. 990-997 byte-id gates stay green by virtue
* of bufio being test-only.
*
* GATE POLARITY: must stay GREEN. A red here means lib/bufio
* regressed, the sret value-return path miscompiled, the intrusive
* io.stream→*stream cast miscomputed offsets, the io.vtable
* first-field embed lost its offset-0 invariant, or the fn-ptr-
* equality discriminator in isbuffered stopped resolving.
*/
#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;
}
#define STAGE_CS 1
#define STAGE_WW 2
struct row {
const char *label;
const char *src;
int want_exit;
int stage_mask;
int byte_id;
};
static const struct row rows[] = {
{ "stream_write_flush",
"package main;\n"
"import os;\n"
"import bufio;\n"
"import memio;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let raw: [16]u8;\n"
" let mst: memio.stream = memio.fixed(raw[0:16]);\n"
" let msrc: io.stream = &mst.vt;\n"
" let rbuf: [8]u8;\n"
" let wbuf: [8]u8;\n"
" let b: bufio.stream = bufio.init(msrc, rbuf[0:8], wbuf[0:8]);\n"
" let vs: io.stream = &b.vt;\n"
" let payload: [5]u8;\n"
" payload[0] = 104u8; payload[1] = 105u8;\n"
" payload[2] = 33u8; payload[3] = 98u8; payload[4] = 121u8;\n"
" let wr = io.write(vs, payload[0:5]);\n"
" let nw: size = 0;\n"
" if (wr is size) { nw = wr as size; };\n"
" if (mst.pos != 0) { return 91; };\n"
" let cl = io.close(vs);\n"
" if (cl is io.error) { return 92; };\n"
" if (mst.pos != 5) { return 93; };\n"
" if (nw != 5: size) { return 94; };\n"
" if (raw[0] != 104u8 || raw[4] != 121u8) { return 95; };\n"
" return 46;\n"
"};\n",
46,
STAGE_CS | STAGE_WW, 1 },
{ "stream_read_unread",
"package main;\n"
"import os;\n"
"import bufio;\n"
"import memio;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let raw: [8]u8;\n"
" raw[0] = 65u8; raw[1] = 66u8; raw[2] = 67u8; raw[3] = 68u8;\n"
" raw[4] = 69u8; raw[5] = 70u8; raw[6] = 71u8; raw[7] = 72u8;\n"
" let mst: memio.stream = memio.fixed(raw[0:8]);\n"
" let msrc: io.stream = &mst.vt;\n"
" let rbuf: [8]u8;\n"
" let wbuf: [4]u8;\n"
" let b: bufio.stream = bufio.init(msrc, rbuf[0:8], wbuf[0:4]);\n"
" let vs: io.stream = &b.vt;\n"
" let out: [4]u8;\n"
" let rd1 = io.read(vs, out[0:3]);\n"
" let n1: size = 0;\n"
" if (rd1 is size) { n1 = rd1 as size; };\n"
" if (n1 != 3: size) { return 91; };\n"
" if (out[0] != 65u8 || out[2] != 67u8) { return 92; };\n"
" let rd2 = io.read(vs, out[0:4]);\n"
" let n2: size = 0;\n"
" if (rd2 is size) { n2 = rd2 as size; };\n"
" if (n2 != 4: size) { return 93; };\n"
" if (out[0] != 68u8 || out[3] != 71u8) { return 94; };\n"
" return 47;\n"
"};\n",
47,
STAGE_CS | STAGE_WW, 1 },
{ "isbuffered_v_discriminate",
"package main;\n"
"import os;\n"
"import bufio;\n"
"import memio;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let raw: [8]u8;\n"
" let mst: memio.stream = memio.fixed(raw[0:8]);\n"
" let msrc: io.stream = &mst.vt;\n"
" let rbuf: [4]u8;\n"
" let wbuf: [4]u8;\n"
" let b: bufio.stream = bufio.init(msrc, rbuf[0:4], wbuf[0:4]);\n"
" let vsbuf: io.stream = &b.vt;\n"
" let buf2: [4]u8;\n"
" let st2: memio.stream = memio.fixed(buf2[0:4]);\n"
" let vsplain: io.stream = &st2.vt;\n"
" if (!bufio.isbuffered(vsbuf)) { return 91; };\n"
" if (bufio.isbuffered(vsplain)) { return 92; };\n"
" return 48;\n"
"};\n",
48,
STAGE_CS | STAGE_WW, 1 },
{ "isbuffered_v_boundary",
"package main;\n"
"import os;\n"
"import bufio;\n"
"import memio;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let raw: [8]u8;\n"
" let mst: memio.stream = memio.fixed(raw[0:8]);\n"
" let msrc: io.stream = &mst.vt;\n"
" let rbuf: [4]u8;\n"
" let wbuf: [4]u8;\n"
" let bc: bufio.stream = bufio.init(msrc, rbuf[0:4], wbuf[0:4]);\n"
" let vs: io.stream = &bc.vt;\n"
" if (!bufio.isbuffered(vs)) { return 91; };\n"
" if (bufio.isbuffered(msrc)) { return 92; };\n"
" return 49;\n"
"};\n",
49,
STAGE_CS | STAGE_WW, 1 },
{ "branched_bufio_wrap",
"package main;\n"
"import os;\n"
"import bufio;\n"
"import memio;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let raA: [4]u8;\n"
" raA[0] = 10u8; raA[1] = 11u8; raA[2] = 12u8; raA[3] = 13u8;\n"
" let raB: [4]u8;\n"
" raB[0] = 20u8; raB[1] = 21u8; raB[2] = 22u8; raB[3] = 23u8;\n"
" let mstA: memio.stream = memio.fixed(raA[0:4]);\n"
" let mstB: memio.stream = memio.fixed(raB[0:4]);\n"
" let sel: i32 = 1;\n"
" let src: io.stream = &mstA.vt;\n"
" if (sel == 0) { src = &mstB.vt; };\n"
" let rbuf: [4]u8;\n"
" let wbuf: [4]u8;\n"
" let bc: bufio.stream = bufio.init(src, rbuf[0:4], wbuf[0:4]);\n"
" let vs: io.stream = &bc.vt;\n"
" let out: [4]u8;\n"
" let rd = io.read(vs, out[0:4]);\n"
" let n: size = 0;\n"
" if (rd is size) { n = rd as size; };\n"
" if (n != 4: size) { return 91; };\n"
" if (out[0] != 10u8 || out[3] != 13u8) { return 92; };\n"
" return 51;\n"
"};\n",
51,
STAGE_CS | STAGE_WW, 1 },
/* scanner_lines — fold-eFinal PREP: the value-return scanner over an
* io.stream src. newscannerbuf (Hare newscanner_buf) → scanline
* twice ("ab", "c") → trailing "def" with no newline is EOF_DISCARD
* → io.eof → finish. Pins the scanner reads through io.read
* (vtable dispatch) on the underlying memio stream. cs.s==ww.s. */
{ "scanner_lines",
"package main;\n"
"import os;\n"
"import bufio;\n"
"import memio;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let raw: [8]u8;\n"
" raw[0]=97u8; raw[1]=98u8; raw[2]=10u8;\n"
" raw[3]=99u8; raw[4]=10u8;\n"
" raw[5]=100u8; raw[6]=101u8; raw[7]=102u8;\n"
" let mst: memio.stream = memio.dynamicfrom(raw[0:8]);\n"
" let vs: io.stream = &mst.vt;\n"
" let win: [16]u8;\n"
" let sc: bufio.scanner = bufio.newscannerbuf(vs, win[0:16]);\n"
" let ok: i32 = 0;\n"
" match (bufio.scanline(&sc)) {\n"
" case let s: str => { if (s.len == 2 && s[0] == 97u8 && s[1] == 98u8) { ok += 1; }; };\n"
" case io.eof => { return 81; };\n"
" case let e: io.error => { return 82; };\n"
" case bufio.overflow => { return 83; };\n"
" };\n"
" match (bufio.scanline(&sc)) {\n"
" case let s: str => { if (s.len == 1 && s[0] == 99u8) { ok += 1; }; };\n"
" case io.eof => { return 84; };\n"
" case let e: io.error => { return 85; };\n"
" case bufio.overflow => { return 86; };\n"
" };\n"
" match (bufio.scanline(&sc)) {\n"
" case let s: str => { return 87; };\n"
" case io.eof => { ok += 1; };\n"
" case let e: io.error => { return 88; };\n"
" case bufio.overflow => { return 89; };\n"
" };\n"
" bufio.finish(&sc);\n"
" if (ok != 3) { return 90; };\n"
" return 52;\n"
"};\n",
52,
STAGE_CS | STAGE_WW, 1 },
/* scanner_byte_bytes — scanbyte pops 'A'; scanbytes(',') then
* tokenizes "B" and "CD" from "AB,CD,". Single-byte delim (the OLD
* scantok behaviour; multibyte is #217). cs.s==ww.s. */
{ "scanner_byte_bytes",
"package main;\n"
"import os;\n"
"import bufio;\n"
"import memio;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let raw: [6]u8;\n"
" raw[0]=65u8; raw[1]=66u8; raw[2]=44u8;\n"
" raw[3]=67u8; raw[4]=68u8; raw[5]=44u8;\n"
" let mst: memio.stream = memio.dynamicfrom(raw[0:6]);\n"
" let vs: io.stream = &mst.vt;\n"
" let win: [8]u8;\n"
" let sc: bufio.scanner = bufio.newscannerbuf(vs, win[0:8]);\n"
" let fb: u8 = 0u8;\n"
" match (bufio.scanbyte(&sc)) {\n"
" case let b: u8 => { fb = b; };\n"
" case io.eof => { return 81; };\n"
" case let e: io.error => { return 82; };\n"
" case bufio.overflow => { return 92; };\n"
" };\n"
" if (fb != 65u8) { return 83; };\n"
" match (bufio.scanbytes(&sc, 44u8)) {\n"
" case let bs: []u8 => { if (bs.len != 1 || bs[0] != 66u8) { return 84; }; };\n"
" case io.eof => { return 85; };\n"
" case let e: io.error => { return 86; };\n"
" case bufio.overflow => { return 87; };\n"
" };\n"
" match (bufio.scanbytes(&sc, 44u8)) {\n"
" case let bs: []u8 => { if (bs.len != 2 || bs[0] != 67u8 || bs[1] != 68u8) { return 88; }; };\n"
" case io.eof => { return 89; };\n"
" case let e: io.error => { return 90; };\n"
" case bufio.overflow => { return 91; };\n"
" };\n"
" return 53;\n"
"};\n",
53,
STAGE_CS | STAGE_WW, 1 },
/* stream_setflush — setflush swaps the auto-flush byte-set to ';';
* writing "ab;" auto-flushes (contains ';') so the sink advances to
* 3 with no explicit flush. Pins setflush + the flush-scan path.
* cs.s==ww.s. */
{ "stream_setflush",
"package main;\n"
"import os;\n"
"import bufio;\n"
"import memio;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let sinkbuf: [16]u8;\n"
" let sink: memio.stream = memio.fixed(sinkbuf[0:16]);\n"
" let sinkvs: io.stream = &sink.vt;\n"
" let rb: [4]u8;\n"
" let wb: [8]u8;\n"
" let b: bufio.stream = bufio.init(sinkvs, rb[0:4], wb[0:8]);\n"
" let bvs: io.stream = &b.vt;\n"
" let semi: [1]u8;\n"
" semi[0] = 59u8;\n"
" bufio.setflush(&b, semi[0:1]);\n"
" let payload: [3]u8;\n"
" payload[0]=97u8; payload[1]=98u8; payload[2]=59u8;\n"
" let wr = io.write(bvs, payload[0:3]);\n"
" if (!(wr is size)) { return 81; };\n"
" if (sink.pos != 3) { return 82; };\n"
" if (sinkbuf[0] != 97u8 || sinkbuf[2] != 59u8) { return 83; };\n"
" return 54;\n"
"};\n",
54,
STAGE_CS | STAGE_WW, 1 },
/* stream_unread — read "XYZ" then unread("XY") pushes the two
* bytes back in front of the read buffer; the next read returns them
* first. Pins unread's in-place shift through *stream.
* cs.s==ww.s. */
{ "stream_unread",
"package main;\n"
"import os;\n"
"import bufio;\n"
"import memio;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let raw: [4]u8;\n"
" raw[0]=88u8; raw[1]=89u8; raw[2]=90u8;\n"
" let mst: memio.stream = memio.dynamicfrom(raw[0:3]);\n"
" let vs: io.stream = &mst.vt;\n"
" let rbuf: [4]u8;\n"
" let wbuf: [4]u8;\n"
" let b: bufio.stream = bufio.init(vs, rbuf[0:4], wbuf[0:4]);\n"
" let bvs: io.stream = &b.vt;\n"
" let out: [4]u8;\n"
" let rd = io.read(bvs, out[0:3]);\n"
" let n: size = 0;\n"
" if (rd is size) { n = rd as size; };\n"
" if (n != 3: size || out[0] != 88u8 || out[2] != 90u8) { return 81; };\n"
" let push: [2]u8;\n"
" push[0]=88u8; push[1]=89u8;\n"
" bufio.unread(&b, push[0:2]);\n"
" let out2: [2]u8;\n"
" let rd2 = io.read(bvs, out2[0:2]);\n"
" let n2: size = 0;\n"
" if (rd2 is size) { n2 = rd2 as size; };\n"
" if (n2 != 2: size || out2[0] != 88u8 || out2[1] != 89u8) { return 82; };\n"
" return 55;\n"
"};\n",
55,
STAGE_CS | STAGE_WW, 1 },
};
static int
write_source(const char *path, const char *src)
{
FILE *f = fopen(path, "wb");
if (!f) return -1;
fputs(src, f);
fclose(f);
return 0;
}
/* Per-row tmpdir cleanup. ww_ww writes intermediates next to the
* source (filed task #15), so each row's build leaves
* <src>.{combined.ww,s,o} + bare exe alongside. Sweep all then
* rmdir. Mirror of 776's/777's cleanup_tmp. */
static void
cleanup_tmp(const char *tmpdir, const char *base)
{
char p[640];
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p);
/* #93 sep layout: the sep scratch dir + the tmpdir-pinned pkgcache. */
snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir);
if (system(p)) {} /* best-effort */
rmdir(tmpdir);
}
static int
build_via_driver(const char *driver, const char *tmpdir, const char *cwd,
const char *src)
{
char cmd[2048];
/* #93 sep layout: `--sep -o <src-stem>` emits the asm to
* <stem>.sepwork/__root.s; WW_PKGCACHE is pinned under tmpdir so the
* shared out/.pkgcache is untouched and the cache dies with the
* tmpdir. PRESERVES the `-I %s/lib` import-dir flag. */
snprintf(cmd, sizeof cmd,
"cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep "
"-I %s/lib -o \"${b%%.ww}\" \"$b\" 2>/dev/null",
tmpdir, src, tmpdir, driver, cwd);
return runwait(cmd);
}
static int
run_row(const char *driver, const char *cwd, const struct row *r, int seq)
{
char tmpdir[256], src[512], base[64], outbin[768];
snprintf(tmpdir, sizeof tmpdir, "/tmp/bvs_%d_d_%d", getpid(), seq);
snprintf(base, sizeof base, "main778");
snprintf(src, sizeof src, "%s/%s.ww", tmpdir, base);
mkdir(tmpdir, 0755);
if (write_source(src, r->src) != 0) { cleanup_tmp(tmpdir, base); return -1; }
int rc;
int br = build_via_driver(driver, tmpdir, cwd, src);
if (br == 0) {
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
rc = runwait(outbin);
} else {
rc = -1;
}
cleanup_tmp(tmpdir, base);
return rc;
}
/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so
* ww_ww writing intermediates next to the source doesn't clobber the
* cstage .s (CLAUDE.md rule 14 phase split). Mirror of 776's/777's. */
static int
asm_byte_identical(const char *cdrv, const char *wdrv, const char *cwd,
const struct row *r, int seq)
{
char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512];
snprintf(tdc, sizeof tdc, "/tmp/bvs_%d_c_%d", getpid(), seq);
snprintf(tdw, sizeof tdw, "/tmp/bvs_%d_w_%d", getpid(), seq);
snprintf(base, sizeof base, "main778");
mkdir(tdc, 0755);
mkdir(tdw, 0755);
snprintf(src, sizeof src, "%s/%s.ww", tdc, base);
if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; }
int rc = -1;
if (build_via_driver(cdrv, tdc, cwd, src) != 0) goto out;
snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base);
snprintf(src, sizeof src, "%s/%s.ww", tdw, base);
if (write_source(src, r->src) != 0) goto out;
if (build_via_driver(wdrv, tdw, cwd, src) != 0) goto out;
snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base);
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
if (fc && fw) {
rc = 0;
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
out:
cleanup_tmp(tdc, base);
cleanup_tmp(tdw, base);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char cwd[256];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
char absbin[512];
if (bin[0] != '/') {
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[640], wdrv[640];
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;
int wwpresent = (access(wdrv, X_OK) == 0);
int seq = 0;
for (int i = 0; i < n; i++) {
if (rows[i].stage_mask & STAGE_CS) {
total++;
int got = run_row(cdrv, cwd, &rows[i], seq++);
if (got != rows[i].want_exit) {
fprintf(stderr,
"init_run[cs][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
}
if (wwpresent && (rows[i].stage_mask & STAGE_WW)) {
total++;
int got = run_row(wdrv, cwd, &rows[i], seq++);
if (got != rows[i].want_exit) {
fprintf(stderr,
"init_run[ww][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
if (rows[i].byte_id) {
total++;
if (asm_byte_identical(cdrv, wdrv, cwd, &rows[i], seq++) != 0) {
fprintf(stderr,
"init_run[byte-id][%s]: cstage vs wwstage asm differs\n",
rows[i].label);
fail++;
}
}
}
}
if (!wwpresent)
fprintf(stderr, "init_run: skip wwstage (no %s)\n", wdrv);
if (fail) {
fprintf(stderr, "init_run: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("init_run: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,385 +0,0 @@
/*
* 791_io_handle_run — project #5 commit-1 sentinel. Pins the lib/io
* handle dispatch surface: `handle = (file | stream)` and the
* read/write/close/seek/tell dispatchers (ref/hare/io/handle.ha:16-67)
* over BOTH arms:
*
* - file-arm: a real OS fd (io.file = i32) routed to
* os.{read,write,lseek,close} — the Q2 layering (lib/io imports os,
* os is the import floor). The file-arm syscall-error path returns
* the #199b errors.unsupported placeholder (see lib/io/types.ww:40),
* pinned by the file_arm_error_stub row (a never-opened fd forces
* EBADF on every file-arm dispatcher).
* - stream-arm: a memio (or hand-built) vtable routed to the private
* st_read/st_write/st_seek (ref/hare/io/stream.ha:44-77).
*
* row | what it pins
* -------------------------+--------------------------------------
* file_arm_roundtrip | open(/tmp) → io.write "ABC" → io.seek
* | SET 0 → io.read back → io.seek END == 3
* | → io.tell == 3 → io.close. Real fd
* | through every file-arm dispatcher;
* | SET+END+CUR(via tell) whence. exit 42.
* stream_write_read | memio.fixed: io.write 3 bytes, verify
* | the backing buffer, then read them back
* | through io.read on a fresh fixed stream.
* | exit 43.
* stream_seek_unsupported | hand-built vtable with the seeker slot
* | left void; io.seek takes st_seek's
* | void-arm and returns an io.error (not
* | an off). exit 44. (Was memio.fixed:
* | memio wires a seeker now, so its seek
* | succeeds — pinned by memiotest; this
* | row keeps the void-arm dispatch pin.)
* stream_seeker_tell | hand-built vtable WITH a seeker fn:
* | io.seek returns the seeker's off, and
* | io.tell == seek(s, 0, CUR). exit 45.
* file_arm_error_stub | never-opened fd (9999) → every file-arm
* | dispatcher's syscall returns -1 (EBADF)
* | → the #199b errors.unsupported stub
* | comes back as io.error. exit 46.
*
* Both stages run each row; cs.s == ww.s byte-id is asserted per row
* (rule-10). GATE POLARITY: must stay GREEN — a red means the handle
* union decompose, a dispatcher arm, or the seeker slot regressed.
*/
#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;
}
#define STAGE_CS 1
#define STAGE_WW 2
struct row {
const char *label;
const char *src;
int want_exit;
int stage_mask;
int byte_id;
};
static const struct row rows[] = {
{ "file_arm_roundtrip",
"package main;\n"
"import io;\n"
"import os;\n"
"export fn main() i32 = {\n"
" let cflags: os.flag = os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC;\n"
" let fd: i32 = 0;\n"
" match (os.tryopen(\"/tmp/ww_io791_handle.dat\", cflags, 420i32)) {\n"
" case let e: os.oserror => return 81;\n"
" case let f: i32 => fd = f;\n"
" };\n"
" let h: io.file = fd;\n"
" let data: [3]u8;\n"
" data[0] = 65u8; data[1] = 66u8; data[2] = 67u8;\n"
" match (io.write(h, data[0:3])) {\n"
" case let n: size => { if (n != 3: size) { return 82; }; };\n"
" case let e: io.error => return 83;\n"
" };\n"
" match (io.seek(h, 0: io.off, io.whence.SET)) {\n"
" case let o: io.off => { if (o != 0: io.off) { return 84; }; };\n"
" case let e: io.error => return 85;\n"
" };\n"
" let rb: [3]u8;\n"
" match (io.read(h, rb[0:3])) {\n"
" case let n: size => { if (n != 3: size) { return 86; }; };\n"
" case let z: io.eof => return 87;\n"
" case let e: io.error => return 88;\n"
" };\n"
" if (rb[0] != 65u8 || rb[1] != 66u8 || rb[2] != 67u8) { return 89; };\n"
" match (io.seek(h, 0: io.off, io.whence.END)) {\n"
" case let o: io.off => { if (o != 3: io.off) { return 93; }; };\n"
" case let e: io.error => return 94;\n"
" };\n"
" match (io.tell(h)) {\n"
" case let o: io.off => { if (o != 3: io.off) { return 90; }; };\n"
" case let e: io.error => return 91;\n"
" };\n"
" match (io.close(h)) {\n"
" case void => void;\n"
" case let e: io.error => return 92;\n"
" };\n"
" return 42;\n"
"};\n",
42,
STAGE_CS | STAGE_WW, 1 },
{ "stream_write_read",
"package main;\n"
"import io;\n"
"import memio;\n"
"export fn main() i32 = {\n"
" let buf: [8]u8;\n"
" let st: memio.stream = memio.fixed(buf[0:8]);\n"
" let s: io.stream = &st.vt;\n"
" let data: [3]u8;\n"
" data[0] = 88u8; data[1] = 89u8; data[2] = 90u8;\n"
" match (io.write(s, data[0:3])) {\n"
" case let n: size => { if (n != 3: size) { return 71; }; };\n"
" case let e: io.error => return 72;\n"
" };\n"
" if (buf[0] != 88u8 || buf[1] != 89u8 || buf[2] != 90u8) { return 73; };\n"
" let st2: memio.stream = memio.fixed(buf[0:8]);\n"
" let s2: io.stream = &st2.vt;\n"
" let rb: [3]u8;\n"
" match (io.read(s2, rb[0:3])) {\n"
" case let n: size => { if (n != 3: size) { return 74; }; };\n"
" case let z: io.eof => return 75;\n"
" case let e: io.error => return 76;\n"
" };\n"
" if (rb[0] != 88u8 || rb[1] != 89u8 || rb[2] != 90u8) { return 77; };\n"
" return 43;\n"
"};\n",
43,
STAGE_CS | STAGE_WW, 1 },
{ "stream_seek_unsupported",
"package main;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let vt: io.vtable;\n"
" let s: io.stream = &vt;\n"
" match (io.seek(s, 0: io.off, io.whence.SET)) {\n"
" case let o: io.off => return 61;\n"
" case let e: io.error => return 44;\n"
" };\n"
"};\n",
44,
STAGE_CS | STAGE_WW, 1 },
{ "stream_seeker_tell",
"package main;\n"
"import io;\n"
"fn myseek(s: io.stream, off: io.off, w: io.whence) (io.off | io.error) = {\n"
" return off + 100: io.off;\n"
"};\n"
"export fn main() i32 = {\n"
" let vt: io.vtable;\n"
" vt.seeker = (&myseek): *io.seeker;\n"
" let s: io.stream = &vt;\n"
" match (io.seek(s, 5: io.off, io.whence.SET)) {\n"
" case let o: io.off => { if (o != 105: io.off) { return 51; }; };\n"
" case let e: io.error => return 52;\n"
" };\n"
" match (io.tell(s)) {\n"
" case let o: io.off => { if (o != 100: io.off) { return 53; }; };\n"
" case let e: io.error => return 54;\n"
" };\n"
" return 45;\n"
"};\n",
45,
STAGE_CS | STAGE_WW, 1 },
{ "file_arm_error_stub",
"package main;\n"
"import io;\n"
"export fn main() i32 = {\n"
/* A never-opened fd: every file-arm syscall returns -1 (EBADF),
* driving the #199b errors.unsupported stub on all four
* dispatchers (write/read/seek/close). Since that stub is the
* only error-producing path reachable here, an io.error arm
* taken == the placeholder came back. */
" let h: io.file = 9999i32;\n"
" let data: [3]u8;\n"
" data[0] = 65u8; data[1] = 66u8; data[2] = 67u8;\n"
" match (io.write(h, data[0:3])) {\n"
" case let n: size => return 61;\n"
" case let e: io.error => void;\n"
" };\n"
" match (io.read(h, data[0:3])) {\n"
" case let n: size => return 62;\n"
" case let z: io.eof => return 63;\n"
" case let e: io.error => void;\n"
" };\n"
" match (io.seek(h, 0: io.off, io.whence.SET)) {\n"
" case let o: io.off => return 64;\n"
" case let e: io.error => void;\n"
" };\n"
" match (io.close(h)) {\n"
" case void => return 65;\n"
" case let e: io.error => void;\n"
" };\n"
" return 46;\n"
"};\n",
46,
STAGE_CS | STAGE_WW, 1 },
};
static int
write_source(const char *path, const char *src)
{
FILE *f = fopen(path, "wb");
if (!f) return -1;
fputs(src, f);
fclose(f);
return 0;
}
/* Per-row tmpdir cleanup. ww_ww writes intermediates next to the source
* (filed task #15), so each row's build leaves <src>.{combined.ww,s,o}
* + bare exe alongside. Sweep them all then rmdir. */
static void
cleanup_tmp(const char *tmpdir, const char *base)
{
char p[640];
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p);
/* #93 sep layout: the sep scratch dir + the tmpdir-pinned pkgcache. */
snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir);
if (system(p)) {} /* best-effort */
rmdir(tmpdir);
}
static int
build_via_driver(const char *driver, const char *tmpdir, const char *cwd,
const char *src)
{
char cmd[2048];
/* #93 sep layout: `--sep -o <src-stem>` emits the asm to
* <stem>.sepwork/__root.s; WW_PKGCACHE is pinned under tmpdir so the
* shared out/.pkgcache is untouched and the cache dies with the
* tmpdir. PRESERVES the `-I %s/lib` import-dir flag. */
snprintf(cmd, sizeof cmd,
"cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep "
"-I %s/lib -o \"${b%%.ww}\" \"$b\" 2>/dev/null",
tmpdir, src, tmpdir, driver, cwd);
return runwait(cmd);
}
static int
run_row(const char *driver, const char *cwd, const struct row *r, int seq)
{
char tmpdir[256], src[512], base[64], outbin[768];
snprintf(tmpdir, sizeof tmpdir, "/tmp/ioh_%d_d_%d", getpid(), seq);
snprintf(base, sizeof base, "main791");
snprintf(src, sizeof src, "%s/%s.ww", tmpdir, base);
mkdir(tmpdir, 0755);
if (write_source(src, r->src) != 0) { cleanup_tmp(tmpdir, base); return -1; }
int rc;
int br = build_via_driver(driver, tmpdir, cwd, src);
if (br == 0) {
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
rc = runwait(outbin);
} else {
rc = -1;
}
cleanup_tmp(tmpdir, base);
return rc;
}
/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so
* ww_ww writing intermediates next to the source doesn't clobber the
* cstage .s (CLAUDE.md rule 14 phase split). */
static int
asm_byte_identical(const char *cdrv, const char *wdrv, const char *cwd,
const struct row *r, int seq)
{
char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512];
snprintf(tdc, sizeof tdc, "/tmp/ioh_%d_c_%d", getpid(), seq);
snprintf(tdw, sizeof tdw, "/tmp/ioh_%d_w_%d", getpid(), seq);
snprintf(base, sizeof base, "main791");
mkdir(tdc, 0755);
mkdir(tdw, 0755);
snprintf(src, sizeof src, "%s/%s.ww", tdc, base);
if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; }
int rc = -1;
if (build_via_driver(cdrv, tdc, cwd, src) != 0) goto out;
snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base);
snprintf(src, sizeof src, "%s/%s.ww", tdw, base);
if (write_source(src, r->src) != 0) goto out;
if (build_via_driver(wdrv, tdw, cwd, src) != 0) goto out;
snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base);
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
if (fc && fw) {
rc = 0;
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
out:
cleanup_tmp(tdc, base);
cleanup_tmp(tdw, base);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char cwd[256];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
char absbin[512];
if (bin[0] != '/') {
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[640], wdrv[640];
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;
int wwpresent = (access(wdrv, X_OK) == 0);
int seq = 0;
for (int i = 0; i < n; i++) {
if (rows[i].stage_mask & STAGE_CS) {
total++;
int got = run_row(cdrv, cwd, &rows[i], seq++);
if (got != rows[i].want_exit) {
fprintf(stderr,
"io_handle_run[cs][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
}
if (wwpresent && (rows[i].stage_mask & STAGE_WW)) {
total++;
int got = run_row(wdrv, cwd, &rows[i], seq++);
if (got != rows[i].want_exit) {
fprintf(stderr,
"io_handle_run[ww][%s]: exit=%d want=%d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
if (rows[i].byte_id) {
total++;
if (asm_byte_identical(cdrv, wdrv, cwd, &rows[i], seq++) != 0) {
fprintf(stderr,
"io_handle_run[byte-id][%s]: cstage vs wwstage asm differs\n",
rows[i].label);
fail++;
}
}
}
}
if (!wwpresent)
fprintf(stderr, "io_handle_run: skip wwstage (no %s)\n", wdrv);
if (fail) {
fprintf(stderr, "io_handle_run: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("io_handle_run: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,159 +0,0 @@
/*
* 792_spread_variant_match — project #209 close. Pins that the wwstage
* checker accepts a `match` over a tagged union with a `...inner` SPREAD
* variant, byte-identically with cstage (rule-10).
*
* THE BUG (wwstage-CHECKER-only, cs!=ww): a spread variant
* `type field = (...inner | str)` flattens its inner union's members
* into `field` (i64|bool|rune|str). cstage's resolve_type performs the
* flatten at type-build (cmd/wcc/check.c:651-674), so its match-arm
* validity + exhaustiveness see the flattened members. wwstage's checker
* (selfhost/cmd/wcc/check.ww checkmatchexhaust) walked the RAW AST
* `u.list`, which keeps the spread unexpanded — every member arm tripped
* "case: not a variant of scrutinee" and the phantom spread node tripped
* "match: variant not handled". So any module that matches over a spread
* union (e.g. fmt's `field = (...formattable | *mods)`) was
* wwstage-uncompilable. cstage compiled it fine. Hare flattens spreads;
* align UP to cstage (a too-strict checker, NOT a down-align).
*
* THE FIX (#209): casevariantin + a checkvariantcovered coverage helper
* recurse into a spread variant's inner-union members (resolvealias →
* N_TTAGGED → walk members), mirroring cstage's flatten at the AST
* layer; the #61a tinfo build additionally sizes the union off the
* flattened MEMBERS (not the whole inner union) so the slot matches
* cstage's. cgen already dispatches off the flattened tinfo.params
* (flatvariantidxt), so no cgen change is needed — checker-only.
*
* The PRE-FIX failure mode is a wwstage CHECKER REJECT, so the
* discriminator is `w6c_ww` on the driver-produced combined.ww
* succeeding AT ALL — pre-fix it errored out; post-fix it succeeds AND
* is byte-id with cstage's w6c. The probe stays on the param+match shape
* (a fn taking the spread union and matching it, exhaustive, no
* default): that is exactly what #209's checker fix covers and is
* byte-id. (Spread-union CONSTRUCTION/return-ABI is a separate,
* pre-existing both-stage cgen path — out of #209 scope — so it is
* deliberately NOT exercised here.) Self-contained single-file probe.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.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);
int cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
static const char SRC[] =
"package main;\n"
"\n"
"type inner = (i64 | bool | rune);\n"
"type field = (...inner | str);\n"
"\n"
"fn classify(f: field) i64 = {\n"
" match (f) {\n"
" case let n: i64 => return n;\n"
" case let b: bool => return 1;\n"
" case let r: rune => return 2;\n"
" case let s: str => return (s.len: i64);\n"
" };\n"
"};\n"
"\n"
"export fn main() i32 = { return 0; };\n";
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[2048];
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[2100], wdrv[2100];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
if (access(wdrv, X_OK) != 0) {
fprintf(stderr, "792: ww_ww missing — cannot run the cs==ww "
"byte-id gate (the whole point of this test)\n");
return 1;
}
char dir[] = "/tmp/ww792_XXXXXX";
if (mkdtemp(dir) == NULL) { fprintf(stderr, "792: mkdtemp\n"); return 1; }
char path[1024], cmd[4096], cs_s[1024], ws_s[1024];
int rc = 0;
snprintf(path, sizeof path, "%s/main.ww", dir);
FILE *f = fopen(path, "wb");
if (!f) { fprintf(stderr, "792: write main.ww\n"); rc = 1; goto done; }
fputs(SRC, f);
fclose(f);
/* #94: sep layout. The cstage driver's --sep build emits the root
* unit's asm to <stem>.sepwork/__root.s via its internal w6c; pin
* WW_PKGCACHE under the scratch dir so out/.pkgcache is untouched. */
snprintf(cs_s, sizeof cs_s, "%s/main.sepwork/__root.s", dir);
snprintf(ws_s, sizeof ws_s, "%s/mainww.sepwork/__root.s", dir);
snprintf(cmd, sizeof cmd,
"cd %s && WW_PKGCACHE=%s/pkgc_c %s build --sep -I %s -o %s/main %s/main.ww",
dir, dir, cdrv, dir, dir, dir);
if (runwait(cmd) != 0) {
fprintf(stderr, "792: cstage sep build failed\n");
rc = 1; goto done;
}
/* The #209 discriminator: the wwstage driver's --sep build runs the
* wwstage checker over the same root unit. Pre-fix it REJECTED the
* spread-union match arms (build fails); post-fix it accepts AND its
* w6c_ww-emitted __root.s is byte-id with the cstage __root.s. */
snprintf(cmd, sizeof cmd,
"cd %s && WW_PKGCACHE=%s/pkgc_w %s build --sep -I %s -o %s/mainww %s/main.ww",
dir, dir, wdrv, dir, dir, dir);
if (runwait(cmd) != 0) {
fprintf(stderr, "792: wwstage sep build failed "
"(#209 spread-union match reject?)\n");
rc = 1; goto done;
}
if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr, "792: cs.s/ww.s DIFFER (rule-10 byte-id "
"violation)\n");
rc = 1; goto done;
}
printf("spread_variant_match: ok (w6c_ww accepts spread match + "
"cs==ww byte-id)\n");
done:
snprintf(cmd, sizeof cmd, "rm -rf %s", dir);
(void)runwait(cmd);
return rc;
}

View File

@@ -1,491 +0,0 @@
/*
* 800_append_wide_elem — cstage and wwstage agree, byte-for-byte and at
* runtime, that `append(s, v)` stores the FULL element width for every
* element kind (task #34; the regex fold-2a blocker).
*
* The bug: both stages lowered the append element store as ONE sized
* mov from AX (`MOV* AX, (BX)`) — correct only for scalars <= 8B. A
* str/slice element (24B {ptr,len,cap}, cgexpr -> AX/BX/CX) kept only
* .ptr (byte-id-BLIND: both stages identical and identically wrong); a
* tagged element got the raw payload written into the tag slot (no
* boxing — the #12 pathology); a struct element kept only its first
* qword. wwstage ADDITIONALLY fed rt_ensure membsz from bare
* elemsizeof, whose 8-sentinel under-allocated AND mis-strided named
* tagged/struct elements (the #8 family; cs!=ww on the `MOVQ $N, SI`
* line and the stride IMUL).
*
* The fix (BOTH stages, converged byte-identical), keyed on the
* DECLARED slice local's element type (cstage su->sub; wwstage stamped
* tinfo via elemsizeofc — NEVER the value node, the #25/#31 esz=0
* trap):
* - scalar 1/2/4/8: UNTOUCHED (the u8 row's asm is unchanged vs
* pre-fix master — regression-pinned by byte-id + runtime).
* - str/slice: push AX/BX/CX across rt_ensure, dst in DX (BX holds
* the element .len after the pops — the #24 register discipline),
* 3-word store at (DX)/8(DX)/16(DX).
* - tagged: grow FIRST, dst -> BX, then the #12 widen choke-point
* (cg_widen_tagged_store / cgwidentaggedstore) boxes {tag,payload}
* through @tagbase/@tagscr (via_outer mode).
* - struct: grow first; literal -> dst spilled to @appendscr +
* structlit fill (DST_PTR_LOCAL); local ident -> word-copy; ANY
* other source shape is a rule-7 loud-stop (build fails), never a
* silent scalar fall-through.
* - spread `append(s, items...)`: the source element is already a
* fully-formed T (tag included), so the wide arm grows first and
* whole-width word-copies &items[i] -> dst, recomputing both
* addresses from the slice headers after the (possibly
* reallocating) rt_ensure.
*
* row | shape | want
* ---------------------+--------------------------------------+------
* u8_baseline | []u8, two appends, sum | 8
* enum_alias_esz | []ek (enum i32 alias) — pins the | 5
* | elemsizeofc swap: wwstage fed SI=$8 |
* | where cstage fed $4 (cs!=ww growth). |
* tagged_box | [](i64|bool), append 100i64, match | 100
* | readback. Pre-fix: raw 100 landed in |
* | the tag slot, no arm matched. |
* tagged_two_append | two appends — element 0 survives the | 42
* | realloc full-width. |
* str_len_bytes | []str, append, len + byte readback | 11
* str_two_append | two str appends, len0*10 + len1 | 32
* slice_elem | [][]u8, append, len + byte readback | 12
* struct_first8 | []pt (16B), x+y (first qword — ken's | 8
* | characterization row) |
* struct_tail_word | []pt, z at +8 (the word the 1-word | 8
* | store dropped) |
* struct_lit_two | two struct-LITERAL appends (pins the | 7
* | @appendscr per-fn dedup: frame + |
* | byte-id diverge if cstage allocs two |
* | scratch slots where wwstage dedups) |
* spread_str | append(ys, xs...) of []str + single | 7
* spread_tagged | append(ys, xs...) of [](i64|bool) | 42
* tagged_ident_src | append of ALREADY-TAGGED locals | 42
* | (i64 + bool members) — the widener's |
* | already-tagged source path. |
* struct_eight_appends | 8 struct-lit appends of the getopt | 15
* | option shape ({rune,str} = 32B), |
* | full-width readback of element 7 — |
* | the >6-element OOB regression pin |
* | (getopt's deleted helper fed a stale |
* | hardcoded membsz=24: 8-slot grow was |
* | 192B while writes strode 32; the |
* | builtin derives 32 from the type |
* | table, MOVQ $32, SI). |
* struct_call_loudstop | append(xs, f()) struct-from-call is | BUILD_FAIL
* | the deferred source shape — must |
* | fail LOUD on both stages (rule-7), |
* | never silently store one word. |
* spread_call_loudstop | append(ys, f()...) non-ident spread | BUILD_FAIL
* | source — pre-review this fell PAST |
* | the spread arm (cstage garbage store |
* | vs wwstage silent skip, divergent); |
* | now a rule-7 loud-stop (task #37). |
*
* Every non-BUILD_FAIL row also asserts cstage/wwstage asm byte-id,
* which subsumes the frame-size canary (TEXT main,$N) — the @tagbase/
* @tagscr/@appendscr scratch allocations must land in the same order
* and size on both stages (#25/#31 lesson).
*/
#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;
}
/* want == BUILD_FAIL: the row must FAIL to build on both stages (the
* rule-7 loud-stop for a deferred source shape). run_driver returns -1
* exactly when the build fails, so `got == -1` is the pass. */
#define BUILD_FAIL (-2147483647 - 1)
struct row { const char *label; const char *src; int want; };
static const struct row rows[] = {
{ "u8_baseline",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []u8 = [];\n"
"\tappend(xs, 5u8);\n"
"\tappend(xs, 3u8);\n"
"\treturn (xs[0] + xs[1]): i32;\n"
"};\n",
8 },
{ "enum_alias_esz",
"package main;\n"
"type ek = enum { A = 5, B = 9 };\n"
"export fn main() i32 = {\n"
"\tlet xs: []ek = [];\n"
"\tappend(xs, ek.A);\n"
"\treturn xs[0]: i32;\n"
"};\n",
5 },
{ "tagged_box",
"package main;\n"
"type cell = (i64 | bool);\n"
"export fn main() i32 = {\n"
"\tlet xs: []cell = [];\n"
"\tappend(xs, 100i64);\n"
"\tlet r: i32 = 1;\n"
"\tmatch (xs[0]) {\n"
"\tcase let v: i64 => r = v: i32;\n"
"\tcase bool => r = 2;\n"
"\t};\n"
"\treturn r;\n"
"};\n",
100 },
{ "tagged_two_append",
"package main;\n"
"type cell = (i64 | bool);\n"
"export fn main() i32 = {\n"
"\tlet xs: []cell = [];\n"
"\tappend(xs, 40i64);\n"
"\tappend(xs, 2i64);\n"
"\tlet r: i32 = 0;\n"
"\tmatch (xs[0]) {\n"
"\tcase let v: i64 => r += v: i32;\n"
"\tcase bool => r = 99;\n"
"\t};\n"
"\tmatch (xs[1]) {\n"
"\tcase let v: i64 => r += v: i32;\n"
"\tcase bool => r = 98;\n"
"\t};\n"
"\treturn r;\n"
"};\n",
42 },
{ "str_len_bytes",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []str = [];\n"
"\tlet s: str = \"abcdef\";\n"
"\tappend(xs, s);\n"
"\treturn (xs[0].len: i32) + (xs[0][5]: i32) - (xs[0][0]: i32);\n"
"};\n",
11 },
{ "str_two_append",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []str = [];\n"
"\tlet a: str = \"abc\";\n"
"\tlet b: str = \"fg\";\n"
"\tappend(xs, a);\n"
"\tappend(xs, b);\n"
"\treturn (xs[0].len * 10 + xs[1].len): i32;\n"
"};\n",
32 },
{ "slice_elem",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet hb: [4]u8; hb[0] = 9u8; hb[1] = 4u8;\n"
"\tlet a: []u8; a.ptr = &hb[0]; a.len = 3; a.cap = 4;\n"
"\tlet ys: [][]u8 = [];\n"
"\tappend(ys, a);\n"
"\treturn (ys[0].len: i32) + (ys[0][0]: i32);\n"
"};\n",
12 },
{ "struct_first8",
"package main;\n"
"type pt = struct { x: i32, y: i32, z: i64 };\n"
"export fn main() i32 = {\n"
"\tlet xs: []pt = [];\n"
"\tlet p: pt = pt { x = 3, y = 5, z = 9 };\n"
"\tappend(xs, p);\n"
"\treturn (xs[0].x + xs[0].y): i32;\n"
"};\n",
8 },
{ "struct_tail_word",
"package main;\n"
"type pt = struct { x: i32, y: i32, z: i64 };\n"
"export fn main() i32 = {\n"
"\tlet xs: []pt = [];\n"
"\tlet p: pt = pt { x = 3, y = 5, z = 9 };\n"
"\tappend(xs, p);\n"
"\treturn (xs[0].z - 1): i32;\n"
"};\n",
8 },
{ "struct_lit_two",
"package main;\n"
"type pt = struct { x: i32, y: i32, z: i64 };\n"
"export fn main() i32 = {\n"
"\tlet xs: []pt = [];\n"
"\tappend(xs, pt { x = 1, y = 2, z = 5 });\n"
"\tappend(xs, pt { x = 3, y = 4, z = 7 });\n"
"\treturn (xs[0].z + xs[1].z): i32 - xs[0].x - xs[1].y;\n"
"};\n",
7 },
{ "spread_str",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []str = [];\n"
"\tlet a: str = \"abc\";\n"
"\tlet b: str = \"fg\";\n"
"\tappend(xs, a);\n"
"\tlet ys: []str = [];\n"
"\tappend(ys, xs...);\n"
"\tappend(ys, b);\n"
"\treturn (ys[0].len + ys[1].len + ys.len): i32;\n"
"};\n",
7 },
{ "spread_tagged",
"package main;\n"
"type cell = (i64 | bool);\n"
"export fn main() i32 = {\n"
"\tlet xs: []cell = [];\n"
"\tappend(xs, 40i64);\n"
"\tappend(xs, 2i64);\n"
"\tlet ys: []cell = [];\n"
"\tappend(ys, xs...);\n"
"\tlet r: i32 = 0;\n"
"\tmatch (ys[0]) {\n"
"\tcase let v: i64 => r += v: i32;\n"
"\tcase bool => r = 99;\n"
"\t};\n"
"\tmatch (ys[1]) {\n"
"\tcase let v: i64 => r += v: i32;\n"
"\tcase bool => r = 98;\n"
"\t};\n"
"\treturn r;\n"
"};\n",
42 },
{ "tagged_ident_src",
"package main;\n"
"type cell = (i64 | bool);\n"
"export fn main() i32 = {\n"
"\tlet xs: []cell = [];\n"
"\tlet c: cell = 40i64;\n"
"\tlet b: cell = true;\n"
"\tappend(xs, c);\n"
"\tappend(xs, b);\n"
"\tlet r: i32 = 0;\n"
"\tmatch (xs[0]) {\n"
"\tcase let v: i64 => r += v: i32;\n"
"\tcase bool => r = 99;\n"
"\t};\n"
"\tmatch (xs[1]) {\n"
"\tcase i64 => r = 98;\n"
"\tcase let w: bool => { if (w) { r += 2; }; };\n"
"\t};\n"
"\treturn r;\n"
"};\n",
42 },
{ "struct_eight_appends",
"package main;\n"
"type option = struct { flag: rune, value: str };\n"
"export fn main() i32 = {\n"
"\tlet opts: []option = [];\n"
"\tappend(opts, option { flag = 'a', value = \"v0\" });\n"
"\tappend(opts, option { flag = 'b', value = \"v1\" });\n"
"\tappend(opts, option { flag = 'c', value = \"v2\" });\n"
"\tappend(opts, option { flag = 'd', value = \"v3\" });\n"
"\tappend(opts, option { flag = 'e', value = \"v4\" });\n"
"\tappend(opts, option { flag = 'f', value = \"v5\" });\n"
"\tappend(opts, option { flag = 'g', value = \"v6\" });\n"
"\tappend(opts, option { flag = 'h', value = \"seventh\" });\n"
"\treturn (opts[7].value.len + opts.len): i32;\n"
"};\n",
15 },
{ "struct_call_loudstop",
"package main;\n"
"type pt = struct { x: i32, y: i32, z: i64 };\n"
"fn mk() pt = {\n"
"\treturn pt { x = 1, y = 2, z = 3 };\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet xs: []pt = [];\n"
"\tappend(xs, mk());\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL },
{ "spread_call_loudstop",
"package main;\n"
"fn mks() []str = {\n"
"\tlet xs: []str = [];\n"
"\tlet a: str = \"abc\";\n"
"\tappend(xs, a);\n"
"\treturn xs;\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet ys: []str = [];\n"
"\tappend(ys, mks()...);\n"
"\treturn ys[0].len: i32;\n"
"};\n",
BUILD_FAIL },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/apwe_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/apwe_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/apwe_%d_%d", tmpdir, getpid(), i);
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 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
if (r->want != BUILD_FAIL)
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
* w6c_ww and diff. Subsumes the frame-size canary: a scratch-order or
* esz divergence shows up on the TEXT main,$N line or the MOVQ $N, SI
* line. Pre-fix the tagged/struct rows differed (wwstage SI=$8 vs $16);
* the str rows were byte-id-blind (both wrong identically), which the
* runtime rows above catch. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/apwe_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/apwe_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/apwe_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "append_wide_elem: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
int bad = rows[i].want == BUILD_FAIL
? (got != -1) : (got != rows[i].want);
if (bad) {
fprintf(stderr,
"append_wide_elem[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
if (rows[i].want == BUILD_FAIL)
continue;
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"append_wide_elem: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("append_wide_elem: %d fixtures passed\n", total);
return 0;
}

View File

@@ -1,482 +0,0 @@
/*
* 804_delete_elem — cstage and wwstage agree, byte-for-byte and at
* runtime, that `delete(xs[i])` removes element i from a slice: the
* tail [i+1..len) shifts down one stride, len -= 1, cap unchanged
* (the delete-half of task #35; insert() stays deferred; regex
* fold-2b's delete_thread/failed-thread/PC-dedup consumers).
*
* Lowering (BOTH stages, converged byte-identical by construction —
* there was no pre-existing reference side): push i and &hdr, then an
* ascending word-copy loop moves element j+1 onto element j until
* j >= len-1, then hdr.len -= 1. The element move is a same-type
* whole-stride byte copy — src and dst are elements of the SAME
* slice, so no boxing/coercion exists for any element kind; the rows
* below prove that across scalar (8B), narrow (4B/1B — the MOVL/MOVB
* copy tails), str (24B header), struct (16B) and tagged (56B) esz.
* esz comes off the STAMPED base type (the #34/#48 discipline), so a
* named-alias element cannot mis-stride.
*
* Hare's delete also accepts the range form delete(xs[i..j])
* (ref/harec/src/check.c:1981-2027 EXPR_SLICE) — implemented as the
* fold-5a prereq P2; covered by 809_delete_range.
*
* row | shape | want
* ----------------+----------------------------------------+------
* i64_first | [7,11,13], delete(xs[0]) | 44
* i64_middle | [7,11,13], delete(xs[1]) | 40
* i64_last | [7,11,13], delete(xs[2]) (no copy, | 38
* | loop body never runs) |
* i32_narrow | []i32 esz=4 — the MOVL copy tail | 229
* u16_narrow | []u16 esz=2 — the MOVW copy tail (the | 26
* | last otherwise-unexercised tail arm) |
* u8_narrow | []u8 esz=1 — the MOVB copy tail | 28
* cap_unchanged | manual {ptr,len=3,cap=8} header over a | 91
* | stack backing; delete middle; cap must |
* | still read 8, tail value intact |
* delete_to_empty | 1-element slice, delete(xs[0]), len=0 | 7
* str_elem | []str esz=24 — 3-qword header moves | 234
* | whole |
* tagged_56b | [](s6|bool) esz=56 — 7-qword raw move, | 231
* | tag+payload survive; match head + |
* | raw-byte tail readback (#43 caveat) |
* regex_shape | delete((*threads)[i]) behind *[]thread |
* | — the fold-2b delete_thread shape | 24
* | (deref-of-local base, struct element) |
* delete_in_loop | drain [1,2,3,4] to empty via repeated | 112
* | delete(xs[0]) — len bookkeeping under |
* | iteration, base-4 order accumulation |
* reject_array | delete(t[0]) on [3]i64 — "delete must | BUILD_FAIL
* | operate on a slice" |
* reject_nonindex | delete(xs) — operand must be an index- | BUILD_FAIL
* | ing or slicing expression |
* reject_arity | delete(xs[0], xs[1]) | BUILD_FAIL
*
* BUILD_FAIL rows also assert the diagnostic TEXT (stderr substring,
* both stages) — a build that fails for any other reason (parse error,
* crash) is a vacuous reject and fails the row.
*
* Every non-BUILD_FAIL row also asserts cstage/wwstage asm byte-id,
* which subsumes the frame canary (TEXT main,$N) and the del_l/del_e
* label-counter symmetry.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.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;
}
/* want == BUILD_FAIL: the row must FAIL to build on both stages AND
* emit expect_err on stderr (the checker reject set — message text
* included — is part of the contract: rule 7, never a silent
* acceptance; without the message check a row would pass vacuously on
* any unrelated build failure). */
#define BUILD_FAIL (-2147483647 - 1)
struct row {
const char *label;
const char *src;
int want;
const char *expect_err; /* BUILD_FAIL rows: required stderr substring */
};
static const struct row rows[] = {
{ "i64_first",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 7);\n"
"\tappend(xs, 11);\n"
"\tappend(xs, 13);\n"
"\tdelete(xs[0]);\n"
"\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n"
"};\n",
44, NULL },
{ "i64_middle",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 7);\n"
"\tappend(xs, 11);\n"
"\tappend(xs, 13);\n"
"\tdelete(xs[1]);\n"
"\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n"
"};\n",
40, NULL },
{ "i64_last",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 7);\n"
"\tappend(xs, 11);\n"
"\tappend(xs, 13);\n"
"\tdelete(xs[2]);\n"
"\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n"
"};\n",
38, NULL },
{ "i32_narrow",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i32 = [];\n"
"\tappend(xs, 4i32);\n"
"\tappend(xs, 9i32);\n"
"\tappend(xs, 2i32);\n"
"\tdelete(xs[0]);\n"
"\treturn xs[0] + xs[1]*10 + len(xs)*100;\n"
"};\n",
229, NULL },
{ "u16_narrow",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []u16 = [];\n"
"\tappend(xs, 4u16);\n"
"\tappend(xs, 9u16);\n"
"\tappend(xs, 2u16);\n"
"\tdelete(xs[1]);\n"
"\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n"
"};\n",
26, NULL },
{ "u8_narrow",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []u8 = [];\n"
"\tappend(xs, 5u8);\n"
"\tappend(xs, 9u8);\n"
"\tappend(xs, 3u8);\n"
"\tdelete(xs[1]);\n"
"\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n"
"};\n",
28, NULL },
{ "cap_unchanged",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet hb: [8]u8; hb[0] = 4u8; hb[1] = 6u8; hb[2] = 9u8;\n"
"\tlet xs: []u8; xs.ptr = &hb[0]; xs.len = 3; xs.cap = 8;\n"
"\tdelete(xs[1]);\n"
"\treturn (xs.cap*10 + xs.len): i32 + xs[1]: i32;\n"
"};\n",
91, NULL },
{ "delete_to_empty",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []u8 = [];\n"
"\tappend(xs, 5u8);\n"
"\tdelete(xs[0]);\n"
"\treturn len(xs) + 7;\n"
"};\n",
7, NULL },
{ "str_elem",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []str = [];\n"
"\tlet a: str = \"abc\";\n"
"\tlet b: str = \"de\";\n"
"\tlet g: str = \"fghi\";\n"
"\tappend(xs, a);\n"
"\tappend(xs, b);\n"
"\tappend(xs, g);\n"
"\tdelete(xs[1]);\n"
"\treturn (xs[0].len*10 + xs[1].len): i32 + len(xs)*100;\n"
"};\n",
234, NULL },
/* 56B element: tag qword + 48B struct payload — seven whole-qword
* moves; tag AND payload must both survive the raw move (a tagged
* element at rest is just bytes; no boxing on a same-slice move).
* Readback is split: match proves tag + head (s.a), and the tail
* qword (f, element byte 48 -> absolute byte 104 of element 1) is
* read RAW via a *u8 over xs.ptr — the indexed-match payload
* cursor itself truncates past 32B (pre-existing, task #43), so a
* match on s.f would mis-fail regardless of the move. Verified
* truncation-free against a no-delete control. */
{ "tagged_56b",
"package main;\n"
"type s6 = struct { a: i64, b: i64, c: i64, d: i64, e: i64, f: i64 };\n"
"type cell = (s6 | bool);\n"
"export fn main() i32 = {\n"
"\tlet xs: []cell = [];\n"
"\tlet p0: s6 = s6 { a = 1, b = 2, c = 3, d = 4, e = 5, f = 6 };\n"
"\tlet p1: s6 = s6 { a = 7, b = 8, c = 9, d = 10, e = 11, f = 12 };\n"
"\tlet p2: s6 = s6 { a = 13, b = 14, c = 15, d = 16, e = 17, f = 18 };\n"
"\tappend(xs, p0);\n"
"\tappend(xs, p1);\n"
"\tappend(xs, p2);\n"
"\tdelete(xs[1]);\n"
"\tlet r: i32 = 0;\n"
"\tmatch (xs[1]) {\n"
"\tcase let s: s6 => r = s.a: i32;\n"
"\tcase bool => r = 99;\n"
"\t};\n"
"\tlet bp: *u8 = xs.ptr: *u8;\n"
"\tr += bp[104]: i32;\n"
"\treturn r + len(xs)*100;\n"
"};\n",
231, NULL },
/* The fold-2b consumer shape: regex.ha:547-551 delete_thread takes
* threads: *[]thread and deletes through the pointer. ww spells the
* autoderef explicitly: delete((*threads)[i]). */
{ "regex_shape",
"package main;\n"
"type thread = struct { pc: i64, matched: bool };\n"
"fn delete_thread(i: i64, threads: *[]thread) void = {\n"
"\tdelete((*threads)[i]);\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet xs: []thread = [];\n"
"\tappend(xs, thread { pc = 1, matched = false });\n"
"\tappend(xs, thread { pc = 2, matched = false });\n"
"\tappend(xs, thread { pc = 3, matched = false });\n"
"\tdelete_thread(1, &xs);\n"
"\tlet r: i32 = (xs[0].pc + xs[1].pc): i32;\n"
"\treturn r + len(xs)*10;\n"
"};\n",
24, NULL },
/* Pins the len bookkeeping under iteration: each pass reads the
* new head then deletes it; base-4 positional accumulation makes
* any wrong order, double-shift, or stale len visible (a stuck
* len would never terminate; the row would time out as a wrong
* exit via the harness). 1,2,3,4 -> ((1*4+2)*4+3)*4+4 = 112. */
{ "delete_in_loop",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 1);\n"
"\tappend(xs, 2);\n"
"\tappend(xs, 3);\n"
"\tappend(xs, 4);\n"
"\tlet acc: i64 = 0;\n"
"\tfor (len(xs) > 0) {\n"
"\t\tacc = acc*4 + xs[0];\n"
"\t\tdelete(xs[0]);\n"
"\t};\n"
"\treturn acc: i32 + len(xs)*1000;\n"
"};\n",
112, NULL },
{ "reject_array",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: [3]i64 = [1, 2, 3];\n"
"\tdelete(t[0]);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL, "delete must operate on a slice" },
{ "reject_nonindex",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []u8 = [];\n"
"\tappend(xs, 5u8);\n"
"\tdelete(xs);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL, "delete: operand must be an indexing or slicing expression" },
{ "reject_arity",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []u8 = [];\n"
"\tappend(xs, 5u8);\n"
"\tappend(xs, 6u8);\n"
"\tdelete(xs[0], xs[1]);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL, "delete: takes exactly one argument" },
};
/* errlog_has — the build-failure stderr must carry the row's expected
* diagnostic; any other failure (parse error, crash) is a vacuous
* reject and must not pass. */
static int
errlog_has(const char *path, const char *needle)
{
FILE *f = fopen(path, "rb");
if (!f) return 0;
char buf[8192];
size_t got = fread(buf, 1, sizeof buf - 1, f);
fclose(f);
buf[got] = '\0';
return strstr(buf, needle) != NULL;
}
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], errlog[160], rmcmd[160];
char cmd[1200];
snprintf(tmpdir, sizeof tmpdir, "/tmp/dele_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/dele_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/dele_%d_%d", tmpdir, getpid(), i);
snprintf(errlog, sizeof errlog, "%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 2>%s",
driver, outbin, src, errlog);
if (runwait(cmd) != 0) {
int rc = -1;
if (r->want != BUILD_FAIL) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
} else if (r->expect_err &&
!errlog_has(errlog, r->expect_err)) {
fprintf(stderr, "row[%s]: %s build failed without "
"expected diagnostic \"%s\"\n",
r->label, driver, r->expect_err);
rc = -3; /* failed, but for the wrong reason */
}
runwait(rmcmd);
return rc;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
* w6c_ww and diff. Both delete lowerings are written fresh, so this is
* the converged-by-construction gate: any drift in the shift loop, the
* del_l/del_e label sequence, or the esz word-copy shows here. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/dele_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/dele_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/dele_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "delete_elem: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
int bad = rows[i].want == BUILD_FAIL
? (got != -1) : (got != rows[i].want);
if (bad) {
fprintf(stderr,
"delete_elem[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
if (rows[i].want == BUILD_FAIL)
continue;
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"delete_elem: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("delete_elem: %d fixtures passed\n", total);
return 0;
}

View File

@@ -1,473 +0,0 @@
/*
* 806_tryprop_multisuccess — the `?` operator's interim single-success
* gate (F8, task #5) and the direct try-form is/match reject parity
* (F9, task #12).
*
* F8: ww's `?` assumes ONE success member end-to-end — the checker
* collapses the result to the first non-error variant and cgen emits a
* single tag compare — so `f()?` over (A|B|err) silently PROPAGATED
* the other success member to the caller as if it were an error
* (scratch/fold2b_probes/p11h, exit 21; graduated below as
* reject_success2). Until the honest subset-union result typing lands
* (task #14, harec check.c:2759-2835), BOTH stages loud-reject
* |success| > 1 at the checker; (T|err1|err2) — one success, many
* errors — stays legal (runtime canary: 925_tryprop_tag_remap_run).
*
* F9: cstage types `f()?` as the success variant, so the direct forms
* `f()? is T` / `match (f()?)` hit its non-tagged is/match gates and
* reject — while wwstage's scruttype (IDENT/DOT-only) silently
* ACCEPTED the same files (cs≠ww, gate-blind). Align-richer-DOWN:
* wwstage gains the same verdicts (text differs per per-stage diag
* conventions, asserted exactly per stage below). A success variant
* that is itself a named tagged union must KEEP being accepted
* (accept_nested_tagged — guards the mirror against over-rejecting).
*
* row | shape | want
* ----------------------+--------------------------------------+------
* reject_success2 | (void|[]capture|nomem)? — p11h | BUILD_FAIL
* reject_success3 | (i32|bool|u64|nomem)? | BUILD_FAIL
* reject_try_is | f()? is i32, success = i32 | BUILD_FAIL
* reject_try_match | match (f()?), success = i32 | BUILD_FAIL
* reject_unw_success2 | (void|[]capture|nomem)! — `!` is the | BUILD_FAIL
* | same class (rob ruling, #133) |
* reject_tryunw_is | f()! is i32 — kind-agnostic sibling | BUILD_FAIL
* reject_callarg | g(f()?) — gate in call-arg position | BUILD_FAIL
* accept_two_member | (i32|nomem)? unwrap runtime | 0
* accept_unw_multi_error| (i32|e1|e2)! unwrap runtime | 0
* accept_void_success | (void|nomem)? statement runtime | 0
* accept_nested_tagged | (ab|nomem)? is i32, ab = (i32|bool) | 0
*
* BUILD_FAIL rows assert the diagnostic TEXT per stage (a build that
* fails for any other reason is a vacuous reject and fails the row).
* Accept rows also assert cstage/wwstage asm byte-id.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.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;
}
/* want == BUILD_FAIL: the row must FAIL to build on both stages AND
* emit the per-stage expected diagnostic on stderr (rule 7 — never a
* silent acceptance). */
#define BUILD_FAIL (-2147483647 - 1)
struct row {
const char *label;
const char *src;
int want;
const char *expect_cs; /* BUILD_FAIL: required cstage stderr substring */
const char *expect_ww; /* BUILD_FAIL: required wwstage stderr substring */
};
#define MULTISUCC_DIAG \
"?: multi-success union unwired (task #14): bind and match instead"
static const struct row rows[] = {
/* p11h graduated: pre-gate this BUILT and exited 21 (the []capture
* success wrongly propagated and read back as nomem). */
{ "reject_success2",
"package main;\n"
"type capture = struct { content: str, start: size, end: size };\n"
"fn search2(k: i32) (void | []capture | nomem) = {\n"
"\tif (k == 0) { return; };\n"
"\tlet caps: []capture = [];\n"
"\tappend(caps, capture { content = \"xy\", start = 1, end = 3 });\n"
"\treturn caps;\n"
"};\n"
"fn tb(k: i32) (i32 | nomem) = {\n"
"\tlet r: (void | []capture) = search2(k)?;\n"
"\tif (r is []capture) { return 7; };\n"
"\treturn 8;\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet x: (i32 | nomem) = tb(1);\n"
"\tif (x is nomem) { return 21; };\n"
"\tif (x is i32) { return 22; };\n"
"\treturn 23;\n"
"};\n",
BUILD_FAIL, MULTISUCC_DIAG, MULTISUCC_DIAG },
{ "reject_success3",
"package main;\n"
"fn f(k: i32) (i32 | bool | u64 | nomem) = { return 7; };\n"
"fn g() (i32 | nomem) = {\n"
"\tlet v: i32 = f(1)?;\n"
"\treturn v;\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet x: (i32 | nomem) = g();\n"
"\tif (x is i32) { return 0; };\n"
"\treturn 1;\n"
"};\n",
BUILD_FAIL, MULTISUCC_DIAG, MULTISUCC_DIAG },
/* F9 graduation rows: the ?-result is bare i32, so `is`/`match`
* over it must reject on BOTH stages (pre-fix wwstage accepted). */
{ "reject_try_is",
"package main;\n"
"fn f(k: i32) (i32 | nomem) = { return 7; };\n"
"fn g() (i32 | nomem) = {\n"
"\tif (f(1)? is i32) { return 1; };\n"
"\treturn 0;\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet x: (i32 | nomem) = g();\n"
"\tif (x is i32) { return 0; };\n"
"\treturn 1;\n"
"};\n",
BUILD_FAIL,
"is on non-tagged-union",
"is/as: operand is not a tagged union" },
{ "reject_try_match",
"package main;\n"
"fn f(k: i32) (i32 | nomem) = { return 7; };\n"
"fn g() (i32 | nomem) = {\n"
"\tmatch (f(1)?) {\n"
"\tcase i32 => return 1;\n"
"\t};\n"
"\treturn 0;\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet x: (i32 | nomem) = g();\n"
"\tif (x is i32) { return 0; };\n"
"\treturn 1;\n"
"};\n",
BUILD_FAIL,
"match on non-tagged-union",
"match on non-tagged-union try-result" },
/* `!` shares the single-success collapse — one class, both ops
* (rob ruling, #133 precedent): pre-gate this BUILT on BOTH stages
* and silently aborted-on-success at runtime (probe q_card2_unw,
* graduated). */
{ "reject_unw_success2",
"package main;\n"
"type capture = struct { content: str, start: size, end: size };\n"
"fn search2(k: i32) (void | []capture | nomem) = {\n"
"\tif (k == 0) { return; };\n"
"\tlet caps: []capture = [];\n"
"\tappend(caps, capture { content = \"xy\", start = 1, end = 3 });\n"
"\treturn caps;\n"
"};\n"
"fn tb(k: i32) i32 = {\n"
"\tlet r: (void | []capture) = search2(k)!;\n"
"\tif (r is []capture) { return 7; };\n"
"\treturn 8;\n"
"};\n"
"export fn main() i32 = { return tb(1); };\n",
BUILD_FAIL,
"!: multi-success union unwired (task #14): bind and match instead",
"!: multi-success union unwired (task #14): bind and match instead" },
/* cstage's reject derives from the typed result, not the operator
* kind — `!` collapses identically, so its direct form must match
* verdicts too (pre-fix wwstage built this and misbehaved). */
{ "reject_tryunw_is",
"package main;\n"
"fn f(k: i32) (i32 | nomem) = { return 7; };\n"
"fn g() i32 = {\n"
"\tif (f(1)! is i32) { return 1; };\n"
"\treturn 0;\n"
"};\n"
"export fn main() i32 = { return g() - 1; };\n",
BUILD_FAIL,
"is on non-tagged-union",
"is/as: operand is not a tagged union" },
/* `?` nested in a CALL-ARG: the gate keys on the operand's type at
* the checker expression walk, so position must not matter — guards
* against a walker path that types call args without visiting the
* try node. */
{ "reject_callarg",
"package main;\n"
"fn f(k: i32) (i32 | bool | nomem) = { return 7; };\n"
"fn g(v: i32) i32 = { return v; };\n"
"fn h() (i32 | nomem) = {\n"
"\treturn g(f(1)?);\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet x: (i32 | nomem) = h();\n"
"\tif (x is i32) { return 0; };\n"
"\treturn 1;\n"
"};\n",
BUILD_FAIL, MULTISUCC_DIAG, MULTISUCC_DIAG },
/* |success| == 1: the gate must not fire and the unwrap must keep
* working at runtime. The 3-member single-success shape
* (i32|e1|e2) is pinned by 925_tryprop_tag_remap_run. */
{ "accept_two_member",
"package main;\n"
"fn f(k: i32) (i32 | nomem) = { return 7; };\n"
"fn g() (i32 | nomem) = {\n"
"\tlet v: i32 = f(1)?;\n"
"\treturn v + 1;\n"
"};\n"
"export fn main() i32 = {\n"
"\tmatch (g()) {\n"
"\tcase let v: i32 => return v - 8;\n"
"\tcase nomem => return 2;\n"
"\t};\n"
"\treturn 3;\n"
"};\n",
0, NULL, NULL },
/* |success| == 1 with MULTIPLE errors under `!`: the gate must not
* fire and the success unwrap must keep working at runtime (the
* `!` twin of 925's ? canary shape). */
{ "accept_unw_multi_error",
"package main;\n"
"type e1 = !void;\n"
"type e2 = !void;\n"
"fn g(which: i32) (i32 | e1 | e2) = {\n"
"\tif (which == 1) { return e1{}; };\n"
"\tif (which == 2) { return e2{}; };\n"
"\treturn 100;\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet v: i32 = g(0)!;\n"
"\treturn v - 100;\n"
"};\n",
0, NULL, NULL },
/* VOID success: (void|nomem)? as an expression-statement is the
* dominant lib/ shape (io writes etc.) — void counts as the one
* success member, nsucc == 1, the gate must not fire. */
{ "accept_void_success",
"package main;\n"
"fn f(k: i32) (void | nomem) = {\n"
"\treturn;\n"
"};\n"
"fn g() (i32 | nomem) = {\n"
"\tf(1)?;\n"
"\treturn 5;\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet x: (i32 | nomem) = g();\n"
"\tmatch (x) {\n"
"\tcase let v: i32 => return v - 5;\n"
"\tcase nomem => return 2;\n"
"\t};\n"
"\treturn 3;\n"
"};\n",
0, NULL, NULL },
/* Success variant is itself a NAMED tagged union: `f()? is i32`
* is `is` over (i32|bool) — tagged — and cstage ACCEPTS. The
* wwstage F9 mirror must key on the resolved success type, not
* on the try syntax, or this over-rejects. */
{ "accept_nested_tagged",
"package main;\n"
"type ab = (i32 | bool);\n"
"fn f(k: i32) (ab | nomem) = {\n"
"\tlet v: ab = 7;\n"
"\treturn v;\n"
"};\n"
"fn g() (i32 | nomem) = {\n"
"\tif (f(1)? is i32) { return 0; };\n"
"\treturn 1;\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet x: (i32 | nomem) = g();\n"
"\tmatch (x) {\n"
"\tcase let v: i32 => return v;\n"
"\tcase nomem => return 2;\n"
"\t};\n"
"\treturn 3;\n"
"};\n",
0, NULL, NULL },
};
/* errlog_has — the build-failure stderr must carry the row's expected
* diagnostic; any other failure (parse error, crash) is a vacuous
* reject and must not pass. */
static int
errlog_has(const char *path, const char *needle)
{
FILE *f = fopen(path, "rb");
if (!f) return 0;
char buf[8192];
size_t got = fread(buf, 1, sizeof buf - 1, f);
fclose(f);
buf[got] = '\0';
return strstr(buf, needle) != NULL;
}
static int
run_driver(const char *driver, const char *expect, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], errlog[128], rmcmd[160], cmd[1200];
snprintf(tmpdir, sizeof tmpdir, "/tmp/tpms_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/tpms_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/tpms_%d_%d", tmpdir, getpid(), i);
snprintf(errlog, sizeof errlog, "%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 2>%s",
driver, outbin, src, errlog);
if (runwait(cmd) != 0) {
int rc = -1;
if (r->want != BUILD_FAIL) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
} else if (expect && !errlog_has(errlog, expect)) {
fprintf(stderr, "row[%s]: %s build failed without "
"expected diagnostic \"%s\"\n",
r->label, driver, expect);
rc = -3; /* failed, but for the wrong reason */
}
runwait(rmcmd);
return rc;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
* w6c_ww and diff (rule 10 on the accept rows). */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/tpms_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/tpms_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/tpms_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[2120];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct {
const char *name;
const char *path;
int is_ww;
int gated_on_existence;
} drivers[] = {
{ "cstage", cdrv, 0, 0 },
{ "wwstage", wdrv, 1, 1 },
{ NULL, NULL, 0, 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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr,
"tryprop_multisuccess: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
const char *expect = drivers[d].is_ww
? rows[i].expect_ww : rows[i].expect_cs;
int got = run_driver(drivers[d].path, expect,
&rows[i], i);
total++;
int bad = rows[i].want == BUILD_FAIL
? (got != -1) : (got != rows[i].want);
if (bad) {
fprintf(stderr,
"tryprop_multisuccess[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
if (rows[i].want == BUILD_FAIL)
continue;
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"tryprop_multisuccess: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("tryprop_multisuccess: %d fixtures passed\n", total);
return 0;
}

View File

@@ -1,844 +0,0 @@
/*
* 807_insert_elem — cstage and wwstage agree, byte-for-byte and at
* runtime, that `insert(xs[idx], v)` inserts v BEFORE idx: len += 1,
* the tail [idx..oldlen) shifts up one stride, v lands at idx
* (the insert-half of task #35, delete()'s twin; regex fold-3's
* `|`/`?`/`*` compile-arm consumers, regex.ha:347/419/441).
*
* Lowering (BOTH stages, converged byte-identical by construction)
* is a DESUGAR: append(xs, v) — reusing append's grow (rt_ensure)
* and the whole #34 value-store dispatch (scalar / str-slice header /
* tagged widen / struct fill) verbatim, one boxing choke-point —
* lands v at slot len-1; then a rotate-right of [idx, len) moves it
* home through an esz frame scratch (@insscr). The rotate is
* delete's shift loop in reverse (descending j, the safe memmove-up
* direction) and is a same-slice whole-stride raw byte move — no
* boxing exists for any element kind. idx evaluates BEFORE the grow
* (Hare's left-to-right operand order — the pregrow_len_idx row pins
* insert(xs[len(xs)-1], v) reading the pre-grow len; an end-insert
* via len(xs) cannot discriminate, see the end_insert_len row).
*
* idx == len is a legal end-insert per harec's shared append/insert
* checker arm (ref/harec/src/check.c:745, "insert" at :786; the
* ref/hare os/exec/platform_cmd.ha:86 `insert(cmd.env[len(...)...]`
* idiom) — pinned by the i64_end / end_insert_len rows.
*
* row | shape | want
* ----------------+----------------------------------------+------
* i64_front | [7,11,13], insert(xs[0], 5) | 115
* i64_middle | [7,11,13], insert(xs[1], 5) | 97
* i64_end | [7,11,13], insert(xs[3], 5) (idx==len, | 157
* | rotate loop never runs) |
* end_insert_len | insert(xs[len(xs)], v) — idx==len | 13
* | spelled through a dynamic len read |
* pregrow_len_idx | insert(xs[len(xs)-1], v) — the eval- | 48
* | order pin: pre-grow idx=1 -> [7,13,11],|
* | post-grow idx=2 -> [7,11,13] |
* i32_narrow | []i32 esz=4 — the MOVL copy tail | 42
* u16_narrow | []u16 esz=2 — the MOVW copy tail | 43
* u8_narrow | []u8 esz=1 — the MOVB copy tail | 44
* empty_insert | insert(xs[0], v) on an empty slice | 15
* | (grow 0->1; rotate degenerates) |
* str_elem | []str esz=24 — 3-qword headers move | 45
* | whole |
* struct_elem | []p2t esz=16 — struct body insert + | 46
* | survivor shift |
* tagged_56b | [](s6|bool) esz=56, value from a TYPED |
* | LOCAL (the regex newinst shape, |
* | ha:347); tag+payload survive the | 217
* | rotate; match head + raw tail byte |
* tagged_cast | insert(insts[k], (7: inst_split)) — |
* | CAST-rvalue value boxed by append's | 27
* | widen choke-point (ha:419/441 shape) |
* regex_shape | insert((*p)[i], v) behind *[]i64 — |
* | deref-of-local base, delete's | 171
* | regex_shape twin |
* insert_in_loop | front-insert 1,2,3,4 -> [4,3,2,1] — | 47
* | len bookkeeping under iteration + |
* | per-position checks |
* tagged_pregrow_val | #50 value eval-order pin, tagged | 50
* | dst: v = (xs.len: size)+2 reads the |
* | PRE-grow len (post-grow boxing read 5) |
* scalar_pregrow_val | #50 scalar control: value-first | 55
* | order was already Hare-correct |
* tagged_selfref_val | #50: v = xs[1], an element of the | 51
* | dst — pre-grow boxing reads OLD base |
* tagged_str_payload | #50: str-variant box runs pre-grow | 52
* tagged_regex_minrep | #50: the regex {,0} fold-5b shape, | 53
* | len-reading value cast to a named |
* | variant alias (regex.ww:643-650) |
* tagged_realloc_selfref_loop | #50 ken k50a: 30-append | 56
* | self-ref loop, value reads xs[0] across|
* | actual rt_ensure base moves |
* tagged_append_pregrow_val | #50 direct append() pin (the | 54
* | fix site; insert inherits via desugar) |
* tagged_seq_positions | #50 ken k50b: sequenced inserts at | 57
* | 0 then mid, both len-reading values, |
* | full final-order check [200,10,4,20] |
* tagged_void_variant | #50 ken k50c: void box (tag-only) | 58
* | through @apptagscr, then a len-reading |
* | size insert over the mixed slice |
*
* Wants stay under 256 (the exit-status byte); the if-ladder rows
* return a distinct small failure code per check, so a wrong element
* pinpoints itself.
* reject_array | insert(t[0], v) on [3]i64 | BUILD_FAIL
* reject_nonindex | insert(xs, v) | BUILD_FAIL
* reject_range | insert(xs[0:1], v) — not Hare (harec | BUILD_FAIL
* | only parses an index place) |
* reject_arity1 | insert(xs[0]) | BUILD_FAIL
* reject_arity3 | insert(xs[0], a, b) — also covers the | BUILD_FAIL
* | harec with-length form (#35) |
* reject_spread | insert(xs[0], vs...) — multi form | BUILD_FAIL
* | deferred, message cites task #35 |
*
* BUILD_FAIL rows also assert the diagnostic TEXT (stderr substring,
* both stages) — a build that fails for any other reason (parse error,
* crash) is a vacuous reject and fails the row.
*
* Every non-BUILD_FAIL row also asserts cstage/wwstage asm byte-id,
* which subsumes the frame canary (TEXT main,$N — @insscr sizing) and
* the ins_l/ins_e label-counter symmetry.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.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;
}
/* want == BUILD_FAIL: the row must FAIL to build on both stages AND
* emit expect_err on stderr (the checker reject set — message text
* included, esp. the #35 cite on the deferred spread form — is part
* of the contract: rule 7, never a silent acceptance; without the
* message check a row would pass vacuously on any unrelated build
* failure). */
#define BUILD_FAIL (-2147483647 - 1)
struct row {
const char *label;
const char *src;
int want;
const char *expect_err; /* BUILD_FAIL rows: required stderr substring */
};
static const struct row rows[] = {
{ "i64_front",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 7);\n"
"\tappend(xs, 11);\n"
"\tappend(xs, 13);\n"
"\tinsert(xs[0], 5);\n"
"\treturn (xs[0] + xs[1]*10): i32 + len(xs)*10;\n"
"};\n",
115, NULL },
{ "i64_middle",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 7);\n"
"\tappend(xs, 11);\n"
"\tappend(xs, 13);\n"
"\tinsert(xs[1], 5);\n"
"\treturn (xs[0] + xs[1]*10): i32 + len(xs)*10;\n"
"};\n",
97, NULL },
{ "i64_end",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 7);\n"
"\tappend(xs, 11);\n"
"\tappend(xs, 13);\n"
"\tinsert(xs[3], 5);\n"
"\treturn (xs[0] + xs[1]*10): i32 + len(xs)*10;\n"
"};\n",
157, NULL },
/* idx == len spelled THROUGH a dynamic len(xs) read (the
* ref/hare os/exec idiom). NOTE: this row does NOT discriminate
* idx eval order — under the desugar, post-grow idx=newlen
* degenerates the rotate and lands v at the end too; the
* pregrow_len_idx row below is the eval-order pin. */
{ "end_insert_len",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 7);\n"
"\tappend(xs, 11);\n"
"\tinsert(xs[len(xs)], 13);\n"
"\tif (len(xs) != 3) { return 1; };\n"
"\treturn xs[2]: i32;\n"
"};\n",
13, NULL },
/* THE pre-grow eval-order pin (Hare's left-to-right operand
* order): idx = len(xs)-1 reads the PRE-grow len -> idx=1 ->
* [7,13,11]; a post-grow evaluation would compute idx=2 and
* produce a plain end-insert [7,11,13] — every position is
* checked, so the orders are distinguishable. */
{ "pregrow_len_idx",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 7);\n"
"\tappend(xs, 11);\n"
"\tinsert(xs[len(xs) - 1], 13);\n"
"\tif (len(xs) != 3) { return 1; };\n"
"\tif (xs[0] != 7) { return 2; };\n"
"\tif (xs[1] != 13) { return 3; };\n"
"\tif (xs[2] != 11) { return 4; };\n"
"\treturn 48;\n"
"};\n",
48, NULL },
{ "i32_narrow",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i32 = [];\n"
"\tappend(xs, 4i32);\n"
"\tappend(xs, 2i32);\n"
"\tinsert(xs[1], 9i32);\n"
"\tif (len(xs) != 3) { return 1; };\n"
"\tif (xs[0] != 4i32) { return 2; };\n"
"\tif (xs[1] != 9i32) { return 3; };\n"
"\tif (xs[2] != 2i32) { return 4; };\n"
"\treturn 42;\n"
"};\n",
42, NULL },
{ "u16_narrow",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []u16 = [];\n"
"\tappend(xs, 4u16);\n"
"\tappend(xs, 2u16);\n"
"\tinsert(xs[1], 9u16);\n"
"\tif (len(xs) != 3) { return 1; };\n"
"\tif (xs[0] != 4u16) { return 2; };\n"
"\tif (xs[1] != 9u16) { return 3; };\n"
"\tif (xs[2] != 2u16) { return 4; };\n"
"\treturn 43;\n"
"};\n",
43, NULL },
{ "u8_narrow",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []u8 = [];\n"
"\tappend(xs, 5u8);\n"
"\tappend(xs, 3u8);\n"
"\tinsert(xs[0], 2u8);\n"
"\tif (len(xs) != 3) { return 1; };\n"
"\tif (xs[0] != 2u8) { return 2; };\n"
"\tif (xs[1] != 5u8) { return 3; };\n"
"\tif (xs[2] != 3u8) { return 4; };\n"
"\treturn 44;\n"
"};\n",
44, NULL },
{ "empty_insert",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tinsert(xs[0], 5);\n"
"\treturn xs[0]: i32 + len(xs)*10;\n"
"};\n",
15, NULL },
{ "str_elem",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []str = [];\n"
"\tlet a: str = \"abc\";\n"
"\tlet g: str = \"fghi\";\n"
"\tappend(xs, a);\n"
"\tappend(xs, g);\n"
"\tlet b: str = \"de\";\n"
"\tinsert(xs[1], b);\n"
"\tif (len(xs) != 3) { return 1; };\n"
"\tif (xs[0].len != 3) { return 2; };\n"
"\tif (xs[1].len != 2) { return 3; };\n"
"\tif (xs[2].len != 4) { return 4; };\n"
"\treturn 45;\n"
"};\n",
45, NULL },
{ "struct_elem",
"package main;\n"
"type p2t = struct { x: i64, y: i64 };\n"
"export fn main() i32 = {\n"
"\tlet xs: []p2t = [];\n"
"\tappend(xs, p2t { x = 1, y = 2 });\n"
"\tappend(xs, p2t { x = 5, y = 6 });\n"
"\tinsert(xs[1], p2t { x = 3, y = 4 });\n"
"\tif (len(xs) != 3) { return 1; };\n"
"\tif (xs[0].x != 1 || xs[0].y != 2) { return 2; };\n"
"\tif (xs[1].x != 3 || xs[1].y != 4) { return 3; };\n"
"\tif (xs[2].x != 5 || xs[2].y != 6) { return 4; };\n"
"\treturn 46;\n"
"};\n",
46, NULL },
/* 56B element from a TYPED LOCAL — the regex fold-3 newinst
* shape (regex.ha:347: `insert(insts[split_idx], newinst)`).
* Tag qword + 48B payload: seven whole-qword moves through
* @insscr; tag AND payload must survive both the append store
* and the rotate. Readback is split like 804's tagged_56b row:
* match proves tag + head (s.a), and the tail qword of the
* SHIFTED element (f = 18, element 2 byte 48 -> absolute byte
* 160) is read RAW via a *u8 over xs.ptr — the indexed-match
* payload cursor truncates past 32B (pre-existing, task #43). */
{ "tagged_56b",
"package main;\n"
"type s6 = struct { a: i64, b: i64, c: i64, d: i64, e: i64, f: i64 };\n"
"type cell = (s6 | bool);\n"
"export fn main() i32 = {\n"
"\tlet xs: []cell = [];\n"
"\tlet p0: s6 = s6 { a = 1, b = 2, c = 3, d = 4, e = 5, f = 6 };\n"
"\tlet p2: s6 = s6 { a = 13, b = 14, c = 15, d = 16, e = 17, f = 18 };\n"
"\tappend(xs, p0);\n"
"\tappend(xs, p2);\n"
"\tlet p1: cell = (s6 { a = 7, b = 8, c = 9, d = 10, e = 11, f = 12 });\n"
"\tinsert(xs[1], p1);\n"
"\tlet r: i32 = 0;\n"
"\tmatch (xs[1]) {\n"
"\tcase let s: s6 => r = s.a: i32;\n"
"\tcase bool => r = 99;\n"
"\t};\n"
"\tlet bp: *u8 = xs.ptr: *u8;\n"
"\tr += (bp[160]: i32) * 10;\n"
"\treturn r + len(xs)*10;\n"
"};\n",
217, NULL },
/* CAST-rvalue value into a tagged slice — the regex ha:419/441
* shape `insert(insts[term_start_idx], after_idx: inst_split)`;
* append's widen choke-point boxes it PRE-grow (#50). */
{ "tagged_cast",
"package main;\n"
"type inst_lit = rune;\n"
"type inst_split = i64;\n"
"type inst = (inst_lit | inst_split);\n"
"export fn main() i32 = {\n"
"\tlet insts: []inst = [];\n"
"\tappend(insts, ('a': inst_lit));\n"
"\tappend(insts, ('b': inst_lit));\n"
"\tinsert(insts[1], (7: inst_split));\n"
"\tif (len(insts) != 3) { return 1; };\n"
"\tlet r: i32 = 0;\n"
"\tmatch (insts[1]) {\n"
"\tcase let z: inst_split => r += (z: i32);\n"
"\tcase => return 2;\n"
"\t};\n"
"\tmatch (insts[2]) {\n"
"\tcase let l: inst_lit => { if ((l: rune) == 'b') { r += 20; }; };\n"
"\tcase => return 3;\n"
"\t};\n"
"\treturn r;\n"
"};\n",
27, NULL },
/* The deref-of-local base — delete's regex_shape twin: the
* header lives behind a *[]i64 param. */
{ "regex_shape",
"package main;\n"
"fn ins_at(i: i64, p: *[]i64, v: i64) void = {\n"
"\tinsert((*p)[i], v);\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 1);\n"
"\tappend(xs, 3);\n"
"\tins_at(1, &xs, 2);\n"
"\tif (len(xs) != 3) { return 1; };\n"
"\treturn (xs[0] + xs[1]*10 + xs[2]*50): i32;\n"
"};\n",
171, NULL },
/* Pins len bookkeeping under iteration: each front-insert must
* see the grown len and shift the whole accumulated tail;
* per-position checks of [4,3,2,1] make any wrong order, missed
* shift, or stale len visible. */
{ "insert_in_loop",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tlet v: i64 = 1;\n"
"\tfor (v <= 4) {\n"
"\t\tinsert(xs[0], v);\n"
"\t\tv += 1;\n"
"\t};\n"
"\tif (len(xs) != 4) { return 1; };\n"
"\tlet k: i32 = 0;\n"
"\tfor (k < 4) {\n"
"\t\tif (xs[k] != (4 - k): i64) { return 2 + k; };\n"
"\t\tk += 1;\n"
"\t};\n"
"\treturn 47;\n"
"};\n",
47, NULL },
/* #50 THE value eval-order pin for the TAGGED-dst arm (ken's
* f50v4_tagged shape exact): v = (xs.len: size)+2 widens into
* the box. Pre-grow len=2 -> v=4; the pre-#50 post-grow boxing
* read len=3 -> v=5 (the exit-15 path). The scalar arm always
* ordered value-first (scalar_pregrow_val below); #50 aligns
* the boxing arm to it. */
{ "tagged_pregrow_val",
"package main;\n"
"type un = (size | void);\n"
"export fn main() i32 = {\n"
"\tlet xs: []un = [];\n"
"\tappend(xs, 10: size);\n"
"\tappend(xs, 20: size);\n"
"\tinsert(xs[1], ((xs.len: size) + 2));\n"
"\tmatch (xs[1]) {\n"
"\tcase let v: size => { if (v != 4) { return (v: i32) + 10; }; };\n"
"\tcase void => { return 2; };\n"
"\t};\n"
"\tif (len(xs) != 3) { return 3; };\n"
"\treturn 50;\n"
"};\n",
50, NULL },
/* #50 scalar no-regress control (ken's f50_insertorder): the
* scalar-dst arm was Hare-correct pre-#50 — value reads the
* pre-grow len. */
{ "scalar_pregrow_val",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []size = [];\n"
"\tappend(xs, 10: size);\n"
"\tappend(xs, 20: size);\n"
"\tinsert(xs[1], len(xs): size);\n"
"\tif (xs[1] != 2) { return 1; };\n"
"\tif (len(xs) != 3) { return 2; };\n"
"\treturn 55;\n"
"};\n",
55, NULL },
/* #50 SELF-REFERENCE value: v is an element of the destination
* itself — pre-grow boxing must read the OLD base (and survive
* a rt_ensure realloc via the frame scratch): [10,20] ->
* insert(xs[0], xs[1]) -> [20,10,20]. */
{ "tagged_selfref_val",
"package main;\n"
"type un = (size | void);\n"
"export fn main() i32 = {\n"
"\tlet xs: []un = [];\n"
"\tappend(xs, 10: size);\n"
"\tappend(xs, 20: size);\n"
"\tinsert(xs[0], xs[1]);\n"
"\tmatch (xs[0]) {\n"
"\tcase let v: size => { if (v != 20) { return 1; }; };\n"
"\tcase void => { return 2; };\n"
"\t};\n"
"\tmatch (xs[1]) {\n"
"\tcase let v: size => { if (v != 10) { return 3; }; };\n"
"\tcase void => { return 4; };\n"
"\t};\n"
"\tmatch (xs[2]) {\n"
"\tcase let v: size => { if (v != 20) { return 5; }; };\n"
"\tcase void => { return 6; };\n"
"\t};\n"
"\tif (len(xs) != 3) { return 7; };\n"
"\treturn 51;\n"
"};\n",
51, NULL },
/* #50 str-payload tagged box: the widen store's str branch
* (AX=ptr BX=len) also runs pre-grow now — header lands
* whole. */
{ "tagged_str_payload",
"package main;\n"
"type su = (str | void);\n"
"export fn main() i32 = {\n"
"\tlet ss: []su = [];\n"
"\tappend(ss, \"aa\");\n"
"\tinsert(ss[0], \"bb\");\n"
"\tmatch (ss[0]) {\n"
"\tcase let s: str => {\n"
"\t\tif (s.len != 2) { return 1; };\n"
"\t\tif (s[0] != 'b') { return 2; };\n"
"\t};\n"
"\tcase void => { return 3; };\n"
"\t};\n"
"\tmatch (ss[1]) {\n"
"\tcase let s: str => { if (s[0] != 'a') { return 4; }; };\n"
"\tcase void => { return 5; };\n"
"\t};\n"
"\treturn 52;\n"
"};\n",
52, NULL },
/* #50 the regex {,0}-class shape standalone (regex.ww:643-650,
* the fold-5b consumer reviewer-5b's mutant killed): a
* len-reading value CAST to a named variant alias —
* `((insts.len: size) + 2): inst_split`. Pre-grow len=2 -> 4. */
{ "tagged_regex_minrep",
"package main;\n"
"type ispl = size;\n"
"type un = (ispl | void);\n"
"export fn main() i32 = {\n"
"\tlet xs: []un = [];\n"
"\tappend(xs, 7: ispl);\n"
"\tappend(xs, 8: ispl);\n"
"\tinsert(xs[0], ((xs.len: size) + 2): ispl);\n"
"\tmatch (xs[0]) {\n"
"\tcase let v: ispl => { if (v != 4) { return (v: i32) + 10; }; };\n"
"\tcase void => { return 2; };\n"
"\t};\n"
"\tif (len(xs) != 3) { return 3; };\n"
"\treturn 53;\n"
"};\n",
53, NULL },
/* #50 ken's k50a: 30 appends force repeated rt_ensure realloc;
* every value reads xs[0] (as-unwrap + arithmetic) off the
* potentially-moved base — the one shape that pins the OLD-base
* read across an ACTUAL base move, which single-grow rows
* can't. */
{ "tagged_realloc_selfref_loop",
"package main;\n"
"type un = (void | size);\n"
"export fn main() i32 = {\n"
"\tlet xs: []un = [];\n"
"\tappend(xs, 100: size);\n"
"\tlet i: size = 0;\n"
"\tfor (i < 30) {\n"
"\t\tappend(xs, ((xs[0] as size) + i));\n"
"\t\ti += 1;\n"
"\t};\n"
"\tif (len(xs) != 31) { return 1; };\n"
"\tlet k: size = 1;\n"
"\tfor (k < 31) {\n"
"\t\tmatch (xs[k]) {\n"
"\t\tcase let v: size => { if (v != 100 + (k - 1)) { return 2; }; };\n"
"\t\tcase void => { return 3; };\n"
"\t\t};\n"
"\t\tk += 1;\n"
"\t};\n"
"\treturn 56;\n"
"};\n",
56, NULL },
/* #50 direct append() (no insert desugar) — the fix lives in
* append's tagged arm, so pin it without the rotate. */
{ "tagged_append_pregrow_val",
"package main;\n"
"type un = (size | void);\n"
"export fn main() i32 = {\n"
"\tlet xs: []un = [];\n"
"\tappend(xs, 10: size);\n"
"\tappend(xs, 20: size);\n"
"\tappend(xs, (xs.len: size) + 2);\n"
"\tmatch (xs[2]) {\n"
"\tcase let v: size => { if (v != 4) { return (v: i32) + 10; }; };\n"
"\tcase void => { return 2; };\n"
"\t};\n"
"\tif (len(xs) != 3) { return 3; };\n"
"\treturn 54;\n"
"};\n",
54, NULL },
/* #50 ken's k50b: SEQUENCED inserts at position edges (0, then
* mid), each with a len-reading value — every boxing must see
* its own pre-grow len (2 -> 200, then 3 -> 4), and the second
* insert's rotate must shift the first's result correctly:
* [10,20] -> [200,10,20] -> [200,10,4,20]. */
{ "tagged_seq_positions",
"package main;\n"
"type un = (void | size);\n"
"export fn main() i32 = {\n"
"\tlet xs: []un = [];\n"
"\tappend(xs, 10: size);\n"
"\tappend(xs, 20: size);\n"
"\tinsert(xs[0], ((xs.len: size) * 100));\n"
"\tmatch (xs[0]) {\n"
"\tcase let v: size => { if (v != 200) { return 1; }; };\n"
"\tcase void => { return 2; };\n"
"\t};\n"
"\tinsert(xs[2], ((xs.len: size) + 1));\n"
"\tmatch (xs[2]) {\n"
"\tcase let v: size => { if (v != 4) { return 3; }; };\n"
"\tcase void => { return 4; };\n"
"\t};\n"
"\tmatch (xs[1]) { case let v: size => { if (v != 10) { return 5; }; }; case void => { return 6; }; };\n"
"\tmatch (xs[3]) { case let v: size => { if (v != 20) { return 7; }; }; case void => { return 8; }; };\n"
"\tif (len(xs) != 4) { return 9; };\n"
"\treturn 57;\n"
"};\n",
57, NULL },
/* #50 ken's k50c: VOID-variant value — a tag-only box through
* the fresh @apptagscr (the zeroed scratch IS the payload),
* then a len-reading size insert over the mixed slice. */
{ "tagged_void_variant",
"package main;\n"
"type un = (void | size);\n"
"export fn main() i32 = {\n"
"\tlet xs: []un = [];\n"
"\tappend(xs, 5: size);\n"
"\tinsert(xs[0], void);\n"
"\tif (!(xs[0] is void)) { return 1; };\n"
"\tmatch (xs[1]) { case let v: size => { if (v != 5) { return 2; }; }; case void => { return 3; }; };\n"
"\tinsert(xs[1], ((xs.len: size)));\n"
"\tmatch (xs[1]) { case let v: size => { if (v != 2) { return 4; }; }; case void => { return 5; }; };\n"
"\tif (len(xs) != 3) { return 6; };\n"
"\treturn 58;\n"
"};\n",
58, NULL },
{ "reject_array",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: [3]i64 = [1, 2, 3];\n"
"\tinsert(t[0], 9);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL, "insert must operate on a slice" },
{ "reject_nonindex",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []u8 = [];\n"
"\tappend(xs, 5u8);\n"
"\tinsert(xs, 6u8);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL, "insert: operand must be an indexing expression" },
{ "reject_range",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []u8 = [];\n"
"\tappend(xs, 5u8);\n"
"\tappend(xs, 6u8);\n"
"\tinsert(xs[0:1], 7u8);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL, "insert: range place is invalid" },
{ "reject_arity1",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []u8 = [];\n"
"\tappend(xs, 5u8);\n"
"\tinsert(xs[0]);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL, "insert: takes exactly two arguments" },
{ "reject_arity3",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []u8 = [];\n"
"\tappend(xs, 5u8);\n"
"\tinsert(xs[0], 6u8, 7u8);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL, "insert: takes exactly two arguments" },
{ "reject_spread",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []u8 = [];\n"
"\tlet vs: []u8 = [];\n"
"\tappend(vs, 1u8);\n"
"\tinsert(xs[0], vs...);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL, "insert: spread form insert(xs[i], vs...) unimplemented (task #35)" },
};
/* errlog_has — the build-failure stderr must carry the row's expected
* diagnostic; any other failure (parse error, crash) is a vacuous
* reject and must not pass. */
static int
errlog_has(const char *path, const char *needle)
{
FILE *f = fopen(path, "rb");
if (!f) return 0;
char buf[8192];
size_t got = fread(buf, 1, sizeof buf - 1, f);
fclose(f);
buf[got] = '\0';
return strstr(buf, needle) != NULL;
}
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], errlog[128], rmcmd[160], cmd[1200];
snprintf(tmpdir, sizeof tmpdir, "/tmp/inse_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/inse_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/inse_%d_%d", tmpdir, getpid(), i);
snprintf(errlog, sizeof errlog, "%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 2>%s",
driver, outbin, src, errlog);
if (runwait(cmd) != 0) {
int rc = -1;
if (r->want != BUILD_FAIL) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
} else if (r->expect_err &&
!errlog_has(errlog, r->expect_err)) {
fprintf(stderr, "row[%s]: %s build failed without "
"expected diagnostic \"%s\"\n",
r->label, driver, r->expect_err);
rc = -3; /* failed, but for the wrong reason */
}
runwait(rmcmd);
return rc;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
* w6c_ww and diff. Both insert lowerings are written fresh, so this is
* the converged-by-construction gate: any drift in the rotate loop,
* the ins_l/ins_e label sequence, the @insscr frame slot, or the
* reused append body shows here. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/inse_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/inse_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/inse_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "insert_elem: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
int bad = rows[i].want == BUILD_FAIL
? (got != -1) : (got != rows[i].want);
if (bad) {
fprintf(stderr,
"insert_elem[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
if (rows[i].want == BUILD_FAIL)
continue;
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"insert_elem: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("insert_elem: %d fixtures passed\n", total);
return 0;
}

View File

@@ -1,507 +0,0 @@
/*
* 808_arrlit_overlong — an array literal with MORE elements than the
* declared [N] must be a loud checker reject in BOTH stages (#71).
*
* The bug: `let a: [2]u8 = [1u8, 2u8, 3u8];` was silently accepted by
* both checkers — type_assignable fails on the length mismatch, but
* arrlit_init_fits (#130 accept-if-fits) only range-checked the
* elements and never compared the literal's count against the declared
* length. cgen then stored every element at its natural offset, writing
* past the slot: stack-frame smash for locals (the `[1,2,3...]`-into-
* `[2]int` repeat form clobbered the saved BP outright), silent
* neighbour corruption for module-level DATA. The repeat-marker form
* was worse on cstage: clet's has_arr_repeat bypass skipped ALL checks
* for any `...` literal, so `[2]u8 = [999...]` also dodged the #130
* range check that wwstage already enforced.
*
* The fix (BOTH stages, one choke point each): the shared accept-if-
* fits helper (cmd/wcc/check.c arrlit_init_fits; selfhost/cmd/wcc/
* check.ww checkarrlitfits) pre-counts the literal's elements (skipping
* the `...` marker) and rejects count > N naming both counts. All four
* declaration contexts (local let / module let / def / struct-field
* literal) funnel through that helper. cstage clet's repeat bypass is
* narrowed to non-array targets so repeat literals into arrays run the
* same checks wwstage always ran.
*
* #105 (the W3 fold): a NAMED-ALIAS element type ([2]row, row=[2]int)
* dodged wwstage's nested recursion — the choke point's inner gate
* keyed on the raw elemtn kind (N_TNAME, not N_TARRAY), so the module
* static-DATA emitter silently TRUNCATED the overlong inner literal
* (exit-masked once #60 removed the read-side segv; cstage loud-
* rejects every spelling via its typed-literal assignability net).
* Fixed by chasing elemtn through resolvealias (transitive) before
* the recursion gate — alias spellings of any depth now take the same
* count + range checks as the direct shape, at all four declaration
* contexts. The alias_nested_* rows pin the flip; alias_exact_module
* is the 0/0 byte-id control (ken's m7c_global_ok).
*
* Out of scope, probed + filed separately: UNDER-long literals (no
* `...`) stay accepted in both stages (Hare rejects); `[0]`/`[_]`
* alen==0 sentinel conflation; wwstage assign/call-arg overlong
* acceptance (cstage already rejects those positions); OUTER alias-
* of-array overlong (`let g: arr = [5 elems]`, arr=[4]int) — the
* call-site N_TARRAY gates are alias-blind, ww still truncates
* (#105-sibling, filed); cstage accepting the out-of-range NESTED
* narrow element ww rejects (range net asymmetry, pre-existing on
* the direct spelling).
*
* accept rows | want
* ----------------------------------+------
* exact_local [2]int = [1,2] | runs, 0
* exact_module module-level [2] | runs, 0
* exact_def def [3] = [1,2,3] | runs, 0
* exact_field struct f=[2 elems] | runs, 0
* repeat_fill [4]int = [9...] | runs, 0
* repeat_partial [4]int = [1,2...] | runs, 0
* infer_len [_]int = [1,2,3] | runs, 0
* alias_exact_module [2]row exact | runs, 0 (#105 control)
*
* reject rows (build must FAIL on both drivers, and stderr must name
* both counts — per-stage expected substring: cstage catches the
* NESTED rows through its typed-literal assignability net instead of
* the choke-point diag, so those carry a different cstage substring)
* ----------------------------------------------
* overlong local / module / def(5-vs-3) / struct-field
* overlong repeat `[2]int = [1,2,3...]`
* overlong narrow `[2]u8 = [1u8,2u8,3u8]`
* nested module `[2][2]int = [[1,2,3],[4,5]]` (inner overlong —
* pre-fix wwstage emitted corrupted DATA: 1,2,4,5)
* nested field struct{f:[2][2]int} inner overlong
* alias nested module / 2lvl / def / field — the #105 flips: same
* inner-overlong shapes spelled through `type row = [2]int` (and
* `row2 = row`); pre-fix wwstage silently truncated (module DATA)
* or smashed frames (cstage was already loud on every row)
* alias nested local — ww pre-fix loud via the LATE cgen #270-1c
* fatal; pins the text move to the checker count diag
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.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; };
static const struct row rows[] = {
{ "exact_local",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: [2]int = [1, 2];\n"
"\treturn a[1]: i32 - 2;\n"
"};\n",
0 },
{ "exact_module",
"package main;\n"
"let g: [2]int = [4, 5];\n"
"export fn main() i32 = {\n"
"\treturn g[1]: i32 - 5;\n"
"};\n",
0 },
{ "exact_def",
"package main;\n"
"def TAB: [3]int = [1, 2, 3];\n"
"export fn main() i32 = {\n"
"\treturn TAB[2]: i32 - 3;\n"
"};\n",
0 },
{ "exact_field",
"package main;\n"
"type s = struct { f: [2]int, g: int };\n"
"export fn main() i32 = {\n"
"\tlet v: s = s{ f = [6, 7], g = 8 };\n"
"\treturn v.f[1]: i32 + v.g: i32 - 15;\n"
"};\n",
0 },
{ "repeat_fill",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: [4]int = [9...];\n"
"\treturn a[3]: i32 - 9;\n"
"};\n",
0 },
{ "repeat_partial",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: [4]int = [1, 2...];\n"
"\treturn a[3]: i32 - 2;\n"
"};\n",
0 },
{ "infer_len",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: [_]int = [10, 20, 30];\n"
"\treturn a.len: i32 - 3;\n"
"};\n",
0 },
/* #105 control (ken's m7c_global_ok): alias-elem module global,
* exact fit — must keep building 0/0 byte-id through the chased
* recursion gate. `N: int` cast spelling: the bare-int nested
* accept is cstage-rejected (#17), out of this row's scope. */
{ "alias_exact_module",
"package main;\n"
"type row = [2]int;\n"
"let g: [2]row = [[1: int, 2: int], [4: int, 5: int]];\n"
"export fn main() i32 = {\n"
"\tif (g[0][0] != 1) { return 1; };\n"
"\tif (g[1][1] != 5) { return 2; };\n"
"\treturn 0;\n"
"};\n",
0 },
};
/* Overlong literals — both stages must FAIL the build (the old accept
* stored every element at its natural offset: frame smash) AND the
* diagnostic must carry the expected substring (the choke-point reject
* names BOTH counts; the nested rows reach cstage's pre-existing
* assignability net instead, hence per-stage substrings). */
struct negrow {
const char *label;
const char *src;
const char *diag_c; /* expected stderr substring, cstage */
const char *diag_w; /* expected stderr substring, wwstage */
};
#define OVERLONG_3V2 "array literal has 3 elements but declared array holds 2"
static const struct negrow neg[] = {
{ "overlong_local",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: [2]int = [1, 2, 3];\n"
"\treturn 0;\n"
"};\n",
OVERLONG_3V2, OVERLONG_3V2 },
{ "overlong_module",
"package main;\n"
"let g: [2]int = [1, 2, 3];\n"
"export fn main() i32 = { return 0; };\n",
OVERLONG_3V2, OVERLONG_3V2 },
/* counts deliberately differ from the other rows so a diag that
* hardcodes 3/2 instead of naming the real counts trips here */
{ "overlong_def",
"package main;\n"
"def TAB: [3]int = [1, 2, 3, 4, 5];\n"
"export fn main() i32 = { return 0; };\n",
"array literal has 5 elements but declared array holds 3",
"array literal has 5 elements but declared array holds 3" },
{ "overlong_field",
"package main;\n"
"type s = struct { f: [2]int };\n"
"export fn main() i32 = {\n"
"\tlet v: s = s{ f = [1, 2, 3] };\n"
"\treturn 0;\n"
"};\n",
OVERLONG_3V2, OVERLONG_3V2 },
/* repeat marker with too many explicit elements — the worst
* pre-fix case (wrote at the saved BP) */
{ "overlong_repeat",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: [2]int = [1, 2, 3...];\n"
"\treturn 0;\n"
"};\n",
OVERLONG_3V2, OVERLONG_3V2 },
/* narrow element width (sub-8B store path) */
{ "overlong_narrow",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: [2]u8 = [1u8, 2u8, 3u8];\n"
"\treturn 0;\n"
"};\n",
OVERLONG_3V2, OVERLONG_3V2 },
/* nested inner overlong, module DATA — pre-fix wwstage silently
* emitted 1,2,4,5; cstage rejects via assignability, wwstage via
* the recursive choke point */
{ "nested_module",
"package main;\n"
"let g: [2][2]int = [[1, 2, 3], [4, 5]];\n"
"export fn main() i32 = { return 0; };\n",
"not assignable", OVERLONG_3V2 },
{ "nested_field",
"package main;\n"
"type s = struct { f: [2][2]int };\n"
"export fn main() i32 = {\n"
"\tlet v: s = s{ f = [[1, 2, 3], [4, 5]] };\n"
"\treturn 0;\n"
"};\n",
"not assignable", OVERLONG_3V2 },
/* #105 flips: alias-elem inner overlong, the four declaration
* contexts through the one (now chased) choke point. Pre-fix
* wwstage built all four silently — the module row emitted
* truncated DATA (1,2,4,5; ken's m7c_global, exit-masked since
* #60). cstage was already loud on each via assignability. */
{ "alias_nested_module",
"package main;\n"
"type row = [2]int;\n"
"let g: [2]row = [[1: int, 2: int, 3: int], [4: int, 5: int]];\n"
"export fn main() i32 = { return 0; };\n",
"not assignable", OVERLONG_3V2 },
/* 2-level alias — pins the transitive chase */
{ "alias_nested_2lvl",
"package main;\n"
"type row = [2]int;\n"
"type row2 = row;\n"
"let g: [2]row2 = [[1: int, 2: int, 3: int], [4: int, 5: int]];\n"
"export fn main() i32 = { return 0; };\n",
"not assignable", OVERLONG_3V2 },
{ "alias_nested_def",
"package main;\n"
"type row = [2]int;\n"
"def TAB: [2]row = [[1: int, 2: int, 3: int], [4: int, 5: int]];\n"
"export fn main() i32 = { return 0; };\n",
"not assignable", OVERLONG_3V2 },
/* local let — ww was ALREADY loud pre-fix, but via the cgen
* #270-1c aggregate-element fatal; the chase moves it to the
* checker count diag (ken's m7/m7b text move). This row pins the
* new text so a regression back to the late generic fatal reds. */
{ "alias_nested_local",
"package main;\n"
"type row = [2]int;\n"
"export fn main() i32 = {\n"
"\tlet g: [2]row = [[1: int, 2: int, 3: int], [4: int, 5: int]];\n"
"\treturn 0;\n"
"};\n",
"not assignable", OVERLONG_3V2 },
{ "alias_nested_field",
"package main;\n"
"type row = [2]int;\n"
"type s = struct { f: [2]row };\n"
"export fn main() i32 = {\n"
"\tlet v: s = s{ f = [[1: int, 2: int, 3: int], [4: int, 5: int]] };\n"
"\treturn 0;\n"
"};\n",
"not assignable", OVERLONG_3V2 },
};
static int
run_driver(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/aol_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/aol_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/aol_%d_%d", tmpdir, getpid(), i);
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 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* build_should_fail — an overlong literal must error on `driver` AND
* the diagnostic must contain `diag`; returns 0 when the build
* correctly FAILS with the expected text, non-zero otherwise. */
static int
build_should_fail(const char *dname, const char *driver,
const struct negrow *r, const char *diag, int i)
{
char s[128], tmpdir[64], errf[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/aoln_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(s, sizeof s, "%s/aoln_%d_%d.ww", tmpdir, getpid(), i);
snprintf(errf, sizeof errf, "%s/aoln_%d_%d.err", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/aoln_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(s, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>%s",
driver, outbin, s, errf);
int rc = runwait(cmd);
int bad = 0;
if (rc == 0) {
fprintf(stderr, "arrlit_overlong[%s][%s]: built ok, "
"expected a loud error\n", dname, r->label);
bad = 1;
} else {
char ebuf[4096];
size_t n = 0;
FILE *ef = fopen(errf, "rb");
if (ef) {
n = fread(ebuf, 1, sizeof ebuf - 1, ef);
fclose(ef);
}
ebuf[n] = '\0';
if (strstr(ebuf, diag) == NULL) {
fprintf(stderr, "arrlit_overlong[%s][%s]: rejected "
"but diagnostic lacks \"%s\"; got: %s\n",
dname, r->label, diag, ebuf);
bad = 1;
}
}
runwait(rmcmd);
return bad;
}
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/aol_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/aol_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/aol_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
drivers[] = {
{ "cstage", cdrv, 0 },
{ "wwstage", wdrv, 1 },
{ NULL, NULL, 0 },
};
int n = (int)(sizeof rows / sizeof rows[0]);
int nn = (int)(sizeof neg / sizeof neg[0]);
int total = 0, fail = 0;
for (int d = 0; drivers[d].name; d++) {
if (drivers[d].gated_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "arrlit_overlong: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"arrlit_overlong[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
for (int i = 0; i < nn; i++) {
total++;
const char *diag =
strcmp(drivers[d].name, "cstage") == 0
? neg[i].diag_c : neg[i].diag_w;
if (build_should_fail(drivers[d].name,
drivers[d].path, &neg[i], diag, 100 + i) != 0)
fail++;
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"arrlit_overlong: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("arrlit_overlong: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,604 +0,0 @@
/*
* 809_delete_range — cstage and wwstage agree, byte-for-byte and at
* runtime, that the range form `delete(xs[lo:hi])` removes elements
* [lo, hi) from a slice: the tail [hi..len) shifts down count = hi-lo
* strides, len -= count, cap unchanged; lo defaults 0, hi defaults
* len (so delete(xs[:]) clears the slice, storage retained). The
* range half of the delete() builtin — fold-5a prereq P2 for the
* regex.ha:333 consumer `delete(jump_idxs[group_level][..])` (harec
* ref/harec/src/check.c:1981-2027 accepts EXPR_SLICE alongside the
* 804-covered ACCESS_INDEX form).
*
* Lowering (BOTH stages, converged byte-identical by construction):
* push &hdr, lo and count = hi-lo, then an ascending word-copy loop
* moves element j+count onto element j until j+count >= len, then
* hdr.len -= count. The per-element move is 804's same-slice
* whole-stride word copy with a DYNAMIC src offset (count*esz via a
* src register) instead of the constant one-stride; the esz spread
* below (1/2/4/8/16/24) re-proves every copy-tail arm against the
* dynamic src. Base shapes: local slice ident, deref-of-local
* ptr-to-slice, and — new for the range arm — an INDEXED local slice
* base xs[g][lo:hi] (the regex fold-5a consumer shape). Bounds are
* implicit (no range check), matching the 804 single-element arm and
* the rest of cgen — so no out-of-bounds abort rows here; a
* violating range is as undefined as a violating index.
*
* row | shape | want
* -----------------+----------------------------------------+------
* full_range | [7,11,13], delete(xs[:]) -> len 0 | 7
* full_explicit | [7,11,13], delete(xs[0:3]) -> len 0 | 8
* full_twice | delete(xs[:]) twice — second is a | 9
* | no-op on the emptied slice (count 0) |
* head | [1,2,3], delete(xs[:2]) — lo default 0 | 13
* mid | [1..5], delete(xs[1:3]) | 148
* tail_open | [1,2,3], delete(xs[1:]) — hi default | 11
* | len, copy loop never entered |
* empty_range | [1,2], delete(xs[1:1]) — count 0, | 23
* | self-copy loop, len unchanged |
* end_boundary | [1,2], delete(xs[len:len]) — lo==hi== | 212
* | len, loop exits at entry, no deref |
* | past the end |
* empty_explicit | delete(xs[0:0]) on a never-appended | 31
* | empty slice (ptr nil) — count 0 and |
* | len 0, nothing dereferenced |
* single_via_range | delete(xs[1:2]) == delete(ys[1]) | 42
* | element-for-element |
* cap_preserved | manual {ptr,len=3,cap=8} header, | 80
* | delete(xs[:]); cap must still read 8 |
* u8_narrow | []u8 esz=1 — MOVB tail, dynamic src | 215
* u16_narrow | []u16 esz=2 — MOVW tail, dynamic src | 25
* i32_narrow | []i32 esz=4 — MOVL tail, dynamic src | 50
* struct16 | []pair esz=16 — 2 whole qwords, no | 216
* | sub-word tail (804's struct esz twin) |
* str_elem | []str esz=24 — 3-qword headers move | 235
* | whole across a 2-stride shift |
* regex_exact | [][]size, delete(jj[lvl][:]) — the | 10
* | EXACT regex.ha:333 consumer shape |
* | (indexed base, size-typed index) |
* deref_shape | delete((*p)[:]) behind *[]u8 — the | 17
* | 804 regex_shape twin |
* order_of_eval | delete(xs[lof():hif()]) — lo then hi, | 16
* | each CALLED exactly once (trace must |
* | read 12; harec evaluates the slicing |
* | operands left-to-right, once) |
* alias_visible | delete(xs[poke(&xs):]) — lo's write | 91
* | through &xs lands BEFORE the shift |
* | (loop reads ptr/len/elems fresh from |
* | the header, after operand eval) |
* reject_array | delete(t[0:2]) on [3]i64 — "delete | BUILD_FAIL
* | must operate on a slice" |
* reject_str | delete(s[:]) on str — slicing a str | BUILD_FAIL
* | yields str, not a slice |
*
* BUILD_FAIL rows also assert the diagnostic TEXT (stderr substring,
* both stages) — a build that fails for any other reason (parse error,
* crash) is a vacuous reject and fails the row.
*
* Every non-BUILD_FAIL row also asserts cstage/wwstage asm byte-id,
* which subsumes the frame canary (TEXT main,$N) and the rdl_l/rdl_e
* label-counter symmetry.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.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;
}
/* want == BUILD_FAIL: the row must FAIL to build on both stages AND
* emit expect_err on stderr (the checker reject set — message text
* included — is part of the contract: rule 7, never a silent
* acceptance; without the message check a row would pass vacuously on
* any unrelated build failure). */
#define BUILD_FAIL (-2147483647 - 1)
struct row {
const char *label;
const char *src;
int want;
const char *expect_err; /* BUILD_FAIL rows: required stderr substring */
};
static const struct row rows[] = {
{ "full_range",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 7);\n"
"\tappend(xs, 11);\n"
"\tappend(xs, 13);\n"
"\tdelete(xs[:]);\n"
"\treturn len(xs): i32 + 7;\n"
"};\n",
7, NULL },
{ "full_explicit",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 7);\n"
"\tappend(xs, 11);\n"
"\tappend(xs, 13);\n"
"\tdelete(xs[0:3]);\n"
"\treturn len(xs): i32 + 8;\n"
"};\n",
8, NULL },
/* Second full-range delete sees len == 0: count = len - 0 = 0,
* the copy loop exits at entry, len -= 0. Pins that an emptied
* slice survives a re-clear (the regex `)` arm clears
* jump_idxs[group_level] whether or not `|` populated it). */
{ "full_twice",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 7);\n"
"\tappend(xs, 11);\n"
"\tdelete(xs[:]);\n"
"\tdelete(xs[:]);\n"
"\treturn len(xs): i32 + 9;\n"
"};\n",
9, NULL },
{ "head",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 1);\n"
"\tappend(xs, 2);\n"
"\tappend(xs, 3);\n"
"\tdelete(xs[:2]);\n"
"\treturn xs[0]: i32 + len(xs)*10;\n"
"};\n",
13, NULL },
{ "mid",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 1);\n"
"\tappend(xs, 2);\n"
"\tappend(xs, 3);\n"
"\tappend(xs, 4);\n"
"\tappend(xs, 5);\n"
"\tdelete(xs[1:3]);\n"
"\treturn (xs[0]*100 + xs[1]*10 + xs[2]): i32 + len(xs);\n"
"};\n",
148, NULL },
{ "tail_open",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 1);\n"
"\tappend(xs, 2);\n"
"\tappend(xs, 3);\n"
"\tdelete(xs[1:]);\n"
"\treturn xs[0]: i32 + len(xs)*10;\n"
"};\n",
11, NULL },
{ "empty_range",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 1);\n"
"\tappend(xs, 2);\n"
"\tdelete(xs[1:1]);\n"
"\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n"
"};\n",
23, NULL },
/* lo == hi == len: the loop guard j+count >= len holds at entry
* with j = len, so nothing past the end is ever addressed. */
{ "end_boundary",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 1);\n"
"\tappend(xs, 2);\n"
"\tdelete(xs[len(xs):len(xs)]);\n"
"\treturn (xs[0]*10 + xs[1]): i32 + len(xs)*100;\n"
"};\n",
212, NULL },
/* Never-appended empty slice: ptr is nil. count 0 and len 0 mean
* the loop body (the only dereferencing code) never runs. */
{ "empty_explicit",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tdelete(xs[0:0]);\n"
"\treturn len(xs): i32 + 31;\n"
"};\n",
31, NULL },
{ "single_via_range",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 7);\n"
"\tappend(xs, 11);\n"
"\tappend(xs, 13);\n"
"\tdelete(xs[1:2]);\n"
"\tlet ys: []i64 = [];\n"
"\tappend(ys, 7);\n"
"\tappend(ys, 11);\n"
"\tappend(ys, 13);\n"
"\tdelete(ys[1]);\n"
"\tif (len(xs) != len(ys)) { return 99; };\n"
"\tif (xs[0] != ys[0]) { return 98; };\n"
"\tif (xs[1] != ys[1]) { return 97; };\n"
"\treturn 42;\n"
"};\n",
42, NULL },
{ "cap_preserved",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet hb: [8]u8; hb[0] = 4u8; hb[1] = 6u8; hb[2] = 9u8;\n"
"\tlet xs: []u8; xs.ptr = &hb[0]; xs.len = 3; xs.cap = 8;\n"
"\tdelete(xs[:]);\n"
"\treturn (xs.cap*10 + xs.len): i32;\n"
"};\n",
80, NULL },
{ "u8_narrow",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []u8 = [];\n"
"\tappend(xs, 1u8);\n"
"\tappend(xs, 2u8);\n"
"\tappend(xs, 3u8);\n"
"\tappend(xs, 4u8);\n"
"\tappend(xs, 5u8);\n"
"\tdelete(xs[1:4]);\n"
"\treturn (xs[0]*10u8 + xs[1]): i32 + len(xs)*100;\n"
"};\n",
215, NULL },
{ "u16_narrow",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []u16 = [];\n"
"\tappend(xs, 1u16);\n"
"\tappend(xs, 2u16);\n"
"\tappend(xs, 3u16);\n"
"\tappend(xs, 4u16);\n"
"\tdelete(xs[1:3]);\n"
"\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n"
"};\n",
25, NULL },
{ "i32_narrow",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i32 = [];\n"
"\tappend(xs, 10i32);\n"
"\tappend(xs, 20i32);\n"
"\tappend(xs, 30i32);\n"
"\tappend(xs, 40i32);\n"
"\tdelete(xs[2:4]);\n"
"\treturn xs[0] + xs[1] + len(xs)*10;\n"
"};\n",
50, NULL },
/* esz 16: exactly two whole qwords per element — the only spread
* point with multiple MOVQs and NO sub-word tail. */
{ "struct16",
"package main;\n"
"type pair = struct { a: i64, b: i64 };\n"
"export fn main() i32 = {\n"
"\tlet xs: []pair = [];\n"
"\tappend(xs, pair{ a = 1, b = 2 });\n"
"\tappend(xs, pair{ a = 3, b = 4 });\n"
"\tappend(xs, pair{ a = 5, b = 6 });\n"
"\tdelete(xs[1:2]);\n"
"\treturn (xs[0].a*10 + xs[1].b): i32 + len(xs)*100;\n"
"};\n",
216, NULL },
{ "str_elem",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []str = [];\n"
"\tlet a: str = \"abc\";\n"
"\tlet b: str = \"de\";\n"
"\tlet g: str = \"fghi\";\n"
"\tlet h: str = \"jklmn\";\n"
"\tappend(xs, a);\n"
"\tappend(xs, b);\n"
"\tappend(xs, g);\n"
"\tappend(xs, h);\n"
"\tdelete(xs[1:3]);\n"
"\treturn (xs[0].len*10 + xs[1].len): i32 + len(xs)*100;\n"
"};\n",
235, NULL },
/* The fold-5a consumer, shape-EXACT: indexed local-slice base,
* size-typed outer index, defaulted full range (regex.ha:333
* `delete(jump_idxs[group_level][..])`; ww spells `[..]` as
* `[:]`, D7). The header lives at jj.ptr + g*size([]size) —
* outer stride off the type table, never a literal. */
{ "regex_exact",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet jump_idxs: [][]size = [];\n"
"\tlet lvl0: []size = [];\n"
"\tlet a: size = 3;\n"
"\tlet b: size = 9;\n"
"\tappend(lvl0, a);\n"
"\tappend(lvl0, b);\n"
"\tappend(jump_idxs, lvl0);\n"
"\tlet group_level: size = 0;\n"
"\tdelete(jump_idxs[group_level][:]);\n"
"\treturn len(jump_idxs[0]): i32 + len(jump_idxs): i32 * 10;\n"
"};\n",
10, NULL },
{ "deref_shape",
"package main;\n"
"fn clear(p: *[]u8) void = {\n"
"\tdelete((*p)[:]);\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet xs: []u8 = [];\n"
"\tappend(xs, 5u8);\n"
"\tappend(xs, 6u8);\n"
"\tclear(&xs);\n"
"\treturn len(xs): i32 + 17;\n"
"};\n",
17, NULL },
/* lo and hi as CALLs: each side effect fires exactly once, lo
* before hi (trace 12, never 21/121/122) — pins that neither
* bound expression is re-evaluated by the count computation or
* the copy loop. */
{ "order_of_eval",
"package main;\n"
"let trace: i64 = 0;\n"
"fn lof() size = {\n"
"\ttrace = trace*10 + 1;\n"
"\treturn 1;\n"
"};\n"
"fn hif() size = {\n"
"\ttrace = trace*10 + 2;\n"
"\treturn 3;\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 1);\n"
"\tappend(xs, 2);\n"
"\tappend(xs, 3);\n"
"\tappend(xs, 4);\n"
"\tdelete(xs[lof():hif()]);\n"
"\tif (trace != 12) { return 90; };\n"
"\treturn (xs[0]*10 + xs[1]): i32 + len(xs);\n"
"};\n",
16, NULL },
/* lo mutates the slice it is deleting from: only the header
* ADDRESS is taken before operand eval; ptr/len/elements are read
* after, so poke's write to xs[0] survives into the result. */
{ "alias_visible",
"package main;\n"
"fn poke(p: *[]i64) size = {\n"
"\t(*p)[0] = 9;\n"
"\treturn 1;\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 1);\n"
"\tappend(xs, 2);\n"
"\tappend(xs, 3);\n"
"\tdelete(xs[poke(&xs):]);\n"
"\treturn xs[0]: i32 * 10 + len(xs);\n"
"};\n",
91, NULL },
{ "reject_array",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: [3]i64 = [1, 2, 3];\n"
"\tdelete(t[0:2]);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL, "delete must operate on a slice" },
{ "reject_str",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet s: str = \"abc\";\n"
"\tdelete(s[:]);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL, "delete must operate on a slice" },
};
/* errlog_has — the build-failure stderr must carry the row's expected
* diagnostic; any other failure (parse error, crash) is a vacuous
* reject and must not pass. */
static int
errlog_has(const char *path, const char *needle)
{
FILE *f = fopen(path, "rb");
if (!f) return 0;
char buf[8192];
size_t got = fread(buf, 1, sizeof buf - 1, f);
fclose(f);
buf[got] = '\0';
return strstr(buf, needle) != NULL;
}
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], errlog[128], rmcmd[160];
char cmd[1200];
snprintf(tmpdir, sizeof tmpdir, "/tmp/delr_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/delr_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/delr_%d_%d", tmpdir, getpid(), i);
snprintf(errlog, sizeof errlog, "%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 2>%s",
driver, outbin, src, errlog);
if (runwait(cmd) != 0) {
int rc = -1;
if (r->want != BUILD_FAIL) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
} else if (r->expect_err &&
!errlog_has(errlog, r->expect_err)) {
fprintf(stderr, "row[%s]: %s build failed without "
"expected diagnostic \"%s\"\n",
r->label, driver, r->expect_err);
rc = -3; /* failed, but for the wrong reason */
}
runwait(rmcmd);
return rc;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — compile the row's source standalone with w6c and
* w6c_ww and diff. Both range-delete lowerings are written fresh, so
* this is the converged-by-construction gate: any drift in the shift
* loop, the rdl_l/rdl_e label sequence, the dynamic-src copy, or the
* indexed-base header address shows here. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/delr_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/delr_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/delr_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "delete_range: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
int bad = rows[i].want == BUILD_FAIL
? (got != -1) : (got != rows[i].want);
if (bad) {
fprintf(stderr,
"delete_range[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
if (rows[i].want == BUILD_FAIL)
continue;
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"delete_range: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("delete_range: %d fixtures passed\n", total);
return 0;
}

View File

@@ -1,550 +0,0 @@
/*
* 809_idx_structlit_store — cstage and wwstage agree, byte-for-byte and
* at runtime, that a struct-LITERAL store into an INDEXED element place
* (`a[i] = pt{...}`, `cs[i] = capture{...}`, `(*ts)[i].caps[k] =
* capture{...}`) and into a DEREF place (`*p = pt{...}`) writes every
* field (task #20; ken's p13 array find + prober PG4 slice extension;
* gates regex fold-5a's run_thread groupstart capture store,
* regex.ha:643-651).
*
* Pre-fix BOTH stages compiled these byte-identically WRONG (gate-blind
* — only a runtime pin can hold this): the N_ASSIGN N_INDEX-lhs arm's
* aggregate branch (#270-1b) gated its rhs on ident/dot/deref shapes,
* so an N_STRUCTLIT rhs fell to the scalar store tail; cgexpr on a
* struct literal emits NOTHING (AX stays 0) and the tail stored ONE
* zero word at the element base. Net effect: every literal field
* silently dropped, the element's first 8 bytes zeroed — for a
* str-leading element (the regex capture shape) that nulls content.ptr
* and the next read of the str field SEGFAULTS. The N_UN(STAR) deref
* arm (`*p = pt{...}`) had the same fall-to-scalar tail — same class.
*
* The fix (BOTH stages, converged byte-identical): a struct-lit rhs
* aimed at an N_INDEX, N_UN(STAR), or indexed-base N_DOT place
* (`a[i].f = pt{...}`, reviewer-20 sibling — the a[i].f legacy arm's
* fldstoreop tail had the same silent drop) skips the legacy arms and
* routes through the F6 assign-resolver (cgplaceaddr) — the SAME C1.25
* aggregate arm that wires `(*ts)[i].field = capture{...}` N_DOT
* places: materialise the literal into a FRESH per-use @placescr slot
* (cg_structlit_fill_bp / cgstructlitfillbp: nested literals, str
* fields, TK_ELLIPSIS autofill), resolve the place address, word-copy
* esz bytes. Close-by-construction: every non-ident place kind (DOT /
* INDEX / UN-STAR) now funnels struct-lit stores through that single
* arm; ident places keep their enumerated fill paths. Class boundary
* for OTHER rhs kinds at these places: tuple-lit/str-lit into INDEX
* and DOT places work (pinned below); array-lit rhs at assignment is
* unwired for every place kind and now dies LOUD (task #32, build-fail
* rows below); tuple-lit through deref truncates (filed #31-E); CALL
* rhs into INDEX/UN-STAR places stores only RAX (filed #31-G).
*
* row | shape | want
* ------------------+-----------------------------------------+------
* arr_elem | a[1] = pt{3,4}; a[1].x*10+a[1].y | 34
* arr_neighbor | store a[1]; a[0]/a[2] field-set intact | 82
* arr_var_idx | a[geti()] = pt{...} (runtime index) | 34
* small_elem | 8B one-field elem (esz<=8 word-copy) | 52
* nested_lit | a[1] = outer{ in = inner{3,4}, t=5 } | 45
* partial_ellipsis | prefill 9,9; a[1] = pt{x=3, ...} → y=0 | 30
* alias_lit | type qt = pt; a[1] = qt{3,4} (chase | 34
* | peels the NAMED alias before TY_STRUCT) |
* all_default | prefill 9,9; a[1] = pt{} zeroes EVERY | 7
* | word (fill-loop floor) + 7 |
* tuple_elem | a[1] = (3,4); copy-out readback | 34
* dotidx_field | a[1].p = pt{3,4} (indexed-base DOT | 34
* | place, reviewer-20 sibling fix) |
* slice_elem_str | cs[1] = capture{...} 56B str-leading | 128
* fieldplace_slice | ts[0].caps[1] = capture{...} | 128
* derefspine_slice | (*tsp)[0].caps[1] = capture{...} | 128
* callee_capture | (*ts)[i].caps[k] = capture{...} in a | 197
* | callee w/ *[]thr param; str readback via |
* | strings.compare + len + both neighbors |
* | (the EXACT fold-5a consumer shape) |
* deref_lit | *p = pt{3,4} (N_UN place, same class) | 34
* global_arr | g[1] = pt{3,4}, g: [3]pt module let | 34
* arrlit_idx_loud | a[1] = [3,4] — LOUD build-fail (#32) | -1
* arrlit_deref_loud | *p = [3,4] — LOUD build-fail (#32) | -1
*
* global_arr is RUNTIME-ONLY (byteid=0): an uninitialised [N]struct
* value-global already diverges on master — wwstage emits a zero
* `DATAW main.g(SB)` record (under-sized: 24B for a 48B array),
* cstage emits none. Pre-existing, independent of this fix (task #11
* family: value-global data emission); the store/readback this row
* pins is symmetric and correct on both stages. The arrlit_* rows pin
* task #32's loud boundary (want -1 = driver build MUST fail; pre-#32
* both stages silently zeroed one word); flip them to runtime rows
* when #32 wires the fill.
*/
#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; int byteid; };
/* The 56B regex capture shape: str header leading + 4 size fields.
* Shared preamble for the slice rows; only the store site differs. */
#define CAPTURE_DEFS \
"package main;\n" \
"type capture = struct {\n" \
"\tcontent: str,\n" \
"\tstart: size,\n" \
"\tstart_bytesize: size,\n" \
"\tend: size,\n" \
"\tend_bytesize: size,\n" \
"};\n"
#define CAPTURE_SEED \
"\tlet cs: []capture = [];\n" \
"\tappend(cs, capture { content = \"a\", start = 1: size, " \
"start_bytesize = 1: size, end = 1: size, " \
"end_bytesize = 1: size });\n" \
"\tappend(cs, capture { content = \"b\", start = 2: size, " \
"start_bytesize = 2: size, end = 2: size, " \
"end_bytesize = 2: size });\n"
#define CAPTURE_STORE(place) \
"\t" place " = capture { content = \"grp\", start = 3: size, " \
"start_bytesize = 4: size, end = 60: size, " \
"end_bytesize = 61: size };\n"
static const struct row rows[] = {
{ "arr_elem",
"package main;\n"
"type pt = struct { x: u64, y: u64 };\n"
"export fn main() i32 = {\n"
"\tlet a: [3]pt;\n"
"\ta[1] = pt { x = 3u64, y = 4u64 };\n"
"\treturn (a[1].x * 10u64 + a[1].y): i32;\n"
"};\n",
34, 1 },
{ "arr_neighbor",
"package main;\n"
"type pt = struct { x: u64, y: u64 };\n"
"export fn main() i32 = {\n"
"\tlet a: [3]pt;\n"
"\ta[0].x = 7u64; a[2].y = 9u64;\n"
"\ta[1] = pt { x = 3u64, y = 4u64 };\n"
"\treturn (a[0].x * 10u64 + a[2].y + a[1].x): i32;\n"
"};\n",
82, 1 },
{ "arr_var_idx",
"package main;\n"
"type pt = struct { x: u64, y: u64 };\n"
"fn geti() size = { return 1: size; };\n"
"export fn main() i32 = {\n"
"\tlet a: [3]pt;\n"
"\ta[geti()] = pt { x = 3u64, y = 4u64 };\n"
"\treturn (a[1].x * 10u64 + a[1].y): i32;\n"
"};\n",
34, 1 },
{ "small_elem",
"package main;\n"
"type one = struct { v: u64 };\n"
"export fn main() i32 = {\n"
"\tlet a: [2]one;\n"
"\ta[0].v = 2u64;\n"
"\ta[1] = one { v = 5u64 };\n"
"\treturn (a[1].v * 10u64 + a[0].v): i32;\n"
"};\n",
52, 1 },
{ "nested_lit",
"package main;\n"
"type inner = struct { a: u64, b: u64 };\n"
"type outer = struct { in: inner, t: u64 };\n"
"export fn main() i32 = {\n"
"\tlet a: [2]outer;\n"
"\ta[1] = outer { in = inner { a = 3u64, b = 4u64 }, "
"t = 5u64 };\n"
"\treturn (a[1].in.b * 10u64 + a[1].t): i32;\n"
"};\n",
45, 1 },
{ "partial_ellipsis",
"package main;\n"
"type pt = struct { x: u64, y: u64 };\n"
"export fn main() i32 = {\n"
"\tlet a: [3]pt;\n"
"\ta[1].x = 9u64; a[1].y = 9u64;\n"
"\ta[1] = pt { x = 3u64, ... };\n"
"\treturn (a[1].x * 10u64 + a[1].y): i32;\n"
"};\n",
30, 1 },
{ "alias_lit",
"package main;\n"
"type pt = struct { x: u64, y: u64 };\n"
"type qt = pt;\n"
"export fn main() i32 = {\n"
"\tlet a: [3]pt;\n"
"\ta[1] = qt { x = 3u64, y = 4u64 };\n"
"\treturn (a[1].x * 10u64 + a[1].y): i32;\n"
"};\n",
34, 1 },
{ "all_default",
"package main;\n"
"type pt = struct { x: u64, y: u64 };\n"
"export fn main() i32 = {\n"
"\tlet a: [3]pt;\n"
"\ta[1].x = 9u64; a[1].y = 9u64;\n"
"\ta[1] = pt {};\n"
"\treturn (a[1].x * 10u64 + a[1].y + 7u64): i32;\n"
"};\n",
7, 1 },
{ "tuple_elem",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: [2](u64, u64);\n"
"\ta[1] = (3u64, 4u64);\n"
"\tlet t: (u64, u64) = a[1];\n"
"\treturn (t.0 * 10u64 + t.1): i32;\n"
"};\n",
34, 1 },
{ "dotidx_field",
"package main;\n"
"type pt = struct { x: u64, y: u64 };\n"
"type box = struct { p: pt, t: u64 };\n"
"export fn main() i32 = {\n"
"\tlet a: [2]box;\n"
"\ta[1].p.x = 9u64; a[1].p.y = 9u64;\n"
"\tif (a[1].p.x != 9u64 || a[1].p.y != 9u64) { return 250; };\n"
"\ta[1].p = pt { x = 3u64, y = 4u64 };\n"
"\treturn (a[1].p.x * 10u64 + a[1].p.y): i32;\n"
"};\n",
34, 1 },
{ "slice_elem_str",
CAPTURE_DEFS
"export fn main() i32 = {\n"
CAPTURE_SEED
CAPTURE_STORE("cs[1]")
"\tif (cs[0].start != 1) { return 250; };\n"
"\treturn (cs[1].start + cs[1].start_bytesize + cs[1].end + "
"cs[1].end_bytesize): i32;\n"
"};\n",
128, 1 },
{ "fieldplace_slice",
CAPTURE_DEFS
"type thr = struct { pc: size, caps: []capture };\n"
"export fn main() i32 = {\n"
CAPTURE_SEED
"\tlet ts: []thr = [];\n"
"\tappend(ts, thr { pc = 5: size, caps = cs });\n"
CAPTURE_STORE("ts[0].caps[1]")
"\tif (ts[0].caps[0].start != 1) { return 250; };\n"
"\treturn (ts[0].caps[1].start + ts[0].caps[1].start_bytesize + "
"ts[0].caps[1].end + ts[0].caps[1].end_bytesize): i32;\n"
"};\n",
128, 1 },
{ "derefspine_slice",
CAPTURE_DEFS
"type thr = struct { pc: size, caps: []capture };\n"
"export fn main() i32 = {\n"
CAPTURE_SEED
"\tlet ts: []thr = [];\n"
"\tappend(ts, thr { pc = 5: size, caps = cs });\n"
"\tlet tsp = &ts;\n"
CAPTURE_STORE("(*tsp)[0].caps[1]")
"\tif (ts[0].caps[0].start != 1) { return 250; };\n"
"\treturn (ts[0].caps[1].start + ts[0].caps[1].start_bytesize + "
"ts[0].caps[1].end + ts[0].caps[1].end_bytesize): i32;\n"
"};\n",
128, 1 },
/* The fold-5a consumer pin: regex.ha:643-651 verbatim shape —
* callee takes *[]thr, double-indexes through the deref spine,
* stores the 56B capture literal. Readback covers the str field
* (strings.compare + len — pre-fix this SEGFAULTED on the nulled
* content.ptr), both neighbor elements (56B store must not
* smear), and all four size fields. 3+4+60+61+3*23 = 197. */
{ "callee_capture",
"package main;\n"
"\n"
"import strings;\n"
"\n"
"type capture = struct {\n"
"\tcontent: str,\n"
"\tstart: size,\n"
"\tstart_bytesize: size,\n"
"\tend: size,\n"
"\tend_bytesize: size,\n"
"};\n"
"type thr = struct { pc: size, caps: []capture };\n"
"fn store(ts: *[]thr, i: size, idx: size) void = {\n"
"\t(*ts)[i].caps[idx] = capture {\n"
"\t\tcontent = \"grp\",\n"
"\t\tstart = 3: size,\n"
"\t\tstart_bytesize = 4: size,\n"
"\t\tend = 60: size,\n"
"\t\tend_bytesize = 61: size,\n"
"\t};\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet cs: []capture = [];\n"
"\tappend(cs, capture { content = \"a\", start = 1: size, "
"start_bytesize = 1: size, end = 1: size, "
"end_bytesize = 1: size });\n"
"\tappend(cs, capture { content = \"b\", start = 2: size, "
"start_bytesize = 2: size, end = 2: size, "
"end_bytesize = 2: size });\n"
"\tappend(cs, capture { content = \"c\", start = 9: size, "
"start_bytesize = 9: size, end = 9: size, "
"end_bytesize = 9: size });\n"
"\tlet ts: []thr = [];\n"
"\tappend(ts, thr { pc = 5: size, caps = cs });\n"
"\tstore(&ts, 0: size, 1: size);\n"
"\tif (strings.compare(ts[0].caps[1].content, \"grp\") != 0) "
"{ return 250; };\n"
"\tif (len(ts[0].caps[1].content) != 3) { return 249; };\n"
"\tif (ts[0].caps[0].end != 1 || ts[0].caps[2].start != 9) "
"{ return 248; };\n"
"\tif (strings.compare(ts[0].caps[2].content, \"c\") != 0) "
"{ return 247; };\n"
"\tlet acc = ts[0].caps[1].start + ts[0].caps[1].start_bytesize\n"
"\t\t+ ts[0].caps[1].end + ts[0].caps[1].end_bytesize\n"
"\t\t+ ts[0].caps[1].start * 23;\n"
"\treturn acc: i32;\n"
"};\n",
197, 1 },
{ "deref_lit",
"package main;\n"
"type pt = struct { x: u64, y: u64 };\n"
"export fn main() i32 = {\n"
"\tlet a: pt = pt { x = 1u64, y = 2u64 };\n"
"\tlet p: *pt = &a;\n"
"\t*p = pt { x = 3u64, y = 4u64 };\n"
"\treturn (a.x * 10u64 + a.y): i32;\n"
"};\n",
34, 1 },
{ "global_arr",
"package main;\n"
"type pt = struct { x: u64, y: u64 };\n"
"let g: [3]pt;\n"
"export fn main() i32 = {\n"
"\tg[1] = pt { x = 3u64, y = 4u64 };\n"
"\treturn (g[1].x * 10u64 + g[1].y): i32;\n"
"};\n",
34, 0 },
{ "arrlit_idx_loud",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: [2][2]u64;\n"
"\ta[1] = [3u64, 4u64];\n"
"\treturn (a[1][0] * 10u64 + a[1][1]): i32;\n"
"};\n",
-1, 0 },
{ "arrlit_deref_loud",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: [2]u64;\n"
"\tlet p: *[2]u64 = &a;\n"
"\t*p = [3u64, 4u64];\n"
"\treturn (a[0] * 10u64 + a[1]): i32;\n"
"};\n",
-1, 0 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/isls_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/isls_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/isls_%d_%d", tmpdir, getpid(), i);
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 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
/* want == -1 rows pin a LOUD build refusal (task #32). */
if (r->want != -1)
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — #103 sep layout: the driver flip moves build
* artifacts from next-to-source <stem>.s to a <stem>.sepwork/ scratch
* dir, so drive `ww build --sep -o <stem>` twice — once with cstage's w6c
* and once with the wwstage compiler (WW_W6C override; w6a/w6l stay
* cstage — only the .s is compared) — then concat each build's
* per-package .sepwork asm (sorted glob, identical package set:
* callee_capture pulls strings, so this is multi-package) and diff. The
* driver route — not a bare `w6c src.ww` — so importing rows resolve;
* --sep is flip-invariant (green pre- and post-flip). WW_PKGCACHE pins
* the package cache to the scratch dir so out/.pkgcache stays untouched.
* Pre-fix the asm was byte-identically WRONG (both stages dropped the
* fill), so these rows pin only that the converged fix stays symmetric;
* the runtime rows above carry correctness. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[96], tmpdir[64], outc[128], outw[128];
char cs[96], ws[96], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/isls_asm_%d_d_%d", getpid(), i);
snprintf(src, sizeof src, "%s/main809.ww", tmpdir);
snprintf(outc, sizeof outc, "%s/main809c", tmpdir);
snprintf(outw, sizeof outw, "%s/main809w", tmpdir);
snprintf(cs, sizeof cs, "%s/all_cs.s", tmpdir);
snprintf(ws, sizeof ws, "%s/all_ww.s", tmpdir);
mkdir(tmpdir, 0755);
FILE *f = fopen(src, "wb");
if (!f) {
snprintf(cmd, sizeof cmd, "rm -rf %s", tmpdir);
if (system(cmd)) {}
return -1;
}
fputs(r->src, f);
fclose(f);
int rc = 0;
snprintf(cmd, sizeof cmd,
"cd %s && WW_PKGCACHE=%s/pkgc_c %s/ww build --sep -o %s %s 2>/dev/null",
tmpdir, tmpdir, bin, outc, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: cstage sep build failed\n", r->label);
rc = -1;
}
if (rc == 0) {
snprintf(cmd, sizeof cmd,
"cd %s && WW_PKGCACHE=%s/pkgc_w WW_W6C=%s/w6c_ww "
"%s/ww build --sep -o %s %s 2>/dev/null",
tmpdir, tmpdir, bin, bin, outw, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: wwstage sep build failed\n",
r->label);
rc = -1;
}
}
if (rc == 0) {
snprintf(cmd, sizeof cmd, "cat %s.sepwork/*.s > %s 2>/dev/null",
outc, cs); if (system(cmd)) {}
snprintf(cmd, sizeof cmd, "cat %s.sepwork/*.s > %s 2>/dev/null",
outw, ws); if (system(cmd)) {}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
if (rc != 0)
fprintf(stderr,
"row[%s]: cstage vs wwstage asm differs\n",
r->label);
}
snprintf(cmd, sizeof cmd, "rm -rf %s", tmpdir);
if (system(cmd)) {}
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 cdrv[1024];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr,
"idx_structlit_store: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"idx_structlit_store[%s][%s]: "
"exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
if (!rows[i].byteid) continue;
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"idx_structlit_store: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("idx_structlit_store: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,362 +0,0 @@
/*
* 811_inferred_let_struct — cstage and wwstage agree, byte-for-byte and
* at runtime, on field access through an ANNOTATION-LESS struct-literal
* binding `let p = pt { ... };` (task #24, fold-5 prereq; the PG5
* wwstage pair).
*
* The bug (wwstage only): for an inferred binding the checker planted
* the struct decl's BODY node (N_TSTRUCT, exprtype N_STRUCTLIT returns
* ms.decl.lhs per #66) as the let's type — but every cgen local-arm
* dispatch (cgdot field read, cgassign tagged-field store, the alias
* peel) is N_TNAME-keyed. The body matched no arm, so `p.f` fell
* through to the module-qualified fallback and emitted the FIELD NAME
* as a global symbol (`MOVQ f(SB), AX` — the #211 name-leak family;
* loud at link, silent corruption if a same-named global exists), and
* a tagged-field assign fell to the assign-resolver's TY_TAGGED loud
* bound ("assign-resolver: tagged field not wired"). cstage was
* unaffected: check.c:1477 clet carries Sym.type (tinfo) and its
* emission is annotation-invariant.
*
* The fix (wwstage checker, check.ww checkletassign): normalize the
* inferred binding to the synthesized N_TNAME (mktname), making it
* indistinguishable from the annotated form downstream — every read/
* assign/is/as arm then takes the already-byte-id annotated route.
*
* Mutation coverage: on pre-fix wwstage every unannotated row LINK-
* FAILS (undefined reference to the field name), so run_driver's -1
* catches a regression outright; the annotated rows pin that the
* normalization didn't perturb the annotated path (byte-id rows assert
* cstage == wwstage asm for all rows).
*
* row | shape | want
* ---------------------+------------------------------------+------
* plain_read | let p = pt{..}; p.x*10 + p.y | 42
* tagged_read_is_as | (void|size) field: read + is/as | 6
* tagged_void_read | min=void; `is void` arm | 7
* tagged_assign | r.min = 2: size through inferred | 3
* tagged_str_field | (void|str): assign + as-str .len | 14
* boxed_prebound | field-wise build, box into union, | 103
* | match-extract, is/as (PG5h) |
* annotated_pin | same as tagged_assign but with an | 3
* | explicit `: rep` annotation |
* nested_read | plain nested s.f.g read (master | 42
* | ran right but cs!=ww asm) |
* ellipsis_fill | pt{ x = 4, ... } autofill (master | 40
* | wwstage link-failed) |
* paren_form | (pt{..}) — parser unwraps parens, | 42
* | same N_STRUCTLIT rhs (master |
* | wwstage link-failed) |
*/
#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; };
static const struct row rows[] = {
{ "plain_read",
"package main;\n"
"type pt = struct { x: size, y: size };\n"
"export fn main() i32 = {\n"
"\tlet p = pt { x = 4: size, y = 2: size };\n"
"\treturn (p.x * 10 + p.y): i32;\n"
"};\n",
42 },
{ "tagged_read_is_as",
"package main;\n"
"type rep = struct { id: size, min: (void | size) };\n"
"export fn main() i32 = {\n"
"\tlet r = rep { id = 1: size, min = 5: size };\n"
"\tlet acc = 0: size;\n"
"\tacc += r.id;\n"
"\tif (r.min is size) { acc += r.min as size; };\n"
"\treturn acc: i32;\n"
"};\n",
6 },
{ "tagged_void_read",
"package main;\n"
"type rep = struct { id: size, min: (void | size) };\n"
"export fn main() i32 = {\n"
"\tlet r = rep { id = 0: size, min = void };\n"
"\tlet acc = 0: size;\n"
"\tif (r.min is void) { acc += 7; };\n"
"\tif (r.min is size) { acc += r.min as size; };\n"
"\treturn acc: i32;\n"
"};\n",
7 },
{ "tagged_assign",
"package main;\n"
"type rep = struct { id: size, min: (void | size) };\n"
"export fn main() i32 = {\n"
"\tlet r = rep { id = 0: size, min = void };\n"
"\tr.id = 1;\n"
"\tr.min = 2: size;\n"
"\tlet acc = 0: size;\n"
"\tacc += r.id;\n"
"\tif (r.min is size) { acc += r.min as size; };\n"
"\treturn acc: i32;\n"
"};\n",
3 },
{ "tagged_str_field",
"package main;\n"
"type rep = struct { id: size, min: (void | size), name: (void | str) };\n"
"export fn main() i32 = {\n"
"\tlet b = rep { id = 0: size, min = void, name = void };\n"
"\tb.id = 2;\n"
"\tb.min = 9: size;\n"
"\tb.name = \"hey\";\n"
"\tlet acc = 0: size;\n"
"\tacc += b.id;\n"
"\tif (b.min is size) { acc += b.min as size; };\n"
"\tif (b.name is str) { acc += (b.name as str).len: size; };\n"
"\treturn acc: i32;\n"
"};\n",
14 },
/* PG5h: field-wise construction through the inferred binding,
* boxed into a tagged union, match-extracted, is/as off the
* binding — the fold-5b inst_repeat consumer shape. */
{ "boxed_prebound",
"package main;\n"
"type inst_lit = rune;\n"
"type rep3 = struct { id: size, min: (void | size), max: (void | size) };\n"
"type inst = (inst_lit | rep3);\n"
"export fn main() i32 = {\n"
"\tlet r = rep3 { id = 0: size, min = void, max = void };\n"
"\tr.id = 1;\n"
"\tr.min = 2: size;\n"
"\tr.max = 5: size;\n"
"\tlet pre = 0: size;\n"
"\tif (r.min is size) { pre += r.min as size; };\n"
"\tif (pre != 2) { return 200 + pre: i32; };\n"
"\tlet v: inst = r;\n"
"\tlet acc = 0: size;\n"
"\tmatch (v) {\n"
"\tcase let ir: rep3 => {\n"
"\t\tacc += ir.id;\n"
"\t\tif (ir.min is size) { acc += ir.min as size; };\n"
"\t\tif (ir.max is size && ir.max as size == 5) { acc += 100; };\n"
"\t};\n"
"\tcase let l: inst_lit => { acc += 1; };\n"
"\t};\n"
"\treturn acc: i32;\n"
"};\n",
103 },
/* Regression pin: the explicit annotation must stay on its prior
* (already byte-id) route — the normalization only fires for the
* inferred binding. */
{ "annotated_pin",
"package main;\n"
"type rep = struct { id: size, min: (void | size) };\n"
"export fn main() i32 = {\n"
"\tlet r: rep = rep { id = 0: size, min = void };\n"
"\tr.id = 1;\n"
"\tr.min = 2: size;\n"
"\tlet acc = 0: size;\n"
"\tacc += r.id;\n"
"\tif (r.min is size) { acc += r.min as size; };\n"
"\treturn acc: i32;\n"
"};\n",
3 },
/* Plain nested s.f.g through the inferred binding: on master
* wwstage this RAN correctly but emitted different asm (the body-
* node planting took a divergent cgdot route) — pins byte-id. */
{ "nested_read",
"package main;\n"
"type in_ = struct { g: size };\n"
"type out_ = struct { f: in_ };\n"
"export fn main() i32 = {\n"
"\tlet s = out_ { f = in_ { g = 6: size } };\n"
"\treturn (s.f.g * 7): i32;\n"
"};\n",
42 },
/* Trailing-`...` autofill (parse/expr.ww TK_ELLIPSIS, stash on
* s.op): same N_STRUCTLIT rhs kind, so the normalization must
* cover it — master wwstage link-failed this form too. */
{ "ellipsis_fill",
"package main;\n"
"type pt = struct { x: size, y: size };\n"
"export fn main() i32 = {\n"
"\tlet p = pt { x = 4: size, ... };\n"
"\treturn (p.x * 10 + p.y): i32;\n"
"};\n",
40 },
/* Parenthesized literal: the parser unwraps parens (no N_PAREN
* node), so the rhs is the same bare N_STRUCTLIT — pins that the
* form-coverage claim holds at the parse layer. */
{ "paren_form",
"package main;\n"
"type pt = struct { x: size, y: size };\n"
"export fn main() i32 = {\n"
"\tlet p = (pt { x = 4: size, y = 2: size });\n"
"\treturn (p.x * 10 + p.y): i32;\n"
"};\n",
42 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/ils_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/ils_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/ils_%d_%d", tmpdir, getpid(), i);
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 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
* w6c_ww and diff. Pre-fix the unannotated rows emitted `MOVQ
* <field>(SB), AX` on wwstage only, so the diff was non-empty (and
* the wwstage link failed). */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/ils_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/ils_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/ils_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "inferred_let_struct: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"inferred_let_struct[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"inferred_let_struct: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("inferred_let_struct: all %d fixtures passed\n", total);
return 0;
}

View File

@@ -1,493 +0,0 @@
/*
* 812_agg_assign_width — cstage and wwstage agree, byte-for-byte and
* at runtime, that an aggregate ASSIGN copies the WHOLE value (task
* #49, Family A of ken's silent-set triage, /tmp/ken_silent_triage.md).
*
* The bug: plain whole-aggregate reassignment `b = a` fell to the
* N_ASSIGN scalar tail in BOTH stages and copied ONE MOVQ — word 0 of
* a 16/24/48B struct (ken f49_min cs.s asm proof; fA_16b shows even a
* 16B {size,size} fails). Same class at three more positions: a
* struct-lit FIELD initialised from an ident source (`outer{.., r=r}`,
* ken f38b / x5f-h) word0-copied inside cg_structlit_fill; a deref
* place `*p = s` truncated to 8 bytes (#31-A); a module-let global
* `g = a` / `g = pt{...}` stored one word (scalar `MOVQ AX, g(SB)`).
* Tagged/match context was INCIDENTAL — the hole is tagged-independent
* and gate-blind (byte-identical wrongness), latent only because lib
* style is let-init (`let b = a` runs the #265/#268 full-width copy —
* the working sibling these fixes mirror).
*
* The fix (BOTH stages, converged byte-identical): ONE place-resolved
* mem-to-mem copy funnel — cg_aggcopy/aggcopy, extracted verbatim from
* the C1.25 assign-resolver tail — fed by aggarg_srcaddr (source addr →
* SI) and a LEAQ of the destination (BP slot or g(SB) symbol → BX),
* wired at: the N_ASSIGN ident-aggregate arm (local + global), the
* deref-place divert into the existing resolver aggregate arm, and the
* structlit-fill aggregate-field arm. Non-addressable aggregate rhs
* shapes die LOUD (rule 7) instead of silently truncating.
*
* MUTATION: every row below fails at master 7545bf7 (probed 2026-06-05,
* ken's matrix f49_min/f49b/f49c/fA_16b/f38b + impl-A probes pA_*: all
* non-zero exits, byte-id both stages — the class is gate-blind, so
* runtime readback is the only oracle).
*
* row | shape | want
* ---------------------+-------------------------------------+------
* assign_16b | {size,size} b = a, x*10+y readback | 39
* assign_24b_plain | {size,size,size} b = a, sum | 6
* assign_48b | 6-field b = a, sum | 21
* assign_odd_12b | {u32,u32,u32} 12B maxalign-4 (gA2) | 0
* assign_match_bind | dst = s from a match binding over | 0
* | (st|void), st has a tagged member |
* | (ken f49_min — the ORIGINAL #49 |
* | context, regression pin) |
* lit_field_from_ident | outer{tag, r = r} field fill (f38b) | 21
* assign_str_field | {id, name:str} b = a, ladder | 0
* assign_nested | {tag, inner{x,y}} b = a, sum | 6
* arrelem_roundtrip | a[1] = b (kin, pre-wired #270-1b) | 15
* | then cc = a[1] (the new read side) |
* assign_deref | *p = s, 24B struct (#31-A fold) w/ | 27
* | guards either side of the pointee |
* assign_global | g = a then b = g (module-let dst | 36
* | and src, both new arms) |
* assign_dot_source | b = o.i (N_DOT source via | 6
* | aggarg_srcaddr) |
* assign_tuple | (size,size) u = t slot copy | 13
* global_structlit | g = pt{...} (DST_GLOBAL fill wire) | 18
* alias_named_assign | type row = st; b = a (ken R1/gA3b — | 0
* | full alias chase, field-wise init) |
* alias_twolevel_assign| ali -> base -> struct chain (gA6) | 0
* neighbor_guard | b = a with live guards either side | 0
* | of b — over-copy smashes them |
*/
#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; };
static const struct row rows[] = {
{ "assign_16b",
"package main;\n"
"type p2 = struct { x: size, y: size };\n"
"export fn main() i32 = {\n"
"\tlet a: p2 = p2 { x = 3: size, y = 9: size };\n"
"\tlet b: p2 = p2 { x = 0: size, y = 0: size };\n"
"\tb = a;\n"
"\treturn (b.x * 10 + b.y): i32;\n"
"};\n",
39 },
{ "assign_24b_plain",
"package main;\n"
"type st = struct { id: size, x: size, y: size };\n"
"export fn main() i32 = {\n"
"\tlet a: st = st { id = 1: size, x = 2: size, y = 3: size };\n"
"\tlet b: st = st { id = 0: size, x = 0: size, y = 0: size };\n"
"\tb = a;\n"
"\treturn (b.id + b.x + b.y): i32;\n"
"};\n",
6 },
{ "assign_48b",
"package main;\n"
"type big = struct { a: size, b: size, c: size, d: size, e: size, f: size };\n"
"export fn main() i32 = {\n"
"\tlet x: big = big { a = 1: size, b = 2: size, c = 3: size, d = 4: size, e = 5: size, f = 6: size };\n"
"\tlet y: big = big { a = 0: size, b = 0: size, c = 0: size, d = 0: size, e = 0: size, f = 0: size };\n"
"\ty = x;\n"
"\treturn (y.a + y.b + y.c + y.d + y.e + y.f): i32;\n"
"};\n",
21 },
/* ken gA2 — odd size: 12B {u32,u32,u32}, maxalign 4. Pins the
* MOVQ-run + MOVL tail of the funnel (the fi.fsz slot-padded
* skew worry) — NOT a multiple of 8. Pre-fix at master: word0
* copies a+b in one MOVQ, c lost → exit 3 (probed both stages). */
{ "assign_odd_12b",
"package main;\n"
"type tri = struct { a: u32, b: u32, c: u32 };\n"
"export fn main() i32 = {\n"
"\tlet x: tri = tri { a = 7: u32, b = 8: u32, c = 9: u32 };\n"
"\tlet y: tri = tri { a = 0: u32, b = 0: u32, c = 0: u32 };\n"
"\ty = x;\n"
"\tif (y.a != 7) { return 1; };\n"
"\tif (y.b != 8) { return 2; };\n"
"\tif (y.c != 9) { return 3; };\n"
"\treturn 0;\n"
"};\n",
0 },
/* ken f49_min verbatim — the ORIGINAL #49 filing context:
* assign from a match binding, struct with a tagged member.
* Pre-fix exit 4 (dst.m's tag word never copied). */
{ "assign_match_bind",
"package main;\n"
"type st = struct { id: size, m: (void | size) };\n"
"type un = (st | void);\n"
"export fn main() i32 = {\n"
"\tlet src: un = st { id = 6: size, m = 8: size };\n"
"\tlet dst: st = st { id = 0: size, m = void };\n"
"\tmatch (src) {\n"
"\tcase let s: st => {\n"
"\t\tif (!(s.m is size)) { return 1; };\n"
"\t\tdst = s;\n"
"\t};\n"
"\tcase void => { return 2; };\n"
"\t};\n"
"\tif (dst.id != 6) { return 3; };\n"
"\tif (!(dst.m is size)) { return 4; };\n"
"\tif (dst.m as size != 8) { return 5; };\n"
"\treturn 0;\n"
"};\n",
0 },
/* ken f38b shape — struct-lit FIELD from an ident source. Pre-fix
* exit: o.r.id copied (word0), x/y zero → 6. */
{ "lit_field_from_ident",
"package main;\n"
"type rep = struct { id: size, x: size, y: size };\n"
"type outer = struct { tag: size, r: rep };\n"
"export fn main() i32 = {\n"
"\tlet r: rep = rep { id = 6: size, x = 7: size, y = 8: size };\n"
"\tlet o: outer = outer { tag = 4: size, r = r };\n"
"\tif (o.tag != 4) { return 99; };\n"
"\treturn (o.r.id + o.r.x + o.r.y): i32;\n"
"};\n",
21 },
/* 3-word str header inside the copied struct: the header words
* past .ptr are exactly what a word0 copy drops. Ladder form —
* `.len` in mixed arithmetic trips the PRE-EXISTING task-#26
* typing divergence (cstage rejects), which is not this class. */
{ "assign_str_field",
"package main;\n"
"type named = struct { id: size, name: str };\n"
"export fn main() i32 = {\n"
"\tlet a: named = named { id = 7: size, name = \"hello\" };\n"
"\tlet b: named = named { id = 0: size, name = \"\" };\n"
"\tb = a;\n"
"\tif (b.id != 7) { return 1; };\n"
"\tif (b.name.len != 5) { return 2; };\n"
"\treturn 0;\n"
"};\n",
0 },
{ "assign_nested",
"package main;\n"
"type inner = struct { x: size, y: size };\n"
"type outer = struct { tag: size, i: inner };\n"
"export fn main() i32 = {\n"
"\tlet a: outer = outer { tag = 1: size, i = inner { x = 2: size, y = 3: size } };\n"
"\tlet b: outer = outer { tag = 0: size, i = inner { x = 0: size, y = 0: size } };\n"
"\tb = a;\n"
"\treturn (b.tag + b.i.x + b.i.y): i32;\n"
"};\n",
6 },
/* a[i] = b was already wired (#270-1b INDEX-place word-copy) —
* this row pins the KIN pair: the pre-existing store plus the
* NEW ident-from-index read (`cc = a[1]` word0-copied pre-fix). */
{ "arrelem_roundtrip",
"package main;\n"
"type pt = struct { x: size, y: size, z: size };\n"
"export fn main() i32 = {\n"
"\tlet a: [2]pt = [pt { x = 0: size, y = 0: size, z = 0: size }, pt { x = 0: size, y = 0: size, z = 0: size }];\n"
"\tlet b: pt = pt { x = 4: size, y = 5: size, z = 6: size };\n"
"\ta[1] = b;\n"
"\tif (a[0].x != 0) { return 99; };\n"
"\tlet cc: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n"
"\tcc = a[1];\n"
"\treturn (cc.x + cc.y + cc.z): i32;\n"
"};\n",
15 },
/* #31-A fold: deref place from an addressable aggregate ident —
* pre-fix stored 8 bytes (probe pA_deref exit 2). Guards either
* side of the pointee slot per ken gA4: an over-wide copy through
* the resolved place smashes one. */
{ "assign_deref",
"package main;\n"
"type pt = struct { x: size, y: size, z: size };\n"
"export fn main() i32 = {\n"
"\tlet guard1: size = 111: size;\n"
"\tlet d: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n"
"\tlet guard2: size = 222: size;\n"
"\tlet s: pt = pt { x = 8: size, y = 9: size, z = 10: size };\n"
"\tlet p: *pt = &d;\n"
"\t*p = s;\n"
"\tif (guard1 != 111) { return 98; };\n"
"\tif (guard2 != 222) { return 97; };\n"
"\treturn (d.x + d.y + d.z): i32;\n"
"};\n",
27 },
/* Module-let global as BOTH destination (`g = a`, LEAQ g(SB)
* dest) and source (`b = g`, aggarg_srcaddr global arm). */
{ "assign_global",
"package main;\n"
"type pt = struct { x: size, y: size, z: size };\n"
"let g: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n"
"export fn main() i32 = {\n"
"\tlet a: pt = pt { x = 11: size, y = 12: size, z = 13: size };\n"
"\tg = a;\n"
"\tif (g.z != 13) { return 99; };\n"
"\tlet b: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n"
"\tb = g;\n"
"\treturn (b.x + b.y + b.z): i32;\n"
"};\n",
36 },
{ "assign_dot_source",
"package main;\n"
"type inner = struct { x: size, y: size, z: size };\n"
"type outer = struct { tag: size, i: inner };\n"
"export fn main() i32 = {\n"
"\tlet o: outer = outer { tag = 1: size, i = inner { x = 2: size, y = 3: size, z = 4: size } };\n"
"\tlet b: inner = inner { x = 0: size, y = 0: size, z = 0: size };\n"
"\tb = o.i;\n"
"\treturn (b.x + b.z): i32;\n"
"};\n",
6 },
/* Tuple ident reassign rides the same funnel (8B/elem slots). */
{ "assign_tuple",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: (size, size) = (4: size, 9: size);\n"
"\tlet u: (size, size) = (0: size, 0: size);\n"
"\tu = t;\n"
"\treturn (u.0 + u.1): i32;\n"
"};\n",
13 },
/* Global structlit reassign — newly wired through the DST_GLOBAL
* fill (pre-fix: one scalar MOVQ of AX≈0 to g(SB)). */
{ "global_structlit",
"package main;\n"
"type pt = struct { x: size, y: size, z: size };\n"
"let g: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n"
"export fn main() i32 = {\n"
"\tg = pt { x = 5: size, y = 6: size, z = 7: size };\n"
"\treturn (g.x + g.y + g.z): i32;\n"
"};\n",
18 },
/* ken R1 (gA3b): ALIAS-named aggregate — the funnel dispatch must
* FULL-chase the alias (type_chase_named / chased tinfo); a
* single peel left cstage at TY_NAMED, fell to the scalar tail
* and word0-copied while wwstage copied full-width (live cs≠ww).
* FIELD-WISE init: the struct-LIT spelling can't pin the ww side
* (it louds earlier at the pre-existing task-#7 aggregate-let
* bound — the #5 alias-arc's hole, not this funnel's). */
{ "alias_named_assign",
"package main;\n"
"type st = struct { a: size, b: size };\n"
"type row = st;\n"
"export fn main() i32 = {\n"
"\tlet a: row;\n"
"\ta.a = 4; a.b = 9;\n"
"\tlet b: row;\n"
"\tb.a = 0; b.b = 0;\n"
"\tb = a;\n"
"\tif (b.a != 4) { return 1; };\n"
"\tif (b.b != 9) { return 2; };\n"
"\treturn 0;\n"
"};\n",
0 },
/* ken gA6: TWO-level alias chain (`ali -> base -> struct`) — pins
* that the dispatch is a full CHASE, not a deeper fixed peel. */
{ "alias_twolevel_assign",
"package main;\n"
"type ali = base;\n"
"type base = struct { a: size, b: size, c: size };\n"
"export fn main() i32 = {\n"
"\tlet a: ali;\n"
"\ta.a = 4; a.b = 9; a.c = 13;\n"
"\tlet b: ali;\n"
"\tb.a = 0; b.b = 0; b.c = 0;\n"
"\tb = a;\n"
"\tif (b.a != 4) { return 1; };\n"
"\tif (b.b != 9) { return 2; };\n"
"\tif (b.c != 13) { return 3; };\n"
"\treturn 0;\n"
"};\n",
0 },
/* Neighbor guards on both sides of the destination slot: a copy
* WIDER than size(T) (the inverse regression) smashes one. */
{ "neighbor_guard",
"package main;\n"
"type pt = struct { x: size, y: size, z: size };\n"
"export fn main() i32 = {\n"
"\tlet guard1: size = 111: size;\n"
"\tlet b: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n"
"\tlet guard2: size = 222: size;\n"
"\tlet a: pt = pt { x = 1: size, y = 2: size, z = 3: size };\n"
"\tb = a;\n"
"\tif (guard1 != 111) { return 1; };\n"
"\tif (guard2 != 222) { return 2; };\n"
"\tif (b.x + b.y + b.z != 6) { return 3; };\n"
"\treturn 0;\n"
"};\n",
0 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/aggas_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/aggas_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/aggas_%d_%d", tmpdir, getpid(), i);
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 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
* w6c_ww and diff. The class is gate-blind (both stages were wrong
* identically), so byte-id here pins that the CONVERGED fix stays
* converged. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/aggas_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/aggas_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/aggas_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "agg_assign_width: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"agg_assign_width[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"agg_assign_width: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("agg_assign_width: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,366 +0,0 @@
/*
* 814_def_arr_infer_len — cstage and wwstage agree, byte-for-byte and at
* runtime, that a `def NAME: [_]T = [...]` array infers its length from
* the initialiser's element count — the `def` twin of the `let` path
* pinned by 684 (task #11 / #5; #7 wired only the let decl path).
*
* The bug (BOTH stages, byte-id identical): the `[_]` infer sentinel
* (alen=0 / nil length-child) was stamped on the `let` decl path but NOT
* on the `def` decl path. A `def [_]int = [10,20,30]` stayed sized 0,
* emitted no/short DATA, and indexed reads (`TAB[1]`) / `.len` returned
* garbage with no diagnostic (rule-7 silent miscompile).
*
* The fix (CHECKER ONLY — cgen already keys stride/length/DATA off the
* stamped length, so `def [3]int` works today):
* - cstage cmd/wcc/check.c N_DEF pass-2: mirror the module N_LET path —
* if d->type is `[_]T` (TY_ARRAY, alen==0), chase the rhs type,
* rebuild type_array(.., iu->alen), and re-point BOTH d->type AND the
* installed SK_DEF Sym (an indexed read resolves the def through its
* Sym, so the Sym repoint is required).
* - wwstage selfhost/cmd/wcc/check.ww N_DEF arm: call inferarraylen(c,d)
* BEFORE resolvewalk stamps d.lhs's tinfo (the N_TARRAY AST is the
* SSoT; no Sym repoint needed). A `def [_]T` whose init is not an
* array literal can't infer → LOUD error, never a silent length-0.
*
* `def` is MODULE-SCOPE ONLY in both stages, so there is exactly one def
* site per stage and every element type rides the single inference arm
* (it is gated only on TY_ARRAY+alen==0, agnostic to the element type).
*
* SCOPE (corrected at impl-probe — the inference is complete, but two
* adjacent shapes hit SEPARATE PRE-EXISTING cgen gaps that fail the SAME
* way on an explicit-length `def [N]T`, so they are NOT #11 and are
* excluded here; each is filed as its own task):
* - `def`-array `.len`/`.ptr` on wwstage falls to the SB fallback and
* emits `MOVQ len(SB)` (w6l: undefined reference). cstage handles it
* (so cstage vs wwstage asm also DIVERGES) — the wwstage cgdot
* def-array arm is missing, the def twin of #7's let-global cgdot
* fix. Every `.len` row is dropped for this reason.
* - `def [_]str` / `def [N]str` emit no DATA symbol (w6l: undefined
* reference, BOTH stages) — the #270 / str-slice-array-element DATA
* lineage. Every str row is dropped for this reason.
* The pin therefore exercises INDEX reads across int / u8 / multi-dim,
* which is exactly the shape #11 corrupts and is mutation-sensitive
* (a collapse-to-0 def lays no DATA row, so the indexed read strikes
* garbage / segv).
*
* The NEG row (`def [_]int = 5`, non-array init) proves the loud-error
* arm fires (rule-7: never a silent zero-length array).
*
* row | shape | want
* --------------+--------------------------------------------+-----
* def_int_i1 | def [_]int=[10,20,30], TAB[1] (ken oracle)| 20
* def_int_last | def [_]int=[10,20,30], TAB[2] (full len) | 30
* def_one_elem | def [_]int=[7], A[0] (1-elem edge) | 7
* def_u8_first | def [_]u8=[1..5], B[0] (narrow stride) | 1
* def_u8_last | def [_]u8=[1..5], B[4] | 5
* def_2d_first | def [_][2]int=[[1,2],[3,4],[5,6]], D[0][0] | 1
* def_2d_last | def [_][2]int=[..], D[2][1] | 6
* NEG def_noarr | def [_]int = 5; (non-array init) | BUILD FAIL
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.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; };
static const struct row rows[] = {
/* INDEX reads only — the shape #11 corrupts. Reading the LAST
* element of an N-element def proves the inference stamped the full
* length: a collapse-to-0 lays no DATA row at that offset, so the
* read strikes garbage / segv. This is mutation-sensitive without
* `.len` (whose def-array codegen is a SEPARATE pre-existing gap —
* see the header). */
{ "def_int_i1",
"package main;\n"
"def TAB: [_]int = [10, 20, 30];\n"
"export fn main() i32 = {\n"
"\treturn TAB[1]: i32;\n"
"};\n",
20 },
{ "def_int_last",
"package main;\n"
"def TAB: [_]int = [10, 20, 30];\n"
"export fn main() i32 = {\n"
"\treturn TAB[2]: i32;\n"
"};\n",
30 },
/* 1-element edge — the minimal non-empty count. */
{ "def_one_elem",
"package main;\n"
"def A: [_]int = [7];\n"
"export fn main() i32 = {\n"
"\treturn A[0]: i32;\n"
"};\n",
7 },
{ "def_u8_first",
"package main;\n"
"def B: [_]u8 = [1u8, 2u8, 3u8, 4u8, 5u8];\n"
"export fn main() i32 = {\n"
"\treturn B[0]: i32;\n"
"};\n",
1 },
/* narrow (1B) stride — the last element of 5 proves both the stride
* and the inferred length. */
{ "def_u8_last",
"package main;\n"
"def B: [_]u8 = [1u8, 2u8, 3u8, 4u8, 5u8];\n"
"export fn main() i32 = {\n"
"\treturn B[4]: i32;\n"
"};\n",
5 },
/* multi-dim: outer `[_]` infers 3 from the row count, element type
* is the explicit `[2]int`. The def static-init DATA path lays the
* full 3x2 aggregate (the `let` runtime-store path for this shape is
* separately #270-1c-blocked, so this is def-only today). */
{ "def_2d_first",
"package main;\n"
"def D: [_][2]int = [[1, 2], [3, 4], [5, 6]];\n"
"export fn main() i32 = {\n"
"\treturn D[0][0]: i32;\n"
"};\n",
1 },
{ "def_2d_last",
"package main;\n"
"def D: [_][2]int = [[1, 2], [3, 4], [5, 6]];\n"
"export fn main() i32 = {\n"
"\treturn D[2][1]: i32;\n"
"};\n",
6 },
};
/* `def [_]T` whose initialiser is not an array literal can't infer its
* length — both stages must FAIL the build (loud diagnostic, not a silent
* zero-length array). `def` requires an `= value` init (parser), so the
* no-init case can't reach the checker; the non-array init is the only
* negative shape. */
static const char *neg[] = {
/* non-array initialiser */
"package main;\n"
"def TAB: [_]int = 5;\n"
"export fn main() i32 = { return 0; };\n",
};
/* Per-neg expected diagnostic body (parallel to neg[]); shared by both
* stages (cstage prepends file:line:col, wwstage does not — #20). */
static const char *neg_diag[] = {
"[_]T needs an array-literal initialiser",
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/dail_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/dail_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/dail_%d_%d", tmpdir, getpid(), i);
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 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
static int
filehas(const char *path, const char *needle)
{
char buf[8192];
FILE *f = fopen(path, "rb");
if (!f) return 0;
size_t n = fread(buf, 1, sizeof buf - 1, f);
fclose(f);
buf[n] = '\0';
return strstr(buf, needle) != NULL;
}
/* build_should_fail — a `def [_]T` that can't infer must error on
* `driver`; returns 0 when the build FAILS *and* emits `diag`. A crash
* (segfault) wraps as a nonzero "w6c failed" with no diagnostic, so the
* substring check distinguishes it from a clean reject (#20). */
static int
build_should_fail(const char *driver, const char *src, const char *diag,
int i)
{
char tmpdir[64], s[128], outbin[128], errf[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/dailn_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(s, sizeof s, "%s/dailn_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/dailn_%d_%d", tmpdir, getpid(), i);
snprintf(errf, sizeof errf, "%s/err", tmpdir);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(s, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
driver, outbin, s, errf);
int rc = runwait(cmd);
int hasmsg = filehas(errf, diag);
runwait(rmcmd);
/* build must NOT succeed AND must emit its diagnostic. */
return (rc != 0 && hasmsg) ? 0 : -1;
}
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match.
* The bug is byte-id-BLIND (both stages emitted identical wrong asm), so
* this is a rule-10 convergence invariant, not a mutation detector. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/dail_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/dail_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/dail_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
drivers[] = {
{ "cstage", cdrv, 0 },
{ "wwstage", wdrv, 1 },
{ NULL, NULL, 0 },
};
int n = (int)(sizeof rows / sizeof rows[0]);
int nn = (int)(sizeof neg / sizeof neg[0]);
int total = 0, fail = 0;
for (int d = 0; drivers[d].name; d++) {
if (drivers[d].gated_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "def_arr_infer_len: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"def_arr_infer_len[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
for (int i = 0; i < nn; i++) {
total++;
if (build_should_fail(drivers[d].path, neg[i],
neg_diag[i], 100 + i) != 0) {
fprintf(stderr,
"def_arr_infer_len[%s][neg%d]: built ok, "
"expected a loud error\n",
drivers[d].name, i);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"def_arr_infer_len: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("def_arr_infer_len: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,237 +0,0 @@
/*
* 816_def_arr_len — cstage and wwstage agree, byte-for-byte and at runtime,
* that `.len` on a module-level `def NAME: [N]T = [...]` array is the static
* element count (an immediate `MOVQ $alen, AX`).
*
* The bug (GAP-A.len, WWSTAGE-ONLY): the #7 let-global `.len` cgdot arm in
* selfhost/cmd/wcc/cgenexpr.ww gated on letvartnode (c.lets only). A `def`
* lives in c.defs, missed that arm, and fell through to the module-qualified
* SB fallback that mis-emitted `MOVQ len(SB)` → w6l: undefined reference to
* 'len' (a build/link failure, not silent). cstage cgen.c already emitted the
* immediate, so the two stages also DIVERGED. The fix adds a def-twin `.len`
* arm using defvartnode (the def-side mirror of letvartnode) — `.len` ONLY.
*
* SCOPE: `.len` only. `.ptr` (cstage itself emits LEAQ (BP), task GAP-A.ptr)
* and `.cap` (arrays have no cap; cstage rejects, wwstage silently emits
* garbage, task GAP-A.cap) are SEPARATE bugs, not mirrored here.
*
* Covers BOTH the explicit-length `def [3]int` and the inferred `def [_]int`
* (#11-stamped) so the deftnode/.rhs immediate path is exercised for the
* inference case too.
*
* row | shape | want
* ---------------+---------------------------------------------+-----
* explicit_len | def A: [3]int=[10,20,30], A.len | 3
* infer_len | def B: [_]int=[1,2,3,4], B.len | 4
* sum | A.len + B.len (ken oracle) | 7
* one_elem | def C: [_]int=[9], C.len | 1
* u8_len | def D: [_]u8=[1u8..5u8], D.len | 5
*/
#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; };
static const struct row rows[] = {
{ "explicit_len",
"package main;\n"
"def A: [3]int = [10, 20, 30];\n"
"export fn main() i32 = {\n"
"\treturn A.len: i32;\n"
"};\n",
3 },
{ "infer_len",
"package main;\n"
"def B: [_]int = [1, 2, 3, 4];\n"
"export fn main() i32 = {\n"
"\treturn B.len: i32;\n"
"};\n",
4 },
/* ken oracle: explicit + inferred together = 7. */
{ "sum",
"package main;\n"
"def A: [3]int = [10, 20, 30];\n"
"def B: [_]int = [1, 2, 3, 4];\n"
"export fn main() i32 = {\n"
"\treturn (A.len: i32) + (B.len: i32);\n"
"};\n",
7 },
{ "one_elem",
"package main;\n"
"def C: [_]int = [9];\n"
"export fn main() i32 = {\n"
"\treturn C.len: i32;\n"
"};\n",
1 },
/* narrow stride — `.len` is a count, independent of element width. */
{ "u8_len",
"package main;\n"
"def D: [_]u8 = [1u8, 2u8, 3u8, 4u8, 5u8];\n"
"export fn main() i32 = {\n"
"\treturn D.len: i32;\n"
"};\n",
5 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/dal_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/dal_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/dal_%d_%d", tmpdir, getpid(), i);
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 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match.
* Post-fix both stages emit `MOVQ $alen, AX`; the rule-10 convergence
* invariant. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/dal_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/dal_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/dal_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "def_arr_len: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"def_arr_len[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"def_arr_len: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("def_arr_len: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,330 +0,0 @@
/*
* 817_arr_cap_reject — a fixed-size array has NO capacity word; `.cap` on an
* array is INVALID ww and BOTH stages must LOUDLY REJECT at check time
* (GAP-A.cap, task #12; drew ruling .ai/drew-gapa-ptr-ruling.md). `.len`
* (GAP-A.len, 7b0e09e) AND `.ptr` (≡ &A[0], the sanctioned ww backing-
* pointer spelling, task #13 divergence-record) stay VALID on an array —
* only `.cap` is rejected.
*
* The bug (each stage misbehaved DIFFERENTLY on the same invalid construct,
* hence both-stage):
* - cstage check.c typed `.cap` on a TY_ARRAY as i32 → cgen then hard-
* rejected with a generic "unsupported field-read shape" (loud but at
* the WRONG layer, vague message).
* - wwstage check.ww stamped `.cap` → cgen link-error (def-global
* `A.cap` → w6l undefined 'cap') or SILENT garbage (local `a.cap`→30).
*
* The fix (BOTH checkers, one `.cap`-on-array gate each, byte-identical
* diagnostic body): reject early with "no field 'cap' on a fixed-size array
* (arrays have no capacity; use .len)". Checker-only: the reject makes the
* buggy cgen `.cap` array paths unreachable (close by construction).
*
* Mutation-sanity: the genuinely-silent pre-fix case is wwstage's LOCAL
* .cap, which BUILT and returned garbage (30) — neg_local_cap is the row
* with real mutation power (built-ok pre-fix → caught here). The other
* neg rows were already loud pre-fix but at the WRONG layer (cstage cgen
* vague-reject on use; wwstage global link-error); this commit converges
* all of them onto one early checker diagnostic.
*
* neg row | shape | gate
* -----------------+----------------------------------------+----------
* neg_local_cap | local [3]int, a.cap | build FAIL
* neg_def_cap | def A:[3]int, A.cap | build FAIL
* neg_let_glob_cap | let G:[3]int (module), G.cap | build FAIL
* neg_infer_cap | def A:[_]int, A.cap (#11 infer path) | build FAIL
*
* pos row | shape | want
* -----------------+----------------------------------------+------
* pos_local_len | local [3]int, a.len | 3
* pos_def_len | def A:[_]int, A.len | 3
* pos_local_ptr | local [3]int, *a.ptr (sanctioned idiom) | 10
*
* The pos_local_ptr row pins that `.ptr`-on-array stays VALID (cgen
* unchanged in this commit; the def-global `.ptr` cgen fix + its build+run
* pin are task #11 / test 818, NOT here — so this control uses a LOCAL
* array `.ptr`, which is correct in both stages today).
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.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; };
static const struct row rows[] = {
{ "pos_local_len",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: [3]int = [10, 20, 30];\n"
"\treturn a.len: i32;\n"
"};\n",
3 },
{ "pos_def_len",
"package main;\n"
"def A: [_]int = [10, 20, 30];\n"
"export fn main() i32 = {\n"
"\treturn A.len: i32;\n"
"};\n",
3 },
/* `.ptr`-on-array is the sanctioned ww spelling for &a[0] (task #13).
* LOCAL array `.ptr` is correct in BOTH stages today (cgen unchanged
* here); the def-global `.ptr` cgen fix is task #11 / test 818. */
{ "pos_local_ptr",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: [3]int = [10, 20, 30];\n"
"\tlet p: *int = a.ptr;\n"
"\treturn (*p): i32;\n"
"};\n",
10 },
};
/* `.cap` on a fixed array — both stages must FAIL the build (loud checker
* diagnostic, not silent garbage / link-error). */
static const char *neg[] = {
/* neg_local_cap */
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: [3]int = [1, 2, 3];\n"
"\treturn a.cap: i32;\n"
"};\n",
/* neg_def_cap */
"package main;\n"
"def A: [3]int = [1, 2, 3];\n"
"export fn main() i32 = {\n"
"\treturn A.cap: i32;\n"
"};\n",
/* neg_let_glob_cap */
"package main;\n"
"let G: [3]int = [1, 2, 3];\n"
"export fn main() i32 = {\n"
"\treturn G.cap: i32;\n"
"};\n",
/* neg_infer_cap — the #11 [_] infer path */
"package main;\n"
"def A: [_]int = [1, 2, 3];\n"
"export fn main() i32 = {\n"
"\treturn A.cap: i32;\n"
"};\n",
};
/* Per-neg expected diagnostic body (parallel to neg[]); all four rows hit
* the same "no field 'cap' on a fixed-size array" path on both stages. */
static const char *neg_diag[] = {
"no field 'cap' on a fixed-size array",
"no field 'cap' on a fixed-size array",
"no field 'cap' on a fixed-size array",
"no field 'cap' on a fixed-size array",
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/acr_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/acr_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/acr_%d_%d", tmpdir, getpid(), i);
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 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
static int
filehas(const char *path, const char *needle)
{
char buf[8192];
FILE *f = fopen(path, "rb");
if (!f) return 0;
size_t n = fread(buf, 1, sizeof buf - 1, f);
fclose(f);
buf[n] = '\0';
return strstr(buf, needle) != NULL;
}
/* build_should_fail — `.cap` on an array must error on `driver`; returns 0
* when the build FAILS *and* emits `diag`. A crash (segfault) wraps as a
* nonzero "w6c failed" with no diagnostic, so the substring distinguishes
* it from a clean reject (#20). */
static int
build_should_fail(const char *driver, const char *src, const char *diag,
int i)
{
char tmpdir[64], s[128], outbin[128], errf[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/acrn_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(s, sizeof s, "%s/acrn_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/acrn_%d_%d", tmpdir, getpid(), i);
snprintf(errf, sizeof errf, "%s/err", tmpdir);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(s, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
driver, outbin, s, errf);
int rc = runwait(cmd);
int hasmsg = filehas(errf, diag);
runwait(rmcmd);
/* build must NOT succeed AND must emit its diagnostic. */
return (rc != 0 && hasmsg) ? 0 : -1;
}
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/acr_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/acr_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/acr_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
drivers[] = {
{ "cstage", cdrv, 0 },
{ "wwstage", wdrv, 1 },
{ NULL, NULL, 0 },
};
int n = (int)(sizeof rows / sizeof rows[0]);
int nn = (int)(sizeof neg / sizeof neg[0]);
int total = 0, fail = 0;
for (int d = 0; drivers[d].name; d++) {
if (drivers[d].gated_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "arr_cap_reject: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"arr_cap_reject[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
for (int i = 0; i < nn; i++) {
total++;
if (build_should_fail(drivers[d].path, neg[i],
neg_diag[i], 100 + i) != 0) {
fprintf(stderr,
"arr_cap_reject[%s][neg%d]: built ok, "
"expected a loud error\n",
drivers[d].name, i);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"arr_cap_reject: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("arr_cap_reject: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,225 +0,0 @@
/*
* 818_arr_ptr_global — `.ptr` on a fixed-size array is the sanctioned ww
* spelling for &A[0] (task #13 divergence-record, drew ruling
* .ai/drew-gapa-ptr-ruling.md). For a LOCAL array the backing pointer is the
* frame slot (LEAQ off(BP)); for a module GLOBAL array (let or def) it is the
* symbol address (LEAQ name(SB)). cstage cgen emitted LEAQ off(BP) for BOTH
* flavours — and for a global, off==0, so `*A.ptr` read the frame's first
* slot = stack garbage (the GAP-A.ptr silent miscompile, task #11; the
* off==0/global base-selection class of #231/#48).
*
* The fix (cmd/w6c/cgen.c N_DOT array .ptr arm + selfhost cgenexpr.ww def
* arm): when off==0 and the operand is a module global (let_islet ||
* def_isarraydef), emit LEAQ name(SB) instead of LEAQ (BP), mirroring the
* def-array index base at cgen.c:4367. wwstage's LET-global `.ptr` was
* already correct (LEAQ name(SB)); only the DEF-global arm was missing.
* After this commit local / let-global / def-global `.ptr` are byte-
* IDENTICAL across both stages.
*
* row | shape | want
* -----------------+-----------------------------------------+------
* def_glob_ptr | def A:[3]int, *A.ptr (THE regression) | 10
* let_glob_ptr | let G:[3]int, *G.ptr (BP→SB converge) | 5
* local_ptr | local [3]int, (a.ptr)[2] (unchanged) | 3
*
* def_glob_ptr / let_glob_ptr are the mutation-sane rows: pre-fix they read
* stack garbage. local_ptr guards the 14 live local-`.ptr` consumers (cgen
* unchanged for locals). A byte-id row pins cstage==wwstage `.s` for the
* def-global `.ptr` source (the convergence the fix delivers).
*/
#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; };
static const struct row rows[] = {
/* def_glob_ptr — THE regression pin: pre-fix `*A.ptr` reads stack
* garbage because cstage LEAQ (BP)'d the def-global base. */
{ "def_glob_ptr",
"package main;\n"
"def A: [3]int = [10, 20, 30];\n"
"export fn main() i32 = {\n"
"\tlet p: *int = A.ptr;\n"
"\treturn (*p): i32;\n"
"};\n",
10 },
/* let_glob_ptr — pins the cstage BP→SB convergence (wwstage was
* already right for let-global, so this closes the latent cs≠ww). */
{ "let_glob_ptr",
"package main;\n"
"let G: [3]int = [5, 6, 7];\n"
"export fn main() i32 = {\n"
"\tlet p: *int = G.ptr;\n"
"\treturn (*p): i32;\n"
"};\n",
5 },
/* local_ptr — the 14-site regression guard; cgen unchanged for a
* LOCAL array (off!=0 → LEAQ off(BP)). p[2] strides element 2. */
{ "local_ptr",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: [3]int = [1, 2, 3];\n"
"\tlet p: *int = a.ptr;\n"
"\treturn p[2]: i32;\n"
"};\n",
3 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/apg_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/apg_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/apg_%d_%d", tmpdir, getpid(), i);
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 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/apg_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/apg_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/apg_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "arr_ptr_global: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"arr_ptr_global[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"arr_ptr_global: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("arr_ptr_global: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,269 +0,0 @@
/*
* 819_def_str_table — `def C: [N]str = [...]` module-level str-array static
* init + element load (#8 / GAP-B, #270 family). A `let [N]str` global static-
* inits correctly (#18); the def-twin did NOT — both stages emitted the index
* ref `LEAQ main.C(SB)` but NEVER emitted the backing DATA block, so w6l failed
* with `undefined reference to 'main.C'` (LOUD, symmetric, both stages — not
* silent, not cs≠ww).
*
* ROOT: emit_strarray_data / emitstrarraydata (the str-array DATAW header +
* per-element A_DATAR ptr-reloc emitter) was gated to the "DATAW" directive
* (let only). A def array routes through emit_array_data(..,"DATA",..) so the
* gate skipped it → no DATA block. The str backing must live in DATAW anyway
* (w6a requires a DATAR reloc-holder be a DATAW slot, asm.c:362); def
* immutability is checker-enforced, independent of the section bit. The fix
* drops the directive gate in BOTH stages so a def str-array rides the same
* DATAW+DATAR emitter as let. int-defs are plain DATA (no reloc) — unaffected.
*
* row | shape | want
* -----------------+---------------------------------------------+------
* def_str_len | def C:[2]str; C.len | 2
* def_str_elem0 | def C:[2]str=["ab","cde"]; C[0].len | 2
* def_str_elem1 | def C:[2]str=["ab","cde"]; C[1].len | 3
* def_str_content | def C:[2]str=["ab","cde"]; C[1][0] ('c') | 99 (DATAR reloc
* | | resolves to the
* | | right _S_ rodata)
* def_str_multi | def C:[3]str=["a","bb","ccc"]; C[2].len | 3
* def_str_varbind | def C:[3]str; let s=C[2]; s.len | 3
* let_str_elem1 | let C:[2]str=["ab","cde"]; C[1].len (CTRL) | 3 (#18, already
* | | works; no-regr)
*
* Each def row fails-to-LINK pre-fix (mutation-sane: delete the gate-drop and
* the def rows go back to w6l undefined). let_str_elem1 is the #18 regression
* guard. A byte-id pass pins cstage==wwstage `.s` for every row (the both-
* wrong-identical convergence: pre-fix both omit the block, post-fix both emit
* the same DATAW+DATAR).
*/
#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; };
static const struct row rows[] = {
/* def_str_len — `.len` on a def str-array (static element count; works
* via the def-twin .len arm even pre-#8, but pinned here for the set). */
{ "def_str_len",
"package main;\n"
"def C: [2]str = [\"ab\", \"cde\"];\n"
"export fn main() i32 = {\n"
"\treturn C.len: i32;\n"
"};\n",
2 },
/* def_str_elem0 — THE regression: load element 0, read its .len.
* Pre-#8 the def DATA block is never emitted → w6l undefined main.C. */
{ "def_str_elem0",
"package main;\n"
"def C: [2]str = [\"ab\", \"cde\"];\n"
"export fn main() i32 = {\n"
"\treturn C[0].len: i32;\n"
"};\n",
2 },
/* def_str_elem1 — element 1 .len (ken oracle row). */
{ "def_str_elem1",
"package main;\n"
"def C: [2]str = [\"ab\", \"cde\"];\n"
"export fn main() i32 = {\n"
"\treturn C[1].len: i32;\n"
"};\n",
3 },
/* def_str_content — C[1][0] is 'c' (99). Proves the per-element A_DATAR
* ptr-reloc resolves to the right _S_ rodata row, not just that a block
* exists. */
{ "def_str_content",
"package main;\n"
"def C: [2]str = [\"ab\", \"cde\"];\n"
"export fn main() i32 = {\n"
"\treturn C[1][0]: i32;\n"
"};\n",
99 },
/* def_str_multi — 3-element def array, element 2 .len. */
{ "def_str_multi",
"package main;\n"
"def C: [3]str = [\"a\", \"bb\", \"ccc\"];\n"
"export fn main() i32 = {\n"
"\treturn C[2].len: i32;\n"
"};\n",
3 },
/* def_str_varbind — bind an element to a let, then read .len (ken
* matrix var-bind form: a full 24B str-header element copy). */
{ "def_str_varbind",
"package main;\n"
"def C: [3]str = [\"x\", \"yy\", \"zzz\"];\n"
"export fn main() i32 = {\n"
"\tlet s: str = C[2];\n"
"\treturn s.len: i32;\n"
"};\n",
3 },
/* let_str_elem1 — CONTROL: the #18 let-global str-array (already works).
* Guards against the gate-drop regressing the let path. */
{ "let_str_elem1",
"package main;\n"
"let C: [2]str = [\"ab\", \"cde\"];\n"
"export fn main() i32 = {\n"
"\treturn C[1].len: i32;\n"
"};\n",
3 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/dst_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/dst_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/dst_%d_%d", tmpdir, getpid(), i);
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 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/dst_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/dst_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/dst_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "def_str_table: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"def_str_table[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"def_str_table: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("def_str_table: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,340 +0,0 @@
/*
* 820_arr_zero_vs_infer — an EXPLICIT zero/short fixed-size array over-filled
* by its initializer (`[0]int = [1,2]`, `[2]int = [1,2,3]`) is a LOUD length-
* mismatch on BOTH stages; an INFER `[_]` still infers its length from the
* initializer (task #9; ken oracle .ai/ken-9-oracle.md, rob spec
* .ai/rob-9-spec.md).
*
* The bug: post-resolve_type, both `[0]` and `[_]` collapse to alen==0 — the
* Type loses the distinction. The #71 over-fill diagnostic was suppressed for
* alen==0, so `[0]int = [1,2]` slipped past and each stage misbehaved
* DIFFERENTLY (byte-id-blind):
* - cstage silently RESIZED [0]→[2] (exit 2), or for the `def`/`let` cases
* resized too.
* - wwstage kept [0] and OOB-read / SEGFAULTed (exit 8 / 139), or resized
* the local (exit 2) — inconsistent across local vs module.
*
* The fix (one both-stage CHECKER commit): the AST RETAINS the distinction the
* Type loses — an infer `[_]` leaves the N_TARRAY length-child NULL, an
* explicit `[N]` (incl `[0]`) carries an N_INTLIT. cstage gates the four
* infer-resize / no-init sites on is_infer_arr(<type-AST>) and drops the
* `alen > 0` exemption at the over-fill check; wwstage's shared count-gate
* checkarrlitfits fires whenever `arrtn.rhs != nil`. So an explicit `[N]=[init]`
* with count>N louds in EVERY context, INCLUDING N==0, before codegen.
*
* Mutation-sanity: every neg `[0]` row BUILT+RAN pre-fix (silent resize / OOB);
* it must now FAIL to build (the rows assert build-FAIL, which only holds
* post-fix). def_two_overfill (`[2]=[1,2,3]`) is the #71 N>0 regression guard.
*
* neg row | shape | gate
* -------------------+------------------------------------+----------
* def_zero_overfill | def X:[0]int=[1,2] | build FAIL
* let_zero_overfill | let X:[0]int=[1,2] (module) | build FAIL
* local_zero_overfill| local [0]int=[1,2] | build FAIL
* def_two_overfill | def X:[2]int=[1,2,3] (#71 guard) | build FAIL
* str_zero_overfill | def X:[0]str=["a"] (elem-agnostic) | build FAIL
*
* pos row | shape | want
* ------------+--------------------------------+------
* infer_ctl | def X:[_]int=[10,20]; X[1] | 20 (#11 infer works)
* empty_zero | let X:[0]int=[]; return 7 | 7 (legit empty array)
* infer_len | let X:[_]int=[1,2,3]; X.len | 3 (infer unaffected)
*/
#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;
}
/* beid — include this row in the cstage-vs-wwstage byte-id sweep. Both the
* empty_zero global (`let X:[0]int=[]`) and the empty_zero_local row now
* converge byte-identically (beid=1): #15 closed the empty-`[0]T` cgen
* divergence — wwstage's spurious zero-width `DATAW main.X(SB),""` (cstage
* omits a zero-byte global) and its over-allocated `$16` local frame (cstage
* `$0` — a zero-length array reserves no slot) are both gated on the array
* being non-empty. See .ai/rob-15-spec.md: cgen.ww emitletdataw gates the
* array DATAW on `sz > 0`; cgenutil.ww slotsize returns 0 for a TY_ARRAY of
* alen==0. Both forms still run 7. */
struct row { const char *label; const char *src; int want; int beid; };
static const struct row rows[] = {
/* infer_ctl — `[_]` still infers length from the initializer (#11). */
{ "infer_ctl",
"package main;\n"
"def X: [_]int = [10, 20];\n"
"export fn main() i32 = {\n"
"\treturn X[1]: i32;\n"
"};\n",
20, 1 },
/* empty_zero — a real zero-length array (`[0]int = []`) stays VALID.
* beid=1: #15 closed the empty-array-global DATAW divergence. */
{ "empty_zero",
"package main;\n"
"let X: [0]int = [];\n"
"export fn main() i32 = {\n"
"\treturn 7;\n"
"};\n",
7, 1 },
/* empty_zero_local — a LOCAL zero-length array (`[0]int = []`) reserves
* no frame slot (#15: cstage `$0`, wwstage was `$16`). beid=1. */
{ "empty_zero_local",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet x: [0]int = [];\n"
"\treturn 7;\n"
"};\n",
7, 1 },
/* void_local — a zero-SIZE (not zero-length) local: `done = void` sizes
* 0 via slotsize, but cstage cglet defaults a non-composite local to an
* 8B slot. #15 dropped localreserve's sub-8 floor, which had masked this
* — letslotsize now floors a void local to 8 (cstage parity). beid=1: a
* regression here (void slot 0) collides with the spilled param. */
{ "void_local",
"package main;\n"
"type done = void;\n"
"export fn main() i32 = {\n"
"\tlet d: done;\n"
"\tlet a: i32 = 7;\n"
"\treturn a;\n"
"};\n",
7, 1 },
/* infer_len — `[_]` infer is unaffected, `.len` reads the real count. */
{ "infer_len",
"package main;\n"
"let X: [_]int = [1, 2, 3];\n"
"export fn main() i32 = {\n"
"\treturn X.len: i32;\n"
"};\n",
3, 1 },
};
/* An EXPLICIT `[N]int = [init]` with init-count > N — both stages must FAIL
* the build (loud over-fill diagnostic, not silent resize / OOB). */
static const char *neg[] = {
/* def_zero_overfill */
"package main;\n"
"def X: [0]int = [1, 2];\n"
"export fn main() i32 = {\n"
"\treturn X[1]: i32;\n"
"};\n",
/* let_zero_overfill */
"package main;\n"
"let X: [0]int = [1, 2];\n"
"export fn main() i32 = {\n"
"\treturn X[1]: i32;\n"
"};\n",
/* local_zero_overfill */
"package main;\n"
"export fn main() i32 = {\n"
"\tlet X: [0]int = [1, 2];\n"
"\treturn X[1]: i32;\n"
"};\n",
/* def_two_overfill — the #71 N>0 regression guard, must stay loud */
"package main;\n"
"def X: [2]int = [1, 2, 3];\n"
"export fn main() i32 = {\n"
"\treturn X[1]: i32;\n"
"};\n",
/* str_zero_overfill — element-type-agnostic */
"package main;\n"
"def X: [0]str = [\"a\"];\n"
"export fn main() i32 = {\n"
"\treturn 0;\n"
"};\n",
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/azi_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/azi_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/azi_%d_%d", tmpdir, getpid(), i);
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 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* build_should_fail — the over-fill must error on `driver`; returns 0 when the
* build correctly FAILS, non-zero when it wrongly succeeded. */
static int
build_should_fail(const char *driver, const char *src, int i)
{
char tmpdir[64], s[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/azin_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(s, sizeof s, "%s/azin_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/out", tmpdir);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(s, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
driver, outbin, s);
int rc = runwait(cmd);
runwait(rmcmd);
return rc == 0 ? -1 : 0; /* build must NOT succeed */
}
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/azi_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/azi_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/azi_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
drivers[] = {
{ "cstage", cdrv, 0 },
{ "wwstage", wdrv, 1 },
{ NULL, NULL, 0 },
};
int n = (int)(sizeof rows / sizeof rows[0]);
int nn = (int)(sizeof neg / sizeof neg[0]);
int total = 0, fail = 0;
for (int d = 0; drivers[d].name; d++) {
if (drivers[d].gated_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "arr_zero_vs_infer: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"arr_zero_vs_infer[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
for (int i = 0; i < nn; i++) {
total++;
if (build_should_fail(drivers[d].path, neg[i],
100 + i) != 0) {
fprintf(stderr,
"arr_zero_vs_infer[%s][neg%d]: built ok, "
"expected a loud error\n",
drivers[d].name, i);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
if (!rows[i].beid)
continue; /* see `beid` — out-of-#9 divergence */
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"arr_zero_vs_infer: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("arr_zero_vs_infer: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,352 +0,0 @@
/*
* 821_def_str_index_reject — indexing a bare DEF-GLOBAL scalar str is INVALID
* ww and BOTH stages must LOUDLY REJECT at check time (#14, the #8 sibling;
* drew ruling .ai/drew-14-ruling.md, rob spec .ai/rob-14-spec.md).
*
* `str[i] -> u8` itself is VALID ww — a sanctioned Go-like direct byte-index,
* a deliberate divergence from Hare's `strings::toutf8(s)[i]` (the Hare
* reference checker rejects str-index, harec check.c:362). lib/strings is
* load-bearing on it (compare/dup/join). The reject is SCOPED to ONE operand
* shape: a bare def-global SCALAR str.
*
* The bug (both-stage, but each side misbehaved DIFFERENTLY):
* - cstage: `def S:str="hi"; S[0]` BUILT and emitted an unbacked `main.S(SB)`
* base load — at runtime a frame-garbage base → SILENT SEGFAULT.
* - wwstage: the same source referenced an unbacked symbol → w6l LINK-FAIL.
* A `def` is an inline compile-time CONSTANT (def-as-constant; not storage-
* backed like a `let`), so it has no address to index. The fix REJECTS that
* one operand at the checker (N_IDENT operand + SK_DEF sym + scalar TY_STR
* base), surfacing a source error before cgen — segfault gone by construction.
*
* Mutation-sanity: the genuinely-silent pre-fix case is cstage's def-scalar
* index, which BUILT (then segfaulted) — neg rows assert BUILD-FAIL, so they
* FAIL pre-fix on cstage (built-ok) and pass post-fix. wwstage already
* link-failed pre-fix.
*
* neg row | shape | gate
* --------------------+----------------------------------------+----------
* def_scalar_index | def S:str="hi", return S[0]:i32 | build FAIL
* def_scalar_varbind | def S:str="hi", let c=S[1] | build FAIL
*
* pos row | shape | want
* --------------------+----------------------------------------+------
* let_scalar_index | module let S:str="hi", S[0]:i32 | 104 ('h')
* param_index | fn f(s:str) i32 = s[0]:i32; f("hi") | 104 ('h')
* local_index | local let s:str="hi", s[0]:i32 | 104 ('h')
* def_strarray_elem | def C:[2]str=["ab","cd"], C[1][0]:i32 | 99 ('c')
*
* The def_strarray_elem row pins the #8 materialised array-element str stays
* VALID: the outer operand `C[1]` is N_INDEX (not N_IDENT), so the scalar-str
* gate excludes it; and a bare def str-ARRAY operand's base is TY_ARRAY (not
* scalar str), also excluded. let/param/local + string-literal operands stay
* valid (the lib/strings compare/dup pattern).
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.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; };
static const struct row rows[] = {
/* module-level `let` str is storage-backed (DATA) → indexable. */
{ "let_scalar_index",
"package main;\n"
"let S: str = \"hi\";\n"
"export fn main() i32 = {\n"
"\treturn S[0]: i32;\n"
"};\n",
104 },
{ "param_index",
"package main;\n"
"fn f(s: str) i32 = {\n"
"\treturn s[0]: i32;\n"
"};\n"
"export fn main() i32 = {\n"
"\treturn f(\"hi\");\n"
"};\n",
104 },
{ "local_index",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet s: str = \"hi\";\n"
"\treturn s[0]: i32;\n"
"};\n",
104 },
/* #8 materialised array-element str — `C[1]` is N_INDEX, not N_IDENT,
* so the def-scalar-str gate excludes it; must STAY valid. */
{ "def_strarray_elem",
"package main;\n"
"def C: [2]str = [\"ab\", \"cd\"];\n"
"export fn main() i32 = {\n"
"\treturn C[1][0]: i32;\n"
"};\n",
99 },
/* CRITICAL PRESERVE — INTERP-style def-global scalar str `.len`/`.ptr`
* N_DOT FIELD read (lib w6l/dynout.ww INTERP, load-bearing in the ELF
* emit; bootstrap dies if this breaks). The #14 reject is on N_INDEX,
* NOT N_DOT — this field read must STAY valid. 3 + *"abc".ptr(='a'=97)
* = 100. */
{ "def_scalar_field",
"package main;\n"
"def S: str = \"abc\";\n"
"export fn main() i32 = {\n"
"\tlet n: i32 = S.len: i32;\n"
"\tlet p: *u8 = S.ptr;\n"
"\treturn n + (*p): i32;\n"
"};\n",
100 },
/* def-global NON-str array index — the scalar-str gate (TY_STR only)
* must NOT over-catch a legit def-array index (the #8 array path). */
{ "def_arr_index",
"package main;\n"
"def A: [3]int = [10, 20, 30];\n"
"export fn main() i32 = {\n"
"\treturn A[1]: i32;\n"
"};\n",
20 },
};
/* Indexing a bare def-global scalar str — both stages must FAIL the build
* (loud checker diagnostic, not silent segfault / link-error). */
static const char *neg[] = {
/* def_scalar_index */
"package main;\n"
"def S: str = \"hi\";\n"
"export fn main() i32 = {\n"
"\treturn S[0]: i32;\n"
"};\n",
/* def_scalar_varbind */
"package main;\n"
"def S: str = \"hi\";\n"
"export fn main() i32 = {\n"
"\tlet c = S[1];\n"
"\treturn c: i32;\n"
"};\n",
};
/* Per-neg expected diagnostic body (parallel to neg[]); both rows hit the
* same "cannot index a def-constant str" path on both stages. */
static const char *neg_diag[] = {
"cannot index a def-constant str",
"cannot index a def-constant str",
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], cmd[1024], rmcmd[160];
snprintf(tmpdir, sizeof tmpdir, "/tmp/dsi_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/dsi_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/dsi_%d_%d", tmpdir, getpid(), i);
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);
/* #8: -o pins binary + .sepwork scratch under tmpdir; rm -rf on
* every exit path removes the now-non-empty dir. */
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
static int
filehas(const char *path, const char *needle)
{
char buf[8192];
FILE *f = fopen(path, "rb");
if (!f) return 0;
size_t n = fread(buf, 1, sizeof buf - 1, f);
fclose(f);
buf[n] = '\0';
return strstr(buf, needle) != NULL;
}
/* build_should_fail — indexing a def-global scalar str must error on
* `driver`; returns 0 when the build FAILS *and* emits `diag`. A crash
* (segfault) wraps as a nonzero "w6c failed" with no diagnostic, so the
* substring distinguishes it from a clean reject (#20). */
static int
build_should_fail(const char *driver, const char *src, const char *diag,
int i)
{
char tmpdir[64], s[128], outbin[128], cmd[1024], errf[128], rmcmd[160];
snprintf(tmpdir, sizeof tmpdir, "/tmp/dsin_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(s, sizeof s, "%s/dsin_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/dsin_%d_%d", tmpdir, getpid(), i);
snprintf(errf, sizeof errf, "%s/err", tmpdir);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(s, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(src, f);
fclose(f);
/* #8: -o pins binary + any .sepwork scratch under tmpdir; rm -rf
* removes the now-non-empty dir. */
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
driver, outbin, s, errf);
int rc = runwait(cmd);
int hasmsg = filehas(errf, diag);
runwait(rmcmd);
/* build must NOT succeed AND must emit its diagnostic. */
return (rc != 0 && hasmsg) ? 0 : -1;
}
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/dsi_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/dsi_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/dsi_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
drivers[] = {
{ "cstage", cdrv, 0 },
{ "wwstage", wdrv, 1 },
{ NULL, NULL, 0 },
};
int n = (int)(sizeof rows / sizeof rows[0]);
int nn = (int)(sizeof neg / sizeof neg[0]);
int total = 0, fail = 0;
for (int d = 0; drivers[d].name; d++) {
if (drivers[d].gated_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "def_str_index_reject: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"def_str_index_reject[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
for (int i = 0; i < nn; i++) {
total++;
if (build_should_fail(drivers[d].path, neg[i],
neg_diag[i], 100 + i) != 0) {
fprintf(stderr,
"def_str_index_reject[%s][neg%d]: built ok, "
"expected a loud error\n",
drivers[d].name, i);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"def_str_index_reject: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("def_str_index_reject: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,242 +0,0 @@
/*
* 822_struct_global_byval_arg — a module-global struct passed BY VALUE to
* a fn (`let g: pt = pt{...}; take(g)`) was silently miscompiled, mirror-
* opposite on the two stages (task #150, the off==0 localfind footgun /
* GAP-A.ptr / #231 family):
*
* - cstage (cmd/w6c/cgen.c by-value struct-IDENT call-arg arm): localfind
* of a global returns 0, so the BP loads read the stack FRAME, never
* `LEAQ main.g(SB)` → garbage (ken saw 104, not the field sum).
* - wwstage (selfhost cgenexpr/cgenutil pushargsrev struct arm): the
* ≤16B-struct ident with no local slot fell past the local fast path
* AND was excluded from the #271 aggregate arm (structident), landing
* on the scalar single-PUSHQ default — ONE word for a 2-word struct,
* so the callee's word1 read stack garbage (silent field-drop).
*
* The fix (ONE both-stage cgen commit, converged byte-IDENTICAL): when the
* struct-arg operand is a module-global (off==0 && let_islet||def_isstructdef),
* resolve the global base into BX via `LEAQ name(SB)` and copy ALL
* eightbytes (`MOVQ off(BX),AX; PUSHQ`) high→low — the #129-A.2 struct-
* global access shape. The LOCAL path (off!=0) is untouched.
*
* row | shape | want
* ------------+------------------------------------------------+------
* two_field | let g:pt{a:int,b:int}=pt{a=5,b=9}; take(g) | 14
* narrow_mix | let g:struct{a:i32,b:i32,c:int}; sum | 6
* three_word | let g:struct{a,b,c:int} (24B, #271 arm); sum | 7
* ctrl_local | LOCAL struct arg (off!=0, unchanged, byte-id) | 14
*
* two_field / narrow_mix are the mutation-sane rows: pre-fix cstage reads
* the frame (garbage) and wwstage drops word1. three_word confirms the >16B
* #271 aggregate arm already handles a struct global (let_islet base).
* ctrl_local guards the unchanged local-struct path. A byte-id row pins
* cstage==wwstage `.s` for each source — the convergence the fix delivers.
*
* Explicit-typed globals ONLY (`let g: pt = ...`): an inferred `let g =
* pt{}` would loud via the separate checker Bug 1 (#18) and mask #150.
*/
#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; };
static const struct row rows[] = {
/* two_field — THE regression pin: 2-field 16B struct global by value.
* Pre-fix cstage reads the frame (garbage), wwstage drops field b. */
{ "two_field",
"package main;\n"
"type pt = struct { a: int, b: int };\n"
"let g: pt = pt { a = 5, b = 9 };\n"
"fn take(x: pt) int = { return x.a + x.b; };\n"
"export fn main() i32 = { return take(g): i32; };\n",
14 },
/* narrow_mix — i32/i32/int packs two narrow fields into eightbyte 0,
* int into eightbyte 1; 16B → the struct-ident fast arm (the fix). */
{ "narrow_mix",
"package main;\n"
"type pt = struct { a: i32, b: i32, c: int };\n"
"let g: pt = pt { a = 1, b = 2, c = 3 };\n"
"fn take(x: pt) int = { return (x.a: int) + (x.b: int) + x.c; };\n"
"export fn main() i32 = { return take(g): i32; };\n",
6 },
/* three_word — 24B struct global (>16B) routes through the #271
* aggregate arm; confirms the global base (let_islet) is honoured. */
{ "three_word",
"package main;\n"
"type pt = struct { a: int, b: int, c: int };\n"
"let g: pt = pt { a = 1, b = 2, c = 4 };\n"
"fn take(x: pt) int = { return x.a + x.b + x.c; };\n"
"export fn main() i32 = { return take(g): i32; };\n",
7 },
/* ctrl_local — a LOCAL struct arg (off!=0). cgen unchanged; pins the
* local-struct push path stays byte-id (no regress). */
{ "ctrl_local",
"package main;\n"
"type pt = struct { a: int, b: int };\n"
"fn take(x: pt) int = { return x.a + x.b; };\n"
"export fn main() i32 = {\n"
"\tlet g: pt = pt { a = 5, b = 9 };\n"
"\treturn take(g): i32;\n"
"};\n",
14 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/sga_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/sga_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/sga_%d_%d", tmpdir, getpid(), i);
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 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/sga_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/sga_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/sga_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "struct_global_byval_arg: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"struct_global_byval_arg[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"struct_global_byval_arg: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("struct_global_byval_arg: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,231 +0,0 @@
/*
* 823_inferred_global_let — an inferred-type (annotation-less) module-global
* `let g = <init>;` must type-resolve at every downstream read. cstage's
* module N_LET pass-2 (cmd/wcc/check.c) stamped `d->type` from the rhs but
* never repointed the Sym that pass-1 installed with the annotation-less NULL
* type — so every later N_IDENT read of the global resolved its type as nil:
* `let n = 5; return n + 0` → `arithmetic on non-numeric type`
* `let g = pt{..}; g.a + g.b` → `arithmetic on non-numeric type`
* `let g = pt{..}; take(g)` → `argument type <nil> not assignable to pt`
*
* The fix (cmd/w6c/cgen.c sibling check.c) mirrors the #11 [_]-array Sym
* repoint a few lines up: after `d->type = type_default(rt)`, repoint the
* Sym at the stamped type. cstage-ONLY — wwstage's single-pass resolve
* already stamps the inferred global (all three rows built clean pre-fix),
* so this LIFTS cstage from loud-reject to accept, converging acceptance.
* No asm moves for code that already built; the inferred form now reaches
* the same cgen as the explicit-typed form, so cstage==wwstage byte-id.
*
* row | shape | want
* --------------+---------------------------------------------+-----
* scalar | let n = 5; return n + 0 (broadest case) | 5
* struct_field | let g = pt{a=7,b=9}; g.a + g.b | 16
* struct_arg | let g = pt{a=5,b=9}; take(g) (rides #150-A) | 14
*
* All three are mutation-sane: pre-fix cstage loud-rejects each (the build
* fails), so a regression that drops the repoint re-louds and fails the row.
* The byte-id rows pin cstage==wwstage `.s` for the now-accepted inferred
* source.
*/
#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; };
static const struct row rows[] = {
/* scalar — the broadest case: an inferred scalar global read in
* arithmetic. The read must flow through the repointed Sym's numeric
* type; pre-fix cstage `arithmetic on non-numeric type` (a bare
* `return n: i32` would NOT loud — the cast's target type masks the
* nil Sym — so `+ 0` is the mutation-sane shape). */
{ "scalar",
"package main;\n"
"let n = 5;\n"
"export fn main() i32 = {\n"
"\treturn (n + 0): i32;\n"
"};\n",
5 },
/* struct_field — inferred struct global, both fields read. pre-fix
* cstage `arithmetic on non-numeric type`. */
{ "struct_field",
"package main;\n"
"type pt = struct { a: int, b: int };\n"
"let g = pt { a = 7, b = 9 };\n"
"export fn main() i32 = {\n"
"\treturn (g.a + g.b): i32;\n"
"};\n",
16 },
/* struct_arg — inferred struct global passed BY VALUE; rides the
* #150 Commit A cgen marshalling. pre-fix cstage `argument type
* <nil> not assignable to pt`. */
{ "struct_arg",
"package main;\n"
"type pt = struct { a: int, b: int };\n"
"let g = pt { a = 5, b = 9 };\n"
"fn take(x: pt) int = {\n"
"\treturn x.a + x.b;\n"
"};\n"
"export fn main() i32 = {\n"
"\treturn take(g): i32;\n"
"};\n",
14 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/igl_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/igl_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/igl_%d_%d", tmpdir, getpid(), i);
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 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/igl_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/igl_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/igl_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "inferred_global_let: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"inferred_global_let[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"inferred_global_let: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("inferred_global_let: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,266 +0,0 @@
/*
* 824_inferred_float_global — an inferred-type (annotation-less) module-global
* `let pi = 3.5;` whose init is a FLOAT literal must load as a float (MOVSD)
* at every downstream read, exactly as the explicit-typed `let pi: f64 = 3.5`
* does. WWSTAGE-ONLY bug (cstage already correct post-#150-B / #18).
*
* Root (selfhost/cmd/wcc/cgen.ww): the checker backfills the inferred decl's
* type annotation to the literal's type node `untyped_float` (check.ww
* checkletassign). letemitsize keys global slot-sizing on letscalarprim /
* letfloatprim, NEITHER of which recognises `untyped_float` → returns 0 → the
* global is DROPPED from collectlets: no DATAW slot emitted, and every read
* falls through cgident's `isletvar` guard to the silent bare return, leaving
* X0 holding a stale spilled value. `let pi = 3.5; (pi * 2.0): int` then
* computed 2.0*2.0 = 4, not 7 — silent, no diagnostic.
*
* defaultinferredlets already type_defaults an inferred-INT global's
* `untyped_int` annotation to the machine word `int`; its comment explicitly
* carved the float twin out and deferred it to #135, because cstage USED to
* integer-type an inferred float at the use site (MOVQ) so defaulting ww-side
* alone would diverge (cs≠ww, rule-10). #150-B fixed cstage to type_default
* `untyped_float` → f64 and load MOVSD, so the divergence is gone: this commit
* flips the carve-out (default `untyped_float` → f64, peeling one unary +/- as
* the int arm does) and adds a name-keyword fallback to cgident's let-float
* gate (the renamed `f64`/`f32` N_TNAME carries no stamped tinfo cgen can read,
* so isfloattype's stamp-read misses it; letfloatprim on the keyword fires).
* wwstage now emits the IDENTICAL `LEAQ main.pi(SB); MOVSD (CX), X0` as cstage.
*
* row | shape | want
* ------------+---------------------------------------------+-----
* infer_mul | let pi = 3.5; (pi * 2.0): int (the bug) | 7
* infer_read | let r = 2.5; (r + 0.5): int | 3
* infer_neg | let g = -2.5; (g * -2.0): int (unary peel) | 5
* infer_f32 | let q = 1.5f32; (q + 1.5f32): int (MOVSS) | 3
* ctrl_expl | let e: f64 = 3.5; (e * 2.0): int (explicit) | 7
* ctrl_int | let n = 5; (n + 1): int (int stays MOVQ) | 6
*
* Pre-fix the infer_* rows ran wwstage=garbage (stale X0 / dropped global) and
* cstage=correct (cs≠ww); post-fix all six are byte-identical between stages.
* ctrl_int guards that the inferred-INT default (untyped_int → int, MOVQ) is
* untouched; ctrl_expl guards the explicit-f64 path didn't regress.
*/
#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; };
static const struct row rows[] = {
/* infer_mul — the bug: inferred float global read in a float binop,
* cast to int. Pre-fix wwstage dropped main.pi (no DATAW) and left X0
* stale → 2.0*2.0 = 4; cstage 7. */
{ "infer_mul",
"package main;\n"
"let pi = 3.5;\n"
"export fn main() i32 = {\n"
"\treturn (pi * 2.0): i32;\n"
"};\n",
7 },
/* infer_read — second inferred-float read shape (+, not *). */
{ "infer_read",
"package main;\n"
"let r = 2.5;\n"
"export fn main() i32 = {\n"
"\treturn (r + 0.5): i32;\n"
"};\n",
3 },
/* infer_neg — inferred float global with a unary-minus init; exercises
* defaultinferredlets' single-unary peel (mirror of the int arm). */
{ "infer_neg",
"package main;\n"
"let g = -2.5;\n"
"export fn main() i32 = {\n"
"\treturn (g * -2.0): i32;\n"
"};\n",
5 },
/* infer_f32 — a bare f32-suffixed literal infers f32; the float load
* must pick MOVSS, not MOVSD. (f32 already worked via letfloatprim's
* "f32"; row guards the suffix-inferred path stays MOVSS.) */
{ "infer_f32",
"package main;\n"
"let q = 1.5f32;\n"
"export fn main() i32 = {\n"
"\treturn (q + 1.5f32): i32;\n"
"};\n",
3 },
/* ctrl_expl — explicit f64 annotation; already worked (lv.tnode is the
* non-nil f64 node). No-regress guard. */
{ "ctrl_expl",
"package main;\n"
"let e: f64 = 3.5;\n"
"export fn main() i32 = {\n"
"\treturn (e * 2.0): i32;\n"
"};\n",
7 },
/* ctrl_int — inferred INT global. defaultinferredlets defaults it to
* `int` (MOVQ scalar load); the float arm must NOT pull it into MOVSD.
* No-regress guard for the int path. */
{ "ctrl_int",
"package main;\n"
"let n = 5;\n"
"export fn main() i32 = {\n"
"\treturn (n + 1): i32;\n"
"};\n",
6 },
};
static int
run_driver(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/ifg_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/ifg_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/ifg_%d_%d", tmpdir, getpid(), i);
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 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/ifg_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/ifg_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/ifg_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "inferred_float_global: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"inferred_float_global[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"inferred_float_global: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("inferred_float_global: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,274 +0,0 @@
/*
* 825_errfirst_union_try — error-first tagged-union try (`?`/`!`) success-tag
* (#52, the #216 deferred divergence). wwstage HARDCODED the success tag as 0,
* so for an ERROR-FIRST union — error variant at tag 0, success at tag 1+ (e.g.
* `(e | u16)` with `type e = !i32`) — the success (tag 1) failed the `CMPQ $0`
* success check and fell to the error path: `!` → exit(1), `?` → propagate.
* cstage was already correct via cg_tagged_success_tag (cmd/w6c/cgen.c:857):
* if any variant is an error, the success tag is the first NON-error variant's
* index, else 0. The `.s` diff was literally `CMPQ $1` (cstage) vs `CMPQ $0`
* (wwstage).
*
* The fix (selfhost cgenexpr.ww only — cstage UNCHANGED): a `successtag`
* helper mirroring cstage's, applied at the FOUR sites that shared the tag-0 /
* first-param assumption — cgtryprop CMPQ, cgtryunw CMPQ, and the
* cgtrytupleshift / cgtrytaggedshift payload-variant lookups (successvariant).
*
* row | shape | want
* ----------------+----------------------------------------+------
* errfirst_must | (e|u16) `!` unwrap (THE repro) | 44
* errfirst_prop | (e|u16) `?` propagate through outer fn | 44
* succfirst_ctrl | (u16|e) `!` — successtag=0, CMPQ $0 | 7
* errfirst_3var | (e1|e2|u16) success at index 2 | 99
* errfirst_tuple | (e|(u16,u16)) `!` — successvariant shift| 51
*
* errfirst_tuple is the LATENT-site row: the cgtrytupleshift payload-shift only
* bites an error-first union whose SUCCESS variant is itself a tuple. Pre-fix it
* fell two ways — the CMPQ $0 rejected tag-1 (→ exit 1), AND even with the CMPQ
* fixed the shift read ou.params.type_ (the error variant, not a tuple) so the
* rvalue tuple never filled the cursor (verified mutation: 88, not 51). The
* successvariant fix routes the shift to the tuple. The sibling cgtrytaggedshift
* (nested-tagged success) shares the identical successvariant call; it is not
* exercised here because nested-tagged union *construction* (the return-coercion
* box, main.ok) has a separate pre-existing cs!=ww divergence — #52-independent
* (present success-first too) — filed for its own fix.
*
* Exit codes are the process low-8-bits: 300 & 0xFF = 44. errfirst_* are the
* mutation-sane rows: pre-fix wwstage = exit 1 (wrong-tag → error path),
* cstage = correct → cs!=ww. succfirst_ctrl is the byte-id no-regress guard:
* successtag returns 0 there, so its `CMPQ $0` is UNCHANGED. A byte-id pass
* pins cstage==wwstage `.s` for every row.
*/
#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; };
static const struct row rows[] = {
/* errfirst_must — THE repro: error-first `(e|u16)`, `!` unwrap of a
* tag-1 success. Pre-fix wwstage CMPQ $0 vs tag 1 → error → exit 1. */
{ "errfirst_must",
"package main;\n"
"type e = !i32;\n"
"type u = (e | u16);\n"
"fn ok() u = { return 300u16; };\n"
"export fn main() i32 = {\n"
"\tlet x = ok()!;\n"
"\treturn x: i32;\n"
"};\n",
44 },
/* errfirst_prop — the `?` twin: outer propagates inner's success
* through the same error-first union, then `!` unwraps. */
{ "errfirst_prop",
"package main;\n"
"type e = !i32;\n"
"type u = (e | u16);\n"
"fn inner() u = { return 300u16; };\n"
"fn outer() u = {\n"
"\tlet v = inner()?;\n"
"\treturn v;\n"
"};\n"
"export fn main() i32 = {\n"
"\treturn outer()!: i32;\n"
"};\n",
44 },
/* succfirst_ctrl — success-FIRST `(u16|e)`, successtag=0: the byte-id
* no-regress guard, CMPQ $0 unchanged. */
{ "succfirst_ctrl",
"package main;\n"
"type e = !i32;\n"
"type u2 = (u16 | e);\n"
"fn g() u2 = { return 7u16; };\n"
"export fn main() i32 = {\n"
"\treturn g()!: i32;\n"
"};\n",
7 },
/* errfirst_3var — two error variants then success: successtag walks
* past index 0 AND 1 to return 2, pinning the walk (not 0, not 1). */
{ "errfirst_3var",
"package main;\n"
"type e1 = !i32;\n"
"type e2 = !i64;\n"
"type u3 = (e1 | e2 | u16);\n"
"fn h() u3 = { return 99u16; };\n"
"export fn main() i32 = {\n"
"\treturn h()!: i32;\n"
"};\n",
99 },
/* errfirst_tuple — error-first union whose SUCCESS variant is a tuple:
* exercises cgtrytupleshift via successvariant. Pre-fix exit 1 (CMPQ $0
* rejects tag 1); CMPQ-fixed-but-shift-reverted gives 88; full fix → 51
* (300+7 = 307, 307 & 0xFF = 51). */
{ "errfirst_tuple",
"package main;\n"
"type e = !i32;\n"
"type ut = (e | (u16, u16));\n"
"fn ok() ut = { return (300u16, 7u16); };\n"
"export fn main() i32 = {\n"
"\tlet t = ok()!;\n"
"\tlet (a, b) = t;\n"
"\treturn (a: i32) + (b: i32);\n"
"};\n",
51 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/efu_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/efu_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/efu_%d_%d", tmpdir, getpid(), i);
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 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/efu_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/efu_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/efu_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "errfirst_union_try: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"errfirst_union_try[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"errfirst_union_try: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("errfirst_union_try: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,310 +0,0 @@
/*
* 827_str_eq — str `==` / `!=` is a CONTENT compare (rt_streq), not a
* pointer compare. #146 (the ww-twin of the #154 cstage fix).
*
* THE BUG (wwstage-only; cstage was already correct via #154): wwstage
* cgbin (selfhost/cmd/wcc/cgenexpr.ww) had NO str-awareness, so every
* `==`/`!=` fell to the generic comparison tail — a single `CMPQ BX, AX`
* on the eager-eval'd registers. For a str, eager-eval collapses the
* 3-word header to its ptr word, so the compare became ptr-vs-ptr: two
* DISTINCT-pointer equal-content strings answered FALSE on wwstage while
* cstage (which CALLs rt_streq) answered TRUE. THE divergence row is
* eq_dup_true: `let a="abc"; let b=strings.dup("abc"); a==b`.
*
* THE FIX: cgbin now has a str ==/!= branch at the top (before the
* generic eval), mirroring cstage cgen.c cbinop:4564-4623 — push rhs
* then lhs (len, ptr each), POPQ DI/SI/DX/CX = a.ptr,a.len,b.ptr,b.len
* (rt/streq.s ABI), CALL rt_streq -> AX in {0,1}, XORQ $1 for !=.
*
* Two row sets:
* runrows — built+RUN on BOTH drivers (need strings.dup for a
* distinct-pointer copy, so they `import strings` and build
* via `ww` which links the lib). These pin RUNTIME
* correctness; eq_dup_true/ne_dup_false are mutation-sane
* (pre-fix wwstage gave the wrong ptr-compare answer).
* asmrows — import-free single files run straight through w6c /
* w6c_ww for the BYTE-ID headline (w6c compiles one file and
* can't resolve strings.dup, so the import rows can't be
* byte-id'd directly; these import-free str==/!= sources
* exercise the identical cgbin branch and pin cs==ww `.s`).
*
* runrows | want
* -------------------------------------------------+------
* eq_dup_true a=="abc", b=dup("abc"); a==b | 1 (THE bug)
* ne_dup_false a=="abc", b=dup("abc"); a!=b | 0 (!= twin)
* diff_content "abc"=="abd" | 0 (control)
* eq_self a="x"; a==a | 1 (same ptr)
* empty a="", b=dup(""); a==b | 1 (len-0 edge)
*/
#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; };
static const struct row runrows[] = {
/* eq_dup_true — THE regression pin: distinct pointer, equal
* content. Pre-fix wwstage ptr-compared -> 0 (WRONG); cstage
* CALLs rt_streq -> 1. */
{ "eq_dup_true",
"package main;\n"
"import strings;\n"
"export fn main() i32 = {\n"
"\tlet a: str = \"abc\";\n"
"\tlet b: str = strings.dup(\"abc\");\n"
"\tif (a == b) { return 1; };\n"
"\treturn 0;\n"
"};\n",
1 },
/* ne_dup_false — the != twin: distinct pointer, equal content, so
* `!=` is FALSE. Pre-fix wwstage ptr-compared -> != true (WRONG). */
{ "ne_dup_false",
"package main;\n"
"import strings;\n"
"export fn main() i32 = {\n"
"\tlet a: str = \"abc\";\n"
"\tlet b: str = strings.dup(\"abc\");\n"
"\tif (a != b) { return 1; };\n"
"\treturn 0;\n"
"};\n",
0 },
/* diff_content — distinct content; both stages agree (control). */
{ "diff_content",
"package main;\n"
"export fn main() i32 = {\n"
"\tif (\"abc\" == \"abd\") { return 1; };\n"
"\treturn 0;\n"
"};\n",
0 },
/* eq_self — same pointer, equal content; both stages agree. */
{ "eq_self",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: str = \"x\";\n"
"\tif (a == a) { return 1; };\n"
"\treturn 0;\n"
"};\n",
1 },
/* empty — len-0 edge: rt_streq must match two empty strings. */
{ "empty",
"package main;\n"
"import strings;\n"
"export fn main() i32 = {\n"
"\tlet a: str = \"\";\n"
"\tlet b: str = strings.dup(\"\");\n"
"\tif (a == b) { return 1; };\n"
"\treturn 0;\n"
"};\n",
1 },
};
/* Import-free sources for the byte-id pass (w6c compiles a single file
* and can't resolve cross-module strings.dup). Each exercises the cgbin
* str ==/!= branch; the `.s` must be identical cstage vs wwstage. */
static const struct row asmrows[] = {
/* be_eq_local — local str == local str (ident operand load). */
{ "be_eq_local",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: str = \"abc\";\n"
"\tlet b: str = \"abd\";\n"
"\tif (a == b) { return 1; };\n"
"\treturn 0;\n"
"};\n",
0 },
/* be_ne_local — the != twin (pins the XORQ $1, AX). */
{ "be_ne_local",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: str = \"abc\";\n"
"\tlet b: str = \"abd\";\n"
"\tif (a != b) { return 1; };\n"
"\treturn 0;\n"
"};\n",
1 },
/* be_eq_strlit — strlit == strlit (non-ident cgexpr operand). */
{ "be_eq_strlit",
"package main;\n"
"export fn main() i32 = {\n"
"\tif (\"abc\" == \"abc\") { return 1; };\n"
"\treturn 0;\n"
"};\n",
1 },
/* be_eq_global — module-GLOBAL str == global str. This is the
* raison d'etre of the typeisstr gate (vs the spec's nodeisstr,
* whose N_IDENT arm keys on localfindnode and returns false for a
* global, leaving a latent cs!=ww hole). The cgstreqpush global arm
* (LEAQ name(SB)) must byte-match cstage's #154 let_islet branch. */
{ "be_eq_global",
"package main;\n"
"let g: str = \"abc\";\n"
"let h: str = \"abd\";\n"
"export fn main() i32 = {\n"
"\tif (g == h) { return 1; };\n"
"\treturn 0;\n"
"};\n",
0 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/seq_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/seq_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/seq_%d_%d", tmpdir, getpid(), i);
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 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/seq_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/seq_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/seq_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
drivers[] = {
{ "cstage", cdrv, 0 },
{ "wwstage", wdrv, 1 },
{ NULL, NULL, 0 },
};
int nrun = (int)(sizeof runrows / sizeof runrows[0]);
int nasm = (int)(sizeof asmrows / sizeof asmrows[0]);
int total = 0, fail = 0;
for (int d = 0; drivers[d].name; d++) {
if (drivers[d].gated_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "str_eq: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < nrun; i++) {
int got = run_driver(drivers[d].path, &runrows[i], i);
total++;
if (got != runrows[i].want) {
fprintf(stderr,
"str_eq[%s][%s]: exit=%d want=%d\n",
drivers[d].name, runrows[i].label,
got, runrows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < nasm; i++) {
total++;
if (asm_byte_identical(bin, &asmrows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr, "str_eq: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("str_eq: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,312 +0,0 @@
/*
* 828_overlong_arrlit — an OVERLONG array literal (more initialisers than the
* declared [N]) is INVALID ww; BOTH stages must LOUDLY REJECT at check time
* (#12 + #106; rob spec .ai/rob-12-106-spec.md). #9 wired the over-fill at the
* DECL position only; this extends checkarrlitfits to the RETURN + CALL-ARG
* positions and chases a TY_NAMED alias to its underlying [N]T at the top of
* checkarrlitfits, so EVERY caller is alias-aware by construction.
*
* WWSTAGE-ONLY fix — cstage already rejects all four shapes (type_assignable
* counts elements). Pre-fix wwstage divergences (the mutation-sanity targets):
* - #12a RETURN `fn f() [2]int = [1,2,3]` : ww silently ACCEPTED (exit 0)
* - #12b CALL-ARG `g([1,2,3])`, g(a:[2]int) : ww loud-but-LATE via cgen #271
* - #106 alias-global `type A=[2]int; let g:A=[1,2,3]` : ww silently ACCEPTED
* (+ truncated DATA)
* - alias-RETURN `type A=[2]int; fn f() A=[1,2,3]` : proves the alias
* chase covers the return too
*
* The diagnostic TEXT may differ between stages ("over-fill" vs "not
* assignable") — that is byte-id-blind (stderr is not asm); both REJECT and
* emit no asm, so 990-997 byte-id is untouched. Do NOT chase message parity.
*
* neg row | shape | gate
* -----------------+---------------------------------------------+----------
* ret_overlong | fn f() [2]int = [1,2,3] | build FAIL
* arg_overlong | fn g(a:[2]int)..; g([1,2,3]) | build FAIL
* alias_g_over | type A=[2]int; let g:A=[1,2,3] | build FAIL
* alias_ret_over | type A=[2]int; fn f() A = [1,2,3] | build FAIL
*
* pos row | shape | want
* -----------------+---------------------------------------------+------
* ret_exact | fn f() [2]int = [1,2]; f()[1] | 2
* arg_exact | fn g(a:[2]int) int = a[1]; g([10,20]) | 20
* alias_exact | type A=[2]int; let g:A=[1,2]; g[1] | 2
*/
#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; };
static const struct row rows[] = {
{ "ret_exact",
"package main;\n"
"fn f() [2]int = {\n"
"\treturn [1, 2];\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet r: [2]int = f();\n"
"\treturn r[1]: i32;\n"
"};\n",
2 },
/* A bare array-LITERAL fixed-array arg is blocked on BOTH stages by
* cgen #271 (aggregate arg from unsupported source); the supported
* valid form passes via a variable. This control confirms the #12b
* desugarcallargs over-fill wiring (gated to `a.kind == N_ARRLIT`)
* leaves a normal variable call-arg untouched. */
{ "arg_exact",
"package main;\n"
"fn g(a: [2]int) int = {\n"
"\treturn a[1];\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet v: [2]int = [10, 20];\n"
"\treturn g(v): i32;\n"
"};\n",
20 },
{ "alias_exact",
"package main;\n"
"type A = [2]int;\n"
"let g: A = [1, 2];\n"
"export fn main() i32 = {\n"
"\treturn g[1]: i32;\n"
"};\n",
2 },
};
/* An overlong array literal — both stages must FAIL the build (loud checker
* diagnostic, not silent accept / DATA-truncate / late cgen loud). */
static const char *neg[] = {
/* ret_overlong (#12a) */
"package main;\n"
"fn f() [2]int = {\n"
"\treturn [1, 2, 3];\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet r: [2]int = f();\n"
"\treturn r[1]: i32;\n"
"};\n",
/* arg_overlong (#12b) */
"package main;\n"
"fn g(a: [2]int) int = {\n"
"\treturn a[1];\n"
"};\n"
"export fn main() i32 = {\n"
"\treturn g([1, 2, 3]): i32;\n"
"};\n",
/* alias_g_over (#106) */
"package main;\n"
"type A = [2]int;\n"
"let g: A = [1, 2, 3];\n"
"export fn main() i32 = {\n"
"\treturn g[1]: i32;\n"
"};\n",
/* alias_ret_over (alias + return) */
"package main;\n"
"type A = [2]int;\n"
"fn f() A = {\n"
"\treturn [1, 2, 3];\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet r: A = f();\n"
"\treturn r[1]: i32;\n"
"};\n",
};
static int
run_driver(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/oal_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/oal_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/oal_%d_%d", tmpdir, getpid(), i);
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 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* build_should_fail — an overlong array literal must error on `driver`;
* returns 0 when the build correctly FAILS, non-zero when it wrongly
* succeeded. */
static int
build_should_fail(const char *driver, const char *src, int i)
{
char s[128], tmpdir[64], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/oaln_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(s, sizeof s, "%s/oaln_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/oaln_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(s, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(src, f);
fclose(f);
/* explicit -o inside tmpdir so the binary AND <stem>.sepwork both land
* in tmpdir and die with rm -rf (the stem follows the output, not the
* source). */
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
driver, outbin, s);
int rc = runwait(cmd);
runwait(rmcmd);
return rc == 0 ? -1 : 0; /* build must NOT succeed */
}
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/oal_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/oal_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/oal_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
drivers[] = {
{ "cstage", cdrv, 0 },
{ "wwstage", wdrv, 1 },
{ NULL, NULL, 0 },
};
int n = (int)(sizeof rows / sizeof rows[0]);
int nn = (int)(sizeof neg / sizeof neg[0]);
int total = 0, fail = 0;
for (int d = 0; drivers[d].name; d++) {
if (drivers[d].gated_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "overlong_arrlit: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"overlong_arrlit[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
for (int i = 0; i < nn; i++) {
total++;
if (build_should_fail(drivers[d].path, neg[i],
100 + i) != 0) {
fprintf(stderr,
"overlong_arrlit[%s][neg%d]: built ok, "
"expected a loud error\n",
drivers[d].name, i);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"overlong_arrlit: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("overlong_arrlit: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,318 +0,0 @@
/*
* 829_slice_str_global_arg — a module-global SLICE or STR passed BY VALUE to
* a fn (`let s: str = "..."; take(s)`, `let g: []int = [...]; take(g)`) was
* silently miscompiled by wwstage only (task #151, the slice/str twin of the
* #150 struct-global arg):
*
* - cstage (cmd/w6c/cgen.c): node_isslice/node_isstr are TYPE-keyed
* (n->type), so a global slice/str ident still pushes all 3 header words
* {ptr,len,cap} — slice via its dedicated push arm (BX-base per-word,
* cgen.c:9124), str via cgexpr's cgslicehdr (CX-base AX/BX/CX,
* cgen.c:1866) then the node_isstr triple push.
* - wwstage (selfhost cgenutil.ww pushargsrev): nodeisslice/nodeisstr are
* LOCAL-keyed (localfindnode→nil for a global → return false), so a
* global slice/str ident fell past every header arm to the scalar
* single-PUSHQ default — ONE word (ptr), dropping len+cap. The callee
* then read garbage for .len/.cap (silent field-drop).
*
* The fix (ONE wwstage cgen commit, cstage UNTOUCHED → w6c md5 unchanged):
* add a global slice/str arm beside the #150 struct arm in pushargsrev's
* `lc == nil` branch, gated `(isletvar || deflookup)`. cstage emits a
* DIFFERENT per-type sequence, so each arm is hand-authored to MATCH:
* - slice: LEAQ name(SB),BX; MOVQ {16,8,0}(BX),AX; PUSHQ AX (BX-base)
* - str: LEAQ name(SB),CX; MOVQ (CX),AX / 8(CX),BX / 16(CX),CX;
* PUSHQ CX/BX/AX (cgslicehdr CX-base)
* Each `.s` is now byte-IDENTICAL to cstage per type. The LOCAL slice/str
* push path (off!=0) is untouched.
*
* row | shape | want
* ---------------+----------------------------------------------+------
* str_glob_len | let s:str="abcd"; take reads x.len | 4
* str_glob_ptr | let s:str="abcd"; take reads x[0] (uses ptr) | 97
* slice_glob_len | let g:[]int=[10..50]; take reads x.len | 5
* slice_glob_idx | let g:[]int=[10..50]; take reads x[2] | 30
* def_str_len | def s:str="abcd"; take reads x.len (#21) | 4
* def_str_ptr | def s:str="abcd"; take reads x[0] (#21) | 97
* def_str_return | return <def-str>; take reads x.len (#21) | 5
* ctrl_local_str | LOCAL str arg (off!=0, unchanged, byte-id) | 2
* ctrl_local_sl | LOCAL []int arg (off!=0, unchanged, byte-id) | 8
*
* The def_* rows are the #21 residual of this by-value-global family: a
* module-level `def s: str` (NO DATA symbol) passed BY VALUE. Pre-fix wwstage's
* nodeisstr was LOCAL-keyed for an N_IDENT (def→false), so the def-str fell to
* the scalar single-PUSHQ default and dropped len/cap; cstage's node_isstr is
* TYPE-keyed (n->type) and pushes all 3 words. A def rides cgident's const-fold
* (LEAQ _S_n, MOVQ $len) — NOT the let LEAQ name(SB) path — then the now
* type-keyed nodeisstr triple push. def_str_return is a SIBLING guard (a
* def-str flowed through a fn return, then pushed via nodeisstr's pre-existing
* N_CALL arm) — byte-id-identical pre/post-fix, no #21 teeth of its own.
*
* The *_glob_* rows are mutation-sane: pre-fix wwstage drops len/cap and the
* callee reads garbage; cstage is correct. ctrl_* guard the unchanged local
* path. A byte-id row pins cstage==wwstage `.s` for each source.
*
* NOTE — slice element type is []int, not []u8: a `let g: []u8 = [intlits]`
* module-global is rejected by the wwstage checker ("let: not assignable",
* the untyped-int→u8 array-lit coercion) while cstage accepts it — a
* SEPARATE pre-existing checker divergence (#10/#125 batch, "bare []u8
* cs≠ww"), orthogonal to this caller-side push fix. []int exercises the
* identical TY_SLICE push arm. The str rows cover the []u8-header shape
* (str IS []u8), including a byte read via x[0].
*/
#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; };
static const struct row rows[] = {
/* str_glob_len — THE regression pin (str header): global str by value,
* callee reads .len. Pre-fix wwstage drops len → garbage. */
{ "str_glob_len",
"package main;\n"
"let s: str = \"abcd\";\n"
"fn take(x: str) int = { return x.len: int; };\n"
"export fn main() i32 = { return take(s): i32; };\n",
4 },
/* str_glob_ptr — global str by value, callee reads x[0] (proves the
* ptr word survived the push). 'a' == 97. */
{ "str_glob_ptr",
"package main;\n"
"let s: str = \"abcd\";\n"
"fn take(x: str) int = { return x[0]: int; };\n"
"export fn main() i32 = { return take(s): i32; };\n",
97 },
/* slice_glob_len — global slice by value, callee reads .len. Pre-fix
* wwstage drops len/cap → garbage; the TY_SLICE BX-base arm. */
{ "slice_glob_len",
"package main;\n"
"let g: []int = [10, 20, 30, 40, 50];\n"
"fn take(x: []int) int = { return x.len: int; };\n"
"export fn main() i32 = { return take(g): i32; };\n",
5 },
/* slice_glob_idx — global slice by value, callee reads x[2] (proves
* ptr survived the push). */
{ "slice_glob_idx",
"package main;\n"
"let g: []int = [10, 20, 30, 40, 50];\n"
"fn take(x: []int) int = { return x[2]; };\n"
"export fn main() i32 = { return take(g): i32; };\n",
30 },
/* def_str_len — THE #21 regression pin: a module-level `def s: str`
* passed BY VALUE, callee reads x.len. Pre-fix wwstage drops len/cap
* (scalar single-PUSHQ default); cstage type-keyed pushes all 3 words.
* A def has NO DATA symbol — it rides cgident's const-fold (LEAQ _S_x,
* MOVQ $len BX, $len CX), then the type-keyed str triple push. */
{ "def_str_len",
"package main;\n"
"def s: str = \"abcd\";\n"
"fn take(x: str) int = { return x.len: int; };\n"
"export fn main() i32 = { return take(s): i32; };\n",
4 },
/* def_str_ptr — def-str by value, callee reads x[0] (proves the ptr
* word survived the push). 'a' == 97. */
{ "def_str_ptr",
"package main;\n"
"def s: str = \"abcd\";\n"
"fn take(x: str) int = { return x[0]: int; };\n"
"export fn main() i32 = { return take(s): i32; };\n",
97 },
/* def_str_return — a def-str flowed through a fn return: give()'s
* `return s` leaves the const-folded str header (AX=ptr/BX=len/CX=cap),
* and the outer `take(give())` arg rides nodeisstr's pre-existing N_CALL
* arm (give returns str) for the 3-word push. A #21 SIBLING, not the
* N_IDENT def arm — it is byte-id-IDENTICAL pre- and post-fix (no teeth
* of its own), kept as a convergence guard that a def-str surviving a fn
* return stays a full str header on both stages. */
{ "def_str_return",
"package main;\n"
"def s: str = \"hello\";\n"
"fn give() str = { return s; };\n"
"fn take(x: str) int = { return x.len: int; };\n"
"export fn main() i32 = { return take(give()): i32; };\n",
5 },
/* ctrl_local_str — a LOCAL str arg (off!=0). cgen unchanged; pins the
* local-str push path stays byte-id (no regress). */
{ "ctrl_local_str",
"package main;\n"
"fn take(x: str) int = { return x.len: int; };\n"
"export fn main() i32 = {\n"
"\tlet s: str = \"ab\";\n"
"\treturn take(s): i32;\n"
"};\n",
2 },
/* ctrl_local_sl — a LOCAL []int arg (off!=0). cgen unchanged; pins the
* local-slice push path stays byte-id (no regress). */
{ "ctrl_local_sl",
"package main;\n"
"fn take(x: []int) int = { return x[1]; };\n"
"export fn main() i32 = {\n"
"\tlet g: []int = [7, 8, 9];\n"
"\treturn take(g): i32;\n"
"};\n",
8 },
};
static int
run_driver(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/ssg_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/ssg_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/ssg_%d_%d", tmpdir, getpid(), i);
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 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/ssg_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/ssg_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/ssg_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "slice_str_global_arg: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"slice_str_global_arg[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"slice_str_global_arg: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("slice_str_global_arg: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,279 +0,0 @@
/*
* 830_cast_base_index — indexing a DIRECT-CAST base `(e:*[N]T)[i]` must
* stride by the cast element type's size, not the 8B default (task
* #19old, .ai/rob-19old-spec.md). wwstage-only fix; cstage was already
* correct.
*
* The bug: cgindex (selfhost/cmd/wcc/cgenexpr.ww) derived the element
* stride/load-width `esz` only for N_DOT / N_UN-deref / N_INDEX base
* node-kinds (read off the stamped index-result tinfo n.type_). An
* N_CAST base matched NO arm, so esz stayed at the 8B default — for a
* narrow element (u32/u16/u8) the stride and load width were both
* wrong and the read returned 0/garbage. cstage reads it uniformly via
* idx_eff(base->type)->sub->size (cmd/w6c/cgen.c N_INDEX); the fix adds
* `|| base.kind == nkind.N_CAST` to the stamped-tinfo esz arm, which
* fixes stride + load width + signedness together (all read from dt).
*
* row | shape | stride | want
* ---------+-------------------------------+--------+------
* u32_idx | [4]u32, ((&a):*[4]u32)[i] | 4 | 30
* u16_idx | [4]u16, ((&a):*[4]u16)[i] | 2 | 30
* u8_idx | [4]u8, ((&a):*[4]u8)[i] | 1 | 40
* u64_ctl | [4]u64, ((&a):*[4]u64)[i] | 8 | 20 (control)
*
* The whitelist of base node-kinds was whack-a-mole (#19old residuals):
* N_CALL, N_SLICE and N_TYPEASSERT bases ALSO fell to the 8B default. The
* fix routes every non-ident, non-N_INDEX base through the stamped-n.type_
* arm (mirrors cstage's uniform idx_eff, no node-kind gate). These rows
* pin the residual base kinds:
*
* call_idx | mk(&a)[i] (N_CALL *[4]u32) | 4 | 30
* slc_idx | a[0:4][i] (N_SLICE []u16) | 2 | 30
* asrt_idx | (v as *[4]u16)[i] (N_TYPEASSERT) | 2 | 30
*
* The <8B rows are the mutation-sane rows: pre-fix wwstage strides by 8
* and reads 0/garbage. u64_ctl pins no-regress for the already-correct
* 8B-stride case. A byte-id row pins cstage==wwstage `.s` for the
* cast-base index source (the convergence the fix delivers).
*/
#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; };
static const struct row rows[] = {
/* u32_idx — stride 4 / MOVL. THE bug: pre-fix wwstage strides by
* 8 and reads 0. */
{ "u32_idx",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: [4]u32 = [10u32, 20u32, 30u32, 40u32];\n"
"\tlet i: int = 2;\n"
"\treturn ((&a): *[4]u32)[i]: i32;\n"
"};\n",
30 },
/* u16_idx — stride 2 / MOVW. */
{ "u16_idx",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: [4]u16 = [10u16, 20u16, 30u16, 40u16];\n"
"\tlet i: int = 2;\n"
"\treturn ((&a): *[4]u16)[i]: i32;\n"
"};\n",
30 },
/* u8_idx — stride 1 / MOVB. */
{ "u8_idx",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: [4]u8 = [10u8, 20u8, 30u8, 40u8];\n"
"\tlet i: int = 3;\n"
"\treturn ((&a): *[4]u8)[i]: i32;\n"
"};\n",
40 },
/* u64_ctl — stride 8 control: already correct (matches the 8B
* default), guards no-regress for the wide-element cast base. */
{ "u64_ctl",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: [4]u64 = [10u64, 20u64, 30u64, 40u64];\n"
"\tlet i: int = 1;\n"
"\treturn ((&a): *[4]u64)[i]: i32;\n"
"};\n",
20 },
/* call_idx — N_CALL base mk(&a)[i], stride 4 / MOVL. #19old residual:
* a call-returning-pointer base also fell to the 8B default. */
{ "call_idx",
"package main;\n"
"fn mk(p: *[4]u32) *[4]u32 = { return p; };\n"
"export fn main() i32 = {\n"
"\tlet a: [4]u32 = [10u32, 20u32, 30u32, 40u32];\n"
"\tlet i: int = 2;\n"
"\treturn mk(&a)[i]: i32;\n"
"};\n",
30 },
/* slc_idx — N_SLICE base a[0:4][i], stride 2 / MOVZWQ. #19old
* residual: a slice-expression base also fell to the 8B default. */
{ "slc_idx",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet a: [4]u16 = [10u16, 20u16, 30u16, 40u16];\n"
"\tlet i: int = 2;\n"
"\treturn a[0:4][i]: i32;\n"
"};\n",
30 },
/* asrt_idx — N_TYPEASSERT base (v as *[4]u16)[i], stride 2 / MOVZWQ.
* #19old residual: a type-assert base also fell to the 8B default. */
{ "asrt_idx",
"package main;\n"
"type IP = (*[4]u16 | int);\n"
"export fn main() i32 = {\n"
"\tlet a: [4]u16 = [10u16, 20u16, 30u16, 40u16];\n"
"\tlet v: IP = &a;\n"
"\tlet i: int = 2;\n"
"\treturn (v as *[4]u16)[i]: i32;\n"
"};\n",
30 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/cbi_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/cbi_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/cbi_%d_%d", tmpdir, getpid(), i);
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 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/cbi_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/cbi_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/cbi_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "cast_base_index: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"cast_base_index[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"cast_base_index: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("cast_base_index: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,294 +0,0 @@
/*
* 831_match_field_inplace — match on a tagged-union struct-FIELD
* scrutinee. wwstage cgmatch reads an addressable BP-relative field
* (`match (b.t)`, b a local VALUE struct) IN PLACE at base.off +
* field.offset, dispatching off the field's own slot — no @match_spill
* copy. Verbatim mirror of cstage N_MATCH's in-place arm
* (cmd/w6c/cgen.c:10241-10296). Task #25 (M1).
*
* Pre-fix (wwstage): the non-ident cgmatch branch unconditionally
* spilled ANY non-ident scrutinee — including an addressable field —
* into @match_spill (+16B scratch → frame TEXT $48). cstage read the
* field where it lives (TEXT $32). Both stages were runtime-correct
* (ken's battery), so this was a latent rule-10 leanness gap, not a
* miscompile; the fix aligns wwstage DOWN to cstage so both emit
* byte-identical asm.
*
* Predicate (copied from cstage): in-place iff the scrutinee is an
* N_DOT whose lhs is a BARE N_IDENT with a stamped type, that type
* chases to TY_STRUCT, and the field resolves by name. Everything else
* — a *ptr-field base (`match (h.t)`, h:*struct, chases to TY_PTR so
* the by-name scan misses), a plain-local tagged ident, a call-result
* scrutinee — keeps its existing (in-place-ident / spill) path,
* unchanged.
*
* What this table pins, per row, across BOTH driver stages plus a
* cstage-vs-wwstage asm byte-identity check:
* (a) local VALUE-struct tagged field [NEW in-place arm]
* (b) *ptr-struct tagged field [stays on spill — TY_PTR]
* (c) plain-local tagged ident [unchanged in-place-ident]
* (d) call-result tagged scrutinee [spill]
* (e) bool->int payload remap on a field [width: 1-word variants]
* (f) str-payload (24B box) on a field [width: 3-word variant]
*
* The boundary, not just the new arm, is the subject: a regression
* that mis-routed (b)/(c)/(d) into the in-place arm, or failed to route
* (a)/(e)/(f) there, would break either the runtime exit code or the
* byte-identity. The $48->$32 frame convergence itself is ken's
* authoritative byte-id bind; a runtime test cannot observe frame size.
*/
#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; };
static const struct row rows[] = {
/* (a) NEW in-place arm: local VALUE struct, field reassigned to
* the int variant, matched in place. Returns 42. */
{ "local_field_int_inplace",
"package main;\n"
"type box = struct { t: (int | bool), n: int };\n"
"fn main() i32 = {\n"
" let b: box = box { t = 0, n = 5 };\n"
" b.t = 42;\n"
" match (b.t) {\n"
" case let n: int => return n: i32;\n"
" case bool => return 1;\n"
" };\n"
"};\n",
42 },
/* (b) *ptr-struct field: base chases to TY_PTR, the by-name scan
* misses, scrutinee falls to the spill path — exactly as cstage,
* no separate deref guard. Returns 9. */
{ "ptr_field_stays_spill",
"package main;\n"
"type box = struct { t: (int | bool), n: int };\n"
"fn pick(h: *box) i32 = {\n"
" match (h.t) {\n"
" case let n: int => return n: i32;\n"
" case bool => return 1;\n"
" };\n"
"};\n"
"fn main() i32 = {\n"
" let b: box = box { t = 9, n = 5 };\n"
" return pick(&b);\n"
"};\n",
9 },
/* (c) plain-local tagged ident: unchanged in-place-ident path
* (not N_DOT). Returns 5. */
{ "plain_local_ident",
"package main;\n"
"fn main() i32 = {\n"
" let t: (int | bool) = 5;\n"
" match (t) {\n"
" case let n: int => return n: i32;\n"
" case bool => return 1;\n"
" };\n"
"};\n",
5 },
/* (d) call-result tagged scrutinee: not N_DOT, keeps the spill
* path. Returns 7. */
{ "call_result_spill",
"package main;\n"
"fn mk(b: bool) (int | bool) = {\n"
" if (b) { return 7; };\n"
" return false;\n"
"};\n"
"fn main() i32 = {\n"
" match (mk(true)) {\n"
" case let n: int => return n: i32;\n"
" case bool => return 1;\n"
" };\n"
"};\n",
7 },
/* (e) width: a field whose variant remaps from bool to int. The
* in-place arm reads tag + one payload word at the field slot;
* the int arm also reads a sibling field (b.n) to prove the
* surrounding struct frame is undisturbed. Returns 7 + 5 = 12. */
{ "field_bool_to_int_remap",
"package main;\n"
"type box = struct { t: (int | bool), n: int };\n"
"fn main() i32 = {\n"
" let b: box = box { t = true, n = 5 };\n"
" b.t = 7;\n"
" match (b.t) {\n"
" case let n: int => return (n + b.n): i32;\n"
" case bool => return 1;\n"
" };\n"
"};\n",
12 },
/* (f) width: a 24B str-payload variant read in place at the
* field slot (tag@+0, .ptr@+8, .len@+16). Returns len("hello")
* = 5. */
{ "field_str_payload_24B",
"package main;\n"
"type box = struct { t: (str | int), n: int };\n"
"fn main() i32 = {\n"
" let b: box = box { t = 0, n = 5 };\n"
" b.t = \"hello\";\n"
" match (b.t) {\n"
" case let s: str => return len(s): i32;\n"
" case int => return 99;\n"
" };\n"
"};\n",
5 },
};
static int
run_driver(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/wcmfi_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/wcmfi_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wcmfi_%d_%d", tmpdir, getpid(), i);
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", driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — the whole point of M1 is that wwstage's frame
* stops bloating for an addressable field scrutinee, so the cstage vs
* wwstage text output must match byte-for-byte on every row. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/wcmfi_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/wcmfi_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/wcmfi_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[512];
if (bin[0] != '/') {
char cwd[256];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[640];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[640];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "match_field_inplace: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"match_field_inplace[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"match_field_inplace: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("match_field_inplace: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,238 +0,0 @@
/*
* 833_inferred_array_global — an inferred-type (annotation-less) module-global
* `let xs = [10, 20, 30];` whose init is an ARRAY literal must compile and run
* exactly as the explicit-typed `let xs: [3]int = [10, 20, 30]` does. The
* array twin of #135/#150-B (inferred float / inferred scalar global).
* WWSTAGE-ONLY bug (cstage already correct post-#150-B).
*
* Root (selfhost/cmd/wcc/check.ww exprtype N_ARRLIT, ~3294): for an inferred
* array let, exprtype synthesizes the array type — an N_TARRAY whose lhs is
* the element TNAME and whose rhs is a fresh N_INTLIT count node — and stamps
* e.type_ and arr.type_, but NEVER the count node's type_. checkletassign
* plants this synthesized arr on the inferred let's n.lhs; the pass-3
* asserttyped walker then descends arr.rhs (the count N_INTLIT, an expr kind)
* with type_ == nil and trips `asserttyped: int` — a HARD loud build failure.
* wwstage could not compile an inferred array global at all. An explicit
* `[3]int` works because resolvewalk stamps its parser-built count node.
*
* The fix (check.ww, checker-only): stamp the synthesized count node's type_.
* The element TNAME needs no stamp (N_TNAME is not an asserttyped expr kind).
* cgen reads arr.rhs.uval, so the stamp is byte-id-inert: once the checker
* accepts the inferred array identically to an explicit one, the existing
* [N]T-global cgen path emits the IDENTICAL `.s`. cstage is UNTOUCHED.
*
* row | shape | want
* -----------+---------------------------------------------+-----
* int_idx | let xs = [10,20,30]; xs[1] (the bug) | 20
* int_len | let xs = [10,20,30]; xs.len | 3
* u8_elem | let bs = [1u8,2u8,3u8]; bs[2] (narrow elem) | 3
* ctrl_expl | let xs: [3]int = [10,20,30]; xs[1] (explicit)| 20
*
* Pre-fix the inferred rows ran wwstage=LOUD (asserttyped exit 1) and
* cstage=correct (cs≠ww); post-fix all four run in both stages and the
* inferred rows' `.s` is byte-identical between stages (and to the explicit
* form). ctrl_expl guards the explicit-[N]T path didn't regress and is the
* byte-id reference the inferred rows must match.
*/
#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; };
static const struct row rows[] = {
/* int_idx — the bug: inferred array global indexed, cast to int.
* Pre-fix wwstage tripped asserttyped (exit 1); cstage 20. */
{ "int_idx",
"package main;\n"
"let xs = [10, 20, 30];\n"
"export fn main() i32 = {\n"
"\treturn xs[1]: i32;\n"
"};\n",
20 },
/* int_len — inferred array global .len pseudo-field read. */
{ "int_len",
"package main;\n"
"let xs = [10, 20, 30];\n"
"export fn main() i32 = {\n"
"\treturn xs.len: i32;\n"
"};\n",
3 },
/* u8_elem — narrow (u8-default) element; the synthesized element
* type defaults to u8 here, exercising a non-int stride. */
{ "u8_elem",
"package main;\n"
"let bs = [1u8, 2u8, 3u8];\n"
"export fn main() i32 = {\n"
"\treturn bs[2]: i32;\n"
"};\n",
3 },
/* ctrl_expl — explicit [3]int annotation; already worked. The
* byte-id reference the inferred int rows must match. No-regress. */
{ "ctrl_expl",
"package main;\n"
"let xs: [3]int = [10, 20, 30];\n"
"export fn main() i32 = {\n"
"\treturn xs[1]: i32;\n"
"};\n",
20 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/iag_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/iag_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/iag_%d_%d", tmpdir, getpid(), i);
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 2>/dev/null",
driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/iag_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/iag_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/iag_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "inferred_array_global: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"inferred_array_global[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"inferred_array_global: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("inferred_array_global: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,260 +0,0 @@
/*
* 841_match_global_field — match on a tagged-union struct-FIELD of a
* GLOBAL (module-level) VALUE struct: `match (g.t)`, g a `let g: box`.
* Task #29.
*
* GATE-BLIND both-wrong regression from M1 (#25). M1 added an in-place
* N_DOT match arm that points the dispatch slot at base.off +
* field.offset, computing base.off via localfind. For a LOCAL base that
* is the field's true frame slot ($32 in-place, correct). For a GLOBAL
* base localfind returns its 0/not-found sentinel, so 0 + field.offset
* lands in the saved-BP / return-addr region (BP+0/BP+8): both stages
* emit `MOVQ (BP),AX` and read garbage. Both stages were identical-wrong
* (byte-id GREEN), so only the RUNTIME oracle catches it — pre-fix
* match(g.t) returns 213 instead of 42.
*
* Fix (both stages, rule 10): gate the in-place arm on a confirmed-LOCAL
* base. A base is GLOBAL iff localfind(base)==0 && let_islet(base)
* (cstage) / isletvar(c, base) (wwstage) — the same idiom cstage uses at
* cgen.c:2000 (also 4707/4730/5393). A global base falls THROUGH to the
* existing spill `else`, which cgexprs the scrutinee and resolves g(SB)
* correctly. M1's LOCAL in-place path is unchanged (row (d) pins it).
*
* Rows pin the RUNTIME exit code on BOTH driver stages (cstage +
* wwstage) — the discriminator is the value, not byte-id, since both
* stages were identical-wrong. A cstage-vs-wwstage asm byte-identity
* check is kept per row so the symmetric (rule-10) property stays pinned
* after the guard lands in both stages.
* (a) global VALUE-struct int-variant field [spill, g(SB)]
* (b) global field bool->int payload remap [1-word variant]
* (c) global field str-payload (24B box) [3-word variant]
* (d) local VALUE-struct field, in place [M1 regression guard]
*/
#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; };
static const struct row rows[] = {
/* (a) GLOBAL value struct, field set to the int variant in main,
* matched. Pre-fix the in-place arm read BP+0 (213); post-fix the
* spill path resolves g(SB) and returns 42. */
{ "global_field_int",
"package main;\n"
"type box = struct { t: (int | bool), n: int };\n"
"let g: box = box { t = 0, n = 5 };\n"
"fn main() i32 = {\n"
" g.t = 42;\n"
" match (g.t) {\n"
" case let n: int => return n: i32;\n"
" case bool => return 1;\n"
" };\n"
"};\n",
42 },
/* (b) GLOBAL field reassigned, then matched; the int arm also reads
* the sibling field g.n to prove the surrounding global is
* undisturbed. Returns 7 + 5 = 12. The static initializer uses the
* ZERO payload (t = 0): a nonzero tagged-field payload in a static
* struct initializer trips a SEPARATE, deferred static-init DATA
* divergence (task #19/#30 — wwstage drops the payload word to 0,
* cstage emits it) that is orthogonal to the #29 match-cgen path
* exercised here; a zero payload is byte-identical across stages. */
{ "global_field_reassign_remap",
"package main;\n"
"type box = struct { t: (int | bool), n: int };\n"
"let g: box = box { t = 0, n = 5 };\n"
"fn main() i32 = {\n"
" g.t = 7;\n"
" match (g.t) {\n"
" case let n: int => return (n + g.n): i32;\n"
" case bool => return 1;\n"
" };\n"
"};\n",
12 },
/* (c) GLOBAL field, 24B str-payload variant (tag@+0, .ptr@+8,
* .len@+16). Returns len("hello") = 5. */
{ "global_field_str_payload_24B",
"package main;\n"
"type box = struct { t: (str | int), n: int };\n"
"let g: box = box { t = 0, n = 5 };\n"
"fn main() i32 = {\n"
" g.t = \"hello\";\n"
" match (g.t) {\n"
" case let s: str => return len(s): i32;\n"
" case int => return 99;\n"
" };\n"
"};\n",
5 },
/* (d) regression guard: a LOCAL value-struct field still reads in
* place (M1's $32 path stays). Returns 42. */
{ "local_field_inplace_guard",
"package main;\n"
"type box = struct { t: (int | bool), n: int };\n"
"fn main() i32 = {\n"
" let b: box = box { t = 0, n = 5 };\n"
" b.t = 42;\n"
" match (b.t) {\n"
" case let n: int => return n: i32;\n"
" case bool => return 1;\n"
" };\n"
"};\n",
42 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wcmgf_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/wcmgf_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wcmgf_%d_%d", tmpdir, getpid(), i);
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", driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return got;
}
/* asm_byte_identical — the guard lands in BOTH stages, so cstage vs
* wwstage text output must stay byte-for-byte identical on every row. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/wcmgf_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/wcmgf_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/wcmgf_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[512];
if (bin[0] != '/') {
char cwd[256];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[640];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[640];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
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_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "match_global_field: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"match_global_field[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr,
"match_global_field: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("match_global_field: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,360 +0,0 @@
/*
* 843_tagged_staticinit — #19 option A (rob's EXTRACT ruling). A tagged-
* union VALUE nested in module-level static-init DATA (an array element or
* a struct field) now emits the correct (tag@+0, payload@+8) box, the same
* SSoT a SCALAR tagged global and a runtime LOCAL box use. Pre-fix the
* nested member fell to the INT emitter, which mis-folded the payload into
* the tag word — cstage (value, value), wwstage (value, 0); NEITHER is
* (tag, value). Both BUILT and ran WRONG.
*
* The fix extracts a shared raw-byte core emit_tagged_bytes / emittaggedbytes
* (tag@0, int payload@8, zero-pad) from the scalar emitter, and the array/
* struct member emitters call it at the full slot stride. A WIDE (str/slice)
* payload nested in an aggregate needs a reloc the raw core cannot place
* mid-directive, and a struct/non-foldable payload has no scalar form —
* both LOUD-REJECT (deferred, task #30; mirror of the slice-of-tagged
* reject). Only zero + int payload is implemented, which is exactly what
* the corpus exercises.
*
* row | shape | kind
* ---------------------+----------------------------------------+------
* array_nonzero | let gs:[2](int|bool)=[42,7]; gs[0] | RUN 42
* array_bool_variant | let gs:[2](int|bool)=[7,true]; gs[1] | RUN 2
* | (NON-tag-0: tag=1,payload=1 — pre-fix |
* | (1,0) read bool=false -> 3) |
* struct_nonzero | let g:box{t:(int|bool)}=box{t=55};g.t | RUN 55
* array_zero_then_set | let gs:[2](int|bool)=[0,0]; gs[0]=42 | RUN 42
* | (zero-placeholder idiom — byte-id- |
* | neutral build, mirrors 841/989) |
* struct_zero_then_set | let g:box{t=0}; g.t=42 | RUN 42
* plain_struct_ok | non-tagged struct static-init | RUN 9
* wide_array_str | let gs:[2](int|str)=["hi",0] | REJECT
* wide_struct_str | let g:sbox{s:(int|str)}=sbox{s="hi"} | REJECT
*
* DISCRIMINATION: pre-fix array_nonzero BUILT and match(gs[0]) returned 1
* (the match fallthrough — the payload 42 sat in the TAG word, matching no
* variant); post-fix it returns 42. array_bool_variant pins the NON-tag-0
* edge (tag != payload word): pre-fix (1,0) read the bool payload as 0
* (false) -> 3; post-fix (1,1) -> true -> 2. RUN rows also assert cstage/
* wwstage asm byte-id (rule 10). The reject diagnostic core text is shared
* across stages (cstage adds the harness "ww: " prefix). NNN<950, self-
* contained (/tmp, no imports).
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.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 expect_build; /* 1 = build+run to want_exit; 0 = must REJECT */
int want_exit;
const char *diag; /* reject core substring (NULL for RUN rows) */
};
static const struct row rows[] = {
/* RUN — non-zero int payload in an array element: now correct. */
{ "array_nonzero",
"package main;\n"
"let gs: [2](int | bool) = [42, 7];\n"
"export fn main() int = {\n"
"\tmatch (gs[0]) {\n"
"\tcase let n: int => return n;\n"
"\tcase bool => return 99;\n"
"\t};\n"
"\treturn 1;\n"
"};\n",
1, 42, NULL },
/* RUN — NON-tag-0 edge: a bool element (variant index 1, payload 1).
* Pre-fix the int-fold emitted (1, 0) so the bool payload read as 0
* (false) -> 3; post-fix (1, 1) -> true -> 2. */
{ "array_bool_variant",
"package main;\n"
"let gs: [2](int | bool) = [7, true];\n"
"export fn main() int = {\n"
"\tmatch (gs[1]) {\n"
"\tcase let n: int => return 1;\n"
"\tcase let b: bool => { if (b) { return 2; }; return 3; };\n"
"\t};\n"
"\treturn 4;\n"
"};\n",
1, 2, NULL },
/* RUN — non-zero int payload in a struct field: now correct. */
{ "struct_nonzero",
"package main;\n"
"type box = struct { t: (int | bool), n: int };\n"
"let g: box = box { t = 55, n = 5 };\n"
"fn main() i32 = {\n"
"\tmatch (g.t) {\n"
"\tcase let v: int => return v: i32;\n"
"\tcase bool => return 7;\n"
"\t};\n"
"\treturn 1;\n"
"};\n",
1, 55, NULL },
/* RUN — zero-placeholder array idiom (mirror 989_taggedglobalindex):
* static-init [0,0] is byte-id-neutral, runtime write then matches. */
{ "array_zero_then_set",
"package main;\n"
"let gs: [2](int | bool) = [0, 0];\n"
"export fn main() int = {\n"
"\tgs[0] = 42;\n"
"\tmatch (gs[0]) {\n"
"\tcase let n: int => return n;\n"
"\tcase bool => return 9;\n"
"\t};\n"
"\treturn 1;\n"
"};\n",
1, 42, NULL },
/* RUN — zero-placeholder struct idiom (mirror 841): {t=0} byte-id-
* neutral, runtime write then matches. */
{ "struct_zero_then_set",
"package main;\n"
"type box = struct { t: (int | bool), n: int };\n"
"let g: box = box { t = 0, n = 5 };\n"
"fn main() i32 = {\n"
"\tg.t = 42;\n"
"\tmatch (g.t) {\n"
"\tcase let v: int => return v: i32;\n"
"\tcase bool => return 7;\n"
"\t};\n"
"\treturn 1;\n"
"};\n",
1, 42, NULL },
/* RUN — non-tagged struct static-init still emits + runs. 9. */
{ "plain_struct_ok",
"package main;\n"
"type pt = struct { x: int, y: int };\n"
"let p: pt = pt { x = 5, y = 9 };\n"
"export fn main() int = {\n"
"\treturn p.y;\n"
"};\n",
1, 9, NULL },
/* REJECT — wide (str) payload tagged element in an array static-init:
* the reloc-in-aggregate case is deferred (task #30). */
{ "wide_array_str",
"package main;\n"
"let gs: [2](int | str) = [\"hi\", 0];\n"
"export fn main() int = {\n"
"\treturn 0;\n"
"};\n",
0, 0,
"tagged-union array element static-init needs a zero/int payload" },
/* REJECT — wide (str) payload tagged field in a struct static-init. */
{ "wide_struct_str",
"package main;\n"
"type sbox = struct { s: (int | str) };\n"
"let g: sbox = sbox { s = \"hi\" };\n"
"export fn main() int = {\n"
"\treturn 0;\n"
"};\n",
0, 0,
"tagged-union struct-field" },
};
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/tsi_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/tsi_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/tsi_%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;
}
/* A reject row must (a) fail to build and (b) emit the shared diagnostic
* core text. Returns 0 on the expected reject, -1 otherwise. */
static int
build_should_reject(const char *driver, const struct row *r, int i)
{
char s[96], tmpdir[64], outbin[128], errf[112], rmcmd[160], cmd[1280];
snprintf(tmpdir, sizeof tmpdir, "/tmp/tsn_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(s, sizeof s, "%s/tsn_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/tsn_%d_%d", tmpdir, getpid(), i);
snprintf(errf, sizeof errf, "%s/tsn_%d_%d.err", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(s, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>%s",
driver, outbin, s, errf);
int rc = runwait(cmd);
int have_diag = 0;
FILE *e = fopen(errf, "rb");
if (e) {
char buf[4096];
size_t n = fread(buf, 1, sizeof buf - 1, e);
buf[n] = '\0';
fclose(e);
have_diag = (strstr(buf, r->diag) != NULL);
}
runwait(rmcmd);
/* build must NOT succeed AND the shared diagnostic must appear. */
return (rc != 0 && have_diag) ? 0 : -1;
}
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[64], cs[64], ws[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/tsi_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/tsi_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/tsi_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;
}
FILE *fc = fopen(cs, "rb");
FILE *fw = fopen(ws, "rb");
int rc = 0;
if (!fc || !fw) {
rc = -1;
} else {
for (;;) {
int a = fgetc(fc);
int b = fgetc(fw);
if (a != b) { rc = -1; break; }
if (a == EOF) break;
}
}
if (fc) fclose(fc);
if (fw) fclose(fw);
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[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, "tagged_staticinit: skip %s (no %s)\n",
drivers[d].name, drivers[d].drv);
continue;
}
for (int i = 0; i < n; i++) {
total++;
if (rows[i].expect_build) {
int got = run_build(drivers[d].drv, &rows[i], i);
if (got != rows[i].want_exit) {
fprintf(stderr, "tagged_staticinit[%s][%s]: "
"exit=%d want=%d\n", drivers[d].name,
rows[i].label, got, rows[i].want_exit);
fail++;
}
} else {
if (build_should_reject(drivers[d].drv, &rows[i],
100 + i) != 0) {
fprintf(stderr, "tagged_staticinit[%s][%s]: "
"expected a loud reject with \"%s\"\n",
drivers[d].name, rows[i].label, rows[i].diag);
fail++;
}
}
}
}
/* RUN rows must be cstage/wwstage asm byte-id (rule 10). */
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
if (!rows[i].expect_build) continue;
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0)
fail++;
}
}
if (fail) {
fprintf(stderr, "tagged_staticinit: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("tagged_staticinit: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,207 +0,0 @@
/*
* 913_def_mangle_run — byte-id regression net for #127: top-level
* `def X: T = LITERAL;` must emit cs==ww identical DATA symbols. Pre-
* #127 the wwstage `emitdefconstants` (selfhost/cmd/wcc/cgen.ww:1483)
* used a separate `d.exported`/`d.nmod` gate at the DATA-emit site
* instead of the same `emitsymname` mangler used at LOAD/CALL sites.
* When a non-exported `def X` in package main collided with the
* imported os.PATH_MAX (whose exported def landed too via the
* combined.ww embedding), the wwstage's DATA emitted the os one
* unqualified (`PATH_MAX(SB)`) while cstage emitted it module-
* qualified (`main.PATH_MAX(SB)`) — a 1-line cs/ww divergence in
* selfhost/cmd/ww/main.combined.ww that the 990-997 corpus didn't
* include.
*
* Fix (rule-12 sea-of-stars consolidation): replace the 8 lines of
* duplicated d.exported/d.nmod logic in emitdefconstants with a
* single `emitsymname(c, d.str)` call — the SAME mangler used at
* every LOAD/CALL site. Removes the asymmetry by construction:
* DATA-emit and LOAD-emit now route through one path. Cstage twin
* cmd/w6c/cgen.c:8494 already uses `mod_mangle(c, d->str)` for
* exactly this purpose.
*
* The original PATH_MAX consumer was cleaned up in 90d31c5 (drew's
* task #32 commit-1 dropped the duplicate main-local def in favor
* of the imported os.PATH_MAX), so the divergence is bootstrap-
* NEUTRAL post-#127 — confirmed by all 5 tool combined.ww emitting
* cs==ww byte-identical asm. This probe pins the consolidation
* forward: a new caller introducing a collision would now produce
* the same symbol shape from both stages, by construction.
*
* Rows exercise:
* - exported_def: `export def X: i32 = 4096;` — verifies the
* unqualified emit path (modlookup returns empty for exported).
* - main_local_def: `def X: i32 = 4096;` in package main — verifies
* the qualified emit path (modlookup returns "main"; both stages
* emit `main.X(SB)`).
* - load_site_match: a function reading the def — asserts the
* LOAD-site symbol shape matches the DATA-emit symbol shape (the
* core consolidation invariant — both routes through emitsymname).
*
* Each row carries (a) cstage `ww build` + run asserting the 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[] = {
/* Exported i64 def: emitsymname → modlookup returns empty
* (exported defs aren't tracked in mod_map per collectmods's
* `if (d.exported == 0)` gate) → unqualified `X(SB)` in DATA.
* i64 type chosen to dodge a separate pre-existing wwstage
* cgident divergence on narrow signed defs (extra MOVSXD on
* LOAD); the DEF-emit invariant this row pins is the DATA-side
* symbol shape, byte-id with cstage. */
{ "exported_def_i64",
"package main;\n"
"export def X: i64 = 100i64;\n"
"export fn main() i32 = { return X: i32; };\n", 100 },
/* Non-exported (package-private) i64 def: modlookup returns
* "main" → `main.X(SB)` qualified in DATA. LOAD site (cgident
* via emitsymname) also produces `main.X(SB)` — consolidation
* invariant: DATA and LOAD agree by construction. */
{ "main_local_def_i64",
"package main;\n"
"def X: i64 = 77i64;\n"
"export fn main() i32 = { return X: i32; };\n", 77 },
/* u64 width — pin that emitsymname doesn't differ on width. */
{ "u64_def",
"package main;\n"
"def X: u64 = 200u64;\n"
"export fn main() i32 = { return X: i32; };\n", 200 },
/* Multiple defs at package level — pin emitsymname is consistent
* across multiple DATA-emit calls (the surrounding loop walks
* file.list; per-def state should not leak). */
{ "multi_def",
"package main;\n"
"def A: u64 = 10u64;\n"
"def B: u64 = 30u64;\n"
"export def C: u64 = 60u64;\n"
"export fn main() i32 = { return (A + B + C): i32; };\n", 100 },
{ 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, "defmangle: 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/wwdef_%d_d_%d",
getpid(), i);
mkdir(tmpdir, 0755);
/* #8: source, binary, .sepwork scratch and the byte-id .s
* dumps all live under tmpdir; rm -rf on every exit path.
* Symbol mangling is package/import-derived, not entry-path
* derived, so the in-tmpdir source keeps the cs==ww .s identical. */
char src[128], outbin[128], cs_s[128], ws_s[128], rmcmd[160];
snprintf(src, sizeof src, "%s/wwdef_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wwdef_%d_%d", tmpdir, getpid(), i);
snprintf(cs_s, sizeof cs_s, "%s/cs.s", tmpdir);
snprintf(ws_s, sizeof ws_s, "%s/ww.s", 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];
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 def-mangle tests failed\n", fail, n);
return 1;
}
printf("defmangle: %d/%d ok (cstage run + cs==ww byte-id)\n",
n, n);
return 0;
}

View File

@@ -1,281 +0,0 @@
/*
* 920_array_init_acceptiffits_run — cs==ww net for #130: module-level
* `let A: [N]T = [...]` array-init "accept-if-fits".
*
* Pre-#130 divergence (Drew-adjudicated B/E synthesis):
* - cstage REJECTED all bare-int array elements ("init not assignable")
* — its array assignability path didn't wire the untyped-int→narrow-
* element coercion its OWN scalar path has.
* - wwstage ACCEPTED everything (no per-element validation): bare-int
* in-range (correct), bare-int out-of-range (silent truncate —
* miscompile), str→u8 (silent garbage). Top-level lets weren't even
* run through checkletassign.
*
* #130 fix = accept-if-fits, BOTH stages converge:
* - foldable int literal element → range-check against T via
* def_cast_fits/defcastfits (type table, rule 13). In-range accepts;
* out-of-range REJECTS loud (rule-7 / Drew: Hare range-checks at the
* literal-value level, ref/harec/src/types.c:923 promote_flexible).
* - non-foldable element → type_assignable / isassignable to T
* (untyped-int→u8 ok; str→u8 rejected).
* - cstage: check.c arrlit_init_fits fallback after whole-array
* type_assignable fails.
* - wwstage: check.ww checkletassign array branch + top-level lets now
* routed through checkletassign (were missed). Merges #146 (the
* wwstage str→u8 over-accept).
*
* Scope: ARRAY-init only. Scalar `let X: u8 = 300` truncation is a
* pre-existing language-wide gap (#148), deferred.
*
* This test asserts the ACCEPT rows compile + run + cs==ww byte-id, and
* (via the cstage/wwstage exit-code probe) that the REJECT rows fail to
* build on BOTH stages. Reject rows use a separate build-must-fail check.
*
* Accept rows (size strata 1B/4B/8B, sign, typed-vs-bare, both stages):
* - bare_inrange `[4]u8 = [1,2,3,4]` → A[0]==1
* - bare_u32 `[4]u32 = [10,20,30,40]` → A[0]==10
* - bare_u64 `[2]u64 = [5,6]` → A[0]==5
* - signed_inrange `[4]i32 = [-1,-2,-3,-4]` → A[0]==-1 (255)
* - typed_regress `[4]u8 = [1u8,2u8,3u8,4u8]` → A[0]==1
* - boundary_max `[2]u8 = [255, 0]` → A[0]==255
*
* Reject rows (must FAIL build on both stages):
* - over_range `[2]u8 = [300, 1]`
* - neg_for_unsigned `[2]u8 = [-1, 0]`
* - str_to_u8 `[2]u8 = ["x", "y"]`
*/
#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 arow { const char *label; const char *src; int want_exit; };
struct rrow { const char *label; const char *src; };
static const struct arow accept_rows[] = {
{ "bare_inrange",
"package main;\n"
"let A: [4]u8 = [1, 2, 3, 4];\n"
"export fn main() i32 = { return A[0]: i32; };\n", 1 },
{ "bare_u32",
"package main;\n"
"let A: [4]u32 = [10, 20, 30, 40];\n"
"export fn main() i32 = { return A[0]: i32; };\n", 10 },
{ "bare_u64",
"package main;\n"
"let A: [2]u64 = [5, 6];\n"
"export fn main() i32 = { return A[0]: i32; };\n", 5 },
{ "signed_inrange",
"package main;\n"
"let A: [4]i32 = [-1, -2, -3, -4];\n"
"export fn main() i32 = { return A[0]; };\n", 255 /* -1 */ },
{ "typed_regress",
"package main;\n"
"let A: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n"
"export fn main() i32 = { return A[0]: i32; };\n", 1 },
{ "boundary_max",
"package main;\n"
"let A: [2]u8 = [255, 0];\n"
"export fn main() i32 = { return A[0]: i32; };\n", 255 },
/* i8 signed boundary: -128..127 both in-range. */
{ "i8_boundary",
"package main;\n"
"let A: [2]i8 = [127, -128];\n"
"export fn main() i32 = { return A[0]: i32; };\n", 127 },
/* Non-foldable element of matching type — exercises the else
* branch (type_assignable / isassignable, not the fold path).
* Must be a FUNCTION-BODY array: a module-level array with a
* non-foldable (runtime-valued) element isn't statically
* emittable (no const fold → no DATA row). The checker else
* branch fires identically for body lets, where the stack slot
* takes the runtime value. cs==ww verified. */
{ "nonfold_match",
"package main;\n"
"export fn main() i32 = {\n"
" let u: u8 = 5u8;\n"
" let A: [2]u8 = [u, 0u8];\n"
" return A[0]: i32;\n"
"};\n", 5 },
{ NULL, NULL, 0 }
};
static const struct rrow reject_rows[] = {
{ "over_range",
"package main;\n"
"let A: [2]u8 = [300, 1];\n"
"export fn main() i32 = { return A[0]: i32; };\n" },
{ "neg_for_unsigned",
"package main;\n"
"let A: [2]u8 = [-1, 0];\n"
"export fn main() i32 = { return 0; };\n" },
{ "str_to_u8",
"package main;\n"
"let A: [2]u8 = [\"x\", \"y\"];\n"
"export fn main() i32 = { return 0; };\n" },
/* Just-over-256 — pins the u8 upper bound exactly. */
{ "over_256",
"package main;\n"
"let A: [2]u8 = [256, 0];\n"
"export fn main() i32 = { return 0; };\n" },
/* i8 over-range (128 > 127). */
{ "i8_over",
"package main;\n"
"let A: [2]i8 = [128, 0];\n"
"export fn main() i32 = { return 0; };\n" },
/* Non-foldable wider-runtime int → narrow element needs an
* explicit cast in Hare; reject both stages (the else-branch
* reject path, cs==ww verified). */
{ "nonfold_wider",
"package main;\n"
"let i: int = 5;\n"
"let A: [2]u8 = [i, 0u8];\n"
"export fn main() i32 = { return 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;
}
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, "arrfit: w6c_ww missing — cannot run the "
"cs==ww gate (the whole point of this test)\n");
return 1;
}
int n = 0, fail = 0;
/* Accept rows: cstage build + run + cs==ww byte-id. */
for (int i = 0; accept_rows[i].src; i++, n++) {
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwafit_%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/wwafit_%d_%d.ww",
tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wwafit_%d_%d",
tmpdir, getpid(), i);
snprintf(cs_s, sizeof cs_s, "%s/wwafit_%d_%d_cs.s",
tmpdir, getpid(), i);
snprintf(ws_s, sizeof ws_s, "%s/wwafit_%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(accept_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, "accept[%s]: cstage build failed\n",
accept_rows[i].label);
fail++; runwait(rmcmd); continue;
}
int got = runwait(outbin);
if (got != accept_rows[i].want_exit) {
fprintf(stderr, "accept[%s]: exit %d, want %d\n",
accept_rows[i].label, got, accept_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, "accept[%s]: w6c failed\n", accept_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, "accept[%s]: w6c_ww failed\n", accept_rows[i].label);
fail++; runwait(rmcmd); continue;
}
if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr, "accept[%s]: cs/ww .s DIFFER (rule-10)\n",
accept_rows[i].label);
fail++;
}
runwait(rmcmd);
}
/* Reject rows: BOTH stages must fail to build (loud reject). */
for (int i = 0; reject_rows[i].src; i++, n++) {
char src[64];
snprintf(src, sizeof src, "/tmp/wwrfit_%d_%d.ww", getpid(), i);
FILE *f = fopen(src, "wb");
if (f == NULL) { fail++; continue; }
fputs(reject_rows[i].src, f);
fclose(f);
char cmd[2048], dst[80];
snprintf(dst, sizeof dst, "/tmp/wwrfit_%d_%d.s", getpid(), i);
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, dst, src);
int cs_rc = runwait(cmd);
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c_ww, dst, src);
int ww_rc = runwait(cmd);
if (cs_rc == 0) {
fprintf(stderr, "reject[%s]: cstage ACCEPTED (want reject)\n",
reject_rows[i].label);
fail++;
}
if (ww_rc == 0) {
fprintf(stderr, "reject[%s]: wwstage ACCEPTED (want reject)\n",
reject_rows[i].label);
fail++;
}
unlink(src); unlink(dst);
}
if (fail) {
fprintf(stderr, "%d/%d array-init-accept-if-fits tests failed\n",
fail, n);
return 1;
}
printf("arrfit: %d/%d ok (accept: run + cs==ww; reject: both-stage fail)\n",
n, n);
return 0;
}

View File

@@ -1,287 +0,0 @@
/*
* 921_amp_def_global_run — address-of a module-level global (#149) +
* scalar-def address-of (#147 consolidation). Regression net for the
* cgaddr def-symbol / module-qualified gap: A.2 (0ed0b39) + A.3
* (9e3bc4e) widened the VALUE-LOAD path for struct/array defs but the
* ADDRESS-OF twin (cgexpr N_UN TK_AMP / cgun) never handled def-symbols
* nor module-qualified globals, so `&def` / `&mod.global` emitted an
* uninitialised AX (PUSHQ AX / POPQ DI / CALL — no LEAQ) → garbage
* pointer, despite the DATA symbol existing. cs+ww were BYTE-IDENTICAL
* on the bug (gate-blind), so each row carries BOTH dimensions:
* (a) `ww build` + run, asserting the exit code == the value reached
* THROUGH the pointer (a garbage pointer yields a wrong value, not
* a false exit-0 — the probe lesson).
* (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge (rule-10).
*
* Two operand shapes, one class (cgaddr-doesn't-handle-defs):
* Shape 1 `&G` — N_IDENT, G a top-level def (struct/array/scalar)
* Shape 2 `&mod.G` — N_DOT, mod a module qualifier (kind-agnostic:
* cross-module let / def / scalar / fn)
* The fold-4 / γ-cleanup driver is the cross-module struct-def row
* (`&mod.def_struct`, the `&math::f64info` shape).
*
* Non-addressable defs (str def inlined; computed-rhs float like
* `def NAN = 0.0/0.0` — #147, no DATA symbol) are NOT silently dropped:
* `&them` is a LOUD build error in both stages (rule-7). Those rows set
* want_build_fail.
*
* NOTE: array-element reads THROUGH a `*[N]T` pointer param have a
* pre-existing cs!=ww divergence (narrow MOVSXD/MOVL + esz-stride),
* unrelated to #149 — so the def-array row reads element 0 via a
* `*[N]T -> *T` cast + plain deref, which isolates the `&A` LEAQ.
*
* Single-file multi-package form (like 953_f64crossmod_run): `package
* myf; ... package main; import myf; ...` so w6c/w6c_ww see the cross-
* module reference without -I plumbing.
*/
#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;
int want_build_fail; /* 1: w6c must reject (loud rule-7 error) */
};
static const struct row rows[] = {
/* Shape 1 — same-pkg `&def`. */
{ "def_struct",
"package main;\n"
"type pt = struct { x: i32, y: i32 };\n"
"def P: pt = pt{x=7, y=11};\n"
"fn rd(p: *pt) i32 = { return p.x + p.y; };\n"
"export fn main() i32 = { return rd(&P); };\n", 18, 0 },
{ "def_array",
"package main;\n"
"def A: [3]i64 = [18i64, 11i64, 0i64];\n"
"fn rd(p: *[3]i64) i64 = { let q: *i64 = p: *i64; return *q; };\n"
"export fn main() i32 = { return rd(&A): i32; };\n", 18, 0 },
{ "def_scalar_int",
"package main;\n"
"def N: i32 = 42;\n"
"fn rd(p: *i32) i32 = { return *p; };\n"
"export fn main() i32 = { return rd(&N); };\n", 42, 0 },
{ "def_scalar_float",
"package main;\n"
"def D: f64 = 0.5;\n"
"fn rd(p: *f64) f64 = { return *p; };\n"
"export fn main() i32 = { return (rd(&D) * 100.0): i32; };\n", 50, 0 },
/* Shape 2 — cross-pkg `&mod.G` (kind-agnostic). */
{ "xmod_def_struct", /* the &math::f64info / fold-4 shape */
"package myf;\n"
"export type pt = struct { x: i32, y: i32 };\n"
"export def P: pt = pt{x=7, y=11};\n"
"package main;\n"
"import myf;\n"
"fn rd(p: *myf.pt) i32 = { return p.x + p.y; };\n"
"export fn main() i32 = { return rd(&myf.P); };\n", 18, 0 },
{ "xmod_let_struct",
"package myf;\n"
"export type pt = struct { x: i32, y: i32 };\n"
"export let L: pt = pt{x=7, y=11};\n"
"package main;\n"
"import myf;\n"
"fn rd(p: *myf.pt) i32 = { return p.x + p.y; };\n"
"export fn main() i32 = { return rd(&myf.L); };\n", 18, 0 },
{ "xmod_scalar_let",
"package myf;\n"
"export let S: i32 = 42;\n"
"package main;\n"
"import myf;\n"
"fn rd(p: *i32) i32 = { return *p; };\n"
"export fn main() i32 = { return rd(&myf.S); };\n", 42, 0 },
{ "xmod_func", /* Shape 2 TY_FN leaf → LEAQ fn(SB) */
"package myf;\n"
"export fn helper() i32 = { return 42; };\n"
"package main;\n"
"import myf;\n"
"fn take(p: *fn() i32) i32 = { return 7; };\n"
"export fn main() i32 = { return take(&myf.helper); };\n", 7, 0 },
/* Regressions — paths the fix must NOT disturb. */
{ "reg_let_struct", /* &let_struct worked same-pkg pre-#149 */
"package main;\n"
"type pt = struct { x: i32, y: i32 };\n"
"let L: pt = pt{x=7, y=11};\n"
"fn rd(p: *pt) i32 = { return p.x + p.y; };\n"
"export fn main() i32 = { return rd(&L); };\n", 18, 0 },
{ "reg_amp_arr_idx", /* &local_arr[i] */
"package main;\n"
"fn rd(p: *i64) i64 = { return *p; };\n"
"export fn main() i32 = {\n"
" let a: [3]i64 = [5i64, 18i64, 9i64];\n"
" let p: *i64 = &a[1];\n"
" return rd(p): i32; };\n", 18, 0 },
/* rule-7 LOUD: `&non-addressable def` (no DATA symbol). */
{ "fail_str_def",
"package main;\n"
"def MSG: str = \"hello\";\n"
"fn rd(p: *str) i32 = { return 7; };\n"
"export fn main() i32 = { return rd(&MSG); };\n", 0, 1 },
{ "fail_computed_float", /* #147 `def NAN = 0.0/0.0` shape */
"package main;\n"
"def D: f64 = 1.0 / 4.0;\n"
"fn rd(p: *f64) f64 = { return *p; };\n"
"export fn main() i32 = { return (rd(&D) * 100.0): i32; };\n", 0, 1 },
{ NULL, NULL, 0, 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, "amp_def_global: 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], rmcmd[80];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwamp_%d_d_%d",
getpid(), i);
mkdir(tmpdir, 0755);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
char src[128];
snprintf(src, sizeof src, "%s/wwamp_%d_%d.ww",
tmpdir, getpid(), i);
FILE *f = fopen(src, "wb");
if (f == NULL) { runwait(rmcmd); fail++; continue; }
fputs(rows[i].src, f);
fclose(f);
if (rows[i].want_build_fail) {
/* rule-7 LOUD: both stages must REJECT (`&` of a non-
* addressable def has no DATA symbol). Build to .s and
* assert non-zero exit on each stage. */
char cmd[2048], dump[128];
snprintf(dump, sizeof dump, "%s/wwamp_%d_%d.s",
tmpdir, getpid(), i);
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c, dump, src);
if (runwait(cmd) == 0) {
fprintf(stderr, "row[%s]: w6c ACCEPTED &non-"
"addressable-def (want loud reject)\n",
rows[i].label);
fail++;
}
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c_ww, dump, src);
if (runwait(cmd) == 0) {
fprintf(stderr, "row[%s]: w6c_ww ACCEPTED &non-"
"addressable-def (want loud reject)\n",
rows[i].label);
fail++;
}
runwait(rmcmd);
continue;
}
/* (a) cstage build + run; assert exit == value reached
* through the pointer. */
char outbin[128];
snprintf(outbin, sizeof outbin, "%s/wwamp_%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. */
char cs_s[128], ws_s[128];
snprintf(cs_s, sizeof cs_s, "%s/wwamp_%d_%d_cs.s",
tmpdir, getpid(), i);
snprintf(ws_s, sizeof ws_s, "%s/wwamp_%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 amp-def-global tests failed\n", fail, n);
return 1;
}
printf("amp_def_global: %d/%d ok (cstage run + cs==ww byte-id)\n",
n, n);
return 0;
}

View File

@@ -1,143 +0,0 @@
/*
* 930_struct_tuple_field_slot — project #237: a tuple-typed struct field
* must contribute its real slot width to the enclosing struct's slotsize.
*
* wwstage's checker `fieldslotsize` (check.ww) summed struct field SLOT
* sizes to stamp a struct's tinfo.slotsize, but had no TY_TUPLE arm — a
* tuple field fell to the 8B default. So `S = struct { f: ([]u8,[]u8) }`
* stamped slotsize=8 while size=48 (the natural sum, correct). A `let s:S`
* slot is allocated off ti.slotsize (cgenutil.ww slotsize), so wwstage
* reserved an 8-byte frame slot for a 48-byte struct: a SILENT stack-
* corrupting miscompile. cstage has no size/slotsize split (it sizes the
* field at f->type->size=48 throughout), so the two stages DIVERGED on the
* emitted frame ($16 wwstage vs $64 cstage) — invisible to a cstage-only
* check, caught only by cs==ww byte-id (rule 10). The fix adds the TY_TUPLE
* arm (return the tuple's own slotsize), aligning fieldslotsize with the
* cgen-side cgenutil.ww fieldsize that already returns 48.
*
* GATE: pure cs==ww .s byte-id. No runtime — the divergence is a frame/slot
* SIZE, fully visible in the emitted assembly. The fixture takes &s.f so the
* struct slot is materialised; no over-cap call is involved (this isolates
* #237 from the #234 store-routing). A FAIL means the stages disagree on the
* struct's frame size (the #237 regression).
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.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);
int cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
struct row { const char *label; const char *src; };
static const struct row rows[] = {
/* one tuple field: slotsize must be 48, not the 8B default. */
{ "single_tuple_field",
"package main;\n"
"type S = struct { f: ([]u8, []u8) };\n"
"export fn main() i32 = {\n"
" let s: S;\n"
" let p: *int = (&s.f): *int;\n"
" p[1] = 1;\n"
" return 0;\n"
"};\n" },
/* tuple field preceded by a scalar: foff!=0, slot still full-width. */
{ "hdr_then_tuple_field",
"package main;\n"
"type S = struct { hdr: int, f: ([]u8, []u8) };\n"
"export fn main() i32 = {\n"
" let s: S;\n"
" s.hdr = 9;\n"
" let p: *int = (&s.f): *int;\n"
" p[1] = 1;\n"
" return 0;\n"
"};\n" },
{ NULL, NULL },
};
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[256];
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, "struct_tuple_field_slot: w6c_ww missing — "
"cannot run the cs==ww byte-id gate\n");
return 1;
}
int n = 0, fail = 0;
for (int i = 0; rows[i].src; i++, n++) {
char src[64], cs_s[64], ws_s[64], cmd[2048];
snprintf(src, sizeof src, "/tmp/stfs_%d_%d.ww", getpid(), i);
snprintf(cs_s, sizeof cs_s, "/tmp/stfs_%d_%d_cs.s", getpid(), i);
snprintf(ws_s, sizeof ws_s, "/tmp/stfs_%d_%d_ww.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (f == NULL) { fail++; continue; }
fputs(rows[i].src, f);
fclose(f);
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++; unlink(src); 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++; unlink(src); unlink(cs_s); continue;
}
if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr, "row[%s]: cstage/wwstage .s DIFFER "
"(#237 struct tuple-field slot-size regression)\n",
rows[i].label);
fail++;
}
unlink(src); unlink(cs_s); unlink(ws_s);
}
if (fail) {
fprintf(stderr, "%d/%d struct-tuple-field-slot tests failed\n",
fail, n);
return 1;
}
printf("struct_tuple_field_slot: %d/%d ok (cs==ww byte-id)\n", n, n);
return 0;
}

View File

@@ -1,287 +0,0 @@
/*
* 944_peellint_gate — teeth for tools/peellint (#5 alias-arc B7).
*
* The lint is the enforcement half of the close-by-construction
* contract: zero raw under-token reads in scope outside the annotated
* whitelist. A gate without negative validation can rot green (B4
* precedent), so this test pins BOTH directions:
*
* 1. real tree at HEAD lints CLEAN (the closure proof itself);
* 2. a re-introduced raw peel REDS the lint — C `->under` ternary
* and ww `.under` if-peel, the four-times-burned spellings;
* 3. a corrupted whitelist annotation REDS the lint (token-bounded
* `peel-ok` matcher: `peel-okk-…` must NOT exempt);
* 4. regression rows that must stay GREEN: the check.ww:3683
* "io.underread" prose (token bound), a code read of a longer
* field (`s.underread`), comment-quoted `.under`/`->under`
* prose (comment strip), and the already-landed `peellint-ok`
* sibling spelling (history is not re-spelled);
* 5. review-found evasion spellings REDS (B7 review probes E1-E6,
* every one compiles): `t -> under` spacing, `t->`/EOL +
* `under` next line (both stages' split), C deref-dot
* `(*t).under`, ww `t. under`, and a string literal containing
* a block-comment OPENER token that blinded the old regex
* comment-strip for the rest of the file.
* 6. RULE 2 (#101/#109) — bare primsize() in the ww stage is the
* alias-blind width shape aliasprimsize() supersedes. A bare
* `primsize(` REDS; the SSoT wrapper `aliasprimsize(` must NOT
* (left word boundary); the evasion spellings (space-before-paren,
* name-at-EOL line split, string-blind block-comment opener in a
* literal, paren-wrap `(primsize)(nm)`, function-value bind
* `let p = primsize` — the last two reviewer-109-found, both
* compile + run) all RED; a
* `primsize-ok` annotation exempts; a corrupted one does not; the
* primsize-ok and peel-ok windows are independent (neither blinds
* the other's shape); and a C-file `primsize(` is out of scope
* (the C stage chases via type_chase_named, no primsize symbol).
*
* Scratch trees live under /tmp and exercise the lint via its ROOT
* override (sizelint-style), so the real tree is never touched.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static char root[1024]; /* repo root (cwd when run via test/run) */
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
write_file(const char *path, const char *body)
{
FILE *f = fopen(path, "wb");
if (!f) return -1;
fputs(body, f);
fclose(f);
return 0;
}
/* lint_scratch — run tools/peellint over a one-file scratch tree and
* return its exit code. relpath selects the in-scope directory. */
static int
lint_scratch(const char *scratch, const char *relpath, const char *body)
{
char cmd[2048], path[1400];
snprintf(cmd, sizeof cmd, "rm -rf %s", scratch);
runwait(cmd);
snprintf(path, sizeof path, "%s/%s", scratch, relpath);
char dir[1400];
snprintf(dir, sizeof dir, "%s", path);
char *slash = strrchr(dir, '/');
if (slash) *slash = '\0';
snprintf(cmd, sizeof cmd, "mkdir -p %s", dir);
if (runwait(cmd) != 0) return -1;
if (write_file(path, body) != 0) return -1;
snprintf(cmd, sizeof cmd,
"ROOT=%s sh %s/tools/peellint >/dev/null 2>&1", scratch, root);
int rc = runwait(cmd);
snprintf(cmd, sizeof cmd, "rm -rf %s", scratch);
runwait(cmd);
return rc;
}
struct lintrow { const char *label; const char *relpath;
const char *body; int wantexit; };
static const struct lintrow lintrows[] = {
{ "reinject_c_peel", "cmd/w6c/x.c",
"static Type *f(Type *t) {\n"
"\tType *u = (t->kind == TY_NAMED) ? t->under : t;\n"
"\treturn u;\n"
"}\n", 1 },
{ "reinject_ww_peel", "selfhost/cmd/wcc/x.ww",
"fn f(t: *tinfo) *tinfo = {\n"
"\tif (t.kind == tykind.TY_NAMED) { return t.under; };\n"
"\treturn t;\n"
"};\n", 1 },
{ "annotated_c_peel_ok", "cmd/w6c/x.c",
"static Type *f(Type *t) {\n"
"\tType *u = (t->kind == TY_NAMED) ? t->under : t; "
"/* peel-ok: probe */\n"
"\treturn u;\n"
"}\n", 0 },
{ "corrupt_annotation", "cmd/w6c/x.c",
"static Type *f(Type *t) {\n"
"\tType *u = (t->kind == TY_NAMED) ? t->under : t; "
"/* peel-okk-corrupt: probe */\n"
"\treturn u;\n"
"}\n", 1 },
{ "peellint_ok_spelling", "selfhost/cmd/wcc/x.ww",
"fn f(t: *tinfo, u: *tinfo) void = {\n"
"\t// peellint-ok: construction\n"
"\tt.under = u;\n"
"};\n", 0 },
/* check.ww:3683 regression: prose token "io.underread" must not
* trip the ww matcher (token bound), nor `.under` quoted in a
* line comment (comment strip). */
{ "io_underread_prose", "selfhost/cmd/wcc/x.ww",
"fn f(x: int) int = {\n"
"\t// #199 repro io.underread -> (size|io.eof|io.error)\n"
"\t// the NAMED.under chain stays terminating\n"
"\tlet v: int = x + 2; // io.underread again\n"
"\treturn v;\n"
"};\n", 0 },
{ "code_longer_field", "selfhost/cmd/wcc/x.ww",
"fn f(s: stream) int = { return s.underread; };\n", 0 },
{ "c_block_comment_prose", "cmd/wcc/x.c",
"/* walk the chain: a raw t->under read here\n"
" * would single-peel; t->under in prose only. */\n"
"int g(int x) { return x; }\n", 0 },
{ "lib_ww_in_scope", "lib/ww/x.ww",
"fn f(t: *tinfo) *tinfo = {\n"
"\tif (t.kind == tykind.TY_NAMED) { return t.under; };\n"
"\treturn t;\n"
"};\n", 1 },
/* Review-found evasions (all compile; pre-amendment lint passed
* every one of them green): the matcher must red each. */
{ "evade_c_spacing", "cmd/w6c/x.c",
"static Type *f(Type *t) {\n"
"\treturn (t->kind == TY_NAMED) ? t -> under : t;\n"
"}\n", 1 },
{ "evade_c_linesplit", "cmd/w6c/x.c",
"static Type *f(Type *t) {\n"
"\treturn (t->kind == TY_NAMED) ? t->\n"
"\t under : t;\n"
"}\n", 1 },
{ "evade_c_derefdot", "cmd/w6c/x.c",
"static Type *f(Type *t) {\n"
"\treturn (t->kind == TY_NAMED) ? (*t).under : t;\n"
"}\n", 1 },
{ "evade_ww_dotspace", "selfhost/cmd/wcc/x.ww",
"fn f(t: *tinfo) *tinfo = {\n"
"\tif (t.kind == tykind.TY_NAMED) { return t. under; };\n"
"\treturn t;\n"
"};\n", 1 },
{ "evade_ww_linesplit", "selfhost/cmd/wcc/x.ww",
"fn f(t: *tinfo) *tinfo = {\n"
"\tlet u: *tinfo = t.\n"
"\t\tunder;\n"
"\treturn u;\n"
"};\n", 1 },
{ "evade_c_string_blind", "cmd/w6c/x.c",
"static const char *s = \"/*\";\n"
"static Type *f(Type *t) { return t->under; }\n", 1 },
/* RULE 2 (#101/#109): bare primsize() outside the chase is the
* forbidden alias-blind width shape; aliasprimsize is the SSoT. */
{ "prim_bare", "selfhost/cmd/wcc/x.ww",
"fn f(c: *cgen, nm: str) i32 = {\n"
"\tlet z: i32 = primsize(nm);\n"
"\treturn z;\n"
"};\n", 1 },
{ "prim_evade_spacing", "selfhost/cmd/wcc/x.ww",
"fn f(c: *cgen, nm: str) i32 = {\n"
"\tlet z: i32 = primsize (nm);\n"
"\treturn z;\n"
"};\n", 1 },
{ "prim_evade_linesplit", "selfhost/cmd/wcc/x.ww",
"fn f(c: *cgen, nm: str) i32 = {\n"
"\tlet z: i32 = primsize\n"
"\t (nm);\n"
"\treturn z;\n"
"};\n", 1 },
{ "prim_evade_string_blind", "selfhost/cmd/wcc/x.ww",
"fn f(c: *cgen) str = {\n"
"\tlet s: str = \"/*\";\n"
"\tlet z: i32 = primsize(s);\n"
"\treturn s;\n"
"};\n", 1 },
/* review-109 evasions: both COMPILE + run (verified) yet slipped a
* `primsize(`-only matcher — the token rule reds them. */
{ "prim_evade_parenwrap", "selfhost/cmd/wcc/x.ww",
"fn f(c: *cgen, nm: str) i32 = {\n"
"\tlet z: i32 = (primsize)(nm);\n"
"\treturn z;\n"
"};\n", 1 },
{ "prim_evade_fnvalue", "selfhost/cmd/wcc/x.ww",
"fn f(c: *cgen, nm: str) i32 = {\n"
"\tlet p = primsize;\n"
"\treturn p(nm);\n"
"};\n", 1 },
/* aliasprimsize() is the SSoT wrapper — its `primsize` suffix must
* NOT trip the left-word-bounded matcher (the central evasion). */
{ "prim_alias_wrapper_ok", "selfhost/cmd/wcc/x.ww",
"fn f(c: *cgen, nm: str) i32 = {\n"
"\tlet z: i32 = aliasprimsize(c, nm);\n"
"\treturn z;\n"
"};\n", 0 },
{ "prim_annotated_ok", "selfhost/cmd/wcc/x.ww",
"fn f(c: *cgen, nm: str) i32 = {\n"
"\t// primsize-ok: chase body\n"
"\tlet z: i32 = primsize(nm);\n"
"\treturn z;\n"
"};\n", 0 },
{ "prim_corrupt_annotation", "selfhost/cmd/wcc/x.ww",
"fn f(c: *cgen, nm: str) i32 = {\n"
"\t// primsize-okk-corrupt: nope\n"
"\tlet z: i32 = primsize(nm);\n"
"\treturn z;\n"
"};\n", 1 },
/* the two exemption windows are independent: primsize-ok must not
* blind an under-token peel, nor peel-ok a bare primsize. */
{ "prim_window_no_cross_under", "selfhost/cmd/wcc/x.ww",
"fn f(t: *tinfo) *tinfo = {\n"
"\t// primsize-ok: must NOT exempt the under peel below\n"
"\treturn t.under;\n"
"};\n", 1 },
{ "peel_window_no_cross_prim", "selfhost/cmd/wcc/x.ww",
"fn f(c: *cgen, nm: str) i32 = {\n"
"\t// peel-ok: must NOT exempt the primsize below\n"
"\tlet z: i32 = primsize(nm);\n"
"\treturn z;\n"
"};\n", 1 },
/* RULE 2 is ww-only: the C stage dealiases via type_chase_named and
* has no primsize symbol — a C `primsize(` is not in scope. */
{ "prim_c_file_out_of_scope", "cmd/w6c/x.c",
"static int primsize(const char *n) { return 0; }\n"
"int g(void) { return primsize(\"u8\"); }\n", 0 },
};
int
main(void)
{
if (getcwd(root, sizeof root) == NULL) return 1;
int total = 0, fail = 0;
char cmd[2048], scratch[256];
/* 1. The closure proof: the real tree lints clean at HEAD. */
total++;
snprintf(cmd, sizeof cmd, "sh %s/tools/peellint", root);
if (runwait(cmd) != 0) {
fprintf(stderr, "peellint_gate: real tree NOT clean\n");
fail++;
}
/* 2-4. Scratch rows: negative validation + matcher regressions. */
int n = (int)(sizeof lintrows / sizeof lintrows[0]);
for (int i = 0; i < n; i++) {
total++;
snprintf(scratch, sizeof scratch, "/tmp/plint_%d_%d",
getpid(), i);
int got = lint_scratch(scratch, lintrows[i].relpath,
lintrows[i].body);
if (got != lintrows[i].wantexit) {
fprintf(stderr, "row[%s]: lint exit %d, want %d\n",
lintrows[i].label, got, lintrows[i].wantexit);
fail++;
}
}
if (fail) {
fprintf(stderr, "peellint_gate: %d/%d checks failed\n",
fail, total);
return 1;
}
printf("peellint_gate: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,271 +0,0 @@
/*
* 949_errtype_compare — #246: the wwstage checker must REJECT a
* comparison whose operand is an error type (`!T`) paired with a
* differing type, e.g. `strconv.invalid != i32`. cstage's cbinop
* (cmd/wcc/check.c:952) routes every comparison through unify_arith,
* which loud-rejects the differing-types pair; the wwstage checker's
* binoptype returned `bool` for comparisons WITHOUT any unify step, so
* it silently accepted a program cstage rejects — a rule-10 break.
*
* The fix (selfhost/cmd/wcc/check.ww binoptype) mirrors cstage DOWN
* (rule 10), scoped to the error-type operand so the broad
* differing-types diagnostic (typeeqast-vs-cstage-type_eq asymmetry
* risk) stays out of wwstage.
*
* K_BUILDERR rows: build FAILS with the differing-types diagnostic on
* BOTH drivers. K_RUN rows: build+run exit 0 on BOTH drivers AND the
* cs==ww .s is byte-identical — the positive controls pin that an
* error-type compared with itself, and a plain int compare, still pass
* (cstage allows both via type_eq).
*
* 940/945 precedent: ww_ww builds /tmp fixtures, so the selfhost-tree
* sibling-write race does not apply.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.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;
}
static int
file_contains(const char *path, const char *needle)
{
FILE *f = fopen(path, "rb");
if (!f) return 0;
char buf[8192];
size_t n = fread(buf, 1, sizeof buf - 1, f);
fclose(f);
buf[n] = '\0';
return strstr(buf, needle) != NULL;
}
#define K_RUN 0 /* build+run both drivers, exit==want, + cs==ww byte-id */
#define K_BUILDERR 1 /* build must FAIL with experr on BOTH drivers (rule 7) */
struct row { const char *label; const char *src; int kind; int want;
const char *experr; };
static const struct row rows[] = {
/* the headline #246 case: error type `invalid` (!i32) compared with
* a plain i32 via `!=`. cstage rejects; wwstage must now too. */
{ "neq_invalid_i32",
"package main;\n"
"type invalid = !i32;\n"
"export fn main() i32 = {\n"
" let e: invalid = 5: invalid;\n"
" let n: i32 = 3;\n"
" if (e != n) { return 1; };\n"
" return 0;\n"
"};\n",
K_BUILDERR, 0, "operands have differing types" },
/* sibling op: `==` over the same mismatched error/int pair. */
{ "eq_invalid_i32",
"package main;\n"
"type invalid = !i32;\n"
"export fn main() i32 = {\n"
" let e: invalid = 5: invalid;\n"
" let n: i32 = 3;\n"
" if (e == n) { return 1; };\n"
" return 0;\n"
"};\n",
K_BUILDERR, 0, "operands have differing types" },
/* ordered comparison shares cstage's unify_arith route; the error
* operand must reject there too (closes the comparison family, not
* just `!=`). */
{ "lt_invalid_i32",
"package main;\n"
"type invalid = !i32;\n"
"export fn main() i32 = {\n"
" let e: invalid = 5: invalid;\n"
" let n: i32 = 3;\n"
" if (e < n) { return 1; };\n"
" return 0;\n"
"};\n",
K_BUILDERR, 0, "operands have differing types" },
/* positive control: error type compared with ITSELF. cstage's
* type_eq holds, so unify_arith accepts — wwstage must not
* over-reject (the fix is scoped to a DIFFERING pair). */
{ "neq_same_error",
"package main;\n"
"type invalid = !i32;\n"
"export fn main() i32 = {\n"
" let e: invalid = 5: invalid;\n"
" let f: invalid = 6: invalid;\n"
" if (e != f) { return 0; };\n"
" return 0;\n"
"};\n",
K_RUN, 0, NULL },
/* positive control: plain int compare, untouched by the fix. */
{ "neq_int_int",
"package main;\n"
"export fn main() i32 = {\n"
" let a: i32 = 1;\n"
" let b: i32 = 2;\n"
" if (a != b) { return 0; };\n"
" return 1;\n"
"};\n",
K_RUN, 0, NULL },
};
/* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */
static int
run_driver(const char *driver, const struct row *r, int i)
{
char src[128], tmpdir[96], errf[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/etc_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/etc_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/etc_%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 (r->kind == K_BUILDERR) {
int ok = (brc != 0)
&& (r->experr == NULL || file_contains(errf, r->experr));
if (!ok)
fprintf(stderr, "row[%s]: %s expected loud #246 builderr "
"(brc=%d)\n", r->label, driver, brc);
runwait(rmcmd);
return ok ? 0 : 1;
}
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;
}
/* cs==ww .s byte-id (rule 10) for the K_RUN rows. */
static int
byteid(const char *w6c, const char *w6c_ww, const struct row *r, int i)
{
char src[96], cs_s[96], ws_s[96], cmd[1024];
snprintf(src, sizeof src, "/tmp/etc_bi_%d_%d.ww", getpid(), i);
snprintf(cs_s, sizeof cs_s, "/tmp/etc_bi_%d_%d_cs.s", getpid(), i);
snprintf(ws_s, sizeof ws_s, "/tmp/etc_bi_%d_%d_ww.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
int rc = 0;
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", r->label); rc = 1; }
else {
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", r->label); rc = 1; }
else if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr, "row[%s]: cstage/wwstage .s DIFFER (#246)\n",
r->label);
rc = 1;
}
}
unlink(src); unlink(cs_s); unlink(ws_s);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[512];
if (bin[0] != '/') {
char cwd[256];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[640], wdrv[640], w6c[640], w6c_ww[640];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
struct { const char *name; const char *path; 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].path, X_OK) != 0) {
fprintf(stderr, "errtype_compare: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
total++;
if (run_driver(drivers[d].path, &rows[i], i) != 0) fail++;
}
}
if (access(w6c_ww, X_OK) == 0) {
for (int i = 0; i < n; i++) {
if (rows[i].kind != K_RUN) continue;
total++;
if (byteid(w6c, w6c_ww, &rows[i], i) != 0) fail++;
}
}
if (fail) {
fprintf(stderr, "errtype_compare: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("errtype_compare: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,336 +0,0 @@
/*
* 949_intbinop_mismatch — #26: ww requires an explicit integer
* conversion in binary ops (Go-faithful, user-blessed). cstage's cbinop
* routes arithmetic/bitwise/comparison through unify_arith
* (cmd/wcc/check.c:1034), which loud-rejects a typed/typed operand
* mismatch (check.c:1068). The wwstage checker's unifyarith returned the
* lhs type for any mismatch (silent accept) AND the comparison arm never
* called it at all, so `int < len(s)` (len() = i32) and `int & i32`
* silently compiled where cstage rejects — a rule-10 break (and a latent
* signed/unsigned miscompile for `int < uint`).
*
* The fix (selfhost/cmd/wcc/check.ww) aligns wwstage DOWN: unifyarith
* loud-rejects a mismatched typed pair, keeping only cstage's one-sided
* alias-vs-base promotion (check.c:1060), and the ORDERED comparison arm
* now routes through unifyarith. A rune LITERAL stays assignable to an
* integer (cstage's untyped_rune, type.c:379), so `c - '0'` is unaffected.
*
* K_BUILDERR rows: build FAILS with the differing-types diagnostic on
* BOTH drivers. K_RUN rows: build+run exit 0 on BOTH drivers AND the
* cs==ww .s is byte-identical — the positive controls pin that matched
* pairs, an explicit cast, the alias-vs-base promotion (part (a), the
* LOAD-BEARING row), and a rune-literal arithmetic mix all still pass.
*
* 940/945 precedent: ww_ww builds /tmp fixtures, so the selfhost-tree
* sibling-write race does not apply.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.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;
}
static int
file_contains(const char *path, const char *needle)
{
FILE *f = fopen(path, "rb");
if (!f) return 0;
char buf[8192];
size_t n = fread(buf, 1, sizeof buf - 1, f);
fclose(f);
buf[n] = '\0';
return strstr(buf, needle) != NULL;
}
#define K_RUN 0 /* build+run both drivers, exit==want, + cs==ww byte-id */
#define K_BUILDERR 1 /* build must FAIL with experr on BOTH drivers (rule 7) */
struct row { const char *label; const char *src; int kind; int want;
const char *experr; };
static const struct row rows[] = {
/* headline #26: `int < len(s)` (len() returns i32) — the latent
* signed-mix comparison that wwstage silently accepted. */
{ "int_lt_len_i32",
"package main;\n"
"export fn main() i32 = {\n"
" let a: int = 1;\n"
" let s: []u8 = [];\n"
" if (a < len(s)) { return 1; };\n"
" return 0;\n"
"};\n",
K_BUILDERR, 0, "operands have differing types" },
/* signed/unsigned mismatch — the miscompile-prone shape. */
{ "int_lt_uint",
"package main;\n"
"export fn main() i32 = {\n"
" let a: int = 1;\n"
" let b: uint = 2;\n"
" if (a < b) { return 1; };\n"
" return 0;\n"
"};\n",
K_BUILDERR, 0, "operands have differing types" },
/* differing-width signed pair. */
{ "int_lt_i64",
"package main;\n"
"export fn main() i32 = {\n"
" let a: int = 1;\n"
" let b: i64 = 2i64;\n"
" if (a < b) { return 1; };\n"
" return 0;\n"
"};\n",
K_BUILDERR, 0, "operands have differing types" },
/* bitwise feeds the same unifyarith path (check.ww :2598). */
{ "int_and_i32",
"package main;\n"
"export fn main() i32 = {\n"
" let a: int = 1;\n"
" let b: i32 = 2;\n"
" let r: int = a & b;\n"
" if (r < 0) { return 1; };\n"
" return 0;\n"
"};\n",
K_BUILDERR, 0, "operands have differing types" },
/* positive control: matched int/int comparison. */
{ "int_lt_int",
"package main;\n"
"export fn main() i32 = {\n"
" let a: int = 1;\n"
" let b: int = 2;\n"
" if (a < b) { return 0; };\n"
" return 0;\n"
"};\n",
K_RUN, 0, NULL },
/* positive control: matched i32/i32. */
{ "i32_lt_i32",
"package main;\n"
"export fn main() i32 = {\n"
" let a: i32 = 1;\n"
" let b: i32 = 2;\n"
" if (a < b) { return 0; };\n"
" return 0;\n"
"};\n",
K_RUN, 0, NULL },
/* positive control: i32 < len(s) — both i32, the canonical loop. */
{ "i32_lt_len",
"package main;\n"
"export fn main() i32 = {\n"
" let s: []u8 = [];\n"
" let a: i32 = 0;\n"
" if (a < len(s)) { return 0; };\n"
" return 0;\n"
"};\n",
K_RUN, 0, NULL },
/* positive control: explicit cast resolves the #26 mix. */
{ "int_cast_len",
"package main;\n"
"export fn main() i32 = {\n"
" let s: []u8 = [];\n"
" let a: int = 0;\n"
" if (a < len(s): int) { return 0; };\n"
" return 0;\n"
"};\n",
K_RUN, 0, NULL },
/* LOAD-BEARING positive control for part (a): a one-sided alias vs
* its base promotes (cstage check.c:1060). Drops if the alias-chase
* arm is missing or mis-detects a primitive as a named type. */
{ "alias_vs_base",
"package main;\n"
"type myint = i32;\n"
"export fn main() i32 = {\n"
" let a: myint = 1: myint;\n"
" let b: i32 = 2;\n"
" if (a < b) { return 0; };\n"
" return 0;\n"
"};\n",
K_RUN, 0, NULL },
/* positive control: a rune LITERAL stays assignable to an integer
* (cstage's untyped_rune, type.c:379); `c - '0'` must NOT trip the
* #26 reject. */
{ "rune_lit_arith",
"package main;\n"
"export fn main() i32 = {\n"
" let c: u8 = 53u8;\n"
" let r: u8 = c - '0';\n"
" if (r < 10u8) { return 0; };\n"
" return 0;\n"
"};\n",
K_RUN, 0, NULL },
/* LOAD-BEARING positive control: a chained same-enum bitwise OR
* (`flag.A | flag.B | flag.C`, the w6l os.flag pattern). Both operands
* are the SAME enum type, which cstage's unify_arith accepts via
* type_eq's identity check (type.c:250) — wwstage must accept it too.
* Drops if typeeqast lacks the identity fast-path: the #26 reject then
* over-rejects a same-enum binop, breaking the w6l self-compile (994). */
{ "enum_flag_or",
"package main;\n"
"type flag = enum uint { A = 1, B = 2, C = 4 };\n"
"export fn main() i32 = {\n"
" let f: flag = flag.A | flag.B | flag.C;\n"
" return (f: i32) - 7;\n"
"};\n",
K_RUN, 0, NULL },
};
/* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */
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/ibm_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/ibm_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/ibm_%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 (r->kind == K_BUILDERR) {
int ok = (brc != 0)
&& (r->experr == NULL || file_contains(errf, r->experr));
if (!ok)
fprintf(stderr, "row[%s]: %s expected loud #26 builderr "
"(brc=%d)\n", r->label, driver, brc);
runwait(rmcmd);
return ok ? 0 : 1;
}
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;
}
/* cs==ww .s byte-id (rule 10) for the K_RUN rows. */
static int
byteid(const char *w6c, const char *w6c_ww, const struct row *r, int i)
{
char src[96], cs_s[96], ws_s[96], cmd[1024];
snprintf(src, sizeof src, "/tmp/ibm_bi_%d_%d.ww", getpid(), i);
snprintf(cs_s, sizeof cs_s, "/tmp/ibm_bi_%d_%d_cs.s", getpid(), i);
snprintf(ws_s, sizeof ws_s, "/tmp/ibm_bi_%d_%d_ww.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
int rc = 0;
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", r->label); rc = 1; }
else {
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", r->label); rc = 1; }
else if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr, "row[%s]: cstage/wwstage .s DIFFER (#26)\n",
r->label);
rc = 1;
}
}
unlink(src); unlink(cs_s); unlink(ws_s);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[512];
if (bin[0] != '/') {
char cwd[256];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[640], wdrv[640], w6c[640], w6c_ww[640];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
struct { const char *name; const char *path; 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].path, X_OK) != 0) {
fprintf(stderr, "intbinop_mismatch: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
total++;
if (run_driver(drivers[d].path, &rows[i], i) != 0) fail++;
}
}
if (access(w6c_ww, X_OK) == 0) {
for (int i = 0; i < n; i++) {
if (rows[i].kind != K_RUN) continue;
total++;
if (byteid(w6c, w6c_ww, &rows[i], i) != 0) fail++;
}
}
if (fail) {
fprintf(stderr, "intbinop_mismatch: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("intbinop_mismatch: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,214 +0,0 @@
/*
* 953_f64crossmod_run — runtime + byte-id regression net for the
* cross-module f64-return codegen bug, one root with two symptoms:
* #101 — `mod.g(): i32` where g returns f64 must CVTTSD2SI X0->AX
* (f64->int convert), NOT MOVSXD (integer sign-extend).
* #98 — `dbl(mod.g())` forwarding an imported f64 call-result as an
* f64 arg must MOVSD-spill it, NOT integer PUSHQ AX / POPQ DI.
*
* Root: wwstage exprfloatkind's N_CALL arm only resolved an N_IDENT
* callee's return type; a module-qualified N_DOT callee (`mod.g()`)
* never reached fnretlookup and fell through to integer kind 0. cgcast
* (#101) and pushargsrev (#98) then both took the integer path for an
* imported f64-returning fn. Fixed in cgenutil.ww by routing the N_DOT
* callee through fnretlookupmod (cstage cg_isfloat reads the resolved
* call result type directly — cmd/w6c/cgen.c:117,155 — and is correct
* for both cases).
*
* THIS TEST MUST CATCH A WWSTAGE-ONLY REGRESSION. cstage is correct
* before and after the fix, so a cstage-only probe (like
* 951_f64cgen_run) is gate-blind to a wwstage-only divergence. Each row
* therefore carries BOTH dimensions:
* (a) cstage `ww build` + run, asserting the exit code — pins that
* the asm both stages converge on is the runtime-correct one.
* (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge. On master
* 6f8b658 (pre-fix) this diverges (#101 ~2 lines MOVSXD vs
* CVTTSD2SI, #98 ~6 lines PUSHQ/POPQ vs MOVSD-spill); post-fix it
* is byte-identical.
*
* Single-file multi-package form (like 728_match_4arm_cross_module):
* `package myf; ... package main; import myf; ...` in one source, so
* w6c/w6c_ww see the cross-module call without -I path plumbing.
*/
#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[] = {
/* #101 basic: g() returns -7.0, cast to i32 -> -7, exit u8 249. */
{ "x101_neg",
"package myf;\n"
"export fn g() f64 = { return -7.0; };\n"
"package main;\n"
"import myf;\n"
"export fn main() i32 = { return myf.g(): i32; };\n", 249 },
/* #101 truncation toward zero, positive: 3.9 -> 3. */
{ "x101_trunc_pos",
"package myf;\n"
"export fn g() f64 = { return 3.9; };\n"
"package main;\n"
"import myf;\n"
"export fn main() i32 = { return myf.g(): i32; };\n", 3 },
/* #101 truncation toward zero, negative: -3.9 -> -3, exit u8 253. */
{ "x101_trunc_neg",
"package myf;\n"
"export fn g() f64 = { return -3.9; };\n"
"package main;\n"
"import myf;\n"
"export fn main() i32 = { return myf.g(): i32; };\n", 253 },
/* #98 nested f64-call-as-f64-arg: dbl(myf.g()) = -7.0*2 = -14,
* cast to i32 -> -14, exit u8 242. The imported call-result is
* forwarded as an f64 arg, exercising pushargsrev's float spill. */
{ "x98_nested_arg",
"package myf;\n"
"export fn g() f64 = { return -7.0; };\n"
"package main;\n"
"import myf;\n"
"fn dbl(x: f64) f64 = { return x * 2.0; };\n"
"export fn main() i32 = { return dbl(myf.g()): i32; };\n", 242 },
/* #98 XMM-pressure: myf.g() forwarded as the first of three f64
* args; pushargsrev must MOVSD-spill the imported call-result into
* X2 while two more f64 args are live — a distinct path from the
* basic single-arg X0 spill above. sum3(-7,10.5,4.5)=8.0 -> i32 8. */
{ "x98_xmm_pressure",
"package myf;\n"
"export fn g() f64 = { return -7.0; };\n"
"package main;\n"
"import myf;\n"
"fn sum3(a: f64, b: f64, c: f64) f64 = { return a + b + c; };\n"
"export fn main() i32 = { return sum3(myf.g(), 10.5, 4.5): i32; };\n", 8 },
{ 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, "f64crossmod: 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++) {
/* src, the build output, and both .s files all live under one
* tmpdir so the compiler's .sepwork scratch (derived from the
* source path) stays inside it; a single rm -rf at the end of
* both phases reclaims everything. */
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwf64x_%d_d_%d",
getpid(), i);
mkdir(tmpdir, 0755);
char src[128], outbin[128], rmcmd[160];
snprintf(src, sizeof src, "%s/wwf64x_%d_%d.ww",
tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wwf64x_%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.
* FAILS on the pre-fix wwstage divergence. */
char cs_s[160], ws_s[160];
snprintf(cs_s, sizeof cs_s, "%s/wwf64x_%d_%d_cs.s",
tmpdir, getpid(), i);
snprintf(ws_s, sizeof ws_s, "%s/wwf64x_%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 f64 cross-module tests failed\n",
fail, n);
return 1;
}
printf("f64crossmod: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n);
return 0;
}

View File

@@ -1,195 +0,0 @@
#!/bin/sh
# tools/peellint — gate against two raw single-resolve shapes that
# bypass the alias-chase accessors.
#
# RULE 1 (raw TY_NAMED single-peel reads) — the #5 alias-arc
# close-by-construction contract (rob F2/B7 rulings): one chased
# accessor is the only spelled way to dealias — type_chase_named
# (cmd/wcc/type.c) on the C side, tichase (selfhost/cmd/wcc/cgenutil.ww)
# on the ww side. A raw `->under` / `.under` read peels exactly one
# NAMED layer; chain-of-aliases stacks two, so every kind-gated consumer
# downstream of a single peel falls to a scalar shape (the four-times-
# burned family: #60/#61/#62/#70…). ZERO raw under-token reads may
# exist in scope outside the annotated whitelist.
#
# RULE 2 (bare primsize() name-keyed width) — the #101/#109 close-by-
# construction contract. primsize(name) is the ww-stage primitive-width
# table; it is ALIAS-BLIND — a narrow alias (`type my32 = u32`) returns
# 0, defaulting strides/widths to 8 (the #101 struct-fill miscompile and
# the #109 is-primitive GUARD family). aliasprimsize(c, name) is the
# SSoT chase (primsize else aliaslookup-chase, cgenutil.ww). A bare
# primsize() outside the chase machinery is the forbidden shape: the
# size/guard sites route through aliasprimsize so an alias resolves.
# This rule is ww-only — the C side dealiases via type_chase_named and
# has no primsize symbol. ZERO bare primsize() calls may exist in the
# ww stage outside the annotated whitelist.
#
# Both rules land in the same commit that deletes the last raw shape and
# keep the class unwritable.
#
# What rule 1 does NOT close (stated honestly, per the f2-ruling): a
# consumer that never spells `under` at all — a switch on t->kind that
# simply never peels — has no token to see here. That NO-PEEL class is
# closed only where classification routes through the internalized
# chasing helpers, and contained elsewhere by the acceptance-commit-
# carries-tripwires doctrine. Rule 2 has the symmetric edge: a size
# computed by a hardcoded literal instead of primsize is caught by
# sizelint (rule 13), not here.
#
# Matcher: a character scan strips block/line comments and string/char
# literals first (a regex pass mis-nests `/*` inside a string — review
# probe E6), then the tokens are matched accessor-spelling-wide:
# rule 1: `->under`/`.under` in C (deref-dot `(*t).under` is the same
# peel), `.under` in ww, with optional whitespace after the operator
# and a line-split continuation (`t->` at EOL, `under` next line).
# rule 2 (ww only): the bare `primsize` token, LEFT+RIGHT word-bounded
# so the superstring `aliasprimsize` is NOT a hit. Matching the
# standalone token (not just `primsize(`) closes the call form
# `primsize(nm)`, the paren-wrap `(primsize)(nm)`, the function-value
# bind `let p = primsize`, and every line-split — all of which
# compile and reintroduce the alias-blind width (review probes).
# Right/left token bounds keep prose like "io.underread" (check.ww) and
# "aliasprimsize" out.
#
# Exemption: a line containing `peel-ok` (or the equivalent landed
# spelling `peellint-ok`) exempts rule-1 violations on itself and the
# following 9 lines; a line containing `primsize-ok` exempts rule-2
# violations over the same window. The windows are kept separate so a
# rule-1 annotation cannot blind a rule-2 bug and vice versa. Wide
# enough that one annotation atop a short construction/chase body covers
# it, narrow enough that a stray shape can't hide behind a distant
# annotation. Reasons stay WHY-only (rule 8): construction, chase body,
# recursive chase, resolve-state probe, structural-by-design sizer, or a
# cited task.
#
# Scope: cmd/wcc cmd/w6c selfhost/cmd/wcc lib/ww (skip *.combined.ww).
# lib/ww/typ.ww is in scope deliberately — it is type.c's ww mirror,
# the accessor/classifier layer itself (B7 ruling: excluding it leaves
# an unwatched file where the forbidden shape could be written).
# Exit code: 0 if clean, 1 with one diagnostic per violation.
set -u
ROOT=${ROOT:-$(cd "$(dirname "$0")/.." && pwd)}
cd "$ROOT"
dirs=
for d in cmd/wcc cmd/w6c selfhost/cmd/wcc lib/ww; do
[ -d "$d" ] && dirs="$dirs $d"
done
[ -z "$dirs" ] && exit 0
files=$(find $dirs \
\( -type f \( -name '*.c' -o -name '*.h' -o -name '*.ww' \) \
! -name '*.combined.ww' -print \) )
[ -z "$files" ] && exit 0
exec awk -v sq="'" '
BEGIN { nviol = 0 }
FNR == 1 {
cur_file = FILENAME
is_c = (cur_file ~ /\.(c|h)$/)
und_exempt_until = 0
prim_exempt_until = 0
in_block = 0
pending_und = 0
}
# Whitelist annotations: arm the exemption windows on the RAW line so an
# annotation inside a comment still counts. `peellint-ok` is the
# already-landed sibling spelling (check.ww construction) — accepted
# as-is, history is not re-spelled. The two windows are independent.
tolower($0) ~ /peel(lint)?-ok([^a-z0-9_]|$)/ {
if (FNR + 9 > und_exempt_until) und_exempt_until = FNR + 9
}
tolower($0) ~ /primsize-ok([^a-z0-9_]|$)/ {
if (FNR + 9 > prim_exempt_until) prim_exempt_until = FNR + 9
}
# Strip comments and string/char literals by character scan: a comment
# opener inside a string is not a comment (E6), and literal text is
# never code. in_block carries across lines; strings/chars do not.
{
code = ""
n = length($0)
i = 1
in_str = 0; in_chr = 0
while (i <= n) {
c = substr($0, i, 1)
c2 = substr($0, i, 2)
if (in_block) {
if (c2 == "*/") { in_block = 0; i += 2 } else i++
continue
}
if (in_str) {
if (c == "\\") i += 2
else { if (c == "\"") in_str = 0; i++ }
continue
}
if (in_chr) {
if (c == "\\") i += 2
else { if (c == sq) in_chr = 0; i++ }
continue
}
if (c2 == "//") break
if (c2 == "/*") { in_block = 1; i += 2; continue }
if (c == "\"") { in_str = 1; i++; continue }
if (c == sq) { in_chr = 1; i++; continue }
code = code c
i++
}
}
{
und_exempt = (FNR <= und_exempt_until)
prim_exempt = (FNR <= prim_exempt_until)
blank = (code ~ /^[ \t]*$/)
# RULE 1 — raw under-token peel. Both C spellings peel:
# p->under and (*p).under / v.under.
if (!und_exempt) {
if (is_c)
ure = "(->|\\.)[ \t]*under([^A-Za-z0-9_]|$)"
else
ure = "\\.[ \t]*under([^A-Za-z0-9_]|$)"
uhit = (code ~ ure)
# Line-split continuation: operator at EOL, token opening the
# next code line. Comment-only lines keep the pend alive.
if (!uhit && pending_und && code ~ /^[ \t]*under([^A-Za-z0-9_]|$)/)
uhit = 1
if (uhit) {
printf("%s:%d: raw under-token read outside the chase accessor; " \
"route via type_chase_named (C) / tichase (ww), or annotate " \
"peel-ok: <why>\n", cur_file, FNR)
nviol++
}
}
# RULE 2 — bare primsize token (ww only). LEFT+RIGHT word bounds
# so the superstring aliasprimsize() is never a hit and a longer
# identifier with a primsize prefix is not matched. Matching the
# token (not `primsize(`) catches the paren-wrap `(primsize)(nm)`,
# the function-value bind `let p = primsize`, and every line-split —
# all compile and reintroduce the alias-blind width (review probes).
if (!is_c && !prim_exempt) {
if (code ~ /(^|[^A-Za-z0-9_])primsize([^A-Za-z0-9_]|$)/) {
printf("%s:%d: bare primsize outside the chase accessor; " \
"route via aliasprimsize (ww), or annotate " \
"primsize-ok: <why>\n", cur_file, FNR)
nviol++
}
}
# Pending updates. An exempt line resets its pend (an annotated
# operator/name must not carry into a non-exempt next line); a blank
# or comment-only line keeps the pend alive; otherwise re-derive.
if (und_exempt) pending_und = 0
else if (!blank) {
if (is_c) pending_und = (code ~ /(->|\.)[ \t]*$/)
else pending_und = (code ~ /\.[ \t]*$/)
}
}
END { exit (nviol > 0 ? 1 : 0) }
' $files

View File

@@ -1,125 +0,0 @@
#!/bin/sh
# tools/sizelint — gate against hardcoded size literals in size-computation
# contexts. Per Drew's framing of Hare's discipline and ww task #64:
# every byte size that names a type's footprint must route through the
# type table (tinfo.size / Type.size / ty_*->size / size(T)). Bare
# numerics encode the layout twice and silently desync (#1, #43, #60,
# #65 sweeps caught one site at a time post-hoc).
#
# Patterns are matched in two tiers:
# 1) Always-on strong signals — direct writes to a type's size /
# slotsize / align field, the `prim(...,size,align)` factory call,
# and any literal in the rhs of `*->size = ` style assignments.
# 2) Context-gated literals — bare 16/24/32 (suffix-tagged or not) only
# inside files or functions whose name matches one of:
# size|slot|elem|field|stride|paramfield|tinfo|primtype|slotsize|
# letemit|tagged
#
# Exempt a single line with an end-of-line `// sizelint-ok: <reason>`
# comment. Use sparingly and cite a task or structural reason.
#
# Scope: cmd/ selfhost/ lib/ (skip ref/ out/ bootstrap/ .combined.ww).
# Exit code: 0 if clean, 1 with one diagnostic per violation.
set -u
ROOT=${ROOT:-$(cd "$(dirname "$0")/.." && pwd)}
cd "$ROOT"
files=$(find cmd selfhost lib \
\( -type d -name out -prune \) -o \
\( -type d -name bootstrap -prune \) -o \
\( -type d -name ref -prune \) -o \
\( -type d -name .git -prune \) -o \
\( -type d -name .claude -prune \) -o \
\( -type d -name .ai -prune \) -o \
\( -type d -name '*.sepwork' -prune \) -o \
\( -type f \( -name '*.c' -o -name '*.h' -o -name '*.ww' \) \
! -name '*.combined.ww' -print \) )
exec awk '
BEGIN {
ctx_re = "size|slot|elem|field|stride|paramfield|tinfo|primtype|slotsize|letemit|tagged"
tagged_lit_re = "(16|24|32)(u64|i64)"
return_lit_re = "return[ \t]+(16|24|32)[ \t]*[;}]"
# Strong signal: assignment to .size / ->size / .slotsize / ->slotsize.
# Value 8 is the natural pointer/scalar size — leave un-flagged so
# typeptr/typechan stay quiet without an allow-list per assignment.
size_assign_re = "(\\.|->)[ \t]*(size|slotsize)[ \t]*=[ \t]*(16|24|32)([^0-9]|$)"
# Strong signal: type-table factory `prim(arena, kind, "name", SIZE, ALIGN)`.
prim_call_re = "\\<prim[ \t]*\\([^)]*\"[A-Za-z_<>]+\"[ \t]*,[ \t]*(16|24|32)"
nviol = 0
}
FNR == 1 {
cur_file = FILENAME
file_in_ctx = (tolower(cur_file) ~ ctx_re)
is_c = (cur_file ~ /\.(c|h)$/)
fn_name = ""
fn_in_ctx = 0
}
# Track function context (ww + C styles).
{
if (match($0, /^[ \t]*(export[ \t]+)?fn[ \t]+[A-Za-z_][A-Za-z0-9_]*/)) {
s = substr($0, RSTART, RLENGTH)
sub(/^[ \t]*(export[ \t]+)?fn[ \t]+/, "", s)
fn_name = s
fn_in_ctx = (tolower(fn_name) ~ ctx_re)
} else if (is_c && match($0, /^[A-Za-z_][A-Za-z0-9_]*\(/)) {
s = substr($0, RSTART, RLENGTH - 1)
fn_name = s
fn_in_ctx = (tolower(fn_name) ~ ctx_re)
}
}
# Allow-list (case-insensitive on the keyword) — check the raw line so a
# doc comment can still exempt itself. Accepts both `// sizelint-ok:` (ww
# and C99) and `/* sizelint-ok: ... */` (Plan 9 C style).
tolower($0) ~ /sizelint-ok:/ { next }
# Strip line and block comments before pattern matching — a comment that
# quotes a hardcoded size in prose is not a violation. Single-line block
# comments only; multi-line `/* ... */` spans rarely contain assignments
# we care about.
{
code = $0
sub(/\/\/.*$/, "", code)
gsub(/\/\*[^*]*\*+([^/*][^*]*\*+)*\//, "", code)
}
# Always-on strong-signal patterns.
{
if (match(code, size_assign_re)) {
report(substr(code, RSTART, RLENGTH),
"assignment to .size/.slotsize with literal; route via type SSoT (tinfo.size / ty_*->size)")
}
if (match(code, prim_call_re)) {
report(substr(code, RSTART, RLENGTH),
"literal in prim(...) size slot; type-table is SSoT (cmd/wcc/type.c, lib/ww/typ.ww)")
}
}
# Context-gated patterns.
file_in_ctx || fn_in_ctx {
line = code
while (match(line, tagged_lit_re)) {
report(substr(line, RSTART, RLENGTH),
"size literal in size-context; route via size(T) / primtypesize / tyslicesize SSoT")
line = substr(line, RSTART + RLENGTH)
}
if (match(code, return_lit_re)) {
report(substr(code, RSTART, RLENGTH),
"return of bare size literal in size-context; route via type SSoT")
}
}
function report(snip, msg) {
# Trim trailing newline / extra whitespace from snippet.
gsub(/[ \t]+$/, "", snip)
printf("%s:%d: %s; suggest: %s\n", cur_file, FNR, snip, msg)
nviol++
}
END { exit (nviol > 0 ? 1 : 0) }
' $files