From 7c75dd218ad316f1a6db05011f87f9d9d7cb42d0 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 13 May 2026 03:06:46 +0900 Subject: [PATCH] selfhost: fix several wwstage cgen miscompilations Surfaced via examples/lisp, which had to work around the following in source. Each lowering now matches cstage on the same shape. - cgassign / cgdot: two-level field through a non-pointer sub-struct. `(*L).cur.kind = k` (cur a struct-by-value field of L) silently dropped the store; the corresponding read fell into the SB-symbol fallback and the linker reported `undefined reference to kind`. The two new branches resolve outer-field offset + inner-field offset and emit a single direct store/load at the combined slot, both for T-by-value and *T-base shapes. - cgdot: `xs[i].field` chains the trailing field load through the N_INDEX result for [N]T / []T / *T element-of-struct-ptr. The cgforrange loop variable now carries the elem tnode so the same fast path covers `for (let x .. xs) { x.field }`. - cgindex / cgassign: top-level `[N]T` array and `*T` pointer used as an index base. cgindex now emits LEAQ name(SB) (array) or MOVQ name(SB) (pointer) with the correct element scaling; without this the fallback emitted neither base and walked off the saved BP slot. Adds letvartnode() helper, an N_TARRAY branch to letemitsize so the array shows up in c.lets, and an N_TARRAY initialiser path in emitletdataw that lays the literal bytes into DATAW. - cglet / scanlocals: infer the local's tnode for an unannotated `let x = f()` / `let x = f()?`. inferletcalltype() reads the callee's declared return; `?` and `!` strip to the success variant so a tagged-union let allocates the full 24B slot and the struct-field dispatch in cgdot/cgassign sees the right type. letslotsize now defers to slotsize on the inferred type. - slotsize: follow type aliases for tagged-union variants. With `type parserr = !str;`, the variant slot was 8B instead of the required 16B; the tagged let stomped on the next slot at the AX/DX/CX spill. - cgreturn: tagged-union return forwarding. `return f();` where f also returns a tagged union now passes the (tag, payload1, payload2) triple through unchanged instead of re-wrapping it. - cgreturn / cglet / taggedvariantindex: dispatch by variant name with module-qualified-vs-bare matching, and recognise N_STRUCTLIT as the variant tag for `return eof{};`. cgexpr default emits `MOVQ $0, AX` so the surrounding return shuffle isn't left with a stale AX. - isstrtype / nodeisstr: resolve through `!T` aliases. `parserr = !str` was not propagating the str-shape to the rhs check and the MOVQ BX,CX shuffle was being dropped from str-typed local returns. - exprfloatkind: recognise `p.field` as f64/f32 when the struct field is so declared, so `v.fval: i64` lowers to CVTTSD2SI on X0. - cgassign: str field on a direct struct local writes both halves. `L.src = s;` previously dropped s.len. - cgcall: pop into the int reg window only up to 6 (DI..R9); rest stays on the stack and the caller emits ADDQ to clean up. cgfnparams accepts >6-arg signatures by registering the overflow params at positive BP offsets (16+8*k(BP)), no spill instruction emitted. All 26 harness tests pass; bootstrap reaches a byte-stable fixed point at ww3 == ww4. --- selfhost/cmd/wcc/cgen.ww | 110 ++++++++ selfhost/cmd/wcc/cgendecl.ww | 147 ++++++----- selfhost/cmd/wcc/cgenexpr.ww | 473 ++++++++++++++++++++++++++++++++--- selfhost/cmd/wcc/cgenstmt.ww | 50 +++- selfhost/cmd/wcc/cgenutil.ww | 182 +++++++++++++- 5 files changed, 854 insertions(+), 108 deletions(-) diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index 1ea502b6..8bab09ad 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -381,6 +381,20 @@ fn localalloc(c: *cgen, name: str, sz: i32, tnode: *node) i32 = { return off; }; +// localaddstack — register a param at a positive BP offset. Used for +// args that overflow the 6 SysV int / 8 float reg windows; the caller +// pushes them in reverse, so each spilled arg lives at 16(BP), 24(BP), +// etc. (after the saved RIP+BP). No spill instruction is emitted; the +// slot IS the caller's stack slot. +fn localaddstack(c: *cgen, name: str, tnode: *node, off: i32) void = { + let l: *local = amalloc(c.a, 48u64): *local; + l.name = name; + l.off = off; + l.tnode = tnode; + l.lnext = c.locals; + c.locals = l; +}; + fn localadd(c: *cgen, name: str, sz: i32, tnode: *node) i32 = { // Name-based slot reuse for N_LETs and params: if `name` is // already declared in this function, return its existing @@ -634,6 +648,22 @@ fn letemitsize(c: *cgen, d: *node) i32 = { for (t != nil) { if (t.kind == nkind.N_TPTR) { return 8; }; if (t.kind == nkind.N_TSLICE) { return 24; }; + if (t.kind == nkind.N_TARRAY) { + let lenn: *node = t.rhs; + let elemn: *node = t.lhs; + let alen: i32 = 1; + if (lenn != nil) { + if (lenn.kind == nkind.N_INTLIT) { alen = lenn.uval: i32; }; + }; + let esz: i32 = 8; + if (elemn != nil) { + if (elemn.kind == nkind.N_TNAME) { + let ps: i32 = primsize(elemn.str); + if (ps > 0) { esz = ps; }; + }; + }; + return alen * esz; + }; if (t.kind != nkind.N_TNAME) { return 0; }; let nm: str = t.str; if (letscalarprim(nm)) { return 8; }; @@ -683,6 +713,19 @@ fn isletvar(c: *cgen, name: str) bool = { // aliases to mirror C cgen's `let_isstr`. Used by cgident/cgdot/ // cgassign to pick the (LEAQ, MOVQ, MOVQ) sequence over the bare // MOVQ scalar load. +// letvartnode — direct lookup of a top-level let's tnode. Used by +// cgindex / cgassign to detect global `[N]T` arrays and `*T` +// pointers, where the addressing path needs LEAQ name(SB) (array) +// or MOVQ name(SB) (pointer) and the element size from T. +fn letvartnode(c: *cgen, name: str) *node = { + let lv: *letvar = c.lets; + for (lv != nil) { + if (streq(lv.name, name)) { return lv.tnode; }; + lv = lv.lvnext; + }; + return nil; +}; + fn letvarisstr(c: *cgen, name: str) bool = { let lv: *letvar = c.lets; for (lv != nil) { @@ -1055,6 +1098,73 @@ fn emitletdataw(c: *cgen, file: *node) void = { emitline("\"\n"); }; }; + // Top-level `[N]T = [a, b, ...]` array global. + // Emits N*esz bytes with each element's bytes + // little-endian for the declared primitive width. + // Without this, `let arr: [N]T = ...` references + // from function bodies link-fail with `undefined + // reference to arr`, and bare-name addressing + // (LEAQ arr(SB)) inside cgindex / cgassign has no + // symbol to bind to. + if (d.lhs != nil) { + if (d.lhs.kind == nkind.N_TARRAY) { + let elemn: *node = d.lhs.lhs; + let esz: i32 = 8; + if (elemn != nil) { + if (elemn.kind == nkind.N_TNAME) { + let ps: i32 = primsize(elemn.str); + if (ps > 0) { esz = ps; }; + }; + }; + let total: i32 = sz; + let alen: i32 = total / esz; + let elems: *node = nil; + if (d.rhs != nil) { + if (d.rhs.kind == nkind.N_ARRLIT) { + elems = d.rhs.list; + }; + }; + emitline("DATAW "); + emitsymname(c, nm); + emitline("(SB),\""); + let i: i32 = 0; + let e: *node = elems; + let fillv: u64 = 0u64; + let inrepeat: bool = false; + for (i < alen) { + let v: u64 = fillv; + if (!inrepeat && e != nil) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { + // `..., ...` repeat marker: prior v stays. + inrepeat = true; + } else { + if (e.lhs != nil) { + if (e.lhs.kind == nkind.N_INTLIT) { v = e.lhs.uval; }; + if (e.lhs.kind == nkind.N_RUNELIT) { v = e.lhs.uval; }; + }; + fillv = v; + e = e.next; + }; + } else { + if (e.kind == nkind.N_INTLIT) { v = e.uval; }; + if (e.kind == nkind.N_RUNELIT) { v = e.uval; }; + fillv = v; + e = e.next; + }; + }; + let nb: u64 = v; + let b: i32 = 0; + for (b < esz) { + emitdatawbyte((nb & 255u64): u8); + nb = nb >> 8u64; + b += 1; + }; + i += 1; + }; + emitline("\"\n"); + }; + }; }; }; d = d.next; diff --git a/selfhost/cmd/wcc/cgendecl.ww b/selfhost/cmd/wcc/cgendecl.ww index 2c03d48b..a2947394 100644 --- a/selfhost/cmd/wcc/cgendecl.ww +++ b/selfhost/cmd/wcc/cgendecl.ww @@ -167,6 +167,11 @@ fn cgfnparams(c: *cgen, params: *node) void = { let p: *node = params; let idx: i32 = 0; let fidx: i32 = 0; + // Cursor for args that overflow the SysV reg windows. Each + // stack-passed arg lives at 16+8*k(BP) — no spill, the local + // is registered with a *positive* offset pointing into the + // caller's frame. Mirrors C cgen's cg_stack_arg_cursor. + let stkcursor: i32 = 0; for (p != nil) { if (p.kind == nkind.N_PARAM) { let nm: str = p.str; @@ -175,81 +180,101 @@ fn cgfnparams(c: *cgen, params: *node) void = { // (X0..X7). 8B (f64) or 4B (f32) slot. let fsz: i32 = 8; if (isf32type(c, p.lhs)) { fsz = 4; }; - let off: i32 = localadd(c, nm, fsz, p.lhs); - let mov: str = "MOVSD"; - if (fsz == 4) { mov = "MOVSS"; }; - emitline("\t"); - emitline(mov); - emitline("\t"); - emitline(fargregname(fidx)); - emitline(", "); - emitoff(off: i64); - emitline("(BP)\n"); - fidx += 1; + if (fidx < 8) { + let off: i32 = localadd(c, nm, fsz, p.lhs); + let mov: str = "MOVSD"; + if (fsz == 4) { mov = "MOVSS"; }; + emitline("\t"); + emitline(mov); + emitline("\t"); + emitline(fargregname(fidx)); + emitline(", "); + emitoff(off: i64); + emitline("(BP)\n"); + fidx += 1; + } else { + localaddstack(c, nm, p.lhs, 16 + stkcursor*8); + stkcursor += 1; + }; p = p.next; continue; }; if (istaggedtype(p.lhs)) { - // tagged-union param: spill size/8 registers - // (tag + value words). Slot sized to match. let slot: i32 = slotsize(c, p.lhs); - let off: i32 = localadd(c, nm, slot, p.lhs); let nw: i32 = slot / 8; - let w: i32 = 0; - for (w < nw) { + if (idx + nw <= 6) { + let off: i32 = localadd(c, nm, slot, p.lhs); + let w: i32 = 0; + for (w < nw) { + emitline("\tMOVQ\t"); + emitline(argregname(idx)); + emitline(", "); + emitoff((off + w*8): i64); + emitline("(BP)\n"); + idx += 1; + w += 1; + }; + } else { + localaddstack(c, nm, p.lhs, 16 + stkcursor*8); + stkcursor += nw; + }; + } else { if (isslicetype(c, p.lhs)) { + if (idx + 3 <= 6) { + let off: i32 = localadd(c, nm, 24, p.lhs); emitline("\tMOVQ\t"); emitline(argregname(idx)); emitline(", "); - emitoff((off + w*8): i64); + emitoff(off: i64); emitline("(BP)\n"); idx += 1; - w += 1; + emitline("\tMOVQ\t"); + emitline(argregname(idx)); + emitline(", "); + emitoff((off + 8): i64); + emitline("(BP)\n"); + idx += 1; + emitline("\tMOVQ\t"); + emitline(argregname(idx)); + emitline(", "); + emitoff((off + 16): i64); + emitline("(BP)\n"); + idx += 1; + } else { + localaddstack(c, nm, p.lhs, 16 + stkcursor*8); + stkcursor += 3; }; - } else { if (isslicetype(c, p.lhs)) { - // slice param: 3 regs (ptr, len, cap), 24-byte slot. - let off: i32 = localadd(c, nm, 24, p.lhs); - emitline("\tMOVQ\t"); - emitline(argregname(idx)); - emitline(", "); - emitoff(off: i64); - emitline("(BP)\n"); - idx += 1; - emitline("\tMOVQ\t"); - emitline(argregname(idx)); - emitline(", "); - emitoff((off + 8): i64); - emitline("(BP)\n"); - idx += 1; - emitline("\tMOVQ\t"); - emitline(argregname(idx)); - emitline(", "); - emitoff((off + 16): i64); - emitline("(BP)\n"); - idx += 1; } else { if (isstrtype(c, p.lhs)) { - // str param: passed in two regs (ptr, len). - // Slot is 16 bytes; ptr at off+0, len at off+8. - let off: i32 = localadd(c, nm, 16, p.lhs); - emitline("\tMOVQ\t"); - emitline(argregname(idx)); - emitline(", "); - emitoff(off: i64); - emitline("(BP)\n"); - idx += 1; - emitline("\tMOVQ\t"); - emitline(argregname(idx)); - emitline(", "); - emitoff((off + 8): i64); - emitline("(BP)\n"); - idx += 1; + if (idx + 2 <= 6) { + let off: i32 = localadd(c, nm, 16, p.lhs); + emitline("\tMOVQ\t"); + emitline(argregname(idx)); + emitline(", "); + emitoff(off: i64); + emitline("(BP)\n"); + idx += 1; + emitline("\tMOVQ\t"); + emitline(argregname(idx)); + emitline(", "); + emitoff((off + 8): i64); + emitline("(BP)\n"); + idx += 1; + } else { + localaddstack(c, nm, p.lhs, 16 + stkcursor*8); + stkcursor += 2; + }; } else { - let off: i32 = localadd(c, nm, 8, p.lhs); - emitline("\tMOVQ\t"); - emitline(argregname(idx)); - emitline(", "); - emitoff(off: i64); - emitline("(BP)\n"); - idx += 1; + if (idx < 6) { + let off: i32 = localadd(c, nm, 8, p.lhs); + emitline("\tMOVQ\t"); + emitline(argregname(idx)); + emitline(", "); + emitoff(off: i64); + emitline("(BP)\n"); + idx += 1; + } else { + localaddstack(c, nm, p.lhs, 16 + stkcursor*8); + stkcursor += 1; + }; };};}; }; p = p.next; diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 52f4b729..b2b4a866 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -100,6 +100,12 @@ fn cgexpr(c: *cgen, n: *node) void = { if (k == nkind.N_TRYUNW) { cgtryunw(c, n); return; }; if (k == nkind.N_TYPETEST) { cgtypetest(c, n); return; }; if (k == nkind.N_TYPEASSERT) { cgtypeassert(c, n); return; }; + // Default fallback: produce a deterministic AX = 0. Mirrors + // the C cgen's `default: cgexpr_int(c, 0)` branch, which is + // what `return eof{};` (N_STRUCTLIT with an empty !void + // variant) silently relies on — without this AX carries a + // stale value into the tagged-union return shuffle. + emitline("\tMOVQ\t$0, AX\n"); }; // cgtagvariantidx — find the 0-based variant index of `vt` inside the @@ -524,6 +530,14 @@ fn cgindex(c: *cgen, n: *node) void = { let esz: i32 = 8; let signed_elem: bool = false; let baselocal: *local = nil; + // Global `[N]T` array or `*T` pointer used as an index base. + // The local-ident lookup above misses it; we need LEAQ name(SB) + // (array, the symbol IS the storage) or MOVQ name(SB) (pointer, + // the symbol holds the address) to feed the addend. + let isglobalarr: bool = false; + let isglobalptr: bool = false; + let globalname: str; + globalname.ptr = nil; globalname.len = 0; if (base != nil) { if (base.kind == nkind.N_IDENT) { let bn: str = base.str; @@ -531,6 +545,22 @@ fn cgindex(c: *cgen, n: *node) void = { if (baselocal != nil) { esz = elemsizeof(baselocal.tnode); signed_elem = elemissigned(baselocal.tnode); + } else { + let tn: *node = letvartnode(c, bn); + if (tn != nil) { + if (tn.kind == nkind.N_TARRAY) { + isglobalarr = true; + globalname = bn; + esz = elemsizeof(tn); + signed_elem = elemissigned(tn); + }; + if (tn.kind == nkind.N_TPTR) { + isglobalptr = true; + globalname = bn; + esz = elemsizeof(tn); + signed_elem = elemissigned(tn); + }; + }; }; } else { if (base.kind == nkind.N_DOT) { esz = indexbaseesz(c, base); @@ -543,6 +573,31 @@ fn cgindex(c: *cgen, n: *node) void = { emitline(", CX\n"); emitline("\tIMULQ\tCX, AX\n"); }; + if (isglobalarr || isglobalptr) { + if (isglobalarr) { + emitline("\tLEAQ\t"); + emitsymname(c, globalname); + emitline("(SB), BX\n"); + } else { + emitline("\tMOVQ\t"); + emitsymname(c, globalname); + emitline("(SB), BX\n"); + }; + emitline("\tADDQ\tAX, BX\n"); + if (esz == 16) { + emitline("\tMOVQ\t8(BX), CX\n"); + emitline("\tMOVQ\t(BX), AX\n"); + emitline("\tMOVQ\tCX, BX\n"); + return; + }; + if (esz == 1) { emitline("\tMOVZBQ\t(BX), AX\n"); } + else { if (esz == 4) { + if (signed_elem) { emitline("\tMOVSXD\t(BX), AX\n"); } + else { emitline("\tMOVL\t(BX), AX\n"); }; + } + else { emitline("\tMOVQ\t(BX), AX\n"); };}; + return; + }; if (baselocal != nil) { let tn: *node = baselocal.tnode; let isarray: bool = false; @@ -782,7 +837,7 @@ fn cgmatch(c: *cgen, n: *node) void = { let found: bool = false; for (v != nil) { if (v.kind == nkind.N_TNAME) { - if (streq(v.str, patname)) { + if (variantnamematch(v.str, patname)) { want = idx; found = true; v = nil; @@ -1210,6 +1265,71 @@ fn cgdot(c: *cgen, n: *node) void = { }; }; }; + // `xs[i].field` — slice/array/ptr-of-struct element field access. + // Without this the cgen falls through to the module-qualified + // SB fallback below and emits `MOVQ (SB), AX` (linker + // reports `undefined reference to `). cgexpr(c, lhs) + // dispatches to cgindex which leaves the element value in AX + // — for a []*T element that's the *T pointer, so we just chain + // the field load through (AX). + if (lhs != nil) { + if (lhs.kind == nkind.N_INDEX) { + let idxbase: *node = lhs.lhs; + if (idxbase != nil) { if (idxbase.kind == nkind.N_IDENT) { + let lc: *local = localfindnode(c, idxbase.str); + if (lc != nil) { if (lc.tnode != nil) { + let tn: *node = lc.tnode; + let elemt: *node = nil; + let tk: nkind = tn.kind; + if (tk == nkind.N_TSLICE) { elemt = tn.lhs; }; + if (tk == nkind.N_TARRAY) { elemt = tn.lhs; }; + if (tk == nkind.N_TPTR) { elemt = tn.lhs; }; + if (elemt != nil) { if (elemt.kind == nkind.N_TPTR) { + let inner: *node = elemt.lhs; + if (inner != nil) { if (inner.kind == nkind.N_TNAME) { + let sname: str = inner.str; + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + if (streq(fi.fname, fld)) { + cgexpr(c, lhs); // AX = *Struct + if (isstrtype(c, fi.tnode)) { + emitline("\tMOVQ\t"); + emitdispreg((fi.foff + 8): i64, "AX"); + emitline(", BX\n"); + emitline("\tMOVQ\t"); + emitdispreg(fi.foff: i64, "AX"); + emitline(", AX\n"); + return; + }; + if (isfloattype(c, fi.tnode)) { + let mov: str = "MOVSD"; + if (isf32type(c, fi.tnode)) { mov = "MOVSS"; }; + emitline("\t"); + emitline(mov); + emitline("\t"); + emitdispreg(fi.foff: i64, "AX"); + emitline(", X0\n"); + return; + }; + let lop: str = fieldloadop(fi); + emitline("\t"); + emitline(lop); + emitline("\t"); + emitdispreg(fi.foff: i64, "AX"); + emitline(", AX\n"); + return; + }; + fi = fi.finext; + }; + }; + };}; + };}; + };}; + };}; + }; + }; // Module-qualified value reference: `mod.name` where `mod` // is nkind.N_IDENT bound as skind.SK_USE and the leaf isn't a local. // Treat as a SB symbol — `MOVQ leaf(SB), AX`. Same fallback @@ -1287,6 +1407,120 @@ fn cgdot(c: *cgen, n: *node) void = { }; }; }; + // Chained `(ident).f1.f2` read where f1 is a struct-by-value + // field. Mirror of the cgassign branch added for the same shape. + // Without this, `L.cur.kind` (cur a by-value struct of *L) + // falls into the SB-fallback and emits `MOVQ kind(SB), AX`. + if (lhs != nil) { + if (lhs.kind == nkind.N_DOT) { + let inner: *node = lhs.lhs; + let innerfld: str = lhs.str; + if (inner != nil) { if (inner.kind == nkind.N_IDENT) { + let lc: *local = localfindnode(c, inner.str); + if (lc != nil) { if (lc.tnode != nil) { + let tn: *node = lc.tnode; + let lkind: nkind = tn.kind; + let outname: str; + outname.ptr = nil; outname.len = 0; + let isptr: bool = false; + if (lkind == nkind.N_TNAME) { outname = tn.str; }; + if (lkind == nkind.N_TPTR) { + let pe: *node = tn.lhs; + if (pe != nil) { if (pe.kind == nkind.N_TNAME) { + outname = pe.str; + isptr = true; + };}; + }; + if (outname.len > 0) { + let osi: *structinfo = structlookup(c, outname); + if (osi != nil) { + let ofi: *fieldinfo = osi.fields; + for (ofi != nil) { + if (streq(ofi.fname, innerfld)) { + let oft: *node = ofi.tnode; + if (oft != nil) { if (oft.kind == nkind.N_TNAME) { + if (primsize(oft.str) == 0) { + let isi: *structinfo = structlookup(c, oft.str); + if (isi != nil) { + let ffi: *fieldinfo = isi.fields; + for (ffi != nil) { + if (streq(ffi.fname, fld)) { + let totoff: i32 = ofi.foff + ffi.foff; + if (isstrtype(c, ffi.tnode)) { + if (isptr) { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), CX\n"); + emitline("\tMOVQ\t"); + emitdispreg((totoff + 8): i64, "CX"); + emitline(", BX\n"); + emitline("\tMOVQ\t"); + emitdispreg(totoff: i64, "CX"); + emitline(", AX\n"); + } else { + emitline("\tMOVQ\t"); + emitoff((lc.off + totoff): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\t"); + emitoff((lc.off + totoff + 8): i64); + emitline("(BP), BX\n"); + }; + return; + }; + if (isfloattype(c, ffi.tnode)) { + let mov: str = "MOVSD"; + if (isf32type(c, ffi.tnode)) { mov = "MOVSS"; }; + if (isptr) { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + emitline("\t"); + emitline(mov); + emitline("\t"); + emitdispreg(totoff: i64, "BX"); + emitline(", X0\n"); + } else { + emitline("\t"); + emitline(mov); + emitline("\t"); + emitoff((lc.off + totoff): i64); + emitline("(BP), X0\n"); + }; + return; + }; + let lop: str = fieldloadop(ffi); + if (isptr) { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + emitline("\t"); + emitline(lop); + emitline("\t"); + emitdispreg(totoff: i64, "BX"); + emitline(", AX\n"); + } else { + emitline("\t"); + emitline(lop); + emitline("\t"); + emitoff((lc.off + totoff): i64); + emitline("(BP), AX\n"); + }; + return; + }; + ffi = ffi.finext; + }; + }; + }; + };}; + }; + ofi = ofi.finext; + }; + }; + }; + };}; + };}; + }; + }; return; }; @@ -1732,43 +1966,48 @@ fn cgcall(c: *cgen, n: *node) void = { // SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else // pops into the int stream (DI..R9) per the SysV ABI. Walk the // args list alongside the pop counter so we know each arg's - // register class. + // register class. SysV has only 6 int arg regs (DI/SI/DX/CX/R8/R9); + // the remaining slots stay on the stack and the callee reads them + // via 16+8*k(BP). Caller-cleanup is emitted after the CALL. let intidx: i32 = 0; let fpidx: i32 = 0; let a: *node = n.list; let popped: i32 = 0; + let stackslots: i32 = 0; for (a != nil) { let fk: i32 = exprfloatkind(c, a); if (fk != 0) { let mov: str = "MOVSD"; if (fk == 1) { mov = "MOVSS"; }; - emitline("\t"); - emitline(mov); - emitline("\t(SP), "); - emitline(fargregname(fpidx)); - emitline("\n"); - emitline("\tADDQ\t$8, SP\n"); - fpidx += 1; + if (fpidx < 8) { + emitline("\t"); + emitline(mov); + emitline("\t(SP), "); + emitline(fargregname(fpidx)); + emitline("\n"); + emitline("\tADDQ\t$8, SP\n"); + fpidx += 1; + } else { + stackslots += 1; + }; popped += 1; } else { - emitline("\tPOPQ\t"); - emitline(argregname(intidx)); - emitline("\n"); - intidx += 1; - popped += 1; - // Multi-word args (str=2, slice/tagged=3): drain - // the remaining words into successive int regs. let extra: i32 = 0; if (nodeisstr(c, a)) { extra = 1; }; if (nodeisslice(c, a)) { extra = 2; }; - let e: i32 = 0; - for (e < extra) { - emitline("\tPOPQ\t"); - emitline(argregname(intidx)); - emitline("\n"); - intidx += 1; + let words: i32 = 1 + extra; + let w: i32 = 0; + for (w < words) { + if (intidx < 6) { + emitline("\tPOPQ\t"); + emitline(argregname(intidx)); + emitline("\n"); + intidx += 1; + } else { + stackslots += 1; + }; popped += 1; - e += 1; + w += 1; }; }; a = a.next; @@ -1779,10 +2018,14 @@ fn cgcall(c: *cgen, n: *node) void = { // case here is identical pre-port behaviour. let i: i32 = popped; for (i < nargs) { - emitline("\tPOPQ\t"); - emitline(argregname(intidx)); - emitline("\n"); - intidx += 1; + if (intidx < 6) { + emitline("\tPOPQ\t"); + emitline(argregname(intidx)); + emitline("\n"); + intidx += 1; + } else { + stackslots += 1; + }; i += 1; }; let callee: *node = n.lhs; @@ -1869,6 +2112,14 @@ fn cgcall(c: *cgen, n: *node) void = { }; emitline("(SB)\n"); }; + // Caller cleanup for stack-passed args (args 7+, or any + // overflow past the int/float reg windows). Mirrors C cgen: + // pushed 8 bytes each, ADDQ them off after the CALL. + if (stackslots > 0) { + emitline("\tADDQ\t$"); + emitint((stackslots * 8): i64); + emitline(", SP\n"); + }; // SysV returns 16-byte aggregates in (AX, DX). Our str // convention is (AX, BX), so shuffle for str-returning calls. if (calleename.len > 0) { @@ -1991,12 +2242,30 @@ fn cgassign(c: *cgen, n: *node) void = { let idx: *node = lhs.rhs; let esz: i32 = 8; let baselocal: *local = nil; + let isglobalarr: bool = false; + let isglobalptr: bool = false; + let globalname: str; + globalname.ptr = nil; globalname.len = 0; if (base != nil) { if (base.kind == nkind.N_IDENT) { let bn: str = base.str; baselocal = localfindnode(c, bn); if (baselocal != nil) { esz = elemsizeof(baselocal.tnode); + } else { + let tn: *node = letvartnode(c, bn); + if (tn != nil) { + if (tn.kind == nkind.N_TARRAY) { + isglobalarr = true; + globalname = bn; + esz = elemsizeof(tn); + }; + if (tn.kind == nkind.N_TPTR) { + isglobalptr = true; + globalname = bn; + esz = elemsizeof(tn); + }; + }; }; } else { if (base.kind == nkind.N_DOT) { esz = indexbaseesz(c, base); @@ -2013,7 +2282,15 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tIMULQ\tCX, AX\n"); }; emitline("\tPUSHQ\tAX\n"); // scaled idx - if (baselocal != nil) { + if (isglobalarr) { + emitline("\tLEAQ\t"); + emitsymname(c, globalname); + emitline("(SB), BX\n"); + } else { if (isglobalptr) { + emitline("\tMOVQ\t"); + emitsymname(c, globalname); + emitline("(SB), BX\n"); + } else { if (baselocal != nil) { let tn: *node = baselocal.tnode; let isarray: bool = false; if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isarray = true; }; }; @@ -2029,7 +2306,7 @@ fn cgassign(c: *cgen, n: *node) void = { } else { cgexpr(c, base); emitline("\tMOVQ\tAX, BX\n"); - }; + };};}; emitline("\tPOPQ\tAX\n"); // scaled idx emitline("\tADDQ\tAX, BX\n"); emitline("\tPOPQ\tAX\n"); // value @@ -2160,6 +2437,20 @@ fn cgassign(c: *cgen, n: *node) void = { let fn_: str = fi.fname; if (streq(fn_, fld)) { cgexpr(c, n.rhs); + // str field: cgexpr left (AX=ptr, BX=len); + // store both halves at +0/+8. Without this, + // `L.src = s` would only write the ptr and + // `L.src.len` would carry whatever was on the + // stack. + if (isstrtype(c, fi.tnode)) { + emitline("\tMOVQ\tAX, "); + emitoff((lc.off + fi.foff): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tBX, "); + emitoff((lc.off + fi.foff + 8): i64); + emitline("(BP)\n"); + return; + }; // f64/f32 direct struct local store: route via X0. if (isfloattype(c, fi.tnode)) { let mov: str = "MOVSD"; @@ -2436,6 +2727,130 @@ fn cgassign(c: *cgen, n: *node) void = { }; }; }; + // Chained `(ident).f1.f2 = v` where f1 is a struct-by-value + // field. The earlier chained-DOT branch handles f1: *T (deref + // then store). This handles f1: T (in-place sub-struct), which + // would otherwise silently emit no store — lispcore's lexer had + // to flatten `cur.kind`/`cur.ival`/... into top-level fields to + // work around it. Only plain `=` is wired; compound on a by- + // value sub-field hasn't surfaced. + if (lhs != nil) { + if (lhs.kind == nkind.N_DOT) { + let base: *node = lhs.lhs; + let fld: str = lhs.str; + if (base != nil) { if (base.kind == nkind.N_DOT) { + let inner: *node = base.lhs; + let innerfld: str = base.str; + if (inner != nil) { if (inner.kind == nkind.N_IDENT) { + let lc: *local = localfindnode(c, inner.str); + if (lc != nil) { if (lc.tnode != nil) { + let tn: *node = lc.tnode; + let lkind: nkind = tn.kind; + let outname: str; + outname.ptr = nil; outname.len = 0; + let isptr: bool = false; + if (lkind == nkind.N_TNAME) { outname = tn.str; }; + if (lkind == nkind.N_TPTR) { + let pe: *node = tn.lhs; + if (pe != nil) { if (pe.kind == nkind.N_TNAME) { + outname = pe.str; + isptr = true; + };}; + }; + if (outname.len > 0) { + let osi: *structinfo = structlookup(c, outname); + if (osi != nil) { + let ofi: *fieldinfo = osi.fields; + for (ofi != nil) { + if (streq(ofi.fname, innerfld)) { + let oft: *node = ofi.tnode; + if (oft != nil) { if (oft.kind == nkind.N_TNAME) { + if (primsize(oft.str) == 0) { + let isi: *structinfo = structlookup(c, oft.str); + if (isi != nil) { + let ffi: *fieldinfo = isi.fields; + for (ffi != nil) { + if (streq(ffi.fname, fld)) { + if (n.op == tkind.TK_ASSIGN) { + let totoff: i32 = ofi.foff + ffi.foff; + cgexpr(c, n.rhs); + if (isstrtype(c, ffi.tnode)) { + if (isptr) { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), CX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg(totoff: i64, "CX"); + emitline("\n"); + emitline("\tMOVQ\tBX, "); + emitdispreg((totoff + 8): i64, "CX"); + emitline("\n"); + } else { + emitline("\tMOVQ\tAX, "); + emitoff((lc.off + totoff): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tBX, "); + emitoff((lc.off + totoff + 8): i64); + emitline("(BP)\n"); + }; + return; + }; + if (isfloattype(c, ffi.tnode)) { + let mov: str = "MOVSD"; + if (isf32type(c, ffi.tnode)) { mov = "MOVSS"; }; + if (isptr) { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + emitline("\t"); + emitline(mov); + emitline("\tX0, "); + emitdispreg(totoff: i64, "BX"); + emitline("\n"); + } else { + emitline("\t"); + emitline(mov); + emitline("\tX0, "); + emitoff((lc.off + totoff): i64); + emitline("(BP)\n"); + }; + return; + }; + let sop: str = fieldstoreop(ffi); + if (isptr) { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + emitline("\t"); + emitline(sop); + emitline("\tAX, "); + emitdispreg(totoff: i64, "BX"); + emitline("\n"); + } else { + emitline("\t"); + emitline(sop); + emitline("\tAX, "); + emitoff((lc.off + totoff): i64); + emitline("(BP)\n"); + }; + return; + }; + }; + ffi = ffi.finext; + }; + }; + }; + };}; + }; + ofi = ofi.finext; + }; + }; + }; + };}; + };}; + };}; + }; + }; // Local-ident target — plain `=` and the simple compound // forms (+= -= *= /=); other compounds fall back to // "evaluate rhs, replace". Mirrors C cgen's IDENT-assign path. diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index e48cda92..0be233c9 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -143,6 +143,25 @@ fn cgreturn(c: *cgen, n: *node) void = { // Nullable folded `(*T | void)`: just one word; AX is // already the pointer (or 0). No shuffle, no tag. if (istaggedtype(c.fnret)) { + // Forwarding a fallible call: `return f();` where f + // also returns a tagged union. The result is already + // in (AX=tag, DX=v0, CX=v1) — no shuffle, no tag. + // Mirrors the rhsreturnstagged path in cglet and the + // !type_istagged guard in C cgen's N_RETURN. + let forwardtagged: bool = false; + if (rhs.kind == nkind.N_CALL) { + let callee: *node = rhs.lhs; + if (callee != nil) { + let calleename: str; + calleename.ptr = nil; calleename.len = 0; + if (callee.kind == nkind.N_IDENT) { calleename = callee.str; }; + if (callee.kind == nkind.N_DOT) { calleename = callee.str; }; + if (calleename.len > 0) { + let rt: *node = fnretlookup(c, calleename); + if (istaggedtype(rt)) { forwardtagged = true; }; + }; + }; + }; cgexpr(c, rhs); if (isnullabletype(c.fnret)) { emitline("\tMOVQ\tBP, SP\n"); @@ -151,6 +170,13 @@ fn cgreturn(c: *cgen, n: *node) void = { c.lastwasreturn = 1; return; }; + if (forwardtagged) { + emitline("\tMOVQ\tBP, SP\n"); + emitline("\tPOPQ\tBP\n"); + emitline("\tRET\n"); + c.lastwasreturn = 1; + return; + }; let idx: i32 = taggedvariantindex(c, c.fnret, rhs); if (nodeisstr(c, rhs)) { emitline("\tMOVQ\tBX, CX\n"); @@ -213,7 +239,12 @@ fn cgexprstmt(c: *cgen, n: *node) void = { fn cglet(c: *cgen, n: *node) void = { let nm: str = n.str; let sz: i32 = letslotsize(c, n); - let off: i32 = localadd(c, nm, sz, n.lhs); + // `let x = f()?` has no annotation but the cgen's struct-field + // paths need a tnode to dispatch off. Infer from f's tagged + // success variant — see inferletcalltype. + let tn: *node = n.lhs; + if (tn == nil) { tn = inferletcalltype(c, n.rhs); }; + let off: i32 = localadd(c, nm, sz, tn); if (n.rhs != nil) { let rhs: *node = n.rhs; // Tagged-union init: `let r: (T | E) = expr;`. @@ -222,8 +253,8 @@ fn cglet(c: *cgen, n: *node) void = { // just spill all three. // - Otherwise rhs is a bare variant value: pack tag + // value(s). - if (istaggedtype(n.lhs)) { - let nullable: bool = isnullabletype(n.lhs); + if (istaggedtype(tn)) { + let nullable: bool = isnullabletype(tn); let rhsreturnstagged: bool = false; if (rhs.kind == nkind.N_CALL) { let callee: *node = rhs.lhs; @@ -266,7 +297,7 @@ fn cglet(c: *cgen, n: *node) void = { c.lastwasreturn = 0; return; }; - let tagidx: i32 = taggedvariantindex(c, n.lhs, rhs); + let tagidx: i32 = taggedvariantindex(c, tn, rhs); if (tagidx < 0) { tagidx = 0; }; if (nodeisstr(c, rhs)) { emitline("\tMOVQ\tAX, "); @@ -841,9 +872,9 @@ fn cgforrange(c: *cgen, n: *node) void = { bind_signed[nbinds] = signf; let bnm: str = m.str; if (bnm.len > 0) { - bind_off[nbinds] = localadd(c, bnm, slot_sz, nil); + bind_off[nbinds] = localadd(c, bnm, slot_sz, tp); } else { - bind_off[nbinds] = localalloc(c, mkscratchname(c, "fr"), slot_sz, nil); + bind_off[nbinds] = localalloc(c, mkscratchname(c, "fr"), slot_sz, tp); }; field_off += fsz; nbinds += 1; @@ -863,9 +894,12 @@ fn cgforrange(c: *cgen, n: *node) void = { bind_signed[0] = paramissigned(elemt); }; if (n.str.len > 0) { - bind_off[0] = localadd(c, n.str, slot_sz, nil); + // Register with elem tnode so x.field on a loop + // var resolves through the standard local-typed + // path instead of falling into the SB fallback. + bind_off[0] = localadd(c, n.str, slot_sz, elemt); } else { - bind_off[0] = localalloc(c, mkscratchname(c, "fr"), slot_sz, nil); + bind_off[0] = localalloc(c, mkscratchname(c, "fr"), slot_sz, elemt); }; nbinds = 1; }; diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 3904f19c..97de3c9c 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -190,13 +190,11 @@ fn nodeisstr(c: *cgen, n: *node) bool = { let nm: str = n.str; let lc: *local = localfindnode(c, nm); if (lc != nil) { - let tn: *node = lc.tnode; - if (tn != nil) { - if (tn.kind == nkind.N_TNAME) { - let tnm: str = tn.str; - if (streq(tnm, "str")) { return true; }; - }; - }; + // Use isstrtype so `!str` aliases (parserr = !str) and + // `type foo = str;` chains resolve through. The bare + // `streq("str", ...)` test missed them and dropped the + // MOVQ BX,CX shuffle on returns of str-aliased locals. + if (isstrtype(c, lc.tnode)) { return true; }; }; return false; }; @@ -684,11 +682,88 @@ fn primsize(name: str) i32 = { return 0; }; +// variantnamematch — tagged-union variant names are compared as if +// they'd been alias-resolved. Pattern names can be module-qualified +// (`strconv.invalid` from a `case let e: strconv.invalid =>`), +// while the variant's declared name inside its own module is bare +// (`invalid`). With no checker the cgen can't follow imports, so we +// accept exact match plus suffix-after-`.` on either side. Mirrors +// the C cgen's type_eq, which goes through resolved Type pointers. +fn variantnamematch(vname: str, pname: str) bool = { + if (streq(vname, pname)) { return true; }; + // `pname` is qualified, `vname` is bare: drop module prefix. + let i: i32 = 0; + for (i < pname.len) { + if (pname[i] == '.': u8) { + let tail: str; + tail.ptr = pname.ptr + i + 1; + tail.len = pname.len - i - 1; + if (streq(tail, vname)) { return true; }; + }; + i += 1; + }; + // `vname` is qualified, `pname` is bare: same trick in reverse. + let j: i32 = 0; + for (j < vname.len) { + if (vname[j] == '.': u8) { + let tail: str; + tail.ptr = vname.ptr + j + 1; + tail.len = vname.len - j - 1; + if (streq(tail, pname)) { return true; }; + }; + j += 1; + }; + return false; +}; + +// inferletcalltype — for an annotation-less `let x = expr;`, return +// a usable tnode for cgen's struct-aware paths. Today: `let x = +// f()?` infers x's type from the success variant of f's tagged +// return; without this, x has tnode = nil and `x.field` falls into +// the SB-symbol fallback (linker reports `undefined reference to +// `). We don't infer for plain `let x = f()` yet — +// non-tagged returns don't carry their type back the same way. +fn inferletcalltype(c: *cgen, rhs: *node) *node = { + if (rhs == nil) { return nil; }; + // `?` (N_TRYPROP) and `!` (N_TRYUNW) both unwrap a tagged + // return to its success variant; the rhs we want the type of + // is the inner call expression. + let unwrap: bool = false; + let call: *node = rhs; + if (rhs.kind == nkind.N_TRYPROP) { call = rhs.lhs; unwrap = true; }; + if (rhs.kind == nkind.N_TRYUNW) { call = rhs.lhs; unwrap = true; }; + if (call == nil) { return nil; }; + if (call.kind != nkind.N_CALL) { return nil; }; + let callee: *node = call.lhs; + if (callee == nil) { return nil; }; + let cname: str; + cname.ptr = nil; cname.len = 0; + if (callee.kind == nkind.N_IDENT) { cname = callee.str; }; + if (callee.kind == nkind.N_DOT) { cname = callee.str; }; + if (cname.len == 0) { return nil; }; + let rt: *node = fnretlookup(c, cname); + if (rt == nil) { return nil; }; + if (unwrap) { + // Strip error variants — success type is the first + // variant of the tagged return. + if (rt.kind != nkind.N_TTAGGED) { return nil; }; + return rt.list; + }; + // Plain call: declared return type is the local's type. + return rt; +}; + // letslotsize — slot size for a `let` binding. Like slotsize, but // detects `[_]T = arrlit;` (the type-AST has rhs == nil as the // length-inferred sentinel) and computes count × element-size from // the initialiser. Used by both scanlocals (prologue sizing) and // cglet (slot alloc) so they agree on the frame layout. +// +// `let x = f();` (no annotation): infer from `f`'s declared return +// type so a 24B tagged-union return reserves all three spill slots, +// not the default 8B. Without this, the AX:DX:CX spill in cglet's +// tagged-init branch writes past the local and tramples the next +// slot. export fn letslotsize(c: *cgen, n: *node) i32 = { // `[_]T = arrlit;` — inferred-length array. slotsize would // return elem_size * 1 (treating missing length as 1); intercept @@ -727,7 +802,13 @@ export fn letslotsize(c: *cgen, n: *node) i32 = { }; }; }; - return slotsize(c, n.lhs); + if (n.lhs != nil) { return slotsize(c, n.lhs); }; + // Annotation-less init: defer to the call's return type if we + // can infer it. Tagged-union returns need 24B; everything else + // matches slotsize on the inferred type. + let inferred: *node = inferletcalltype(c, n.rhs); + if (inferred != nil) { return slotsize(c, inferred); }; + return 8; }; fn slotsize(c: *cgen, typn: *node) i32 = { @@ -779,6 +860,19 @@ fn slotsize(c: *cgen, typn: *node) i32 = { // Named struct lookup. let si: *structinfo = structlookup(c, nm); if (si != nil) { return si.totsize; }; + // Type alias (`type foo = !str;` / `type foo = bar;`): + // follow it so a tagged-union variant of a !str-aliased + // error type contributes 16 bytes to the max payload + // rather than 8 (the default). + if (c != nil) { + let aliased: *node = aliaslookup(c, nm); + if (aliased != nil) { + if (aliased.kind == nkind.N_TBANG) { + return slotsize(c, aliased.lhs); + }; + return slotsize(c, aliased); + }; + }; return 8; }; if (k == nkind.N_TARRAY) { @@ -930,7 +1024,20 @@ fn isstrtype(c: *cgen, t: *node) bool = { if (isstrtyperaw(t)) { return true; }; if (c == nil) { return false; }; let r: *node = resolvetype(c, t); - return isstrtyperaw(r); + if (isstrtyperaw(r)) { return true; }; + // `parserr = !str` — `!T` aliases shouldn't hide their + // underlying type from str-routing. Unwrap and re-check. + if (r != nil) { + if (r.kind == nkind.N_TBANG) { + let inner: *node = r.lhs; + if (isstrtyperaw(inner)) { return true; }; + if (inner != nil) { + let r2: *node = resolvetype(c, inner); + if (isstrtyperaw(r2)) { return true; }; + }; + }; + }; + return false; }; fn isslicetyperaw(t: *node) bool = { @@ -1061,6 +1168,48 @@ export fn exprfloatkind(c: *cgen, n: *node) i32 = { }; return 0; }; + if (k == nkind.N_DOT) { + // `p.field` where the struct field is f64/f32. Without this, + // `v.fval: i64` lowers to CVTSI on an integer-load value + // instead of CVTTSD2SI on the X0 the cgdot path actually + // emits for an f64 field. + let base: *node = n.lhs; + let fld: str = n.str; + if (base != nil) { + let sname: str; + sname.ptr = nil; sname.len = 0; + if (base.kind == nkind.N_IDENT) { + let lc: *local = localfindnode(c, base.str); + if (lc != nil) { + let tn: *node = lc.tnode; + if (tn != nil) { + if (tn.kind == nkind.N_TNAME) { sname = tn.str; }; + if (tn.kind == nkind.N_TPTR) { + let pe: *node = tn.lhs; + if (pe != nil) { + if (pe.kind == nkind.N_TNAME) { sname = pe.str; }; + }; + }; + }; + }; + }; + if (sname.len > 0) { + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + if (streq(fi.fname, fld)) { + if (isf32type(c, fi.tnode)) { return 1; }; + if (isfloattype(c, fi.tnode)) { return 2; }; + return 0; + }; + fi = fi.finext; + }; + }; + }; + }; + return 0; + }; return 0; }; @@ -1129,6 +1278,19 @@ fn rhstargetname(c: *cgen, rhs: *node) str = { return nm; }; if (rhs.kind == nkind.N_STRLIT) { return "str"; }; + // `T{}` carries its type name on the lhs N_IDENT — the parser + // builds `N_STRUCTLIT{ lhs = N_IDENT("T"), list = fields }`. + // Needed so `return eof{};` (variant of a tagged union) resolves + // to the `eof` variant index rather than falling through to the + // "first non-str variant" fallback in taggedvariantindex. + if (rhs.kind == nkind.N_STRUCTLIT) { + let tref: *node = rhs.lhs; + if (tref != nil) { + if (tref.kind == nkind.N_IDENT) { return tref.str; }; + if (tref.kind == nkind.N_TNAME) { return tref.str; }; + }; + return nm; + }; if (rhs.kind == nkind.N_IDENT) { let lc: *local = localfindnode(c, rhs.str); if (lc != nil) { @@ -1154,7 +1316,7 @@ fn taggedvariantindex(c: *cgen, tagged: *node, rhs: *node) i32 = { let idx: i32 = 0; for (v != nil) { if (v.kind == nkind.N_TNAME) { - if (streq(v.str, wantname)) { return idx; }; + if (variantnamematch(v.str, wantname)) { return idx; }; }; v = v.next; idx += 1;