From 26724feefbc2f26628c018df3c4ff101abce6b2e Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 23 May 2026 01:51:29 +0900 Subject: [PATCH] selfhost/cmd/wcc/check: populate TY_TAGGED.params (#50 phase 1, A.6.3f-a) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- selfhost/cmd/w6c/main.combined.ww | 42 ++++++++++++++++++---------- selfhost/cmd/wcc/check.ww | 42 ++++++++++++++++++---------- selfhost/cmd/wwdump/main.combined.ww | 42 ++++++++++++++++++---------- 3 files changed, 81 insertions(+), 45 deletions(-) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index e3842bfc..20b1fc08 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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; diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index c6a39a79..711945ff 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -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; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index c6a3e8a4..5ac78ace 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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;