From 901ddf20b92dada03185d51ef423171a602ebb95 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 23 May 2026 22:57:13 +0900 Subject: [PATCH] selfhost/cmd/wcc/cgenutil: tagged-store machinery reads tinfo.params (#68) cgwidentaggedstore/storebp/cgwidentagremap took a type *node and re-derived the tagged shape via resolvetagged + N_TTAGGED.list walks. Migrate them onto the stamped tinfo (the store-side parallel of #66's match-side flip): dst is now the tagged *tinfo (peel TY_NAMED->du, gate TY_TAGGED), variant lookup + tag-remap read tinfo.params by typeeq, mirroring cstage cg_widen_tagged_store / cg_widen_tag_remap / cg_tag_for_variant (cmd/w6c/cgen.c:1273/1177/503). The N_CAST widening test flips from surface-name streq to `castu==dt || (castu tagged && typeeq(castt,dst))` (cgen.c:1295). Node-form flatvariantidx/taggedvariantindex become thin shims over new tinfo cores (flatvariantidxt/taggedvariantindext) so node-side callers (cgreturn cgenstmt:269, pushargs cgenutil:192) are untouched. The 9 cgwidentaggedstore callers pass node.type_ (each already istaggedtype/slotsize-gated). resolvetagged is retained for its 7 match-side callers. Incidentally retires two latent ww-vs-cstage divergences, both byte-id-neutral on the corpus: the old N_CAST test set castisdst for ANY N_TTAGGED regardless of type equality (cstage guards on type_eq), and the old remap walked the UNflattened src.list (cstage walks the flattened params). Unblocks cgassign's indexvaluetnode drop (#69/#61d). make test 134/134, byte-id 990-997 hold. --- selfhost/cmd/w6c/main.combined.ww | 214 +++++++++++++++------------ selfhost/cmd/wcc/cgenexpr.ww | 10 +- selfhost/cmd/wcc/cgenstmt.ww | 4 +- selfhost/cmd/wcc/cgenutil.ww | 200 ++++++++++++++----------- selfhost/cmd/wwdump/main.combined.ww | 214 +++++++++++++++------------ 5 files changed, 354 insertions(+), 288 deletions(-) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 2ee02f75..40467141 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -10767,7 +10767,7 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { emitline("(BP)\n"); zz += 8; }; - cgwidentaggedstore(c, ptype, arg, "BP", scroff, widensz); + cgwidentaggedstore(c, ptype.type_: *tinfo, arg, "BP", scroff, widensz); let pp: i32 = widensz - 8; for (pp >= 0) { emitline("\tMOVQ\t"); @@ -12732,65 +12732,48 @@ fn rhstargetname(c: *cgen, rhs: *node) str = { // "any str-shape variant matches an str-typed value". fn taggedvariantindex(c: *cgen, tagged: *node, rhs: *node) i32 = { if (tagged == nil) { return -1; }; - if (rhs == nil) { return -1; }; - // Alias-unwrap: wwstage has no typed AST, so an aliased tagged - // return (`type ft = (i64|str|bool); fn f() ft = ...`) reaches - // here as N_TNAME("ft"), not N_TTAGGED. flatvariantidx and the - // fallback both gate on N_TTAGGED → -1 → caller maps to 0, - // silently emitting `MOVQ $0, AX` for every non-leading variant. - // Cstage's check.c canonicalizes N_TNAME → underlying upfront; - // every wwstage cgen consumer of a type-bearing node has to - // remember this step itself. TODO(#11): a wwstage check pass - // between parse and cgen would replace the per-site unwrap with - // a single canonicalization. Same shape of fix as nodeisstr. - let resolved: *node = resolvetagged(c, tagged); - if (resolved != nil) { tagged = resolved; }; - // #66 Phase-N step 3: match the value's stamped type against the - // variant types by typeeq (flatvariantidx), replacing the - // rhstargetname surface-name compare. Untyped/loose values (whose - // .type_ is untyped_* and can't typeeq a concrete variant) return - // -1 here and drop to the str/slice shape scan below — ww has no - // type_assignable to mirror cg_variant_match's untyped-src arm. - let r: i32 = flatvariantidx(c, tagged, rhs); + return taggedvariantindext(c, tagged.type_: *tinfo, rhs); +}; + +// taggedvariantindext — tinfo-keyed core of taggedvariantindex. Given +// the dst tagged tinfo `du` (NAMED-peeled internally, gated TY_TAGGED) +// and the source value node, returns the 0-based variant index. #68: the +// tagged-store machinery reads tinfo directly (no type node), so the +// node-keyed taggedvariantindex delegates here off `tagged.type_`. +// +// #66 Phase-N step 3: match the value's stamped type against the variant +// types by typeeq (flatvariantidxt), replacing the rhstargetname surface- +// name compare. Untyped/loose values (whose .type_ is untyped_* and can't +// typeeq a concrete variant) return -1 there and drop to the str/slice +// shape scan below — ww has no type_assignable to mirror cg_variant_match's +// untyped-src arm. +fn taggedvariantindext(c: *cgen, du: *tinfo, rhs: *node) i32 = { + if (du == nil) { return -1; }; + if (rhs == nil) { return -1; }; + let ti: *tinfo = du; + for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; + if (ti == nil) { return -1; }; + if (ti.kind != tykind.TY_TAGGED) { return -1; }; + let r: i32 = flatvariantidxt(ti, rhs.type_: *tinfo); if (r >= 0) { return r; }; // Shape fallback: classify rhs as (str, slice, scalar/other) and // pick the first variant of matching shape. Stands in for cstage's // type_assignable on an untyped src — the typeeq pass above can't // match untyped_* against a concrete variant, and the slice axis // keeps a (u8 | []u8) widen off the leading scalar variant (task - // #19). Walks the spread-flattened list so a `(...inner | str)` - // outer agrees with the inner's str / slice positions. + // #19). tinfo.params is already spread-flattened (#61a — `...inner` + // inlined in declaration order), so the old N_TTAGGED.list spread- + // walk collapses to a flat scan over p.type_. let wantstr: bool = nodeisstr(c, rhs); let wantslice: bool = nodeisslice(c, rhs); - let v: *node = tagged.list; + let p: *tparam = ti.params; let idx: i32 = 0; - for (v != nil) { - let isspread: bool = (v.op == tkind.TK_ELLIPSIS); - if (isspread) { - let inner: *node = v; - if (inner.kind == nkind.N_TNAME) { - let a: *node = aliaslookup(c, inner.str); - if (a != nil) { inner = a; }; - }; - if (inner != nil) { - if (inner.kind == nkind.N_TTAGGED) { - let iv: *node = inner.list; - for (iv != nil) { - let ivisstr: bool = isstrtype(c, iv); - let ivisslice: bool = isslicetype(c, iv); - if (ivisstr == wantstr && ivisslice == wantslice) { return idx; }; - iv = iv.next; - idx += 1; - }; - v = v.next; - continue; - }; - }; - }; - let visstr: bool = isstrtype(c, v); - let visslice: bool = isslicetype(c, v); + for (p != nil) { + let vt: *tinfo = p.type_; + let visstr: bool = typeisstr(vt); + let visslice: bool = typeisslice(vt); if (visstr == wantstr && visslice == wantslice) { return idx; }; - v = v.next; + p = p.tnext; idx += 1; }; return -1; @@ -12813,9 +12796,21 @@ fn taggedvariantindex(c: *cgen, tagged: *node, rhs: *node) i32 = { fn flatvariantidx(c: *cgen, tagged: *node, pat: *node) i32 = { if (tagged == nil) { return -1; }; if (pat == nil) { return -1; }; - let want: *tinfo = pat.type_: *tinfo; + return flatvariantidxt(tagged.type_: *tinfo, pat.type_: *tinfo); +}; + +// flatvariantidxt — tinfo-keyed core of flatvariantidx: flat 0-based +// index of the variant whose type typeeq's `want`, over the pre- +// flattened tinfo.params chain (#61a). Peels TY_NAMED then gates +// TY_TAGGED. #68: the tagged-store machinery reads tinfo directly and +// has no type node to hand the node-keyed flatvariantidx, so the typeeq +// core lives here; flatvariantidx + taggedvariantindext + cgwidentagremap +// all funnel through it. Mirrors cstage cg_tag_for_variant +// (cmd/w6c/cgen.c:503) + cg_variant_match's both-NAMED ptr-id / typeeq +// arm (:451). +fn flatvariantidxt(tagged: *tinfo, want: *tinfo) i32 = { if (want == nil) { return -1; }; - let ti: *tinfo = tagged.type_: *tinfo; + let ti: *tinfo = tagged; for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; if (ti == nil) { return -1; }; if (ti.kind != tykind.TY_TAGGED) { return -1; }; @@ -12874,31 +12869,39 @@ fn flatslicevariantidx(c: *cgen, tagged: *node, elem: *node) i32 = { // cgwidentagremap — when widening from one tagged union to a wider one, // rewrite the source's variant tag at slot_off+0 to use the destination's // variant indices. No-op when src and dst index orders coincide. -// Mirrors cg_widen_tag_remap in cmd/w6c/cgen.c. -fn cgwidentagremap(c: *cgen, dst: *node, src: *node, slot_off: i32) void = { - if (dst == nil) { return; }; - if (src == nil) { return; }; - if (dst.kind != nkind.N_TTAGGED) { return; }; - if (src.kind != nkind.N_TTAGGED) { return; }; +// +// #68: both `du` (dst) and `su` (src) are now the tagged tinfos — peel +// TY_NAMED then walk su.params, mapping each source variant to its dst +// index by typeeq (flatvariantidxt). Mirrors cg_widen_tag_remap +// (cmd/w6c/cgen.c:1177) over su->params + cg_tag_for_variant (:503). +fn cgwidentagremap(c: *cgen, du: *tinfo, su: *tinfo, slot_off: i32) void = { + let dt: *tinfo = du; + for (dt != nil && dt.kind == tykind.TY_NAMED) { dt = dt.under; }; + if (dt == nil) { return; }; + if (dt.kind != tykind.TY_TAGGED) { return; }; + let st: *tinfo = su; + for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; }; + if (st == nil) { return; }; + if (st.kind != tykind.TY_TAGGED) { return; }; let identity: bool = true; - let v: *node = src.list; + let p: *tparam = st.params; let idx: i32 = 0; - for (v != nil) { - let di: i32 = cgtagvariantidx(c, dst, v); + for (p != nil) { + let di: i32 = flatvariantidxt(dt, p.type_); if (di < 0) { di = 0; }; - if (di != idx) { identity = false; v = nil; } - else { v = v.next; idx += 1; }; + if (di != idx) { identity = false; p = nil; } + else { p = p.tnext; idx += 1; }; }; if (identity) { return; }; let done: str = mklabel(c, "remap_done"); emitline("\tMOVQ\t"); emitoff(slot_off: i64); emitline("(BP), AX\n"); - v = src.list; + p = st.params; idx = 0; - for (v != nil) { + for (p != nil) { let next: str = mklabel(c, "remap_next"); - let di: i32 = cgtagvariantidx(c, dst, v); + let di: i32 = flatvariantidxt(dt, p.type_); if (di < 0) { di = 0; }; emitline("\tCMPQ\t$"); emitint(idx: i64); @@ -12916,7 +12919,7 @@ fn cgwidentagremap(c: *cgen, dst: *node, src: *node, slot_off: i32) void = { emitline(done); emitline("\n"); emitlabel(next); - v = v.next; + p = p.tnext; idx += 1; }; emitlabel(done); @@ -13145,7 +13148,7 @@ fn cgloadtaggedfield(c: *cgen, basereg: str, foff: i32, slot_sz: i32) void = { // tag last. // - str src: tag@+0, ptr@+8, len@+16. // - scalar src: tag@+0, value@+8. -fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, +fn cgwidentaggedstore(c: *cgen, dst: *tinfo, src: *node, basereg: str, slot_off: i32, slot_sz: i32) void = { if (streq(basereg, "BP")) { cgwidentaggedstorebp(c, dst, src, slot_off, slot_sz); @@ -13195,11 +13198,18 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, // for the natural "BP" case and via the wrapper's scratch path for // pointer-rooted dst. Direct callers exist only in case of future // inlined uses inside this file; new code should call the wrapper. -fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: i32) void = { - let dt: *node = resolvetagged(c, dst); +fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_sz: i32) void = { + // #68: dst is the tagged tinfo. Peel TY_NAMED → du and gate + // TY_TAGGED, mirroring cstage cg_widen_tagged_store's + // `du = (dst->kind==TY_NAMED)?dst->under:dst` + TY_TAGGED guard + // (cmd/w6c/cgen.c:1273). resolvetagged's N_TBANG unwrap is already + // handled upstream by tinfofornode (check.ww:1203-1210). + let dt: *tinfo = dst; + for (dt != nil && dt.kind == tykind.TY_NAMED) { dt = dt.under; }; if (dt == nil) { return; }; + if (dt.kind != tykind.TY_TAGGED) { return; }; // Nullable fold: one 8B word holding the pointer (or 0 for void). - if (isnullabletype(dst)) { + if (dt.nullable != 0) { cgexpr(c, src); emitline("\tMOVQ\tAX, "); emitoff(slot_off: i64); @@ -13229,22 +13239,32 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz }; // Cast's destination = the dst tagged union // itself? The rhs of N_CAST holds the target - // type. Compare nominally via str match on - // the tagged-alias name. + // type. #68: compare on the stamped tinfos — + // `castu == dt` (peeled-underlying ptr-id) || + // (castu tagged && typeeq(castt, dst)) — mirroring + // cstage cg_widen_tagged_store's + // `cast_is_widen = (castu==du) || (castu->kind== + // TY_TAGGED && type_eq(castt, dst))` + // (cmd/w6c/cgen.c:1295-1296). Replaces the prior + // surface-name streq; same-alias TNAMEs share one + // NAMED tinfo (check.ww:1160-1173) so typeeq hits + // the a==b fast path. let castisdst: bool = false; let castrhs: *node = src.rhs; if (castrhs != nil) { - if (castrhs.kind == nkind.N_TTAGGED) { - castisdst = true; + let castt: *tinfo = castrhs.type_: *tinfo; + let castu: *tinfo = castt; + for (castu != nil && castu.kind == tykind.TY_NAMED) { + castu = castu.under; }; - if (castrhs.kind == nkind.N_TNAME) { - if (dst != nil) { - if (dst.kind == nkind.N_TNAME) { - if (streq(castrhs.str, dst.str)) { - castisdst = true; - }; + if (castu != nil) { + if (castu == dt) { + castisdst = true; + } else { if (castu.kind == tykind.TY_TAGGED) { + if (typeeq(castt, dst)) { + castisdst = true; }; - }; + }; }; }; }; if (castisdst && !inneristagged) { @@ -13254,6 +13274,8 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz }; }; // Tagged source ident: byte-copy slot words then tag-remap. + // rhstaggedident gates "src is a tagged-typed local ident"; the + // remap reads the source tagged tinfo off the local's tnode (#68). let st: *node = rhstaggedident(c, src); if (st != nil) { let lc: *local = localfindnode(c, src.str); @@ -13279,7 +13301,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz p += 8; }; }; - cgwidentagremap(c, dt, st, slot_off); + cgwidentagremap(c, dt, lc.tnode.type_: *tinfo, slot_off); return; }; // Tagged source via AX/DX/CX/R8 register ABI (N_CALL, N_INDEX @@ -13320,7 +13342,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz emitline("(BP)\n"); zoff += 8; }; - let tag: i32 = taggedvariantindex(c, dt, src); + let tag: i32 = taggedvariantindext(c, dt, src); if (tag < 0) { tag = 0; }; if (src.kind == nkind.N_STRUCTLIT) { let fnode: *node = src.list; @@ -13414,7 +13436,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz emitline("\tMOVQ\tBX, "); emitoff((slot_off + 16): i64); emitline("(BP)\n"); - let tag: i32 = taggedvariantindex(c, dt, src); + let tag: i32 = taggedvariantindext(c, dt, src); if (tag < 0) { tag = 0; }; emitline("\tMOVQ\t$"); emitint(tag: i64); @@ -13436,7 +13458,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz emitline("\tMOVQ\tCX, "); emitoff((slot_off + 24): i64); emitline("(BP)\n"); - let tag: i32 = taggedvariantindex(c, dt, src); + let tag: i32 = taggedvariantindext(c, dt, src); if (tag < 0) { tag = 0; }; emitline("\tMOVQ\t$"); emitint(tag: i64); @@ -13472,7 +13494,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz // like the slice axis, not nominal identity. let wantf32: bool = (fkind == 1); let ftag: i32 = -1; - let fti: *tinfo = dt.type_: *tinfo; + let fti: *tinfo = dt; for (fti != nil && fti.kind == tykind.TY_NAMED) { fti = fti.under; }; if (fti != nil) { if (fti.kind == tykind.TY_TAGGED) { let fp: *tparam = fti.params; @@ -13502,7 +13524,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz emitline("\tMOVQ\tAX, "); emitoff((slot_off + 8): i64); emitline("(BP)\n"); - let tag: i32 = taggedvariantindex(c, dt, src); + let tag: i32 = taggedvariantindext(c, dt, src); if (tag < 0) { tag = 0; }; emitline("\tMOVQ\t$"); emitint(tag: i64); @@ -13780,7 +13802,7 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node, emitsymname(c, srcname); emitline("(SB), BX\n"); }; - cgwidentaggedstore(c, fi.tnode, + cgwidentaggedstore(c, fi.tnode.type_: *tinfo, fieldnode.lhs, basereg, disp + fi.foff, fi.fsz); fi = nil; @@ -17149,7 +17171,7 @@ fn cgcall(c: *cgen, n: *node) void = { // dst is the per-element tagged type; // pass velem (cstage cgen.c:4382 passes // velem, not the slice wrap vsu). - cgwidentaggedstore(c, velem, + cgwidentaggedstore(c, velem.type_: *tinfo, aa2, "BP", slot, esz); } else { if (velemstr) { cgexpr(c, aa2); @@ -17484,7 +17506,7 @@ fn cgassign(c: *cgen, n: *node) void = { if (lc != nil) { if (istaggedtype(c, lc.tnode)) { let lsz: i32 = slotsize(c, lc.tnode); - cgwidentaggedstore(c, lc.tnode, + cgwidentaggedstore(c, lc.tnode.type_: *tinfo, n.rhs, "BP", lc.off, lsz); return; }; @@ -17764,7 +17786,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("(BP)\n"); zz += 8; }; - cgwidentaggedstore(c, elemtn, n.rhs, + cgwidentaggedstore(c, elemtn.type_: *tinfo, n.rhs, "BP", scroff, slot_sz); cgexpr(c, idx); if (slot_sz > 1) { @@ -18142,7 +18164,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); - cgwidentaggedstore(c, fi.tnode, + cgwidentaggedstore(c, fi.tnode.type_: *tinfo, n.rhs, "BX", fi.foff, fsz); return; }; @@ -18379,7 +18401,7 @@ fn cgassign(c: *cgen, n: *node) void = { if (n.op == tkind.TK_ASSIGN && istaggedtype(c, fi.tnode)) { let fsz: i32 = slotsize(c, fi.tnode); - cgwidentaggedstore(c, fi.tnode, + cgwidentaggedstore(c, fi.tnode.type_: *tinfo, n.rhs, "BP", lc.off + fi.foff, fsz); return; }; @@ -20062,7 +20084,7 @@ fn cgreturn(c: *cgen, n: *node) void = { emitline("(BP)\n"); zz += 8; }; - cgwidentaggedstore(c, c.fnret, rhs, "BP", + cgwidentaggedstore(c, c.fnret.type_: *tinfo, rhs, "BP", scroff, rsz); emitline("\tMOVQ\t"); emitoff(scroff: i64); @@ -20552,7 +20574,7 @@ fn cglet(c: *cgen, n: *node) void = { // ABI call), struct payload (literal/ident), str payload, // scalar payload — with tag remap for tagged-subset widening. if (istaggedtype(c, tn)) { - cgwidentaggedstore(c, tn, rhs, "BP", off, sz); + cgwidentaggedstore(c, tn.type_: *tinfo, rhs, "BP", off, sz); c.lastwasreturn = 0; return; }; diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index e5f8c1c0..f3815eaf 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -3144,7 +3144,7 @@ fn cgcall(c: *cgen, n: *node) void = { // dst is the per-element tagged type; // pass velem (cstage cgen.c:4382 passes // velem, not the slice wrap vsu). - cgwidentaggedstore(c, velem, + cgwidentaggedstore(c, velem.type_: *tinfo, aa2, "BP", slot, esz); } else { if (velemstr) { cgexpr(c, aa2); @@ -3479,7 +3479,7 @@ fn cgassign(c: *cgen, n: *node) void = { if (lc != nil) { if (istaggedtype(c, lc.tnode)) { let lsz: i32 = slotsize(c, lc.tnode); - cgwidentaggedstore(c, lc.tnode, + cgwidentaggedstore(c, lc.tnode.type_: *tinfo, n.rhs, "BP", lc.off, lsz); return; }; @@ -3759,7 +3759,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("(BP)\n"); zz += 8; }; - cgwidentaggedstore(c, elemtn, n.rhs, + cgwidentaggedstore(c, elemtn.type_: *tinfo, n.rhs, "BP", scroff, slot_sz); cgexpr(c, idx); if (slot_sz > 1) { @@ -4137,7 +4137,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); - cgwidentaggedstore(c, fi.tnode, + cgwidentaggedstore(c, fi.tnode.type_: *tinfo, n.rhs, "BX", fi.foff, fsz); return; }; @@ -4374,7 +4374,7 @@ fn cgassign(c: *cgen, n: *node) void = { if (n.op == tkind.TK_ASSIGN && istaggedtype(c, fi.tnode)) { let fsz: i32 = slotsize(c, fi.tnode); - cgwidentaggedstore(c, fi.tnode, + cgwidentaggedstore(c, fi.tnode.type_: *tinfo, n.rhs, "BP", lc.off + fi.foff, fsz); return; }; diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index e868cab9..80143f10 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -225,7 +225,7 @@ fn cgreturn(c: *cgen, n: *node) void = { emitline("(BP)\n"); zz += 8; }; - cgwidentaggedstore(c, c.fnret, rhs, "BP", + cgwidentaggedstore(c, c.fnret.type_: *tinfo, rhs, "BP", scroff, rsz); emitline("\tMOVQ\t"); emitoff(scroff: i64); @@ -715,7 +715,7 @@ fn cglet(c: *cgen, n: *node) void = { // ABI call), struct payload (literal/ident), str payload, // scalar payload — with tag remap for tagged-subset widening. if (istaggedtype(c, tn)) { - cgwidentaggedstore(c, tn, rhs, "BP", off, sz); + cgwidentaggedstore(c, tn.type_: *tinfo, rhs, "BP", off, sz); c.lastwasreturn = 0; return; }; diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 52733622..6a09c585 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -222,7 +222,7 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { emitline("(BP)\n"); zz += 8; }; - cgwidentaggedstore(c, ptype, arg, "BP", scroff, widensz); + cgwidentaggedstore(c, ptype.type_: *tinfo, arg, "BP", scroff, widensz); let pp: i32 = widensz - 8; for (pp >= 0) { emitline("\tMOVQ\t"); @@ -2187,65 +2187,48 @@ fn rhstargetname(c: *cgen, rhs: *node) str = { // "any str-shape variant matches an str-typed value". fn taggedvariantindex(c: *cgen, tagged: *node, rhs: *node) i32 = { if (tagged == nil) { return -1; }; - if (rhs == nil) { return -1; }; - // Alias-unwrap: wwstage has no typed AST, so an aliased tagged - // return (`type ft = (i64|str|bool); fn f() ft = ...`) reaches - // here as N_TNAME("ft"), not N_TTAGGED. flatvariantidx and the - // fallback both gate on N_TTAGGED → -1 → caller maps to 0, - // silently emitting `MOVQ $0, AX` for every non-leading variant. - // Cstage's check.c canonicalizes N_TNAME → underlying upfront; - // every wwstage cgen consumer of a type-bearing node has to - // remember this step itself. TODO(#11): a wwstage check pass - // between parse and cgen would replace the per-site unwrap with - // a single canonicalization. Same shape of fix as nodeisstr. - let resolved: *node = resolvetagged(c, tagged); - if (resolved != nil) { tagged = resolved; }; - // #66 Phase-N step 3: match the value's stamped type against the - // variant types by typeeq (flatvariantidx), replacing the - // rhstargetname surface-name compare. Untyped/loose values (whose - // .type_ is untyped_* and can't typeeq a concrete variant) return - // -1 here and drop to the str/slice shape scan below — ww has no - // type_assignable to mirror cg_variant_match's untyped-src arm. - let r: i32 = flatvariantidx(c, tagged, rhs); + return taggedvariantindext(c, tagged.type_: *tinfo, rhs); +}; + +// taggedvariantindext — tinfo-keyed core of taggedvariantindex. Given +// the dst tagged tinfo `du` (NAMED-peeled internally, gated TY_TAGGED) +// and the source value node, returns the 0-based variant index. #68: the +// tagged-store machinery reads tinfo directly (no type node), so the +// node-keyed taggedvariantindex delegates here off `tagged.type_`. +// +// #66 Phase-N step 3: match the value's stamped type against the variant +// types by typeeq (flatvariantidxt), replacing the rhstargetname surface- +// name compare. Untyped/loose values (whose .type_ is untyped_* and can't +// typeeq a concrete variant) return -1 there and drop to the str/slice +// shape scan below — ww has no type_assignable to mirror cg_variant_match's +// untyped-src arm. +fn taggedvariantindext(c: *cgen, du: *tinfo, rhs: *node) i32 = { + if (du == nil) { return -1; }; + if (rhs == nil) { return -1; }; + let ti: *tinfo = du; + for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; + if (ti == nil) { return -1; }; + if (ti.kind != tykind.TY_TAGGED) { return -1; }; + let r: i32 = flatvariantidxt(ti, rhs.type_: *tinfo); if (r >= 0) { return r; }; // Shape fallback: classify rhs as (str, slice, scalar/other) and // pick the first variant of matching shape. Stands in for cstage's // type_assignable on an untyped src — the typeeq pass above can't // match untyped_* against a concrete variant, and the slice axis // keeps a (u8 | []u8) widen off the leading scalar variant (task - // #19). Walks the spread-flattened list so a `(...inner | str)` - // outer agrees with the inner's str / slice positions. + // #19). tinfo.params is already spread-flattened (#61a — `...inner` + // inlined in declaration order), so the old N_TTAGGED.list spread- + // walk collapses to a flat scan over p.type_. let wantstr: bool = nodeisstr(c, rhs); let wantslice: bool = nodeisslice(c, rhs); - let v: *node = tagged.list; + let p: *tparam = ti.params; let idx: i32 = 0; - for (v != nil) { - let isspread: bool = (v.op == tkind.TK_ELLIPSIS); - if (isspread) { - let inner: *node = v; - if (inner.kind == nkind.N_TNAME) { - let a: *node = aliaslookup(c, inner.str); - if (a != nil) { inner = a; }; - }; - if (inner != nil) { - if (inner.kind == nkind.N_TTAGGED) { - let iv: *node = inner.list; - for (iv != nil) { - let ivisstr: bool = isstrtype(c, iv); - let ivisslice: bool = isslicetype(c, iv); - if (ivisstr == wantstr && ivisslice == wantslice) { return idx; }; - iv = iv.next; - idx += 1; - }; - v = v.next; - continue; - }; - }; - }; - let visstr: bool = isstrtype(c, v); - let visslice: bool = isslicetype(c, v); + for (p != nil) { + let vt: *tinfo = p.type_; + let visstr: bool = typeisstr(vt); + let visslice: bool = typeisslice(vt); if (visstr == wantstr && visslice == wantslice) { return idx; }; - v = v.next; + p = p.tnext; idx += 1; }; return -1; @@ -2268,9 +2251,21 @@ fn taggedvariantindex(c: *cgen, tagged: *node, rhs: *node) i32 = { fn flatvariantidx(c: *cgen, tagged: *node, pat: *node) i32 = { if (tagged == nil) { return -1; }; if (pat == nil) { return -1; }; - let want: *tinfo = pat.type_: *tinfo; + return flatvariantidxt(tagged.type_: *tinfo, pat.type_: *tinfo); +}; + +// flatvariantidxt — tinfo-keyed core of flatvariantidx: flat 0-based +// index of the variant whose type typeeq's `want`, over the pre- +// flattened tinfo.params chain (#61a). Peels TY_NAMED then gates +// TY_TAGGED. #68: the tagged-store machinery reads tinfo directly and +// has no type node to hand the node-keyed flatvariantidx, so the typeeq +// core lives here; flatvariantidx + taggedvariantindext + cgwidentagremap +// all funnel through it. Mirrors cstage cg_tag_for_variant +// (cmd/w6c/cgen.c:503) + cg_variant_match's both-NAMED ptr-id / typeeq +// arm (:451). +fn flatvariantidxt(tagged: *tinfo, want: *tinfo) i32 = { if (want == nil) { return -1; }; - let ti: *tinfo = tagged.type_: *tinfo; + let ti: *tinfo = tagged; for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; if (ti == nil) { return -1; }; if (ti.kind != tykind.TY_TAGGED) { return -1; }; @@ -2329,31 +2324,39 @@ fn flatslicevariantidx(c: *cgen, tagged: *node, elem: *node) i32 = { // cgwidentagremap — when widening from one tagged union to a wider one, // rewrite the source's variant tag at slot_off+0 to use the destination's // variant indices. No-op when src and dst index orders coincide. -// Mirrors cg_widen_tag_remap in cmd/w6c/cgen.c. -fn cgwidentagremap(c: *cgen, dst: *node, src: *node, slot_off: i32) void = { - if (dst == nil) { return; }; - if (src == nil) { return; }; - if (dst.kind != nkind.N_TTAGGED) { return; }; - if (src.kind != nkind.N_TTAGGED) { return; }; +// +// #68: both `du` (dst) and `su` (src) are now the tagged tinfos — peel +// TY_NAMED then walk su.params, mapping each source variant to its dst +// index by typeeq (flatvariantidxt). Mirrors cg_widen_tag_remap +// (cmd/w6c/cgen.c:1177) over su->params + cg_tag_for_variant (:503). +fn cgwidentagremap(c: *cgen, du: *tinfo, su: *tinfo, slot_off: i32) void = { + let dt: *tinfo = du; + for (dt != nil && dt.kind == tykind.TY_NAMED) { dt = dt.under; }; + if (dt == nil) { return; }; + if (dt.kind != tykind.TY_TAGGED) { return; }; + let st: *tinfo = su; + for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; }; + if (st == nil) { return; }; + if (st.kind != tykind.TY_TAGGED) { return; }; let identity: bool = true; - let v: *node = src.list; + let p: *tparam = st.params; let idx: i32 = 0; - for (v != nil) { - let di: i32 = cgtagvariantidx(c, dst, v); + for (p != nil) { + let di: i32 = flatvariantidxt(dt, p.type_); if (di < 0) { di = 0; }; - if (di != idx) { identity = false; v = nil; } - else { v = v.next; idx += 1; }; + if (di != idx) { identity = false; p = nil; } + else { p = p.tnext; idx += 1; }; }; if (identity) { return; }; let done: str = mklabel(c, "remap_done"); emitline("\tMOVQ\t"); emitoff(slot_off: i64); emitline("(BP), AX\n"); - v = src.list; + p = st.params; idx = 0; - for (v != nil) { + for (p != nil) { let next: str = mklabel(c, "remap_next"); - let di: i32 = cgtagvariantidx(c, dst, v); + let di: i32 = flatvariantidxt(dt, p.type_); if (di < 0) { di = 0; }; emitline("\tCMPQ\t$"); emitint(idx: i64); @@ -2371,7 +2374,7 @@ fn cgwidentagremap(c: *cgen, dst: *node, src: *node, slot_off: i32) void = { emitline(done); emitline("\n"); emitlabel(next); - v = v.next; + p = p.tnext; idx += 1; }; emitlabel(done); @@ -2600,7 +2603,7 @@ fn cgloadtaggedfield(c: *cgen, basereg: str, foff: i32, slot_sz: i32) void = { // tag last. // - str src: tag@+0, ptr@+8, len@+16. // - scalar src: tag@+0, value@+8. -fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, +fn cgwidentaggedstore(c: *cgen, dst: *tinfo, src: *node, basereg: str, slot_off: i32, slot_sz: i32) void = { if (streq(basereg, "BP")) { cgwidentaggedstorebp(c, dst, src, slot_off, slot_sz); @@ -2650,11 +2653,18 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, // for the natural "BP" case and via the wrapper's scratch path for // pointer-rooted dst. Direct callers exist only in case of future // inlined uses inside this file; new code should call the wrapper. -fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: i32) void = { - let dt: *node = resolvetagged(c, dst); +fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_sz: i32) void = { + // #68: dst is the tagged tinfo. Peel TY_NAMED → du and gate + // TY_TAGGED, mirroring cstage cg_widen_tagged_store's + // `du = (dst->kind==TY_NAMED)?dst->under:dst` + TY_TAGGED guard + // (cmd/w6c/cgen.c:1273). resolvetagged's N_TBANG unwrap is already + // handled upstream by tinfofornode (check.ww:1203-1210). + let dt: *tinfo = dst; + for (dt != nil && dt.kind == tykind.TY_NAMED) { dt = dt.under; }; if (dt == nil) { return; }; + if (dt.kind != tykind.TY_TAGGED) { return; }; // Nullable fold: one 8B word holding the pointer (or 0 for void). - if (isnullabletype(dst)) { + if (dt.nullable != 0) { cgexpr(c, src); emitline("\tMOVQ\tAX, "); emitoff(slot_off: i64); @@ -2684,22 +2694,32 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz }; // Cast's destination = the dst tagged union // itself? The rhs of N_CAST holds the target - // type. Compare nominally via str match on - // the tagged-alias name. + // type. #68: compare on the stamped tinfos — + // `castu == dt` (peeled-underlying ptr-id) || + // (castu tagged && typeeq(castt, dst)) — mirroring + // cstage cg_widen_tagged_store's + // `cast_is_widen = (castu==du) || (castu->kind== + // TY_TAGGED && type_eq(castt, dst))` + // (cmd/w6c/cgen.c:1295-1296). Replaces the prior + // surface-name streq; same-alias TNAMEs share one + // NAMED tinfo (check.ww:1160-1173) so typeeq hits + // the a==b fast path. let castisdst: bool = false; let castrhs: *node = src.rhs; if (castrhs != nil) { - if (castrhs.kind == nkind.N_TTAGGED) { - castisdst = true; + let castt: *tinfo = castrhs.type_: *tinfo; + let castu: *tinfo = castt; + for (castu != nil && castu.kind == tykind.TY_NAMED) { + castu = castu.under; }; - if (castrhs.kind == nkind.N_TNAME) { - if (dst != nil) { - if (dst.kind == nkind.N_TNAME) { - if (streq(castrhs.str, dst.str)) { - castisdst = true; - }; + if (castu != nil) { + if (castu == dt) { + castisdst = true; + } else { if (castu.kind == tykind.TY_TAGGED) { + if (typeeq(castt, dst)) { + castisdst = true; }; - }; + }; }; }; }; if (castisdst && !inneristagged) { @@ -2709,6 +2729,8 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz }; }; // Tagged source ident: byte-copy slot words then tag-remap. + // rhstaggedident gates "src is a tagged-typed local ident"; the + // remap reads the source tagged tinfo off the local's tnode (#68). let st: *node = rhstaggedident(c, src); if (st != nil) { let lc: *local = localfindnode(c, src.str); @@ -2734,7 +2756,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz p += 8; }; }; - cgwidentagremap(c, dt, st, slot_off); + cgwidentagremap(c, dt, lc.tnode.type_: *tinfo, slot_off); return; }; // Tagged source via AX/DX/CX/R8 register ABI (N_CALL, N_INDEX @@ -2775,7 +2797,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz emitline("(BP)\n"); zoff += 8; }; - let tag: i32 = taggedvariantindex(c, dt, src); + let tag: i32 = taggedvariantindext(c, dt, src); if (tag < 0) { tag = 0; }; if (src.kind == nkind.N_STRUCTLIT) { let fnode: *node = src.list; @@ -2869,7 +2891,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz emitline("\tMOVQ\tBX, "); emitoff((slot_off + 16): i64); emitline("(BP)\n"); - let tag: i32 = taggedvariantindex(c, dt, src); + let tag: i32 = taggedvariantindext(c, dt, src); if (tag < 0) { tag = 0; }; emitline("\tMOVQ\t$"); emitint(tag: i64); @@ -2891,7 +2913,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz emitline("\tMOVQ\tCX, "); emitoff((slot_off + 24): i64); emitline("(BP)\n"); - let tag: i32 = taggedvariantindex(c, dt, src); + let tag: i32 = taggedvariantindext(c, dt, src); if (tag < 0) { tag = 0; }; emitline("\tMOVQ\t$"); emitint(tag: i64); @@ -2927,7 +2949,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz // like the slice axis, not nominal identity. let wantf32: bool = (fkind == 1); let ftag: i32 = -1; - let fti: *tinfo = dt.type_: *tinfo; + let fti: *tinfo = dt; for (fti != nil && fti.kind == tykind.TY_NAMED) { fti = fti.under; }; if (fti != nil) { if (fti.kind == tykind.TY_TAGGED) { let fp: *tparam = fti.params; @@ -2957,7 +2979,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz emitline("\tMOVQ\tAX, "); emitoff((slot_off + 8): i64); emitline("(BP)\n"); - let tag: i32 = taggedvariantindex(c, dt, src); + let tag: i32 = taggedvariantindext(c, dt, src); if (tag < 0) { tag = 0; }; emitline("\tMOVQ\t$"); emitint(tag: i64); @@ -3235,7 +3257,7 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node, emitsymname(c, srcname); emitline("(SB), BX\n"); }; - cgwidentaggedstore(c, fi.tnode, + cgwidentaggedstore(c, fi.tnode.type_: *tinfo, fieldnode.lhs, basereg, disp + fi.foff, fi.fsz); fi = nil; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 54e9566d..a91165a0 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -10767,7 +10767,7 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { emitline("(BP)\n"); zz += 8; }; - cgwidentaggedstore(c, ptype, arg, "BP", scroff, widensz); + cgwidentaggedstore(c, ptype.type_: *tinfo, arg, "BP", scroff, widensz); let pp: i32 = widensz - 8; for (pp >= 0) { emitline("\tMOVQ\t"); @@ -12732,65 +12732,48 @@ fn rhstargetname(c: *cgen, rhs: *node) str = { // "any str-shape variant matches an str-typed value". fn taggedvariantindex(c: *cgen, tagged: *node, rhs: *node) i32 = { if (tagged == nil) { return -1; }; - if (rhs == nil) { return -1; }; - // Alias-unwrap: wwstage has no typed AST, so an aliased tagged - // return (`type ft = (i64|str|bool); fn f() ft = ...`) reaches - // here as N_TNAME("ft"), not N_TTAGGED. flatvariantidx and the - // fallback both gate on N_TTAGGED → -1 → caller maps to 0, - // silently emitting `MOVQ $0, AX` for every non-leading variant. - // Cstage's check.c canonicalizes N_TNAME → underlying upfront; - // every wwstage cgen consumer of a type-bearing node has to - // remember this step itself. TODO(#11): a wwstage check pass - // between parse and cgen would replace the per-site unwrap with - // a single canonicalization. Same shape of fix as nodeisstr. - let resolved: *node = resolvetagged(c, tagged); - if (resolved != nil) { tagged = resolved; }; - // #66 Phase-N step 3: match the value's stamped type against the - // variant types by typeeq (flatvariantidx), replacing the - // rhstargetname surface-name compare. Untyped/loose values (whose - // .type_ is untyped_* and can't typeeq a concrete variant) return - // -1 here and drop to the str/slice shape scan below — ww has no - // type_assignable to mirror cg_variant_match's untyped-src arm. - let r: i32 = flatvariantidx(c, tagged, rhs); + return taggedvariantindext(c, tagged.type_: *tinfo, rhs); +}; + +// taggedvariantindext — tinfo-keyed core of taggedvariantindex. Given +// the dst tagged tinfo `du` (NAMED-peeled internally, gated TY_TAGGED) +// and the source value node, returns the 0-based variant index. #68: the +// tagged-store machinery reads tinfo directly (no type node), so the +// node-keyed taggedvariantindex delegates here off `tagged.type_`. +// +// #66 Phase-N step 3: match the value's stamped type against the variant +// types by typeeq (flatvariantidxt), replacing the rhstargetname surface- +// name compare. Untyped/loose values (whose .type_ is untyped_* and can't +// typeeq a concrete variant) return -1 there and drop to the str/slice +// shape scan below — ww has no type_assignable to mirror cg_variant_match's +// untyped-src arm. +fn taggedvariantindext(c: *cgen, du: *tinfo, rhs: *node) i32 = { + if (du == nil) { return -1; }; + if (rhs == nil) { return -1; }; + let ti: *tinfo = du; + for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; + if (ti == nil) { return -1; }; + if (ti.kind != tykind.TY_TAGGED) { return -1; }; + let r: i32 = flatvariantidxt(ti, rhs.type_: *tinfo); if (r >= 0) { return r; }; // Shape fallback: classify rhs as (str, slice, scalar/other) and // pick the first variant of matching shape. Stands in for cstage's // type_assignable on an untyped src — the typeeq pass above can't // match untyped_* against a concrete variant, and the slice axis // keeps a (u8 | []u8) widen off the leading scalar variant (task - // #19). Walks the spread-flattened list so a `(...inner | str)` - // outer agrees with the inner's str / slice positions. + // #19). tinfo.params is already spread-flattened (#61a — `...inner` + // inlined in declaration order), so the old N_TTAGGED.list spread- + // walk collapses to a flat scan over p.type_. let wantstr: bool = nodeisstr(c, rhs); let wantslice: bool = nodeisslice(c, rhs); - let v: *node = tagged.list; + let p: *tparam = ti.params; let idx: i32 = 0; - for (v != nil) { - let isspread: bool = (v.op == tkind.TK_ELLIPSIS); - if (isspread) { - let inner: *node = v; - if (inner.kind == nkind.N_TNAME) { - let a: *node = aliaslookup(c, inner.str); - if (a != nil) { inner = a; }; - }; - if (inner != nil) { - if (inner.kind == nkind.N_TTAGGED) { - let iv: *node = inner.list; - for (iv != nil) { - let ivisstr: bool = isstrtype(c, iv); - let ivisslice: bool = isslicetype(c, iv); - if (ivisstr == wantstr && ivisslice == wantslice) { return idx; }; - iv = iv.next; - idx += 1; - }; - v = v.next; - continue; - }; - }; - }; - let visstr: bool = isstrtype(c, v); - let visslice: bool = isslicetype(c, v); + for (p != nil) { + let vt: *tinfo = p.type_; + let visstr: bool = typeisstr(vt); + let visslice: bool = typeisslice(vt); if (visstr == wantstr && visslice == wantslice) { return idx; }; - v = v.next; + p = p.tnext; idx += 1; }; return -1; @@ -12813,9 +12796,21 @@ fn taggedvariantindex(c: *cgen, tagged: *node, rhs: *node) i32 = { fn flatvariantidx(c: *cgen, tagged: *node, pat: *node) i32 = { if (tagged == nil) { return -1; }; if (pat == nil) { return -1; }; - let want: *tinfo = pat.type_: *tinfo; + return flatvariantidxt(tagged.type_: *tinfo, pat.type_: *tinfo); +}; + +// flatvariantidxt — tinfo-keyed core of flatvariantidx: flat 0-based +// index of the variant whose type typeeq's `want`, over the pre- +// flattened tinfo.params chain (#61a). Peels TY_NAMED then gates +// TY_TAGGED. #68: the tagged-store machinery reads tinfo directly and +// has no type node to hand the node-keyed flatvariantidx, so the typeeq +// core lives here; flatvariantidx + taggedvariantindext + cgwidentagremap +// all funnel through it. Mirrors cstage cg_tag_for_variant +// (cmd/w6c/cgen.c:503) + cg_variant_match's both-NAMED ptr-id / typeeq +// arm (:451). +fn flatvariantidxt(tagged: *tinfo, want: *tinfo) i32 = { if (want == nil) { return -1; }; - let ti: *tinfo = tagged.type_: *tinfo; + let ti: *tinfo = tagged; for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; if (ti == nil) { return -1; }; if (ti.kind != tykind.TY_TAGGED) { return -1; }; @@ -12874,31 +12869,39 @@ fn flatslicevariantidx(c: *cgen, tagged: *node, elem: *node) i32 = { // cgwidentagremap — when widening from one tagged union to a wider one, // rewrite the source's variant tag at slot_off+0 to use the destination's // variant indices. No-op when src and dst index orders coincide. -// Mirrors cg_widen_tag_remap in cmd/w6c/cgen.c. -fn cgwidentagremap(c: *cgen, dst: *node, src: *node, slot_off: i32) void = { - if (dst == nil) { return; }; - if (src == nil) { return; }; - if (dst.kind != nkind.N_TTAGGED) { return; }; - if (src.kind != nkind.N_TTAGGED) { return; }; +// +// #68: both `du` (dst) and `su` (src) are now the tagged tinfos — peel +// TY_NAMED then walk su.params, mapping each source variant to its dst +// index by typeeq (flatvariantidxt). Mirrors cg_widen_tag_remap +// (cmd/w6c/cgen.c:1177) over su->params + cg_tag_for_variant (:503). +fn cgwidentagremap(c: *cgen, du: *tinfo, su: *tinfo, slot_off: i32) void = { + let dt: *tinfo = du; + for (dt != nil && dt.kind == tykind.TY_NAMED) { dt = dt.under; }; + if (dt == nil) { return; }; + if (dt.kind != tykind.TY_TAGGED) { return; }; + let st: *tinfo = su; + for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; }; + if (st == nil) { return; }; + if (st.kind != tykind.TY_TAGGED) { return; }; let identity: bool = true; - let v: *node = src.list; + let p: *tparam = st.params; let idx: i32 = 0; - for (v != nil) { - let di: i32 = cgtagvariantidx(c, dst, v); + for (p != nil) { + let di: i32 = flatvariantidxt(dt, p.type_); if (di < 0) { di = 0; }; - if (di != idx) { identity = false; v = nil; } - else { v = v.next; idx += 1; }; + if (di != idx) { identity = false; p = nil; } + else { p = p.tnext; idx += 1; }; }; if (identity) { return; }; let done: str = mklabel(c, "remap_done"); emitline("\tMOVQ\t"); emitoff(slot_off: i64); emitline("(BP), AX\n"); - v = src.list; + p = st.params; idx = 0; - for (v != nil) { + for (p != nil) { let next: str = mklabel(c, "remap_next"); - let di: i32 = cgtagvariantidx(c, dst, v); + let di: i32 = flatvariantidxt(dt, p.type_); if (di < 0) { di = 0; }; emitline("\tCMPQ\t$"); emitint(idx: i64); @@ -12916,7 +12919,7 @@ fn cgwidentagremap(c: *cgen, dst: *node, src: *node, slot_off: i32) void = { emitline(done); emitline("\n"); emitlabel(next); - v = v.next; + p = p.tnext; idx += 1; }; emitlabel(done); @@ -13145,7 +13148,7 @@ fn cgloadtaggedfield(c: *cgen, basereg: str, foff: i32, slot_sz: i32) void = { // tag last. // - str src: tag@+0, ptr@+8, len@+16. // - scalar src: tag@+0, value@+8. -fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, +fn cgwidentaggedstore(c: *cgen, dst: *tinfo, src: *node, basereg: str, slot_off: i32, slot_sz: i32) void = { if (streq(basereg, "BP")) { cgwidentaggedstorebp(c, dst, src, slot_off, slot_sz); @@ -13195,11 +13198,18 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, // for the natural "BP" case and via the wrapper's scratch path for // pointer-rooted dst. Direct callers exist only in case of future // inlined uses inside this file; new code should call the wrapper. -fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: i32) void = { - let dt: *node = resolvetagged(c, dst); +fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_sz: i32) void = { + // #68: dst is the tagged tinfo. Peel TY_NAMED → du and gate + // TY_TAGGED, mirroring cstage cg_widen_tagged_store's + // `du = (dst->kind==TY_NAMED)?dst->under:dst` + TY_TAGGED guard + // (cmd/w6c/cgen.c:1273). resolvetagged's N_TBANG unwrap is already + // handled upstream by tinfofornode (check.ww:1203-1210). + let dt: *tinfo = dst; + for (dt != nil && dt.kind == tykind.TY_NAMED) { dt = dt.under; }; if (dt == nil) { return; }; + if (dt.kind != tykind.TY_TAGGED) { return; }; // Nullable fold: one 8B word holding the pointer (or 0 for void). - if (isnullabletype(dst)) { + if (dt.nullable != 0) { cgexpr(c, src); emitline("\tMOVQ\tAX, "); emitoff(slot_off: i64); @@ -13229,22 +13239,32 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz }; // Cast's destination = the dst tagged union // itself? The rhs of N_CAST holds the target - // type. Compare nominally via str match on - // the tagged-alias name. + // type. #68: compare on the stamped tinfos — + // `castu == dt` (peeled-underlying ptr-id) || + // (castu tagged && typeeq(castt, dst)) — mirroring + // cstage cg_widen_tagged_store's + // `cast_is_widen = (castu==du) || (castu->kind== + // TY_TAGGED && type_eq(castt, dst))` + // (cmd/w6c/cgen.c:1295-1296). Replaces the prior + // surface-name streq; same-alias TNAMEs share one + // NAMED tinfo (check.ww:1160-1173) so typeeq hits + // the a==b fast path. let castisdst: bool = false; let castrhs: *node = src.rhs; if (castrhs != nil) { - if (castrhs.kind == nkind.N_TTAGGED) { - castisdst = true; + let castt: *tinfo = castrhs.type_: *tinfo; + let castu: *tinfo = castt; + for (castu != nil && castu.kind == tykind.TY_NAMED) { + castu = castu.under; }; - if (castrhs.kind == nkind.N_TNAME) { - if (dst != nil) { - if (dst.kind == nkind.N_TNAME) { - if (streq(castrhs.str, dst.str)) { - castisdst = true; - }; + if (castu != nil) { + if (castu == dt) { + castisdst = true; + } else { if (castu.kind == tykind.TY_TAGGED) { + if (typeeq(castt, dst)) { + castisdst = true; }; - }; + }; }; }; }; if (castisdst && !inneristagged) { @@ -13254,6 +13274,8 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz }; }; // Tagged source ident: byte-copy slot words then tag-remap. + // rhstaggedident gates "src is a tagged-typed local ident"; the + // remap reads the source tagged tinfo off the local's tnode (#68). let st: *node = rhstaggedident(c, src); if (st != nil) { let lc: *local = localfindnode(c, src.str); @@ -13279,7 +13301,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz p += 8; }; }; - cgwidentagremap(c, dt, st, slot_off); + cgwidentagremap(c, dt, lc.tnode.type_: *tinfo, slot_off); return; }; // Tagged source via AX/DX/CX/R8 register ABI (N_CALL, N_INDEX @@ -13320,7 +13342,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz emitline("(BP)\n"); zoff += 8; }; - let tag: i32 = taggedvariantindex(c, dt, src); + let tag: i32 = taggedvariantindext(c, dt, src); if (tag < 0) { tag = 0; }; if (src.kind == nkind.N_STRUCTLIT) { let fnode: *node = src.list; @@ -13414,7 +13436,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz emitline("\tMOVQ\tBX, "); emitoff((slot_off + 16): i64); emitline("(BP)\n"); - let tag: i32 = taggedvariantindex(c, dt, src); + let tag: i32 = taggedvariantindext(c, dt, src); if (tag < 0) { tag = 0; }; emitline("\tMOVQ\t$"); emitint(tag: i64); @@ -13436,7 +13458,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz emitline("\tMOVQ\tCX, "); emitoff((slot_off + 24): i64); emitline("(BP)\n"); - let tag: i32 = taggedvariantindex(c, dt, src); + let tag: i32 = taggedvariantindext(c, dt, src); if (tag < 0) { tag = 0; }; emitline("\tMOVQ\t$"); emitint(tag: i64); @@ -13472,7 +13494,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz // like the slice axis, not nominal identity. let wantf32: bool = (fkind == 1); let ftag: i32 = -1; - let fti: *tinfo = dt.type_: *tinfo; + let fti: *tinfo = dt; for (fti != nil && fti.kind == tykind.TY_NAMED) { fti = fti.under; }; if (fti != nil) { if (fti.kind == tykind.TY_TAGGED) { let fp: *tparam = fti.params; @@ -13502,7 +13524,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz emitline("\tMOVQ\tAX, "); emitoff((slot_off + 8): i64); emitline("(BP)\n"); - let tag: i32 = taggedvariantindex(c, dt, src); + let tag: i32 = taggedvariantindext(c, dt, src); if (tag < 0) { tag = 0; }; emitline("\tMOVQ\t$"); emitint(tag: i64); @@ -13780,7 +13802,7 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node, emitsymname(c, srcname); emitline("(SB), BX\n"); }; - cgwidentaggedstore(c, fi.tnode, + cgwidentaggedstore(c, fi.tnode.type_: *tinfo, fieldnode.lhs, basereg, disp + fi.foff, fi.fsz); fi = nil; @@ -17149,7 +17171,7 @@ fn cgcall(c: *cgen, n: *node) void = { // dst is the per-element tagged type; // pass velem (cstage cgen.c:4382 passes // velem, not the slice wrap vsu). - cgwidentaggedstore(c, velem, + cgwidentaggedstore(c, velem.type_: *tinfo, aa2, "BP", slot, esz); } else { if (velemstr) { cgexpr(c, aa2); @@ -17484,7 +17506,7 @@ fn cgassign(c: *cgen, n: *node) void = { if (lc != nil) { if (istaggedtype(c, lc.tnode)) { let lsz: i32 = slotsize(c, lc.tnode); - cgwidentaggedstore(c, lc.tnode, + cgwidentaggedstore(c, lc.tnode.type_: *tinfo, n.rhs, "BP", lc.off, lsz); return; }; @@ -17764,7 +17786,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("(BP)\n"); zz += 8; }; - cgwidentaggedstore(c, elemtn, n.rhs, + cgwidentaggedstore(c, elemtn.type_: *tinfo, n.rhs, "BP", scroff, slot_sz); cgexpr(c, idx); if (slot_sz > 1) { @@ -18142,7 +18164,7 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); - cgwidentaggedstore(c, fi.tnode, + cgwidentaggedstore(c, fi.tnode.type_: *tinfo, n.rhs, "BX", fi.foff, fsz); return; }; @@ -18379,7 +18401,7 @@ fn cgassign(c: *cgen, n: *node) void = { if (n.op == tkind.TK_ASSIGN && istaggedtype(c, fi.tnode)) { let fsz: i32 = slotsize(c, fi.tnode); - cgwidentaggedstore(c, fi.tnode, + cgwidentaggedstore(c, fi.tnode.type_: *tinfo, n.rhs, "BP", lc.off + fi.foff, fsz); return; }; @@ -20062,7 +20084,7 @@ fn cgreturn(c: *cgen, n: *node) void = { emitline("(BP)\n"); zz += 8; }; - cgwidentaggedstore(c, c.fnret, rhs, "BP", + cgwidentaggedstore(c, c.fnret.type_: *tinfo, rhs, "BP", scroff, rsz); emitline("\tMOVQ\t"); emitoff(scroff: i64); @@ -20552,7 +20574,7 @@ fn cglet(c: *cgen, n: *node) void = { // ABI call), struct payload (literal/ident), str payload, // scalar payload — with tag remap for tagged-subset widening. if (istaggedtype(c, tn)) { - cgwidentaggedstore(c, tn, rhs, "BP", off, sz); + cgwidentaggedstore(c, tn.type_: *tinfo, rhs, "BP", off, sz); c.lastwasreturn = 0; return; };