diff --git a/lib/ww/typ.ww b/lib/ww/typ.ww index 5881839d..97603333 100644 --- a/lib/ww/typ.ww +++ b/lib/ww/typ.ww @@ -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, }; diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index a8664c3f..6e24ace7 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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; diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index e46b61da..4213b309 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -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; diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index f992e365..ffe71b1f 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -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; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index a1c218a7..aaa997ee 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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;