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:
2026-05-20 14:30:55 +09:00
parent e37b76710a
commit 9fd79cdc33
6 changed files with 621 additions and 168 deletions

View File

@@ -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

View File

@@ -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;
};