selfhost/cmd/wcc/check: populate TY_TAGGED.params (#50 phase 1, A.6.3f-a)

Phase 1 of Rob's two-phase A.6.3f pattern: populate the variant
chain in `tinfofornode`'s TTAGGED arm so phase 2 (#50b) can retire
`nullable_ptr_tag`'s AST-keyed walk in cgenutil onto a tinfo read.
Per b8e5a92 (A.6.3b) commit body: "nullableptrtag stays AST-keyed
for now — tinfofornode doesn't populate TY_TAGGED.params … so the
tinfo equivalent of cstage cgen.c:405 nullable_ptr_tag can't read
params today."

Restructure the TTAGGED arm into a single pre-pass: walk `n.list`,
resolve each variant via tinfofornode, alloc `tparam{name="",
type_=vt, tnext=nil}`, link head/tail, accumulate maxsz + al
inline. Set `r.params = head` after the loop. AST-level nullable
fold runs after, before size assignment — kept AST-keyed (not
ported to cstage's tinfo-level `kind==TY_VOID && !iserror` check)
because wwstage tinfo carries no `iserror` field; that's an honest
data-shape divergence (filed in passing as part of #13's TTAGGED
normalization arc).

Mirrors cstage cmd/wcc/check.c:347-435 — same head/tail append-
list construction, same Tparam reuse across struct-fields /
tuple-fields / fn-params / tagged-variants (sea-of-stars per
rule 12 — one record, four consumers, no per-kind variant of
the param node). Diverges from harec's array+id-sort at
ref/harec/include/types.h:128-132 / ref/harec/src/type_store.c:
431-432; rule 10 anchors wwstage byte-id to cstage, not harec.

Purely additive: ti.params has zero readers on TY_TAGGED today
(typeeq walks params only for TY_FN/TY_TUPLE; cgenutil's
nullableptrtag is still AST-keyed; #50b will consume). Byte-
identity (994/995) unchanged at 133/133 — pre-impl risk audit
by ken-thompson came back zero, confirmed by full make test.

A latent divergence wwstage doesn't cover (never-drop /
...spread / dedup / single-variant collapse — see cstage type
set normalization at check.c:393-432) is pre-existing and out
of #50's scope; tracked as #13. Phase 2 nullableptrtag is a
linear walk for the first TY_PTR variant in a 2-variant
nullable, indifferent to ordering and dedup, so it does not
need #13 closed first.

Net +12 LOC per file across check.ww + two .combined.ww
bundler regens.
This commit is contained in:
2026-05-23 01:51:29 +09:00
parent 883665e962
commit 26724feefb
3 changed files with 81 additions and 45 deletions

View File

@@ -8369,12 +8369,36 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
// sum-type shapes through NAMED variants).
r = newtype(tykind.TY_TAGGED);
tinfocachebind(c.tc, n, r);
// #50 / A.6.3f phase 1: populate ti.params as a tparam linked
// list (head=first source variant) in lock-step with the
// size/align accumulator. Mirrors cstage check.c:374-396 —
// same Tparam shape ww reuses across struct-fields / tuple-
// fields / fn-params / tagged-variants (sea-of-stars per rule
// 12). Phase 2 (#50b) retires cgenutil's AST-keyed
// nullableptrtag onto the chain.
let head: *tparam = nil;
let tail: *tparam = nil;
let maxsz: u64 = 0u64;
let al: u64 = 8u64;
let v: *node = n.list;
for (v != nil) {
let vt: *tinfo = tinfofornode(c, v);
let tp: *tparam = alloc(tparam{name="", type_=vt, tnext=nil})!;
if (head == nil) { head = tp; } else { tail.tnext = tp; };
tail = tp;
if (vt != nil) {
if (vt.size > maxsz) { maxsz = vt.size; };
if (vt.align > al) { al = vt.align; };
};
v = v.next;
};
r.params = head;
// #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.
// and not NAMED. AST-kind discrimination retained: wwstage
// tinfo carries no `iserror` field, so cstage's tinfo-level
// (kind==TY_VOID && !iserror) check doesn't port symmetrically.
let a: *node = n.list;
if (a != nil) {
let b: *node = a.next;
@@ -8393,22 +8417,10 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
r.align = 8u64;
r.nullable = 1;
r.slotsize = 8u64;
tinfocachebind(c.tc, n, r);
return r;
};
};
};
let maxsz: u64 = 0u64;
let al: u64 = 8u64;
let v: *node = n.list;
for (v != nil) {
let vt: *tinfo = tinfofornode(c, v);
if (vt != nil) {
if (vt.size > maxsz) { maxsz = vt.size; };
if (vt.align > al) { al = vt.align; };
};
v = v.next;
};
let pad: u64 = (maxsz + 7u64) & ~7u64;
r.size = 8u64 + pad;
r.align = al;

