/* * 809_idx_structlit_store — cstage and wwstage agree, byte-for-byte and * at runtime, that a struct-LITERAL store into an INDEXED element place * (`a[i] = pt{...}`, `cs[i] = capture{...}`, `(*ts)[i].caps[k] = * capture{...}`) and into a DEREF place (`*p = pt{...}`) writes every * field (task #20; ken's p13 array find + prober PG4 slice extension; * gates regex fold-5a's run_thread groupstart capture store, * regex.ha:643-651). * * Pre-fix BOTH stages compiled these byte-identically WRONG (gate-blind * — only a runtime pin can hold this): the N_ASSIGN N_INDEX-lhs arm's * aggregate branch (#270-1b) gated its rhs on ident/dot/deref shapes, * so an N_STRUCTLIT rhs fell to the scalar store tail; cgexpr on a * struct literal emits NOTHING (AX stays 0) and the tail stored ONE * zero word at the element base. Net effect: every literal field * silently dropped, the element's first 8 bytes zeroed — for a * str-leading element (the regex capture shape) that nulls content.ptr * and the next read of the str field SEGFAULTS. The N_UN(STAR) deref * arm (`*p = pt{...}`) had the same fall-to-scalar tail — same class. * * The fix (BOTH stages, converged byte-identical): a struct-lit rhs * aimed at an N_INDEX, N_UN(STAR), or indexed-base N_DOT place * (`a[i].f = pt{...}`, reviewer-20 sibling — the a[i].f legacy arm's * fldstoreop tail had the same silent drop) skips the legacy arms and * routes through the F6 assign-resolver (cgplaceaddr) — the SAME C1.25 * aggregate arm that wires `(*ts)[i].field = capture{...}` N_DOT * places: materialise the literal into a FRESH per-use @placescr slot * (cg_structlit_fill_bp / cgstructlitfillbp: nested literals, str * fields, TK_ELLIPSIS autofill), resolve the place address, word-copy * esz bytes. Close-by-construction: every non-ident place kind (DOT / * INDEX / UN-STAR) now funnels struct-lit stores through that single * arm; ident places keep their enumerated fill paths. Class boundary * for OTHER rhs kinds at these places: tuple-lit/str-lit into INDEX * and DOT places work (pinned below); array-lit rhs at assignment is * unwired for every place kind and now dies LOUD (task #32, build-fail * rows below); tuple-lit through deref truncates (filed #31-E); CALL * rhs into INDEX/UN-STAR places stores only RAX (filed #31-G). * * row | shape | want * ------------------+-----------------------------------------+------ * arr_elem | a[1] = pt{3,4}; a[1].x*10+a[1].y | 34 * arr_neighbor | store a[1]; a[0]/a[2] field-set intact | 82 * arr_var_idx | a[geti()] = pt{...} (runtime index) | 34 * small_elem | 8B one-field elem (esz<=8 word-copy) | 52 * nested_lit | a[1] = outer{ in = inner{3,4}, t=5 } | 45 * partial_ellipsis | prefill 9,9; a[1] = pt{x=3, ...} → y=0 | 30 * alias_lit | type qt = pt; a[1] = qt{3,4} (chase | 34 * | peels the NAMED alias before TY_STRUCT) | * all_default | prefill 9,9; a[1] = pt{} zeroes EVERY | 7 * | word (fill-loop floor) + 7 | * tuple_elem | a[1] = (3,4); copy-out readback | 34 * dotidx_field | a[1].p = pt{3,4} (indexed-base DOT | 34 * | place, reviewer-20 sibling fix) | * slice_elem_str | cs[1] = capture{...} 56B str-leading | 128 * fieldplace_slice | ts[0].caps[1] = capture{...} | 128 * derefspine_slice | (*tsp)[0].caps[1] = capture{...} | 128 * callee_capture | (*ts)[i].caps[k] = capture{...} in a | 197 * | callee w/ *[]thr param; str readback via | * | strings.compare + len + both neighbors | * | (the EXACT fold-5a consumer shape) | * deref_lit | *p = pt{3,4} (N_UN place, same class) | 34 * global_arr | g[1] = pt{3,4}, g: [3]pt module let | 34 * arrlit_idx_loud | a[1] = [3,4] — LOUD build-fail (#32) | -1 * arrlit_deref_loud | *p = [3,4] — LOUD build-fail (#32) | -1 * * global_arr is RUNTIME-ONLY (byteid=0): an uninitialised [N]struct * value-global already diverges on master — wwstage emits a zero * `DATAW main.g(SB)` record (under-sized: 24B for a 48B array), * cstage emits none. Pre-existing, independent of this fix (task #11 * family: value-global data emission); the store/readback this row * pins is symmetric and correct on both stages. The arrlit_* rows pin * task #32's loud boundary (want -1 = driver build MUST fail; pre-#32 * both stages silently zeroed one word); flip them to runtime rows * when #32 wires the fill. */ #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; int byteid; }; /* The 56B regex capture shape: str header leading + 4 size fields. * Shared preamble for the slice rows; only the store site differs. */ #define CAPTURE_DEFS \ "package main;\n" \ "type capture = struct {\n" \ "\tcontent: str,\n" \ "\tstart: size,\n" \ "\tstart_bytesize: size,\n" \ "\tend: size,\n" \ "\tend_bytesize: size,\n" \ "};\n" #define CAPTURE_SEED \ "\tlet cs: []capture = [];\n" \ "\tappend(cs, capture { content = \"a\", start = 1: size, " \ "start_bytesize = 1: size, end = 1: size, " \ "end_bytesize = 1: size });\n" \ "\tappend(cs, capture { content = \"b\", start = 2: size, " \ "start_bytesize = 2: size, end = 2: size, " \ "end_bytesize = 2: size });\n" #define CAPTURE_STORE(place) \ "\t" place " = capture { content = \"grp\", start = 3: size, " \ "start_bytesize = 4: size, end = 60: size, " \ "end_bytesize = 61: size };\n" static const struct row rows[] = { { "arr_elem", "package main;\n" "type pt = struct { x: u64, y: u64 };\n" "export fn main() i32 = {\n" "\tlet a: [3]pt;\n" "\ta[1] = pt { x = 3u64, y = 4u64 };\n" "\treturn (a[1].x * 10u64 + a[1].y): i32;\n" "};\n", 34, 1 }, { "arr_neighbor", "package main;\n" "type pt = struct { x: u64, y: u64 };\n" "export fn main() i32 = {\n" "\tlet a: [3]pt;\n" "\ta[0].x = 7u64; a[2].y = 9u64;\n" "\ta[1] = pt { x = 3u64, y = 4u64 };\n" "\treturn (a[0].x * 10u64 + a[2].y + a[1].x): i32;\n" "};\n", 82, 1 }, { "arr_var_idx", "package main;\n" "type pt = struct { x: u64, y: u64 };\n" "fn geti() size = { return 1: size; };\n" "export fn main() i32 = {\n" "\tlet a: [3]pt;\n" "\ta[geti()] = pt { x = 3u64, y = 4u64 };\n" "\treturn (a[1].x * 10u64 + a[1].y): i32;\n" "};\n", 34, 1 }, { "small_elem", "package main;\n" "type one = struct { v: u64 };\n" "export fn main() i32 = {\n" "\tlet a: [2]one;\n" "\ta[0].v = 2u64;\n" "\ta[1] = one { v = 5u64 };\n" "\treturn (a[1].v * 10u64 + a[0].v): i32;\n" "};\n", 52, 1 }, { "nested_lit", "package main;\n" "type inner = struct { a: u64, b: u64 };\n" "type outer = struct { in: inner, t: u64 };\n" "export fn main() i32 = {\n" "\tlet a: [2]outer;\n" "\ta[1] = outer { in = inner { a = 3u64, b = 4u64 }, " "t = 5u64 };\n" "\treturn (a[1].in.b * 10u64 + a[1].t): i32;\n" "};\n", 45, 1 }, { "partial_ellipsis", "package main;\n" "type pt = struct { x: u64, y: u64 };\n" "export fn main() i32 = {\n" "\tlet a: [3]pt;\n" "\ta[1].x = 9u64; a[1].y = 9u64;\n" "\ta[1] = pt { x = 3u64, ... };\n" "\treturn (a[1].x * 10u64 + a[1].y): i32;\n" "};\n", 30, 1 }, { "alias_lit", "package main;\n" "type pt = struct { x: u64, y: u64 };\n" "type qt = pt;\n" "export fn main() i32 = {\n" "\tlet a: [3]pt;\n" "\ta[1] = qt { x = 3u64, y = 4u64 };\n" "\treturn (a[1].x * 10u64 + a[1].y): i32;\n" "};\n", 34, 1 }, { "all_default", "package main;\n" "type pt = struct { x: u64, y: u64 };\n" "export fn main() i32 = {\n" "\tlet a: [3]pt;\n" "\ta[1].x = 9u64; a[1].y = 9u64;\n" "\ta[1] = pt {};\n" "\treturn (a[1].x * 10u64 + a[1].y + 7u64): i32;\n" "};\n", 7, 1 }, { "tuple_elem", "package main;\n" "export fn main() i32 = {\n" "\tlet a: [2](u64, u64);\n" "\ta[1] = (3u64, 4u64);\n" "\tlet t: (u64, u64) = a[1];\n" "\treturn (t.0 * 10u64 + t.1): i32;\n" "};\n", 34, 1 }, { "dotidx_field", "package main;\n" "type pt = struct { x: u64, y: u64 };\n" "type box = struct { p: pt, t: u64 };\n" "export fn main() i32 = {\n" "\tlet a: [2]box;\n" "\ta[1].p.x = 9u64; a[1].p.y = 9u64;\n" "\tif (a[1].p.x != 9u64 || a[1].p.y != 9u64) { return 250; };\n" "\ta[1].p = pt { x = 3u64, y = 4u64 };\n" "\treturn (a[1].p.x * 10u64 + a[1].p.y): i32;\n" "};\n", 34, 1 }, { "slice_elem_str", CAPTURE_DEFS "export fn main() i32 = {\n" CAPTURE_SEED CAPTURE_STORE("cs[1]") "\tif (cs[0].start != 1) { return 250; };\n" "\treturn (cs[1].start + cs[1].start_bytesize + cs[1].end + " "cs[1].end_bytesize): i32;\n" "};\n", 128, 1 }, { "fieldplace_slice", CAPTURE_DEFS "type thr = struct { pc: size, caps: []capture };\n" "export fn main() i32 = {\n" CAPTURE_SEED "\tlet ts: []thr = [];\n" "\tappend(ts, thr { pc = 5: size, caps = cs });\n" CAPTURE_STORE("ts[0].caps[1]") "\tif (ts[0].caps[0].start != 1) { return 250; };\n" "\treturn (ts[0].caps[1].start + ts[0].caps[1].start_bytesize + " "ts[0].caps[1].end + ts[0].caps[1].end_bytesize): i32;\n" "};\n", 128, 1 }, { "derefspine_slice", CAPTURE_DEFS "type thr = struct { pc: size, caps: []capture };\n" "export fn main() i32 = {\n" CAPTURE_SEED "\tlet ts: []thr = [];\n" "\tappend(ts, thr { pc = 5: size, caps = cs });\n" "\tlet tsp = &ts;\n" CAPTURE_STORE("(*tsp)[0].caps[1]") "\tif (ts[0].caps[0].start != 1) { return 250; };\n" "\treturn (ts[0].caps[1].start + ts[0].caps[1].start_bytesize + " "ts[0].caps[1].end + ts[0].caps[1].end_bytesize): i32;\n" "};\n", 128, 1 }, /* The fold-5a consumer pin: regex.ha:643-651 verbatim shape — * callee takes *[]thr, double-indexes through the deref spine, * stores the 56B capture literal. Readback covers the str field * (strings.compare + len — pre-fix this SEGFAULTED on the nulled * content.ptr), both neighbor elements (56B store must not * smear), and all four size fields. 3+4+60+61+3*23 = 197. */ { "callee_capture", "package main;\n" "\n" "import strings;\n" "\n" "type capture = struct {\n" "\tcontent: str,\n" "\tstart: size,\n" "\tstart_bytesize: size,\n" "\tend: size,\n" "\tend_bytesize: size,\n" "};\n" "type thr = struct { pc: size, caps: []capture };\n" "fn store(ts: *[]thr, i: size, idx: size) void = {\n" "\t(*ts)[i].caps[idx] = capture {\n" "\t\tcontent = \"grp\",\n" "\t\tstart = 3: size,\n" "\t\tstart_bytesize = 4: size,\n" "\t\tend = 60: size,\n" "\t\tend_bytesize = 61: size,\n" "\t};\n" "};\n" "export fn main() i32 = {\n" "\tlet cs: []capture = [];\n" "\tappend(cs, capture { content = \"a\", start = 1: size, " "start_bytesize = 1: size, end = 1: size, " "end_bytesize = 1: size });\n" "\tappend(cs, capture { content = \"b\", start = 2: size, " "start_bytesize = 2: size, end = 2: size, " "end_bytesize = 2: size });\n" "\tappend(cs, capture { content = \"c\", start = 9: size, " "start_bytesize = 9: size, end = 9: size, " "end_bytesize = 9: size });\n" "\tlet ts: []thr = [];\n" "\tappend(ts, thr { pc = 5: size, caps = cs });\n" "\tstore(&ts, 0: size, 1: size);\n" "\tif (strings.compare(ts[0].caps[1].content, \"grp\") != 0) " "{ return 250; };\n" "\tif (len(ts[0].caps[1].content) != 3) { return 249; };\n" "\tif (ts[0].caps[0].end != 1 || ts[0].caps[2].start != 9) " "{ return 248; };\n" "\tif (strings.compare(ts[0].caps[2].content, \"c\") != 0) " "{ return 247; };\n" "\tlet acc = ts[0].caps[1].start + ts[0].caps[1].start_bytesize\n" "\t\t+ ts[0].caps[1].end + ts[0].caps[1].end_bytesize\n" "\t\t+ ts[0].caps[1].start * 23;\n" "\treturn acc: i32;\n" "};\n", 197, 1 }, { "deref_lit", "package main;\n" "type pt = struct { x: u64, y: u64 };\n" "export fn main() i32 = {\n" "\tlet a: pt = pt { x = 1u64, y = 2u64 };\n" "\tlet p: *pt = &a;\n" "\t*p = pt { x = 3u64, y = 4u64 };\n" "\treturn (a.x * 10u64 + a.y): i32;\n" "};\n", 34, 1 }, { "global_arr", "package main;\n" "type pt = struct { x: u64, y: u64 };\n" "let g: [3]pt;\n" "export fn main() i32 = {\n" "\tg[1] = pt { x = 3u64, y = 4u64 };\n" "\treturn (g[1].x * 10u64 + g[1].y): i32;\n" "};\n", 34, 0 }, { "arrlit_idx_loud", "package main;\n" "export fn main() i32 = {\n" "\tlet a: [2][2]u64;\n" "\ta[1] = [3u64, 4u64];\n" "\treturn (a[1][0] * 10u64 + a[1][1]): i32;\n" "};\n", -1, 0 }, { "arrlit_deref_loud", "package main;\n" "export fn main() i32 = {\n" "\tlet a: [2]u64;\n" "\tlet p: *[2]u64 = &a;\n" "\t*p = [3u64, 4u64];\n" "\treturn (a[0] * 10u64 + a[1]): i32;\n" "};\n", -1, 0 }, }; static int run_driver(const char *driver, const struct row *r, int i) { char tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024]; snprintf(tmpdir, sizeof tmpdir, "/tmp/isls_%d_d_%d", getpid(), i); mkdir(tmpdir, 0755); snprintf(src, sizeof src, "%s/isls_%d_%d.ww", tmpdir, getpid(), i); snprintf(outbin, sizeof outbin, "%s/isls_%d_%d", tmpdir, getpid(), i); snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); FILE *f = fopen(src, "wb"); if (!f) { runwait(rmcmd); return -1; } fputs(r->src, f); fclose(f); snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null", driver, outbin, src); if (runwait(cmd) != 0) { /* want == -1 rows pin a LOUD build refusal (task #32). */ if (r->want != -1) fprintf(stderr, "row[%s]: build via %s failed\n", r->label, driver); runwait(rmcmd); return -1; } int got = runwait(outbin); runwait(rmcmd); return got; } /* asm_byte_identical — #103 sep layout: the driver flip moves build * artifacts from next-to-source .s to a .sepwork/ scratch * dir, so drive `ww build --sep -o ` twice — once with cstage's w6c * and once with the wwstage compiler (WW_W6C override; w6a/w6l stay * cstage — only the .s is compared) — then concat each build's * per-package .sepwork asm (sorted glob, identical package set: * callee_capture pulls strings, so this is multi-package) and diff. The * driver route — not a bare `w6c src.ww` — so importing rows resolve; * --sep is flip-invariant (green pre- and post-flip). WW_PKGCACHE pins * the package cache to the scratch dir so out/.pkgcache stays untouched. * Pre-fix the asm was byte-identically WRONG (both stages dropped the * fill), so these rows pin only that the converged fix stays symmetric; * the runtime rows above carry correctness. */ static int asm_byte_identical(const char *bin, const struct row *r, int i) { char src[96], tmpdir[64], outc[128], outw[128]; char cs[96], ws[96], cmd[1024]; snprintf(tmpdir, sizeof tmpdir, "/tmp/isls_asm_%d_d_%d", getpid(), i); snprintf(src, sizeof src, "%s/main809.ww", tmpdir); snprintf(outc, sizeof outc, "%s/main809c", tmpdir); snprintf(outw, sizeof outw, "%s/main809w", tmpdir); snprintf(cs, sizeof cs, "%s/all_cs.s", tmpdir); snprintf(ws, sizeof ws, "%s/all_ww.s", tmpdir); mkdir(tmpdir, 0755); FILE *f = fopen(src, "wb"); if (!f) { snprintf(cmd, sizeof cmd, "rm -rf %s", tmpdir); if (system(cmd)) {} return -1; } fputs(r->src, f); fclose(f); int rc = 0; snprintf(cmd, sizeof cmd, "cd %s && WW_PKGCACHE=%s/pkgc_c %s/ww build --sep -o %s %s 2>/dev/null", tmpdir, tmpdir, bin, outc, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: cstage sep build failed\n", r->label); rc = -1; } if (rc == 0) { snprintf(cmd, sizeof cmd, "cd %s && WW_PKGCACHE=%s/pkgc_w WW_W6C=%s/w6c_ww " "%s/ww build --sep -o %s %s 2>/dev/null", tmpdir, tmpdir, bin, bin, outw, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: wwstage sep build failed\n", r->label); rc = -1; } } if (rc == 0) { snprintf(cmd, sizeof cmd, "cat %s.sepwork/*.s > %s 2>/dev/null", outc, cs); if (system(cmd)) {} snprintf(cmd, sizeof cmd, "cat %s.sepwork/*.s > %s 2>/dev/null", outw, ws); if (system(cmd)) {} FILE *fc = fopen(cs, "rb"); FILE *fw = fopen(ws, "rb"); 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); } snprintf(cmd, sizeof cmd, "rm -rf %s", tmpdir); if (system(cmd)) {} 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, "idx_structlit_store: 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, "idx_structlit_store[%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++) { if (!rows[i].byteid) continue; total++; if (asm_byte_identical(bin, &rows[i], i) != 0) fail++; } } if (fail) { fprintf(stderr, "idx_structlit_store: %d/%d fixtures failed\n", fail, total); return 1; } printf("idx_structlit_store: %d/%d ok\n", total, total); return 0; }