/* * 989_globslicefield_run — F8-c1 (report-item #35): a SLICE field of a * module-GLOBAL struct, assigned with `g.field = `, must store the * full {ptr,len,cap} header — both stages, byte-identical and correct. * * THE BUG (cat-A silent miscompile, align-UP): in cgenexpr.ww the * single-dot global-struct field-assign block (`g.f = v`, base is a direct * global struct ident) had a str arm that staged the base in DX and stored * all three header words, but it gated on isstrtype ONLY — a non-str slice * field ([]i64, []int, ...) fell through to the generic 1-word scalar store * (`LEAQ g(SB),BX; MOVQ AX,(BX)`), silently dropping .len and .cap. cstage * (cmd/w6c/cgen.c:5055) gates the same arm on TY_STR||TY_SLICE, so it stored * the full header and ran correct — the cat-A divergence. The bootstrap * corpus never assigns a non-str slice to a global-struct field, so 990-997 * stayed green; a runtime row is the net. THE FIX: the wwstage gate widens * to isstrtype||isslicetype (aligning UP to cstage); a slice rides the * existing, already-correct 3-word DX store. * * Rows (each builds+runs on cstage `ww` and, when present, wwstage `ww_ww`; * rule-10 — both stages agree AND hit want_exit): * row | shape | want * ---------------+---------------------------------------------+----- * glob_slice_len | g:{sl:[]i64}; g.sl=b[0:3]; len(g.sl) | 3 [#35] * glob_slice_cap | same; g.sl.cap (b:[4]i64 → cap 4) | 4 [#35] * glob_off_len | g:{pad:i64,sl:[]i64}; sl@foff 8; len(g.sl) | 2 [#35: * | pins the DX+foff+8 header store] | * glob_str_len | g:{name:str}; g.name="hello"; len(g.name) | 5 (str-arm * | regression pin — the pre-existing path) | * glob_scalar_n | g:{n:i64}; g.n=7; g.n (scalar field control) | 7 (the * | 1-word store the slice arm must not steal) | * * Pre-fix wwstage: glob_slice_len/cap/off_len all read 0 for the dropped * words (the 1-word store); cstage and the str/scalar rows were correct. */ #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_exit; }; static const struct row rows[] = { { "glob_slice_len", "package main;\n" "type box = struct { sl: []i64 };\n" "let g: box;\n" "export fn main() int = {\n" " let b: [4]i64 = [10, 11, 12, 13];\n" " g.sl = b[0:3];\n" " return len(g.sl): int;\n" "};\n", 3 }, { "glob_slice_cap", "package main;\n" "type box = struct { sl: []i64 };\n" "let g: box;\n" "export fn main() int = {\n" " let b: [4]i64 = [10, 11, 12, 13];\n" " g.sl = b[0:3];\n" " return g.sl.cap: int;\n" "};\n", 4 }, /* slice field at NON-ZERO field offset (pad: i64 @0, sl @8). Pins * the DX+foff+8 / +16 header store. */ { "glob_off_len", "package main;\n" "type box = struct { pad: i64, sl: []i64 };\n" "let g: box;\n" "export fn main() int = {\n" " let b: [4]i64 = [10, 11, 12, 13];\n" " g.pad = 99;\n" " g.sl = b[0:2];\n" " return len(g.sl): int;\n" "};\n", 2 }, /* str field on a global struct — the pre-existing isstrtype arm; a * regression pin that the widened gate keeps it byte-identical. */ { "glob_str_len", "package main;\n" "type box = struct { name: str };\n" "let g: box;\n" "export fn main() int = {\n" " g.name = \"hello\";\n" " return len(g.name): int;\n" "};\n", 5 }, /* scalar field on a global struct — the generic 1-word store the * slice arm must not divert (control). */ { "glob_scalar_n", "package main;\n" "type box = struct { n: i64 };\n" "let g: box;\n" "export fn main() int = {\n" " g.n = 7;\n" " return g.n: 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/gslf_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/gslf_%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, "globslicefield_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_exit) { fprintf(stderr, "globslicefield_run[%s][%s]: exit=%d " "want=%d\n", drivers[d].name, rows[i].label, got, rows[i].want_exit); fail++; } } } if (fail) { fprintf(stderr, "globslicefield_run: %d/%d fixtures failed\n", fail, total); return 1; } printf("globslicefield_run: %d/%d ok\n", total, total); return 0; }