/* * 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 "wwtestpkg.h" 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 }, /* `*T` parameter, single-dot read: `fn f(o: *outer) i32 = o.f`. * Already worked via the existing pointer-to-struct-field * branch; pinned here so future refactors don't regress it. */ { "ptr_root_single_read", "type outer = struct { f: i32, g: i32 };\n" "fn get(o: *outer) i32 = { return o.f; };\n" "fn main() i32 = {\n" " let x: outer; x.f = 42; x.g = 0;\n" " return get(&x);\n" "};\n", 42 }, /* `*T` parameter, single-dot write: `fn f(o: *outer) void = …`. * Already worked via the via_ptr branch in N_ASSIGN; pinned * for the same reason. */ { "ptr_root_single_write", "type outer = struct { f: i32, g: i32 };\n" "fn set(o: *outer) void = { o.f = 42; };\n" "fn main() i32 = {\n" " let x: outer; x.f = 0; x.g = 0;\n" " set(&x);\n" " return x.f;\n" "};\n", 42 }, /* Drew's report shape — `*T` parameter, 2-deep read. * Used to lower to `MOVQ a(SB), AX` (link-time undefined ref). * Now the spine walker dereferences the root pointer. */ { "ptr_root_2deep_read", "type inner = struct { a: i32, b: i32 };\n" "type outer = struct { i: inner, x: i32 };\n" "fn get(o: *outer) i32 = { return o.i.a; };\n" "fn main() i32 = {\n" " let v: outer; v.i.a = 7; v.i.b = 0; v.x = 0;\n" " return get(&v);\n" "};\n", 7 }, /* `*T` parameter, 2-deep write — bufio.init shape. * Previously silently dropped; now MOVQ off(BP), CX + * store at total_off(CX). */ { "ptr_root_2deep_write", "type inner = struct { a: i32, b: i32 };\n" "type outer = struct { i: inner, x: i32 };\n" "fn set(o: *outer) void = { o.i.a = 5; };\n" "fn main() i32 = {\n" " let v: outer; v.i.a = 0; v.i.b = 0; v.x = 0;\n" " set(&v);\n" " return v.i.a;\n" "};\n", 5 }, /* 3-deep `*T`-rooted: proves the spine walker loops past the * pointer-root hop and isn't hardcoded at depth 2. */ { "ptr_root_3deep", "type a3 = struct { x: i32 };\n" "type a2 = struct { a: a3 };\n" "type a1 = struct { a: a2 };\n" "fn put(p: *a1) void = { p.a.a.x = 9; };\n" "fn get(p: *a1) i32 = { return p.a.a.x; };\n" "fn main() i32 = {\n" " let v: a1; v.a.a.x = 0;\n" " put(&v);\n" " return get(&v);\n" "};\n", 9 }, /* `*T` *local* (not just param) — same emit path through * localfind, but exercised separately for symmetry. */ { "ptr_root_local", "type inner = struct { a: i32, b: i32 };\n" "type outer = struct { i: inner, x: i32 };\n" "fn main() i32 = {\n" " let v: outer; v.i.a = 0; v.i.b = 0; v.x = 0;\n" " let p: *outer = &v;\n" " p.i.a = 13;\n" " return p.i.a;\n" "};\n", 13 }, /* str leaf field through a `*T`-rooted chain — exercises the * (AX=ptr, BX=len) convention through the CX-base load. */ { "ptr_root_str_leaf", "type holder = struct { s: str, pad: i32 };\n" "type box = struct { h: holder, tag: i32 };\n" "fn put(b: *box) void = { b.h.s = \"abc\"; };\n" "fn main() i32 = {\n" " let b: box;\n" " let empty: str; b.h.s = empty; b.h.pad = 0; b.tag = 0;\n" " put(&b);\n" " if (b.h.s.len == 3) { return 42; };\n" " return 0;\n" "};\n", 42 }, /* slice pseudo-field on a `*T`-rooted chain. .len of an empty * slice round-trips through the CX-base load. */ { "ptr_root_slice_pseudo", "type bag = struct { buf: []u8, x: i32 };\n" "fn setlen(b: *bag, n: i32) void = { b.buf.len = n; };\n" "fn main() i32 = {\n" " let b: bag;\n" " let nope: []u8; b.buf = nope; b.x = 0;\n" " setlen(&b, 5);\n" " return b.buf.len: i32;\n" "};\n", 5 }, /* f64 leaf field through a `*T`-rooted chain — exercises the * X0 spill / MOVSD store path off the CX base. */ { "ptr_root_f64_leaf", "type holder = struct { v: f64, pad: i32 };\n" "type box = struct { h: holder, tag: i32 };\n" "fn put(b: *box) void = { b.h.v = 0.5f64; };\n" "fn main() i32 = {\n" " let b: box; b.h.v = 0.0f64; b.h.pad = 0; b.tag = 0;\n" " put(&b);\n" " if (b.h.v == 0.5f64) { return 42; };\n" " return 0;\n" "};\n", 42 }, /* Sub-word leaf widths through a `*T`-rooted chain — pins * fldloadop / fldstoreop on the CX-base path. u8/i8/u16/i16/i32 * all stored then read back through the inner pointer. */ { "ptr_root_subword_u8", "type holder = struct { v: u8, pad: u8 };\n" "type box = struct { h: holder, tag: i32 };\n" "fn put(b: *box) void = { b.h.v = 99u8; };\n" "fn main() i32 = {\n" " let b: box; b.h.v = 0u8; b.h.pad = 0u8; b.tag = 0;\n" " put(&b);\n" " return b.h.v: i32;\n" "};\n", 99 }, { "ptr_root_subword_i32", "type holder = struct { v: i32, pad: i32 };\n" "type box = struct { h: holder, tag: i32 };\n" "fn put(b: *box) void = { b.h.v = 0x12345; };\n" "fn main() i32 = {\n" " let b: box; b.h.v = 0; b.h.pad = 0; b.tag = 0;\n" " put(&b);\n" " if (b.h.v == 0x12345) { return 42; };\n" " return 0;\n" "};\n", 42 }, { "ptr_root_subword_i16", "type holder = struct { v: i16, pad: i16 };\n" "type box = struct { h: holder, tag: i32 };\n" "fn put(b: *box) void = { b.h.v = 4321i16; };\n" "fn main() i32 = {\n" " let b: box; b.h.v = 0i16; b.h.pad = 0i16; b.tag = 0;\n" " put(&b);\n" " if (b.h.v == 4321i16) { return 42; };\n" " return 0;\n" "};\n", 42 }, /* Negative signed leaf widths through a `*T`-rooted chain. * Stored value's sign bit must round-trip — pins localloadop / * fldloadop sign-extension on the CX-base path so `*T`-rooted * chains don't silently widen to a positive bit pattern. i8, i16 * and i32 each store −7 and check `< 0` after the chained read. */ { "ptr_root_subword_i8_neg", "type holder = struct { v: i8, pad: i8 };\n" "type box = struct { h: holder, tag: i32 };\n" "fn put(b: *box) void = { b.h.v = -7i8; };\n" "fn main() i32 = {\n" " let b: box; b.h.v = 0i8; b.h.pad = 0i8; b.tag = 0;\n" " put(&b);\n" " let r: i32 = b.h.v: i32;\n" " if (r == -7) { return 42; };\n" " return 0;\n" "};\n", 42 }, { "ptr_root_subword_i16_neg", "type holder = struct { v: i16, pad: i16 };\n" "type box = struct { h: holder, tag: i32 };\n" "fn put(b: *box) void = { b.h.v = -7i16; };\n" "fn main() i32 = {\n" " let b: box; b.h.v = 0i16; b.h.pad = 0i16; b.tag = 0;\n" " put(&b);\n" " let r: i32 = b.h.v: i32;\n" " if (r == -7) { return 42; };\n" " return 0;\n" "};\n", 42 }, { "ptr_root_subword_i32_neg", "type holder = struct { v: i32, pad: i32 };\n" "type box = struct { h: holder, tag: i32 };\n" "fn put(b: *box) void = { b.h.v = -7; };\n" "fn main() i32 = {\n" " let b: box; b.h.v = 0; b.h.pad = 0; b.tag = 0;\n" " put(&b);\n" " if (b.h.v < 0) { return 42; };\n" " return 0;\n" "};\n", 42 }, /* Round-trip across the aliasing seam: caller writes via the * local value, callee reads via the `*T`-rooted chain — and * vice versa. Pins that both branches index the SAME slot * (same field offsets resolved off the right base). */ { "ptr_root_roundtrip_local_write_ptr_read", "type inner = struct { a: i32, b: i32 };\n" "type outer = struct { i: inner, x: i32 };\n" "fn get(o: *outer) i32 = { return o.i.a; };\n" "fn main() i32 = {\n" " let o: outer; o.i.a = 0; o.i.b = 0; o.x = 0;\n" " let p: *outer = &o;\n" " o.i.a = 42;\n" " return get(p);\n" "};\n", 42 }, { "ptr_root_roundtrip_ptr_write_local_read", "type inner = struct { a: i32, b: i32 };\n" "type outer = struct { i: inner, x: i32 };\n" "fn main() i32 = {\n" " let o: outer; o.i.a = 0; o.i.b = 0; o.x = 0;\n" " let p: *outer = &o;\n" " p.i.a = 42;\n" " return o.i.a;\n" "};\n", 42 }, }; 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/wdc_%d_d_%d", getpid(), i); mkdir(tmpdir, 0755); snprintf(src, sizeof src, "%s/wdc_%d_%d.ww", tmpdir, getpid(), i); snprintf(outbin, sizeof outbin, "%s/wdc_%d_%d", tmpdir, getpid(), i); snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); FILE *f = fopen(src, "wb"); if (!f) { runwait(rmcmd); return -1; } wwtest_fputs(r->src, f); fclose(f); snprintf(cmd, sizeof cmd, "%s build -o %s %s", 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; } 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; }