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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user