diff --git a/Makefile b/Makefile index 417712ef..db3edadb 100644 --- a/Makefile +++ b/Makefile @@ -3129,8 +3129,10 @@ $(BIN)/test_regex_run: test/wcc/989_regex_run.c $(BIN)/ww $(BIN)/w6c \ $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< -$(BIN)/test_lib_byteid: test/wcc/989_lib_byteid.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) +$(BIN)/test_lib_byteid: test/wcc/989_lib_byteid.c \ + $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ + $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ + $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< $(BIN)/test_m2wwi_run: test/wcc/989_m2wwi_run.c $(BIN)/ww $(BIN)/w6c \ diff --git a/test/wcc/989_lib_byteid.c b/test/wcc/989_lib_byteid.c index 8e75f998..51a1a032 100644 --- a/test/wcc/989_lib_byteid.c +++ b/test/wcc/989_lib_byteid.c @@ -7,31 +7,41 @@ * utf8 + lex/parse/wcc/ww). Every other lib/ module compiles through * the CSTAGE driver alone (the 9xx _run tests), so a cs≠ww divergence * there ships gate-green — regex.finish was cs≠ww for weeks this way - * (task #21, FC0). This gate closes the class: each lib test fixture's - * resolved unit (driver-shaped .combined.ww, cf 990 resolveunit) is - * compiled through BOTH w6c and w6c_ww and the asm byte-compared. + * (task #21, FC0). This gate closes the class: each lib test fixture is + * sep-built through BOTH driver stages and the per-package asm compared. + * + * #94 sep layout: the gate drives `ww --sep` (cstage) and `ww_ww --sep` + * (wwstage) instead of feeding a single .combined.ww to raw w6c/w6c_ww. + * Under sep EACH package compiles to its OWN .sepwork/.s — the + * lib body lands in .s, NOT in __root.s (which holds only the test + * fixture / probe main). So the byte-id MUST concat EVERY .s (the + * imported lib body + lib/test's auto-bundled runner included), never + * just __root.s — comparing the root alone would byte-id the harness + * wrapper while the engine ships uncovered (the green-covering-nothing + * trap this gate exists to close). * * Three pinned outcomes, loud over blind (task #21 ruling): - * ID — byte-identical, zero tolerance. + * ID — byte-identical across all per-package .s, zero tolerance. * DIVERGE — known cs≠ww divergence, documented in task #59 with the * first-diff symbol. Both stages must still compile and - * the outputs must still DIFFER: when a fix lands, the - * entry fails loudly demanding graduation to ID rather + * the concatenated asm must still DIFFER: when a fix lands, + * the entry fails loudly demanding graduation to ID rather * than silently widening or narrowing coverage. - * WWREJECT — w6c compiles, w6c_ww errors (front-end gap, task #59). - * Graduation is pinned the same way. + * WWREJECT — cstage --sep compiles, wwstage --sep errors (front-end + * gap, task #59). Graduation is pinned the same way. * * Modules with no test fixture and no embedding (sort/path/endian/net/ * hash/fnv/crypto.math/c.libc) are covered via import-probe stubs: - * `import` drags the whole module into the combined unit, so both + * `import` drags the whole module into its own sep package, so both * cgens chew the full module body. The driver silently skips an * unresolvable import, so each probe carries a sentinel proving the - * body landed; and a corpus-completeness scan fails loudly on any - * lib/ dir not enrolled here, so new modules can't ship uncovered. + * body landed in a per-package .unit.ww; and a corpus-completeness scan + * fails loudly on any lib/ dir not enrolled here, so new modules can't + * ship uncovered. * - * Compile+cmp only — no driver run, no source-tree writes (everything - * lives in a getpid-keyed /tmp dir), so this is phase-1 parallel-safe - * next to the 9xx _run tests that write stem artifacts in-tree. + * Compile+cmp only (--sep -o redirects every artifact to a getpid-keyed + * /tmp dir; WW_PKGCACHE pinned there so out/.pkgcache is untouched), so + * this stays phase-1 parallel-safe next to the 9xx _run tests. * * 9xx is full; shares the 989 prefix per the 989_sha256 precedent (the * `short` name keys the binary). @@ -217,7 +227,7 @@ static int check_one(const char *bin, const char *cwd, const struct ent *e, int idx) { const char *label = e->fixture ? e->fixture : "import-probe"; - char td[64], cmd[4096]; + char td[64], cmd[8192]; int rc = -1; snprintf(td, sizeof td, "/tmp/wwlbi_%d_%d", getpid(), idx); @@ -248,8 +258,7 @@ check_one(const char *bin, const char *cwd, const struct ent *e, int idx) /* dirname(fixture) leads the search path so bare same-module * imports (`import base32` from lib/encoding/base32) resolve as - * they do under the in-tree driver run; resolveunit's bare-copy - * shape loses that and mis-drops modules (silent expand skip). */ + * they do under the in-tree driver run. */ char incs[2048] = ""; if (e->fixture) { const char *slash = strrchr(e->fixture, '/'); @@ -261,73 +270,63 @@ check_one(const char *bin, const char *cwd, const struct ent *e, int idx) snprintf(incs + n, sizeof incs - n, " -I %s/%s", cwd, e->inc); } - /* exit deliberately ignored: the combined unit is written before - * codegen (cf 990 resolveunit) and only it matters here. A @test - * fixture builds via `ww test -c -o ` (compile-only) so the - * resolved unit AUTO-BUNDLES lib/test — the #17 synth's `run()` callee - * must resolve when w6c/w6c_ww -T compile the combined below. Both the - * `-o ` (test) and the next-to-source (build) paths land the - * combined at .combined.ww. Import-probes carry their own main - * and stay a plain non-test build. */ char stem[512]; snprintf(stem, sizeof stem, "%s", base); char *sd = strrchr(stem, '.'); if (sd) *sd = '\0'; - if (e->fixture) - snprintf(cmd, sizeof cmd, - "cd %s && timeout 180 %s/ww test -c -o %s %s %s >/dev/null 2>&1", - td, bin, stem, incs, base); - else - snprintf(cmd, sizeof cmd, - "cd %s && timeout 180 %s/ww build %s %s >/dev/null 2>&1", - td, bin, incs, base); - runwait(cmd); - char comb[512]; - snprintf(comb, sizeof comb, "%s/%s.combined.ww", td, stem); - if (access(comb, 0) != 0) { - fprintf(stderr, "lib_byteid FAIL: %s — no resolved unit\n", label); + /* Two sep driver builds. @test fixtures are main-less, so `test -c + * --sep` carries -T (synthesizes the entry + keeps @test fns + auto- + * bundles lib/test's run() callee) and emits per-package asm without + * a harness run; import-probes carry their own `fn main()`, which -T + * loud-rejects (910), so they `build --sep` (non-T). Each stage's + * artifacts land under its own -o stem so out/ and the source tree + * stay clean; WW_PKGCACHE is pinned to td. */ + const char *sub = e->fixture ? "test -c --sep" : "build --sep"; + char stemc[600], stemw[600]; + snprintf(stemc, sizeof stemc, "%s/c_%s", td, stem); + snprintf(stemw, sizeof stemw, "%s/w_%s", td, stem); + + snprintf(cmd, sizeof cmd, + "cd %s && WW_PKGCACHE=%s/pc timeout 180 %s/ww %s %s -o %s %s " + ">/dev/null 2>&1", td, td, bin, sub, incs, stemc, base); + int ce = runwait(cmd); + snprintf(cmd, sizeof cmd, + "cd %s && WW_PKGCACHE=%s/pw timeout 180 %s/ww_ww %s %s -o %s %s " + ">/dev/null 2>&1", td, td, bin, sub, incs, stemw, base); + int we = runwait(cmd); + + /* the cstage --sep build resolves the units (root + each package); + * its __root.unit.ww proves resolution ran. WWREJECT still requires + * cstage to compile, so this holds for every mode. */ + char rootu[760]; + snprintf(rootu, sizeof rootu, "%s.sepwork/__root.unit.ww", stemc); + if (ce != 0 || access(rootu, 0) != 0) { + fprintf(stderr, "lib_byteid FAIL: %s — cstage --sep produced " + "no resolved unit\n", label); goto out; } + + /* probe coverage: the driver SILENTLY SKIPS an unresolvable import + * (cmd/ww/main.c locate_import), shrinking the probe to an empty main + * that byte-ids trivially. The sentinel must appear in some per- + * package .unit.ww (the lib body's own sep unit, not __root) to prove + * the module landed. Fixtures self-protect by calling module symbols. */ if (e->sentinel) { - char *cb = NULL; - size_t cl = 0; - if (slurp(comb, &cb, &cl) < 0) { - fprintf(stderr, "lib_byteid FAIL: %s — cannot read " - "resolved unit\n", label); - goto out; - } - int hit = strstr(cb, e->sentinel) != NULL; - free(cb); - if (!hit) { + snprintf(cmd, sizeof cmd, + "grep -Fq -- '%s' %s.sepwork/*.unit.ww >/dev/null 2>&1", + e->sentinel, stemc); + if (runwait(cmd) != 0) { fprintf(stderr, "lib_byteid FAIL: %s — import " - "silently dropped (no \"%s\" in resolved unit); " - "probe covers nothing\n", label, e->sentinel); + "silently dropped (no \"%s\" in any per-package " + "unit); probe covers nothing\n", label, e->sentinel); goto out; } } - char cs[512], ws[512]; - snprintf(cs, sizeof cs, "%s/c.s", td); - snprintf(ws, sizeof ws, "%s/w.s", td); - /* Fixtures are main-less @test files: -T synthesizes the entry and - * keeps the @test fns symmetrically (non-T drops them on both stages, - * #6, so byte-id needs -T). Import-probes carry their own - * `fn main()`, which -T loud-rejects (910), so they stay non-T. */ - const char *tflag = e->fixture ? "-T " : ""; - snprintf(cmd, sizeof cmd, "timeout 180 %s/w6c %s%s > %s 2>/dev/null", - bin, tflag, comb, cs); - if (runwait(cmd) != 0) { - fprintf(stderr, "lib_byteid FAIL: %s — w6c rejected\n", label); - goto out; - } - snprintf(cmd, sizeof cmd, "timeout 180 %s/w6c_ww %s-o %s %s >/dev/null 2>&1", - bin, tflag, ws, comb); - int wrc = runwait(cmd); - if (e->mode == M_WWREJECT) { - if (wrc == 0) { - fprintf(stderr, "lib_byteid FAIL: %s — w6c_ww now " + if (we == 0) { + fprintf(stderr, "lib_byteid FAIL: %s — wwstage --sep now " "compiles this; graduate the %s pin to ID or " "DIVERGE\n", label, e->cite); goto out; @@ -335,11 +334,26 @@ check_one(const char *bin, const char *cwd, const struct ent *e, int idx) rc = 0; goto out; } - if (wrc != 0) { - fprintf(stderr, "lib_byteid FAIL: %s — w6c_ww rejected\n", label); + if (we != 0) { + fprintf(stderr, "lib_byteid FAIL: %s — wwstage --sep rejected\n", + label); goto out; } + /* byte-id over EVERY per-package .s (lib body + lib/test included), + * never just __root.s — see the header note. Identical package sets + * both stages, so the *.s glob (shell-sorted) concatenates in the + * same order on each side. */ + char cs[800], ws[800]; + snprintf(cs, sizeof cs, "%s/all_c.s", td); + snprintf(ws, sizeof ws, "%s/all_w.s", td); + snprintf(cmd, sizeof cmd, "cat %s.sepwork/*.s > %s 2>/dev/null", + stemc, cs); + if (system(cmd)) {} + snprintf(cmd, sizeof cmd, "cat %s.sepwork/*.s > %s 2>/dev/null", + stemw, ws); + if (system(cmd)) {} + char *bc = NULL, *bw = NULL; size_t nc = 0, nw = 0; if (slurp(cs, &bc, &nc) < 0 || slurp(ws, &bw, &nw) < 0) { @@ -376,9 +390,10 @@ static const char *covered[] = { "lib/io", "lib/math", "lib/rt", "lib/types", /* module body dragged into the lib/strconv/test fixtures */ "lib/strconv", - /* #17: the @test runner is AUTO-BUNDLED into every -T combined, so - * every @test fixture above byte-ids it cs/ww; 911_attest_record also - * compares it directly. It has no _test.ww of its own. */ + /* #17: the @test runner is AUTO-BUNDLED into every -T sep build (its + * own test.sepwork/test.s), so every @test fixture above byte-ids it + * cs/ww; 911_attest_record also compares it directly. It has no + * _test.ww of its own. */ "lib/test", NULL, };