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:
@@ -85,6 +85,11 @@ type tinfo = struct {
|
||||
params: *tparam,
|
||||
ret: *tinfo,
|
||||
variadic: i32,
|
||||
nullable: i32, // #61 A.3: TY_TAGGED `(*T | void)` fold collapses to
|
||||
// 8B ptr slot (null is the void variant). Mirrors
|
||||
// cstage Type.nullable (cmd/wcc/ww.h:430-433);
|
||||
// slot sits in variadic's natural pad so amalloc(96)
|
||||
// is unchanged.
|
||||
name: str,
|
||||
under: *tinfo,
|
||||
};
|
||||
|
||||
@@ -6511,6 +6511,11 @@ type tinfo = struct {
|
||||
params: *tparam,
|
||||
ret: *tinfo,
|
||||
variadic: i32,
|
||||
nullable: i32, // #61 A.3: TY_TAGGED `(*T | void)` fold collapses to
|
||||
// 8B ptr slot (null is the void variant). Mirrors
|
||||
// cstage Type.nullable (cmd/wcc/ww.h:430-433);
|
||||
// slot sits in variadic's natural pad so amalloc(96)
|
||||
// is unchanged.
|
||||
name: str,
|
||||
under: *tinfo,
|
||||
};
|
||||
@@ -8037,15 +8042,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;
|
||||
@@ -10919,30 +10947,39 @@ export fn letslotsize(c: *cgen, n: *node) i32 = {
|
||||
};
|
||||
|
||||
fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
// #61 audit §1.8 — A.2 graduation: read tinfo.size off the populated
|
||||
// type-expression node when its kind matches the cstage natural-size
|
||||
// SSoT. Filtered set covers shapes whose tinfo.size already encodes
|
||||
// the cgen slot-size contract: pointer-like (PTR/CHAN/FN), slice
|
||||
// (SLICE), and str (STR). Other kinds flow through the fallback
|
||||
// walker. TAGGED stays out because cstage's resolve_type folds
|
||||
// `(*T | void)` to a single 8B pointer (cmd/wcc/check.c:412-426)
|
||||
// but tinfofornode's TTAGGED arm doesn't yet — graduating TAGGED
|
||||
// would shrink that fold's slot from 16 to 8 on the wwstage side.
|
||||
// Primitive scalars + enums + inline TUPLE / TSTRUCT also keep
|
||||
// flowing through the fallback walker because cgen's slot-pad-to-8
|
||||
// contract (cmd/w6c/cgen.c let_emit_size:691-720 and the per-field
|
||||
// slot rounding in registerstruct/letslotsize) lives there, not in
|
||||
// tinfo.size. Subsequent sub-commits collapse the remaining shapes
|
||||
// onto the same pivot once cstage parity catches up (#61 Phase A —
|
||||
// see the audit doc for the staged plan).
|
||||
// #61 audit §1.8 — A.3 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.
|
||||
// TUPLE / TSTRUCT / TARRAY still flow through the AST-walker fallback
|
||||
// because cgen's per-field stride contract (registerstruct.totsize
|
||||
// rounding, [N]T element-size walk) lives there, not in tinfo.size.
|
||||
if (typn != nil && typn.type_ != nil) {
|
||||
let ti: *tinfo = typn.type_: *tinfo;
|
||||
let kk: tykind = ti.kind;
|
||||
if (kk == tykind.TY_PTR || kk == tykind.TY_SLICE ||
|
||||
kk == tykind.TY_CHAN || kk == tykind.TY_FN ||
|
||||
kk == tykind.TY_STR) {
|
||||
kk == tykind.TY_STR || kk == tykind.TY_TAGGED) {
|
||||
return ti.size: i32;
|
||||
};
|
||||
if (kk == tykind.TY_BOOL || kk == tykind.TY_RUNE ||
|
||||
kk == tykind.TY_I8 || kk == tykind.TY_I16 ||
|
||||
kk == tykind.TY_I32 || kk == tykind.TY_I64 ||
|
||||
kk == tykind.TY_U8 || kk == tykind.TY_U16 ||
|
||||
kk == tykind.TY_U32 || kk == tykind.TY_U64 ||
|
||||
kk == tykind.TY_INT || kk == tykind.TY_UINT ||
|
||||
kk == tykind.TY_UINTPTR || kk == tykind.TY_ENUM ||
|
||||
kk == tykind.TY_F32 || kk == tykind.TY_F64) {
|
||||
return 8;
|
||||
};
|
||||
};
|
||||
if (typn == nil) { return 8; };
|
||||
let k: nkind = typn.kind;
|
||||
|
||||
@@ -1941,30 +1941,39 @@ export fn letslotsize(c: *cgen, n: *node) i32 = {
|
||||
};
|
||||
|
||||
fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
// #61 audit §1.8 — A.2 graduation: read tinfo.size off the populated
|
||||
// type-expression node when its kind matches the cstage natural-size
|
||||
// SSoT. Filtered set covers shapes whose tinfo.size already encodes
|
||||
// the cgen slot-size contract: pointer-like (PTR/CHAN/FN), slice
|
||||
// (SLICE), and str (STR). Other kinds flow through the fallback
|
||||
// walker. TAGGED stays out because cstage's resolve_type folds
|
||||
// `(*T | void)` to a single 8B pointer (cmd/wcc/check.c:412-426)
|
||||
// but tinfofornode's TTAGGED arm doesn't yet — graduating TAGGED
|
||||
// would shrink that fold's slot from 16 to 8 on the wwstage side.
|
||||
// Primitive scalars + enums + inline TUPLE / TSTRUCT also keep
|
||||
// flowing through the fallback walker because cgen's slot-pad-to-8
|
||||
// contract (cmd/w6c/cgen.c let_emit_size:691-720 and the per-field
|
||||
// slot rounding in registerstruct/letslotsize) lives there, not in
|
||||
// tinfo.size. Subsequent sub-commits collapse the remaining shapes
|
||||
// onto the same pivot once cstage parity catches up (#61 Phase A —
|
||||
// see the audit doc for the staged plan).
|
||||
// #61 audit §1.8 — A.3 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.
|
||||
// TUPLE / TSTRUCT / TARRAY still flow through the AST-walker fallback
|
||||
// because cgen's per-field stride contract (registerstruct.totsize
|
||||
// rounding, [N]T element-size walk) lives there, not in tinfo.size.
|
||||
if (typn != nil && typn.type_ != nil) {
|
||||
let ti: *tinfo = typn.type_: *tinfo;
|
||||
let kk: tykind = ti.kind;
|
||||
if (kk == tykind.TY_PTR || kk == tykind.TY_SLICE ||
|
||||
kk == tykind.TY_CHAN || kk == tykind.TY_FN ||
|
||||
kk == tykind.TY_STR) {
|
||||
kk == tykind.TY_STR || kk == tykind.TY_TAGGED) {
|
||||
return ti.size: i32;
|
||||
};
|
||||
if (kk == tykind.TY_BOOL || kk == tykind.TY_RUNE ||
|
||||
kk == tykind.TY_I8 || kk == tykind.TY_I16 ||
|
||||
kk == tykind.TY_I32 || kk == tykind.TY_I64 ||
|
||||
kk == tykind.TY_U8 || kk == tykind.TY_U16 ||
|
||||
kk == tykind.TY_U32 || kk == tykind.TY_U64 ||
|
||||
kk == tykind.TY_INT || kk == tykind.TY_UINT ||
|
||||
kk == tykind.TY_UINTPTR || kk == tykind.TY_ENUM ||
|
||||
kk == tykind.TY_F32 || kk == tykind.TY_F64) {
|
||||
return 8;
|
||||
};
|
||||
};
|
||||
if (typn == nil) { return 8; };
|
||||
let k: nkind = typn.kind;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -6511,6 +6511,11 @@ type tinfo = struct {
|
||||
params: *tparam,
|
||||
ret: *tinfo,
|
||||
variadic: i32,
|
||||
nullable: i32, // #61 A.3: TY_TAGGED `(*T | void)` fold collapses to
|
||||
// 8B ptr slot (null is the void variant). Mirrors
|
||||
// cstage Type.nullable (cmd/wcc/ww.h:430-433);
|
||||
// slot sits in variadic's natural pad so amalloc(96)
|
||||
// is unchanged.
|
||||
name: str,
|
||||
under: *tinfo,
|
||||
};
|
||||
@@ -8037,15 +8042,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;
|
||||
@@ -10919,30 +10947,39 @@ export fn letslotsize(c: *cgen, n: *node) i32 = {
|
||||
};
|
||||
|
||||
fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
// #61 audit §1.8 — A.2 graduation: read tinfo.size off the populated
|
||||
// type-expression node when its kind matches the cstage natural-size
|
||||
// SSoT. Filtered set covers shapes whose tinfo.size already encodes
|
||||
// the cgen slot-size contract: pointer-like (PTR/CHAN/FN), slice
|
||||
// (SLICE), and str (STR). Other kinds flow through the fallback
|
||||
// walker. TAGGED stays out because cstage's resolve_type folds
|
||||
// `(*T | void)` to a single 8B pointer (cmd/wcc/check.c:412-426)
|
||||
// but tinfofornode's TTAGGED arm doesn't yet — graduating TAGGED
|
||||
// would shrink that fold's slot from 16 to 8 on the wwstage side.
|
||||
// Primitive scalars + enums + inline TUPLE / TSTRUCT also keep
|
||||
// flowing through the fallback walker because cgen's slot-pad-to-8
|
||||
// contract (cmd/w6c/cgen.c let_emit_size:691-720 and the per-field
|
||||
// slot rounding in registerstruct/letslotsize) lives there, not in
|
||||
// tinfo.size. Subsequent sub-commits collapse the remaining shapes
|
||||
// onto the same pivot once cstage parity catches up (#61 Phase A —
|
||||
// see the audit doc for the staged plan).
|
||||
// #61 audit §1.8 — A.3 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.
|
||||
// TUPLE / TSTRUCT / TARRAY still flow through the AST-walker fallback
|
||||
// because cgen's per-field stride contract (registerstruct.totsize
|
||||
// rounding, [N]T element-size walk) lives there, not in tinfo.size.
|
||||
if (typn != nil && typn.type_ != nil) {
|
||||
let ti: *tinfo = typn.type_: *tinfo;
|
||||
let kk: tykind = ti.kind;
|
||||
if (kk == tykind.TY_PTR || kk == tykind.TY_SLICE ||
|
||||
kk == tykind.TY_CHAN || kk == tykind.TY_FN ||
|
||||
kk == tykind.TY_STR) {
|
||||
kk == tykind.TY_STR || kk == tykind.TY_TAGGED) {
|
||||
return ti.size: i32;
|
||||
};
|
||||
if (kk == tykind.TY_BOOL || kk == tykind.TY_RUNE ||
|
||||
kk == tykind.TY_I8 || kk == tykind.TY_I16 ||
|
||||
kk == tykind.TY_I32 || kk == tykind.TY_I64 ||
|
||||
kk == tykind.TY_U8 || kk == tykind.TY_U16 ||
|
||||
kk == tykind.TY_U32 || kk == tykind.TY_U64 ||
|
||||
kk == tykind.TY_INT || kk == tykind.TY_UINT ||
|
||||
kk == tykind.TY_UINTPTR || kk == tykind.TY_ENUM ||
|
||||
kk == tykind.TY_F32 || kk == tykind.TY_F64) {
|
||||
return 8;
|
||||
};
|
||||
};
|
||||
if (typn == nil) { return 8; };
|
||||
let k: nkind = typn.kind;
|
||||
|
||||
Reference in New Issue
Block a user