View File

@@ -1299,12 +1299,36 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
// sum-type shapes through NAMED variants).
r = newtype(tykind.TY_TAGGED);
tinfocachebind(c.tc, n, r);
// #50 / A.6.3f phase 1: populate ti.params as a tparam linked
// list (head=first source variant) in lock-step with the
// size/align accumulator. Mirrors cstage check.c:374-396 —
// same Tparam shape ww reuses across struct-fields / tuple-
// fields / fn-params / tagged-variants (sea-of-stars per rule
// 12). Phase 2 (#50b) retires cgenutil's AST-keyed
// nullableptrtag onto the chain.
let head: *tparam = nil;
let tail: *tparam = nil;
let maxsz: u64 = 0u64;
let al: u64 = 8u64;
let v: *node = n.list;
for (v != nil) {
let vt: *tinfo = tinfofornode(c, v);
let tp: *tparam = alloc(tparam{name="", type_=vt, tnext=nil})!;
if (head == nil) { head = tp; } else { tail.tnext = tp; };
tail = tp;
if (vt != nil) {
if (vt.size > maxsz) { maxsz = vt.size; };
if (vt.align > al) { al = vt.align; };
};
v = v.next;
};
r.params = head;
// #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.
// and not NAMED. AST-kind discrimination retained: wwstage
// tinfo carries no `iserror` field, so cstage's tinfo-level
// (kind==TY_VOID && !iserror) check doesn't port symmetrically.
let a: *node = n.list;
if (a != nil) {
let b: *node = a.next;
@@ -1323,22 +1347,10 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
r.align = 8u64;
r.nullable = 1;
r.slotsize = 8u64;
tinfocachebind(c.tc, n, r);
return r;
};
};
};
let maxsz: u64 = 0u64;
let al: u64 = 8u64;
let v: *node = n.list;
for (v != nil) {
let vt: *tinfo = tinfofornode(c, v);
if (vt != nil) {
if (vt.size > maxsz) { maxsz = vt.size; };
if (vt.align > al) { al = vt.align; };
};
v = v.next;
};
let pad: u64 = (maxsz + 7u64) & ~7u64;
r.size = 8u64 + pad;
r.align = al;

View File

@@ -8369,12 +8369,36 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
// sum-type shapes through NAMED variants).
r = newtype(tykind.TY_TAGGED);
tinfocachebind(c.tc, n, r);
// #50 / A.6.3f phase 1: populate ti.params as a tparam linked
// list (head=first source variant) in lock-step with the
// size/align accumulator. Mirrors cstage check.c:374-396 —
// same Tparam shape ww reuses across struct-fields / tuple-
// fields / fn-params / tagged-variants (sea-of-stars per rule
// 12). Phase 2 (#50b) retires cgenutil's AST-keyed
// nullableptrtag onto the chain.
let head: *tparam = nil;
let tail: *tparam = nil;
let maxsz: u64 = 0u64;
let al: u64 = 8u64;
let v: *node = n.list;
for (v != nil) {
let vt: *tinfo = tinfofornode(c, v);
let tp: *tparam = alloc(tparam{name="", type_=vt, tnext=nil})!;
if (head == nil) { head = tp; } else { tail.tnext = tp; };
tail = tp;
if (vt != nil) {
if (vt.size > maxsz) { maxsz = vt.size; };
if (vt.align > al) { al = vt.align; };
};
v = v.next;
};
r.params = head;
// #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.
// and not NAMED. AST-kind discrimination retained: wwstage
// tinfo carries no `iserror` field, so cstage's tinfo-level
// (kind==TY_VOID && !iserror) check doesn't port symmetrically.
let a: *node = n.list;
if (a != nil) {
let b: *node = a.next;
@@ -8393,22 +8417,10 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
r.align = 8u64;
r.nullable = 1;
r.slotsize = 8u64;
tinfocachebind(c.tc, n, r);
return r;
};
};
};
let maxsz: u64 = 0u64;
let al: u64 = 8u64;
let v: *node = n.list;
for (v != nil) {
let vt: *tinfo = tinfofornode(c, v);
if (vt != nil) {
if (vt.size > maxsz) { maxsz = vt.size; };
if (vt.align > al) { al = vt.align; };
};
v = v.next;
};
let pad: u64 = (maxsz + 7u64) & ~7u64;
r.size = 8u64 + pad;
r.align = al;