From d48b53d04d87c82dddc08678c4ce8fdac24e7c26 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Mon, 22 Jun 2026 15:34:36 +0900 Subject: [PATCH] runww: //ww:error arm asserts both stages reject (dual-stage) A rejected program emits no .s, so the test-lang-byteid (T2) gate cannot cover wwstage-reject -- yet the retired C twins asserted that BOTH stages reject with the same diagnostic. runww's //ww:error arm was cstage-only, so migrating reject rows onto it would silently drop the wwstage-reject coverage the C twins carried. Run w6c_ww (wwstage) alongside w6c (cstage) on each //ww:error case and require both to fail with the shared diagnostic body present. The body is identical across stages; only cstage's leading prefix differs, so the substring matches the body alone (no file:line). An ERROR row now reports PASS dual / FAIL cstage / FAIL wwstage. w6c_ww resolves off the same $BIN as the C twins -- no new harness threading. Two pilot reject cases (runww_dup_main_reject, runww_dup_type_reject) exercise the dual-stage path; the wwstage leg is proven non-vacuous (a cstage-rejects/wwstage-accepts case reports FAIL wwstage). Prerequisite for migrating fold-3 reject rows onto runww. --- test/runww.ww | 65 +++++++++++++++++---- test/wcc/data/runww_dup_main_reject/case.ww | 8 +++ test/wcc/data/runww_dup_type_reject/case.ww | 9 +++ 3 files changed, 71 insertions(+), 11 deletions(-) create mode 100644 test/wcc/data/runww_dup_main_reject/case.ww create mode 100644 test/wcc/data/runww_dup_type_reject/case.ww diff --git a/test/runww.ww b/test/runww.ww index bbcc70bf..e21c1511 100644 --- a/test/runww.ww +++ b/test/runww.ww @@ -5,14 +5,22 @@ // directive holds. This replaces the per-test C harnesses that embedded ww // source as C string literals and duplicated fork/exec/grep boilerplate. // -// This is T1: the routine behavioral-correctness gate, cstage-only. The -// wwstage-behavior run is deliberately NOT here — per the rob/USER tier+stage -// split, a wwstage miscompile is already caught by T2 (cstage.s vs wwstage.s -// byte-id over the same case.ww corpus, strictly more sensitive than re-running -// behavior) or by T1's own cstage run (the both-wrong-identical blind spot is -// cstage being wrong, which only a behavioral check on cstage catches). So -// rule-10 stage-symmetry is enforced by T2 (a separate pre-push tool), not by -// runww; routine runs stay cstage-only. +// This is T1: the routine behavioral-correctness gate, cstage-only for the +// RUN/RUNEXIT/COMPILE arms. The wwstage-behavior run is deliberately NOT here +// — per the rob/USER tier+stage split, a wwstage miscompile is already caught +// by T2 (cstage.s vs wwstage.s byte-id over the same case.ww corpus, strictly +// more sensitive than re-running behavior) or by T1's own cstage run (the +// both-wrong-identical blind spot is cstage being wrong, which only a +// behavioral check on cstage catches). So rule-10 stage-symmetry is enforced +// by T2 (a separate pre-push tool), not by runww; routine runs stay cstage-only. +// +// The ERROR arm is the one exception: it runs BOTH stages. A rejected program +// emits no `.s`, so T2's `.s`-cmp gate structurally CANNOT cover wwstage-reject +// (drew-fold3-spec §1a). The retired C reject twins (948/949/944) asserted both +// w6c AND w6c_ww reject with the same diagnostic body, so runww's ERROR arm runs +// w6c_ww directly on the same case.ww and asserts it ALSO fails with the +// substring present (#20 non-vacuity) — the coverage-equivalence safety net for +// reject-row migration. // // Directives (first `//ww:` line of the case): // //ww:run compile + execute, expect exit 0 @@ -244,6 +252,29 @@ fn runcase(drv: str, casepath: str, d: *dirv, errpath: str, tmpout: str) bool = return false; }; +// wwerror — the ERROR arm's wwstage leg: invoke w6c_ww directly on the same +// case.ww (mirroring the retired C twins' `$BIN/w6c_ww -o ` direct +// invocation, not the `ww` driver — w6c_ww is the bare frontend compiler) and +// assert it ALSO hard-rejects with the shared diagnostic body present. #20 +// non-vacuity: passes ONLY if w6c_ww failed AND the substring is on stderr. +fn wwerror(wdrv: str, casepath: str, d: *dirv, errpath: str, tmpout: str) bool = { + let argv: []*u8 = alloc([], 5u64)!; + argv.len = 5; + argv[0] = cstr(wdrv); + argv[1] = cstr("-o"); + argv[2] = cstr(tmpout); + argv[3] = cstr(casepath); + argv[4] = nil: *u8; + + let rc: i32 = runcap(wdrv, argv, errpath); + if (rc == 0) { return false; }; // w6c_ww accepted → wwstage-reject dropped + let eb: []u8 = alloc([], 65536u64)!; + eb.len = 65536; + let en: i64 = readfile(errpath, eb); + if (en < 0i64) { return false; }; + return contains(eb.ptr, en: i32, mkstr(d.subp, d.subn)); +}; + export fn main() int = { let a: []str = os.args(); if (a.len < 2) { @@ -257,6 +288,9 @@ export fn main() int = { case void => { }; }; let cdrv: str = joinp(bin, "/ww"); + // ERROR arm goes dual-stage: w6c_ww (the bare wwstage frontend) is + // resolved off the SAME $BIN the C reject twins used ($BIN/w6c_ww). + let wdrv: str = joinp(bin, "/w6c_ww"); // Fixed scratch paths: the pilot drives cases sequentially in one // process, so reuse is safe; per-pid uniqueness for parallel invocation @@ -289,11 +323,20 @@ export fn main() int = { }; total += 1; - if (runcase(cdrv, casepath, &d, errpath, tmpout)) { - pr("PASS cstage "); pr(casepath); pr("\n"); - } else { + let pass: bool = runcase(cdrv, casepath, &d, errpath, tmpout); + if (!pass) { pr("FAIL cstage "); pr(casepath); pr("\n"); fail += 1; + } else if (d.kind == dkind.ERROR) { + // cstage rejected; now demand wwstage rejects identically. + if (wwerror(wdrv, casepath, &d, errpath, tmpout)) { + pr("PASS dual "); pr(casepath); pr("\n"); + } else { + pr("FAIL wwstage "); pr(casepath); pr("\n"); + fail += 1; + }; + } else { + pr("PASS cstage "); pr(casepath); pr("\n"); }; ci += 1; diff --git a/test/wcc/data/runww_dup_main_reject/case.ww b/test/wcc/data/runww_dup_main_reject/case.ww new file mode 100644 index 00000000..e471e165 --- /dev/null +++ b/test/wcc/data/runww_dup_main_reject/case.ww @@ -0,0 +1,8 @@ +//ww:error "duplicate fn main" +// Pilot for #7a: two `fn main` definitions must be LOUD-rejected by BOTH +// stages. The substring is the shared diagnostic body (no file:line prefix, +// which differs cstage vs wwstage) — the #20 non-vacuity anchor and the +// dual-stage (w6c AND w6c_ww) reject-coverage proof. +package main; +fn main() i32 = { return 0; }; +fn main() i32 = { return 1; }; diff --git a/test/wcc/data/runww_dup_type_reject/case.ww b/test/wcc/data/runww_dup_type_reject/case.ww new file mode 100644 index 00000000..ef508ff0 --- /dev/null +++ b/test/wcc/data/runww_dup_type_reject/case.ww @@ -0,0 +1,9 @@ +//ww:error "duplicate type t" +// Pilot for #7a: a redefined type alias must be LOUD-rejected by BOTH stages. +// The substring is the shared diagnostic body (no file:line prefix, which +// differs cstage vs wwstage) — the #20 non-vacuity anchor and the dual-stage +// (w6c AND w6c_ww) reject-coverage proof. +package main; +type t = i32; +type t = i64; +fn main() i32 = { return 0; };