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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user