selfhost/cmd+test: run check before cgen in wwstage drivers

w6c_ww and wwdump_ww (-c mode) silently passed source into cgen
without type-checking it; any type error flowed through as broken
asm with exit 0. Mirror cstage cmd/w6c/main.c:73-75: between the
parse-error gate and cgeninit, install typesinit + checkinit +
checkfile + `if (ck.errs > 0) return 1;`. Cgen path unchanged —
drivers own check, cgen owns emit (checkfile not idempotent due to
installdecl scopedefine).

The wire-up was blocked by five latent check.ww divergences from
cstage, all landed first: cross-module type refs (#51), enum-int
reinterprets (#52), per-block scoping (#53), nominal-first
tagged-variant inclusion (#55), and same-module N_IDENT callee
preference (#56). With those clear, the broader exercise of
check across every selfhost driver reaches 131/131 first try.

994_w6c_ww grows by one row: a trivially-wrong `let x: i32 =
"hello";` smoke pins both stages to exit-non-zero. Pre-#50 it
emitted 202 bytes of broken asm with exit=0.

Unblocks #42 (size/align/offset wwstage intercepts) and the
audit-§1.8 UP-polarity refactor (node.type_ population can now
land in the same check pass we just wired up).
This commit is contained in:
2026-05-20 04:52:16 +09:00
parent 3fa5ccd5ae
commit c58d3511e8
5 changed files with 87 additions and 0 deletions

View File

@@ -22442,6 +22442,19 @@ export fn main(argc: i32, argv: **u8) i32 = {
// cgen and emits junk asm with a zero exit (silent miscompile).
if (l.errs > 0 || ps.errs > 0) { return 1; };
// #50: run check before cgen so AST mutations from #42 (size/align/
// offset fold) and the audit §1.8 node.type_ population land before
// cgen walks the file. Mirrors cmd/w6c/main.c:73-75. Five precondition
// fixes for fixture cleanliness: #51 cross-module type refs, #52
// enum↔int reinterpret, #53 N_BLOCK scoping, #55 nominal-first variant
// compare, #56 bare-leaf same-module preference.
let tc: tctx;
typesinit(&tc, ar);
let ck: checker;
checkinit(&ck, ar, &tc);
checkfile(&ck, f);
if (ck.errs > 0) { return 1; };
let cg: cgen;
cgeninit(&cg, ar);
cgfile(&cg, f);

View File

@@ -147,6 +147,19 @@ export fn main(argc: i32, argv: **u8) i32 = {
// cgen and emits junk asm with a zero exit (silent miscompile).
if (l.errs > 0 || ps.errs > 0) { return 1; };
// #50: run check before cgen so AST mutations from #42 (size/align/
// offset fold) and the audit §1.8 node.type_ population land before
// cgen walks the file. Mirrors cmd/w6c/main.c:73-75. Five precondition
// fixes for fixture cleanliness: #51 cross-module type refs, #52
// enum↔int reinterpret, #53 N_BLOCK scoping, #55 nominal-first variant
// compare, #56 bare-leaf same-module preference.
let tc: tctx;
typesinit(&tc, ar);
let ck: checker;
checkinit(&ck, ar, &tc);
checkfile(&ck, f);
if (ck.errs > 0) { return 1; };
let cg: cgen;
cgeninit(&cg, ar);
cgfile(&cg, f);

View File

@@ -22454,6 +22454,17 @@ export fn main(argc: i32, argv: **u8) i32 = {
let ps: parser;
parserinit(&ps, a, &l);
let f: *node = parsefile(&ps);
// #50: mirror w6c — run check before cgen so AST mutations
// from #42 (size/align/offset fold) and audit §1.8 (node.type_
// population) land before cgen walks. Without this, wwdump -c
// (the byte-identity probe for 994) would diverge from w6c_ww
// on any program that uses the size/align/offset typed builtins.
let tc: tctx;
typesinit(&tc, a);
let ck: checker;
checkinit(&ck, a, &tc);
checkfile(&ck, f);
if (ck.errs > 0) { return 1; };
let cg: cgen;
cgeninit(&cg, a);
cgfile(&cg, f);

View File

@@ -159,6 +159,17 @@ export fn main(argc: i32, argv: **u8) i32 = {
let ps: parser;
parserinit(&ps, a, &l);
let f: *node = parsefile(&ps);
// #50: mirror w6c — run check before cgen so AST mutations
// from #42 (size/align/offset fold) and audit §1.8 (node.type_
// population) land before cgen walks. Without this, wwdump -c
// (the byte-identity probe for 994) would diverge from w6c_ww
// on any program that uses the size/align/offset typed builtins.
let tc: tctx;
typesinit(&tc, a);
let ck: checker;
checkinit(&ck, a, &tc);
checkfile(&ck, f);
if (ck.errs > 0) { return 1; };
let cg: cgen;
cgeninit(&cg, a);
cgfile(&cg, f);

View File

@@ -248,6 +248,45 @@ main(void)
n++;
}
/* #50: the check pass is wired into both wwdump_ww -c and
* w6c_ww in front of cgen. Pre-#50, a hard type error parsed
* cleanly and reached cgen, which would emit asm with a zero
* exit (silent miscompile). Pin the rejection by giving each
* driver a trivially-broken file and demanding non-zero exit. */
struct { const char *label; const char *src; } bad[] = {
{ "let_str_to_i32",
"package main;\n"
"fn main() i32 = {\n"
" let x: i32 = \"hello\";\n"
" return x;\n"
"};\n" },
{ NULL, NULL },
};
for (int i = 0; bad[i].label; i++) {
char src[64], wcmd[2048], dcmd[2048], s[64];
snprintf(src, sizeof src, "/tmp/wwc6_bad_%d_%d.ww", getpid(), i);
snprintf(s, sizeof s, "/tmp/wwc6_bad_%d_%d.s", getpid(), i);
if (write_file(src, bad[i].src) != 0) { fail++; n++; continue; }
snprintf(wcmd, sizeof wcmd,
"%s/w6c_ww -o %s %s >/dev/null 2>&1", bin, s, src);
snprintf(dcmd, sizeof dcmd,
"%s/wwdump_ww -c %s >%s 2>/dev/null", bin, src, s);
if (runwait(wcmd) == 0) {
fprintf(stderr,
"w6c_ww FAIL: %s slipped past check (silent miscompile)\n",
bad[i].label);
fail++;
}
if (runwait(dcmd) == 0) {
fprintf(stderr,
"wwdump_ww -c FAIL: %s slipped past check (silent miscompile)\n",
bad[i].label);
fail++;
}
unlink(src); unlink(s);
n++;
}
if (fail) {
fprintf(stderr, "w6c_ww: %d/%d diff(s) failed\n", fail, n);
return 1;