/* * 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. * * Each row runs on BOTH cstage (ww) and wwstage (ww_ww), and the * generated asm is checked byte-identical (cs.s == ww.s, rule 10). */ #include #include #include #include #include #include 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 * (/gsret_c_.sepwork) and the wwstage sep build emitted * (/gsret_w_.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]; snprintf(cs, sizeof cs, "%s/bid_cs_%d.s", dir, i); snprintf(ws, sizeof ws, "%s/bid_ww_%d.s", dir, i); snprintf(cmd, sizeof cmd, "cat %s/gsret_c_%d.sepwork/*.s > %s 2>/dev/null", dir, i, cs); if (system(cmd)) {} snprintf(cmd, sizeof cmd, "cat %s/gsret_w_%d.sepwork/*.s > %s 2>/dev/null", dir, i, ws); if (system(cmd)) {} snprintf(cmd, sizeof cmd, "cmp -s %s %s", cs, ws); int rc = runwait(cmd); unlink(cs); unlink(ws); return rc == 0 ? 0 : 1; } /* Build `r` with `driver` via `--sep -o /` (per-package asm in * /.sepwork/), run the binary, return its exit code (or -1 on * build failure). `cachetag` keys a private WW_PKGCACHE. */ static int run_driver(const char *driver, const struct row *r, const char *dir, int i, const char *stem, const char *cachetag) { char src[256], cmd[1024]; snprintf(src, sizeof src, "%s/gsret_%d.ww", dir, i); FILE *f = fopen(src, "wb"); if (!f) return -1; fputs(r->src, f); fclose(f); snprintf(cmd, sizeof cmd, "cd %s && WW_PKGCACHE=%s/pkgc_%s %s build --sep -o %s/%s %s", dir, dir, cachetag, driver, dir, stem, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: build via %s failed\n", r->label, driver); return -1; } char outbin[256]; snprintf(outbin, sizeof outbin, "%s/%s", dir, stem); return runwait(outbin); } 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; for (int i = 0; i < n; i++) { /* cstage: --sep build + run → /gsret_c_.sepwork/. */ int gc = run_driver(cdrv, &rows[i], dir, i, "gsret_c", "c"); total++; if (gc != rows[i].want) { fprintf(stderr, "global_sret_run[cstage][%s]: " "exit=%d want=%d\n", rows[i].label, gc, rows[i].want); fail++; } /* wwstage: --sep build + run → /gsret_w_.sepwork/. */ if (have_ww) { int gw = run_driver(wdrv, &rows[i], dir, i, "gsret_w", "w"); total++; if (gw != rows[i].want) { fprintf(stderr, "global_sret_run[wwstage][%s]: " "exit=%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); unlink(p); snprintf(p, sizeof p, "rm -rf %s/gsret_c_%d.sepwork " "%s/gsret_w_%d.sepwork %s/gsret_c_%d %s/gsret_w_%d " "%s/pkgc_c %s/pkgc_w", dir, i, dir, i, dir, i, dir, i, dir, dir); if (system(p)) {} } rmdir(dir); 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; } printf("global_sret_run: %d/%d ok\n", total, total); return 0; }