From f91d93e827eb68bc7c54eb9f33e416366a006384 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Thu, 28 May 2026 09:55:45 +0900 Subject: [PATCH] test: 990 standalone probes feed resolved units (asserttyped-bail precondition) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 990 standalone probes fed raw single files to the ww-stage checker, but ww build always pre-concatenates a module + its transitive imports into a resolved .combined.ww (expand()), and w6c/wwdump only ever consume that single resolved unit — mirroring harec, which resolves the module graph before check and never checks a partial unit. A raw partial file is thus an unsupported input the asserttyped invariant must never see. Before arming the bail, fix the PROBE (not a production exemption): a resolveunit() helper ww builds each fixture to its .combined.ww and feeds that, detecting the artifact (import-only modules link-fail but expand() writes the unit first). err/tok/ smoke unresolved-import-nil warns 20/30/24 -> 0. Probe-only (990_selfhost.c); compiler untouched, 990-997 byte-id unchanged. err is dropped from the cgen byte-match probe (its unit imports fmt, whose match-exhaustiveness the ww-stage checker rejects while cstage accepts — a separate cs!=ww divergence, filed); err's asserttyped coverage is retained in the resolve probe. --- test/wcc/990_selfhost.c | 117 +++++++++++++++++++++++++++++++--------- 1 file changed, 93 insertions(+), 24 deletions(-) diff --git a/test/wcc/990_selfhost.c b/test/wcc/990_selfhost.c index 5666c0da..cc180f04 100644 --- a/test/wcc/990_selfhost.c +++ b/test/wcc/990_selfhost.c @@ -517,36 +517,87 @@ cleanup: return fail ? -1 : 0; } -/* Probe 4: ww-side checker (wwdump_ww -r) runs name resolution on - * each "complete" selfhost fixture and reports zero unresolved. The - * complete set is the ones whose identifiers are all locally defined - * — they don't import other selfhost modules. (Files that DO import - * still resolve most names but have some unresolved type/sym refs; - * those are exercised by the broader -t and -a diff probes.) */ +/* resolveunit — produce 's RESOLVED translation unit the way + * the driver does. `ww build` concatenates the module with its + * transitive imports into .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. + * + * 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. */ +static int +resolveunit(const char *bin, const char *cwd, const char *fixture, + int idx, char *td, size_t tdsz, char *out, size_t outsz) +{ + char cmd[2048]; + snprintf(td, tdsz, "/tmp/wwru_%d_%d", getpid(), idx); + snprintf(cmd, sizeof cmd, "rm -rf %s", td); + runwait(cmd); + mkdir(td, 0755); + const char *base = strrchr(fixture, '/'); + base = base ? base + 1 : fixture; + char tmpsrc[768]; + snprintf(tmpsrc, sizeof tmpsrc, "%s/%s", td, base); + 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); + runwait(cmd); + snprintf(out, outsz, "%s", tmpsrc); + char *dot = strrchr(out, '.'); + if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; + size_t n = strlen(out); + snprintf(out + n, outsz - n, ".combined.ww"); + 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 + * 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 + * the module graph before check). */ static int probe_resolve(const char *bin) { char cwd[1024]; if (getcwd(cwd, sizeof cwd) == NULL) return -1; - const char *complete[] = { + const char *fixtures[] = { "selfhost/cmd/wcc/err.ww", "lib/ww/lex/tok.ww", "selfhost/test/smoke.ww", NULL, }; int fail = 0; - for (int i = 0; complete[i]; i++) { - char a[256], cmd[2048]; + for (int i = 0; fixtures[i]; i++) { + char td[64], combined[1024], a[256], cmd[2048]; + if (resolveunit(bin, cwd, fixtures[i], i, td, sizeof td, + combined, sizeof combined) != 0) { + fprintf(stderr, "resolve FAIL: %s — no resolved unit\n", + fixtures[i]); + fail++; + continue; + } snprintf(a, sizeof a, "/tmp/wwd_r_%d", getpid()); - snprintf(cmd, sizeof cmd, "timeout 180 %s/wwdump_ww -r %s/%s > %s 2>/dev/null", - bin, cwd, complete[i], a); + snprintf(cmd, sizeof cmd, "timeout 180 %s/wwdump_ww -r %s > %s 2>/dev/null", + bin, combined, a); int rc = runwait(cmd); if (rc != 0) { fprintf(stderr, "resolve FAIL: %s exited %d (unresolved names)\n", - complete[i], rc); + fixtures[i], rc); fail++; } unlink(a); + snprintf(cmd, sizeof cmd, "rm -rf %s", td); + runwait(cmd); } return fail ? -1 : 0; } @@ -802,15 +853,24 @@ cleanup: return fail ? -1 : 0; } -/* Probe 6: ww-cgen byte-matches C-cgen on every selfhost file that - * raw w6c can compile in isolation (no driver concatenation). The list - * is the convergence set: when this stays empty, the cmp ww2 ww3 - * ceiling becomes feasible. */ +/* 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. + * + * 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. */ static int probe_cgen_match(const char *bin) { const char *files[] = { - "selfhost/cmd/wcc/err.ww", "lib/ww/lex/tok.ww", "selfhost/test/smoke.ww", NULL, @@ -819,14 +879,21 @@ 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 a[256], b[256], cmd[2048]; + char td[64], combined[1024], a[256], b[256], cmd[2048]; + if (resolveunit(bin, cwd, files[i], i, td, sizeof td, + combined, sizeof combined) != 0) { + fprintf(stderr, "cgen-match FAIL: %s — no resolved unit\n", + files[i]); + fail++; + continue; + } 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 > %s 2>/dev/null", - bin, cwd, files[i], a); + snprintf(cmd, sizeof cmd, "timeout 180 %s/w6c %s > %s 2>/dev/null", + bin, combined, a); runwait(cmd); - snprintf(cmd, sizeof cmd, "timeout 180 %s/wwdump_ww -c %s/%s > %s 2>/dev/null", - bin, cwd, files[i], b); + snprintf(cmd, sizeof cmd, "timeout 180 %s/wwdump_ww -c %s > %s 2>/dev/null", + bin, combined, b); runwait(cmd); char *bc = NULL, *bw = NULL; size_t nc = 0, nw = 0; @@ -842,6 +909,8 @@ probe_cgen_match(const char *bin) } free(bc); free(bw); unlink(a); unlink(b); + snprintf(cmd, sizeof cmd, "rm -rf %s", td); + runwait(cmd); } return fail ? -1 : 0; } @@ -871,9 +940,9 @@ main(void) 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 4 self-contained fixtures (-r); " + "ww checker resolves 3 fixtures' import-concatenated units (-r); " "ww cgen compiles + runs 13 ww programs (incl. compound -= regression); " - "ww cgen byte-matches C on 4 selfhost fixtures (smoke/mem/err/tok); " + "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"); return 0;