/* * 650_dot_chain — chained dotted access through value-struct fields * must compile to a single load/store at (base + sum-of-offsets), * not silently lower to an undefined global symbol or shuffle stale * registers. Drew filed this from worker-bufio when the Hare-shaped * `scanner` embed in lib/bufio tripped the cgen. * * Each fixture exercises a different chain shape. The repro from * drew's bug report is row[0]; the rest pin: 3-deep chains, slice * pseudo-field tails (`s.buf.len`), global-rooted chains, and * mixed leaf widths (u8 / i32 / u32). `&o.i.a` address-of and i8 * sign-extend leaves are deferred (tasks #9 / #10). * * Exercises both stages via `ww` (cstage) and `ww_ww` (wwstage) * when present, so a regression on either side is caught here. */ #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[] = { /* Drew's repro: 2-deep value-struct read AND write. Returns * o.i.a (=10) + o.x (=5) = 15. */ { "drew_2deep_read_write", "type inner = struct { a: i32, b: i32, c: i32 };\n" "type outer = struct { i: inner, x: i32 };\n" "fn main() i32 = {\n" " let o: outer; o.i.a = 10; o.x = 5;\n" " return o.i.a + o.x;\n" "};\n", 15 }, /* 3-deep value-struct chain. Proves the spine walker is loop- * shaped, not hardcoded to depth 2. */ { "value_struct_3deep", "type a3 = struct { a: i32 };\n" "type a2 = struct { a: a3 };\n" "type a1 = struct { a: a2 };\n" "fn main() i32 = {\n" " let v: a1;\n" " v.a.a.a = 7;\n" " return v.a.a.a;\n" "};\n", 7 }, /* Slice pseudo-field tail through a value-struct field: write * .ptr/.len/.cap on s.buf where buf: []u8 is a struct field. * Read back .len through the same chain. */ { "slice_field_pseudo", "type bag = struct { buf: []u8, x: i32 };\n" "fn main() i32 = {\n" " let arr: [8]u8;\n" " let i: i32 = 0;\n" " for (i < 8) { arr[i] = i: u8; i = i + 1; };\n" " let b: bag;\n" " b.buf.ptr = &arr[0];\n" " b.buf.len = 5;\n" " b.buf.cap = 8;\n" " b.x = 0;\n" " return b.buf.len;\n" "};\n", 5 }, /* u8 leaf through a chained struct: round-trips a single byte * unchanged (no narrow cast in play — direct MOVB write + * MOVZBQ read). Pins the leaf-width dispatch in the walker. */ { "u8_leaf_through_chain", "type holder = struct { val: u8, pad: u8 };\n" "type box = struct { h: holder, tag: i32 };\n" "fn main() i32 = {\n" " let b: box;\n" " b.h.val = 99u8;\n" " b.h.pad = 0u8;\n" " b.tag = 0;\n" " return b.h.val: i32;\n" "};\n", 99 }, /* i32 leaf through a chained struct, value > 255 to confirm the * MOVL store width (not silently narrowed to MOVB). Returns 42 * iff the full 4-byte value round-trips. */ { "i32_leaf_through_chain", "type holder = struct { val: i32, pad: i32 };\n" "type box = struct { h: holder, tag: i32 };\n" "fn main() i32 = {\n" " let b: box;\n" " b.h.val = 0x12345;\n" " b.h.pad = 0;\n" " b.tag = 0;\n" " if (b.h.val == 0x12345) { return 42; };\n" " return 0;\n" "};\n", 42 }, /* u32 leaf through a chained struct. Distinct write + read at * the chained leaf, value chosen so the MOVL store + load * round-trip preserves all four bytes. */ { "u32_leaf_through_chain", "type holder = struct { val: u32, pad: u32 };\n" "type box = struct { h: holder, tag: i32 };\n" "fn main() i32 = {\n" " let b: box;\n" " b.h.val = 4321u32;\n" " b.h.pad = 0u32;\n" " b.tag = 0;\n" " if (b.h.val == 4321u32) { return 42; };\n" " return 0;\n" "};\n", 42 }, /* Global-rooted chain: write+read through a top-level `let g`. * Pins the LEAQ name(SB), CX → MOVQ disp(CX) path on both * stages (the local-rooted rows above pin the BP-relative * path). 11 + 3 = 14. */ { "global_root_chain", "type inner = struct { a: i32, b: i32 };\n" "type outer = struct { i: inner, x: i32 };\n" "let g: outer;\n" "fn main() i32 = {\n" " g.i.a = 11; g.x = 3;\n" " return g.i.a + g.x;\n" "};\n", 14 }, }; 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/wdc_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/wdc_%d_d_%d", getpid(), i); FILE *f = fopen(src, "wb"); if (!f) return -1; fputs(r->src, f); fclose(f); mkdir(tmpdir, 0755); snprintf(cmd, sizeof cmd, "cd %s && %s build %s", tmpdir, driver, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: build via %s failed\n", r->label, driver); unlink(src); rmdir(tmpdir); return -1; } 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; } 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, "dot_chain: 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, "dot_chain[%s][%s]: exit=%d want=%d\n", drivers[d].name, rows[i].label, got, rows[i].want); fail++; } } } if (fail) { fprintf(stderr, "dot_chain: %d/%d fixtures failed\n", fail, total); return 1; } printf("dot_chain: %d/%d ok\n", total, total); return 0; }