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:
@@ -518,21 +518,21 @@ cleanup:
|
||||
}
|
||||
|
||||
/* resolveunit — produce <fixture>'s RESOLVED translation unit the way
|
||||
* the driver does. `ww build` concatenates the module with its
|
||||
* transitive imports into <stem>.combined.ww (cmd/ww/main.c expand()),
|
||||
* which is the single unit w6c/wwdump actually consume; w6c never
|
||||
* resolves imports itself (cmd/w6c/main.c reads one pre-concatenated
|
||||
* file). Feeding a raw module file therefore leaves its import refs
|
||||
* (os.write, fmt.errorln, strconv.i64tos …) unresolved — a partial
|
||||
* unit harec's module::resolve never hands the checker, and which the
|
||||
* ww-stage asserttyped invariant (check.ww) is not meant to see.
|
||||
* the sep driver does. `ww build --sep` composes the root package's
|
||||
* <stem>.sepwork/__root.unit.ww: the transitive deps' `.wwi` interface
|
||||
* stubs followed by the root package body — the single self-contained
|
||||
* unit w6c/wwdump consume for the root (the producer compiles it with no
|
||||
* -I; cmd/ww/main.c sep_compose_unit). Feeding a raw module file instead
|
||||
* would leave its import refs (os.write, fmt.errorln, strconv.i64tos …)
|
||||
* unresolved — a partial unit harec's module::resolve never hands the
|
||||
* 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
|
||||
* / .s / .o lands next to the repo source. The link step fails for an
|
||||
* import-only module (no main), but .combined.ww is written before
|
||||
* codegen, so we detect that artifact rather than gating on the build
|
||||
* exit. Writes the owning tmpdir into `td` (caller rm -rf's it) and the
|
||||
* combined path into `out`. Returns 0 on success, -1 otherwise. */
|
||||
* Built in a private /tmp dir (a copy of the fixture) so the sepwork
|
||||
* lands off the repo source tree. The link step fails for an import-only
|
||||
* module (no main), but the unit is written before the link, so we detect
|
||||
* that artifact rather than gating on the build exit. Writes the owning
|
||||
* tmpdir into `td` (caller rm -rf's it) and the unit path into `out`.
|
||||
* Returns 0 on success, -1 otherwise. */
|
||||
static int
|
||||
resolveunit(const char *bin, const char *cwd, const char *fixture,
|
||||
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);
|
||||
runwait(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);
|
||||
snprintf(out, outsz, "%s", tmpsrc);
|
||||
char *dot = strrchr(out, '.');
|
||||
char stem[768];
|
||||
snprintf(stem, sizeof stem, "%s", tmpsrc);
|
||||
char *dot = strrchr(stem, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
size_t n = strlen(out);
|
||||
snprintf(out + n, outsz - n, ".combined.ww");
|
||||
snprintf(out, outsz, "%s.sepwork/__root.unit.ww", stem);
|
||||
return access(out, 0) == 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
/* 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
|
||||
* its `import` refs (os/fmt/strconv/ascii members) unresolved, i.e. the
|
||||
* partial unit the asserttyped invariant must never see (harec resolves
|
||||
@@ -578,9 +579,9 @@ probe_resolve(const char *bin)
|
||||
};
|
||||
int fail = 0;
|
||||
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,
|
||||
combined, sizeof combined) != 0) {
|
||||
unit, sizeof unit) != 0) {
|
||||
fprintf(stderr, "resolve FAIL: %s — no resolved unit\n",
|
||||
fixtures[i]);
|
||||
fail++;
|
||||
@@ -588,7 +589,7 @@ probe_resolve(const char *bin)
|
||||
}
|
||||
snprintf(a, sizeof a, "/tmp/wwd_r_%d", getpid());
|
||||
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);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "resolve FAIL: %s exited %d (unresolved names)\n",
|
||||
@@ -657,72 +658,21 @@ probe_dump_stable(const char *bin)
|
||||
return fail ? -1 : 0;
|
||||
}
|
||||
|
||||
/* Probe 9: full bootstrap fixed-point. ww1 → ww2 → ww3 with
|
||||
* cmp ww2.s == ww3.s and cmp ww2 == ww3 (byte-identical binaries).
|
||||
* This is the textbook self-host gate: the compiler must reach a
|
||||
* fixed point under iterated self-compilation. */
|
||||
static int
|
||||
probe_bootstrap_fixed_point(const char *bin)
|
||||
{
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return -1;
|
||||
char tmpdir[64];
|
||||
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. */
|
||||
/* Probe 7: end-to-end via the ww driver's sep path. `ww_ww build --sep`
|
||||
* drives the ww-side toolchain (w6c_ww → w6a_ww → w6l_ww) over the
|
||||
* fixture's package graph — per-package compile, archive, reverse-topo
|
||||
* link — and we run the result, comparing the exit code to the fixture's
|
||||
* declared expectation. This is the bootstrap-grade signal: it doesn't
|
||||
* 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
|
||||
* a functionally correct binary. The single-`.s` link the combined path
|
||||
* used is gone with combined-mode (the syntax symbols live in their own
|
||||
* `.o` under --sep, not in the root unit). */
|
||||
static int
|
||||
probe_ww_links(const char *bin)
|
||||
{
|
||||
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 },
|
||||
};
|
||||
char cwd[1024];
|
||||
@@ -745,41 +695,18 @@ probe_ww_links(const char *bin)
|
||||
char cmd[4096];
|
||||
snprintf(cmd, sizeof cmd, "cp %s/%s %s", cwd, fix, tmpsrc);
|
||||
runwait(cmd);
|
||||
/* ww build to get the .combined.ww as a side effect. */
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && timeout 180 %s/ww build -I %s/lib/ww -I %s/selfhost/cmd/wcc %s >/dev/null 2>&1",
|
||||
tmpdir, bin, cwd, cwd, tmpsrc);
|
||||
if (runwait(cmd) != 0) {
|
||||
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);
|
||||
/* Isolated WW_PKGCACHE: the default out/.pkgcache is shared with
|
||||
* the other phase-2 gates' sep builds (perturb + race). -o + the
|
||||
* absolute src/-I paths route every side file into tmpdir, so no
|
||||
* cwd dependency. */
|
||||
char wexep[512];
|
||||
snprintf(wexep, sizeof wexep, "%s_w", stem);
|
||||
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) {
|
||||
fprintf(stderr, "ww-links FAIL: wwdump_ww -c %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);
|
||||
fprintf(stderr, "ww-links FAIL: ww_ww build --sep %s\n", fix);
|
||||
fail++;
|
||||
goto cleanup;
|
||||
}
|
||||
@@ -791,8 +718,6 @@ probe_ww_links(const char *bin)
|
||||
fail++;
|
||||
}
|
||||
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);
|
||||
runwait(cmd);
|
||||
}
|
||||
@@ -800,23 +725,20 @@ cleanup:
|
||||
}
|
||||
|
||||
/* Probe 6: ww-cgen byte-matches C-cgen on each selfhost fixture's
|
||||
* RESOLVED unit (imports concatenated — see resolveunit). The driver
|
||||
* always feeds w6c/wwdump a concatenated unit, so the byte-identity
|
||||
* gate (rule 10) belongs on that shape rather than the raw file; the
|
||||
* 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.
|
||||
* RESOLVED unit (sep root unit — see resolveunit). The producer feeds
|
||||
* w6c/wwdump that self-contained unit, so the byte-identity gate (rule
|
||||
* 10) belongs on that shape rather than the raw file.
|
||||
*
|
||||
* selfhost/cmd/wcc/err.ww is exercised by probe_resolve but not here:
|
||||
* its resolved unit pulls in lib/fmt, whose `formattable` match the
|
||||
* ww-stage checker rejects (check.ww casecovers/casevariantin raise
|
||||
* ck.errs, so wwdump -c bails before cgen) — a cs≠ww match-exhaustive-
|
||||
* ness divergence, latent for the bootstrap (the compiler's own
|
||||
* main.combined.ww imports no fmt) and tracked separately. */
|
||||
* err.ww is included now (it was excluded under combined-mode, where its
|
||||
* unit inlined lib/fmt's BODY and the ww checker rejected fmt's
|
||||
* `formattable` match-exhaustiveness): the sep unit inlines only fmt's
|
||||
* `.wwi` INTERFACE, so the fmt body never reaches the checker and err's
|
||||
* root cgen converges cs==ww. */
|
||||
static int
|
||||
probe_cgen_match(const char *bin)
|
||||
{
|
||||
const char *files[] = {
|
||||
"selfhost/cmd/wcc/err.ww",
|
||||
"lib/ww/syntax/tok.ww",
|
||||
"selfhost/test/smoke.ww",
|
||||
NULL,
|
||||
@@ -825,9 +747,9 @@ probe_cgen_match(const char *bin)
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return -1;
|
||||
int fail = 0;
|
||||
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,
|
||||
combined, sizeof combined) != 0) {
|
||||
unit, sizeof unit) != 0) {
|
||||
fprintf(stderr, "cgen-match FAIL: %s — no resolved unit\n",
|
||||
files[i]);
|
||||
fail++;
|
||||
@@ -836,10 +758,10 @@ probe_cgen_match(const char *bin)
|
||||
snprintf(a, sizeof a, "/tmp/wwd_cm_%d_c", getpid());
|
||||
snprintf(b, sizeof b, "/tmp/wwd_cm_%d_w", getpid());
|
||||
snprintf(cmd, sizeof cmd, "timeout 180 %s/w6c %s > %s 2>/dev/null",
|
||||
bin, combined, a);
|
||||
bin, unit, a);
|
||||
runwait(cmd);
|
||||
snprintf(cmd, sizeof cmd, "timeout 180 %s/wwdump_ww -c %s > %s 2>/dev/null",
|
||||
bin, combined, b);
|
||||
bin, unit, b);
|
||||
runwait(cmd);
|
||||
char *bc = NULL, *bw = NULL;
|
||||
size_t nc = 0, nw = 0;
|
||||
@@ -876,19 +798,26 @@ main(void)
|
||||
if (probe_ww_compile(bin) != 0) fail++;
|
||||
if (probe_cgen_match(bin) != 0) fail++;
|
||||
if (probe_ww_links(bin) != 0) fail++;
|
||||
if (probe_bootstrap_fixed_point(bin) != 0) fail++;
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "selfhost: %d probe(s) failed\n", fail);
|
||||
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; "
|
||||
"ww lexer matches C on 8 fixtures (-t); "
|
||||
"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 byte-matches C on 2 resolved units (tok/smoke); "
|
||||
"ww cgen builds runnable binaries from sym/typ/ast/mem stack; "
|
||||
"ww1 -> ww2 -> ww3 fixed-point reached: ww2.s == ww3.s, ww2 == ww3 byte-identical\n");
|
||||
"ww cgen byte-matches C on 3 sep root units (err/tok/smoke); "
|
||||
"ww toolchain sep-builds + runs the syntax dep stack (sym_link=42)\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user