selfhost/cmd/wcc + lib/ww: tinfo.slotsize SSoT + module-name TNAME fallback (Phase A.5)
A.4 left 74 fallback hits, all TNAME-flavored — 71 TNAME → TY_STRUCT
(natural-align vs slot-padded mismatch) + 3 module-name TNAME quirks
(`let l: lex;` where lex is both struct and imported module).
tinfo gains a slotsize: u64 field (96 → 104 bytes; amalloc bumped
to 112B per rule-7). size(T) stays Hare-natural at the user level;
cgen's slot storage now reads ti.slotsize for kinds where the two
differ. tinfofornode populates both:
- TSTRUCT: existing natural-align walk for r.size; new size-derived
align walk (sz≥8→8, ≥4→4, ≥2→2) for r.slotsize, rounded to 8.
Mirrors cgenutil.ww:2192-2218 registerstruct exactly.
- TTUPLE: parallel via tupleelemslot helper (primitives→8, str=16,
slice=24, ptr/fn/chan/i64/u64/int/uint/uintptr/f64=8, composite
→pt.slotsize, void=0).
- TARRAY: typearray sets slotsize = sub.slotsize * n. [N]i32 stays
4N (natural); [N]Triplet lifts to 16N (slot-padded). Reverts
A.4's r.size override since slot-pad now lives in slotsize.
- TFN/TENUM/TTAGGED/nullable: explicit slotsize. Default trail
`if r.slotsize == 0 then r.slotsize = r.size` catches TBANG.
- New fieldslotsize(ft) helper mirrors registerstruct's per-field
rule (struct→ft.slotsize, array→ft.slotsize, primitive→ft.size,
tagged→ft.size).
slotsize fast-path (cgenutil.ww) reads ti.slotsize for TY_STRUCT,
TY_TUPLE, TY_ARRAY; ti.size stays correct for PTR/SLICE/CHAN/FN/
STR/TAGGED/VOID (size == slotsize for those). Narrow scalars still
pad-to-8 at the read site (moving into slotsize would break
[N]i32 stride).
lib/ww/sym.ww adds scopelookuptype(s, name) — same FNV bucket+parent
walk as scopelookup but filtered on skind==SK_TYPE. resolvealias
calls it when bare-leaf scopelookup returns non-TYPE (e.g., the
SK_USE/SK_MOD short-circuit case). Fixes `let l: lex;` (mod=leaf)
AND `let t: tok;` (mod≠leaf, tok lives in package lex).
Post-A.5 fallback: 0 across full bootstrap. Reviewer's stricter
metric (zero fast-path MISSES when tinfo IS stamped) also 0;
remaining FB_NIL hits are value-expression nodes the checker
doesn't yet stamp — A.6 candidate.
Ragged-tail probe `struct{inner=3*i32, mark:i32}`: ti.size=16
(natural), ti.slotsize=24 (slot-padded). Cstage emits [N]<ragged>
stride=16 on the same source — latent divergence filed as #63.
Not exercised by selfhost, so bootstrap byte-identity holds today.
131/131 + 994 + 995 + bootstrap (ww2==ww3==ww4) all green.
This commit is contained in:
@@ -1941,45 +1941,27 @@ export fn letslotsize(c: *cgen, n: *node) i32 = {
|
||||
};
|
||||
|
||||
fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
// #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).
|
||||
// TY_TAGGED is safe now that tinfofornode folds `(*T | void)`
|
||||
// to 8B (#61 A.3 step 1, mirrors cmd/wcc/check.c:412-426).
|
||||
// - narrow scalars (BOOL/RUNE/I8..I32/U8..U32/ENUM) pad UP to 8 —
|
||||
// cstage's let_emit_size (cmd/w6c/cgen.c:691-720) spills every
|
||||
// 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.
|
||||
// - #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.
|
||||
// #61 audit §1.8 — A.3 / A.4 / A.5 fast-path expansion. Read the
|
||||
// slot-padded width off the populated type-expression node when the
|
||||
// kind matches the cstage natural-size SSoT. Coverage:
|
||||
// - pointer-like (PTR/CHAN/FN), slice, str, tagged: ti.size ===
|
||||
// ti.slotsize (slot already equals natural). TY_TAGGED is safe
|
||||
// now that tinfofornode folds `(*T | void)` to 8B (#61 A.3 step
|
||||
// 1, mirrors cmd/wcc/check.c:412-426).
|
||||
// - narrow scalars (BOOL/RUNE/I8..I32/U8..U32/ENUM/F32) pad UP to
|
||||
// 8 — cstage's let_emit_size (cmd/w6c/cgen.c:691-720) spills
|
||||
// every primitive into an 8B stack slot regardless of
|
||||
// tinfo.size. Pad-to-8 lives at the read site, not in
|
||||
// tinfo.slotsize, so `[N]i32` stride stays 4 (natural) — moving
|
||||
// the pad into ti.slotsize would lift array stride to 8/elem.
|
||||
// - TY_VOID returns 0 (mirrors the TNAME-"void" fallback arm,
|
||||
// same as #61 A.4).
|
||||
// - #61 A.5 adds: TY_STRUCT / TY_TUPLE / TY_ARRAY read ti.slotsize
|
||||
// (slot-padded). tinfofornode populates the slot total mirroring
|
||||
// cgenutil.ww registerstruct (size-derived align, nested struct
|
||||
// fields → si.totsize, final round to 8), and TARRAY threads
|
||||
// stride through sub.slotsize so `[N]Triplet` lifts to padded *
|
||||
// N. `size(T)` stays natural — split SSoT in tinfo.
|
||||
if (typn != nil && typn.type_ != nil) {
|
||||
let ti: *tinfo = typn.type_: *tinfo;
|
||||
let kk: tykind = ti.kind;
|
||||
@@ -1989,8 +1971,11 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
kk == tykind.TY_VOID) {
|
||||
return ti.size: i32;
|
||||
};
|
||||
if (kk == tykind.TY_STRUCT || kk == tykind.TY_TUPLE) {
|
||||
if (ti.slotsize > 0u64) { return ti.slotsize: i32; };
|
||||
};
|
||||
if (kk == tykind.TY_ARRAY) {
|
||||
if (ti.alen > 0u64) { return ti.size: i32; };
|
||||
if (ti.alen > 0u64) { return ti.slotsize: i32; };
|
||||
};
|
||||
if (kk == tykind.TY_BOOL || kk == tykind.TY_RUNE ||
|
||||
kk == tykind.TY_I8 || kk == tykind.TY_I16 ||
|
||||
|
||||
@@ -518,6 +518,26 @@ fn resolvealias(c: *checker, n: *node) *node = {
|
||||
s = scopelookupinmodule(c.cur, head, leaf);
|
||||
} else {
|
||||
s = scopelookup(c.cur, nm);
|
||||
// #61 A.5: bare TNAME that collides with an imported
|
||||
// module bareword. Two shapes hit this:
|
||||
// - `let l: lex;` where `lex` struct lives in
|
||||
// `package lex;` (mod matches leaf).
|
||||
// - `let t: tok;` where `tok` struct lives in
|
||||
// `package lex;` (mod differs from leaf — tok.ww
|
||||
// declares `package lex;`).
|
||||
// scopelookup bucket-walks the flat scope and can land
|
||||
// on the SK_USE entry first; without the fallback we'd
|
||||
// return the unresolved TNAME and tinfofornode aborts on
|
||||
// body == n. scopelookuptype walks the same bucket but
|
||||
// filters on SK_TYPE so the struct entry surfaces
|
||||
// regardless of its declaring package. Mirrors the
|
||||
// bare-vs-qualified pattern from task #57.
|
||||
if (s != nil) {
|
||||
if (s.skind != skind.SK_TYPE) {
|
||||
let sm: *sym = scopelookuptype(c.cur, nm);
|
||||
if (sm != nil) { s = sm; };
|
||||
};
|
||||
};
|
||||
};
|
||||
if (s == nil) { return cur; };
|
||||
if (s.skind != skind.SK_TYPE) { return cur; };
|
||||
@@ -849,6 +869,64 @@ fn foldtointlit(c: *checker, n: *node, v: i64) void = {
|
||||
n.tsuffix = empty;
|
||||
};
|
||||
|
||||
// #61 A.5 helper: per-element slot size when `pt` appears inside a
|
||||
// tuple. Mirrors cgenutil.ww slotsize TTUPLE — cstage's tuple ABI
|
||||
// spills each element into its own register / 8B eightbyte, so narrow
|
||||
// scalars pad to 8 (cgen's let_emit_size + AX:DX:CX positional layout).
|
||||
// str stays 16 (composite primitive), slice 24, pointer/fn/chan 8;
|
||||
// composites contribute their own slot-padded width. void contributes
|
||||
// 0 (never appears in tuples emitted by user code, but kept for SSoT
|
||||
// symmetry with cgen's N_TNAME-"void" fallback arm).
|
||||
fn tupleelemslot(pt: *tinfo) u64 = {
|
||||
if (pt == nil) { return 8u64; };
|
||||
let pk: tykind = pt.kind;
|
||||
if (pk == tykind.TY_VOID) { return 0u64; };
|
||||
if (pk == tykind.TY_STR) { return 16u64; };
|
||||
if (pk == tykind.TY_SLICE) { return 24u64; };
|
||||
if (pk == tykind.TY_PTR || pk == tykind.TY_FN ||
|
||||
pk == tykind.TY_CHAN || pk == tykind.TY_I64 ||
|
||||
pk == tykind.TY_U64 || pk == tykind.TY_INT ||
|
||||
pk == tykind.TY_UINT || pk == tykind.TY_UINTPTR ||
|
||||
pk == tykind.TY_F64) { return 8u64; };
|
||||
if (pk == tykind.TY_BOOL || pk == tykind.TY_RUNE ||
|
||||
pk == tykind.TY_I8 || pk == tykind.TY_I16 ||
|
||||
pk == tykind.TY_I32 || pk == tykind.TY_U8 ||
|
||||
pk == tykind.TY_U16 || pk == tykind.TY_U32 ||
|
||||
pk == tykind.TY_F32 || pk == tykind.TY_ENUM) { return 8u64; };
|
||||
// Composite — struct/tuple/array/tagged carry their own slot total.
|
||||
return pt.slotsize;
|
||||
};
|
||||
|
||||
// #61 A.5 helper: per-field slot size mirroring cgenutil.ww
|
||||
// registerstruct/fieldsize. Nested struct fields contribute their
|
||||
// slot-padded total (si.totsize equivalent); primitives keep their
|
||||
// natural width (struct interior packing is unaffected by stack-slot
|
||||
// pad-to-8); arrays use their slot-padded element-stride * elen.
|
||||
fn fieldslotsize(ft: *tinfo) u64 = {
|
||||
if (ft == nil) { return 8u64; };
|
||||
let fk: tykind = ft.kind;
|
||||
if (fk == tykind.TY_STRUCT) { return ft.slotsize; };
|
||||
if (fk == tykind.TY_ARRAY) { return ft.slotsize; };
|
||||
if (fk == tykind.TY_TAGGED) { return ft.size; };
|
||||
if (fk == tykind.TY_SLICE) { return 24u64; };
|
||||
if (fk == tykind.TY_PTR || fk == tykind.TY_FN ||
|
||||
fk == tykind.TY_CHAN) { return 8u64; };
|
||||
if (fk == tykind.TY_STR) { return 16u64; };
|
||||
// Primitives keep natural width inside structs (matches
|
||||
// cgenutil fieldsize: primsize, not pad-to-8). TY_TUPLE inside a
|
||||
// struct currently defaults to 8 in cgenutil — preserve that
|
||||
// shape until a future graduation aligns the two.
|
||||
if (fk == tykind.TY_BOOL || fk == tykind.TY_RUNE ||
|
||||
fk == tykind.TY_I8 || fk == tykind.TY_I16 ||
|
||||
fk == tykind.TY_I32 || fk == tykind.TY_I64 ||
|
||||
fk == tykind.TY_U8 || fk == tykind.TY_U16 ||
|
||||
fk == tykind.TY_U32 || fk == tykind.TY_U64 ||
|
||||
fk == tykind.TY_INT || fk == tykind.TY_UINT ||
|
||||
fk == tykind.TY_UINTPTR || fk == tykind.TY_F32 ||
|
||||
fk == tykind.TY_F64 || fk == tykind.TY_ENUM) { return ft.size; };
|
||||
return 8u64;
|
||||
};
|
||||
|
||||
// #61 audit §1.8 — resolve a type-expression AST node to its *tinfo.
|
||||
// Mirrors cstage's resolve_type (cmd/wcc/check.c:286-565) which
|
||||
// produces ty_* singletons / arena-allocated composites from a Node*.
|
||||
@@ -940,26 +1018,18 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
// Cstage cmd/wcc/check.c:314-326: length must be an integer
|
||||
// literal (`[_]T` keeps alen=0 as the inferred-length sentinel
|
||||
// patched at letslotsize-time).
|
||||
//
|
||||
// #61 A.5: ti.size = natural (sub.size * elen), ti.slotsize =
|
||||
// slot-padded (sub.slotsize * elen) — typearray handles both.
|
||||
// Reverts A.4's r.size override (which conflated stride with
|
||||
// natural size); the slot-padded stride now lives in slotsize
|
||||
// where cgenutil's fast-path reads it.
|
||||
let elen: u64 = 0u64;
|
||||
if (n.rhs != nil) {
|
||||
if (n.rhs.kind == nkind.N_INTLIT) { elen = n.rhs.uval; };
|
||||
};
|
||||
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
|
||||
@@ -969,6 +1039,7 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
r = newtype(c.a, tykind.TY_FN);
|
||||
r.size = 8u64;
|
||||
r.align = 8u64;
|
||||
r.slotsize = 8u64;
|
||||
tinfocachebind(c.tc, n, r);
|
||||
r.ret = tinfofornode(c, n.lhs);
|
||||
} else { if (k == nkind.N_TENUM) {
|
||||
@@ -983,14 +1054,22 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
r.sub = storage;
|
||||
r.size = storage.size;
|
||||
r.align = storage.align;
|
||||
r.slotsize = storage.size;
|
||||
} else { if (k == nkind.N_TTUPLE) {
|
||||
// Cstage cmd/wcc/check.c:329-345: sum of element sizes with
|
||||
// per-element alignment NOT padded — cstage uses raw sums for
|
||||
// tuples and 8B-rounding lives at the call/return ABI layer.
|
||||
// Pre-bind for cycle protection (recursive tuple shapes).
|
||||
//
|
||||
// #61 A.5: ti.size = natural sum (cstage parity); ti.slotsize
|
||||
// = per-element slot sum mirroring cgenutil.ww:2018-2029
|
||||
// slotsize TTUPLE — narrow scalars pad to 8 (cgen spills each
|
||||
// tuple element into its own register / stack-slot eightbyte),
|
||||
// composites contribute their own ti.slotsize.
|
||||
r = newtype(c.a, tykind.TY_TUPLE);
|
||||
tinfocachebind(c.tc, n, r);
|
||||
let total: u64 = 0u64;
|
||||
let slottotal: u64 = 0u64;
|
||||
let maxal: u64 = 1u64;
|
||||
let p: *node = n.list;
|
||||
for (p != nil) {
|
||||
@@ -998,11 +1077,13 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
if (pt != nil) {
|
||||
if (pt.align > maxal) { maxal = pt.align; };
|
||||
total += pt.size;
|
||||
slottotal += tupleelemslot(pt);
|
||||
};
|
||||
p = p.next;
|
||||
};
|
||||
r.size = total;
|
||||
r.align = maxal;
|
||||
r.slotsize = slottotal;
|
||||
} else { if (k == nkind.N_TSTRUCT) {
|
||||
// Cstage cmd/wcc/check.c:468-527: per-field alignment, max
|
||||
// align for the whole record, total rounded up to alignment.
|
||||
@@ -1016,10 +1097,18 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
// the in-progress stub. r.size is filled in below; the stub's
|
||||
// only consumer during the recursion is typeptr (8B/8B
|
||||
// regardless of pointee size), so partial-fill is safe.
|
||||
//
|
||||
// #61 A.5: alongside the natural layout (cstage parity), walk
|
||||
// the same fields with the slot-padded sizing cgenutil.ww
|
||||
// registerstruct uses (fieldsize → si.totsize for nested
|
||||
// struct; size-derived alignment; final round to 8). That
|
||||
// slot total lands in ti.slotsize so the cgen fast-path can
|
||||
// graduate TY_STRUCT off the AST walker.
|
||||
r = newtype(c.a, tykind.TY_STRUCT);
|
||||
tinfocachebind(c.tc, n, r);
|
||||
let off: u64 = 0u64;
|
||||
let maxalign: u64 = 1u64;
|
||||
let soff: u64 = 0u64;
|
||||
let f: *node = n.list;
|
||||
for (f != nil) {
|
||||
if (f.kind == nkind.N_TFIELD) {
|
||||
@@ -1030,6 +1119,17 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
off = (off + ft.align - 1u64) & ~(ft.align - 1u64);
|
||||
};
|
||||
off += ft.size;
|
||||
// Slot-padded layout (mirror of cgenutil
|
||||
// fieldsize + registerstruct align rules).
|
||||
let fsz: u64 = fieldslotsize(ft);
|
||||
let faln: u64 = 1u64;
|
||||
if (fsz >= 8u64) { faln = 8u64; }
|
||||
else { if (fsz >= 4u64) { faln = 4u64; }
|
||||
else { if (fsz >= 2u64) { faln = 2u64; }; }; };
|
||||
if ((soff & (faln - 1u64)) != 0u64) {
|
||||
soff = (soff + faln - 1u64) & ~(faln - 1u64);
|
||||
};
|
||||
soff += fsz;
|
||||
};
|
||||
};
|
||||
f = f.next;
|
||||
@@ -1038,6 +1138,10 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
r.size = (off + maxalign - 1u64) & ~(maxalign - 1u64);
|
||||
};
|
||||
r.align = maxalign;
|
||||
if ((soff & 7u64) != 0u64) {
|
||||
soff = (soff + 7u64) & ~7u64;
|
||||
};
|
||||
r.slotsize = soff;
|
||||
} else { if (k == nkind.N_TTAGGED) {
|
||||
// Cstage cmd/wcc/check.c:347-435: 8B tag + max(variant)
|
||||
// rounded up to 8. Pre-bind for cycle protection (recursive
|
||||
@@ -1067,6 +1171,7 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
r.size = 8u64;
|
||||
r.align = 8u64;
|
||||
r.nullable = 1;
|
||||
r.slotsize = 8u64;
|
||||
tinfocachebind(c.tc, n, r);
|
||||
return r;
|
||||
};
|
||||
@@ -1086,8 +1191,16 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
|
||||
let pad: u64 = (maxsz + 7u64) & ~7u64;
|
||||
r.size = 8u64 + pad;
|
||||
r.align = al;
|
||||
r.slotsize = 8u64 + pad;
|
||||
};};};};};};};};};};};
|
||||
if (r != nil) { tinfocachebind(c.tc, n, r); };
|
||||
if (r != nil) {
|
||||
// #61 A.5: any arm that didn't set slotsize gets ti.size as
|
||||
// the default (covers primitives via prim() + the ptr/slice/
|
||||
// chan paths which already populate slotsize, plus TBANG which
|
||||
// inherits the inner's tinfo unchanged).
|
||||
if (r.slotsize == 0u64) { r.slotsize = r.size; };
|
||||
tinfocachebind(c.tc, n, r);
|
||||
};
|
||||
return r;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user