/* * 812_agg_assign_width — cstage and wwstage agree, byte-for-byte and * at runtime, that an aggregate ASSIGN copies the WHOLE value (task * #49, Family A of ken's silent-set triage, /tmp/ken_silent_triage.md). * * The bug: plain whole-aggregate reassignment `b = a` fell to the * N_ASSIGN scalar tail in BOTH stages and copied ONE MOVQ — word 0 of * a 16/24/48B struct (ken f49_min cs.s asm proof; fA_16b shows even a * 16B {size,size} fails). Same class at three more positions: a * struct-lit FIELD initialised from an ident source (`outer{.., r=r}`, * ken f38b / x5f-h) word0-copied inside cg_structlit_fill; a deref * place `*p = s` truncated to 8 bytes (#31-A); a module-let global * `g = a` / `g = pt{...}` stored one word (scalar `MOVQ AX, g(SB)`). * Tagged/match context was INCIDENTAL — the hole is tagged-independent * and gate-blind (byte-identical wrongness), latent only because lib * style is let-init (`let b = a` runs the #265/#268 full-width copy — * the working sibling these fixes mirror). * * The fix (BOTH stages, converged byte-identical): ONE place-resolved * mem-to-mem copy funnel — cg_aggcopy/aggcopy, extracted verbatim from * the C1.25 assign-resolver tail — fed by aggarg_srcaddr (source addr → * SI) and a LEAQ of the destination (BP slot or g(SB) symbol → BX), * wired at: the N_ASSIGN ident-aggregate arm (local + global), the * deref-place divert into the existing resolver aggregate arm, and the * structlit-fill aggregate-field arm. Non-addressable aggregate rhs * shapes die LOUD (rule 7) instead of silently truncating. * * MUTATION: every row below fails at master 7545bf7 (probed 2026-06-05, * ken's matrix f49_min/f49b/f49c/fA_16b/f38b + impl-A probes pA_*: all * non-zero exits, byte-id both stages — the class is gate-blind, so * runtime readback is the only oracle). * * row | shape | want * ---------------------+-------------------------------------+------ * assign_16b | {size,size} b = a, x*10+y readback | 39 * assign_24b_plain | {size,size,size} b = a, sum | 6 * assign_48b | 6-field b = a, sum | 21 * assign_odd_12b | {u32,u32,u32} 12B maxalign-4 (gA2) | 0 * assign_match_bind | dst = s from a match binding over | 0 * | (st|void), st has a tagged member | * | (ken f49_min — the ORIGINAL #49 | * | context, regression pin) | * lit_field_from_ident | outer{tag, r = r} field fill (f38b) | 21 * assign_str_field | {id, name:str} b = a, ladder | 0 * assign_nested | {tag, inner{x,y}} b = a, sum | 6 * arrelem_roundtrip | a[1] = b (kin, pre-wired #270-1b) | 15 * | then cc = a[1] (the new read side) | * assign_deref | *p = s, 24B struct (#31-A fold) w/ | 27 * | guards either side of the pointee | * assign_global | g = a then b = g (module-let dst | 36 * | and src, both new arms) | * assign_dot_source | b = o.i (N_DOT source via | 6 * | aggarg_srcaddr) | * assign_tuple | (size,size) u = t slot copy | 13 * global_structlit | g = pt{...} (DST_GLOBAL fill wire) | 18 * alias_named_assign | type row = st; b = a (ken R1/gA3b — | 0 * | full alias chase, field-wise init) | * alias_twolevel_assign| ali -> base -> struct chain (gA6) | 0 * neighbor_guard | b = a with live guards either side | 0 * | of b — over-copy smashes them | */ #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[] = { { "assign_16b", "package main;\n" "type p2 = struct { x: size, y: size };\n" "export fn main() i32 = {\n" "\tlet a: p2 = p2 { x = 3: size, y = 9: size };\n" "\tlet b: p2 = p2 { x = 0: size, y = 0: size };\n" "\tb = a;\n" "\treturn (b.x * 10 + b.y): i32;\n" "};\n", 39 }, { "assign_24b_plain", "package main;\n" "type st = struct { id: size, x: size, y: size };\n" "export fn main() i32 = {\n" "\tlet a: st = st { id = 1: size, x = 2: size, y = 3: size };\n" "\tlet b: st = st { id = 0: size, x = 0: size, y = 0: size };\n" "\tb = a;\n" "\treturn (b.id + b.x + b.y): i32;\n" "};\n", 6 }, { "assign_48b", "package main;\n" "type big = struct { a: size, b: size, c: size, d: size, e: size, f: size };\n" "export fn main() i32 = {\n" "\tlet x: big = big { a = 1: size, b = 2: size, c = 3: size, d = 4: size, e = 5: size, f = 6: size };\n" "\tlet y: big = big { a = 0: size, b = 0: size, c = 0: size, d = 0: size, e = 0: size, f = 0: size };\n" "\ty = x;\n" "\treturn (y.a + y.b + y.c + y.d + y.e + y.f): i32;\n" "};\n", 21 }, /* ken gA2 — odd size: 12B {u32,u32,u32}, maxalign 4. Pins the * MOVQ-run + MOVL tail of the funnel (the fi.fsz slot-padded * skew worry) — NOT a multiple of 8. Pre-fix at master: word0 * copies a+b in one MOVQ, c lost → exit 3 (probed both stages). */ { "assign_odd_12b", "package main;\n" "type tri = struct { a: u32, b: u32, c: u32 };\n" "export fn main() i32 = {\n" "\tlet x: tri = tri { a = 7: u32, b = 8: u32, c = 9: u32 };\n" "\tlet y: tri = tri { a = 0: u32, b = 0: u32, c = 0: u32 };\n" "\ty = x;\n" "\tif (y.a != 7) { return 1; };\n" "\tif (y.b != 8) { return 2; };\n" "\tif (y.c != 9) { return 3; };\n" "\treturn 0;\n" "};\n", 0 }, /* ken f49_min verbatim — the ORIGINAL #49 filing context: * assign from a match binding, struct with a tagged member. * Pre-fix exit 4 (dst.m's tag word never copied). */ { "assign_match_bind", "package main;\n" "type st = struct { id: size, m: (void | size) };\n" "type un = (st | void);\n" "export fn main() i32 = {\n" "\tlet src: un = st { id = 6: size, m = 8: size };\n" "\tlet dst: st = st { id = 0: size, m = void };\n" "\tmatch (src) {\n" "\tcase let s: st => {\n" "\t\tif (!(s.m is size)) { return 1; };\n" "\t\tdst = s;\n" "\t};\n" "\tcase void => { return 2; };\n" "\t};\n" "\tif (dst.id != 6) { return 3; };\n" "\tif (!(dst.m is size)) { return 4; };\n" "\tif (dst.m as size != 8) { return 5; };\n" "\treturn 0;\n" "};\n", 0 }, /* ken f38b shape — struct-lit FIELD from an ident source. Pre-fix * exit: o.r.id copied (word0), x/y zero → 6. */ { "lit_field_from_ident", "package main;\n" "type rep = struct { id: size, x: size, y: size };\n" "type outer = struct { tag: size, r: rep };\n" "export fn main() i32 = {\n" "\tlet r: rep = rep { id = 6: size, x = 7: size, y = 8: size };\n" "\tlet o: outer = outer { tag = 4: size, r = r };\n" "\tif (o.tag != 4) { return 99; };\n" "\treturn (o.r.id + o.r.x + o.r.y): i32;\n" "};\n", 21 }, /* 3-word str header inside the copied struct: the header words * past .ptr are exactly what a word0 copy drops. Ladder form — * `.len` in mixed arithmetic trips the PRE-EXISTING task-#26 * typing divergence (cstage rejects), which is not this class. */ { "assign_str_field", "package main;\n" "type named = struct { id: size, name: str };\n" "export fn main() i32 = {\n" "\tlet a: named = named { id = 7: size, name = \"hello\" };\n" "\tlet b: named = named { id = 0: size, name = \"\" };\n" "\tb = a;\n" "\tif (b.id != 7) { return 1; };\n" "\tif (b.name.len != 5) { return 2; };\n" "\treturn 0;\n" "};\n", 0 }, { "assign_nested", "package main;\n" "type inner = struct { x: size, y: size };\n" "type outer = struct { tag: size, i: inner };\n" "export fn main() i32 = {\n" "\tlet a: outer = outer { tag = 1: size, i = inner { x = 2: size, y = 3: size } };\n" "\tlet b: outer = outer { tag = 0: size, i = inner { x = 0: size, y = 0: size } };\n" "\tb = a;\n" "\treturn (b.tag + b.i.x + b.i.y): i32;\n" "};\n", 6 }, /* a[i] = b was already wired (#270-1b INDEX-place word-copy) — * this row pins the KIN pair: the pre-existing store plus the * NEW ident-from-index read (`cc = a[1]` word0-copied pre-fix). */ { "arrelem_roundtrip", "package main;\n" "type pt = struct { x: size, y: size, z: size };\n" "export fn main() i32 = {\n" "\tlet a: [2]pt = [pt { x = 0: size, y = 0: size, z = 0: size }, pt { x = 0: size, y = 0: size, z = 0: size }];\n" "\tlet b: pt = pt { x = 4: size, y = 5: size, z = 6: size };\n" "\ta[1] = b;\n" "\tif (a[0].x != 0) { return 99; };\n" "\tlet cc: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n" "\tcc = a[1];\n" "\treturn (cc.x + cc.y + cc.z): i32;\n" "};\n", 15 }, /* #31-A fold: deref place from an addressable aggregate ident — * pre-fix stored 8 bytes (probe pA_deref exit 2). Guards either * side of the pointee slot per ken gA4: an over-wide copy through * the resolved place smashes one. */ { "assign_deref", "package main;\n" "type pt = struct { x: size, y: size, z: size };\n" "export fn main() i32 = {\n" "\tlet guard1: size = 111: size;\n" "\tlet d: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n" "\tlet guard2: size = 222: size;\n" "\tlet s: pt = pt { x = 8: size, y = 9: size, z = 10: size };\n" "\tlet p: *pt = &d;\n" "\t*p = s;\n" "\tif (guard1 != 111) { return 98; };\n" "\tif (guard2 != 222) { return 97; };\n" "\treturn (d.x + d.y + d.z): i32;\n" "};\n", 27 }, /* Module-let global as BOTH destination (`g = a`, LEAQ g(SB) * dest) and source (`b = g`, aggarg_srcaddr global arm). */ { "assign_global", "package main;\n" "type pt = struct { x: size, y: size, z: size };\n" "let g: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n" "export fn main() i32 = {\n" "\tlet a: pt = pt { x = 11: size, y = 12: size, z = 13: size };\n" "\tg = a;\n" "\tif (g.z != 13) { return 99; };\n" "\tlet b: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n" "\tb = g;\n" "\treturn (b.x + b.y + b.z): i32;\n" "};\n", 36 }, { "assign_dot_source", "package main;\n" "type inner = struct { x: size, y: size, z: size };\n" "type outer = struct { tag: size, i: inner };\n" "export fn main() i32 = {\n" "\tlet o: outer = outer { tag = 1: size, i = inner { x = 2: size, y = 3: size, z = 4: size } };\n" "\tlet b: inner = inner { x = 0: size, y = 0: size, z = 0: size };\n" "\tb = o.i;\n" "\treturn (b.x + b.z): i32;\n" "};\n", 6 }, /* Tuple ident reassign rides the same funnel (8B/elem slots). */ { "assign_tuple", "package main;\n" "export fn main() i32 = {\n" "\tlet t: (size, size) = (4: size, 9: size);\n" "\tlet u: (size, size) = (0: size, 0: size);\n" "\tu = t;\n" "\treturn (u.0 + u.1): i32;\n" "};\n", 13 }, /* Global structlit reassign — newly wired through the DST_GLOBAL * fill (pre-fix: one scalar MOVQ of AX≈0 to g(SB)). */ { "global_structlit", "package main;\n" "type pt = struct { x: size, y: size, z: size };\n" "let g: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n" "export fn main() i32 = {\n" "\tg = pt { x = 5: size, y = 6: size, z = 7: size };\n" "\treturn (g.x + g.y + g.z): i32;\n" "};\n", 18 }, /* ken R1 (gA3b): ALIAS-named aggregate — the funnel dispatch must * FULL-chase the alias (type_chase_named / chased tinfo); a * single peel left cstage at TY_NAMED, fell to the scalar tail * and word0-copied while wwstage copied full-width (live cs≠ww). * FIELD-WISE init: the struct-LIT spelling can't pin the ww side * (it louds earlier at the pre-existing task-#7 aggregate-let * bound — the #5 alias-arc's hole, not this funnel's). */ { "alias_named_assign", "package main;\n" "type st = struct { a: size, b: size };\n" "type row = st;\n" "export fn main() i32 = {\n" "\tlet a: row;\n" "\ta.a = 4; a.b = 9;\n" "\tlet b: row;\n" "\tb.a = 0; b.b = 0;\n" "\tb = a;\n" "\tif (b.a != 4) { return 1; };\n" "\tif (b.b != 9) { return 2; };\n" "\treturn 0;\n" "};\n", 0 }, /* ken gA6: TWO-level alias chain (`ali -> base -> struct`) — pins * that the dispatch is a full CHASE, not a deeper fixed peel. */ { "alias_twolevel_assign", "package main;\n" "type ali = base;\n" "type base = struct { a: size, b: size, c: size };\n" "export fn main() i32 = {\n" "\tlet a: ali;\n" "\ta.a = 4; a.b = 9; a.c = 13;\n" "\tlet b: ali;\n" "\tb.a = 0; b.b = 0; b.c = 0;\n" "\tb = a;\n" "\tif (b.a != 4) { return 1; };\n" "\tif (b.b != 9) { return 2; };\n" "\tif (b.c != 13) { return 3; };\n" "\treturn 0;\n" "};\n", 0 }, /* Neighbor guards on both sides of the destination slot: a copy * WIDER than size(T) (the inverse regression) smashes one. */ { "neighbor_guard", "package main;\n" "type pt = struct { x: size, y: size, z: size };\n" "export fn main() i32 = {\n" "\tlet guard1: size = 111: size;\n" "\tlet b: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n" "\tlet guard2: size = 222: size;\n" "\tlet a: pt = pt { x = 1: size, y = 2: size, z = 3: size };\n" "\tb = a;\n" "\tif (guard1 != 111) { return 1; };\n" "\tif (guard2 != 222) { return 2; };\n" "\tif (b.x + b.y + b.z != 6) { return 3; };\n" "\treturn 0;\n" "};\n", 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/aggas_%d_d_%d", getpid(), i); mkdir(tmpdir, 0755); snprintf(src, sizeof src, "%s/aggas_%d_%d.ww", tmpdir, getpid(), i); snprintf(outbin, sizeof outbin, "%s/aggas_%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) { 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 — generate .s via cstage's w6c and wwstage's * w6c_ww and diff. The class is gate-blind (both stages were wrong * identically), so byte-id here pins that the CONVERGED fix stays * converged. */ 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/aggas_asm_%d_%d.ww", getpid(), i); snprintf(cs, sizeof cs, "/tmp/aggas_asm_%d_%d_c.s", getpid(), i); snprintf(ws, sizeof ws, "/tmp/aggas_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, "agg_assign_width: 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, "agg_assign_width[%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, "agg_assign_width: %d/%d fixtures failed\n", fail, total); return 1; } printf("agg_assign_width: %d/%d ok\n", total, total); return 0; }