/* * 949_chained_dot_struct_copy_run — #107-class sibling. A depth-≥2 * chained-DOT struct-field store from a struct IDENT source * (`t.m.l = s;`, lhs.lhs is itself an N_DOT) copies the source slot * word-by-word into the destination field. The cstage walker * (cgen.c, TY_STRUCT N_IDENT arm of the chained-DOT N_ASSIGN branch) * ran a MOVQ run then a SINGLE sized-tail word, but its tail selector * was `tail==4 ? MOVL : tail==1 ? MOVB : MOVQ` — every OTHER tail * width {2,3,5,6,7} fell to an 8-byte MOVQ. * * The copy length is the field's NATURAL (non-slot-padded) struct * size: a `@packed` or sub-8-aligned leaf packs at its own stride * (struct @packed {u8,u8,u8}=3, {u16,u32}=6, …). When that stride is * not a {1,4}+8k width, the over-MOVQ wrote 8 bytes into the * destination FIELD and clobbered the @packed neighbour that follows * it within 8 bytes — silently. Pre-fix `t.m.l = s` wrote s's slop * over t.m.z (read-back 0, not the planted value), AND cstage diverged * from wwstage, which already emitted a descending 8/4/2/1 sized tail * (cgenexpr.ww:10083) — a gate-blind cs≠ww divergence. The fix aligns * cstage UP to that ladder (mirrors cg_aggcopy and #107). * * GATE POLARITY: must stay GREEN. Two independent oracles per row: * - the runtime exit code = the value of the @packed NEIGHBOUR field * t.m.z, read back AFTER the `t.m.l = s` copy. A wrong copy width * over-writes it; byte-id is BLIND to a both-wrong pair, and the * pre-fix cstage drop was exit 0 (the planted value silently lost). * - w6c vs w6c_ww .s byte-identity (rule 10). * * Leaf size coverage = the tail widths the bug lived in {2,3,5,6,7} * plus boundaries {1,4,8} the old selector already handled (MOVB / MOVL * / full MOVQ run, no tail) — those lock that the new descending ladder * did NOT perturb the paths that were already correct, including the * new ladder's own k+1<=ssz MOVB arm reached when tail is 1. The * neighbour value is the oracle, computed in the row table (no derived * size literal). */ #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; }; static const struct row rows[] = { /* tail 2: Leaf = struct{u16,[8]u8} = 10B, over-MOVQ at offset 8 * (tail 2) clobbers the @packed neighbour z at offset 10. */ { "tail2_leaf10", "package main;\n" "type Leaf = struct { a: u16, b: [8]u8 };\n" "type Mid = struct @packed { l: Leaf, z: u8 };\n" "type Top = struct { m: Mid, tail: u64 };\n" "fn main() i32 = {\n" " let s = Leaf { a = 0u16, b = [9u8,0u8,0u8,0u8,0u8,0u8,0u8,0u8] };\n" " let t = Top { m = Mid { l = Leaf { a=0u16, b=[0u8,0u8,0u8,0u8,0u8,0u8,0u8,0u8] }, z = 222u8 }, tail = 0u64 };\n" " t.m.l = s;\n" " return t.m.z: i32;\n" "};\n", 222 }, /* tail 3: Leaf = @packed{u8,u8,u8} = 3B, whole copy is a 3-byte * stride; over-MOVQ at offset 0 wrote 8 bytes over z at offset 3. */ { "tail3_leaf3", "package main;\n" "type Leaf = struct @packed { a: u8, b: u8, c: u8 };\n" "type Mid = struct @packed { l: Leaf, z: u8 };\n" "type Top = struct { m: Mid, tail: u64 };\n" "fn main() i32 = {\n" " let s = Leaf { a = 1u8, b = 2u8, c = 3u8 };\n" " let t = Top { m = Mid { l = Leaf { a=0u8, b=0u8, c=0u8 }, z = 88u8 }, tail = 0u64 };\n" " t.m.l = s;\n" " return t.m.z: i32;\n" "};\n", 88 }, /* tail 4: Leaf = @packed{u32} = 4B — MOVL (already correct * pre-fix); locks the new ladder leaves this path identical. */ { "tail4_leaf4", "package main;\n" "type Leaf = struct @packed { a: u32 };\n" "type Mid = struct @packed { l: Leaf, z: u8 };\n" "type Top = struct { m: Mid, tail: u64 };\n" "fn main() i32 = {\n" " let s = Leaf { a = 7u32 };\n" " let t = Top { m = Mid { l = Leaf { a=0u32 }, z = 123u8 }, tail = 0u64 };\n" " t.m.l = s;\n" " return t.m.z: i32;\n" "};\n", 123 }, /* tail 5: Leaf = @packed{u8,u32} = 5B, over-MOVQ at offset 0 wrote * 8 bytes over z at offset 5. */ { "tail5_leaf5", "package main;\n" "type Leaf = struct @packed { a: u8, b: u32 };\n" "type Mid = struct @packed { l: Leaf, z: u8 };\n" "type Top = struct { m: Mid, tail: u64 };\n" "fn main() i32 = {\n" " let s = Leaf { a = 1u8, b = 0u32 };\n" " let t = Top { m = Mid { l = Leaf { a=0u8, b=0u32 }, z = 177u8 }, tail = 0u64 };\n" " t.m.l = s;\n" " return t.m.z: i32;\n" "};\n", 177 }, /* tail 6: Leaf = @packed{u16,u32} = 6B. */ { "tail6_leaf6", "package main;\n" "type Leaf = struct @packed { a: u16, b: u32 };\n" "type Mid = struct @packed { l: Leaf, z: u8 };\n" "type Top = struct { m: Mid, tail: u64 };\n" "fn main() i32 = {\n" " let s = Leaf { a = 1u16, b = 0u32 };\n" " let t = Top { m = Mid { l = Leaf { a=0u16, b=0u32 }, z = 99u8 }, tail = 0u64 };\n" " t.m.l = s;\n" " return t.m.z: i32;\n" "};\n", 99 }, /* tail 7: Leaf = @packed{u8,u16,u32} = 7B. */ { "tail7_leaf7", "package main;\n" "type Leaf = struct @packed { a: u8, b: u16, c: u32 };\n" "type Mid = struct @packed { l: Leaf, z: u8 };\n" "type Top = struct { m: Mid, tail: u64 };\n" "fn main() i32 = {\n" " let s = Leaf { a = 1u8, b = 2u16, c = 0u32 };\n" " let t = Top { m = Mid { l = Leaf { a=0u8, b=0u16, c=0u32 }, z = 111u8 }, tail = 0u64 };\n" " t.m.l = s;\n" " return t.m.z: i32;\n" "};\n", 111 }, /* via_cx tail 2: GLOBAL destination Top. A global (or ptr-root) * chain stores CX-relative ((CX)/8(CX)) rather than BP-relative; * the same 4/2/1 ladder feeds the other dest mode, so this row * locks the via_cx arm a BP-local `t` never reaches. */ { "viacx_global_tail2", "package main;\n" "type Leaf = struct { a: u16, b: [8]u8 };\n" "type Mid = struct @packed { l: Leaf, z: u8 };\n" "type Top = struct { m: Mid, tail: u64 };\n" "let g: Top = Top { m = Mid { l = Leaf { a=0u16, b=[0u8,0u8,0u8,0u8,0u8,0u8,0u8,0u8] }, z = 222u8 }, tail = 0u64 };\n" "fn main() i32 = {\n" " let s = Leaf { a = 0u16, b = [9u8,0u8,0u8,0u8,0u8,0u8,0u8,0u8] };\n" " g.m.l = s;\n" " return g.m.z: i32;\n" "};\n", 222 }, /* tail 1: Leaf = @packed{u8} = 1B — single MOVB, no MOVQ run; * always correct (old MOVB == new ladder's k+1<=ssz MOVB), so this * row locks the new descending ladder's 1-byte arm against a future * regression. */ { "tail1_leaf1", "package main;\n" "type Leaf = struct @packed { a: u8 };\n" "type Mid = struct @packed { l: Leaf, z: u8 };\n" "type Top = struct { m: Mid, tail: u64 };\n" "fn main() i32 = {\n" " let s = Leaf { a = 5u8 };\n" " let t = Top { m = Mid { l = Leaf { a=0u8 }, z = 144u8 }, tail = 0u64 };\n" " t.m.l = s;\n" " return t.m.z: i32;\n" "};\n", 144 }, /* tail 0: Leaf = struct{i64,i64} = 16B — pure MOVQ run, no tail; * locks the run path is undisturbed. Natural (non-packed) Mid so z * is 8-aligned at offset 16 anyway. */ { "tail0_leaf16", "package main;\n" "type Leaf = struct { a: i64, b: i64 };\n" "type Mid = struct { l: Leaf, z: u8 };\n" "type Top = struct { m: Mid, tail: u64 };\n" "fn main() i32 = {\n" " let s = Leaf { a = 5i64, b = 6i64 };\n" " let t = Top { m = Mid { l = Leaf { a=0i64, b=0i64 }, z = 200u8 }, tail = 0u64 };\n" " t.m.l = s;\n" " return t.m.z: i32;\n" "};\n", 200 }, }; static int run_driver(const char *driver, const struct row *r, int i) { char src[64], tmpdir[64], cmd[1024]; snprintf(src, sizeof src, "/tmp/chdotcp_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/chdotcp_%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 && timeout 180 %s build %s 2>/dev/null", tmpdir, driver, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: build via %s failed\n", r->label, driver); unlink(src); rmdir(tmpdir); return -2; } 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 = runwait(outbin); unlink(src); unlink(outbin); rmdir(tmpdir); return got; } /* w6c (cstage) vs w6c_ww (wwstage) .s byte-id (rule 10). */ 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/chdotcp_asm_%d_%d.ww", getpid(), i); snprintf(cs, sizeof cs, "/tmp/chdotcp_asm_%d_%d_c.s", getpid(), i); snprintf(ws, sizeof ws, "/tmp/chdotcp_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, "timeout 180 %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, "timeout 180 %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], wdrv[1024]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); struct { const char *name; const char *path; 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].path, X_OK) != 0) { fprintf(stderr, "chained_dot_struct_copy: 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, "chained_dot_struct_copy[%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, "chained_dot_struct_copy: %d/%d fixtures failed\n", fail, total); return 1; } printf("chained_dot_struct_copy: %d/%d ok\n", total, total); return 0; }