// selfhost/cmd/wcc/cgenexpr.ww — split out of cgen.ww. // // cgexpr is a thin dispatcher over n.kind; each non-trivial branch // lives in a per-kind helper (cgstrlit, cgident, cgindex, cgmatch, // cgdot, cgun, cgbin, cgcall, cgassign). Trivial literal loads // (nkind.N_INTLIT, nkind.N_RUNELIT, nkind.N_TRUE/FALSE/NIL, nkind.N_CAST) stay inline. // // The remainder of cgen lives in cgen.ww (foundation: types, emit // primitives, the collect* tables, FFI/module maps) and cgenstmt.ww // (cgstmt). // // `use cgenexpr;` is unnecessary at consumer sites — cgen.ww imports // this file, so any caller of cgen transitively gets cgexpr. use os; use mem; use ast; use tok; use typ; use sym; use strconv; fn cgexpr(c: *cgen, n: *node) void = { if (n == nil) { return; }; let k: nkind = n.kind; if (k == nkind.N_INTLIT) { // Print signed (i64), not unsigned (u64). C cgen uses // `$%lld` so 64-bit constants with bit 63 set show up as // negative — e.g. FNV-1a's offset basis prints as // $-3750763034362895579, not $14695981039346656037. emitline("\tMOVQ\t$"); emitint(n.uval: i64); emitline(", AX\n"); return; }; if (k == nkind.N_FLOATLIT) { // Materialise the f64 bit pattern in AX, push, then MOVSD it // into X0. The bits come from n.uval — the parser populates // it from the lexer's bitcast of t.fval, so this path stays // integer-only (no SSE in the cgen source). The f32 // narrowing is handled at the consumer site, not here — the // literal always carries the full double precision until // typed by context. emitline("\tMOVQ\t$"); emitint(n.uval: i64); emitline(", AX\n"); emitline("\tPUSHQ\tAX\n"); emitline("\tMOVSD\t(SP), X0\n"); emitline("\tADDQ\t$8, SP\n"); return; }; if (k == nkind.N_RUNELIT) { emitline("\tMOVQ\t$"); emitint(n.uval: i64); emitline(", AX\n"); return; }; if (k == nkind.N_STRLIT) { cgstrlit(c, n); return; }; if (k == nkind.N_TRUE) { emitline("\tMOVQ\t$1, AX\n"); return; }; if (k == nkind.N_FALSE) { emitline("\tMOVQ\t$0, AX\n"); return; }; if (k == nkind.N_NIL) { emitline("\tMOVQ\t$0, AX\n"); return; }; if (k == nkind.N_VOIDLIT) { // void value: zero-size, but the consumer's ABI expects a // deterministic AX. Emit 0 like nil/false do. emitline("\tMOVQ\t$0, AX\n"); return; }; if (k == nkind.N_IDENT) { cgident(c, n); return; }; if (k == nkind.N_INDEX) { cgindex(c, n); return; }; if (k == nkind.N_SLICE) { cgslice(c, n); return; }; if (k == nkind.N_MATCH) { cgmatch(c, n); return; }; if (k == nkind.N_CAST) { cgcast(c, n); return; }; if (k == nkind.N_DOT) { cgdot(c, n); return; }; if (k == nkind.N_UN) { cgun(c, n); return; }; if (k == nkind.N_BIN) { cgbin(c, n); return; }; if (k == nkind.N_CALL) { cgcall(c, n); return; }; if (k == nkind.N_ASSIGN) { cgassign(c, n); return; }; if (k == nkind.N_TRYPROP) { cgtryprop(c, n); return; }; 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 // tagged-union type expression `tagged`. -1 if `tagged` isn't an // nkind.N_TTAGGED or no variant matches. Mirrors the lookup that cgmatch // does inline; pulled out so `is` / `as` can reuse it. fn cgtagvariantidx(c: *cgen, tagged: *node, vt: *node) i32 = { if (tagged == nil) { return -1; }; if (vt == nil) { return -1; }; if (tagged.kind != nkind.N_TTAGGED) { return -1; }; let want: str; want.ptr = nil; want.len = 0; if (vt.kind == nkind.N_TNAME) { want = vt.str; }; if (want.len == 0) { return -1; }; return flatvariantidx(c, tagged, want); }; // cgtryprop — `e?` propagates the error variant up the stack. // Legacy semantics only (success tag = 0). No tag remap; the // selfhost code that uses ? today has the same variant order in // operand and enclosing fn. fn cgtryprop(c: *cgen, n: *node) void = { cgexpr(c, n.lhs); // AX = tag. If non-zero, this is an error; pop frame and RET. let cl: str = mklabel(c, "tryprop_ok"); emitline("\tCMPQ\t$0, AX\n"); emitline("\tJE\t"); emitline(cl); emitline("\n"); emitline("\tMOVQ\tBP, SP\n\tPOPQ\tBP\n\tRET\n"); emitlabel(cl); // Success: unwrap value. Tag-only result was AX; the rest of // the codegen expects the success value in AX (and BX for str). // AX=tag, DX=val0, CX=val1 from the call ABI. For str success, // shuffle (DX,CX) → (AX,BX); else move DX → AX. let succisstr: bool = false; if (n.lhs != nil) { if (n.lhs.kind == nkind.N_CALL) { let callee: *node = n.lhs.lhs; if (callee != 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) { let rt: *node = fnretlookup(c, cname); if (rt != nil) { if (rt.kind == nkind.N_TTAGGED) { let first: *node = rt.list; if (first != nil) { if (isstrtype(c, first)) { succisstr = true; }; }; }; }; }; }; }; }; if (succisstr) { emitline("\tMOVQ\tCX, BX\n"); }; emitline("\tMOVQ\tDX, AX\n"); return; }; // cgtryunw — `e!` aborts on the error variant via exit(1). Legacy // semantics (success tag = 0). fn cgtryunw(c: *cgen, n: *node) void = { cgexpr(c, n.lhs); let cl: str = mklabel(c, "tryunw_ok"); emitline("\tCMPQ\t$0, AX\n"); emitline("\tJE\t"); emitline(cl); emitline("\n"); emitline("\tMOVQ\t$1, DI\n\tMOVQ\t$60, AX\n\tSYSCALL\n"); emitlabel(cl); // Unwrap success value. (Same shuffle pattern as cgtryprop.) let succisstr: bool = false; if (n.lhs != nil) { if (n.lhs.kind == nkind.N_CALL) { let callee: *node = n.lhs.lhs; if (callee != 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) { let rt: *node = fnretlookup(c, cname); if (rt != nil) { if (rt.kind == nkind.N_TTAGGED) { let first: *node = rt.list; if (first != nil) { if (isstrtype(c, first)) { succisstr = true; }; }; }; }; }; }; }; }; if (succisstr) { emitline("\tMOVQ\tCX, BX\n"); }; emitline("\tMOVQ\tDX, AX\n"); return; }; fn cgtypetest(c: *cgen, n: *node) void = { // `e is T` — load the lhs's tag, compare against T's variant // index, set AX = (tag == idx). Result type is bool. // // Slot resolution is inlined (rather than factored into a helper // with output parameters): wwstage cgen has a trap with i32 // stored via *i32 in this context — direct assignment of the // local works, indirection through &scrutoff drops sign bits. let lhs: *node = n.lhs; let scrutoff: i32 = 0; let scrutt: *node = nil; if (lhs != nil) { if (lhs.kind == nkind.N_IDENT) { let lc: *local = localfindnode(c, lhs.str); if (lc != nil) { scrutoff = lc.off; scrutt = resolvetagged(c, lc.tnode); }; }; }; let want: i32 = cgtagvariantidx(c, scrutt, n.rhs); if (want < 0) { want = 0; }; emitline("\tMOVQ\t"); emitoff(scrutoff: i64); emitline("(BP), AX\n"); let nel: str = mklabel(c, "is_ne"); let dnl: str = mklabel(c, "is_done"); emitline("\tCMPQ\t$"); emitint(want: i64); emitline(", AX\n"); emitline("\tJNE\t"); emitline(nel); emitline("\n\tMOVQ\t$1, AX\n\tJMP\t"); emitline(dnl); emitline("\n"); emitlabel(nel); emitline("\tMOVQ\t$0, AX\n"); emitlabel(dnl); return; }; // isenumexpr — does this expression's static type resolve to an enum? // Recognises enum-member access (`Foo.MEMBER`), enum-typed local // idents, and nkind.N_BIN whose either operand is enum (so `R | W` flows // through the cast pass-through too). fn isenumexpr(c: *cgen, e: *node) bool = { if (e == nil) { return false; }; let k: nkind = e.kind; if (k == nkind.N_DOT) { if (e.lhs != nil) { if (e.lhs.kind == nkind.N_IDENT) { if (enumlookup(c, e.lhs.str) != nil) { return true; }; }; }; }; if (k == nkind.N_IDENT) { let lc: *local = localfindnode(c, e.str); if (lc != nil) { if (lc.tnode != nil) { if (lc.tnode.kind == nkind.N_TNAME) { if (enumlookup(c, lc.tnode.str) != nil) { return true; }; }; }; }; }; if (k == nkind.N_BIN) { if (isenumexpr(c, e.lhs)) { return true; }; if (isenumexpr(c, e.rhs)) { return true; }; }; if (k == nkind.N_UN) { if (isenumexpr(c, e.lhs)) { return true; }; }; return false; }; fn isenumtype(c: *cgen, t: *node) bool = { if (t == nil) { return false; }; if (t.kind == nkind.N_TENUM) { return true; }; if (t.kind == nkind.N_TNAME) { if (enumlookup(c, t.str) != nil) { return true; }; }; return false; }; fn cgtypeassert(c: *cgen, n: *node) void = { // Enum ↔ integer: reinterpret-only. The LHS value already // occupies AX (or AX:BX for str variants, irrelevant here); // no tag/unwrap. Matches cmd/w6c/cgen.c's same short-circuit. if (isenumexpr(c, n.lhs) || isenumtype(c, n.rhs)) { cgexpr(c, n.lhs); return; }; // `e as T` — load tag, abort (exit 1) if tag != T's variant // index, otherwise unwrap to T's ABI: scalar/ptr → AX, 16B // str → (AX, BX). Mirrors cgmatch's slot-based value load. // Slot resolution inlined; see cgtypetest comment. let lhs: *node = n.lhs; let scrutoff: i32 = 0; let scrutt: *node = nil; if (lhs != nil) { if (lhs.kind == nkind.N_IDENT) { let lc: *local = localfindnode(c, lhs.str); if (lc != nil) { scrutoff = lc.off; scrutt = resolvetagged(c, lc.tnode); }; }; }; let want: i32 = cgtagvariantidx(c, scrutt, n.rhs); if (want < 0) { want = 0; }; let okl: str = mklabel(c, "asrt_ok"); emitline("\tMOVQ\t"); emitoff(scrutoff: i64); emitline("(BP), AX\n"); emitline("\tCMPQ\t$"); emitint(want: i64); emitline(", AX\n"); emitline("\tJE\t"); emitline(okl); emitline("\n\tMOVQ\t$1, DI\n\tMOVQ\t$60, AX\n\tSYSCALL\n"); emitlabel(okl); emitline("\tMOVQ\t"); emitoff((scrutoff + 8): i64); emitline("(BP), AX\n"); if (isstrtype(c, n.rhs)) { emitline("\tMOVQ\t"); emitoff((scrutoff + 16): i64); emitline("(BP), BX\n"); }; return; }; fn cgcast(c: *cgen, n: *node) void = { let srcfk: i32 = exprfloatkind(c, n.lhs); let dstf64: bool = isfloattype(c, n.rhs); let dstf32: bool = isf32type(c, n.rhs); let dstfk: i32 = 0; if (dstf32) { dstfk = 1; } else { if (dstf64) { dstfk = 2; }; }; cgexpr(c, n.lhs); // 0=int, 1=f32, 2=f64. CVT picks one direction per combo; // same-kind casts (int↔int with widening differences, // f64→f64 etc.) stay no-ops at the asm level, matching the // pre-port behaviour for integer casts. if (srcfk == 0 && dstfk == 0) { return; }; if (srcfk == 0 && dstfk == 2) { emitline("\tCVTSI2SD\tAX, X0\n"); return; }; if (srcfk == 0 && dstfk == 1) { emitline("\tCVTSI2SS\tAX, X0\n"); return; }; if (srcfk == 2 && dstfk == 0) { emitline("\tCVTTSD2SI\tX0, AX\n"); return; }; if (srcfk == 1 && dstfk == 0) { emitline("\tCVTTSS2SI\tX0, AX\n"); return; }; if (srcfk == 2 && dstfk == 1) { emitline("\tCVTSD2SS\tX0, X0\n"); return; }; if (srcfk == 1 && dstfk == 2) { emitline("\tCVTSS2SD\tX0, X0\n"); return; }; // Same-kind float→float: nothing to emit. }; fn cgstrlit(c: *cgen, n: *node) void = { // Result is the (ptr, len) pair: ptr in AX, len in BX. Call // sites that expect a str arg pick these up directly. let nstr: str = n.str; let lab: str = internstrlit(c, nstr); emitline("\tLEAQ\t"); os.write(1, lab.ptr, lab.len: u64); emitline("(SB), AX\n"); emitline("\tMOVQ\t$"); emitint(nstr.len: i64); emitline(", BX\n"); return; }; fn cgident(c: *cgen, n: *node) void = { let nm: str = n.str; let lc: *local = localfindnode(c, nm); if (lc != nil) { let off: i32 = lc.off; // Float local: MOVSS / MOVSD into X0. Skips the AX shuffle // so consumers (cgbin, cgcast, return) pick up the SSE value // directly. if (isfloattype(c, lc.tnode)) { let mov: str = "MOVSD"; if (isf32type(c, lc.tnode)) { mov = "MOVSS"; }; emitline("\t"); emitline(mov); emitline("\t"); emitoff(off: i64); emitline("(BP), X0\n"); return; }; emitline("\tMOVQ\t"); emitoff(off: i64); emitline("(BP), AX\n"); // str local: also load the len half into BX. if (isstrtype(c, lc.tnode)) { emitline("\tMOVQ\t"); emitoff((off + 8): i64); emitline("(BP), BX\n"); }; // slice local: load (ptr, len, cap) into (AX, BX, CX). if (isslicetype(c, lc.tnode)) { emitline("\tMOVQ\t"); emitoff((off + 8): i64); emitline("(BP), BX\n"); emitline("\tMOVQ\t"); emitoff((off + 16): i64); emitline("(BP), CX\n"); }; return; }; // Top-level `def` constant — load from its DATA symbol. if (deflookup(c, nm)) { emitline("\tMOVQ\t"); emitsymname(c, nm); emitline("(SB), AX\n"); return; }; // Fn-name used as a value (e.g. `let f = some_fn;` or // `... = some_fn;`). LEAQ the symbol address into AX. The // emitsymname helper handles ffiresolve and module-mangling // in one go, so a body-less FFI binding emits the C symbol // it was declared with via @symbol(), not the ww-side ident. let rt: *node = fnretlookup(c, nm); if (rt != nil) { emitline("\tLEAQ\t"); emitsymname(c, nm); emitline("(SB), AX\n"); return; }; // Top-level mutable `let` — RIP-relative load from its DATAW // slot. Mirrors C cgen's catch-all `MOVQ masym(s), AX` for // scalar lets, plus the (LEAQ, MOVQ, MOVQ[, MOVQ]) sequence // for str / slice globals so the ABI pair / triple lands in // (AX, BX[, CX]). Names that aren't lets either (typos, // never-defined) drop through to the silent return. if (isletvar(c, nm)) { let isstr: bool = letvarisstr(c, nm); let issl: bool = letvarisslice(c, nm); if (isstr || issl) { emitline("\tLEAQ\t"); emitsymname(c, nm); emitline("(SB), CX\n"); emitline("\tMOVQ\t(CX), AX\n"); emitline("\tMOVQ\t8(CX), BX\n"); if (issl) { // Overwrites the address holder with the // cap as the last step — CX is no longer // needed once both ptr/len are loaded. emitline("\tMOVQ\t16(CX), CX\n"); }; return; }; // Float global: same LEAQ-indirect shape, since MOVSS/ // MOVSD have no D_EXTERN operand form in w6a. let lv: *letvar = c.lets; for (lv != nil) { if (streq(lv.name, nm)) { if (isfloattype(c, lv.tnode)) { let mov: str = "MOVSD"; if (isf32type(c, lv.tnode)) { mov = "MOVSS"; }; emitline("\tLEAQ\t"); emitsymname(c, nm); emitline("(SB), CX\n"); emitline("\t"); emitline(mov); emitline("\t(CX), X0\n"); return; }; lv = nil; } else { lv = lv.lvnext; }; }; emitline("\tMOVQ\t"); emitsymname(c, nm); emitline("(SB), AX\n"); return; }; return; }; fn cgindex(c: *cgen, n: *node) void = { // Element-size-aware load: u8 → MOVZBQ, i32 → MOVSXD, u32 → MOVL, // str → (ptr, len) into (AX, BX), everything else → MOVQ. Fast // path when the base is a bare ident (mem.ww shape). let base: *node = n.lhs; let idx: *node = n.rhs; 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; baselocal = localfindnode(c, bn); if (baselocal != nil) { esz = elemsizeofc(c, 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 = elemsizeofc(c, tn); signed_elem = elemissigned(tn); }; if (tn.kind == nkind.N_TPTR) { isglobalptr = true; globalname = bn; esz = elemsizeofc(c, tn); signed_elem = elemissigned(tn); }; }; }; } else { if (base.kind == nkind.N_DOT) { esz = indexbaseesz(c, base); };}; }; // Tagged-union element: load slot words into (AX=tag, DX=val0, // CX=val1) matching the tagged-return ABI so call-arg / let / // match consumers see the same shape as a tagged-returning fn. // Slot size = esz (8/16/24); nullable folded element is one // word, which the fallthrough below handles via MOVQ AX. let elem_tagged: bool = false; let elem_slot_sz: i32 = esz; if (base != nil) { if (base.kind == nkind.N_IDENT) { let bl: *local = baselocal; let etn: *node = nil; if (bl != nil) { let btn: *node = bl.tnode; if (btn != nil) { let bk: nkind = btn.kind; if (bk == nkind.N_TARRAY) { etn = btn.lhs; }; if (bk == nkind.N_TSLICE) { etn = btn.lhs; }; if (bk == nkind.N_TPTR) { etn = btn.lhs; }; }; } else { let tn: *node = letvartnode(c, base.str); if (tn != nil) { let bk: nkind = tn.kind; if (bk == nkind.N_TARRAY) { etn = tn.lhs; }; if (bk == nkind.N_TSLICE) { etn = tn.lhs; }; if (bk == nkind.N_TPTR) { etn = tn.lhs; }; }; }; if (istaggedtype(c, etn)) { if (!isnullabletype(etn)) { elem_tagged = true; elem_slot_sz = slotsize(c, etn); esz = elem_slot_sz; }; }; }; }; cgexpr(c, idx); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); 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 (elem_tagged) { if (elem_slot_sz > 24) { emitline("\tMOVQ\t24(BX), R8\n"); }; if (elem_slot_sz > 16) { emitline("\tMOVQ\t16(BX), CX\n"); }; if (elem_slot_sz > 8) { emitline("\tMOVQ\t8(BX), DX\n"); }; emitline("\tMOVQ\t(BX), AX\n"); return; }; 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; if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isarray = true; }; }; if (isarray) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); emitline("(BP), BX\n"); } else { emitline("\tMOVQ\t"); emitoff(baselocal.off: i64); emitline("(BP), BX\n"); }; emitline("\tADDQ\tAX, BX\n"); if (elem_tagged) { if (elem_slot_sz > 24) { emitline("\tMOVQ\t24(BX), R8\n"); }; if (elem_slot_sz > 16) { emitline("\tMOVQ\t16(BX), CX\n"); }; if (elem_slot_sz > 8) { emitline("\tMOVQ\t8(BX), DX\n"); }; emitline("\tMOVQ\t(BX), AX\n"); return; }; // str element (16B): load (ptr, len) into (AX, BX) so // the value flows through the str-rhs convention. 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; }; // Generic fallback when base isn't a plain ident. emitline("\tPUSHQ\tAX\n"); cgexpr(c, base); emitline("\tPOPQ\tBX\n"); emitline("\tADDQ\tBX, AX\n"); if (elem_tagged) { // AX holds the element address. Copy to BX (loading slot+0 // into AX clobbers it), then read slot words. emitline("\tMOVQ\tAX, BX\n"); if (elem_slot_sz > 16) { emitline("\tMOVQ\t16(BX), CX\n"); }; if (elem_slot_sz > 8) { emitline("\tMOVQ\t8(BX), DX\n"); }; emitline("\tMOVQ\t(BX), AX\n"); return; }; if (esz == 16) { emitline("\tMOVQ\t8(AX), BX\n"); emitline("\tMOVQ\t(AX), AX\n"); return; }; if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); } else { if (esz == 4) { if (signed_elem) { emitline("\tMOVSXD\t(AX), AX\n"); } else { emitline("\tMOVL\t(AX), AX\n"); }; } else { emitline("\tMOVQ\t(AX), AX\n"); };}; return; }; // cgslice — `base[lo:hi]` as a slice value. Leaves (AX=base+lo, // BX=hi-lo, CX=hi-lo) so callers can route to a slice slot, // return, or arg with the same triple ABI. Cap defaults to the // new length; no syntax for a wider cap yet. Element scaling // on the ptr isn't wired — non-u8 slices need a follow-up audit. fn cgslice(c: *cgen, n: *node) void = { let base: *node = n.lhs; let lo: *node = n.rhs; let hi: *node = n.cond; let baselocal: *local = nil; if (base != nil) { if (base.kind == nkind.N_IDENT) { baselocal = localfindnode(c, base.str); }; }; // base address if (baselocal != nil) { let tn: *node = baselocal.tnode; let isarray: bool = false; if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isarray = true; }; }; if (isarray) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); emitline("(BP), AX\n"); } else { emitline("\tMOVQ\t"); emitoff(baselocal.off: i64); emitline("(BP), AX\n"); }; } else { if (base != nil) { cgexpr(c, base); };}; emitline("\tPUSHQ\tAX\n"); // lo (default 0) if (lo != nil) { cgexpr(c, lo); } else { emitline("\tMOVQ\t$0, AX\n"); }; emitline("\tPUSHQ\tAX\n"); // hi (default base length) if (hi != nil) { cgexpr(c, hi); } else { if (baselocal != nil) { let tn: *node = baselocal.tnode; let handled: bool = false; if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { let lenn: *node = tn.rhs; if (lenn != nil) { if (lenn.kind == nkind.N_INTLIT) { emitline("\tMOVQ\t$"); emituint(lenn.uval); emitline(", AX\n"); handled = true; }; }; } else { if (tn.kind == nkind.N_TSLICE) { emitline("\tMOVQ\t"); emitoff((baselocal.off + 8): i64); emitline("(BP), AX\n"); handled = true; } else { if (tn.kind == nkind.N_TNAME) { if (streq(tn.str, "str")) { emitline("\tMOVQ\t"); emitoff((baselocal.off + 8): i64); emitline("(BP), AX\n"); handled = true; }; };};}; }; if (!handled) { emitline("\tMOVQ\t$0, AX\n"); }; } else { emitline("\tMOVQ\t$0, AX\n"); };}; emitline("\tMOVQ\tAX, BX\n"); emitline("\tPOPQ\tCX\n"); emitline("\tPOPQ\tAX\n"); emitline("\tADDQ\tCX, AX\n"); emitline("\tSUBQ\tCX, BX\n"); emitline("\tMOVQ\tBX, CX\n"); }; fn cgmatch(c: *cgen, n: *node) void = { // match (e) { case let v: T => stmt; ... } // // Read the tagged-union slot and dispatch by tag. Slot // layout: [+0]=tag, [+8]=value0, [+16]=value1. Bindings // (`case let v: T =>`) get a fresh local slot loaded from // slot+8 (and slot+16 for str-typed payload). let scrut: *node = n.lhs; let scrutoff: i32 = 0; let scrutt: *node = nil; if (scrut != nil) { if (scrut.kind == nkind.N_IDENT) { let lc: *local = localfindnode(c, scrut.str); if (lc != nil) { scrutoff = lc.off; scrutt = resolvetagged(c, lc.tnode); }; } else { // Non-ident scrutinee (call result, arr[i], ?, etc.). // Spill into an `@match_spill` scratch slot and // dispatch off it. Tagged returns (N_CALL) follow the // AX:DX:CX convention; tagged-element loads (N_INDEX) // after the cgindex fix produce the same triple. // Nullable returns are single-word (AX = ptr); only +0 // is read, so the extra stores are harmless. We // recover the scrutinee type from fnretlookup (N_CALL) // or the base local's array element type (N_INDEX) so // dispatch can compute variant indices. scrutoff = localalloc(c, "@match_spill", 24, nil); if (scrut.kind == nkind.N_CALL) { let callee: *node = scrut.lhs; if (callee != nil) { let cnm: str; cnm.ptr = nil; cnm.len = 0; if (callee.kind == nkind.N_IDENT) { cnm = callee.str; }; if (callee.kind == nkind.N_DOT) { cnm = callee.str; }; if (cnm.len > 0) { let rt: *node = fnretlookup(c, cnm); if (rt != nil) { scrutt = resolvetagged(c, rt); }; }; }; }; if (scrut.kind == nkind.N_INDEX) { let ibase: *node = scrut.lhs; if (ibase != nil) { if (ibase.kind == nkind.N_IDENT) { let bl: *local = localfindnode(c, ibase.str); let btn: *node = nil; if (bl != nil) { btn = bl.tnode; } else { btn = letvartnode(c, ibase.str); }; if (btn != nil) { let bk: nkind = btn.kind; let etn: *node = nil; if (bk == nkind.N_TARRAY) { etn = btn.lhs; }; if (bk == nkind.N_TSLICE) { etn = btn.lhs; }; if (bk == nkind.N_TPTR) { etn = btn.lhs; }; if (etn != nil) { scrutt = resolvetagged(c, etn); }; }; }; }; }; cgexpr(c, scrut); emitline("\tMOVQ\tAX, "); emitoff(scrutoff: i64); emitline("(BP)\n"); if (!isnullabletype(scrutt)) { emitline("\tMOVQ\tDX, "); emitoff((scrutoff + 8): i64); emitline("(BP)\n"); emitline("\tMOVQ\tCX, "); emitoff((scrutoff + 16): i64); emitline("(BP)\n"); // R8 carries the 4th return word when the // scrutinee's tagged union has a slice-payload // variant (slot 32B). Harmless for narrower // returns — R8 is callee-clobbered either way. let ssz: i32 = slotsize(c, scrutt); if (ssz > 24) { emitline("\tMOVQ\tR8, "); emitoff((scrutoff + 24): i64); emitline("(BP)\n"); }; }; }; }; let endl: str = mklabel(c, "match_end"); // Push end label as the yield target for this match's arm bodies. if (c.yieldtop < LOOP_MAX) { c.yieldbuf[c.yieldtop] = endl; c.yieldtop += 1; }; let cs: *node = n.list; for (cs != nil) { let nxt: str = mklabel(c, "match_next"); let pat: *node = cs.lhs; let nullable: bool = isnullabletype(scrutt); // Per-arm scope: save c.locals before allocating the bind // and restore after the body runs, so the arm's bind (and // any nested lets) don't leak past the arm. Matches the // checker's newscope/restore around N_MCASE. Without this, // `let e: *T = ...; match (r) { case let e: str => ... }; // use e` would resolve `e` after the match to the inner // str slot instead of the outer ptr. let arm_locals_saved: *local = c.locals; // Compute the variant tag for this arm. Default arm // (no pattern) skips the tag check. if (pat != nil) { if (nullable) { // Discriminator = pointer-vs-null. // *T arm: skip if ptr == 0. // void arm: skip if ptr != 0. let ptr_tag: i32 = nullableptrtag(scrutt); let cur_tag: i32 = 0; if (pat.kind == nkind.N_TPTR) { cur_tag = ptr_tag; } else { if (ptr_tag == 0) { cur_tag = 1; }; }; emitline("\tMOVQ\t"); emitoff(scrutoff: i64); emitline("(BP), AX\n"); emitline("\tCMPQ\t$0, AX\n"); if (cur_tag == ptr_tag) { emitline("\tJE\t"); } else { emitline("\tJNE\t"); }; emitline(nxt); emitline("\n"); } else { let want: i32 = 0; if (scrutt != nil) { if (scrutt.kind == nkind.N_TTAGGED) { let patname: str; patname.ptr = nil; patname.len = 0; if (pat.kind == nkind.N_TNAME) { patname = pat.str; }; let r: i32 = flatvariantidx(c, scrutt, patname); if (r >= 0) { want = r; }; }; }; emitline("\tMOVQ\t"); emitoff(scrutoff: i64); emitline("(BP), AX\n"); emitline("\tCMPQ\t$"); emitint(want: i64); emitline(", AX\n"); emitline("\tJNE\t"); emitline(nxt); emitline("\n"); }; }; // Bind `let v: T` from the slot, if requested. let bn: str = cs.str; if (bn.len > 0) { if (pat != nil) { if (nullable) { // Bind the pointer (or skip for the // void arm, which has zero-size). The // value IS slot+0. if (pat.kind == nkind.N_TPTR) { let voff: i32 = localalloc(c, bn, 8, pat); emitline("\tMOVQ\t"); emitoff(scrutoff: i64); emitline("(BP), AX\n"); emitline("\tMOVQ\tAX, "); emitoff(voff: i64); emitline("(BP)\n"); }; } else { let bsz: i32 = 8; if (isstrtype(c, pat)) { bsz = 16; } else { if (isslicetype(c, pat)) { bsz = 24; }; }; // localalloc (not localadd): match-arm // binds don't dedup with same-named binds // in *other* matches, since C's cgexpr // allocates a fresh slot per match expr. let voff: i32 = localalloc(c, bn, bsz, pat); let bw: i32 = 0; for (bw < bsz) { emitline("\tMOVQ\t"); emitoff((scrutoff + 8 + bw): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\tAX, "); emitoff((voff + bw): i64); emitline("(BP)\n"); bw += 8; }; }; }; }; // Body. Match arms are statements; we cgstmt them. if (cs.body != nil) { cgstmt(c, cs.body); }; // Restore the locals head — pop everything the arm pushed // so post-match code resolves names to their original (outer) // bindings. c.locals = arm_locals_saved; emitline("\tJMP\t"); emitline(endl); emitline("\n"); emitlabel(nxt); cs = cs.next; }; emitlabel(endl); if (c.yieldtop > 0) { c.yieldtop -= 1; }; return; }; fn cgdot(c: *cgen, n: *node) void = { let lhs: *node = n.lhs; let fld: str = n.str; // Enum member access: `EnumName.MEMBER` or `pkg.EnumName.MEMBER` // → inline the pre-computed constant. With driver-side // concatenation, both forms key off the leaf type name. if (lhs != nil) { let etname: str; etname.ptr = nil; etname.len = 0; if (lhs.kind == nkind.N_IDENT) { etname = lhs.str; }; if (lhs.kind == nkind.N_DOT) { if (lhs.lhs != nil) { if (lhs.lhs.kind == nkind.N_IDENT) { etname = lhs.str; }; }; }; if (etname.len > 0) { let en: *enumtype = enumlookup(c, etname); if (en != nil) { let v: u64; if (enummemberval(en, fld, &v)) { emitline("\tMOVQ\t$"); emitint(v: i64); emitline(", AX\n"); return; }; }; }; }; if (lhs != nil) { if (lhs.kind == nkind.N_IDENT) { let nm: str = lhs.str; let lc: *local = localfindnode(c, nm); if (lc != nil) { let tn: *node = lc.tnode; let lkind: nkind = nkind.N_NONE; if (tn != nil) { lkind = tn.kind; }; // Pointer-to-struct: deref then field load. if (lkind == nkind.N_TPTR) { let inner: *node = tn.lhs; let sname: str; sname.ptr = nil; sname.len = 0; if (inner != nil) { if (inner.kind == nkind.N_TNAME) { sname = inner.str; }; }; if (sname.len > 0) { let si: *structinfo = structlookup(c, sname); if (si != nil) { let fi: *fieldinfo = si.fields; for (fi != nil) { let fn_: str = fi.fname; if (streq(fn_, fld)) { // str field via *struct: load len into a // scratch first (so loading ptr into AX // last leaves (AX=ptr, BX=len)). emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); if (isstrtype(c, fi.tnode)) { emitline("\tMOVQ\t"); emitdispreg((fi.foff + 8): i64, "BX"); emitline(", CX\n"); emitline("\tMOVQ\t"); emitdispreg(fi.foff: i64, "BX"); emitline(", AX\n"); emitline("\tMOVQ\tCX, BX\n"); } else { if (isfloattype(c, fi.tnode)) { // f64/f32 via *struct: route through X0. // MOVQ into AX leaves the SSE reg stale // and any downstream consumer (arg // pass, return, arithmetic) reads // garbage. let mov: str = "MOVSD"; if (isf32type(c, fi.tnode)) { mov = "MOVSS"; }; emitline("\t"); emitline(mov); emitline("\t"); emitdispreg(fi.foff: i64, "BX"); emitline(", X0\n"); } else { let op: str = fieldloadop(fi); emitline("\t"); emitline(op); emitline("\t"); emitdispreg(fi.foff: i64, "BX"); emitline(", AX\n"); }; }; return; }; fi = fi.finext; }; }; }; }; // Direct struct local: field load at off+foff. if (lkind == nkind.N_TNAME) { let sname: str = tn.str; let si: *structinfo = structlookup(c, sname); if (si != nil) { let fi: *fieldinfo = si.fields; for (fi != nil) { let fn_: str = fi.fname; if (streq(fn_, fld)) { // str field: load both halves so chained // `.ptr` / `.len` see (AX=ptr, BX=len). if (isstrtype(c, fi.tnode)) { emitline("\tMOVQ\t"); emitoff((lc.off + fi.foff): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\t"); emitoff((lc.off + fi.foff + 8): i64); emitline("(BP), BX\n"); } else { if (isfloattype(c, fi.tnode)) { // f64/f32 field: route through X0. let mov: str = "MOVSD"; if (isf32type(c, fi.tnode)) { mov = "MOVSS"; }; emitline("\t"); emitline(mov); emitline("\t"); emitoff((lc.off + fi.foff): i64); emitline("(BP), X0\n"); } else { let op: str = fieldloadop(fi); emitline("\t"); emitline(op); emitline("\t"); emitoff((lc.off + fi.foff): i64); emitline("(BP), AX\n"); }; }; return; }; fi = fi.finext; }; }; }; // Array pseudo-fields: `.ptr` is the array's // address (LEAQ); `.len` is the static element // count (immediate). if (lkind == nkind.N_TARRAY) { if (streq(fld, "ptr")) { emitline("\tLEAQ\t"); emitoff(lc.off: i64); emitline("(BP), AX\n"); return; }; if (streq(fld, "len")) { let lenn: *node = tn.rhs; let alen: i64 = 0i64; if (lenn != nil) { if (lenn.kind == nkind.N_INTLIT) { alen = lenn.uval: i64; }; }; emitline("\tMOVQ\t$"); emitint(alen); emitline(", AX\n"); return; }; }; // Hare-style tuple positional access: `t.0`, `t.1`. // Walk the tuple element type list summing slotsize // (matches the (scalar, str) init layout which puts // the scalar in an 8B slot and the str in 16B). For // a str element, load both halves into (AX, BX) so // chains like `t.1.len` propagate correctly. if (lkind == nkind.N_TTUPLE) { let idx: i32 = fldnumidx(fld); if (idx >= 0) { let tp: *node = tn.list; let foff: i32 = 0; let i: i32 = 0; for (i < idx) { if (tp == nil) { i = idx; } else { foff += slotsize(c, tp); tp = tp.next; i += 1; }; }; if (tp != nil) { if (isstrtyperaw(tp)) { emitline("\tMOVQ\t"); emitoff((lc.off + foff + 0): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\t"); emitoff((lc.off + foff + 8): i64); emitline("(BP), BX\n"); return; }; let sz: i32 = slotsize(c, tp); let op: str = "MOVQ"; if (sz == 1) { op = "MOVZBQ"; } else { if (sz == 4) { op = "MOVL"; }; }; emitline("\t"); emitline(op); emitline("\t"); emitoff((lc.off + foff): i64); emitline("(BP), AX\n"); return; }; }; }; // str/slice pseudo-fields .ptr/.len/.cap on a // direct local: load at slot+delta. let delta: i32 = -1; if (streq(fld, "ptr")) { delta = 0; }; if (streq(fld, "len")) { delta = 8; }; if (streq(fld, "cap")) { delta = 16; }; if (delta >= 0) { // Pointer to str/slice (`*[]u8`, `*str`): // deref, then load at delta within the // pointed-to header. C cgen does the same. if (lkind == nkind.N_TPTR) { let inner: *node = tn.lhs; let innerkind: nkind = nkind.N_NONE; if (inner != nil) { innerkind = inner.kind; }; let innerstr: bool = false; if (innerkind == nkind.N_TNAME) { if (streq(inner.str, "str")) { innerstr = true; }; }; if (innerkind == nkind.N_TSLICE) { innerstr = true; }; if (innerstr) { emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); emitline("\tMOVQ\t"); emitdispreg(delta: i64, "BX"); emitline(", AX\n"); return; }; }; emitline("\tMOVQ\t"); emitoff((lc.off + delta): i64); emitline("(BP), AX\n"); return; }; }; }; }; // `def NAME: str = "..."` field access — inline the literal. // Sdef-backed strs aren't laid out in memory, so falling // through to the SB-load fallback below would mis-emit // `MOVQ (SB), AX` (looking up the field name as a // symbol). Mirrors cmd/w6c/cgen.c nkind.N_DOT off==0 / Sdef branch. if (lhs != nil) { if (lhs.kind == nkind.N_IDENT) { let drhs: *node = deflookuprhs(c, lhs.str); if (drhs != nil) { if (drhs.kind == nkind.N_STRLIT) { let bytes: str = drhs.str; if (streq(fld, "ptr")) { let lab: str = internstrlit(c, bytes); emitline("\tLEAQ\t"); os.write(1, lab.ptr, lab.len: u64); emitline("(SB), AX\n"); return; }; if (streq(fld, "len")) { emitline("\tMOVQ\t$"); emitint(bytes.len: i64); emitline(", AX\n"); return; }; }; }; }; }; // Top-level str/slice global field access — load .ptr / .len // (and .cap for slices) via &name(SB) into CX, then MOVQ // delta(CX), AX. Without this the module-qualified fallback // below would mis-emit `MOVQ (SB), AX`. if (lhs != nil) { if (lhs.kind == nkind.N_IDENT) { if (isletvar(c, lhs.str)) { let isstr: bool = letvarisstr(c, lhs.str); let issl: bool = letvarisslice(c, lhs.str); if (isstr || issl) { let delta: i32 = -1; if (streq(fld, "ptr")) { delta = 0; }; if (streq(fld, "len")) { delta = 8; }; if (issl) { if (streq(fld, "cap")) { delta = 16; }; }; if (delta >= 0) { emitline("\tLEAQ\t"); emitsymname(c, lhs.str); emitline("(SB), CX\n"); emitline("\tMOVQ\t"); emitdispreg(delta: i64, "CX"); emitline(", AX\n"); return; }; }; }; }; }; // Top-level struct global field read — LEAQ name(SB), CX then // load at fi.foff(CX). Mirrors the local "Direct struct local" // branch above, swapping the BP frame slot for the global VA. // Field-width-aware op handles MOVQ / MOVL / MOVZBQ / MOVSXD. if (lhs != nil) { if (lhs.kind == nkind.N_IDENT) { let si: *structinfo = letvarstructinfo(c, lhs.str); if (si != nil) { let fi: *fieldinfo = si.fields; for (fi != nil) { if (streq(fi.fname, fld)) { emitline("\tLEAQ\t"); emitsymname(c, lhs.str); emitline("(SB), CX\n"); if (isstrtype(c, fi.tnode)) { emitline("\tMOVQ\t"); emitdispreg(fi.foff: i64, "CX"); emitline(", AX\n"); emitline("\tMOVQ\t"); emitdispreg((fi.foff + 8): i64, "CX"); emitline(", BX\n"); } else { if (isfloattype(c, fi.tnode)) { // f64/f32 global field: route through X0. let mov: str = "MOVSD"; if (isf32type(c, fi.tnode)) { mov = "MOVSS"; }; emitline("\t"); emitline(mov); emitline("\t"); emitdispreg(fi.foff: i64, "CX"); emitline(", X0\n"); } else { let op: str = fieldloadop(fi); emitline("\t"); emitline(op); emitline("\t"); emitdispreg(fi.foff: i64, "CX"); emitline(", AX\n"); }; }; return; }; fi = fi.finext; }; }; }; }; // `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 // the C cgen takes when bt is NULL/tyerr. if (lhs != nil) { if (lhs.kind == nkind.N_IDENT) { emitline("\tMOVQ\t"); emitsymname(c, fld); emitline("(SB), AX\n"); return; }; }; // Non-ident base pseudo-field: e.g. `"abc".ptr` / `"abc".len`. // Evaluate the str-producing expression — that leaves // (AX=ptr, BX=len). Then `.ptr` returns AX as is; `.len` // shuffles BX→AX. Mirrors what C cgen does (it just evaluates // the literal and picks the half it wants). if (streq(fld, "ptr")) { cgexpr(c, lhs); return; }; if (streq(fld, "len")) { cgexpr(c, lhs); emitline("\tMOVQ\tBX, AX\n"); return; }; // Chained struct-field-via-ptr-via-ptr access: // r.sym.val where r: *lrel, .sym: *lsym, .val: u64 // Inner DOT (`r.sym`) returns a *struct (a pointer-to-struct // field). Outer DOT dereferences and reads `val`. Without this // path the cgen falls through and AX retains whatever the // inner expression left there — typically the *struct pointer // itself, so reads silently get the pointer value instead of // the field. (Showed up porting w6l/pass.ww.) if (lhs != nil) { if (lhs.kind == nkind.N_DOT) { let innert: *node = dotinnerstructptr(c, lhs); if (innert != nil) { let sname: str = innert.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 = ptr to inner struct // str field: load both halves. 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; }; // f64/f32 chained field: route through X0. 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; }; }; }; }; }; // 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; }; }; }; };}; };}; }; }; // Nested module-qualified field where the chain didn't fold to a // known shape (raw w6c on a single file with `use mod;` but no // driver concatenation — the inner enum / struct hasn't been // seen). Emit `MOVQ (SB), AX` so the linker surfaces a // clean undefined-symbol error on the leaf. Mirror of // cmd/w6c/cgen.c N_DOT nested fallback. if (lhs != nil) { if (lhs.kind == nkind.N_DOT) { emitline("\tMOVQ\t"); emitsymname(c, fld); emitline("(SB), AX\n"); return; }; }; return; }; fn cgun(c: *cgen, n: *node) void = { // Match C cgen ordering: evaluate operand first (load into AX), // then apply the unary op. AMP / STAR override AX with the // address / deref. The wasted load before AMP keeps our asm // byte-identical to the C version. let fk: i32 = exprfloatkind(c, n.lhs); if (n.op == tkind.TK_MINUS && fk != 0) { // Float negate: X0 = 0 - X0. Stash orig, load 0.0, subtract. // Zero bit pattern equals 0.0 for both f32 and f64 so we // reuse the integer-zero materialisation. let mov: str = "MOVSD"; let sub: str = "SUBSD"; if (fk == 1) { mov = "MOVSS"; sub = "SUBSS"; }; cgexpr(c, n.lhs); emitline("\tSUBQ\t$8, SP\n"); emitline("\t"); emitline(mov); emitline("\tX0, (SP)\n"); emitline("\tMOVQ\t$0, AX\n"); emitline("\tPUSHQ\tAX\n"); emitline("\t"); emitline(mov); emitline("\t(SP), X0\n"); emitline("\tADDQ\t$8, SP\n"); emitline("\t"); emitline(mov); emitline("\t(SP), X1\n"); emitline("\tADDQ\t$8, SP\n"); emitline("\t"); emitline(sub); emitline("\tX1, X0\n"); return; }; // Address-of has its own evaluation strategy — we want the address // of the operand, not its value. Special-case here so `&arr[i]` // doesn't compile the value load and then discard it. if (n.op == tkind.TK_AMP) { let opnd: *node = n.lhs; if (opnd != nil) { if (opnd.kind == nkind.N_IDENT) { let nm: str = opnd.str; let off: i32 = localfind(c, nm); if (off != 0) { emitline("\tLEAQ\t"); emitoff(off: i64); emitline("(BP), AX\n"); return; }; if (isletvar(c, nm)) { emitline("\tLEAQ\t"); emitsymname(c, nm); emitline("(SB), AX\n"); return; }; return; }; if (opnd.kind == nkind.N_INDEX) { // &base[i] = base + i*esz, no dereference. let base: *node = opnd.lhs; let idx: *node = opnd.rhs; let esz: i32 = 8; let isglobalarr: bool = false; let isglobalptr: bool = false; let globalname: str; globalname.ptr = nil; globalname.len = 0; let baselocal: *local = nil; let isarr: bool = false; if (base != nil) { if (base.kind == nkind.N_IDENT) { baselocal = localfindnode(c, base.str); if (baselocal != nil) { esz = elemsizeofc(c, baselocal.tnode); let tn: *node = baselocal.tnode; if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isarr = true; }; }; } else { let tn: *node = letvartnode(c, base.str); if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isglobalarr = true; globalname = base.str; esz = elemsizeofc(c, tn); }; if (tn.kind == nkind.N_TPTR) { isglobalptr = true; globalname = base.str; esz = elemsizeofc(c, tn); }; }; }; }; }; cgexpr(c, idx); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", CX\n"); emitline("\tIMULQ\tCX, AX\n"); }; 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) { if (isarr) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); emitline("(BP), BX\n"); } else { emitline("\tMOVQ\t"); emitoff(baselocal.off: i64); emitline("(BP), BX\n"); }; } else { // Complex base: spill scaled idx, eval // base to AX, move to BX, restore idx. emitline("\tPUSHQ\tAX\n"); cgexpr(c, base); emitline("\tMOVQ\tAX, BX\n"); emitline("\tPOPQ\tAX\n"); };};}; emitline("\tADDQ\tBX, AX\n"); return; }; }; return; }; cgexpr(c, n.lhs); if (n.op == tkind.TK_MINUS) { emitline("\tNEGQ\tAX\n"); return; }; if (n.op == tkind.TK_TILDE) { emitline("\tNOTQ\tAX\n"); return; }; if (n.op == tkind.TK_STAR) { emitline("\tMOVQ\t(AX), AX\n"); return; }; if (n.op == tkind.TK_NOT) { let t: str = mklabel(c, "tt"); let e: str = mklabel(c, "te"); emitline("\tCMPQ\t$0, AX\n"); emitline("\tJE\t"); emitline(t); emitline("\n"); emitline("\tMOVQ\t$0, AX\n"); emitline("\tJMP\t"); emitline(e); emitline("\n"); emitlabel(t); emitline("\tMOVQ\t$1, AX\n"); emitlabel(e); return; }; return; }; fn cgbin(c: *cgen, n: *node) void = { let unsignd: bool = nodeisunsigned(c, n.lhs); if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); }; // Float arithmetic: both operands flow through X0. Spill rhs // across the stack (SUBQ/MOVSD/MOVSD/ADDQ) since there's no // general FP register saver. ADDSD/SUBSD/MULSD/DIVSD pick SS // variants for f32. Comparison uses UCOMISD + JCC and falls // out to the existing CMPQ-based path below. let lfk: i32 = exprfloatkind(c, n.lhs); let rfk: i32 = exprfloatkind(c, n.rhs); let fk: i32 = lfk; if (fk == 0) { fk = rfk; }; if (fk != 0) { let mov: str = "MOVSD"; if (fk == 1) { mov = "MOVSS"; }; if (n.op == tkind.TK_PLUS || n.op == tkind.TK_MINUS || n.op == tkind.TK_STAR || n.op == tkind.TK_SLASH) { cgexpr(c, n.rhs); emitline("\tSUBQ\t$8, SP\n"); emitline("\t"); emitline(mov); emitline("\tX0, (SP)\n"); cgexpr(c, n.lhs); emitline("\t"); emitline(mov); emitline("\t(SP), X1\n"); emitline("\tADDQ\t$8, SP\n"); let op: str = "ADDSD"; if (n.op == tkind.TK_MINUS) { op = "SUBSD"; }; if (n.op == tkind.TK_STAR) { op = "MULSD"; }; if (n.op == tkind.TK_SLASH) { op = "DIVSD"; }; if (fk == 1) { if (n.op == tkind.TK_PLUS) { op = "ADDSS"; }; if (n.op == tkind.TK_MINUS) { op = "SUBSS"; }; if (n.op == tkind.TK_STAR) { op = "MULSS"; }; if (n.op == tkind.TK_SLASH) { op = "DIVSS"; }; }; emitline("\t"); emitline(op); emitline("\tX1, X0\n"); return; }; let isfcmp: bool = false; let jcc: str = ""; // UCOMISD/SS sets ZF/PF/CF; unordered (NaN) propagates as // "not equal / not less". JA/JAE/JB/JBE keys off CF which // matches the ordered comparisons we need. if (n.op == tkind.TK_EQ) { isfcmp = true; jcc = "JE"; }; if (n.op == tkind.TK_NEQ) { isfcmp = true; jcc = "JNE"; }; if (n.op == tkind.TK_LT) { isfcmp = true; jcc = "JB"; }; if (n.op == tkind.TK_LE) { isfcmp = true; jcc = "JBE"; }; if (n.op == tkind.TK_GT) { isfcmp = true; jcc = "JA"; }; if (n.op == tkind.TK_GE) { isfcmp = true; jcc = "JAE"; }; if (isfcmp) { cgexpr(c, n.rhs); emitline("\tSUBQ\t$8, SP\n"); emitline("\t"); emitline(mov); emitline("\tX0, (SP)\n"); cgexpr(c, n.lhs); emitline("\t"); emitline(mov); emitline("\t(SP), X1\n"); emitline("\tADDQ\t$8, SP\n"); let ucomi: str = "UCOMISD"; if (fk == 1) { ucomi = "UCOMISS"; }; emitline("\t"); emitline(ucomi); emitline("\tX1, X0\n"); let t: str = mklabel(c, "ct"); let e: str = mklabel(c, "ce"); emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n"); emitline("\tMOVQ\t$0, AX\n"); emitline("\tJMP\t"); emitline(e); emitline("\n"); emitlabel(t); emitline("\tMOVQ\t$1, AX\n"); emitlabel(e); return; }; return; }; cgexpr(c, n.rhs); emitline("\tPUSHQ\tAX\n"); cgexpr(c, n.lhs); emitline("\tPOPQ\tBX\n"); if (n.op == tkind.TK_PLUS) { emitline("\tADDQ\tBX, AX\n"); return; }; if (n.op == tkind.TK_MINUS) { emitline("\tSUBQ\tBX, AX\n"); return; }; if (n.op == tkind.TK_STAR) { emitline("\tIMULQ\tBX, AX\n"); return; }; if (n.op == tkind.TK_SLASH) { emitline("\tMOVQ\t$0, DX\n"); if (unsignd) { emitline("\tDIVQ\tBX\n"); } else { emitline("\tIDIVQ\tBX\n"); }; return; }; if (n.op == tkind.TK_PERCENT) { emitline("\tMOVQ\t$0, DX\n"); if (unsignd) { emitline("\tDIVQ\tBX\n"); } else { emitline("\tIDIVQ\tBX\n"); }; emitline("\tMOVQ\tDX, AX\n"); return; }; if (n.op == tkind.TK_AMP) { emitline("\tANDQ\tBX, AX\n"); return; }; if (n.op == tkind.TK_PIPE) { emitline("\tORQ\tBX, AX\n"); return; }; if (n.op == tkind.TK_CARET) { emitline("\tXORQ\tBX, AX\n"); return; }; if (n.op == tkind.TK_LSHIFT) { emitline("\tMOVQ\tBX, CX\n"); emitline("\tSHLQ\tCX, AX\n"); return; }; if (n.op == tkind.TK_RSHIFT) { emitline("\tMOVQ\tBX, CX\n"); emitline("\tSHRQ\tCX, AX\n"); return; }; if (n.op == tkind.TK_AND) { emitline("\tANDQ\tBX, AX\n"); return; }; if (n.op == tkind.TK_OR) { emitline("\tORQ\tBX, AX\n"); return; }; // Comparison: emit CMPQ, jump on signed/unsigned variant, // materialise 0/1 in AX. Same shape as the C cgen. let iscmp: bool = false; let jcc: str = ""; if (n.op == tkind.TK_EQ) { iscmp = true; jcc = "JE"; }; if (n.op == tkind.TK_NEQ) { iscmp = true; jcc = "JNE"; }; if (n.op == tkind.TK_LT) { iscmp = true; if (unsignd) { jcc = "JB"; } else { jcc = "JL"; }; }; if (n.op == tkind.TK_LE) { iscmp = true; if (unsignd) { jcc = "JBE"; } else { jcc = "JLE"; }; }; if (n.op == tkind.TK_GT) { iscmp = true; if (unsignd) { jcc = "JA"; } else { jcc = "JG"; }; }; if (n.op == tkind.TK_GE) { iscmp = true; if (unsignd) { jcc = "JAE"; } else { jcc = "JGE"; }; }; if (iscmp) { let t: str = mklabel(c, "ct"); let e: str = mklabel(c, "ce"); emitline("\tCMPQ\tBX, AX\n"); emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n"); emitline("\tMOVQ\t$0, AX\n"); emitline("\tJMP\t"); emitline(e); emitline("\n"); emitlabel(t); emitline("\tMOVQ\t$1, AX\n"); emitlabel(e); return; }; return; }; // cgalloc — `alloc(value)` builtin lowering. Allocate sizeof(value) // bytes via rt_alloc, then write the value's bytes into the new // region. For an N_STRUCTLIT arg, allocate the struct's totsize and // emit per-field stores at each field's offset. For a scalar/ptr, // allocate 8 bytes and store one word. Mirrors cmd/w6c/cgen.c's // alloc-special branch in N_CALL. Returns the heap ptr in AX. fn cgalloc(c: *cgen, n: *node) void = { let v: *node = n.list; let sz: i32 = 8; let si: *structinfo = nil; if (v.kind == nkind.N_STRUCTLIT) { let trefn: *node = v.lhs; let sname: str; sname.ptr = nil; sname.len = 0; if (trefn != nil) { if (trefn.kind == nkind.N_IDENT) { sname = trefn.str; } else { if (trefn.kind == nkind.N_TNAME) { sname = trefn.str; }; }; }; si = structlookup(c, sname); if (si != nil) { sz = si.totsize; }; }; emitline("\tMOVQ\t$"); emitint(sz: i64); emitline(", DI\n"); emitline("\tCALL\trt_alloc(SB)\n"); emitline("\tPUSHQ\tAX\n"); if (v.kind == nkind.N_STRUCTLIT) { if (si != nil) { let f: *node = v.list; for (f != nil) { if (f.kind == nkind.N_FIELD) { let fname: str = f.str; let fi: *fieldinfo = si.fields; for (fi != nil) { let fn_: str = fi.fname; if (streq(fn_, fname)) { cgexpr(c, f.lhs); // alloc(T{ fval = v }) for f64/f32 field: cgexpr left // the value in X0, not AX — route the store via MOVSD/MOVSS. if (isfloattype(c, fi.tnode)) { let mov: str = "MOVSD"; if (isf32type(c, fi.tnode)) { mov = "MOVSS"; }; emitline("\tMOVQ\t(SP), BX\n"); emitline("\t"); emitline(mov); emitline("\tX0, "); emitint(fi.foff: i64); emitline("(BX)\n"); fi = nil; } else { emitline("\tMOVQ\t(SP), BX\n"); let sop: str = fieldstoreop(fi); emitline("\t"); emitline(sop); emitline("\tAX, "); emitint(fi.foff: i64); emitline("(BX)\n"); fi = nil; }; } else { fi = fi.finext; }; }; }; f = f.next; }; }; } else { cgexpr(c, v); emitline("\tMOVQ\t(SP), BX\n"); let sop: str = "MOVQ"; if (sz == 1) { sop = "MOVB"; } else { if (sz == 4) { sop = "MOVL"; }; }; emitline("\t"); emitline(sop); emitline("\tAX, (BX)\n"); }; emitline("\tPOPQ\tAX\n"); }; // cgappend — Hare-style `append(s, v)` / `append(s, items...)` lowering. // Mirrors cmd/w6c/cgen.c's N_CALL append branch (rt::ensure model). // Each value gets: // ; cgexpr → AX // ; PUSHQ AX // ; ADDQ $1, s.len(BP) // ; LEAQ s(BP), DI ; arg1 = &s // ; MOVQ esz, SI ; arg2 = membsz // ; CALL rt_ensure(SB) // ; MOVQ s.len(BP), CX ; CX = new len // ; SUBQ $1, CX ; CX = slot index // ; [IMULQ esz, CX] ; byte offset (esz>1) // ; MOVQ s.ptr(BP), BX // ; ADDQ CX, BX // ; POPQ AX // ; MOV* AX, (BX) ; store (MOVB / MOVQ) // nkind.N_SPREAD wraps the same body in a counted loop over items.len. fn cgappend(c: *cgen, n: *node) void = { let sn: *node = n.list; if (sn == nil) { return; }; if (sn.kind != nkind.N_IDENT) { return; }; let snlocal: *local = localfindnode(c, sn.str); if (snlocal == nil) { return; }; let sn_off: i32 = snlocal.off; let esz: i32 = elemsizeof(snlocal.tnode); let store_op: str = "MOVQ"; if (esz == 1) { store_op = "MOVB"; }; let vn: *node = sn.next; for (vn != nil) { if (vn.kind == nkind.N_SPREAD) { let it: *node = vn.lhs; if (it == nil) { vn = vn.next; continue; }; if (it.kind != nkind.N_IDENT) { vn = vn.next; continue; }; let itlocal: *local = localfindnode(c, it.str); if (itlocal == nil) { vn = vn.next; continue; }; let it_off: i32 = itlocal.off; let load_op: str = "MOVQ"; if (esz == 1) { load_op = "MOVZBQ"; }; emitline("\tSUBQ\t$8, SP\n"); emitline("\tMOVQ\t$0, (SP)\n"); let ll: str = mklabel(c, "spr_l"); let le: str = mklabel(c, "spr_e"); emitlabel(ll); emitline("\tMOVQ\t(SP), CX\n"); emitline("\tMOVQ\t"); emitoff((it_off + 8): i64); emitline("(BP), DX\n"); emitline("\tCMPQ\tDX, CX\n"); emitline("\tJGE\t"); emitline(le); emitline("\n"); emitline("\tMOVQ\t"); emitoff(it_off: i64); emitline("(BP), BX\n"); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", AX\n"); emitline("\tIMULQ\tAX, CX\n"); }; emitline("\tADDQ\tCX, BX\n"); emitline("\t"); emitline(load_op); emitline("\t(BX), AX\n"); emitline("\tPUSHQ\tAX\n"); emitline("\tADDQ\t$1, "); emitoff((sn_off + 8): i64); emitline("(BP)\n"); emitline("\tLEAQ\t"); emitoff(sn_off: i64); emitline("(BP), DI\n"); emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", SI\n"); emitline("\tCALL\trt_ensure(SB)\n"); emitline("\tMOVQ\t"); emitoff((sn_off + 8): i64); emitline("(BP), CX\n"); emitline("\tSUBQ\t$1, CX\n"); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", AX\n"); emitline("\tIMULQ\tAX, CX\n"); }; emitline("\tMOVQ\t"); emitoff(sn_off: i64); emitline("(BP), BX\n"); emitline("\tADDQ\tCX, BX\n"); emitline("\tPOPQ\tAX\n"); emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n"); emitline("\tADDQ\t$1, (SP)\n"); emitline("\tJMP\t"); emitline(ll); emitline("\n"); emitlabel(le); emitline("\tADDQ\t$8, SP\n"); vn = vn.next; continue; }; cgexpr(c, vn); emitline("\tPUSHQ\tAX\n"); emitline("\tADDQ\t$1, "); emitoff((sn_off + 8): i64); emitline("(BP)\n"); emitline("\tLEAQ\t"); emitoff(sn_off: i64); emitline("(BP), DI\n"); emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", SI\n"); emitline("\tCALL\trt_ensure(SB)\n"); emitline("\tMOVQ\t"); emitoff((sn_off + 8): i64); emitline("(BP), CX\n"); emitline("\tSUBQ\t$1, CX\n"); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", AX\n"); emitline("\tIMULQ\tAX, CX\n"); }; emitline("\tMOVQ\t"); emitoff(sn_off: i64); emitline("(BP), BX\n"); emitline("\tADDQ\tCX, BX\n"); emitline("\tPOPQ\tAX\n"); emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n"); vn = vn.next; }; return; }; fn cgcall(c: *cgen, n: *node) void = { // Hare-style `append(s, v)` / `append(s, items...)` builtin — // special-cased before pushargsrev so the spread variant can run // a counted loop over the items slice instead of a normal call. let callee: *node = n.lhs; if (callee != nil) { if (callee.kind == nkind.N_IDENT) { if (streq(callee.str, "append")) { if (n.list != nil) { if (n.list.next != nil) { cgappend(c, n); return; }; }; }; // `alloc(value)` builtin: heap-init a fresh *T with the // value's bytes. For struct literals, lower to rt_alloc // + per-field stores. Mirrors cmd/w6c/cgen.c's N_CALL // alloc path. if (streq(callee.str, "alloc")) { if (n.list != nil) { cgalloc(c, n); return; }; }; }; }; // Look up the callee's declared params for tagged-union widening. // fn-pointer calls (callee is a local) don't get widening — the // user must build the tagged value explicitly. Matches the most // common case (direct named calls). let calleeparams: *node = nil; if (callee != nil) { if (callee.kind == nkind.N_IDENT) { calleeparams = fnparamslookup(c, callee.str); }; }; // Hare-style variadic last param: gather N tail args into a // frame-resident [N]T (`@vararg_d_`) plus a 24B slice // descriptor (`@vararg_sl_`), then splice a synthesised // N_IDENT pointing at the descriptor into n.list so the rest // of the call machinery sees one slice slot for the variadic. // Forwarding shape (`xs...`) skips the gather: the spread's // inner slice expression replaces the wrapper in place. Empty // (no trailing args) writes a {nil, 0, 0} descriptor. The seq // matches the one scanlocals stamped on n.uval. { let nfixed_v: i32 = 0; let varp: *node = callee_variadic_param(c, callee, &nfixed_v); if (varp != nil) { let nargs0: i32 = 0; let aw: *node = n.list; for (aw != nil) { nargs0 += 1; aw = aw.next; }; let nvar: i32 = nargs0 - nfixed_v; if (nvar < 0) { nvar = 0; }; let forwarding: bool = false; if (nvar == 1) { let aaf: *node = n.list; let kk: i32 = 0; for (kk < nfixed_v) { aaf = aaf.next; kk += 1; }; if (aaf != nil) { if (aaf.kind == nkind.N_SPREAD) { forwarding = true; }; }; }; if (forwarding) { let prev: *node = nil; let cur2: *node = n.list; let kk2: i32 = 0; for (kk2 < nfixed_v) { prev = cur2; cur2 = cur2.next; kk2 += 1; }; let inner: *node = cur2.lhs; if (inner != nil) { inner.next = nil; }; if (prev == nil) { n.list = inner; } else { prev.next = inner; }; } else { let seq: i32 = n.uval: i32; let dname: str = mkvarargname(c, "@vararg_d_", seq); let sname: str = mkvarargname(c, "@vararg_sl_", seq); let esz: i32 = slotsize(c, varp.lhs); if (esz < 1) { esz = 1; }; let velemtagged: bool = istaggedtype(c, varp.lhs); let velemstr: bool = isstrtype(c, varp.lhs); let velemslice: bool = isslicetype(c, varp.lhs); let doff: i32 = 0; if (nvar > 0) { doff = localadd(c, dname, nvar * esz, nil); }; let soff: i32 = localadd(c, sname, 24, slicewrap(c, varp.lhs)); let aa2: *node = n.list; let kk3: i32 = 0; for (kk3 < nfixed_v) { aa2 = aa2.next; kk3 += 1; }; let j: i32 = 0; let prevarg: *node = n.list; if (nfixed_v == 0) { prevarg = nil; } else { let kk4: i32 = 0; for (kk4 < nfixed_v - 1) { prevarg = prevarg.next; kk4 += 1; }; }; for (aa2 != nil) { let slot: i32 = doff + j * esz; if (velemtagged) { cgwidentaggedstore(c, varp.lhs, aa2, slot, esz); } else { if (velemstr) { cgexpr(c, aa2); emitline("\tMOVQ\tAX, "); emitoff(slot: i64); emitline("(BP)\n"); emitline("\tMOVQ\tBX, "); emitoff((slot + 8): i64); emitline("(BP)\n"); } else { if (velemslice) { cgexpr(c, aa2); emitline("\tMOVQ\tAX, "); emitoff(slot: i64); emitline("(BP)\n"); emitline("\tMOVQ\tBX, "); emitoff((slot + 8): i64); emitline("(BP)\n"); emitline("\tMOVQ\tCX, "); emitoff((slot + 16): i64); emitline("(BP)\n"); } else { cgexpr(c, aa2); let op: str = "MOVQ"; if (esz == 1) { op = "MOVB"; } else { if (esz == 4) { op = "MOVL"; }; }; emitline("\t"); emitline(op); emitline("\tAX, "); emitoff(slot: i64); emitline("(BP)\n"); }; }; }; j += 1; aa2 = aa2.next; }; if (nvar > 0) { emitline("\tLEAQ\t"); emitoff(doff: i64); emitline("(BP), AX\n"); } else { emitline("\tXORQ\tAX, AX\n"); }; emitline("\tMOVQ\tAX, "); emitoff(soff: i64); emitline("(BP)\n"); emitline("\tMOVQ\t$"); emitint(nvar: i64); emitline(", AX\n"); emitline("\tMOVQ\tAX, "); emitoff((soff + 8): i64); emitline("(BP)\n"); emitline("\tMOVQ\tAX, "); emitoff((soff + 16): i64); emitline("(BP)\n"); let sn: *node = newnode(c.a, nkind.N_IDENT, "", 0, 0); sn.str = sname; if (prevarg == nil) { n.list = sn; } else { prevarg.next = sn; }; }; }; }; let nargs: i32 = pushargsrev(c, n.list, calleeparams); // Pop forward. Float args were pushed as 8 bytes from X0 via // 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. 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"; }; 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 { let extra: i32 = 0; if (nodeisstr(c, a)) { extra = 1; }; if (nodeisslice(c, a)) { extra = 2; }; 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; w += 1; }; }; a = a.next; }; // Drain any remaining slots that the arg-walker didn't account // for (tagged-union arg sizes > 8B, struct-by-value, etc.). The // existing C cgen pops these into the int stream, so the worst // case here is identical pre-port behaviour. let i: i32 = popped; for (i < nargs) { if (intidx < 6) { emitline("\tPOPQ\t"); emitline(argregname(intidx)); emitline("\n"); intidx += 1; } else { stackslots += 1; }; i += 1; }; let callee: *node = n.lhs; let calleename: str; calleename.ptr = nil; calleename.len = 0; // Detect fn-pointer field call: `w.emit(args)` where `w` is // a struct local and `emit` is an nkind.N_TFN field. Load the // field value into AX and CALL through it. Also detect a // bare `fp(args)` where `fp` is a local holding a function // pointer — mirror C cgen's localfind dispatch (commit // 635818e). Without this the call emits `CALL fp(SB)` and // the linker rightly fails. let isfnptrcall: bool = false; if (callee != nil) { if (callee.kind == nkind.N_IDENT) { let cn: str = callee.str; if (localfindnode(c, cn) != nil) { isfnptrcall = true; }; }; if (callee.kind == nkind.N_DOT) { let base: *node = callee.lhs; let fld: str = callee.str; if (base != nil) { if (base.kind == nkind.N_IDENT) { let bn: str = base.str; let lc: *local = localfindnode(c, bn); if (lc != nil) { let tn: *node = lc.tnode; if (tn != nil) { let lkind: nkind = tn.kind; let sname: str; sname.ptr = nil; sname.len = 0; if (lkind == nkind.N_TNAME) { sname = tn.str; }; if (lkind == nkind.N_TPTR) { let inner: *node = tn.lhs; if (inner != nil) { if (inner.kind == nkind.N_TNAME) { sname = inner.str; }; }; }; if (sname.len > 0) { let si: *structinfo = structlookup(c, sname); if (si != nil) { let fi: *fieldinfo = si.fields; for (fi != nil) { let fn_: str = fi.fname; if (streq(fn_, fld)) { let ft: *node = fi.tnode; if (ft != nil) { if (ft.kind == nkind.N_TFN) { isfnptrcall = true; }; }; fi = nil; } else { fi = fi.finext; }; }; }; }; }; }; }; }; }; }; if (isfnptrcall) { // Load fn-ptr field value into AX; CALL AX. We emit the // load AFTER the args have been popped (so AX/BX/etc // don't get clobbered by the field load before the pops). // `popped args` left DI/SI/etc set; AX is free. cgexpr(c, callee); emitline("\tCALL\tAX\n"); } else { emitline("\tCALL\t"); if (callee != nil) { if (callee.kind == nkind.N_IDENT) { calleename = callee.str; emitsymname(c, calleename); } else { if (callee.kind == nkind.N_DOT) { calleename = callee.str; emitsymname(c, calleename); };}; }; 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) { let rt: *node = fnretlookup(c, calleename); if (isstrtype(c, rt)) { emitline("\tMOVQ\tDX, BX\n"); }; }; return; }; fn cgassign(c: *cgen, n: *node) void = { let lhs: *node = n.lhs; // Discard lvalue `_ = expr;` — evaluate rhs for side effects, // write nothing. Detected by lhs being an nkind.N_IDENT with empty str // (planted by parseprimary on the tkind.TK_UNDER token). if (lhs != nil) { if (lhs.kind == nkind.N_IDENT) { if (lhs.str.len == 0) { if (n.op == tkind.TK_ASSIGN) { cgexpr(c, n.rhs); return; }; }; }; }; // Tagged-union local reassignment: `r = expr;` where r has a // tagged-union type. Delegate to cgwidentaggedstore (same path // as cglet's tagged-init). Covers nullable fold, tagged source, // struct payload, str payload, scalar payload, with tag remap. if (lhs != nil) { if (lhs.kind == nkind.N_IDENT) { if (n.op == tkind.TK_ASSIGN) { let lc: *local = localfindnode(c, lhs.str); if (lc != nil) { if (istaggedtype(c, lc.tnode)) { let lsz: i32 = slotsize(c, lc.tnode); cgwidentaggedstore(c, lc.tnode, n.rhs, lc.off, lsz); return; }; }; }; }; }; // `*p = v` — deref-assign. Element width comes from the // pointer's declared type. Mirrors C cgen: eval rhs (AX, // and BX if str), push, eval pointer, pop value, store. // We default to MOVQ (8B) since most fixtures use it; for // `*bool` / `*u8` / `*i32` we narrow via the local's tnode. if (lhs != nil) { if (lhs.kind == nkind.N_UN) { if (lhs.op == tkind.TK_STAR) { if (n.op == tkind.TK_ASSIGN) { let inner: *node = lhs.lhs; let elemstr: bool = false; let elemfloat: bool = false; let elemf32: bool = false; let storeop: str = "MOVQ"; if (inner != nil) { if (inner.kind == nkind.N_IDENT) { let lc: *local = localfindnode(c, inner.str); if (lc != nil) { let tn: *node = lc.tnode; if (tn != nil) { if (tn.kind == nkind.N_TPTR) { let pe: *node = tn.lhs; if (pe != nil) { if (pe.kind == nkind.N_TNAME) { if (streq(pe.str, "str")) { elemstr = true; } else { if (streq(pe.str, "f64")) { elemfloat = true; } else { if (streq(pe.str, "f32")) { elemfloat = true; elemf32 = true; } else { let ps: i32 = primsize(pe.str); if (ps == 1) { storeop = "MOVB"; } else { if (ps == 4) { storeop = "MOVL"; }; }; }; }; }; }; }; }; }; }; }; }; cgexpr(c, n.rhs); // `*p = v` for *f64 / *f32: value sits in X0. Spill // to the stack, evaluate the pointer (clobbers AX), // then reload X0 and MOVSD/MOVSS through the pointer. if (elemfloat) { let mov: str = "MOVSD"; if (elemf32) { mov = "MOVSS"; }; emitline("\tSUBQ\t$8, SP\n"); emitline("\t"); emitline(mov); emitline("\tX0, (SP)\n"); cgexpr(c, inner); emitline("\tMOVQ\tAX, BX\n"); emitline("\t"); emitline(mov); emitline("\t(SP), X0\n"); emitline("\tADDQ\t$8, SP\n"); emitline("\t"); emitline(mov); emitline("\tX0, (BX)\n"); return; }; // Push order matches C cgen // (cmd/w6c/cgen.c:1033-1041): PUSHQ AX // (ptr) first, then PUSHQ BX (len) if // str, so the pop sequence is POP CX // (len) → POP AX (ptr) → MOVQ AX, // (BX) → MOVQ CX, 8(BX). emitline("\tPUSHQ\tAX\n"); if (elemstr) { emitline("\tPUSHQ\tBX\n"); }; cgexpr(c, inner); emitline("\tMOVQ\tAX, BX\n"); if (elemstr) { emitline("\tPOPQ\tCX\n"); emitline("\tPOPQ\tAX\n"); emitline("\tMOVQ\tAX, (BX)\n"); emitline("\tMOVQ\tCX, 8(BX)\n"); return; }; emitline("\tPOPQ\tAX\n"); emitline("\t"); emitline(storeop); emitline("\tAX, (BX)\n"); return; }; }; }; }; // Array/slice/ptr index store: `arr[i] = v;`. Element size // from base.tnode picks MOVB vs MOVQ. if (lhs != nil) { if (lhs.kind == nkind.N_INDEX) { if (n.op == tkind.TK_ASSIGN) { let base: *node = lhs.lhs; 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; let elemtn: *node = nil; if (base != nil) { if (base.kind == nkind.N_IDENT) { let bn: str = base.str; baselocal = localfindnode(c, bn); if (baselocal != nil) { esz = elemsizeofc(c, baselocal.tnode); let btn: *node = baselocal.tnode; if (btn != nil) { let bk: nkind = btn.kind; if (bk == nkind.N_TARRAY) { elemtn = btn.lhs; }; if (bk == nkind.N_TSLICE) { elemtn = btn.lhs; }; if (bk == nkind.N_TPTR) { elemtn = btn.lhs; }; }; } else { let tn: *node = letvartnode(c, bn); if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isglobalarr = true; globalname = bn; esz = elemsizeofc(c, tn); elemtn = tn.lhs; }; if (tn.kind == nkind.N_TPTR) { isglobalptr = true; globalname = bn; esz = elemsizeofc(c, tn); elemtn = tn.lhs; }; }; }; } else { if (base.kind == nkind.N_DOT) { esz = indexbaseesz(c, base); };}; }; // Tagged-union element: materialize source in a shared // scratch slot via cgwidentaggedstore (handles struct / // str / scalar / subset / nullable variants uniformly), // then compute &arr[i] and byte-copy. The scratch // (@tagscr) is reused across all tagged-arr stores in // the function and counted once in scanlocals. if (elemtn != nil) { if (istaggedtype(c, elemtn)) { let slot_sz: i32 = slotsize(c, elemtn); let scroff: i32 = localadd(c, "@tagscr", 24, nil); // Pre-zero scratch (matches push helper). emitline("\tXORQ\tAX, AX\n"); let zz: i32 = 0; for (zz < slot_sz) { emitline("\tMOVQ\tAX, "); emitoff((scroff + zz): i64); emitline("(BP)\n"); zz += 8; }; cgwidentaggedstore(c, elemtn, n.rhs, scroff, slot_sz); cgexpr(c, idx); if (slot_sz > 1) { emitline("\tMOVQ\t$"); emitint(slot_sz: i64); emitline(", CX\n"); emitline("\tIMULQ\tCX, AX\n"); }; 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 isarr: bool = false; if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isarr = true; }; }; if (isarr) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); emitline("(BP), BX\n"); } else { emitline("\tMOVQ\t"); emitoff(baselocal.off: i64); emitline("(BP), BX\n"); }; } else { emitline("\tPUSHQ\tAX\n"); cgexpr(c, base); emitline("\tMOVQ\tAX, BX\n"); emitline("\tPOPQ\tAX\n"); };};}; emitline("\tADDQ\tAX, BX\n"); let cc: i32 = 0; for (cc < slot_sz) { emitline("\tMOVQ\t"); emitoff((scroff + cc): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\tAX, "); emitoff(cc: i64); emitline("(BX)\n"); cc += 8; }; return; }; }; cgexpr(c, n.rhs); // value → AX if (esz == 16) { emitline("\tPUSHQ\tBX\n"); }; emitline("\tPUSHQ\tAX\n"); cgexpr(c, idx); // idx → AX if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", CX\n"); emitline("\tIMULQ\tCX, AX\n"); }; emitline("\tPUSHQ\tAX\n"); // scaled idx 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; }; }; if (isarray) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); emitline("(BP), BX\n"); } else { emitline("\tMOVQ\t"); emitoff(baselocal.off: i64); emitline("(BP), BX\n"); }; } 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 if (esz == 16) { emitline("\tMOVQ\tAX, (BX)\n"); emitline("\tPOPQ\tCX\n"); emitline("\tMOVQ\tCX, 8(BX)\n"); return; }; if (esz == 1) { emitline("\tMOVB\tAX, (BX)\n"); } else { if (esz == 4) { emitline("\tMOVL\tAX, (BX)\n"); } else { emitline("\tMOVQ\tAX, (BX)\n"); };}; return; }; }; }; // Struct/ptr-to-struct field assignment: `s.f = expr;` or // `p.f = expr;`. Only plain `=` is wired (compound on field // is rare and not yet needed by our fixtures). 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_IDENT) { let bn: str = base.str; let lc: *local = localfindnode(c, bn); if (lc != nil) { let tn: *node = lc.tnode; let lkind: nkind = nkind.N_NONE; if (tn != nil) { lkind = tn.kind; }; // Pointer-to-struct: deref then store. if (lkind == nkind.N_TPTR) { let inner: *node = tn.lhs; let sname: str; sname.ptr = nil; sname.len = 0; if (inner != nil) { if (inner.kind == nkind.N_TNAME) { sname = inner.str; }; }; if (sname.len > 0) { let si: *structinfo = structlookup(c, sname); if (si != nil) { let fi: *fieldinfo = si.fields; for (fi != nil) { let fn_: str = fi.fname; if (streq(fn_, fld)) { if (n.op != tkind.TK_ASSIGN) { // compound: load current value emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); let lop: str = fieldloadop(fi); emitline("\t"); emitline(lop); emitline("\t"); emitdispreg(fi.foff: i64, "BX"); emitline(", BX\n"); emitline("\tPUSHQ\tBX\n"); }; cgexpr(c, n.rhs); if (n.op != tkind.TK_ASSIGN) { emitline("\tPOPQ\tBX\n"); // PLUSEQ is commutative; MINUSEQ // needs lhs - rhs (BX is old lhs, // AX is rhs). if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); }; if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, BX\n"); emitline("\tMOVQ\tBX, AX\n"); }; }; // str field via *struct: rhs left // (AX=ptr, BX=len). Use CX as the // address scratch so we don't clobber // the len half before storing it. if (n.op == tkind.TK_ASSIGN) { if (isstrtype(c, fi.tnode)) { emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), CX\n"); emitline("\tMOVQ\tAX, "); emitdispreg(fi.foff: i64, "CX"); emitline("\n"); emitline("\tMOVQ\tBX, "); emitdispreg((fi.foff + 8): i64, "CX"); emitline("\n"); return; }; // f64/f32 plain `=` via *struct: cgexpr left the // value in X0. Reload struct ptr and MOVSD/MOVSS. if (isfloattype(c, fi.tnode)) { let mov: str = "MOVSD"; if (isf32type(c, fi.tnode)) { mov = "MOVSS"; }; emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); emitline("\t"); emitline(mov); emitline("\tX0, "); emitdispreg(fi.foff: i64, "BX"); emitline("\n"); return; }; }; emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); let sop: str = fieldstoreop(fi); emitline("\t"); emitline(sop); emitline("\tAX, "); emitdispreg(fi.foff: i64, "BX"); emitline("\n"); return; }; fi = fi.finext; }; }; }; }; // Direct struct local: store at off+foff. if (lkind == nkind.N_TNAME) { let sname: str = tn.str; let si: *structinfo = structlookup(c, sname); if (si != nil) { let fi: *fieldinfo = si.fields; for (fi != nil) { 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"; if (isf32type(c, fi.tnode)) { mov = "MOVSS"; }; emitline("\t"); emitline(mov); emitline("\tX0, "); emitoff((lc.off + fi.foff): i64); emitline("(BP)\n"); return; }; let sop: str = fieldstoreop(fi); emitline("\t"); emitline(sop); emitline("\tAX, "); emitoff((lc.off + fi.foff): i64); emitline("(BP)\n"); return; }; fi = fi.finext; }; }; }; // str/slice pseudo-field assignment. let delta: i32 = -1; if (streq(fld, "ptr")) { delta = 0; }; if (streq(fld, "len")) { delta = 8; }; if (streq(fld, "cap")) { delta = 16; }; if (delta >= 0) { if (lkind == nkind.N_TPTR) { let inner: *node = tn.lhs; let innerkind: nkind = nkind.N_NONE; if (inner != nil) { innerkind = inner.kind; }; let innerstr: bool = false; if (innerkind == nkind.N_TNAME) { if (streq(inner.str, "str")) { innerstr = true; }; }; if (innerkind == nkind.N_TSLICE) { innerstr = true; }; if (innerstr) { if (n.op != tkind.TK_ASSIGN) { // Compound on `(*str|*slice).field`: load // current → push → eval rhs → combine → store. emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); emitline("\tMOVQ\t"); emitdispreg(delta: i64, "BX"); emitline(", BX\n"); emitline("\tPUSHQ\tBX\n"); cgexpr(c, n.rhs); emitline("\tPOPQ\tBX\n"); // PLUSEQ is commutative; MINUSEQ // needs lhs - rhs. if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); }; if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, BX\n"); emitline("\tMOVQ\tBX, AX\n"); }; emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); emitline("\tMOVQ\tAX, "); emitdispreg(delta: i64, "BX"); emitline("\n"); return; }; cgexpr(c, n.rhs); emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); emitline("\tMOVQ\tAX, "); emitdispreg(delta: i64, "BX"); emitline("\n"); return; }; }; cgexpr(c, n.rhs); emitline("\tMOVQ\tAX, "); emitoff((lc.off + delta): i64); emitline("(BP)\n"); return; }; }; }; }; }; }; // Top-level struct global field assignment: `g.f = expr;` and // `g.f += expr;` for a scalar/str field. Reached when the local // lookup miss but the IDENT base is a registered struct `let`. // LEAQ name(SB) into BX/CX takes the place of the frame slot // addressing the local branches use. Compound (PLUSEQ/MINUSEQ) // follows the same load → push → eval → combine → store shape // as the via-ptr local path. 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_IDENT) { let bn: str = base.str; if (localfindnode(c, bn) == nil) { let si: *structinfo = letvarstructinfo(c, bn); if (si != nil) { let fi: *fieldinfo = si.fields; for (fi != nil) { if (streq(fi.fname, fld)) { if (n.op == tkind.TK_ASSIGN) { cgexpr(c, n.rhs); if (isstrtype(c, fi.tnode)) { emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), CX\n"); emitline("\tMOVQ\tAX, "); emitdispreg(fi.foff: i64, "CX"); emitline("\n"); emitline("\tMOVQ\tBX, "); emitdispreg((fi.foff + 8): i64, "CX"); emitline("\n"); return; }; // f64/f32 plain `=` on global struct field: value is // in X0; LEAQ the base into BX and MOVSD/MOVSS. if (isfloattype(c, fi.tnode)) { let mov: str = "MOVSD"; if (isf32type(c, fi.tnode)) { mov = "MOVSS"; }; emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); emitline("\t"); emitline(mov); emitline("\tX0, "); emitdispreg(fi.foff: i64, "BX"); emitline("\n"); return; }; let sop: str = fieldstoreop(fi); emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); emitline("\t"); emitline(sop); emitline("\tAX, "); emitdispreg(fi.foff: i64, "BX"); emitline("\n"); return; }; // Compound on scalar field: load // → push → eval rhs → combine → // store. cgexpr clobbers BX, so // re-LEAQ for the store. let lop: str = fieldloadop(fi); emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); emitline("\t"); emitline(lop); emitline("\t"); emitdispreg(fi.foff: i64, "BX"); emitline(", BX\n"); emitline("\tPUSHQ\tBX\n"); cgexpr(c, n.rhs); emitline("\tPOPQ\tBX\n"); if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); }; if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, BX\n"); emitline("\tMOVQ\tBX, AX\n"); }; let sop: str = fieldstoreop(fi); emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); emitline("\t"); emitline(sop); emitline("\tAX, "); emitdispreg(fi.foff: i64, "BX"); emitline("\n"); return; }; fi = fi.finext; }; }; }; }; }; }; }; // Chained `.field = v` where `` itself is a chain // of dots resolving to a *struct. Mirrors the C cgen branch // added to close trap 1 (cmd/w6c/cgen.c). Without this, only // `local.field = v` and `local.fieldptr.field = v` get wired // (the latter through the IDENT-base branch above) — chains // like `s.last.snext = sy` (lib/ww/sym.ww) silently emit no // store. Only plain `=` is wired here; chained compound on a // pointer-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 innert: *node = dotinnerstructptr(c, base); if (innert != nil) { let sname: str = innert.str; let si: *structinfo = structlookup(c, sname); if (si != nil) { let fi: *fieldinfo = si.fields; for (fi != nil) { if (streq(fi.fname, fld)) { if (n.op == tkind.TK_ASSIGN) { if (isstrtype(c, fi.tnode)) { // str rhs: AX=ptr, BX=len. // Stash both, then load // the struct ptr into CX // and write both halves. cgexpr(c, n.rhs); emitline("\tPUSHQ\tBX\n"); emitline("\tPUSHQ\tAX\n"); cgexpr(c, base); emitline("\tMOVQ\tAX, CX\n"); emitline("\tPOPQ\tAX\n"); emitline("\tPOPQ\tBX\n"); emitline("\tMOVQ\tAX, "); emitdispreg(fi.foff: i64, "CX"); emitline("\n"); emitline("\tMOVQ\tBX, "); emitdispreg((fi.foff + 8): i64, "CX"); emitline("\n"); return; }; // f64/f32 chained plain `=`: cgexpr rhs left value in // X0. Spill to stack so cgexpr(base) can use AX, then // reload and MOVSD/MOVSS into the slot. if (isfloattype(c, fi.tnode)) { let mov: str = "MOVSD"; if (isf32type(c, fi.tnode)) { mov = "MOVSS"; }; cgexpr(c, n.rhs); emitline("\tSUBQ\t$8, SP\n"); emitline("\t"); emitline(mov); emitline("\tX0, (SP)\n"); cgexpr(c, base); emitline("\tMOVQ\tAX, BX\n"); emitline("\t"); emitline(mov); emitline("\t(SP), X0\n"); emitline("\tADDQ\t$8, SP\n"); emitline("\t"); emitline(mov); emitline("\tX0, "); emitdispreg(fi.foff: i64, "BX"); emitline("\n"); return; }; cgexpr(c, n.rhs); emitline("\tPUSHQ\tAX\n"); cgexpr(c, base); emitline("\tMOVQ\tAX, BX\n"); emitline("\tPOPQ\tAX\n"); let sop: str = fieldstoreop(fi); emitline("\t"); emitline(sop); emitline("\tAX, "); emitdispreg(fi.foff: i64, "BX"); emitline("\n"); return; }; }; fi = fi.finext; }; }; }; }; }; }; }; // 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. if (lhs != nil) { if (lhs.kind == nkind.N_IDENT) { let nm: str = lhs.str; let off: i32 = localfind(c, nm); if (off == 0) { // Top-level let target: RIP-relative store // for `=`, or load→combine→store for the // compound forms. For a str/slice global, // take its address into CX and store both // halves (plus cap for slice — stashed via // DI since LEAQ overwrites CX); the asm has // no `name+8(SB)` operand form. if (!isletvar(c, nm)) { return; }; // Float global: rhs lands in X0; store via // LEAQ+indirect since MOVSS/MOVSD have no // D_EXTERN operand form. let lvf: *letvar = c.lets; let isfg: bool = false; let isf32g: bool = false; for (lvf != nil) { if (streq(lvf.name, nm)) { isfg = isfloattype(c, lvf.tnode); isf32g = isf32type(c, lvf.tnode); lvf = nil; } else { lvf = lvf.lvnext; }; }; if (isfg) { cgexpr(c, n.rhs); let mov: str = "MOVSD"; let addf: str = "ADDSD"; let subf: str = "SUBSD"; let mulf: str = "MULSD"; let divf: str = "DIVSD"; if (isf32g) { mov = "MOVSS"; addf = "ADDSS"; subf = "SUBSS"; mulf = "MULSS"; divf = "DIVSS"; }; emitline("\tLEAQ\t"); emitsymname(c, nm); emitline("(SB), CX\n"); if (n.op == tkind.TK_ASSIGN) { emitline("\t"); emitline(mov); emitline("\tX0, (CX)\n"); return; }; // Compound: X1 = load; X1 OP= X0; store X1. // ADDSD/SUBSD/MULSD/DIVSD are register-register // only, so we can't combine direct to memory. let fop: str; fop.ptr = nil; fop.len = 0; if (n.op == tkind.TK_PLUSEQ) { fop = addf; }; if (n.op == tkind.TK_MINUSEQ) { fop = subf; }; if (n.op == tkind.TK_STAREQ) { fop = mulf; }; if (n.op == tkind.TK_SLASHEQ) { fop = divf; }; if (fop.len == 0) { // Unsupported (e.g., %= on float): // fall back to plain store of rhs. emitline("\t"); emitline(mov); emitline("\tX0, (CX)\n"); return; }; emitline("\t"); emitline(mov); emitline("\t(CX), X1\n"); emitline("\t"); emitline(fop); emitline("\tX0, X1\n"); emitline("\t"); emitline(mov); emitline("\tX1, (CX)\n"); return; }; cgexpr(c, n.rhs); if (n.op == tkind.TK_ASSIGN) { if (letvarisstr(c, nm)) { emitline("\tLEAQ\t"); emitsymname(c, nm); emitline("(SB), CX\n"); emitline("\tMOVQ\tAX, (CX)\n"); emitline("\tMOVQ\tBX, 8(CX)\n"); return; }; if (letvarisslice(c, nm)) { emitline("\tMOVQ\tCX, DI\n"); emitline("\tLEAQ\t"); emitsymname(c, nm); emitline("(SB), CX\n"); emitline("\tMOVQ\tAX, (CX)\n"); emitline("\tMOVQ\tBX, 8(CX)\n"); emitline("\tMOVQ\tDI, 16(CX)\n"); return; }; emitline("\tMOVQ\tAX, "); emitsymname(c, nm); emitline("(SB)\n"); return; }; emitline("\tMOVQ\t"); emitsymname(c, nm); emitline("(SB), BX\n"); let didcompound: bool = true; if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tAX, BX\n"); } else { if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, BX\n"); } else { if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); } else { if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tAX, BX\n"); } else { if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tAX, BX\n"); } else { if (n.op == tkind.TK_CARETEQ) { emitline("\tXORQ\tAX, BX\n"); } else { if (n.op == tkind.TK_LSHIFTEQ) { emitline("\tMOVQ\tAX, CX\n"); emitline("\tSHLQ\tCX, BX\n"); } else { if (n.op == tkind.TK_RSHIFTEQ) { emitline("\tMOVQ\tAX, CX\n"); emitline("\tSHRQ\tCX, BX\n"); } else { // Unsupported compound: store rhs // directly. Mirrors the local path's // fallback for TK_SLASHEQ etc. didcompound = false; emitline("\tMOVQ\tAX, "); emitsymname(c, nm); emitline("(SB)\n"); };};};};};};};}; if (didcompound) { emitline("\tMOVQ\tBX, "); emitsymname(c, nm); emitline("(SB)\n"); }; return; }; // Detect str/slice-typed local — assignment must store // both halves (AX=ptr at +0, BX=len at +8) for str, // plus the cap (CX at +16) for slice. let lcstr: bool = false; let lcsl: bool = false; let lcn: *local = localfindnode(c, nm); if (lcn != nil) { lcstr = isstrtype(c, lcn.tnode); lcsl = isslicetype(c, lcn.tnode); }; let lcf: bool = false; let lcf32: bool = false; if (lcn != nil) { lcf = isfloattype(c, lcn.tnode); lcf32 = isf32type(c, lcn.tnode); }; // Float-typed local: rhs lands in X0; store via MOVSD/ // MOVSS, no AX shuffle. Compound (+= -= *= /=) loads // slot into X1, combines into X1, stores X1 back — // ADDSD/SUBSD/MULSD/DIVSD are register-register only. if (lcf) { cgexpr(c, n.rhs); let mov: str = "MOVSD"; let addf: str = "ADDSD"; let subf: str = "SUBSD"; let mulf: str = "MULSD"; let divf: str = "DIVSD"; if (lcf32) { mov = "MOVSS"; addf = "ADDSS"; subf = "SUBSS"; mulf = "MULSS"; divf = "DIVSS"; }; if (n.op == tkind.TK_ASSIGN) { emitline("\t"); emitline(mov); emitline("\tX0, "); emitoff(off: i64); emitline("(BP)\n"); return; }; let fop: str; fop.ptr = nil; fop.len = 0; if (n.op == tkind.TK_PLUSEQ) { fop = addf; }; if (n.op == tkind.TK_MINUSEQ) { fop = subf; }; if (n.op == tkind.TK_STAREQ) { fop = mulf; }; if (n.op == tkind.TK_SLASHEQ) { fop = divf; }; if (fop.len == 0) { emitline("\t"); emitline(mov); emitline("\tX0, "); emitoff(off: i64); emitline("(BP)\n"); return; }; emitline("\t"); emitline(mov); emitline("\t"); emitoff(off: i64); emitline("(BP), X1\n"); emitline("\t"); emitline(fop); emitline("\tX0, X1\n"); emitline("\t"); emitline(mov); emitline("\tX1, "); emitoff(off: i64); emitline("(BP)\n"); return; }; cgexpr(c, n.rhs); if (n.op == tkind.TK_ASSIGN) { emitline("\tMOVQ\tAX, "); emitoff(off: i64); emitline("(BP)\n"); if (lcstr || lcsl) { emitline("\tMOVQ\tBX, "); emitoff((off + 8): i64); emitline("(BP)\n"); }; if (lcsl) { emitline("\tMOVQ\tCX, "); emitoff((off + 16): i64); emitline("(BP)\n"); }; return; }; if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tAX, "); emitoff(off: i64); emitline("(BP)\n"); return; }; if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, "); emitoff(off: i64); emitline("(BP)\n"); return; }; // Generic compound: load → combine in BX → store. emitline("\tMOVQ\t"); emitoff(off: i64); emitline("(BP), BX\n"); if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); }; if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tAX, BX\n"); }; if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tAX, BX\n"); }; if (n.op == tkind.TK_CARETEQ) { emitline("\tXORQ\tAX, BX\n"); }; if (n.op == tkind.TK_LSHIFTEQ) { emitline("\tMOVQ\tAX, CX\n"); emitline("\tSHLQ\tCX, BX\n"); }; if (n.op == tkind.TK_RSHIFTEQ) { emitline("\tMOVQ\tAX, CX\n"); emitline("\tSHRQ\tCX, BX\n"); }; emitline("\tMOVQ\tBX, "); emitoff(off: i64); emitline("(BP)\n"); return; }; }; return; };