/* * 989_structcopytail_run — #73 teeth (recorded by impl-55, 2026-06-13). * * Whole-struct field-copy ragged tail. A `x.i = o` copy of a struct whose * NATURAL size is not 8-aligned must move exactly that many bytes via a * descending-greedy 8->4->2->1 tail (MOVL/MOVW/MOVB), NOT round up to an * 8-byte MOVQ that bleeds into the field's natural-offset successor. * * THIS TEST IS cs!=ww-RED UNTIL #73. As impl-55 proved (2026-06-13), BOTH * stages inline a tail handling only {4,1}; a natural size %8 in * {2,3,5,6,7} falls through to an 8-byte MOVQ over-write: * - wwstage: cgenexpr.ww 4 field-copy sites (now length-correct via #71, * tail still 4/1). * - cstage: cmd/w6c/cgen.c:5302/5318 (+ siblings 2970/3332/5268/6255/ * 6314/7259) — str_fu->size length is already natural, tail is 4/1. * ASM PROOF (tail2, x.i = o): cstage emits `MOVQ -8(BP),AX; MOVQ AX,-16(BP)` * (8B over-write -> clobbers mark@-12). #73 routes both stages' field copy * through the canonical greedy emitter (cstage cg_aggcopy@2241 / ww * aggcopy@1662), completing the tail on both -> this test goes green * cs==ww. Do NOT wire it before #73 lands. * * Shapes: * tail2: inner2{a:u8,b:u8} (size 2) in outer2{i:inner2, mark:i32}; #44 * packs mark at natural offset 4. A MOVQ copy of i writes [0,8) * and clobbers mark@4; the MOVW tail writes [0,2). * tail6: inner6{a:u16,b:u16,c:u16} (size 6) in outer6{i:inner6, mark:u8}; * mark at natural offset 6. A MOVQ copy writes [0,8) and clobbers * mark@6; the MOVL+MOVW tail writes [0,6). * ctl8: inner8{a:i64} (size 8, 8-aligned) — the MOVQ loop alone, no * tail; proves the 8-aligned path is unchanged. * Each program reads every field back and returns the 1-based index of the * first mismatch, 0 on all-correct. Both stages build+run (rule-10), pin 0. */ #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; /* >= 0: pin the absolute value; -1: cs==ww only */ }; static const struct row rows[] = { /* tail2 — whole-copy of a 2-byte struct; mark (nat offset 4) must * survive. Pre-tail-fix MOVQ writes [0,8) -> mark clobbered. */ { "tail2", "package main;\n" "type inner2 = struct { a: u8, b: u8 };\n" "type outer2 = struct { i: inner2, mark: i32 };\n" "export fn main() int = {\n" " let o: inner2 = inner2 { a = 0x11, b = 0x22 };\n" " let x: outer2;\n" " x.mark = 0x7f7f7f7f;\n" " x.i = o;\n" " if (x.i.a: int != 0x11) { return 1; };\n" " if (x.i.b: int != 0x22) { return 2; };\n" " if (x.mark != 0x7f7f7f7f) { return 3; };\n" " return 0;\n" "};\n", 0 }, /* tail6 — whole-copy of a 6-byte struct; mark:u8 (nat offset 6) must * survive. Pre-tail-fix MOVQ writes [0,8) -> mark clobbered; the * MOVL+MOVW tail writes exactly [0,6). */ { "tail6", "package main;\n" "type inner6 = struct { a: u16, b: u16, c: u16 };\n" "type outer6 = struct { i: inner6, mark: u8 };\n" "export fn main() int = {\n" " let o: inner6 = inner6 { a = 0x1111, b = 0x2222, c = 0x3333 };\n" " let x: outer6;\n" " x.mark = 0x5a;\n" " x.i = o;\n" " if (x.i.a: int != 0x1111) { return 1; };\n" " if (x.i.b: int != 0x2222) { return 2; };\n" " if (x.i.c: int != 0x3333) { return 3; };\n" " if (x.mark: int != 0x5a) { return 4; };\n" " return 0;\n" "};\n", 0 }, /* tail7 — whole-copy of a 7-byte struct (seven u8, align 1); mark:u8 * at natural offset 7 must survive. Exercises the FULL descending tail * ladder in one shape: MOVL[0,4) + MOVW[4,6) + MOVB[6,7). Pre-fix the * 4/1-only tail defaulted size-7 to a single 8-byte MOVQ writing [0,8) * -> clobbers mark@7; this is the only row hitting the MOVB tail. */ { "tail7", "package main;\n" "type inner7 = struct { a: u8, b: u8, c: u8, d: u8, e: u8, f: u8, g: u8 };\n" "type outer7 = struct { i: inner7, mark: u8 };\n" "export fn main() int = {\n" " let o: inner7 = inner7 { a = 0x11, b = 0x22, c = 0x33, d = 0x44, e = 0x55, f = 0x66, g = 0x77 };\n" " let x: outer7;\n" " x.mark = 0x5a;\n" " x.i = o;\n" " if (x.i.a: int != 0x11) { return 1; };\n" " if (x.i.b: int != 0x22) { return 2; };\n" " if (x.i.c: int != 0x33) { return 3; };\n" " if (x.i.d: int != 0x44) { return 4; };\n" " if (x.i.e: int != 0x55) { return 5; };\n" " if (x.i.f: int != 0x66) { return 6; };\n" " if (x.i.g: int != 0x77) { return 7; };\n" " if (x.mark: int != 0x5a) { return 8; };\n" " return 0;\n" "};\n", 0 }, /* ctl8 — 8-aligned struct: the MOVQ loop alone, zero tail iterations. * Proves the 8-aligned emission path stays correct (byte-id caveat). */ { "ctl8", "package main;\n" "type inner8 = struct { a: i64 };\n" "type outer8 = struct { i: inner8, mark: i64 };\n" "export fn main() int = {\n" " let o: inner8 = inner8 { a = 0x1234567 };\n" " let x: outer8;\n" " x.mark = 0x76543210;\n" " x.i = o;\n" " if (x.i.a != 0x1234567) { return 1; };\n" " if (x.mark != 0x76543210) { return 2; };\n" " return 0;\n" "};\n", 0 }, }; /* 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/sctail_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/sctail_%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); int have_ww = (access(wdrv, X_OK) == 0); int n = (int)(sizeof rows / sizeof rows[0]); int total = 0, fail = 0; for (int i = 0; i < n; i++) { total++; int gc = run_build(cdrv, &rows[i], i); if (gc < 0) { fprintf(stderr, "structcopytail_run[cstage][%s]: build/run " "failed (got %d)\n", rows[i].label, gc); fail++; continue; } if (rows[i].want_exit >= 0 && gc != rows[i].want_exit) { fprintf(stderr, "structcopytail_run[cstage][%s]: exit=%d " "want=%d (field/clobber mismatch)\n", rows[i].label, gc, rows[i].want_exit); fail++; } if (!have_ww) { fprintf(stderr, "structcopytail_run: skip wwstage (no %s)\n", wdrv); continue; } int gw = run_build(wdrv, &rows[i], i); if (gw != gc) { fprintf(stderr, "structcopytail_run[%s]: cs=%d != ww=%d " "(copy-tail divergence — #71)\n", rows[i].label, gc, gw); fail++; } if (rows[i].want_exit >= 0 && gw != rows[i].want_exit) { fprintf(stderr, "structcopytail_run[wwstage][%s]: exit=%d " "want=%d (field/clobber mismatch)\n", rows[i].label, gw, rows[i].want_exit); fail++; } } if (fail) { fprintf(stderr, "structcopytail_run: %d/%d checks failed\n", fail, total); return 1; } printf("structcopytail_run: %d/%d ok\n", total, total); return 0; }