selfhost/cmd/wcc + lib/ww/typ: nullable fold + slot-pad fast-path (Phase A.3)

A.2's slotsize fast-path covered PTR/SLICE/CHAN/FN/STR but bailed on
TAGGED (no nullable fold) and on primitives (cstage let_emit_size pads
to 8B for slot storage; tinfo.size is natural width). Fallback hit
count under wwdump build was 2187. A.3 closes both gaps.

tinfo gains a `nullable: i32` field (fits the existing 4B pad, struct
stays 96B). tinfofornode's N_TTAGGED arm detects `(*T | void)` (exactly
2 variants, one N_TPTR, one bare N_TNAME "void" — aliased or !void-
wrapped void don't match) and folds to size=8, align=8, nullable=1.
Mirrors cmd/wcc/check.c:412-426.

slotsize fast-path re-adds TY_TAGGED (safe now) and gains a primitive-
pad branch: BOOL/RUNE/I8-I64/U8-U64/INT/UINT/UINTPTR/ENUM/F32/F64 →
return 8. Padding lives at the read site; tinfo.size remains a faithful
natural-width SSoT. TUPLE/TSTRUCT/TARRAY deliberately stay on the
fallback because per-field stride is registerstruct.totsize, not
tinfo.size.

Post-A.3 fallback hit count: 134 (94% reduction from A.2's 2187).
Reviewer's per-kind breakdown: N_TNAME 101 (alias-to-struct chains)
+ N_TARRAY 33 (struct-element rounding) account for all remaining
hits. Both A.4 work.

Probes: `(*i32 | void)` byte-identical between stages with the
8B nullable encoding. `(*i32 | nomem)` correctly does NOT fold
(nomem ≠ bare void). `(*i32 | !void)` correctly does NOT fold
(N_TBANG isn't N_TNAME).

131/131 + 994 + 995 + bootstrap byte-identical (ww2==ww3==ww4).
This commit is contained in:
2026-05-20 12:51:22 +09:00
parent a78b26c2d3
commit 82c1948239
5 changed files with 183 additions and 72 deletions

View File

@@ -1009,15 +1009,38 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
r.align = maxalign;
} else { if (k == nkind.N_TTAGGED) {
// Cstage cmd/wcc/check.c:347-435: 8B tag + max(variant)
// rounded up to 8. Variant dedup / never-strip / nullable-fold
// stay in cstage's check.c for now — wwstage cgen only reads
// the size today, and the AST-level pre-fold (astsize's
// TTAGGED arm) already matches the cstage layout numerically
// for the shapes selfhost exercises. Pre-bind for cycle
// protection (recursive sum-type shapes through NAMED
// variants).
// rounded up to 8. Pre-bind for cycle protection (recursive
// sum-type shapes through NAMED variants).
r = newtype(c.a, tykind.TY_TAGGED);
tinfocachebind(c.tc, n, r);
// #61 A.3 nullable fold: `(*T | void)` collapses to a single
// 8B pointer slot, null is the void variant. Mirrors
// cmd/wcc/check.c:412-426 — bare TNAME("void"), not `!void`,
// and not NAMED — so wwstage slotsize fast-path can graduate
// TY_TAGGED off the AST-walker fallback. Match before counting
// variants so the 8B fold lands in tinfo.size directly.
let a: *node = n.list;
if (a != nil) {
let b: *node = a.next;
if (b != nil && b.next == nil) {
let aptr: bool = (a.kind == nkind.N_TPTR);
let bptr: bool = (b.kind == nkind.N_TPTR);
let avoid: bool = (a.kind == nkind.N_TNAME);
if (avoid) { avoid = streq(a.str, "void"); };
let bvoid: bool = (b.kind == nkind.N_TNAME);
if (bvoid) { bvoid = streq(b.str, "void"); };
let isnull: bool = false;
if (aptr) { if (bvoid) { isnull = true; }; };
if (avoid) { if (bptr) { isnull = true; }; };
if (isnull) {
r.size = 8u64;
r.align = 8u64;
r.nullable = 1;
tinfocachebind(c.tc, n, r);
return r;
};
};
};
let maxsz: u64 = 0u64;
let al: u64 = 8u64;
let v: *node = n.list;