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.
308 lines
9.6 KiB
C
308 lines
9.6 KiB
C
/*
|
|
* 940_global_sret_run — project #220. sret (>24B by-value struct
|
|
* return) assigned into a GLOBAL lvalue must land the whole struct,
|
|
* not a truncated 8-byte store.
|
|
*
|
|
* Gate-blind family (sibling of #211): pre-fix BOTH stages emitted the
|
|
* SAME broken `MOVQ AX, g(SB)` (only the sret return pointer reached
|
|
* the global; the struct body, written to a scratch temp, was lost), so
|
|
* asm byte-id stayed GREEN while runtime was wrong — g.pos == 0 for the
|
|
* #94 io vtable cgoutstream pattern. The runtime rows below are the net.
|
|
*
|
|
* Surfacing case: the eFinal FLIP repointed cgen.ww's per-fn body output
|
|
* through a GLOBAL `let cgoutstream: memio.stream` (>24B) wired once via
|
|
* `cgoutstream = memio.dynamic()`; the truncated store left pos==0 so
|
|
* the buffered body never flushed and w6c_ww emitted prologue-only.
|
|
*
|
|
* The fix routes the sret dest pointer to the global's symbol address
|
|
* (LEAQ g(SB), DI) — zero-copy, the same discipline the local case
|
|
* already used (LEAQ off(BP), DI). Local rows pin no regression at the
|
|
* shared cgassign / cglet sret-receive sites.
|
|
*
|
|
* Runtime behavior is carried by the r940_global_sret_* wwfixtures. This
|
|
* wrapper retains the separate-build assembly artifacts and their byte
|
|
* identity (cs.s == ww.s, rule 10).
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
#include "wwtestpkg.h"
|
|
|
|
static int
|
|
runwait(const char *cmd)
|
|
{
|
|
int rc = system(cmd);
|
|
if (rc == -1) return -1;
|
|
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
|
return -1;
|
|
}
|
|
|
|
struct row { const char *label; const char *src; int want; };
|
|
|
|
static const struct row rows[] = {
|
|
/* GLOBAL sret assign: `let g: quad;` at module scope, `g = mk()`
|
|
* in main. Pre-fix the global got only `MOVQ AX, g(SB)` and the
|
|
* field reads returned garbage. */
|
|
{ "global_assign",
|
|
"type quad = struct { a: i64, b: i64, c: i64, d: i64 };\n"
|
|
"fn mk(x: i64) quad = {\n"
|
|
" return quad { a = x, b = x + 1i64, c = x + 2i64, d = x + 3i64 };\n"
|
|
"};\n"
|
|
"let g: quad;\n"
|
|
"export fn main() i32 = {\n"
|
|
" g = mk(10i64);\n"
|
|
" if (g.a != 10i64) { return 1; };\n"
|
|
" if (g.b != 11i64) { return 2; };\n"
|
|
" if (g.c != 12i64) { return 3; };\n"
|
|
" if (g.d != 13i64) { return 4; };\n"
|
|
" return 0;\n"
|
|
"};\n",
|
|
0 },
|
|
/* GLOBAL sret with a BRANCHED callee (#105): the returned value
|
|
* is selected at runtime so a constant-fold coincidence can't mask
|
|
* the register/dest routing. */
|
|
{ "global_branched",
|
|
"type quad = struct { a: i64, b: i64, c: i64, d: i64 };\n"
|
|
"fn mk1() quad = { return quad { a = 1i64, b = 2i64, c = 3i64, d = 4i64 }; };\n"
|
|
"fn mk2() quad = { return quad { a = 50i64, b = 0i64, c = 0i64, d = 0i64 }; };\n"
|
|
"fn pick(w: i64) quad = {\n"
|
|
" if (w == 1i64) { return mk1(); };\n"
|
|
" return mk2();\n"
|
|
"};\n"
|
|
"let g: quad;\n"
|
|
"export fn main() i32 = {\n"
|
|
" g = pick(1i64);\n"
|
|
" if (g.a != 1i64) { return 1; };\n"
|
|
" if (g.b != 2i64) { return 2; };\n"
|
|
" if (g.c != 3i64) { return 3; };\n"
|
|
" if (g.d != 4i64) { return 4; };\n"
|
|
" return 0;\n"
|
|
"};\n",
|
|
0 },
|
|
/* GLOBAL mutated THROUGH A POINTER after the sret assign — the io
|
|
* vtable-callback shape that surfaced #220 (callback receives &g,
|
|
* mutates a field). */
|
|
{ "global_through_pointer",
|
|
"type quad = struct { a: i64, b: i64, c: i64, d: i64 };\n"
|
|
"fn mk(x: i64) quad = {\n"
|
|
" return quad { a = x, b = x + 1i64, c = x + 2i64, d = x + 3i64 };\n"
|
|
"};\n"
|
|
"fn bump(p: *quad) void = { p.a += 100i64; };\n"
|
|
"let g: quad;\n"
|
|
"export fn main() i32 = {\n"
|
|
" g = mk(7i64);\n"
|
|
" bump(&g);\n"
|
|
" if (g.a != 107i64) { return 1; };\n"
|
|
" if (g.b != 8i64) { return 2; };\n"
|
|
" if (g.c != 9i64) { return 3; };\n"
|
|
" if (g.d != 10i64) { return 4; };\n"
|
|
" return 0;\n"
|
|
"};\n",
|
|
0 },
|
|
/* LOCAL decl+assign regression: `let g: quad; g = mk();` routes
|
|
* through the same cgassign sret-receive site the fix touches. */
|
|
{ "local_assign_regression",
|
|
"type quad = struct { a: i64, b: i64, c: i64, d: i64 };\n"
|
|
"fn mk(x: i64) quad = {\n"
|
|
" return quad { a = x, b = x + 1i64, c = x + 2i64, d = x + 3i64 };\n"
|
|
"};\n"
|
|
"export fn main() i32 = {\n"
|
|
" let g: quad;\n"
|
|
" g = mk(20i64);\n"
|
|
" if (g.a != 20i64) { return 1; };\n"
|
|
" if (g.b != 21i64) { return 2; };\n"
|
|
" if (g.c != 22i64) { return 3; };\n"
|
|
" if (g.d != 23i64) { return 4; };\n"
|
|
" return 0;\n"
|
|
"};\n",
|
|
0 },
|
|
/* LOCAL let-init regression: `let g: quad = mk();` routes through
|
|
* cglet's sret-receive branch (the sister site). */
|
|
{ "local_init_regression",
|
|
"type quad = struct { a: i64, b: i64, c: i64, d: i64 };\n"
|
|
"fn mk(x: i64) quad = {\n"
|
|
" return quad { a = x, b = x + 1i64, c = x + 2i64, d = x + 3i64 };\n"
|
|
"};\n"
|
|
"export fn main() i32 = {\n"
|
|
" let g: quad = mk(30i64);\n"
|
|
" if (g.a != 30i64) { return 1; };\n"
|
|
" if (g.b != 31i64) { return 2; };\n"
|
|
" if (g.c != 32i64) { return 3; };\n"
|
|
" if (g.d != 33i64) { return 4; };\n"
|
|
" return 0;\n"
|
|
"};\n",
|
|
0 },
|
|
};
|
|
|
|
/* #94 sep layout: concat the per-package asm the cstage sep build emitted
|
|
* (<dir>/gsret_c_<i>.sepwork) and the wwstage sep build emitted
|
|
* (<dir>/gsret_w_<i>.sepwork), then assert byte-identical. Returns 0 ok,
|
|
* 1 differ, -1 harness error. */
|
|
static int
|
|
byteid(const char *dir, int i)
|
|
{
|
|
char cs[256], ws[256], cmd[1024];
|
|
int rc;
|
|
snprintf(cs, sizeof cs, "%s/bid_cs_%d.s", dir, i);
|
|
snprintf(ws, sizeof ws, "%s/bid_ww_%d.s", dir, i);
|
|
|
|
/* an unmatched sepwork glob must be a loud harness error — two
|
|
* empty concatenations would otherwise cmp equal (vacuous green). */
|
|
snprintf(cmd, sizeof cmd, "cat %s/gsret_c_%d.sepwork/*.s > %s 2>/dev/null",
|
|
dir, i, cs);
|
|
if (runwait(cmd) != 0) {
|
|
rc = -1;
|
|
goto cleanup;
|
|
}
|
|
snprintf(cmd, sizeof cmd, "cat %s/gsret_w_%d.sepwork/*.s > %s 2>/dev/null",
|
|
dir, i, ws);
|
|
if (runwait(cmd) != 0) {
|
|
rc = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
snprintf(cmd, sizeof cmd, "cmp -s %s %s", cs, ws);
|
|
rc = runwait(cmd) == 0 ? 0 : 1;
|
|
cleanup:
|
|
if (unlink(cs) != 0 && errno != ENOENT) { perror(cs); if (rc == 0) rc = -1; }
|
|
if (unlink(ws) != 0 && errno != ENOENT) { perror(ws); if (rc == 0) rc = -1; }
|
|
return rc;
|
|
}
|
|
|
|
/* Build `r` with `driver -o <dir>/<stem>` (per-package asm in
|
|
* <dir>/<stem>.sepwork/), returning zero on build success. */
|
|
static int
|
|
run_driver(const char *driver, const struct row *r, const char *dir,
|
|
int i, const char *stem)
|
|
{
|
|
char src[256], taggedstem[128], cmd[1024];
|
|
snprintf(src, sizeof src, "%s/gsret_%d.ww", dir, i);
|
|
snprintf(taggedstem, sizeof taggedstem, "%s_%d", stem, i);
|
|
|
|
FILE *f = fopen(src, "wb");
|
|
if (!f) return -1;
|
|
wwtest_fputs(r->src, f);
|
|
int werr = ferror(f);
|
|
if (fclose(f) != 0 || werr) {
|
|
fprintf(stderr, "row[%s]: writing %s failed\n", r->label, src);
|
|
return -1;
|
|
}
|
|
|
|
snprintf(cmd, sizeof cmd,
|
|
"cd %s && %s build -S -o %s/%s %s",
|
|
dir, driver, dir, taggedstem, src);
|
|
if (runwait(cmd) != 0) {
|
|
fprintf(stderr, "row[%s]: build via %s failed\n",
|
|
r->label, driver);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
main(void)
|
|
{
|
|
const char *bin = getenv("BIN");
|
|
if (!bin) bin = "out/bin";
|
|
char absbin[512];
|
|
if (bin[0] != '/') {
|
|
char cwd[256];
|
|
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
|
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
|
bin = absbin;
|
|
}
|
|
|
|
char cdrv[640], wdrv[640];
|
|
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
|
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
|
char wwc[640];
|
|
snprintf(wwc, sizeof wwc, "%s/w6c_ww", bin);
|
|
int have_ww = (access(wdrv, X_OK) == 0);
|
|
int have_wwc = (access(wwc, X_OK) == 0);
|
|
|
|
char dir[] = "/tmp/gsret_XXXXXX";
|
|
if (mkdtemp(dir) == NULL) {
|
|
perror("mkdtemp");
|
|
return 1;
|
|
}
|
|
|
|
int n = (int)(sizeof rows / sizeof rows[0]);
|
|
int total = 0, fail = 0, cleanfail = 0;
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
/* cstage separate build → <dir>/gsret_c_<i>.sepwork/. */
|
|
int gc = run_driver(cdrv, &rows[i], dir, i, "gsret_c");
|
|
total++;
|
|
if (gc != rows[i].want) {
|
|
fprintf(stderr, "global_sret_run[cstage][%s]: "
|
|
"build=%d want=%d\n", rows[i].label, gc,
|
|
rows[i].want);
|
|
fail++;
|
|
}
|
|
|
|
/* wwstage separate build → <dir>/gsret_w_<i>.sepwork/. */
|
|
if (have_ww) {
|
|
int gw = run_driver(wdrv, &rows[i], dir, i, "gsret_w");
|
|
total++;
|
|
if (gw != rows[i].want) {
|
|
fprintf(stderr, "global_sret_run[wwstage][%s]: "
|
|
"build=%d want=%d\n", rows[i].label, gw,
|
|
rows[i].want);
|
|
fail++;
|
|
}
|
|
|
|
/* byte-id (rule 10): the per-package w6c asm (cstage sep
|
|
* build) == the w6c_ww asm (wwstage sep build). */
|
|
if (have_wwc) {
|
|
total++;
|
|
int bid = byteid(dir, i);
|
|
if (bid != 0) {
|
|
fprintf(stderr, "global_sret_run[byteid][%s]: "
|
|
"%s\n", rows[i].label,
|
|
bid == 1 ? "cs.s != ww.s"
|
|
: "harness error");
|
|
fail++;
|
|
}
|
|
}
|
|
}
|
|
|
|
char p[640];
|
|
snprintf(p, sizeof p, "%s/gsret_%d.ww", dir, i);
|
|
/* ENOENT = the row failed before writing the source. */
|
|
if (unlink(p) != 0 && errno != ENOENT) {
|
|
perror(p);
|
|
cleanfail = 1;
|
|
}
|
|
snprintf(p, sizeof p, "rm -rf %s/gsret_c_%d.sepwork "
|
|
"%s/gsret_w_%d.sepwork %s/gsret_c_%d %s/gsret_w_%d",
|
|
dir, i, dir, i, dir, i, dir, i);
|
|
if (runwait(p) != 0) {
|
|
fprintf(stderr, "global_sret_run: cleanup row %d "
|
|
"failed\n", i);
|
|
cleanfail = 1;
|
|
}
|
|
}
|
|
if (rmdir(dir) != 0) {
|
|
perror(dir);
|
|
cleanfail = 1;
|
|
}
|
|
|
|
if (!have_ww)
|
|
fprintf(stderr, "global_sret_run: skip wwstage (no %s)\n", wdrv);
|
|
|
|
if (fail) {
|
|
fprintf(stderr, "global_sret_run: %d/%d checks failed\n",
|
|
fail, total);
|
|
return 1;
|
|
}
|
|
/* cleanup diagnostics already printed; must not stay green. */
|
|
if (cleanfail)
|
|
return 1;
|
|
printf("global_sret_run: %d/%d ok\n", total, total);
|
|
return 0;
|
|
}
|