test: re-point 990/994/#110 off combined.ww-input to sep-feed (M4 E4-keystone, #22)

The E3 driver flip makes build_one_sep the sole path; it writes
.sepwork/*.unit.ww, not <stem>.combined.ww, so the flip stops producing
combined.ww. Migrate its consumers to sep-feed first, while both build
paths still exist (reversible; all 5 stage binaries stay byte-identical):

- 990_selfhost: resolveunit drives `ww build --sep` and reads
  <stem>.sepwork/__root.unit.ww; probe_cgen_match gains err.ww (sep
  inlines fmt's .wwi only, so cs==ww holds); probe_ww_links re-expressed
  as `ww_ww build --sep`->run; probe_bootstrap_fixed_point removed (the
  monolith path it tested is deleted by E4 -- the self-application axis
  lives in `make bootstrap`, the cs==ww axis in 995).
- 994_w6c_ww: drop the combined-fed emit_eq + corpus; keep the live
  per-package cs-sep==ww-sep oracle (cmp_sepwork + diff_one + bad[]).
- #110 combined_ww_fresh gate removed (its regen-vs-committed premise
  dies with its producer).

Non-vacuity proven: a one-line emitter mutation reddens 994's per-package
cs!=ww check across 15 packages; revert restores green.
This commit is contained in:
2026-06-18 02:49:09 +09:00
parent 2c62be44a7
commit 5f85852faf
3 changed files with 80 additions and 249 deletions

View File

@@ -187,25 +187,6 @@ if [ "${UNIT:-0}" != "1" ]; then
done | xargs -0 -n2 -P "$JOBS" "$0" --one || true done | xargs -0 -n2 -P "$JOBS" "$0" --one || true
p2par_end=$(date +%s.%N) p2par_end=$(date +%s.%N)
fi fi
# combined.ww freshness gate (#110): 990/995 above unconditionally
# regenerate every tracked combined.ww amalgamation, so a diff vs HEAD
# here means an embedded source changed without its derived artifact
# being regenerated + committed (the #28 staleness hole the per-file
# byte-id gates were blind to). git ls-files keeps new combined.ww
# auto-covered (rule-13 SSoT). Lives inside the UNIT guard because
# test-unit skips 990/995 and thus the regen.
name=combined_ww_fresh
if git diff --exit-code -- $(git ls-files '*.combined.ww') > /dev/null 2>&1; then
printf 'ok %s\n' "$name" > "$RESULTS/$name.status"
else
{
printf 'FAIL %s (stale; regen + commit the derived amalgamation)\n' "$name"
echo '--- stale combined.ww ---'
git diff --name-only -- $(git ls-files '*.combined.ww')
} > "$RESULTS/$name.status"
: > "$RESULTS/$name.fail"
fi
fi fi
fail=0 fail=0

View File

@@ -518,21 +518,21 @@ cleanup:
} }
/* resolveunit — produce <fixture>'s RESOLVED translation unit the way /* resolveunit — produce <fixture>'s RESOLVED translation unit the way
* the driver does. `ww build` concatenates the module with its * the sep driver does. `ww build --sep` composes the root package's
* transitive imports into <stem>.combined.ww (cmd/ww/main.c expand()), * <stem>.sepwork/__root.unit.ww: the transitive deps' `.wwi` interface
* which is the single unit w6c/wwdump actually consume; w6c never * stubs followed by the root package body — the single self-contained
* resolves imports itself (cmd/w6c/main.c reads one pre-concatenated * unit w6c/wwdump consume for the root (the producer compiles it with no
* file). Feeding a raw module file therefore leaves its import refs * -I; cmd/ww/main.c sep_compose_unit). Feeding a raw module file instead
* (os.write, fmt.errorln, strconv.i64tos …) unresolved — a partial * would leave its import refs (os.write, fmt.errorln, strconv.i64tos …)
* unit harec's module::resolve never hands the checker, and which the * unresolved — a partial unit harec's module::resolve never hands the
* ww-stage asserttyped invariant (check.ww) is not meant to see. * checker, and which the ww-stage asserttyped invariant must not see.
* *
* Built in a private /tmp dir (a copy of the fixture) so no .combined.ww * Built in a private /tmp dir (a copy of the fixture) so the sepwork
* / .s / .o lands next to the repo source. The link step fails for an * lands off the repo source tree. The link step fails for an import-only
* import-only module (no main), but .combined.ww is written before * module (no main), but the unit is written before the link, so we detect
* codegen, so we detect that artifact rather than gating on the build * that artifact rather than gating on the build exit. Writes the owning
* exit. Writes the owning tmpdir into `td` (caller rm -rf's it) and the * tmpdir into `td` (caller rm -rf's it) and the unit path into `out`.
* combined path into `out`. Returns 0 on success, -1 otherwise. */ * Returns 0 on success, -1 otherwise. */
static int static int
resolveunit(const char *bin, const char *cwd, const char *fixture, resolveunit(const char *bin, const char *cwd, const char *fixture,
int idx, char *td, size_t tdsz, char *out, size_t outsz) int idx, char *td, size_t tdsz, char *out, size_t outsz)
@@ -549,18 +549,19 @@ resolveunit(const char *bin, const char *cwd, const char *fixture,
snprintf(cmd, sizeof cmd, "cp %s/%s %s", cwd, fixture, tmpsrc); snprintf(cmd, sizeof cmd, "cp %s/%s %s", cwd, fixture, tmpsrc);
runwait(cmd); runwait(cmd);
snprintf(cmd, sizeof cmd, snprintf(cmd, sizeof cmd,
"cd %s && timeout 180 %s/ww build %s >/dev/null 2>&1", td, bin, base); "cd %s && timeout 180 %s/ww build --sep %s >/dev/null 2>&1",
td, bin, base);
runwait(cmd); runwait(cmd);
snprintf(out, outsz, "%s", tmpsrc); char stem[768];
char *dot = strrchr(out, '.'); snprintf(stem, sizeof stem, "%s", tmpsrc);
char *dot = strrchr(stem, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
size_t n = strlen(out); snprintf(out, outsz, "%s.sepwork/__root.unit.ww", stem);
snprintf(out + n, outsz - n, ".combined.ww");
return access(out, 0) == 0 ? 0 : -1; return access(out, 0) == 0 ? 0 : -1;
} }
/* Probe 4: ww-side checker (wwdump_ww -r) runs name resolution on each /* Probe 4: ww-side checker (wwdump_ww -r) runs name resolution on each
* selfhost fixture's RESOLVED unit (imports concatenated — see * selfhost fixture's RESOLVED unit (sep root unit — see
* resolveunit) and reports zero unresolved. The raw file would leave * resolveunit) and reports zero unresolved. The raw file would leave
* its `import` refs (os/fmt/strconv/ascii members) unresolved, i.e. the * its `import` refs (os/fmt/strconv/ascii members) unresolved, i.e. the
* partial unit the asserttyped invariant must never see (harec resolves * partial unit the asserttyped invariant must never see (harec resolves
@@ -578,9 +579,9 @@ probe_resolve(const char *bin)
}; };
int fail = 0; int fail = 0;
for (int i = 0; fixtures[i]; i++) { for (int i = 0; fixtures[i]; i++) {
char td[64], combined[1024], a[256], cmd[2048]; char td[64], unit[1024], a[256], cmd[2048];
if (resolveunit(bin, cwd, fixtures[i], i, td, sizeof td, if (resolveunit(bin, cwd, fixtures[i], i, td, sizeof td,
combined, sizeof combined) != 0) { unit, sizeof unit) != 0) {
fprintf(stderr, "resolve FAIL: %s — no resolved unit\n", fprintf(stderr, "resolve FAIL: %s — no resolved unit\n",
fixtures[i]); fixtures[i]);
fail++; fail++;
@@ -588,7 +589,7 @@ probe_resolve(const char *bin)
} }
snprintf(a, sizeof a, "/tmp/wwd_r_%d", getpid()); snprintf(a, sizeof a, "/tmp/wwd_r_%d", getpid());
snprintf(cmd, sizeof cmd, "timeout 180 %s/wwdump_ww -r %s > %s 2>/dev/null", snprintf(cmd, sizeof cmd, "timeout 180 %s/wwdump_ww -r %s > %s 2>/dev/null",
bin, combined, a); bin, unit, a);
int rc = runwait(cmd); int rc = runwait(cmd);
if (rc != 0) { if (rc != 0) {
fprintf(stderr, "resolve FAIL: %s exited %d (unresolved names)\n", fprintf(stderr, "resolve FAIL: %s exited %d (unresolved names)\n",
@@ -657,72 +658,21 @@ probe_dump_stable(const char *bin)
return fail ? -1 : 0; return fail ? -1 : 0;
} }
/* Probe 9: full bootstrap fixed-point. ww1 → ww2 → ww3 with /* Probe 7: end-to-end via the ww driver's sep path. `ww_ww build --sep`
* cmp ww2.s == ww3.s and cmp ww2 == ww3 (byte-identical binaries). * drives the ww-side toolchain (w6c_ww → w6a_ww → w6l_ww) over the
* This is the textbook self-host gate: the compiler must reach a * fixture's package graph — per-package compile, archive, reverse-topo
* fixed point under iterated self-compilation. */ * link — and we run the result, comparing the exit code to the fixture's
static int * declared expectation. This is the bootstrap-grade signal: it doesn't
probe_bootstrap_fixed_point(const char *bin) * require byte-identity, only that the ww toolchain links the fixture's
{ * dep stack (sym/typ/ast via the syntax package, in a SEPARATE `.o`) into
char cwd[1024]; * a functionally correct binary. The single-`.s` link the combined path
if (getcwd(cwd, sizeof cwd) == NULL) return -1; * used is gone with combined-mode (the syntax symbols live in their own
char tmpdir[64]; * `.o` under --sep, not in the root unit). */
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwfp_%d", getpid());
mkdir(tmpdir, 0755);
char ww2s[512], ww2o[512], ww2e[512];
char ww3s[512], ww3o[512], ww3e[512];
char rt[1024], cmd[4096];
snprintf(rt, sizeof rt, "%s/../lib/libwwrt.a", bin);
snprintf(ww2s, sizeof ww2s, "%s/ww2.s", tmpdir);
snprintf(ww2o, sizeof ww2o, "%s/ww2.o", tmpdir);
snprintf(ww2e, sizeof ww2e, "%s/ww2", tmpdir);
snprintf(ww3s, sizeof ww3s, "%s/ww3.s", tmpdir);
snprintf(ww3o, sizeof ww3o, "%s/ww3.o", tmpdir);
snprintf(ww3e, sizeof ww3e, "%s/ww3", tmpdir);
int fail = 0;
/* ww1 -> ww2 */
snprintf(cmd, sizeof cmd,
"timeout 180 %s/wwdump_ww -c %s/selfhost/cmd/wwdump/main.combined.ww > %s 2>/dev/null",
bin, cwd, ww2s);
if (runwait(cmd) != 0) { fprintf(stderr, "fp FAIL: ww1 self-compile\n"); fail++; goto cleanup; }
snprintf(cmd, sizeof cmd, "timeout 180 %s/w6a -o %s %s 2>/dev/null", bin, ww2o, ww2s);
if (runwait(cmd) != 0) { fprintf(stderr, "fp FAIL: w6a ww2.s\n"); fail++; goto cleanup; }
snprintf(cmd, sizeof cmd, "timeout 180 %s/w6l -o %s %s %s 2>/dev/null", bin, ww2e, ww2o, rt);
if (runwait(cmd) != 0) { fprintf(stderr, "fp FAIL: w6l ww2\n"); fail++; goto cleanup; }
/* ww2 -> ww3 */
snprintf(cmd, sizeof cmd,
"timeout 180 %s -c %s/selfhost/cmd/wwdump/main.combined.ww > %s 2>/dev/null",
ww2e, cwd, ww3s);
if (runwait(cmd) != 0) { fprintf(stderr, "fp FAIL: ww2 self-compile\n"); fail++; goto cleanup; }
snprintf(cmd, sizeof cmd, "timeout 180 %s/w6a -o %s %s 2>/dev/null", bin, ww3o, ww3s);
if (runwait(cmd) != 0) { fprintf(stderr, "fp FAIL: w6a ww3.s\n"); fail++; goto cleanup; }
snprintf(cmd, sizeof cmd, "timeout 180 %s/w6l -o %s %s %s 2>/dev/null", bin, ww3e, ww3o, rt);
if (runwait(cmd) != 0) { fprintf(stderr, "fp FAIL: w6l ww3\n"); fail++; goto cleanup; }
/* The fixed-point check: ww2.s == ww3.s, ww2 == ww3. */
snprintf(cmd, sizeof cmd, "cmp -s %s %s", ww2s, ww3s);
if (runwait(cmd) != 0) { fprintf(stderr, "fp FAIL: ww2.s != ww3.s (no fixed point)\n"); fail++; }
snprintf(cmd, sizeof cmd, "cmp -s %s %s", ww2e, ww3e);
if (runwait(cmd) != 0) { fprintf(stderr, "fp FAIL: ww2 != ww3 binaries\n"); fail++; }
cleanup:
snprintf(cmd, sizeof cmd, "rm -rf %s", tmpdir);
runwait(cmd);
return fail ? -1 : 0;
}
/* Probe 7: end-to-end via wwdump_ww. Use `ww build` to produce the
* concatenated source as a side effect, then drive ww-cgen → w6a →
* w6l → run, comparing the binary's exit code to the fixture's
* declared expectation. This is the bootstrap-grade signal: it
* doesn't require ww-cgen output to be byte-identical to C-cgen,
* only to be functionally correct. The byte-match probe above is
* useful while it lasts but layout choices (slot allocation order,
* spill placement) eventually diverge between cgens; correctness is
* the test that scales. */
static int static int
probe_ww_links(const char *bin) probe_ww_links(const char *bin)
{ {
const char *fixtures[][2] = { const char *fixtures[][2] = {
{ "selfhost/test/sym_link.ww", "42" }, /* sym/typ/ast/mem */ { "selfhost/test/sym_link.ww", "42" }, /* sym/typ/ast via syntax */
{ NULL, NULL }, { NULL, NULL },
}; };
char cwd[1024]; char cwd[1024];
@@ -745,41 +695,18 @@ probe_ww_links(const char *bin)
char cmd[4096]; char cmd[4096];
snprintf(cmd, sizeof cmd, "cp %s/%s %s", cwd, fix, tmpsrc); snprintf(cmd, sizeof cmd, "cp %s/%s %s", cwd, fix, tmpsrc);
runwait(cmd); runwait(cmd);
/* ww build to get the .combined.ww as a side effect. */ /* Isolated WW_PKGCACHE: the default out/.pkgcache is shared with
snprintf(cmd, sizeof cmd, * the other phase-2 gates' sep builds (perturb + race). -o + the
"cd %s && timeout 180 %s/ww build -I %s/lib/ww -I %s/selfhost/cmd/wcc %s >/dev/null 2>&1", * absolute src/-I paths route every side file into tmpdir, so no
tmpdir, bin, cwd, cwd, tmpsrc); * cwd dependency. */
if (runwait(cmd) != 0) { char wexep[512];
fprintf(stderr, "ww-links FAIL: ww build %s\n", fix);
fail++;
goto cleanup;
}
/* Now compile the combined source via the ww frontend. */
char combinedp[512], wsp[512], wop[512], wexep[512];
snprintf(combinedp, sizeof combinedp, "%s.combined.ww", stem);
snprintf(wsp, sizeof wsp, "%s_w.s", stem);
snprintf(wop, sizeof wop, "%s_w.o", stem);
snprintf(wexep, sizeof wexep, "%s_w", stem); snprintf(wexep, sizeof wexep, "%s_w", stem);
snprintf(cmd, sizeof cmd, snprintf(cmd, sizeof cmd,
"timeout 180 %s/wwdump_ww -c %s > %s 2>/dev/null", bin, combinedp, wsp); "WW_PKGCACHE='%s.cache' timeout 180 %s/ww_ww build --sep "
"-o %s -I %s/lib/ww -I %s/selfhost/cmd/wcc %s >/dev/null 2>&1",
stem, bin, wexep, cwd, cwd, tmpsrc);
if (runwait(cmd) != 0) { if (runwait(cmd) != 0) {
fprintf(stderr, "ww-links FAIL: wwdump_ww -c %s\n", fix); fprintf(stderr, "ww-links FAIL: ww_ww build --sep %s\n", fix);
fail++;
goto cleanup;
}
snprintf(cmd, sizeof cmd, "timeout 180 %s/w6a -o %s %s 2>/dev/null",
bin, wop, wsp);
if (runwait(cmd) != 0) {
fprintf(stderr, "ww-links FAIL: w6a %s\n", fix);
fail++;
goto cleanup;
}
char rt[1024];
snprintf(rt, sizeof rt, "%s/../lib/libwwrt.a", bin);
snprintf(cmd, sizeof cmd, "timeout 180 %s/w6l -o %s %s %s 2>/dev/null",
bin, wexep, wop, rt);
if (runwait(cmd) != 0) {
fprintf(stderr, "ww-links FAIL: w6l %s\n", fix);
fail++; fail++;
goto cleanup; goto cleanup;
} }
@@ -791,8 +718,6 @@ probe_ww_links(const char *bin)
fail++; fail++;
} }
cleanup: cleanup:
/* Sweep tmpdir contents, then remove. ww build also drops a
* C-built binary at <stem> — clean it too. */
snprintf(cmd, sizeof cmd, "rm -rf %s", tmpdir); snprintf(cmd, sizeof cmd, "rm -rf %s", tmpdir);
runwait(cmd); runwait(cmd);
} }
@@ -800,23 +725,20 @@ cleanup:
} }
/* Probe 6: ww-cgen byte-matches C-cgen on each selfhost fixture's /* Probe 6: ww-cgen byte-matches C-cgen on each selfhost fixture's
* RESOLVED unit (imports concatenated — see resolveunit). The driver * RESOLVED unit (sep root unit — see resolveunit). The producer feeds
* always feeds w6c/wwdump a concatenated unit, so the byte-identity * w6c/wwdump that self-contained unit, so the byte-identity gate (rule
* gate (rule 10) belongs on that shape rather than the raw file; the * 10) belongs on that shape rather than the raw file.
* unit also drags in the imported stdlib (os/strconv/ascii), widening
* the convergence set. When this stays empty the cmp ww2 ww3 ceiling
* becomes feasible.
* *
* selfhost/cmd/wcc/err.ww is exercised by probe_resolve but not here: * err.ww is included now (it was excluded under combined-mode, where its
* its resolved unit pulls in lib/fmt, whose `formattable` match the * unit inlined lib/fmt's BODY and the ww checker rejected fmt's
* ww-stage checker rejects (check.ww casecovers/casevariantin raise * `formattable` match-exhaustiveness): the sep unit inlines only fmt's
* ck.errs, so wwdump -c bails before cgen) — a cs≠ww match-exhaustive- * `.wwi` INTERFACE, so the fmt body never reaches the checker and err's
* ness divergence, latent for the bootstrap (the compiler's own * root cgen converges cs==ww. */
* main.combined.ww imports no fmt) and tracked separately. */
static int static int
probe_cgen_match(const char *bin) probe_cgen_match(const char *bin)
{ {
const char *files[] = { const char *files[] = {
"selfhost/cmd/wcc/err.ww",
"lib/ww/syntax/tok.ww", "lib/ww/syntax/tok.ww",
"selfhost/test/smoke.ww", "selfhost/test/smoke.ww",
NULL, NULL,
@@ -825,9 +747,9 @@ probe_cgen_match(const char *bin)
if (getcwd(cwd, sizeof cwd) == NULL) return -1; if (getcwd(cwd, sizeof cwd) == NULL) return -1;
int fail = 0; int fail = 0;
for (int i = 0; files[i]; i++) { for (int i = 0; files[i]; i++) {
char td[64], combined[1024], a[256], b[256], cmd[2048]; char td[64], unit[1024], a[256], b[256], cmd[2048];
if (resolveunit(bin, cwd, files[i], i, td, sizeof td, if (resolveunit(bin, cwd, files[i], i, td, sizeof td,
combined, sizeof combined) != 0) { unit, sizeof unit) != 0) {
fprintf(stderr, "cgen-match FAIL: %s — no resolved unit\n", fprintf(stderr, "cgen-match FAIL: %s — no resolved unit\n",
files[i]); files[i]);
fail++; fail++;
@@ -836,10 +758,10 @@ probe_cgen_match(const char *bin)
snprintf(a, sizeof a, "/tmp/wwd_cm_%d_c", getpid()); snprintf(a, sizeof a, "/tmp/wwd_cm_%d_c", getpid());
snprintf(b, sizeof b, "/tmp/wwd_cm_%d_w", getpid()); snprintf(b, sizeof b, "/tmp/wwd_cm_%d_w", getpid());
snprintf(cmd, sizeof cmd, "timeout 180 %s/w6c %s > %s 2>/dev/null", snprintf(cmd, sizeof cmd, "timeout 180 %s/w6c %s > %s 2>/dev/null",
bin, combined, a); bin, unit, a);
runwait(cmd); runwait(cmd);
snprintf(cmd, sizeof cmd, "timeout 180 %s/wwdump_ww -c %s > %s 2>/dev/null", snprintf(cmd, sizeof cmd, "timeout 180 %s/wwdump_ww -c %s > %s 2>/dev/null",
bin, combined, b); bin, unit, b);
runwait(cmd); runwait(cmd);
char *bc = NULL, *bw = NULL; char *bc = NULL, *bw = NULL;
size_t nc = 0, nw = 0; size_t nc = 0, nw = 0;
@@ -876,19 +798,26 @@ main(void)
if (probe_ww_compile(bin) != 0) fail++; if (probe_ww_compile(bin) != 0) fail++;
if (probe_cgen_match(bin) != 0) fail++; if (probe_cgen_match(bin) != 0) fail++;
if (probe_ww_links(bin) != 0) fail++; if (probe_ww_links(bin) != 0) fail++;
if (probe_bootstrap_fixed_point(bin) != 0) fail++;
if (fail) { if (fail) {
fprintf(stderr, "selfhost: %d probe(s) failed\n", fail); fprintf(stderr, "selfhost: %d probe(s) failed\n", fail);
return 1; return 1;
} }
/* The ww1->ww2->ww3 binary fixed point is no longer reproducible here:
* it self-compiled wwdump as a monolith from main.combined.ww, a model
* combined-mode provided and --sep retires (the sep root unit holds only
* the root body + dep INTERFACE stubs, not a linkable whole program). The
* two axes it covered now live elsewhere: the self-application fixed point
* (iterated self-compile converges, ww2==ww3==ww4) is `make bootstrap`'s
* sep-path gate (migrated E2-C1); the orthogonal wwstage==cstage byte
* identity is 995_self_rebuild, which drives ww_ww --sep over all five
* tools (wwdump included) and byte-compares against the cstage binaries. */
printf("selfhost: codegen ok; smoke=42; wwdump stable; " printf("selfhost: codegen ok; smoke=42; wwdump stable; "
"ww lexer matches C on 8 fixtures (-t); " "ww lexer matches C on 8 fixtures (-t); "
"ww parser matches C on 32 fixtures (-a); " "ww parser matches C on 32 fixtures (-a); "
"ww checker resolves 3 fixtures' import-concatenated units (-r); " "ww checker resolves 3 fixtures' sep root units (-r); "
"ww cgen compiles + runs 13 ww programs (incl. compound -= regression); " "ww cgen compiles + runs 13 ww programs (incl. compound -= regression); "
"ww cgen byte-matches C on 2 resolved units (tok/smoke); " "ww cgen byte-matches C on 3 sep root units (err/tok/smoke); "
"ww cgen builds runnable binaries from sym/typ/ast/mem stack; " "ww toolchain sep-builds + runs the syntax dep stack (sym_link=42)\n");
"ww1 -> ww2 -> ww3 fixed-point reached: ww2.s == ww3.s, ww2 == ww3 byte-identical\n");
return 0; return 0;
} }

View File

@@ -120,41 +120,6 @@ files_eq(const char *a, const char *b)
return eq ? 0 : 1; return eq ? 0 : 1;
} }
/* Tier-C oracle helper (rob-c6-spec §2 / amendment-B): a sep-built w6c at
* `sepw6c` must EMIT byte-identical asm to the combined-built `w6c_ww` on
* `target`. (We assert OUTPUT equivalence, never `cmp` the tool binaries —
* sep vs combined differ by a constant link-layout delta.) Returns 0 on
* byte-identical .s, non-zero otherwise. */
static int
emit_eq(const char *sepw6c, const char *w6c_ww, const char *target,
const char *label)
{
char a[64], b[64], cmd[4096];
snprintf(a, sizeof a, "/tmp/wwc6tc_%d_sep.s", getpid());
snprintf(b, sizeof b, "/tmp/wwc6tc_%d_comb.s", getpid());
snprintf(cmd, sizeof cmd, "timeout 240 %s -o %s %s 2>/dev/null",
sepw6c, a, target);
if (runwait(cmd) != 0) {
fprintf(stderr, "Tier-C FAIL: sep-w6c emit errored on %s\n", label);
unlink(a);
return 1;
}
snprintf(cmd, sizeof cmd, "timeout 240 %s -o %s %s 2>/dev/null",
w6c_ww, b, target);
if (runwait(cmd) != 0) {
fprintf(stderr, "Tier-C FAIL: combined-w6c_ww emit errored on %s\n",
label);
unlink(a); unlink(b);
return 1;
}
int rc = files_eq(a, b);
if (rc != 0)
fprintf(stderr, "Tier-C FAIL: %s — sep-out != combined-out "
"(M4 output-equivalence broken)\n", label);
unlink(a); unlink(b);
return rc == 0 ? 0 : 1;
}
/* cs==ww over a sep-build's per-package artifacts: every .s/.o/.wwi the /* cs==ww over a sep-build's per-package artifacts: every .s/.o/.wwi the
* cstage driver produced in `csdir` must be byte-identical to the wwstage * cstage driver produced in `csdir` must be byte-identical to the wwstage
* driver's same-named file in `wwdir` (rule 10; the .wwi interface files * driver's same-named file in `wwdir` (rule 10; the .wwi interface files
@@ -309,17 +274,6 @@ main(void)
{ NULL, NULL }, { NULL, NULL },
}; };
/* Source-tree corpus: the same .combined.ww files the bootstrap
* fixed point chews on. Confirms w6c_ww handles realistic inputs,
* not just hand-tailored ones. */
const char *combined_rel[] = {
"selfhost/cmd/wwdump/main.combined.ww",
"selfhost/cmd/w6a/main.combined.ww",
"selfhost/cmd/w6l/main.combined.ww",
"selfhost/cmd/ww/main.combined.ww",
NULL,
};
int fail = 0, n = 0; int fail = 0, n = 0;
for (int i = 0; progs[i].label; i++) { for (int i = 0; progs[i].label; i++) {
@@ -330,12 +284,6 @@ main(void)
unlink(src); unlink(src);
n++; n++;
} }
for (int i = 0; combined_rel[i]; i++) {
char p[2048];
snprintf(p, sizeof p, "%s/%s", cwd, combined_rel[i]);
if (diff_one(bin, combined_rel[i], p) != 0) fail++;
n++;
}
/* #50: the check pass is wired into both wwdump_ww -c and /* #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 * w6c_ww in front of cgen. Pre-#50, a hard type error parsed
@@ -376,22 +324,19 @@ main(void)
n++; n++;
} }
/* ---- Tier-C capstone (#46 c6, rob-c6-spec §2 / amendment-B) ---------- /* ---- Tier-C capstone (#46 c6, rob-c6-spec §2 / amendment-B; M4 #89) ---
* Fold the dual-path soak into this bootstrap oracle: a w6c built by * The load-bearing M4 oracle: build w6c by SEPARATE compilation under
* SEPARATE compilation must EMIT byte-identical .s to the combined-built * BOTH driver stages and prove they sep-produce byte-identical per-package
* w6c_ww on every real bootstrap input, and both driver stages must * .s/.o/.wwi (cs==ww on the real tool's sep artifacts, rule 10). The
* sep-produce byte-identical per-package .s/.o/.wwi (cs==ww on the real * combined corpus + the sep-out==combined-out leg are gone — combined-mode
* tool's sep artifacts). This is the load-bearing M4 proof — the * is being retired (E3 flip), so combined.ww is no longer fed as compiler
* combined-out==sep-out leg the bootstrap fixed point alone cannot reach. * INPUT; the per-pkg cs==ww comparison is the surviving live oracle. COLD
* We assert OBSERVABLE OUTPUT, never `cmp` the tool binaries (sep vs * (pid-keyed scratch, wiped); heavy (≈ one bootstrap leg) → phase-2. */
* combined differ by a constant link-layout delta). COLD (pid-keyed
* scratch, wiped); heavy (≈ one bootstrap leg) → this gate is phase-2. */
{ {
char inc[4096], cmd[16384]; char inc[4096], cmd[16384];
snprintf(inc, sizeof inc, snprintf(inc, sizeof inc,
"-I %s/lib -I %s/lib/ww -I %s/selfhost/cmd/wcc", cwd, cwd, cwd); "-I %s/lib -I %s/lib/ww -I %s/selfhost/cmd/wcc", cwd, cwd, cwd);
char w6c_ww[2048], root[2048]; char root[2048];
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
snprintf(root, sizeof root, "%s/selfhost/cmd/w6c/main.ww", cwd); snprintf(root, sizeof root, "%s/selfhost/cmd/w6c/main.ww", cwd);
struct { const char *drv, *tag; char prog[2048]; int ok; } struct { const char *drv, *tag; char prog[2048]; int ok; }
@@ -415,29 +360,6 @@ main(void)
n++; n++;
} }
/* OUTPUT equivalence on every real bootstrap input: each built
* sep-w6c vs the combined w6c_ww (the M4 combined-out==sep-out bar;
* cs==ww follows since both sep stages match the same reference). */
const char *targ[] = {
"selfhost/cmd/w6c/main.combined.ww",
"selfhost/cmd/wwdump/main.combined.ww",
"selfhost/cmd/w6a/main.combined.ww",
"selfhost/cmd/w6l/main.combined.ww",
"selfhost/cmd/ww/main.combined.ww",
NULL,
};
for (int s = 0; s < 2; s++) {
if (!stg[s].ok) continue;
for (int i = 0; targ[i]; i++) {
char t[2048], label[256];
snprintf(t, sizeof t, "%s/%s", cwd, targ[i]);
snprintf(label, sizeof label, "sep-%s emit %s",
stg[s].tag, targ[i]);
if (emit_eq(stg[s].prog, w6c_ww, t, label) != 0) fail++;
n++;
}
}
/* cs==ww on the real tool's sep .s/.o/.wwi (rule 10). */ /* cs==ww on the real tool's sep .s/.o/.wwi (rule 10). */
if (stg[0].ok && stg[1].ok) { if (stg[0].ok && stg[1].ok) {
char csdir[2176], wwdir[2176]; char csdir[2176], wwdir[2176];
@@ -458,9 +380,8 @@ main(void)
fprintf(stderr, "w6c_ww: %d/%d diff(s) failed\n", fail, n); fprintf(stderr, "w6c_ww: %d/%d diff(s) failed\n", fail, n);
return 1; return 1;
} }
printf("w6c_ww: byte-identical to wwdump_ww -c on %d corpus inputs " printf("w6c_ww: byte-identical to wwdump_ww -c on %d in-source corpus "
"(in-source + selfhost combined.ww) + Tier-C: sep-built w6c emits " "inputs + Tier-C: cs==ww sep .s/.o/.wwi on the real w6c build\n",
"== combined w6c_ww on all bootstrap inputs, cs==ww sep .s/.o/.wwi\n",
n); n);
return 0; return 0;
} }