From c58d3511e834bd875adf33a49b29a64f22eda8ef Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 20 May 2026 04:52:16 +0900 Subject: [PATCH] selfhost/cmd+test: run check before cgen in wwstage drivers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- selfhost/cmd/w6c/main.combined.ww | 13 ++++++++++ selfhost/cmd/w6c/main.ww | 13 ++++++++++ selfhost/cmd/wwdump/main.combined.ww | 11 ++++++++ selfhost/cmd/wwdump/main.ww | 11 ++++++++ test/wcc/994_w6c_ww.c | 39 ++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index d448cd45..cfa28ad7 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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); diff --git a/selfhost/cmd/w6c/main.ww b/selfhost/cmd/w6c/main.ww index cae8312f..6db6ed5b 100644 --- a/selfhost/cmd/w6c/main.ww +++ b/selfhost/cmd/w6c/main.ww @@ -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); diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index c773f9c7..4aac9a23 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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); diff --git a/selfhost/cmd/wwdump/main.ww b/selfhost/cmd/wwdump/main.ww index 06356b59..40f5c906 100644 --- a/selfhost/cmd/wwdump/main.ww +++ b/selfhost/cmd/wwdump/main.ww @@ -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); diff --git a/test/wcc/994_w6c_ww.c b/test/wcc/994_w6c_ww.c index ee82ecb5..5e6c55a0 100644 --- a/test/wcc/994_w6c_ww.c +++ b/test/wcc/994_w6c_ww.c @@ -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;