/* * 989_globptrfield_run — inverse-set cstage fix (report-item [58], ww correct): * a field store through a module-GLOBAL `*struct` pointer must load the pointer * value from g(SB), not deref the saved-BP word. * * THE BUG (cat-A divergence — cstage SEGV, wwstage correct): in cgen.c the * N_DOT-lhs scalar field-store via_ptr arm loaded the base pointer with * `MOVQ boff(BP),BX`. For a module-global `*struct` ident boff==0, so cstage * emitted `MOVQ (BP),BX` — derefing the saved BP word as if it were the * pointer → store through garbage → SEGV (rc=139). wwstage already loaded the * pointer from the symbol (`LEAQ gp(SB),BX; MOVQ (BX),BX`) and ran correctly * (rc=7). This is the INVERSE of the F8 #263 set: ww is the runtime-correct * reference; the cstage half is filed as ww-core TASK #47. * * THE CSTAGE FIX (align cstage UP to ww): the via_ptr scalar store detects a * global base (boff==0 && let_islet) and emits the ww-correct global-pointer * load — PUSHQ AX; LEAQ gp(SB),BX; MOVQ (BX),BX; POPQ AX — before the store. * Local bases keep the BP load (byte-id). Both stages now emit the identical * .s and exit 7. * * The static-init form `let gp: *S = &backing;` is blocked on wwstage by a * separate static-init-of-&global gap (ww-core TASK #48), so the repro uses * the nil-init + runtime `gp = &backing` form, which builds on both stages. */ #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; /* cs==ww==want */ }; static const struct row rows[] = { { "global_ptr_field_store", "package main;\n" "type S = struct { f: i64, g: i64 };\n" "let backing: S = S { f = 0, g = 0 };\n" "let gp: *S = nil;\n" "export fn main() int = {\n" " gp = &backing;\n" " gp.f = 7;\n" " return backing.f: int;\n" "};\n", 7 }, }; /* run_build — build+run `src` via `driver`; returns the binary's exit * code, or -1 on a build failure. */ static int run_build(const char *driver, const struct row *r, int i) { char src[64], tmpdir[64], cmd[1024]; snprintf(src, sizeof src, "/tmp/gpf_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/gpf_%d_d_%d", getpid(), i); FILE *f = fopen(src, "wb"); if (!f) return -2; fputs(r->src, f); fclose(f); mkdir(tmpdir, 0755); snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null", tmpdir, driver, src); int brc = runwait(cmd); 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 = -1; if (brc == 0) got = runwait(outbin); unlink(src); unlink(outbin); rmdir(tmpdir); return brc == 0 ? got : -1; } 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], wdrv[1024]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); struct { const char *name; const char *drv; int gated; } 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 && access(drivers[d].drv, X_OK) != 0) { fprintf(stderr, "globptrfield_run: skip %s (no %s)\n", drivers[d].name, drivers[d].drv); continue; } for (int i = 0; i < n; i++) { total++; int got = run_build(drivers[d].drv, &rows[i], i); if (got != rows[i].want) { fprintf(stderr, "globptrfield_run[%s][%s]: exit=%d " "want=%d\n", drivers[d].name, rows[i].label, got, rows[i].want); fail++; } } } if (fail) { fprintf(stderr, "globptrfield_run: %d/%d fixtures failed\n", fail, total); return 1; } printf("globptrfield_run: %d/%d ok\n", total, total); return 0; }