test/wcc: carrier ownership repair and driver-contract adaptation

Every surviving carrier now owns its artifacts: checked mkdir/mkdtemp/
fopen acquisition, one all-exit cleanup funnel per carrier, ENOENT-
tolerant checked unlinks, exact-path deletion (rm -rf only for an
owned pid-keyed dir or a .sepwork beneath one), and cleanup failure
fails a passing carrier without overwriting its diagnostic. In the
same pass the carriers adapt to the driver contract this branch lands:
--sep and WW_PKGCACHE are gone, -S and the /tmp/ww_run_<pid> scratch
contract are asserted, and rows whose runtime or reject coverage moved
to test/wcc/data fixtures or test/lang @test owners are trimmed to the
byte/artifact/diagnostic observations only they can make.

Repair and adaptation ride together because most files interleave both
in the same hunks; splitting would manufacture intermediate carrier
states that never existed and cannot run against either driver.
This commit is contained in:
2026-08-07 23:02:47 +09:00
parent b2899dd8d3
commit a95a7a316b
132 changed files with 7573 additions and 6172 deletions

View File

@@ -40,16 +40,13 @@
* 5. fn-with-tuple-return — (i64, str) return shape
* 6. cross-module — `let f = &pkg.fn` (Phase 1 = FINE)
*
* Gates per row:
* a. cstage builds + runs (exit 0); proves the LEAQ emits and
* frame layout survives the AX-store (pre-fix this was junk
* data, not necessarily a segfault — the segfault only fired
* on the deref-call).
* b. cstage .s contains the expected `LEAQ <fnname>(SB)` line —
* Runtime for the five symmetric rows now lives in r764_* fixtures.
* This artifact wrapper retains:
* a. cstage .s contains the expected `LEAQ <fnname>(SB)` line —
* pre-fix that line is absent (silent drop).
* c. wwstage builds + runs, if wwstage driver present.
* d. cstage .s == wwstage .s byte-identical — symmetry gate per
* b. cstage .s == wwstage .s byte-identical — symmetry gate per
* CLAUDE.md rule 10.
* c. the cross-module C-stage-only runtime gate pending #184.
*
* GATE POLARITY: must stay GREEN. A red here means either the cgen
* TK_AMP IDENT TY_FN arm regressed, or stage symmetry drifted.
@@ -57,6 +54,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
@@ -79,6 +77,8 @@ runwait(const char *cmd)
struct row {
const char *label;
/* Symmetric rows use their corpus source as the sole fixture body. */
const char *fixture;
/* Source written into <tmpdir>/<basename>.ww; for cross-mod
* (row 6) the secondary module lives at <tmpdir>/<modname>/
* <modname>.ww. modname/modsrc NULL for single-file rows. */
@@ -94,51 +94,32 @@ struct row {
static const struct row rows[] = {
{ "minimal",
"fn add1(x: i32) i32 = { return x + 1; };\n"
"export fn main() i32 = {\n"
" let f = &add1;\n"
" return 0;\n"
"};\n",
"test/wcc/data/r764_amp_fn_ident_minimal/case.ww",
NULL,
NULL, NULL,
"LEAQ\tmain.add1(SB)",
STAGE_CS | STAGE_WW },
{ "branched_callee",
"fn aa(x: i32) i32 = { return x; };\n"
"fn bb(x: i32) i32 = { return x + 1; };\n"
"export fn main() i32 = {\n"
" let pick: i32 = 1;\n"
" let f = &aa;\n"
" if (pick != 0) { f = &bb; };\n"
" return 0;\n"
"};\n",
"test/wcc/data/r764_amp_fn_ident_branched/case.ww",
NULL,
NULL, NULL,
"LEAQ\tmain.bb(SB)",
STAGE_CS | STAGE_WW },
{ "alias_chain",
"fn add1(x: i32) i32 = { return x + 1; };\n"
"export fn main() i32 = {\n"
" let f = &add1;\n"
" let g = f;\n"
" return 0;\n"
"};\n",
"test/wcc/data/r764_amp_fn_ident_alias_chain/case.ww",
NULL,
NULL, NULL,
"LEAQ\tmain.add1(SB)",
STAGE_CS | STAGE_WW },
{ "fn_with_args",
"fn many(a: i32, b: i64, p: *i32) i64 = { return b; };\n"
"export fn main() i32 = {\n"
" let f = &many;\n"
" return 0;\n"
"};\n",
"test/wcc/data/r764_amp_fn_ident_args/case.ww",
NULL,
NULL, NULL,
"LEAQ\tmain.many(SB)",
STAGE_CS | STAGE_WW },
{ "fn_tuple_return",
"fn pair() (i64, str) = { return (7: i64, \"x\"); };\n"
"export fn main() i32 = {\n"
" let f = &pair;\n"
" return 0;\n"
"};\n",
"test/wcc/data/r764_amp_fn_ident_tuple_return/case.ww",
NULL,
NULL, NULL,
"LEAQ\tmain.pair(SB)",
STAGE_CS | STAGE_WW },
@@ -151,6 +132,7 @@ static const struct row rows[] = {
* &-of hint routes through use_hint and the LEAQ matches the
* mangled definition. */
{ "cross_module",
NULL,
"import wcamffn764mod;\n"
"export fn main() i32 = {\n"
" let f = &wcamffn764mod.somefn;\n"
@@ -163,73 +145,125 @@ static const struct row rows[] = {
STAGE_CS },
};
/* write_sources — writes the row's main source plus, for cross-mod
* rows, the secondary module file. Returns the basename of the main
* source (without .ww) via *base_out. */
static int cleanup_sources(const struct row *, const char *, const char *, int);
/* write_sources — copies a migrated fixture into an isolated package-main
* root, or writes the one asymmetric cross-module row into that tree. */
static int
write_sources(const struct row *r, char *src, size_t srcsz,
char *tmpdir, size_t tdsz, int seq, char *base_out, size_t basz)
{
snprintf(tmpdir, tdsz, "/tmp/wcamffn_%d_d_%d", getpid(), seq);
(void)seq;
int moddir_owned = 0;
snprintf(tmpdir, tdsz, "/tmp/wcamffn_XXXXXX");
if (mkdtemp(tmpdir) == NULL) return -1;
snprintf(src, srcsz, "%s/main764.ww", tmpdir);
snprintf(base_out, basz, "main764");
mkdir(tmpdir, 0755);
if (r->fixture != NULL) {
FILE *in = fopen(r->fixture, "rb");
if (in == NULL) goto fail;
if (fseek(in, 0, SEEK_END) != 0) { fclose(in); goto fail; }
long n = ftell(in);
if (n < 0 || fseek(in, 0, SEEK_SET) != 0) {
fclose(in);
goto fail;
}
char *body = malloc((size_t)n + 1);
if (body == NULL) { fclose(in); goto fail; }
size_t got = fread(body, 1, (size_t)n, in);
int readerr = ferror(in);
fclose(in);
if (got != (size_t)n || readerr) { free(body); goto fail; }
body[got] = '\0';
FILE *out = fopen(src, "wb");
if (out == NULL) { free(body); goto fail; }
wwtest_fputs(body, out);
int wr = ferror(out) ? -1 : 0;
free(body);
if (fclose(out) != 0) wr = -1;
if (wr != 0) goto fail;
return 0;
}
if (r->modname != NULL) {
char moddir[256], modfile[256];
snprintf(moddir, sizeof moddir, "%s/%s", tmpdir, r->modname);
snprintf(modfile, sizeof modfile, "%s/%s.ww",
moddir, r->modname);
mkdir(moddir, 0755);
if (mkdir(moddir, 0755) != 0) goto fail;
moddir_owned = 1;
FILE *mf = fopen(modfile, "wb");
if (!mf) return -1;
if (!mf) goto fail;
wwtest_fputs(r->modsrc, mf);
fclose(mf);
int wr = ferror(mf) ? -1 : 0;
if (fclose(mf) != 0) wr = -1;
if (wr != 0) goto fail;
}
FILE *f = fopen(src, "wb");
if (!f) return -1;
if (!f) goto fail;
wwtest_fputs(r->src, f);
fclose(f);
int wr = ferror(f) ? -1 : 0;
if (fclose(f) != 0) wr = -1;
if (wr != 0) goto fail;
return 0;
fail:
if (cleanup_sources(r, tmpdir, "main764", moddir_owned) != 0)
fprintf(stderr, "amp_fn_ident: setup cleanup failed for %s\n", tmpdir);
return -1;
}
static void
cleanup_sources(const struct row *r, const char *tmpdir, const char *base)
static int
cleanup_sources(const struct row *r, const char *tmpdir, const char *base,
int moddir_owned)
{
char p[512];
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s.s", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s.o", tmpdir, base); unlink(p);
snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p);
/* #93: the sep scratch dir + the tmpdir-pinned pkgcache. */
snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir);
if (system(p) != 0) { /* best-effort */ }
if (r->modname != NULL) {
int rc = 0;
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base);
if (unlink(p) != 0 && errno != ENOENT) rc = -1;
snprintf(p, sizeof p, "%s/%s", tmpdir, base);
if (unlink(p) != 0 && errno != ENOENT) rc = -1;
/* #93: the sep scratch dir + the tmpdir-owned outputs. */
snprintf(p, sizeof p, "rm -rf %s/%s.sepwork", tmpdir, base);
if (runwait(p) != 0) rc = -1;
if (moddir_owned) {
char moddir[256];
snprintf(moddir, sizeof moddir, "%s/%s", tmpdir, r->modname);
snprintf(p, sizeof p, "%s/%s.ww", moddir, r->modname); unlink(p);
rmdir(moddir);
snprintf(p, sizeof p, "%s/%s.ww", moddir, r->modname);
if (unlink(p) != 0 && errno != ENOENT) rc = -1;
if (rmdir(moddir) != 0 && errno != ENOENT) rc = -1;
}
rmdir(tmpdir);
if (rmdir(tmpdir) != 0) rc = -1;
return rc;
}
/* compile_via_driver — runs compiler-only driver output in <tmpdir>; returns
* the compiler exit code without assembling or linking. */
static int
compile_via_driver(const char *driver, const char *tmpdir, const char *src)
{
/* #93 sep layout: `-o <src-stem>` emits the asm to
* <stem>.sepwork/__root.s (the post-flip layout); all outputs stay
* under tmpdir. */
char cmd[2048];
snprintf(cmd, sizeof cmd,
"cd %s && timeout 180 %s build -S -o %s/main764 %s "
"2>/dev/null", tmpdir, driver, tmpdir, src);
return runwait(cmd);
}
/* build_via_driver — runs `<driver> build <src>` in <tmpdir>; returns
* the build exit code. */
static int
build_via_driver(const char *driver, const char *tmpdir, const char *src)
{
/* #93 sep layout: `--sep -o <src-stem>` emits the asm to
* <stem>.sepwork/__root.s (the post-flip layout); WW_PKGCACHE is
* pinned under tmpdir so the shared out/.pkgcache is untouched and
* the cache dies with the tmpdir. */
char stem[512], cmd[1280];
snprintf(stem, sizeof stem, "%s", src);
char *dot = strrchr(stem, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
snprintf(cmd, sizeof cmd,
"cd %s && WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep -o %s %s "
"2>/dev/null", tmpdir, tmpdir, driver, stem, src);
"cd %s && timeout 180 %s build -o %s %s "
"2>/dev/null", tmpdir, driver, stem, src);
return runwait(cmd);
}
@@ -247,7 +281,10 @@ run_row(const char *driver, const struct row *r, int seq)
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
if (runwait(outbin) == 0) rc = 0;
}
cleanup_sources(r, tmpdir, base);
if (cleanup_sources(r, tmpdir, base, r->modname != NULL) != 0) {
fprintf(stderr, "amp_fn_ident[%s]: cleanup failed\n", r->label);
rc = -1;
}
return rc;
}
@@ -277,11 +314,14 @@ check_leaq(const char *driver, const struct row *r, int seq)
seq, base, sizeof base) != 0)
return -1;
int rc = -1;
if (build_via_driver(driver, tmpdir, src) == 0) {
if (compile_via_driver(driver, tmpdir, src) == 0) {
snprintf(asmf, sizeof asmf, "%s/%s.sepwork/__root.s", tmpdir, base);
if (file_contains(asmf, r->expected_leaq)) rc = 0;
}
cleanup_sources(r, tmpdir, base);
if (cleanup_sources(r, tmpdir, base, r->modname != NULL) != 0) {
fprintf(stderr, "amp_fn_ident[%s]: cleanup failed\n", r->label);
rc = -1;
}
return rc;
}
@@ -295,17 +335,17 @@ asm_byte_identical(const char *cdrv, const char *wdrv,
seq, base, sizeof base) != 0)
return -1;
int rc = -1;
if (build_via_driver(cdrv, tdc, src) != 0) goto out;
int have_tdw = 0;
if (compile_via_driver(cdrv, tdc, src) != 0) goto out;
snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base);
/* Build a parallel tree for wwstage so we don't clobber the
* cstage .s. ww_ww writes intermediates next to the .ww source
* (filed bug per CLAUDE.md rule 14 phase split). */
/* Build a parallel tree so each driver's explicitly retained
* <stem>.sepwork/__root.s remains available for comparison. */
if (write_sources(r, src, sizeof src, tdw, sizeof tdw,
seq + 100000, base, sizeof base) != 0)
goto out;
if (build_via_driver(wdrv, tdw, src) != 0) {
cleanup_sources(r, tdw, base);
have_tdw = 1;
if (compile_via_driver(wdrv, tdw, src) != 0) {
goto out;
}
snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base);
@@ -323,9 +363,16 @@ asm_byte_identical(const char *cdrv, const char *wdrv,
}
if (fc) fclose(fc);
if (fw) fclose(fw);
cleanup_sources(r, tdw, base);
out:
cleanup_sources(r, tdc, base);
if (have_tdw
&& cleanup_sources(r, tdw, base, r->modname != NULL) != 0) {
fprintf(stderr, "amp_fn_ident[%s]: ww cleanup failed\n", r->label);
rc = -1;
}
if (cleanup_sources(r, tdc, base, r->modname != NULL) != 0) {
fprintf(stderr, "amp_fn_ident[%s]: c cleanup failed\n", r->label);
rc = -1;
}
return rc;
}
@@ -353,7 +400,10 @@ main(void)
int seq = 0;
for (int i = 0; i < n; i++) {
if (rows[i].stage_mask & STAGE_CS) {
/* Symmetric runtime rows are owned by r764_* fixtures. Keep
* only the cross-module C-stage-only runtime evidence here. */
if ((rows[i].stage_mask & STAGE_CS)
&& !(rows[i].stage_mask & STAGE_WW)) {
total++;
if (run_row(cdrv, &rows[i], seq++) != 0) {
fprintf(stderr,
@@ -361,6 +411,8 @@ main(void)
rows[i].label);
fail++;
}
}
if (rows[i].stage_mask & STAGE_CS) {
total++;
if (check_leaq(cdrv, &rows[i], seq++) != 0) {
fprintf(stderr,
@@ -370,13 +422,6 @@ main(void)
}
}
if (wwpresent && (rows[i].stage_mask & STAGE_WW)) {
total++;
if (run_row(wdrv, &rows[i], seq++) != 0) {
fprintf(stderr,
"amp_fn_ident[wwstage run][%s]: build/run failed\n",
rows[i].label);
fail++;
}
total++;
if (asm_byte_identical(cdrv, wdrv, &rows[i], seq++) != 0) {
fprintf(stderr,