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:
@@ -105,6 +105,35 @@ export fn scopelookup(s: *scope, name: str) *sym = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// scopelookuptype — find an SK_TYPE entry by name regardless of mod.
|
||||
//
|
||||
// Same FNV bucket + hashnext chain + parent walk as scopelookup, with
|
||||
// an `skind == SK_TYPE` filter. Used to disambiguate the bare-TNAME
|
||||
// vs imported-module-bareword collision: when scopelookup returns the
|
||||
// SK_USE sym for a leaf that ALSO names a type (e.g. `tok` struct
|
||||
// declared in lib/ww/lex/tok.ww with `package lex;` while
|
||||
// `import tok;` registers a same-name SK_USE), the resolver needs
|
||||
// the type entry regardless of its declared package — the struct's
|
||||
// mod may differ from the leaf (lex/tok pair) so
|
||||
// scopelookupinmodule(c, leaf, leaf) won't find it.
|
||||
//
|
||||
// Mirrors the bare-vs-qualified disambiguation pattern from task #57.
|
||||
export fn scopelookuptype(s: *scope, name: str) *sym = {
|
||||
for (s != nil) {
|
||||
let h: u64 = hashstr(name);
|
||||
let bi: i32 = (h % (s.nbuckets: u64)): i32;
|
||||
let b: *sym = s.buckets[bi];
|
||||
for (b != nil) {
|
||||
if (streq(b.name, name)) {
|
||||
if (b.skind == skind.SK_TYPE) { return b; };
|
||||
};
|
||||
b = b.hashnext;
|
||||
};
|
||||
s = s.parent;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// scopelookupinmodule — module-filtered chain walk.
|
||||
//
|
||||
// Same FNV bucket + hashnext chain + parent walk as scopelookup, plus
|
||||
|
||||
@@ -92,6 +92,18 @@ type tinfo = struct {
|
||||
// is unchanged.
|
||||
name: str,
|
||||
under: *tinfo,
|
||||
slotsize: u64, // #61 A.5: stack-slot SSoT split from `size`.
|
||||
// `size` stays natural (Hare-faithful);
|
||||
// `slotsize` carries the slot-padded width
|
||||
// cgen's let/struct-field layout demands.
|
||||
// For primitives/ptr/slice/chan/fn/str/tagged
|
||||
// `slotsize == size`; struct + tuple + array
|
||||
// of struct diverge — see check.ww tinfo-
|
||||
// fornode + cgenutil.ww registerstruct.
|
||||
// Pad-to-8 of narrow primitives in let slots
|
||||
// still lives at slotsize()'s read site;
|
||||
// graduating it here would break `[N]i32`
|
||||
// stride (4*N stays natural).
|
||||
};
|
||||
|
||||
// #61 audit §1.8 / Rob+Drew convergence 2026-05-20: memoizes
|
||||
@@ -140,7 +152,9 @@ type tctx = struct {
|
||||
// ---- constructors -----------------------------------------------------
|
||||
|
||||
export fn newtype(a: *arena, k: tykind) *tinfo = {
|
||||
let t: *tinfo = amalloc(a, 96u64): *tinfo;
|
||||
// #61 A.5: grew tinfo by slotsize: u64 (96 → 104). Over-size to 112
|
||||
// per the bootstrap amalloc-undersize trap (selfhost/CLAUDE.md §1).
|
||||
let t: *tinfo = amalloc(a, 112u64): *tinfo;
|
||||
t.kind = k;
|
||||
return t;
|
||||
};
|
||||
@@ -150,6 +164,7 @@ fn prim(a: *arena, k: tykind, nm: str, sz: u64, al: u64) *tinfo = {
|
||||
t.name = nm;
|
||||
t.size = sz;
|
||||
if (al > 0u64) { t.align = al; } else { t.align = sz; };
|
||||
t.slotsize = sz;
|
||||
return t;
|
||||
};
|
||||
|
||||
@@ -188,6 +203,7 @@ export fn typeptr(a: *arena, sub: *tinfo) *tinfo = {
|
||||
t.sub = sub;
|
||||
t.size = 8u64;
|
||||
t.align = 8u64;
|
||||
t.slotsize = 8u64;
|
||||
return t;
|
||||
};
|
||||
|
||||
@@ -196,6 +212,7 @@ export fn typeslice(a: *arena, sub: *tinfo) *tinfo = {
|
||||
t.sub = sub;
|
||||
t.size = 24u64;
|
||||
t.align = 8u64;
|
||||
t.slotsize = 24u64;
|
||||
return t;
|
||||
};
|
||||
|
||||
@@ -206,6 +223,11 @@ export fn typearray(a: *arena, sub: *tinfo, n: u64) *tinfo = {
|
||||
if (sub != nil) {
|
||||
t.size = sub.size * n;
|
||||
t.align = sub.align;
|
||||
// #61 A.5: ti.slotsize = stride * elen using the element's
|
||||
// slot-padded width. Primitives have slotsize == size so
|
||||
// `[N]i32` stride stays 4 (natural); structs have padded
|
||||
// slotsize so `[N]Triplet` stride lifts to 16.
|
||||
t.slotsize = sub.slotsize * n;
|
||||
} else {
|
||||
t.align = 1u64;
|
||||
};
|
||||
@@ -217,6 +239,7 @@ export fn typechan(a: *arena, sub: *tinfo) *tinfo = {
|
||||
t.sub = sub;
|
||||
t.size = 8u64;
|
||||
t.align = 8u64;
|
||||
t.slotsize = 8u64;
|
||||
return t;
|
||||
};
|
||||
|
||||
@@ -227,6 +250,7 @@ export fn typenamed(a: *arena, name: str, under: *tinfo) *tinfo = {
|
||||
if (under != nil) {
|
||||
t.size = under.size;
|
||||
t.align = under.align;
|
||||
t.slotsize = under.slotsize;
|
||||
};
|
||||
return t;
|
||||
};
|
||||
|
||||
@@ -6518,6 +6518,18 @@ type tinfo = struct {
|
||||
// is unchanged.
|
||||
name: str,
|
||||
under: *tinfo,
|
||||
slotsize: u64, // #61 A.5: stack-slot SSoT split from `size`.
|
||||
// `size` stays natural (Hare-faithful);
|
||||
// `slotsize` carries the slot-padded width
|
||||
// cgen's let/struct-field layout demands.
|
||||
// For primitives/ptr/slice/chan/fn/str/tagged
|
||||
// `slotsize == size`; struct + tuple + array
|
||||
// of struct diverge — see check.ww tinfo-
|
||||
// fornode + cgenutil.ww registerstruct.
|
||||
// Pad-to-8 of narrow primitives in let slots
|
||||
// still lives at slotsize()'s read site;
|
||||
// graduating it here would break `[N]i32`
|
||||
// stride (4*N stays natural).
|
||||
};
|
||||
|
||||
// #61 audit §1.8 / Rob+Drew convergence 2026-05-20: memoizes
|
||||
@@ -6566,7 +6578,9 @@ type tctx = struct {
|
||||
// ---- constructors -----------------------------------------------------
|
||||
|
||||
export fn newtype(a: *arena, k: tykind) *tinfo = {
|
||||
let t: *tinfo = amalloc(a, 96u64): *tinfo;
|
||||
// #61 A.5: grew tinfo by slotsize: u64 (96 → 104). Over-size to 112
|
||||
// per the bootstrap amalloc-undersize trap (selfhost/CLAUDE.md §1).
|
||||
let t: *tinfo = amalloc(a, 112u64): *tinfo;
|
||||
t.kind = k;
|
||||
return t;
|
||||
};
|
||||
@@ -6576,6 +6590,7 @@ fn prim(a: *arena, k: tykind, nm: str, sz: u64, al: u64) *tinfo = {
|
||||
t.name = nm;
|
||||
t.size = sz;
|
||||
if (al > 0u64) { t.align = al; } else { t.align = sz; };
|
||||
t.slotsize = sz;
|
||||
return t;
|
||||
};
|
||||
|
||||
@@ -6614,6 +6629,7 @@ export fn typeptr(a: *arena, sub: *tinfo) *tinfo = {
|
||||
t.sub = sub;
|
||||
t.size = 8u64;
|
||||
t.align = 8u64;
|
||||
t.slotsize = 8u64;
|
||||
return t;
|
||||
};
|
||||
|
||||
@@ -6622,6 +6638,7 @@ export fn typeslice(a: *arena, sub: *tinfo) *tinfo = {
|
||||
t.sub = sub;
|
||||
t.size = 24u64;
|
||||
t.align = 8u64;
|
||||
t.slotsize = 24u64;
|
||||
return t;
|
||||
};
|
||||
|
||||
@@ -6632,6 +6649,11 @@ export fn typearray(a: *arena, sub: *tinfo, n: u64) *tinfo = {
|
||||
if (sub != nil) {
|
||||
t.size = sub.size * n;
|
||||
t.align = sub.align;
|
||||
// #61 A.5: ti.slotsize = stride * elen using the element's
|
||||
// slot-padded width. Primitives have slotsize == size so
|
||||
// `[N]i32` stride stays 4 (natural); structs have padded
|
||||
// slotsize so `[N]Triplet` stride lifts to 16.
|
||||
t.slotsize = sub.slotsize * n;
|
||||
} else {
|
||||
t.align = 1u64;
|
||||
};
|
||||
@@ -6643,6 +6665,7 @@ export fn typechan(a: *arena, sub: *tinfo) *tinfo = {
|
||||
t.sub = sub;
|
||||
t.size = 8u64;
|
||||
t.align = 8u64;
|
||||
t.slotsize = 8u64;
|
||||
return t;
|
||||
};
|
||||
|
||||
@@ -6653,6 +6676,7 @@ export fn typenamed(a: *arena, name: str, under: *tinfo) *tinfo = {
|
||||
if (under != nil) {
|
||||
t.size = under.size;
|
||||
t.align = under.align;
|
||||
t.slotsize = under.slotsize;
|
||||
};
|
||||
return t;
|
||||
};
|
||||
@@ -6914,6 +6938,35 @@ export fn scopelookup(s: *scope, name: str) *sym = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// scopelookuptype — find an SK_TYPE entry by name regardless of mod.
|
||||
//
|
||||
// Same FNV bucket + hashnext chain + parent walk as scopelookup, with
|
||||
// an `skind == SK_TYPE` filter. Used to disambiguate the bare-TNAME
|
||||
// vs imported-module-bareword collision: when scopelookup returns the
|
||||
// SK_USE sym for a leaf that ALSO names a type (e.g. `tok` struct
|
||||
// declared in lib/ww/lex/tok.ww with `package lex;` while
|
||||
// `import tok;` registers a same-name SK_USE), the resolver needs
|
||||
// the type entry regardless of its declared package — the struct's
|
||||
// mod may differ from the leaf (lex/tok pair) so
|
||||
// scopelookupinmodule(c, leaf, leaf) won't find it.
|
||||
//
|
||||
// Mirrors the bare-vs-qualified disambiguation pattern from task #57.
|
||||
export fn scopelookuptype(s: *scope, name: str) *sym = {
|
||||
for (s != nil) {
|
||||
let h: u64 = hashstr(name);
|
||||
let bi: i32 = (h % (s.nbuckets: u64)): i32;
|
||||
let b: *sym = s.buckets[bi];
|
||||
for (b != nil) {
|
||||
if (streq(b.name, name)) {
|
||||
if (b.skind == skind.SK_TYPE) { return b; };
|
||||
};
|
||||
b = b.hashnext;
|
||||
};
|
||||
s = s.parent;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// scopelookupinmodule — module-filtered chain walk.
|
||||
//
|
||||
// Same FNV bucket + hashnext chain + parent walk as scopelookup, plus
|
||||
@@ -7551,6 +7604,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; };
|
||||
@@ -7882,6 +7955,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*.
|
||||
@@ -7973,26 +8104,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
|
||||
@@ -8002,6 +8125,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) {
|
||||
@@ -8016,14 +8140,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) {
|
||||
@@ -8031,11 +8163,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.
|
||||
@@ -8049,10 +8183,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) {
|
||||
@@ -8063,6 +8205,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;
|
||||
@@ -8071,6 +8224,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
|
||||
@@ -8100,6 +8257,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;
|
||||
};
|
||||
@@ -8119,8 +8277,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;
|
||||
};
|
||||
|
||||
@@ -10978,45 +11144,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;
|
||||
@@ -11026,8 +11174,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 ||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -6518,6 +6518,18 @@ type tinfo = struct {
|
||||
// is unchanged.
|
||||
name: str,
|
||||
under: *tinfo,
|
||||
slotsize: u64, // #61 A.5: stack-slot SSoT split from `size`.
|
||||
// `size` stays natural (Hare-faithful);
|
||||
// `slotsize` carries the slot-padded width
|
||||
// cgen's let/struct-field layout demands.
|
||||
// For primitives/ptr/slice/chan/fn/str/tagged
|
||||
// `slotsize == size`; struct + tuple + array
|
||||
// of struct diverge — see check.ww tinfo-
|
||||
// fornode + cgenutil.ww registerstruct.
|
||||
// Pad-to-8 of narrow primitives in let slots
|
||||
// still lives at slotsize()'s read site;
|
||||
// graduating it here would break `[N]i32`
|
||||
// stride (4*N stays natural).
|
||||
};
|
||||
|
||||
// #61 audit §1.8 / Rob+Drew convergence 2026-05-20: memoizes
|
||||
@@ -6566,7 +6578,9 @@ type tctx = struct {
|
||||
// ---- constructors -----------------------------------------------------
|
||||
|
||||
export fn newtype(a: *arena, k: tykind) *tinfo = {
|
||||
let t: *tinfo = amalloc(a, 96u64): *tinfo;
|
||||
// #61 A.5: grew tinfo by slotsize: u64 (96 → 104). Over-size to 112
|
||||
// per the bootstrap amalloc-undersize trap (selfhost/CLAUDE.md §1).
|
||||
let t: *tinfo = amalloc(a, 112u64): *tinfo;
|
||||
t.kind = k;
|
||||
return t;
|
||||
};
|
||||
@@ -6576,6 +6590,7 @@ fn prim(a: *arena, k: tykind, nm: str, sz: u64, al: u64) *tinfo = {
|
||||
t.name = nm;
|
||||
t.size = sz;
|
||||
if (al > 0u64) { t.align = al; } else { t.align = sz; };
|
||||
t.slotsize = sz;
|
||||
return t;
|
||||
};
|
||||
|
||||
@@ -6614,6 +6629,7 @@ export fn typeptr(a: *arena, sub: *tinfo) *tinfo = {
|
||||
t.sub = sub;
|
||||
t.size = 8u64;
|
||||
t.align = 8u64;
|
||||
t.slotsize = 8u64;
|
||||
return t;
|
||||
};
|
||||
|
||||
@@ -6622,6 +6638,7 @@ export fn typeslice(a: *arena, sub: *tinfo) *tinfo = {
|
||||
t.sub = sub;
|
||||
t.size = 24u64;
|
||||
t.align = 8u64;
|
||||
t.slotsize = 24u64;
|
||||
return t;
|
||||
};
|
||||
|
||||
@@ -6632,6 +6649,11 @@ export fn typearray(a: *arena, sub: *tinfo, n: u64) *tinfo = {
|
||||
if (sub != nil) {
|
||||
t.size = sub.size * n;
|
||||
t.align = sub.align;
|
||||
// #61 A.5: ti.slotsize = stride * elen using the element's
|
||||
// slot-padded width. Primitives have slotsize == size so
|
||||
// `[N]i32` stride stays 4 (natural); structs have padded
|
||||
// slotsize so `[N]Triplet` stride lifts to 16.
|
||||
t.slotsize = sub.slotsize * n;
|
||||
} else {
|
||||
t.align = 1u64;
|
||||
};
|
||||
@@ -6643,6 +6665,7 @@ export fn typechan(a: *arena, sub: *tinfo) *tinfo = {
|
||||
t.sub = sub;
|
||||
t.size = 8u64;
|
||||
t.align = 8u64;
|
||||
t.slotsize = 8u64;
|
||||
return t;
|
||||
};
|
||||
|
||||
@@ -6653,6 +6676,7 @@ export fn typenamed(a: *arena, name: str, under: *tinfo) *tinfo = {
|
||||
if (under != nil) {
|
||||
t.size = under.size;
|
||||
t.align = under.align;
|
||||
t.slotsize = under.slotsize;
|
||||
};
|
||||
return t;
|
||||
};
|
||||
@@ -6914,6 +6938,35 @@ export fn scopelookup(s: *scope, name: str) *sym = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// scopelookuptype — find an SK_TYPE entry by name regardless of mod.
|
||||
//
|
||||
// Same FNV bucket + hashnext chain + parent walk as scopelookup, with
|
||||
// an `skind == SK_TYPE` filter. Used to disambiguate the bare-TNAME
|
||||
// vs imported-module-bareword collision: when scopelookup returns the
|
||||
// SK_USE sym for a leaf that ALSO names a type (e.g. `tok` struct
|
||||
// declared in lib/ww/lex/tok.ww with `package lex;` while
|
||||
// `import tok;` registers a same-name SK_USE), the resolver needs
|
||||
// the type entry regardless of its declared package — the struct's
|
||||
// mod may differ from the leaf (lex/tok pair) so
|
||||
// scopelookupinmodule(c, leaf, leaf) won't find it.
|
||||
//
|
||||
// Mirrors the bare-vs-qualified disambiguation pattern from task #57.
|
||||
export fn scopelookuptype(s: *scope, name: str) *sym = {
|
||||
for (s != nil) {
|
||||
let h: u64 = hashstr(name);
|
||||
let bi: i32 = (h % (s.nbuckets: u64)): i32;
|
||||
let b: *sym = s.buckets[bi];
|
||||
for (b != nil) {
|
||||
if (streq(b.name, name)) {
|
||||
if (b.skind == skind.SK_TYPE) { return b; };
|
||||
};
|
||||
b = b.hashnext;
|
||||
};
|
||||
s = s.parent;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// scopelookupinmodule — module-filtered chain walk.
|
||||
//
|
||||
// Same FNV bucket + hashnext chain + parent walk as scopelookup, plus
|
||||
@@ -7551,6 +7604,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; };
|
||||
@@ -7882,6 +7955,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*.
|
||||
@@ -7973,26 +8104,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
|
||||
@@ -8002,6 +8125,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) {
|
||||
@@ -8016,14 +8140,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) {
|
||||
@@ -8031,11 +8163,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.
|
||||
@@ -8049,10 +8183,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) {
|
||||
@@ -8063,6 +8205,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;
|
||||
@@ -8071,6 +8224,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
|
||||
@@ -8100,6 +8257,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;
|
||||
};
|
||||
@@ -8119,8 +8277,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;
|
||||
};
|
||||
|
||||
@@ -10978,45 +11144,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;
|
||||
@@ -11026,8 +11174,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 ||
|
||||
|
||||
Reference in New Issue
Block a user