From 77747061c65b2c4d3c1c7d6dbb5fe7b09a3cba4d Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 12 Jun 2026 22:48:37 +0900 Subject: [PATCH] w6c: global *struct base field store loads the pointer from g(SB) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cstage dereferenced 0(BP) for the base — a SEGV on every store through a module-global struct pointer; wwstage was already correct (the inverse-set member). Mirror ww's global-pointer load. New 989_globptrfield_run pins cs==ww both stages. Task #47. --- Makefile | 12 +++ cmd/w6c/cgen.c | 22 ++++- test/wcc/989_globptrfield_run.c | 146 ++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 test/wcc/989_globptrfield_run.c diff --git a/Makefile b/Makefile index b3e356ac..7fa9b5f5 100644 --- a/Makefile +++ b/Makefile @@ -264,6 +264,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_globstructret_run \ $(BIN)/test_globstructwiden_run \ $(BIN)/test_globtagwiden_run \ + $(BIN)/test_globptrfield_run \ $(BIN)/test_globfloatstructarg_run \ $(BIN)/test_arr_ptr_global \ $(BIN)/test_def_arr_infer_len \ @@ -823,6 +824,17 @@ $(BIN)/test_globtagwiden_run: test/wcc/989_globtagwiden_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +# 989_globptrfield_run (inverse-set #47, report-item [58]): a field store +# through a module-global *struct pointer must load the pointer from g(SB), +# not deref saved BP. cstage SEGV'd (rc=139); align cstage UP to ww (rc=7), +# cs==ww byte-identical. Runtime-assign repro (static-init form is #48-blocked). +$(BIN)/test_globptrfield_run: test/wcc/989_globptrfield_run.c \ + $(BIN)/ww $(BIN)/ww_ww \ + $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ + $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ + $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + # 989_globfloatstructarg_run (F8-c9, #31, the EXCEPTION): a module-global # float-bearing struct passed by value must drain its float eightbytes into # the SSE arg regs. align-UP — structfloatclass keyed off the global tnode. diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index f168d876..c44d7cc2 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -5368,7 +5368,27 @@ cgexpr(Cg *c, Node *n, Local *locals) } /* now store AX into target */ if (via_ptr) { - ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_BX)); + if (boff == 0 && let_islet(base->str)) { + /* #47 (inverse): a GLOBAL *struct + * base. The pointer VALUE lives at + * gp(SB), not a BP slot — load it + * from the symbol. Pre-fix MOVQ + * (BP),BX derefed saved BP → SEGV. + * Mirrors the already-correct wwstage + * global-base load; cs==ww. (Other + * via_ptr field arms keep the latent + * BP-deref — inverse siblings #60/#61, + * ken-triage.) */ + ins1(c, A_PUSHQ, areg(D_AX)); + ins2(c, A_LEAQ, + masym(c, base->str), areg(D_BX)); + ins2(c, A_MOVQ, amem(D_BX, 0), + areg(D_BX)); + ins1(c, A_POPQ, areg(D_AX)); + } else { + ins2(c, A_MOVQ, + amem(D_BP, boff), areg(D_BX)); + } ins2(c, store_op, areg(D_AX), amem(D_BX, foff)); } else if (is_global) { ins2(c, A_LEAQ, masym(c, base->str), areg(D_BX)); diff --git a/test/wcc/989_globptrfield_run.c b/test/wcc/989_globptrfield_run.c new file mode 100644 index 00000000..0eae15c3 --- /dev/null +++ b/test/wcc/989_globptrfield_run.c @@ -0,0 +1,146 @@ +/* + * 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; +}