selfhost/cmd/wcc: TARRAY struct-stride + cache-bind resolved body (Phase A.4)
A.3 left wwstage slotsize at 134 fallback hits. Per-kind breakdown:
N_TARRAY 33 + N_TNAME 101 (of which 71 resolve to TY_STRUCT, 3 to
module-name quirks, 27 already had tinfo populated and were spurious
fallbacks via missed cache hits).
tinfofornode N_TNAME: existing arm already reached the resolved body
via aliaslookup → tinfofornode recursion (reviewer-61a3's "isn't
reaching body" hypothesis disproved by per-name instrumentation). A.4
binds the resolved-body node into the cache too — mirrors A.2's
TSTRUCT/TFN/TTUPLE/TTAGGED cycle-break pattern so future calls on
either node short-circuit.
tinfofornode N_TARRAY: when sub.kind == TY_STRUCT, round sub.size up
to 8 before stride. Mirrors registerstruct's slot-padded element
stride (cgenutil.ww:2156-2165 / :2233). Primitive elements stay
natural (slotsize's TARRAY walker also keeps them natural).
slotsize fast-path adds TY_VOID (size 0) and TY_ARRAY (gated on
alen > 0 so `[_]T` keeps routing through letslotsize). TY_STRUCT
deferred to A.5: tinfofornode TSTRUCT uses per-field natural-align
so size(T) stays natural at user level, but registerstruct uses
size-derived align with nested structs slot-padded — diverges on
ragged-tail shapes (`{inner=3*i32, mark: i32}` gives natural=16 vs
totsize=24). Proper A.5 design is a tinfo.slotsize SSoT distinct
from tinfo.size.
Module-name TNAME quirks (`let l: lex;` where lex is both a struct
and the imported module): resolvealias short-circuits on SK_MOD,
n.type_ stays nil, falls through to AST walker which structlookups
correctly. 3 hits in tree. A.5 work alongside TSTRUCT.
Post-A.4 fallback: wwdump 134→45, w6a 17→12, w6l 6→6, ww 12→11
(reviewer also measured w6c at 40). Total 169→74 across the corpus
(56% reduction). All 74 are TNAME → TY_STRUCT or module-name quirks.
131/131 + 994 + 995 + bootstrap (ww2==ww3==ww4) byte-identical.
This commit is contained in:
@@ -7934,9 +7934,25 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
// Mirrors astsize's TNAME fallback so the helpers stay in
|
||||
// lockstep until A.2 collapses each cgen size-walker onto
|
||||
// tinfo.size directly.
|
||||
//
|
||||
// #61 A.4: bind the resolved body too so future
|
||||
// tinfofornode calls on either the TNAME or its target
|
||||
// short-circuit on the cache hit instead of re-walking
|
||||
// the chain. Pre-bind matches A.2's TSTRUCT/TFN/TTUPLE/
|
||||
// TTAGGED cycle-break pattern (a self-referential
|
||||
// struct field's *T → TNAME → body would otherwise
|
||||
// re-enter the same chain).
|
||||
let body: *node = resolvealias(c, n);
|
||||
if (body != nil && body != n) {
|
||||
r = tinfofornode(c, body);
|
||||
let cached2: *tinfo = tinfocachelookup(c.tc, body);
|
||||
if (cached2 != nil) {
|
||||
r = cached2;
|
||||
} else {
|
||||
r = tinfofornode(c, body);
|
||||
if (r != nil) {
|
||||
tinfocachebind(c.tc, body, r);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} else { if (k == nkind.N_TBANG) {
|
||||
@@ -7961,7 +7977,22 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
if (n.rhs != nil) {
|
||||
if (n.rhs.kind == nkind.N_INTLIT) { elen = n.rhs.uval; };
|
||||
};
|
||||
r = typearray(c.a, tinfofornode(c, n.lhs), elen);
|
||||
let sub: *tinfo = tinfofornode(c, n.lhs);
|
||||
r = typearray(c.a, sub, elen);
|
||||
// #61 A.4: cgen's registerstruct.totsize slot-pads each struct
|
||||
// to 8B (cgenutil.ww:2204-2205); fieldsize→[N]Struct stride
|
||||
// (cgenutil.ww:2156-2165) uses that totsize, so tinfo.size
|
||||
// must mirror the pad for byte-identity with the AST-walker
|
||||
// fallback. Primitives (str/i32/u8/...) keep their natural
|
||||
// stride — slotsize's TARRAY arm reads primsize directly,
|
||||
// not si.totsize. Only TY_STRUCT subs need the round.
|
||||
if (sub != nil && sub.kind == tykind.TY_STRUCT) {
|
||||
let stride: u64 = sub.size;
|
||||
if ((stride & 7u64) != 0u64) {
|
||||
stride = (stride + 7u64) & ~7u64;
|
||||
};
|
||||
r.size = stride * elen;
|
||||
};
|
||||
} else { if (k == nkind.N_TFN) {
|
||||
// Cstage cmd/wcc/check.c:437-466: function types are 8B / 8B
|
||||
// (call-target pointer shape). Pre-bind before recursing into
|
||||
@@ -10947,8 +10978,8 @@ export fn letslotsize(c: *cgen, n: *node) i32 = {
|
||||
};
|
||||
|
||||
fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
// #61 audit §1.8 — A.3 fast-path expansion. Read tinfo.size off the
|
||||
// populated type-expression node when the kind matches the cstage
|
||||
// #61 audit §1.8 — A.3 / A.4 fast-path expansion. Read tinfo.size off
|
||||
// the populated type-expression node when the kind matches the cstage
|
||||
// natural-size SSoT. Coverage:
|
||||
// - pointer-like (PTR/CHAN/FN), slice, str, tagged
|
||||
// return ti.size directly (size already encodes the slot).
|
||||
@@ -10959,17 +10990,45 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
// primitive into an 8B stack slot regardless of tinfo.size.
|
||||
// Padding lives at the read site, not in tinfo.size, so size(T)
|
||||
// stays a faithful natural-width SSoT.
|
||||
// TUPLE / TSTRUCT / TARRAY still flow through the AST-walker fallback
|
||||
// because cgen's per-field stride contract (registerstruct.totsize
|
||||
// rounding, [N]T element-size walk) lives there, not in tinfo.size.
|
||||
// - #61 A.4 adds:
|
||||
// · TY_VOID returns 0 (mirrors the TNAME-"void" fallback arm).
|
||||
// Closes 56 N_TNAME-"void" / void-aliased (utf8.invalid,
|
||||
// overflow, done, more, ...) fallback hits — tinfofornode
|
||||
// now caches both the TNAME and its resolved body so future
|
||||
// calls on either land in the cache.
|
||||
// · TY_ARRAY returns ti.size when alen > 0 — tinfofornode's
|
||||
// TARRAY arm now applies the same struct-element round-to-8
|
||||
// cgen's fieldsize uses (#61 A.4 step 2), so [N]Struct's
|
||||
// stride matches. [_]T (alen=0) still routes through the
|
||||
// AST walker; letslotsize patches the length there.
|
||||
// TUPLE still flows through the fallback — slotsize sums raw element
|
||||
// sizes there, while tinfofornode TTUPLE mirrors cstage's natural
|
||||
// sum, and the two diverge for `(i32, str)`-style mixes (slot vs
|
||||
// natural per-element padding).
|
||||
//
|
||||
// TY_STRUCT deferred to A.5: tinfofornode TSTRUCT uses per-field
|
||||
// natural-align for offsets (mirroring cstage's resolve_type +
|
||||
// astsize, so `size(T)` stays natural), but registerstruct uses
|
||||
// size-derived alignment with nested structs slot-padded to 8
|
||||
// (cgenutil.ww:2178-2207). The two agree for the common 8B-aligned
|
||||
// shapes but diverge for "ragged tail" structs like
|
||||
// `struct { inner: struct{i32,i32,i32}, mark: i32 }` where the
|
||||
// inner struct's natural align (4) drops below cgen's nested-
|
||||
// struct contract (aln=8 for sz>=8). Closing the gap without
|
||||
// touching `size(T)`'s natural-width contract needs a separate
|
||||
// SSoT (e.g. tinfo.slotsize) — filed as A.5.
|
||||
if (typn != nil && typn.type_ != nil) {
|
||||
let ti: *tinfo = typn.type_: *tinfo;
|
||||
let kk: tykind = ti.kind;
|
||||
if (kk == tykind.TY_PTR || kk == tykind.TY_SLICE ||
|
||||
kk == tykind.TY_CHAN || kk == tykind.TY_FN ||
|
||||
kk == tykind.TY_STR || kk == tykind.TY_TAGGED) {
|
||||
kk == tykind.TY_STR || kk == tykind.TY_TAGGED ||
|
||||
kk == tykind.TY_VOID) {
|
||||
return ti.size: i32;
|
||||
};
|
||||
if (kk == tykind.TY_ARRAY) {
|
||||
if (ti.alen > 0u64) { return ti.size: i32; };
|
||||
};
|
||||
if (kk == tykind.TY_BOOL || kk == tykind.TY_RUNE ||
|
||||
kk == tykind.TY_I8 || kk == tykind.TY_I16 ||
|
||||
kk == tykind.TY_I32 || kk == tykind.TY_I64 ||
|
||||
|
||||
@@ -1941,8 +1941,8 @@ export fn letslotsize(c: *cgen, n: *node) i32 = {
|
||||
};
|
||||
|
||||
fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
// #61 audit §1.8 — A.3 fast-path expansion. Read tinfo.size off the
|
||||
// populated type-expression node when the kind matches the cstage
|
||||
// #61 audit §1.8 — A.3 / A.4 fast-path expansion. Read tinfo.size off
|
||||
// the populated type-expression node when the kind matches the cstage
|
||||
// natural-size SSoT. Coverage:
|
||||
// - pointer-like (PTR/CHAN/FN), slice, str, tagged
|
||||
// return ti.size directly (size already encodes the slot).
|
||||
@@ -1953,17 +1953,45 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
// primitive into an 8B stack slot regardless of tinfo.size.
|
||||
// Padding lives at the read site, not in tinfo.size, so size(T)
|
||||
// stays a faithful natural-width SSoT.
|
||||
// TUPLE / TSTRUCT / TARRAY still flow through the AST-walker fallback
|
||||
// because cgen's per-field stride contract (registerstruct.totsize
|
||||
// rounding, [N]T element-size walk) lives there, not in tinfo.size.
|
||||
// - #61 A.4 adds:
|
||||
// · TY_VOID returns 0 (mirrors the TNAME-"void" fallback arm).
|
||||
// Closes 56 N_TNAME-"void" / void-aliased (utf8.invalid,
|
||||
// overflow, done, more, ...) fallback hits — tinfofornode
|
||||
// now caches both the TNAME and its resolved body so future
|
||||
// calls on either land in the cache.
|
||||
// · TY_ARRAY returns ti.size when alen > 0 — tinfofornode's
|
||||
// TARRAY arm now applies the same struct-element round-to-8
|
||||
// cgen's fieldsize uses (#61 A.4 step 2), so [N]Struct's
|
||||
// stride matches. [_]T (alen=0) still routes through the
|
||||
// AST walker; letslotsize patches the length there.
|
||||
// TUPLE still flows through the fallback — slotsize sums raw element
|
||||
// sizes there, while tinfofornode TTUPLE mirrors cstage's natural
|
||||
// sum, and the two diverge for `(i32, str)`-style mixes (slot vs
|
||||
// natural per-element padding).
|
||||
//
|
||||
// TY_STRUCT deferred to A.5: tinfofornode TSTRUCT uses per-field
|
||||
// natural-align for offsets (mirroring cstage's resolve_type +
|
||||
// astsize, so `size(T)` stays natural), but registerstruct uses
|
||||
// size-derived alignment with nested structs slot-padded to 8
|
||||
// (cgenutil.ww:2178-2207). The two agree for the common 8B-aligned
|
||||
// shapes but diverge for "ragged tail" structs like
|
||||
// `struct { inner: struct{i32,i32,i32}, mark: i32 }` where the
|
||||
// inner struct's natural align (4) drops below cgen's nested-
|
||||
// struct contract (aln=8 for sz>=8). Closing the gap without
|
||||
// touching `size(T)`'s natural-width contract needs a separate
|
||||
// SSoT (e.g. tinfo.slotsize) — filed as A.5.
|
||||
if (typn != nil && typn.type_ != nil) {
|
||||
let ti: *tinfo = typn.type_: *tinfo;
|
||||
let kk: tykind = ti.kind;
|
||||
if (kk == tykind.TY_PTR || kk == tykind.TY_SLICE ||
|
||||
kk == tykind.TY_CHAN || kk == tykind.TY_FN ||
|
||||
kk == tykind.TY_STR || kk == tykind.TY_TAGGED) {
|
||||
kk == tykind.TY_STR || kk == tykind.TY_TAGGED ||
|
||||
kk == tykind.TY_VOID) {
|
||||
return ti.size: i32;
|
||||
};
|
||||
if (kk == tykind.TY_ARRAY) {
|
||||
if (ti.alen > 0u64) { return ti.size: i32; };
|
||||
};
|
||||
if (kk == tykind.TY_BOOL || kk == tykind.TY_RUNE ||
|
||||
kk == tykind.TY_I8 || kk == tykind.TY_I16 ||
|
||||
kk == tykind.TY_I32 || kk == tykind.TY_I64 ||
|
||||
|
||||
@@ -901,9 +901,25 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
// Mirrors astsize's TNAME fallback so the helpers stay in
|
||||
// lockstep until A.2 collapses each cgen size-walker onto
|
||||
// tinfo.size directly.
|
||||
//
|
||||
// #61 A.4: bind the resolved body too so future
|
||||
// tinfofornode calls on either the TNAME or its target
|
||||
// short-circuit on the cache hit instead of re-walking
|
||||
// the chain. Pre-bind matches A.2's TSTRUCT/TFN/TTUPLE/
|
||||
// TTAGGED cycle-break pattern (a self-referential
|
||||
// struct field's *T → TNAME → body would otherwise
|
||||
// re-enter the same chain).
|
||||
let body: *node = resolvealias(c, n);
|
||||
if (body != nil && body != n) {
|
||||
r = tinfofornode(c, body);
|
||||
let cached2: *tinfo = tinfocachelookup(c.tc, body);
|
||||
if (cached2 != nil) {
|
||||
r = cached2;
|
||||
} else {
|
||||
r = tinfofornode(c, body);
|
||||
if (r != nil) {
|
||||
tinfocachebind(c.tc, body, r);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} else { if (k == nkind.N_TBANG) {
|
||||
@@ -928,7 +944,22 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
if (n.rhs != nil) {
|
||||
if (n.rhs.kind == nkind.N_INTLIT) { elen = n.rhs.uval; };
|
||||
};
|
||||
r = typearray(c.a, tinfofornode(c, n.lhs), elen);
|
||||
let sub: *tinfo = tinfofornode(c, n.lhs);
|
||||
r = typearray(c.a, sub, elen);
|
||||
// #61 A.4: cgen's registerstruct.totsize slot-pads each struct
|
||||
// to 8B (cgenutil.ww:2204-2205); fieldsize→[N]Struct stride
|
||||
// (cgenutil.ww:2156-2165) uses that totsize, so tinfo.size
|
||||
// must mirror the pad for byte-identity with the AST-walker
|
||||
// fallback. Primitives (str/i32/u8/...) keep their natural
|
||||
// stride — slotsize's TARRAY arm reads primsize directly,
|
||||
// not si.totsize. Only TY_STRUCT subs need the round.
|
||||
if (sub != nil && sub.kind == tykind.TY_STRUCT) {
|
||||
let stride: u64 = sub.size;
|
||||
if ((stride & 7u64) != 0u64) {
|
||||
stride = (stride + 7u64) & ~7u64;
|
||||
};
|
||||
r.size = stride * elen;
|
||||
};
|
||||
} else { if (k == nkind.N_TFN) {
|
||||
// Cstage cmd/wcc/check.c:437-466: function types are 8B / 8B
|
||||
// (call-target pointer shape). Pre-bind before recursing into
|
||||
|
||||
@@ -7934,9 +7934,25 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
// Mirrors astsize's TNAME fallback so the helpers stay in
|
||||
// lockstep until A.2 collapses each cgen size-walker onto
|
||||
// tinfo.size directly.
|
||||
//
|
||||
// #61 A.4: bind the resolved body too so future
|
||||
// tinfofornode calls on either the TNAME or its target
|
||||
// short-circuit on the cache hit instead of re-walking
|
||||
// the chain. Pre-bind matches A.2's TSTRUCT/TFN/TTUPLE/
|
||||
// TTAGGED cycle-break pattern (a self-referential
|
||||
// struct field's *T → TNAME → body would otherwise
|
||||
// re-enter the same chain).
|
||||
let body: *node = resolvealias(c, n);
|
||||
if (body != nil && body != n) {
|
||||
r = tinfofornode(c, body);
|
||||
let cached2: *tinfo = tinfocachelookup(c.tc, body);
|
||||
if (cached2 != nil) {
|
||||
r = cached2;
|
||||
} else {
|
||||
r = tinfofornode(c, body);
|
||||
if (r != nil) {
|
||||
tinfocachebind(c.tc, body, r);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
} else { if (k == nkind.N_TBANG) {
|
||||
@@ -7961,7 +7977,22 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
if (n.rhs != nil) {
|
||||
if (n.rhs.kind == nkind.N_INTLIT) { elen = n.rhs.uval; };
|
||||
};
|
||||
r = typearray(c.a, tinfofornode(c, n.lhs), elen);
|
||||
let sub: *tinfo = tinfofornode(c, n.lhs);
|
||||
r = typearray(c.a, sub, elen);
|
||||
// #61 A.4: cgen's registerstruct.totsize slot-pads each struct
|
||||
// to 8B (cgenutil.ww:2204-2205); fieldsize→[N]Struct stride
|
||||
// (cgenutil.ww:2156-2165) uses that totsize, so tinfo.size
|
||||
// must mirror the pad for byte-identity with the AST-walker
|
||||
// fallback. Primitives (str/i32/u8/...) keep their natural
|
||||
// stride — slotsize's TARRAY arm reads primsize directly,
|
||||
// not si.totsize. Only TY_STRUCT subs need the round.
|
||||
if (sub != nil && sub.kind == tykind.TY_STRUCT) {
|
||||
let stride: u64 = sub.size;
|
||||
if ((stride & 7u64) != 0u64) {
|
||||
stride = (stride + 7u64) & ~7u64;
|
||||
};
|
||||
r.size = stride * elen;
|
||||
};
|
||||
} else { if (k == nkind.N_TFN) {
|
||||
// Cstage cmd/wcc/check.c:437-466: function types are 8B / 8B
|
||||
// (call-target pointer shape). Pre-bind before recursing into
|
||||
@@ -10947,8 +10978,8 @@ export fn letslotsize(c: *cgen, n: *node) i32 = {
|
||||
};
|
||||
|
||||
fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
// #61 audit §1.8 — A.3 fast-path expansion. Read tinfo.size off the
|
||||
// populated type-expression node when the kind matches the cstage
|
||||
// #61 audit §1.8 — A.3 / A.4 fast-path expansion. Read tinfo.size off
|
||||
// the populated type-expression node when the kind matches the cstage
|
||||
// natural-size SSoT. Coverage:
|
||||
// - pointer-like (PTR/CHAN/FN), slice, str, tagged
|
||||
// return ti.size directly (size already encodes the slot).
|
||||
@@ -10959,17 +10990,45 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
// primitive into an 8B stack slot regardless of tinfo.size.
|
||||
// Padding lives at the read site, not in tinfo.size, so size(T)
|
||||
// stays a faithful natural-width SSoT.
|
||||
// TUPLE / TSTRUCT / TARRAY still flow through the AST-walker fallback
|
||||
// because cgen's per-field stride contract (registerstruct.totsize
|
||||
// rounding, [N]T element-size walk) lives there, not in tinfo.size.
|
||||
// - #61 A.4 adds:
|
||||
// · TY_VOID returns 0 (mirrors the TNAME-"void" fallback arm).
|
||||
// Closes 56 N_TNAME-"void" / void-aliased (utf8.invalid,
|
||||
// overflow, done, more, ...) fallback hits — tinfofornode
|
||||
// now caches both the TNAME and its resolved body so future
|
||||
// calls on either land in the cache.
|
||||
// · TY_ARRAY returns ti.size when alen > 0 — tinfofornode's
|
||||
// TARRAY arm now applies the same struct-element round-to-8
|
||||
// cgen's fieldsize uses (#61 A.4 step 2), so [N]Struct's
|
||||
// stride matches. [_]T (alen=0) still routes through the
|
||||
// AST walker; letslotsize patches the length there.
|
||||
// TUPLE still flows through the fallback — slotsize sums raw element
|
||||
// sizes there, while tinfofornode TTUPLE mirrors cstage's natural
|
||||
// sum, and the two diverge for `(i32, str)`-style mixes (slot vs
|
||||
// natural per-element padding).
|
||||
//
|
||||
// TY_STRUCT deferred to A.5: tinfofornode TSTRUCT uses per-field
|
||||
// natural-align for offsets (mirroring cstage's resolve_type +
|
||||
// astsize, so `size(T)` stays natural), but registerstruct uses
|
||||
// size-derived alignment with nested structs slot-padded to 8
|
||||
// (cgenutil.ww:2178-2207). The two agree for the common 8B-aligned
|
||||
// shapes but diverge for "ragged tail" structs like
|
||||
// `struct { inner: struct{i32,i32,i32}, mark: i32 }` where the
|
||||
// inner struct's natural align (4) drops below cgen's nested-
|
||||
// struct contract (aln=8 for sz>=8). Closing the gap without
|
||||
// touching `size(T)`'s natural-width contract needs a separate
|
||||
// SSoT (e.g. tinfo.slotsize) — filed as A.5.
|
||||
if (typn != nil && typn.type_ != nil) {
|
||||
let ti: *tinfo = typn.type_: *tinfo;
|
||||
let kk: tykind = ti.kind;
|
||||
if (kk == tykind.TY_PTR || kk == tykind.TY_SLICE ||
|
||||
kk == tykind.TY_CHAN || kk == tykind.TY_FN ||
|
||||
kk == tykind.TY_STR || kk == tykind.TY_TAGGED) {
|
||||
kk == tykind.TY_STR || kk == tykind.TY_TAGGED ||
|
||||
kk == tykind.TY_VOID) {
|
||||
return ti.size: i32;
|
||||
};
|
||||
if (kk == tykind.TY_ARRAY) {
|
||||
if (ti.alen > 0u64) { return ti.size: i32; };
|
||||
};
|
||||
if (kk == tykind.TY_BOOL || kk == tykind.TY_RUNE ||
|
||||
kk == tykind.TY_I8 || kk == tykind.TY_I16 ||
|
||||
kk == tykind.TY_I32 || kk == tykind.TY_I64 ||
|
||||
|
||||
Reference in New Issue
Block a user