/* * 822_struct_global_byval_arg — a module-global struct passed BY VALUE to * a fn (`let g: pt = pt{...}; take(g)`) was silently miscompiled, mirror- * opposite on the two stages (task #150, the off==0 localfind footgun / * GAP-A.ptr / #231 family): * * - cstage (cmd/w6c/cgen.c by-value struct-IDENT call-arg arm): localfind * of a global returns 0, so the BP loads read the stack FRAME, never * `LEAQ main.g(SB)` → garbage (ken saw 104, not the field sum). * - wwstage (selfhost cgenexpr/cgenutil pushargsrev struct arm): the * ≤16B-struct ident with no local slot fell past the local fast path * AND was excluded from the #271 aggregate arm (structident), landing * on the scalar single-PUSHQ default — ONE word for a 2-word struct, * so the callee's word1 read stack garbage (silent field-drop). * * The fix (ONE both-stage cgen commit, converged byte-IDENTICAL): when the * struct-arg operand is a module-global (off==0 && let_islet||def_isstructdef), * resolve the global base into BX via `LEAQ name(SB)` and copy ALL * eightbytes (`MOVQ off(BX),AX; PUSHQ`) high→low — the #129-A.2 struct- * global access shape. The LOCAL path (off!=0) is untouched. * * row | shape | want * ------------+------------------------------------------------+------ * two_field | let g:pt{a:int,b:int}=pt{a=5,b=9}; take(g) | 14 * narrow_mix | let g:struct{a:i32,b:i32,c:int}; sum | 6 * three_word | let g:struct{a,b,c:int} (24B, #271 arm); sum | 7 * ctrl_local | LOCAL struct arg (off!=0, unchanged, byte-id) | 14 * * two_field / narrow_mix are the mutation-sane rows: pre-fix cstage reads * the frame (garbage) and wwstage drops word1. three_word confirms the >16B * #271 aggregate arm already handles a struct global (let_islet base). * ctrl_local guards the unchanged local-struct path. A byte-id row pins * cstage==wwstage `.s` for each source — the convergence the fix delivers. * * Explicit-typed globals ONLY (`let g: pt = ...`): an inferred `let g = * pt{}` would loud via the separate checker Bug 1 (#18) and mask #150. */ #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[] = { /* two_field — THE regression pin: 2-field 16B struct global by value. * Pre-fix cstage reads the frame (garbage), wwstage drops field b. */ { "two_field", "package main;\n" "type pt = struct { a: int, b: int };\n" "let g: pt = pt { a = 5, b = 9 };\n" "fn take(x: pt) int = { return x.a + x.b; };\n" "export fn main() i32 = { return take(g): i32; };\n", 14 }, /* narrow_mix — i32/i32/int packs two narrow fields into eightbyte 0, * int into eightbyte 1; 16B → the struct-ident fast arm (the fix). */ { "narrow_mix", "package main;\n" "type pt = struct { a: i32, b: i32, c: int };\n" "let g: pt = pt { a = 1, b = 2, c = 3 };\n" "fn take(x: pt) int = { return (x.a: int) + (x.b: int) + x.c; };\n" "export fn main() i32 = { return take(g): i32; };\n", 6 }, /* three_word — 24B struct global (>16B) routes through the #271 * aggregate arm; confirms the global base (let_islet) is honoured. */ { "three_word", "package main;\n" "type pt = struct { a: int, b: int, c: int };\n" "let g: pt = pt { a = 1, b = 2, c = 4 };\n" "fn take(x: pt) int = { return x.a + x.b + x.c; };\n" "export fn main() i32 = { return take(g): i32; };\n", 7 }, /* ctrl_local — a LOCAL struct arg (off!=0). cgen unchanged; pins the * local-struct push path stays byte-id (no regress). */ { "ctrl_local", "package main;\n" "type pt = struct { a: int, b: int };\n" "fn take(x: pt) int = { return x.a + x.b; };\n" "export fn main() i32 = {\n" "\tlet g: pt = pt { a = 5, b = 9 };\n" "\treturn take(g): i32;\n" "};\n", 14 }, }; static int run_driver(const char *driver, const struct row *r, int i) { char src[64], tmpdir[64], cmd[1024]; snprintf(src, sizeof src, "/tmp/sga_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/sga_%d_d_%d", getpid(), i); FILE *f = fopen(src, "wb"); if (!f) return -1; fputs(r->src, f); fclose(f); mkdir(tmpdir, 0755); snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null", tmpdir, driver, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: build via %s failed\n", r->label, driver); unlink(src); rmdir(tmpdir); return -1; } const char *base = strrchr(src, '/'); base = base ? base + 1 : src; char outbin[128]; snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); char *dot = strrchr(outbin, '.'); if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; int got = runwait(outbin); unlink(src); unlink(outbin); rmdir(tmpdir); return got; } /* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */ static int asm_byte_identical(const char *bin, const struct row *r, int i) { char src[64], cs[64], ws[64], cmd[1024]; snprintf(src, sizeof src, "/tmp/sga_asm_%d_%d.ww", getpid(), i); snprintf(cs, sizeof cs, "/tmp/sga_asm_%d_%d_c.s", getpid(), i); snprintf(ws, sizeof ws, "/tmp/sga_asm_%d_%d_w.s", getpid(), i); FILE *f = fopen(src, "wb"); if (!f) return -1; fputs(r->src, f); fclose(f); snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: w6c errored\n", r->label); unlink(src); return -1; } snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null", bin, ws, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label); unlink(src); unlink(cs); return -1; } FILE *fc = fopen(cs, "rb"); FILE *fw = fopen(ws, "rb"); int rc = 0; if (!fc || !fw) { rc = -1; } else { for (;;) { int a = fgetc(fc); int b = fgetc(fw); if (a != b) { rc = -1; break; } if (a == EOF) break; } } if (fc) fclose(fc); if (fw) fclose(fw); if (rc != 0) fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n", r->label); unlink(src); unlink(cs); unlink(ws); return rc; } int main(void) { const char *bin = getenv("BIN"); if (!bin) bin = "out/bin"; char absbin[1024]; if (bin[0] != '/') { char cwd[1024]; if (getcwd(cwd, sizeof cwd) == NULL) return 1; snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); bin = absbin; } char cdrv[1024]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); char wdrv[1024]; snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); struct { const char *name; const char *path; int gated_on_existence; } drivers[] = { { "cstage", cdrv, 0 }, { "wwstage", wdrv, 1 }, { NULL, NULL, 0 }, }; int n = (int)(sizeof rows / sizeof rows[0]); int total = 0, fail = 0; for (int d = 0; drivers[d].name; d++) { if (drivers[d].gated_on_existence && access(drivers[d].path, X_OK) != 0) { fprintf(stderr, "struct_global_byval_arg: skip %s (no %s)\n", drivers[d].name, drivers[d].path); continue; } for (int i = 0; i < n; i++) { int got = run_driver(drivers[d].path, &rows[i], i); total++; if (got != rows[i].want) { fprintf(stderr, "struct_global_byval_arg[%s][%s]: exit=%d want=%d\n", drivers[d].name, rows[i].label, got, rows[i].want); fail++; } } } if (access(wdrv, X_OK) == 0) { for (int i = 0; i < n; i++) { total++; if (asm_byte_identical(bin, &rows[i], i) != 0) fail++; } } if (fail) { fprintf(stderr, "struct_global_byval_arg: %d/%d fixtures failed\n", fail, total); return 1; } printf("struct_global_byval_arg: %d/%d ok\n", total, total); return 0; }