test: 990 standalone probes feed resolved units (asserttyped-bail precondition)

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 <stem>.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.
This commit is contained in:
2026-05-28 09:55:45 +09:00
parent f892a91223
commit f91d93e827

View File

@@ -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 <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.
*
* 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;