w6c+selfhost: cgen *T-rooted chained N_DOT (closes #22)

Spine walker now accepts *T root at the last hop (cur->lhs->kind ==
N_IDENT, pu->kind == TY_PTR), substitutes pointee struct, emits
MOVQ off(BP), CX before offset arithmetic. Symmetric in N_DOT and
N_ASSIGN. Distinct gate from existing mid-chain *T-field branch
(cgen.c:4615) — no shadow.

Unblocks task #18 (bufio writer first-field embed). Pre-existing
*T-field mid-chain path unchanged.
This commit is contained in:
2026-05-14 02:44:57 +09:00
parent d2f4659305
commit d66aef382a
6 changed files with 659 additions and 127 deletions

View File

@@ -129,6 +129,219 @@ static const struct row rows[] = {
" 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