From f45caf1cd92ed9125fcb77cfe18babb7896a7f76 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sun, 24 May 2026 03:16:14 +0900 Subject: [PATCH] selfhost/cmd/wcc: index-base esz from n.type_.size + delete indexbaseesz (#72, A.6.3k) The N_INDEX node carries the checker-stamped element tinfo (#60 arc); cgindex, cgun TK_AMP, and cgassign now read the element stride off that .type_.size instead of indexbaseesz's manual N_DOT-pseudo-field + structlookup walk, retiring the helper (0 callers, ~96 LOC). Aligns down to cstage's idx_eff(base->type)->sub->size (cmd/w6c/cgen.c:3517-18, natural element size) -- strictly more cstage-faithful than indexbaseesz's totsize/slotsize derivation (byte-id held only because firing shapes have totsize==natural; cstage reads natural and ww-old==cstage, so the flip is structural). Each site keeps its nil->default-8 fallback; cgindex stays esz-only (signed_elem unset for the N_DOT base, as before). Closes the A.6.3 cgenutil-collapse arc: every type/size/offset query in cgen now reads the checker-stamped tinfo, and the AST-walker / structinfo-walk helpers it replaced (dotfieldtnode, indexvaluetnode, rhstargetname, dotinnerstructptr, indexbaseesz) are retired. make test 134/134, byte-id 990-997 hold. Coverage: 713/741/755 + self-rebuild. --- selfhost/cmd/w6c/main.combined.ww | 144 +++++---------------------- selfhost/cmd/wcc/cgenexpr.ww | 37 ++++--- selfhost/cmd/wcc/cgenutil.ww | 103 +------------------ selfhost/cmd/wcc/check.ww | 4 +- selfhost/cmd/wwdump/main.combined.ww | 144 +++++---------------------- 5 files changed, 78 insertions(+), 354 deletions(-) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 8beb7854..8f4f1d10 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -8373,8 +8373,8 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = { // cmd/wcc/check.c:329-345 which stores tuple positionals on // t->params (Tparam, no offset, consumer recomputes by walking // at cgen.c:5723-5750); the offset-stored shape lets Phase - // 2/J/K consumers (dotchainresolve, indexbaseesz) read offsets - // directly per the A.6 stamp-once-read-many arc. Direct analog + // 2/J consumers (dotchainresolve) read offsets directly per + // the A.6 stamp-once-read-many arc. Direct analog // 26724fe (#50 phase 1, A.6.3f-a) for the head/tail append // pattern. Offset matches cstage's raw-sum layout (no per- // element padding) — rule 10 aligns wwstage tuple layout down @@ -10549,7 +10549,7 @@ fn dynamicgrow(m: *state, need: i32) void = { // - pushargsrev: per-call arg pushing // - type predicates: isstr*/isslice*/istagged*/nodeis* families // - field ops: fieldloadop, fieldstoreop -// - index helpers: indexbaseesz, elemsizeof +// - index helpers: elemsizeof, elemsizeofc // - slot sizing: structlookup, primsize, slotsize, fieldsize, // registerstruct, collectstructs // - rhs helpers: taggedvariantindex @@ -11204,8 +11204,8 @@ fn nodeisstr(c: *cgen, n: *node) bool = { }; // N_INDEX through a struct field: e.g. cmd.argsptr[i] // where argsptr: *str. cgindex correctly loads the - // (ptr, len) pair via indexbaseesz; without this arm - // pushargsrev would only push AX and lose the .len. + // (ptr, len) pair off the stamped element size; without + // this arm pushargsrev would only push AX and lose .len. if (base.kind == nkind.N_DOT) { let fld: str = base.str; if (streq(fld, "ptr")) { return false; }; @@ -11396,103 +11396,6 @@ export fn localloadop(c: *cgen, tnode: *node) str = { return loadopsz(sigd, sz); }; -// indexbaseesz — element size for `arr[i]` where the base is a -// chained-dot pseudo-field `s.ptr` (s being str/*str/slice/*slice). -// For str the element is one byte; for `[]T` / `*[]T` we drill into -// the slice element type. -fn indexbaseesz(c: *cgen, base: *node) i32 = { - if (base == nil) { return 8; }; - if (base.kind != nkind.N_DOT) { return 8; }; - let fld: str = base.str; - let inner: *node = base.lhs; - if (inner == nil) { return 8; }; - if (inner.kind != nkind.N_IDENT) { return 8; }; - let nm: str = inner.str; - let lc: *local = localfindnode(c, nm); - if (lc == nil) { return 8; }; - let tn: *node = lc.tnode; - if (tn == nil) { return 8; }; - - // `.ptr` pseudo-field on str/slice → element of the str/slice. - // Gated on inner kind, NOT on the field name alone: a struct with - // a literal `ptr: *T` field (lib/memio.state, lib/bufio.state) must - // route through the generic struct-field arm below so the stride - // comes from primsize/structlookup, not the str/slice default. The - // over-broad pre-#21 shortcut hard-coded esz=8 and silently - // miscompiled `m.ptr[i]` for `*u8` callers (also widened the load - // op MOVZBQ → MOVQ in cgindex). Mirrors cstage which routes every - // base through `base->type->sub->size` (cmd/w6c/cgen.c idx_eff). - if (streq(fld, "ptr")) { - let innert: *node = tn; - if (tn.kind == nkind.N_TPTR) { innert = tn.lhs; }; - if (innert != nil) { - if (innert.kind == nkind.N_TNAME) { - if (streq(innert.str, "str")) { return 1; }; - }; - // Slice element: resolve through elemsizeofc so a - // slice of a named struct (e.g. *[]option) returns - // the struct stride instead of falling through to - // elemsizeof's default 8. - if (innert.kind == nkind.N_TSLICE) { - return elemsizeofc(c, innert); - }; - }; - // Inner is a struct N_TNAME (or unresolved) — fall through - // to the generic struct-field arm below. - }; - - // Generic struct field: if it's *T, element size is T's size. - let lkind: nkind = tn.kind; - let sname: str; - sname.ptr = nil; sname.len = 0; - if (lkind == nkind.N_TNAME) { sname = tn.str; }; - if (lkind == nkind.N_TPTR) { - let pinner: *node = tn.lhs; - if (pinner != nil) { - if (pinner.kind == nkind.N_TNAME) { sname = pinner.str; }; - }; - }; - if (sname.len == 0) { return 8; }; - let si: *structinfo = structlookup(c, sname); - if (si == nil) { return 8; }; - let fi: *fieldinfo = si.fields; - for (fi != nil) { - let fn_: str = fi.fname; - if (streq(fn_, fld)) { - let ft: *node = fi.tnode; - if (ft == nil) { return 8; }; - if (ft.kind == nkind.N_TPTR) { - let elem: *node = ft.lhs; - if (elem != nil) { - if (elem.kind == nkind.N_TNAME) { - if (streq(elem.str, "str")) { return primtypesize("str"): i32; }; - let ps: i32 = primsize(elem.str); - if (ps > 0) { return ps; }; - // Pointer to named struct: indexing - // stride is the struct slot size. - // Without this, &p.ptr[i] for p.ptr: - // *S falls through to 8 and reads - // the wrong element. - let si: *structinfo = structlookup(c, elem.str); - if (si != nil) { return si.totsize; }; - }; - }; - return 8; - }; - if (ft.kind == nkind.N_TSLICE) { return elemsizeof(ft); }; - // str-typed field: indexing yields one byte - // (`n.s[i]` where .s is str — matches C cgen's - // MOVZBQ for byte indexing). - if (ft.kind == nkind.N_TNAME) { - if (streq(ft.str, "str")) { return 1; }; - }; - return 8; - }; - fi = fi.finext; - }; - return 8; -}; - // elemsizeof — given the type node of an indexable (`*T`, `[]T`, // `[N]T`, `str`), return the byte size of one element (1 for u8/i8/ // bool/str-byte, 8 otherwise — same shape as C cgen's esz fallback). @@ -14588,7 +14491,14 @@ fn cgindex(c: *cgen, n: *node) void = { }; }; } else { if (base.kind == nkind.N_DOT) { - esz = indexbaseesz(c, base); + // `s.ptr[i]` / struct-field index: stride is the + // checker-stamped element tinfo's natural size, the + // same idiom as the N_INDEX-base arm below (#60/#72). + // cstage idx_eff(base->type)->sub->size (cmd/w6c/ + // cgen.c:3517-18). esz-only — N_DOT-base signedness + // stays unset, as before. + let dt: *tinfo = n.type_: *tinfo; + if (dt != nil) { esz = dt.size: i32; }; } else { if (base.kind == nkind.N_INDEX) { // #60: chained `names[i][k]` — n.type_ is the checker- // stamped outer element tinfo (indexresult over the inner @@ -16371,11 +16281,13 @@ fn cgun(c: *cgen, n: *node) void = { }; }; } else { if (base.kind == nkind.N_DOT) { - // `&p.ptr[i]` shape: stride is the element - // of the slice/struct-pointer field, not - // the default 8. Mirrors cgindex's N_DOT - // arm so &p.ptr[i] and p.ptr[i] agree. - esz = indexbaseesz(c, base); + // `&p.ptr[i]`: stride is the checker-stamped + // element tinfo's natural size, mirroring + // cgindex's N_DOT arm so &p.ptr[i] and + // p.ptr[i] agree. cstage idx_eff(base->type) + // ->sub->size (cmd/w6c/cgen.c:3517-18). #72. + let dt: *tinfo = opnd.type_: *tinfo; + if (dt != nil) { esz = dt.size: i32; }; };}; }; cgexpr(c, idx); @@ -17612,18 +17524,14 @@ fn cgassign(c: *cgen, n: *node) void = { }; }; } else { if (base.kind == nkind.N_DOT) { - esz = indexbaseesz(c, base); - // Tagged-element gate (below) keys on the store - // target's element type. lhs.type_ is the - // checker-stamped element tinfo of the N_INDEX, - // so carry lhs and let the gate read it via + // lhs.type_ is the checker-stamped element tinfo + // of the N_INDEX: esz is its natural size and the + // tagged-element gate (below) reads the same // .type_ — same idiom as cgindex's n.type_ read - // (#60). Drops the indexvaluetnode walk for the - // N_DOT base (#69/#61d, was #30). cstage reads - // idx_eff(base->type)->sub (cmd/w6c/cgen.c:3517- - // 3523). + // (#60/#72). cstage idx_eff(base->type)->sub->size + // (cmd/w6c/cgen.c:3517-18). let dt: *tinfo = lhs.type_: *tinfo; - if (dt != nil) { elemtn = lhs; }; + if (dt != nil) { esz = dt.size: i32; elemtn = lhs; }; } else { if (base.kind == nkind.N_INDEX) { // Chained-write write-side parallel of the // cgindex N_INDEX-base arm (#24): `names[i][k] diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 97ed61ca..1abc205e 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -720,7 +720,14 @@ fn cgindex(c: *cgen, n: *node) void = { }; }; } else { if (base.kind == nkind.N_DOT) { - esz = indexbaseesz(c, base); + // `s.ptr[i]` / struct-field index: stride is the + // checker-stamped element tinfo's natural size, the + // same idiom as the N_INDEX-base arm below (#60/#72). + // cstage idx_eff(base->type)->sub->size (cmd/w6c/ + // cgen.c:3517-18). esz-only — N_DOT-base signedness + // stays unset, as before. + let dt: *tinfo = n.type_: *tinfo; + if (dt != nil) { esz = dt.size: i32; }; } else { if (base.kind == nkind.N_INDEX) { // #60: chained `names[i][k]` — n.type_ is the checker- // stamped outer element tinfo (indexresult over the inner @@ -2503,11 +2510,13 @@ fn cgun(c: *cgen, n: *node) void = { }; }; } else { if (base.kind == nkind.N_DOT) { - // `&p.ptr[i]` shape: stride is the element - // of the slice/struct-pointer field, not - // the default 8. Mirrors cgindex's N_DOT - // arm so &p.ptr[i] and p.ptr[i] agree. - esz = indexbaseesz(c, base); + // `&p.ptr[i]`: stride is the checker-stamped + // element tinfo's natural size, mirroring + // cgindex's N_DOT arm so &p.ptr[i] and + // p.ptr[i] agree. cstage idx_eff(base->type) + // ->sub->size (cmd/w6c/cgen.c:3517-18). #72. + let dt: *tinfo = opnd.type_: *tinfo; + if (dt != nil) { esz = dt.size: i32; }; };}; }; cgexpr(c, idx); @@ -3744,18 +3753,14 @@ fn cgassign(c: *cgen, n: *node) void = { }; }; } else { if (base.kind == nkind.N_DOT) { - esz = indexbaseesz(c, base); - // Tagged-element gate (below) keys on the store - // target's element type. lhs.type_ is the - // checker-stamped element tinfo of the N_INDEX, - // so carry lhs and let the gate read it via + // lhs.type_ is the checker-stamped element tinfo + // of the N_INDEX: esz is its natural size and the + // tagged-element gate (below) reads the same // .type_ — same idiom as cgindex's n.type_ read - // (#60). Drops the indexvaluetnode walk for the - // N_DOT base (#69/#61d, was #30). cstage reads - // idx_eff(base->type)->sub (cmd/w6c/cgen.c:3517- - // 3523). + // (#60/#72). cstage idx_eff(base->type)->sub->size + // (cmd/w6c/cgen.c:3517-18). let dt: *tinfo = lhs.type_: *tinfo; - if (dt != nil) { elemtn = lhs; }; + if (dt != nil) { esz = dt.size: i32; elemtn = lhs; }; } else { if (base.kind == nkind.N_INDEX) { // Chained-write write-side parallel of the // cgindex N_INDEX-base arm (#24): `names[i][k] diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 538f2961..ad87d2d4 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -4,7 +4,7 @@ // - pushargsrev: per-call arg pushing // - type predicates: isstr*/isslice*/istagged*/nodeis* families // - field ops: fieldloadop, fieldstoreop -// - index helpers: indexbaseesz, elemsizeof +// - index helpers: elemsizeof, elemsizeofc // - slot sizing: structlookup, primsize, slotsize, fieldsize, // registerstruct, collectstructs // - rhs helpers: taggedvariantindex @@ -659,8 +659,8 @@ fn nodeisstr(c: *cgen, n: *node) bool = { }; // N_INDEX through a struct field: e.g. cmd.argsptr[i] // where argsptr: *str. cgindex correctly loads the - // (ptr, len) pair via indexbaseesz; without this arm - // pushargsrev would only push AX and lose the .len. + // (ptr, len) pair off the stamped element size; without + // this arm pushargsrev would only push AX and lose .len. if (base.kind == nkind.N_DOT) { let fld: str = base.str; if (streq(fld, "ptr")) { return false; }; @@ -851,103 +851,6 @@ export fn localloadop(c: *cgen, tnode: *node) str = { return loadopsz(sigd, sz); }; -// indexbaseesz — element size for `arr[i]` where the base is a -// chained-dot pseudo-field `s.ptr` (s being str/*str/slice/*slice). -// For str the element is one byte; for `[]T` / `*[]T` we drill into -// the slice element type. -fn indexbaseesz(c: *cgen, base: *node) i32 = { - if (base == nil) { return 8; }; - if (base.kind != nkind.N_DOT) { return 8; }; - let fld: str = base.str; - let inner: *node = base.lhs; - if (inner == nil) { return 8; }; - if (inner.kind != nkind.N_IDENT) { return 8; }; - let nm: str = inner.str; - let lc: *local = localfindnode(c, nm); - if (lc == nil) { return 8; }; - let tn: *node = lc.tnode; - if (tn == nil) { return 8; }; - - // `.ptr` pseudo-field on str/slice → element of the str/slice. - // Gated on inner kind, NOT on the field name alone: a struct with - // a literal `ptr: *T` field (lib/memio.state, lib/bufio.state) must - // route through the generic struct-field arm below so the stride - // comes from primsize/structlookup, not the str/slice default. The - // over-broad pre-#21 shortcut hard-coded esz=8 and silently - // miscompiled `m.ptr[i]` for `*u8` callers (also widened the load - // op MOVZBQ → MOVQ in cgindex). Mirrors cstage which routes every - // base through `base->type->sub->size` (cmd/w6c/cgen.c idx_eff). - if (streq(fld, "ptr")) { - let innert: *node = tn; - if (tn.kind == nkind.N_TPTR) { innert = tn.lhs; }; - if (innert != nil) { - if (innert.kind == nkind.N_TNAME) { - if (streq(innert.str, "str")) { return 1; }; - }; - // Slice element: resolve through elemsizeofc so a - // slice of a named struct (e.g. *[]option) returns - // the struct stride instead of falling through to - // elemsizeof's default 8. - if (innert.kind == nkind.N_TSLICE) { - return elemsizeofc(c, innert); - }; - }; - // Inner is a struct N_TNAME (or unresolved) — fall through - // to the generic struct-field arm below. - }; - - // Generic struct field: if it's *T, element size is T's size. - let lkind: nkind = tn.kind; - let sname: str; - sname.ptr = nil; sname.len = 0; - if (lkind == nkind.N_TNAME) { sname = tn.str; }; - if (lkind == nkind.N_TPTR) { - let pinner: *node = tn.lhs; - if (pinner != nil) { - if (pinner.kind == nkind.N_TNAME) { sname = pinner.str; }; - }; - }; - if (sname.len == 0) { return 8; }; - let si: *structinfo = structlookup(c, sname); - if (si == nil) { return 8; }; - let fi: *fieldinfo = si.fields; - for (fi != nil) { - let fn_: str = fi.fname; - if (streq(fn_, fld)) { - let ft: *node = fi.tnode; - if (ft == nil) { return 8; }; - if (ft.kind == nkind.N_TPTR) { - let elem: *node = ft.lhs; - if (elem != nil) { - if (elem.kind == nkind.N_TNAME) { - if (streq(elem.str, "str")) { return primtypesize("str"): i32; }; - let ps: i32 = primsize(elem.str); - if (ps > 0) { return ps; }; - // Pointer to named struct: indexing - // stride is the struct slot size. - // Without this, &p.ptr[i] for p.ptr: - // *S falls through to 8 and reads - // the wrong element. - let si: *structinfo = structlookup(c, elem.str); - if (si != nil) { return si.totsize; }; - }; - }; - return 8; - }; - if (ft.kind == nkind.N_TSLICE) { return elemsizeof(ft); }; - // str-typed field: indexing yields one byte - // (`n.s[i]` where .s is str — matches C cgen's - // MOVZBQ for byte indexing). - if (ft.kind == nkind.N_TNAME) { - if (streq(ft.str, "str")) { return 1; }; - }; - return 8; - }; - fi = fi.finext; - }; - return 8; -}; - // elemsizeof — given the type node of an indexable (`*T`, `[]T`, // `[N]T`, `str`), return the byte size of one element (1 for u8/i8/ // bool/str-byte, 8 otherwise — same shape as C cgen's esz fallback). diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index f38fa4a4..f7692c5c 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -1276,8 +1276,8 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = { // cmd/wcc/check.c:329-345 which stores tuple positionals on // t->params (Tparam, no offset, consumer recomputes by walking // at cgen.c:5723-5750); the offset-stored shape lets Phase - // 2/J/K consumers (dotchainresolve, indexbaseesz) read offsets - // directly per the A.6 stamp-once-read-many arc. Direct analog + // 2/J consumers (dotchainresolve) read offsets directly per + // the A.6 stamp-once-read-many arc. Direct analog // 26724fe (#50 phase 1, A.6.3f-a) for the head/tail append // pattern. Offset matches cstage's raw-sum layout (no per- // element padding) — rule 10 aligns wwstage tuple layout down diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index bb1deb48..bfc37de9 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -8373,8 +8373,8 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = { // cmd/wcc/check.c:329-345 which stores tuple positionals on // t->params (Tparam, no offset, consumer recomputes by walking // at cgen.c:5723-5750); the offset-stored shape lets Phase - // 2/J/K consumers (dotchainresolve, indexbaseesz) read offsets - // directly per the A.6 stamp-once-read-many arc. Direct analog + // 2/J consumers (dotchainresolve) read offsets directly per + // the A.6 stamp-once-read-many arc. Direct analog // 26724fe (#50 phase 1, A.6.3f-a) for the head/tail append // pattern. Offset matches cstage's raw-sum layout (no per- // element padding) — rule 10 aligns wwstage tuple layout down @@ -10549,7 +10549,7 @@ fn dynamicgrow(m: *state, need: i32) void = { // - pushargsrev: per-call arg pushing // - type predicates: isstr*/isslice*/istagged*/nodeis* families // - field ops: fieldloadop, fieldstoreop -// - index helpers: indexbaseesz, elemsizeof +// - index helpers: elemsizeof, elemsizeofc // - slot sizing: structlookup, primsize, slotsize, fieldsize, // registerstruct, collectstructs // - rhs helpers: taggedvariantindex @@ -11204,8 +11204,8 @@ fn nodeisstr(c: *cgen, n: *node) bool = { }; // N_INDEX through a struct field: e.g. cmd.argsptr[i] // where argsptr: *str. cgindex correctly loads the - // (ptr, len) pair via indexbaseesz; without this arm - // pushargsrev would only push AX and lose the .len. + // (ptr, len) pair off the stamped element size; without + // this arm pushargsrev would only push AX and lose .len. if (base.kind == nkind.N_DOT) { let fld: str = base.str; if (streq(fld, "ptr")) { return false; }; @@ -11396,103 +11396,6 @@ export fn localloadop(c: *cgen, tnode: *node) str = { return loadopsz(sigd, sz); }; -// indexbaseesz — element size for `arr[i]` where the base is a -// chained-dot pseudo-field `s.ptr` (s being str/*str/slice/*slice). -// For str the element is one byte; for `[]T` / `*[]T` we drill into -// the slice element type. -fn indexbaseesz(c: *cgen, base: *node) i32 = { - if (base == nil) { return 8; }; - if (base.kind != nkind.N_DOT) { return 8; }; - let fld: str = base.str; - let inner: *node = base.lhs; - if (inner == nil) { return 8; }; - if (inner.kind != nkind.N_IDENT) { return 8; }; - let nm: str = inner.str; - let lc: *local = localfindnode(c, nm); - if (lc == nil) { return 8; }; - let tn: *node = lc.tnode; - if (tn == nil) { return 8; }; - - // `.ptr` pseudo-field on str/slice → element of the str/slice. - // Gated on inner kind, NOT on the field name alone: a struct with - // a literal `ptr: *T` field (lib/memio.state, lib/bufio.state) must - // route through the generic struct-field arm below so the stride - // comes from primsize/structlookup, not the str/slice default. The - // over-broad pre-#21 shortcut hard-coded esz=8 and silently - // miscompiled `m.ptr[i]` for `*u8` callers (also widened the load - // op MOVZBQ → MOVQ in cgindex). Mirrors cstage which routes every - // base through `base->type->sub->size` (cmd/w6c/cgen.c idx_eff). - if (streq(fld, "ptr")) { - let innert: *node = tn; - if (tn.kind == nkind.N_TPTR) { innert = tn.lhs; }; - if (innert != nil) { - if (innert.kind == nkind.N_TNAME) { - if (streq(innert.str, "str")) { return 1; }; - }; - // Slice element: resolve through elemsizeofc so a - // slice of a named struct (e.g. *[]option) returns - // the struct stride instead of falling through to - // elemsizeof's default 8. - if (innert.kind == nkind.N_TSLICE) { - return elemsizeofc(c, innert); - }; - }; - // Inner is a struct N_TNAME (or unresolved) — fall through - // to the generic struct-field arm below. - }; - - // Generic struct field: if it's *T, element size is T's size. - let lkind: nkind = tn.kind; - let sname: str; - sname.ptr = nil; sname.len = 0; - if (lkind == nkind.N_TNAME) { sname = tn.str; }; - if (lkind == nkind.N_TPTR) { - let pinner: *node = tn.lhs; - if (pinner != nil) { - if (pinner.kind == nkind.N_TNAME) { sname = pinner.str; }; - }; - }; - if (sname.len == 0) { return 8; }; - let si: *structinfo = structlookup(c, sname); - if (si == nil) { return 8; }; - let fi: *fieldinfo = si.fields; - for (fi != nil) { - let fn_: str = fi.fname; - if (streq(fn_, fld)) { - let ft: *node = fi.tnode; - if (ft == nil) { return 8; }; - if (ft.kind == nkind.N_TPTR) { - let elem: *node = ft.lhs; - if (elem != nil) { - if (elem.kind == nkind.N_TNAME) { - if (streq(elem.str, "str")) { return primtypesize("str"): i32; }; - let ps: i32 = primsize(elem.str); - if (ps > 0) { return ps; }; - // Pointer to named struct: indexing - // stride is the struct slot size. - // Without this, &p.ptr[i] for p.ptr: - // *S falls through to 8 and reads - // the wrong element. - let si: *structinfo = structlookup(c, elem.str); - if (si != nil) { return si.totsize; }; - }; - }; - return 8; - }; - if (ft.kind == nkind.N_TSLICE) { return elemsizeof(ft); }; - // str-typed field: indexing yields one byte - // (`n.s[i]` where .s is str — matches C cgen's - // MOVZBQ for byte indexing). - if (ft.kind == nkind.N_TNAME) { - if (streq(ft.str, "str")) { return 1; }; - }; - return 8; - }; - fi = fi.finext; - }; - return 8; -}; - // elemsizeof — given the type node of an indexable (`*T`, `[]T`, // `[N]T`, `str`), return the byte size of one element (1 for u8/i8/ // bool/str-byte, 8 otherwise — same shape as C cgen's esz fallback). @@ -14588,7 +14491,14 @@ fn cgindex(c: *cgen, n: *node) void = { }; }; } else { if (base.kind == nkind.N_DOT) { - esz = indexbaseesz(c, base); + // `s.ptr[i]` / struct-field index: stride is the + // checker-stamped element tinfo's natural size, the + // same idiom as the N_INDEX-base arm below (#60/#72). + // cstage idx_eff(base->type)->sub->size (cmd/w6c/ + // cgen.c:3517-18). esz-only — N_DOT-base signedness + // stays unset, as before. + let dt: *tinfo = n.type_: *tinfo; + if (dt != nil) { esz = dt.size: i32; }; } else { if (base.kind == nkind.N_INDEX) { // #60: chained `names[i][k]` — n.type_ is the checker- // stamped outer element tinfo (indexresult over the inner @@ -16371,11 +16281,13 @@ fn cgun(c: *cgen, n: *node) void = { }; }; } else { if (base.kind == nkind.N_DOT) { - // `&p.ptr[i]` shape: stride is the element - // of the slice/struct-pointer field, not - // the default 8. Mirrors cgindex's N_DOT - // arm so &p.ptr[i] and p.ptr[i] agree. - esz = indexbaseesz(c, base); + // `&p.ptr[i]`: stride is the checker-stamped + // element tinfo's natural size, mirroring + // cgindex's N_DOT arm so &p.ptr[i] and + // p.ptr[i] agree. cstage idx_eff(base->type) + // ->sub->size (cmd/w6c/cgen.c:3517-18). #72. + let dt: *tinfo = opnd.type_: *tinfo; + if (dt != nil) { esz = dt.size: i32; }; };}; }; cgexpr(c, idx); @@ -17612,18 +17524,14 @@ fn cgassign(c: *cgen, n: *node) void = { }; }; } else { if (base.kind == nkind.N_DOT) { - esz = indexbaseesz(c, base); - // Tagged-element gate (below) keys on the store - // target's element type. lhs.type_ is the - // checker-stamped element tinfo of the N_INDEX, - // so carry lhs and let the gate read it via + // lhs.type_ is the checker-stamped element tinfo + // of the N_INDEX: esz is its natural size and the + // tagged-element gate (below) reads the same // .type_ — same idiom as cgindex's n.type_ read - // (#60). Drops the indexvaluetnode walk for the - // N_DOT base (#69/#61d, was #30). cstage reads - // idx_eff(base->type)->sub (cmd/w6c/cgen.c:3517- - // 3523). + // (#60/#72). cstage idx_eff(base->type)->sub->size + // (cmd/w6c/cgen.c:3517-18). let dt: *tinfo = lhs.type_: *tinfo; - if (dt != nil) { elemtn = lhs; }; + if (dt != nil) { esz = dt.size: i32; elemtn = lhs; }; } else { if (base.kind == nkind.N_INDEX) { // Chained-write write-side parallel of the // cgindex N_INDEX-base arm (#24): `names[i][k]