// selfhost/cmd/wcc/cgenexpr.ww — split out of cgen.ww. // // 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. package wcc; import os; import syntax; import strconv; // cgfloatbits — materialise a float constant in X0: MOVQ the IEEE bits // into AX, PUSH, MOVSD off the stack into X0. Shared by N_FLOATLIT (bits // already in n.uval from the lexer's bitcast) and the f64/f32-typed // N_INTLIT arm (#103 FACE X). fn cgfloatbits(c: *cgen, bits: u64) void = { emitline("\tMOVQ\t$"); emitint(bits: i64); emitline(", AX\n"); emitline("\tPUSHQ\tAX\n"); emitline("\tMOVSD\t(SP), X0\n"); emitline("\tADDQ\t$8, SP\n"); }; fn cgexpr(c: *cgen, n: *syntax.node) void = { if (n == nil) { return; }; let k: syntax.nkind = n.kind; switch (k) { case syntax.nkind.N_INTLIT: // A no-decimal `0f64`/`8f64` is an N_INTLIT carrying float // TYPE; it must reach X0 like a true float literal, not the // integer-immediate path (which strands it in AX and an SSE // compare/mul reads a stale X0 — #103 FACE X). The bits are // the IEEE pattern of the integer value, mirroring cstage's // `(double)(long long)n->uval`; the (&fv):*u64 bitcast is the // lex.ww idiom (lib/ww/lex/lex.ww). if (isfloattype(c, n)) { let fv: f64 = (n.uval: i64): f64; let pu: *u64 = (&fv): *u64; cgfloatbits(c, *pu); // #104: cgfloatbits materialises a DOUBLE in X0; an // f32-typed literal must narrow with hardware single- // rounding so the downstream MOVSS reads a true single. if (isf32type(c, n)) { emitline("\tCVTSD2SS\tX0, X0\n"); }; return; }; // 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; case syntax.nkind.N_FLOATLIT: // The bits come from n.uval — the parser populates it from // the lexer's bitcast of t.fval. cgfloatbits(c, n.uval); // #104: narrow the double in X0 to single for an f32 literal. if (isf32type(c, n)) { emitline("\tCVTSD2SS\tX0, X0\n"); }; return; case syntax.nkind.N_RUNELIT: emitline("\tMOVQ\t$"); emitint(n.uval: i64); emitline(", AX\n"); return; case syntax.nkind.N_STRLIT: cgstrlit(c, n); return; case syntax.nkind.N_TRUE: emitline("\tMOVQ\t$1, AX\n"); return; case syntax.nkind.N_FALSE: emitline("\tMOVQ\t$0, AX\n"); return; case syntax.nkind.N_NIL: emitline("\tMOVQ\t$0, AX\n"); return; case syntax.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; case syntax.nkind.N_IDENT: cgident(c, n); return; case syntax.nkind.N_INDEX: cgindex(c, n); return; case syntax.nkind.N_SLICE: cgslice(c, n); return; case syntax.nkind.N_MATCH: cgmatch(c, n); return; case syntax.nkind.N_CAST: cgcast(c, n); return; case syntax.nkind.N_DOT: cgdot(c, n); return; case syntax.nkind.N_UN: cgun(c, n); return; case syntax.nkind.N_BIN: cgbin(c, n); return; case syntax.nkind.N_CALL: cgcall(c, n); return; case syntax.nkind.N_ASSIGN: cgassign(c, n); return; case syntax.nkind.N_TRYPROP: cgtryprop(c, n); return; case syntax.nkind.N_TRYUNW: cgtryunw(c, n); return; case syntax.nkind.N_TYPETEST: cgtypetest(c, n); return; case syntax.nkind.N_TYPEASSERT: cgtypeassert(c, n); return; case syntax.nkind.N_TUPLE: // #241: a literal tuple rvalue `(a, b)` is a value — pack its // elements into the register cursor (mirror cgreturn's N_TUPLE // arm) so a let-bind / destructure consumer reads every element, // not just AX = 0 from the default arm below. cgtuplelittocursor(c, n, nil); return; case: // 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: *syntax.node, vt: *syntax.node) i32 = { if (tagged == nil) { return -1; }; if (vt == nil) { return -1; }; if (tagged.kind != syntax.nkind.N_TTAGGED) { // #22a: a STAMPED-CARRIER scrutinee (the #67 matchscrutt // shape — is/as on a tuple element t.N, a struct field, an // indexed element) is not an N_TTAGGED type-AST node; its // tagged type rides .type_. Resolve via the tinfo twin // (flatvariantidxt), the same core the widen-store uses — // cstage cg_tag_for_variant is type-based for every // scrutinee shape, so the AST-keyed -1 here was a silent // tag-0 clamp on wwstage (cs CMPQ $1 vs ww CMPQ $0). let sti: *syntax.tinfo = tagged.type_: *syntax.tinfo; sti = tichase(sti); if (sti != nil && sti.kind == syntax.tykind.TY_TAGGED && vt.type_ != nil) { return flatvariantidxt(sti, vt.type_: *syntax.tinfo, false); }; return -1; }; // `is []T` / `as []T` — slice-shape lookup routes through the // element-aware helper, which carries the loose first-slice-shape // fallback (cstage type_assignable stand-in) that flatvariantidx's // strict typeeq below doesn't. Task #19; #66 refresh. if (vt.kind == syntax.nkind.N_TSLICE) { return flatslicevariantidx(c, tagged, vt.lhs); }; // #66 Phase-N step 3: match by typeeq on vt's stamped tinfo // (flatvariantidx), not vt's surface name. return flatvariantidx(c, tagged, vt); }; // successtag — index of the success variant of a tagged union. Mirrors // cstage cg_tagged_success_tag (cmd/w6c/cgen.c:857): if any variant is an // error (`!T`), the success tag is the first NON-error variant's index; // else 0 (legacy/no-error). Drives the `?`/`!` success-tag CMPQ + the // payload-shift variant lookup (#52/#216 — replaces the hardcoded tag-0). fn successtag(ou: *syntax.tinfo) i64 = { let u: *syntax.tinfo = tichase(ou); if (u == nil) { return 0i64; }; if (u.kind != syntax.tykind.TY_TAGGED) { return 0i64; }; let haserr: bool = false; let p: *syntax.tparam = u.params; for (p != nil) { if (p.iserror) { haserr = true; }; p = p.tnext; }; if (!haserr) { return 0i64; }; let idx: i64 = 0i64; p = u.params; for (p != nil) { if (!p.iserror) { return idx; }; idx += 1i64; p = p.tnext; }; return 0i64; }; // successvariant — the success variant's type_ (the param at successtag). // Lets the #241 tuple/tagged payload-shift sites inspect the SUCCESS // variant's shape, not the (error-first) first param. fn successvariant(ou: *syntax.tinfo) *syntax.tinfo = { let u: *syntax.tinfo = tichase(ou); if (u == nil) { return nil; }; if (u.kind != syntax.tykind.TY_TAGGED) { return nil; }; let st: i64 = successtag(ou); let idx: i64 = 0i64; let p: *syntax.tparam = u.params; for (p != nil) { if (idx == st) { return p.type_; }; idx += 1i64; p = p.tnext; }; return nil; }; // variantiserror — is variant idx of tagged union u an error variant? // Mirrors cstage cg_variant_is_error (cmd/w6c/cgen.c:905): explicit // `!`-marked unions use the per-variant flag; a LEGACY union (no `!` // marks anywhere) treats index 0 as success and every other variant as // an error. The #173 remap loop must use THIS predicate, not the bare // flag — the flag-only gate skipped legacy unions entirely, so `?` // propagated the callee's raw tag into a reordered caller union // (silent wrong arm). fn variantiserror(ou: *syntax.tinfo, idx: i32) bool = { let u: *syntax.tinfo = tichase(ou); if (u == nil) { return false; }; if (u.kind != syntax.tykind.TY_TAGGED) { return false; }; let haserr: bool = false; let p: *syntax.tparam = u.params; for (p != nil) { if (p.iserror) { haserr = true; }; p = p.tnext; }; let i: i32 = 0; p = u.params; for (p != nil) { if (i == idx) { if (haserr) { return p.iserror; }; return idx != 0; }; i += 1; p = p.tnext; }; return false; }; // cgtrytupleshift — #241: if the `?`/`!` operand's success variant is a // tuple, the unwrapped payload is an rvalue tuple that must fill the // register cursor (shift past the tag), and the scalar/str MOVQ DX,AX tail // is skipped. Returns true when it emitted the shift. Reads the operand's // stamped tagged result tinfo (n.lhs.type_); the success variant is the // param at successtag (dynamic, #52 — not the hardcoded first param), // matching the dynamic CMPQ $successtag success-tag convention. fn cgtrytupleshift(c: *cgen, n: *syntax.node) bool = { if (n.lhs == nil) { return false; }; let ou: *syntax.tinfo = n.lhs.type_: *syntax.tinfo; ou = tichase(ou); if (ou == nil) { return false; }; if (ou.kind != syntax.tykind.TY_TAGGED) { return false; }; if (ou.params == nil) { return false; }; let sv: *syntax.tinfo = successvariant(ou); sv = tichase(sv); if (sv == nil) { return false; }; if (sv.kind != syntax.tykind.TY_TUPLE) { return false; }; cgtaggedtuplepayloadshift(c, sv); return true; }; // cgtrytaggedshift — Family C (#35, unwrap source): if the `?`/`!` // operand's success variant (at successtag, #52) is itself a TAGGED union, the // unwrapped value is a NESTED box (ww keeps nested unions // un-flattened) riding the payload words intact — shift past the // outer tag so consumers see the standard AX=tag cursor. The scalar // MOVQ DX,AX tail carried only the inner tag and dropped the payload // (ken unw16). Nullable folds to one word and stays on the scalar // move. Twin of cgtrytupleshift; mirrors cstage N_TRYPROP/N_TRYUNW. fn cgtrytaggedshift(c: *cgen, n: *syntax.node) bool = { if (n.lhs == nil) { return false; }; let ou: *syntax.tinfo = n.lhs.type_: *syntax.tinfo; ou = tichase(ou); if (ou == nil) { return false; }; if (ou.kind != syntax.tykind.TY_TAGGED) { return false; }; if (ou.params == nil) { return false; }; let sv: *syntax.tinfo = successvariant(ou); sv = tichase(sv); if (sv == nil) { return false; }; // #12: a general-aggregate (struct/array) success variant rides the // SAME in-cap {AX=w0,DX=w1,CX=w2} payload shuffle as the nested-TAGGED // box — the union return packs the payload as raw GP words past the // outer tag. Pre-#12 it matched no arm and fell to the bare MOVQ DX,AX // below, materialising only w0 (w1/w2 dropped) — a SILENT both-stage // word-drop. A float-bearing aggregate rides X0/X1 instead (the SSE // return-class), which this GP cursor cannot reach, so LOUD-STOP it // (mirror #11/#165). Mirrors cstage N_TRYPROP/N_TRYUNW. let agg: bool = false; if (sv.kind == syntax.tykind.TY_TAGGED && sv.nullable == 0) { agg = true; }; if (sv.kind == syntax.tykind.TY_STRUCT) { agg = true; }; if (sv.kind == syntax.tykind.TY_ARRAY) { agg = true; }; if (!agg) { return false; }; if (tinfoaggfloat(sv)) { let m12f: str = "#12/#165: float-bearing aggregate success variant unwrap (S|e)! rides SSE X0/X1; GP cursor unwired\n"; os.write(2, m12f.ptr, m12f.len: u64); os.exit(1); }; emitline("\tMOVQ\tDX, AX\n"); if (sv.size: i32 > 8) { emitline("\tMOVQ\tCX, DX\n"); }; if (sv.size: i32 > 16) { emitline("\tMOVQ\tR8, CX\n"); }; return true; }; // cgtryunwcursor — Family C (#35/#46): land the `?`/`!` operand's // tagged box in the AX/DX/CX/R8 cursor for the unwrap tail. Non-call // sources don't fill the cursor on their own: an IDENT loads it from // its frame slot, a mem-based read (deref at any size, >32B // INDEX/DOT) from the box address cgexpr leaves in AX. Both were // silent word0 unwraps pre-#35. >32B non-call stays loud (the cursor // cannot carry it; #40 family). Mirrors cstage N_TRYPROP/N_TRYUNW. // cgdotfieldcombine — single-dot field compound combine. The old // field value is in BX, the rhs in AX; the result is left in AX. // PLUSEQ/MINUSEQ preserve the pre-#34 emission (byte-id); the other 8 // ops were silently DROPPED (the arm fell through to a plain store of // the rhs → `s.f = rhs`, #34/#263). SLASHEQ/PERCENTEQ/LSHIFTEQ/RSHIFTEQ // need the lhs in AX and the divisor/count in CX, so swap (rhs AX→CX, // old BX→AX) first. Signed RSHIFTEQ uses SARQ, unsigned SHRQ (#136). // Mirrors cstage cg_dotfield_combine — both stages emit identical asm. fn cgdotfieldcombine(c: *cgen, op: syntax.tkind, unsignd: bool) void = { if (op == syntax.tkind.TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); return; }; if (op == syntax.tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, BX\n"); emitline("\tMOVQ\tBX, AX\n"); return; }; if (op == syntax.tkind.TK_STAREQ) { emitline("\tIMULQ\tBX, AX\n"); return; }; if (op == syntax.tkind.TK_AMPEQ) { emitline("\tANDQ\tBX, AX\n"); return; }; if (op == syntax.tkind.TK_PIPEEQ) { emitline("\tORQ\tBX, AX\n"); return; }; if (op == syntax.tkind.TK_CARETEQ) { emitline("\tXORQ\tBX, AX\n"); return; }; if (op == syntax.tkind.TK_SLASHEQ) { emitline("\tMOVQ\tAX, CX\n"); emitline("\tMOVQ\tBX, AX\n"); if (unsignd) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); } else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); }; return; }; if (op == syntax.tkind.TK_PERCENTEQ) { emitline("\tMOVQ\tAX, CX\n"); emitline("\tMOVQ\tBX, AX\n"); if (unsignd) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); } else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); }; emitline("\tMOVQ\tDX, AX\n"); return; }; if (op == syntax.tkind.TK_LSHIFTEQ) { emitline("\tMOVQ\tAX, CX\n"); emitline("\tMOVQ\tBX, AX\n"); emitline("\tSHLQ\tCX, AX\n"); return; }; if (op == syntax.tkind.TK_RSHIFTEQ) { emitline("\tMOVQ\tAX, CX\n"); emitline("\tMOVQ\tBX, AX\n"); if (unsignd) { emitline("\tSHRQ\tCX, AX\n"); } else { emitline("\tSARQ\tCX, AX\n"); }; return; }; let m: str = "single-dot field compound: unknown op (#34/rule-7)\n"; os.write(2, m.ptr, m.len: u64); os.exit(1); }; // cgdotfieldhardstop — loud-stop a single-dot field compound on a // non-integer field (float/str/slice/tagged): the combine arm only // speaks integer ABI; pre-#34 these silently became `s.f = rhs`. // Mirrors the cstage cg_dotfield_hardstop gate. (#34/rule-7) fn cgdotfieldhardstop(c: *cgen, ftn: *syntax.node) void = { if (istaggedtype(c, ftn)) { let m: str = "single-dot field compound on tagged field not wired (#34/rule-7)\n"; os.write(2, m.ptr, m.len: u64); os.exit(1); }; if (isstrtype(c, ftn)) { let m: str = "single-dot field compound on str field not wired (#34/rule-7)\n"; os.write(2, m.ptr, m.len: u64); os.exit(1); }; if (isslicetype(c, ftn)) { let m: str = "single-dot field compound on slice field not wired (#34/rule-7)\n"; os.write(2, m.ptr, m.len: u64); os.exit(1); }; if (isfloattype(c, ftn)) { let m: str = "single-dot field compound on float field not wired (#34/rule-7)\n"; os.write(2, m.ptr, m.len: u64); os.exit(1); }; }; // cgdotfieldhardstoptn — tinfo twin of cgdotfieldhardstop (#31): the // single-dot field compound combine speaks integer ABI only, so a // tagged/str/slice/float field compound loud-stops. Keyed off the // checker-STAMPED field tinfo (the receiver-layout tf walk), not a // tnode. Messages verbatim from cgdotfieldhardstop. (#34/rule-7) fn cgdotfieldhardstoptn(c: *cgen, ft: *syntax.tinfo) void = { if (syntax.typeistagged(ft)) { let m: str = "single-dot field compound on tagged field not wired (#34/rule-7)\n"; os.write(2, m.ptr, m.len: u64); os.exit(1); }; if (syntax.typeisstr(ft)) { let m: str = "single-dot field compound on str field not wired (#34/rule-7)\n"; os.write(2, m.ptr, m.len: u64); os.exit(1); }; if (syntax.typeisslice(ft)) { let m: str = "single-dot field compound on slice field not wired (#34/rule-7)\n"; os.write(2, m.ptr, m.len: u64); os.exit(1); }; if (syntax.typeisfloat(ft)) { let m: str = "single-dot field compound on float field not wired (#34/rule-7)\n"; os.write(2, m.ptr, m.len: u64); os.exit(1); }; }; fn cgtryunwcursor(c: *cgen, n: *syntax.node, opname: str) void = { let u: *syntax.tinfo = nil; if (n.lhs != nil) { u = n.lhs.type_: *syntax.tinfo; }; u = tichase(u); let utag: bool = false; if (u != nil) { if (u.kind == syntax.tykind.TY_TAGGED && u.nullable == 0) { utag = true; }; }; if (utag && u.size: i32 > TUPLE_GPCAP * 8 && n.lhs.kind != syntax.nkind.N_CALL) { let p37: str = "#37: `"; os.write(2, p37.ptr, p37.len: u64); os.write(2, opname.ptr, opname.len: u64); let m37t: str = "` on a >32B mem-based tagged read unwired (#40-family follow-up)\n"; os.write(2, m37t.ptr, m37t.len: u64); os.exit(1); }; if (utag && n.lhs.kind == syntax.nkind.N_IDENT) { let lc: *local = localfindnode(c, n.lhs.str); if (lc == nil) { let p35: str = "#35: `"; os.write(2, p35.ptr, p35.len: u64); os.write(2, opname.ptr, opname.len: u64); let m35g: str = "` on a global tagged ident unwired (rule 7)\n"; os.write(2, m35g.ptr, m35g.len: u64); os.exit(1); }; let boff: i32 = lc.off; let bsz: i32 = u.size: i32; if (bsz > 24) { emitline("\tMOVQ\t"); emitoff((boff + 24): i64); emitline("(BP), R8\n"); }; if (bsz > 16) { emitline("\tMOVQ\t"); emitoff((boff + 16): i64); emitline("(BP), CX\n"); }; if (bsz > 8) { emitline("\tMOVQ\t"); emitoff((boff + 8): i64); emitline("(BP), DX\n"); }; emitline("\tMOVQ\t"); emitoff(boff: i64); emitline("(BP), AX\n"); return; }; if (taggedmemread(c, n.lhs)) { let bsz2: i32 = 0; if (u != nil) { bsz2 = u.size: i32; }; cgexpr(c, n.lhs); if (bsz2 > 24) { emitline("\tMOVQ\t24(AX), R8\n"); }; if (bsz2 > 16) { emitline("\tMOVQ\t16(AX), CX\n"); }; if (bsz2 > 8) { emitline("\tMOVQ\t8(AX), DX\n"); }; emitline("\tMOVQ\t(AX), AX\n"); return; }; cgexpr(c, n.lhs); }; // cgtryprop — `e?` propagates the error variant up the stack. // Success tag is dynamic via successtag (#52/#216): the first non-error // variant's index (cstage cg_tagged_success_tag parity), 0 when success-first. fn cgtryprop(c: *cgen, n: *syntax.node) void = { // #38b residuals (rule 7): the cursor read below cannot see an // sret-classified call result (AX = dest pointer), and the // propagate-RET cannot speak an sret-classified enclosing return // (the caller reads memory, not the cursor). #40-family follow-ups. // Mirrors cstage cgen.c N_TRYPROP gates. if (n.lhs != nil) { if (n.lhs.kind == syntax.nkind.N_CALL) { if (callsretsize(c, n.lhs) > 0) { let m38p: str = "#38b: `?` on an sret-class call result unwired (mem-based unwrap is a #40-family follow-up)\n"; os.write(2, m38p.ptr, m38p.len: u64); os.exit(1); }; }; }; if (sretretsize(c, c.fnret) > 0) { let m38q: str = "#38b: `?` propagation into a >32B tagged return unwired (sret error-propagate is a #40-family follow-up)\n"; os.write(2, m38q.ptr, m38q.len: u64); os.exit(1); }; // Family C (#35/#46): ident/deref sources land the box in the // cursor here (was a silent word0 unwrap); call sources keep // the plain cgexpr emission byte-for-byte. cgtryunwcursor(c, n, "?"); // Nullable `(*T | void)`: AX IS the pointer, not a tag. Non-null // = success (any *T variant), null = error → propagate (RET with // AX=0). The pre-fix path compared AX against successtag and so // treated null as success (inverted). Mirrors cstage cgen.c // N_TRYPROP nullable arm; ww's own cgtypetest carries the twin // (cgenexpr.ww nullable fold). (task #15/F4) if (isnullabletype(n.lhs)) { let nl: str = mklabel(c, "tryprop_ok"); emitline("\tCMPQ\t$0, AX\n"); emitline("\tJNE\t"); emitline(nl); emitline("\n"); emitline("\tMOVQ\tBP, SP\n\tPOPQ\tBP\n\tRET\n"); emitlabel(nl); return; }; // AX = tag. If not the success tag, this is an error; pop frame and // RET. Success tag is dynamic (successtag / cstage cg_tagged_success_tag // parity, #52): 0 for success-first, the first non-error index for an // error-first union. let cl: str = mklabel(c, "tryprop_ok"); let propu: *syntax.tinfo = nil; if (n.lhs != nil) { propu = n.lhs.type_: *syntax.tinfo; }; emitline("\tCMPQ\t$"); emitint(successtag(propu)); emitline(", AX\n"); emitline("\tJE\t"); emitline(cl); emitline("\n"); // #173: remap the operand union's error-variant tag to the // enclosing fn return union's variant order before propagating. // When the `?` operand and the enclosing fn return differ in // variant order, the raw operand tag names the WRONG variant in // the return union. Mirrors cstage cmd/w6c/cgen.c:11296-11319. // Payload words DX/CX/R8 ride the RET untouched; only AX (the // tag) is rewritten. Same-order unions map every error variant to // itself → zero instructions, byte-id with the pre-#173 emit. let u: *syntax.tinfo = nil; if (n.lhs != nil) { u = n.lhs.type_: *syntax.tinfo; }; u = tichase(u); let r: *syntax.tinfo = nil; if (c.fnret != nil) { r = c.fnret.type_: *syntax.tinfo; }; r = tichase(r); if (u != nil && r != nil && r.kind == syntax.tykind.TY_TAGGED && u.params != nil) { let propret: str; propret.ptr = nil; propret.len = 0; let haveret: bool = false; let p: *syntax.tparam = u.params; let i: i32 = 0; for (p != nil) { if (variantiserror(u, i)) { let j: i32 = flatvariantidxt(r, p.type_, false); if (j < 0) { let mtp: str = "tryprop: error variant missing from fn return union (rule 7)\n"; os.write(2, mtp.ptr, mtp.len: u64); os.exit(1); }; if (j != i) { let skip: str = mklabel(c, "tryprop_skip"); emitline("\tCMPQ\t$"); emitint(i: i64); emitline(", AX\n"); emitline("\tJNE\t"); emitline(skip); emitline("\n"); emitline("\tMOVQ\t$"); emitint(j: i64); emitline(", AX\n"); if (!haveret) { propret = mklabel(c, "tryprop_ret"); haveret = true; }; emitline("\tJMP\t"); emitline(propret); emitline("\n"); emitlabel(skip); }; }; p = p.tnext; i += 1; }; if (haveret) { emitlabel(propret); }; }; emitline("\tMOVQ\tBP, SP\n\tPOPQ\tBP\n\tRET\n"); emitlabel(cl); // #241: a tuple success payload is an rvalue tuple — fill the cursor // (shift past the tag) so the destructure / let consumer reads every // element, not just word0. Success variant = successtag (#52). if (cgtrytupleshift(c, n)) { return; }; if (cgtrytaggedshift(c, n)) { return; }; // 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. // #16: read the STAMPED operand's success-variant type (any source // shape, any variant order), not a name-keyed call-only lookup of // the first variant. Mirrors cstage cgen.c:10459-10466 // (success_is_str = type_isstr(succ_t at cg_tagged_success_tag)). // #6 (Mech B): a slice success variant shares str's 24B {ptr,len,cap} // header in the tagged ABI {DX,CX,R8}; widen the gate so slice rides // the same shuffle. A struct/aggregate success uses a different // {AX,DX,CX} ABI (separate latent, task #12) — keep str||slice-specific. let succisstr: bool = false; if (n.lhs != nil) { let sv: *syntax.tinfo = successvariant(n.lhs.type_: *syntax.tinfo); succisstr = syntax.typeisstr(sv) || syntax.typeisslice(sv); }; if (succisstr) { // str IS []u8: success arrives DX=ptr, CX=len, R8=cap // (slot 32B). Move len out before cap overwrites CX // (#1/Phase 3). emitline("\tMOVQ\tCX, BX\n"); emitline("\tMOVQ\tR8, CX\n"); }; emitline("\tMOVQ\tDX, AX\n"); return; }; // cgtryunw — `e!` aborts on the error variant via exit(1). Success tag // is dynamic via successtag (#52): the first non-error variant's index // (cstage cg_tagged_success_tag parity), 0 when success-first. fn cgtryunw(c: *cgen, n: *syntax.node) void = { // #38b residual (rule 7): see the cgtryprop twin. if (n.lhs != nil) { if (n.lhs.kind == syntax.nkind.N_CALL) { if (callsretsize(c, n.lhs) > 0) { let m38u: str = "#38b: `!` on an sret-class call result unwired (mem-based unwrap is a #40-family follow-up)\n"; os.write(2, m38u.ptr, m38u.len: u64); os.exit(1); }; }; }; // Family C (#35/#46): see the cgtryprop twin. cgtryunwcursor(c, n, "!"); // Nullable `(*T | void)`: AX IS the pointer. Non-null = success // (leave AX as-is), null = error → abort exit(1). The pre-fix // path compared AX against successtag, treating null as success // (inverted). Mirrors cstage cgen.c N_TRYUNW nullable arm; ww's // own cgtypetest carries the twin. (task #15/F4) if (isnullabletype(n.lhs)) { let nl: str = mklabel(c, "tryunw_ok"); emitline("\tCMPQ\t$0, AX\n"); emitline("\tJNE\t"); emitline(nl); emitline("\n"); emitline("\tMOVQ\t$1, DI\n\tMOVQ\t$60, AX\n\tSYSCALL\n"); emitlabel(nl); return; }; let cl: str = mklabel(c, "tryunw_ok"); // Success tag is dynamic (successtag / cstage cg_tagged_success_tag // parity, #52): 0 for success-first, the first non-error index for an // error-first union. let unwu: *syntax.tinfo = nil; if (n.lhs != nil) { unwu = n.lhs.type_: *syntax.tinfo; }; emitline("\tCMPQ\t$"); emitint(successtag(unwu)); emitline(", AX\n"); emitline("\tJE\t"); emitline(cl); emitline("\n"); emitline("\tMOVQ\t$1, DI\n\tMOVQ\t$60, AX\n\tSYSCALL\n"); emitlabel(cl); // #241: tuple success payload fills the cursor (shift past the tag) — // same rvalue-tuple-into-cursor story as cgtryprop. if (cgtrytupleshift(c, n)) { return; }; if (cgtrytaggedshift(c, n)) { return; }; // Unwrap success value. (Same shuffle pattern as cgtryprop.) // #16: stamped success-variant type, any source/order (twin of // cgtryprop; cstage cgen.c:10595-10602). // #6 (Mech B): slice success shares str's 24B header in the tagged // ABI {DX,CX,R8} — widen the gate (twin of cgtryprop). Struct/aggregate // success uses a different {AX,DX,CX} ABI (task #12) — str||slice only. let succisstr: bool = false; if (n.lhs != nil) { let sv: *syntax.tinfo = successvariant(n.lhs.type_: *syntax.tinfo); succisstr = syntax.typeisstr(sv) || syntax.typeisslice(sv); }; if (succisstr) { // str IS []u8: success arrives DX=ptr, CX=len, R8=cap // (slot 32B). Move len out before cap overwrites CX // (#1/Phase 3). emitline("\tMOVQ\tCX, BX\n"); emitline("\tMOVQ\tR8, CX\n"); }; emitline("\tMOVQ\tDX, AX\n"); return; }; fn cgtypetest(c: *cgen, n: *syntax.node) void = { // 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. // Family C (#35): identity casts are transport no-ops — peel so // the ident emission carries; a WIDENING tagged cast renumbers // the tag the compare keys on and has no wired source arm — // loud below, not a mis-keyed test. Mirrors cstage N_TYPETEST. let lhs: *syntax.node = taggedidcastpeel(c, n.lhs); // #38b residual (rule 7): an sret-class call result leaves AX = // dest pointer, not the tag — mem-based test is a #40-family // follow-up. Mirrors cstage cgen.c N_TYPETEST gate. if (lhs != nil) { if (lhs.kind == syntax.nkind.N_CALL) { if (callsretsize(c, lhs) > 0) { let m38t: str = "#38b: `is` on an sret-class call result unwired (#40-family follow-up)\n"; os.write(2, m38t.ptr, m38t.len: u64); os.exit(1); }; }; }; let scrutoff: i32 = 0; let scrutt: *syntax.node = nil; let nonident: bool = false; let globalident: bool = false; if (lhs != nil) { if (lhs.kind == syntax.nkind.N_IDENT) { let lc: *local = localfindnode(c, lhs.str); if (lc != nil) { scrutoff = lc.off; scrutt = resolvetagged(c, lc.tnode); } else { // #18 is-half (align-UP): global tagged ident — tag word at // g(SB)+0, no BP slot. cstage N_TYPETEST is uniformly cgexpr // (MOVQ g(SB),AX); pre-fix the !nonident emit read 0(BP)=saved BP. let gt: *syntax.node = letvartnode(c, lhs.str); scrutt = resolvetagged(c, gt); globalident = true; }; } else { // #45: non-ident scrutinee (xs[i], p.field, call). // cstage N_TYPETEST never spills — cgexpr leaves the // scrutinee's tag word in AX (tagged element/field/ // call reads load the tag first), so compare AX // directly. The `as` twin's @asrt_spill (#200) is // NOT mirrored here: `as` re-reads payload words // after the check; `is` consumes only the tag, and // a spill would diverge from cstage's asm (rule 10). // Pre-#45 this fell through to scrutoff=0 and the // tag read landed on (BP) — the saved-BP word. nonident = true; // Family C catch-all (rule 7): a widening tagged // cast source — loud. Mirrors cstage. { let icu: *syntax.tinfo = lhs.type_: *syntax.tinfo; icu = tichase(icu); if (lhs.kind == syntax.nkind.N_CAST && icu != nil && icu.kind == syntax.tykind.TY_TAGGED && icu.nullable == 0) { let m35i: str = "#35: tagged cast source shape unwired at `is` (rule 7)\n"; os.write(2, m35i.ptr, m35i.len: u64); os.exit(1); }; }; cgexpr(c, lhs); // #37: a >32B box read leaves its ADDRESS in AX — // load the tag word from memory before the compare. // Mirrors cstage N_TYPETEST. if (taggedmemread(c, lhs)) { emitline("\tMOVQ\t(AX), AX\n"); }; }; }; let want: i32 = 0; let nullcarrier: *syntax.node = scrutt; if (nonident) { // Variant index from the STAMPED scrutinee type (cstage: // u = n->lhs->type) — matchscrutt's node-shape walk can't // carry N_DOT (returns the scrut node, which the // cgtagvariantidx N_TTAGGED gate rejects). flatvariantidx / // flatslicevariantidx read .type_ off any stamped carrier. nullcarrier = lhs; if (n.rhs != nil) { if (n.rhs.kind == syntax.nkind.N_TSLICE) { want = flatslicevariantidx(c, lhs, n.rhs.lhs); } else { want = flatvariantidx(c, lhs, n.rhs); }; }; } else { want = cgtagvariantidx(c, scrutt, n.rhs); }; if (!nonident) { if (globalident) { emitline("\tMOVQ\t"); emitsymname(c, lhs.str); emitline("(SB), AX\n"); } else { emitline("\tMOVQ\t"); emitoff(scrutoff: i64); emitline("(BP), AX\n"); }; }; let nel: str = mklabel(c, "is_ne"); let dnl: str = mklabel(c, "is_done"); if (isnullabletype(nullcarrier)) { // Nullable `(*T | void)` fold: the word in AX IS the // pointer — discriminate pointer-vs-null, not tag-vs-index // (cstage cgen.c N_TYPETEST nullable arm). `want` stays RAW // here: cstage tests tag == ptr_tag unclamped, so a // no-match (-1) takes the void polarity. emitline("\tCMPQ\t$0, AX\n"); if (want == nullableptrtag(nullcarrier)) { emitline("\tJE\t"); } else { emitline("\tJNE\t"); }; } else { if (want < 0) { let mtt: str = "tagged typetest: type not in union (rule 7)\n"; os.write(2, mtt.ptr, mtt.len: u64); os.exit(1); }; 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; }; fn cgtypeassert(c: *cgen, n: *syntax.node) void = { // `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. // Family C (#35): identity-cast peel — see the cgtypetest twin. let lhs: *syntax.node = taggedidcastpeel(c, n.lhs); // Enum ↔ integer: reinterpret-only — the value already occupies AX // (or AX:BX for str variants, irrelevant here); no tag/unwrap. Gate // on the stamped operand / result TYPE, not node shape: a constant- // folded enum member (`flag.NOESCAPE`) reaches cgen as an int-literal // node carrying the enum type_, which a node-shape probe missed → // spurious tagged-assertion + exit(1) (#27b). Mirrors cstage // cmd/w6c/cgen.c N_TYPEASSERT (type_chase_named on operand + result). { let su: *syntax.tinfo = nil; if (lhs != nil) { su = tichase(lhs.type_: *syntax.tinfo); }; let vu: *syntax.tinfo = tichase(n.type_: *syntax.tinfo); let lenum: bool = false; let renum: bool = false; if (su != nil) { if (su.kind == syntax.tykind.TY_ENUM) { lenum = true; }; }; if (vu != nil) { if (vu.kind == syntax.tykind.TY_ENUM) { renum = true; }; }; if (lenum || renum) { cgexpr(c, lhs); return; }; }; // #38b residual (rule 7): the spill below reads the cursor, which // an sret-class call result never fills. Mirrors cstage cgen.c // N_TYPEASSERT gate. if (lhs != nil) { if (lhs.kind == syntax.nkind.N_CALL) { if (callsretsize(c, lhs) > 0) { let m38a: str = "#38b: `as` on an sret-class call result unwired (#40-family follow-up)\n"; os.write(2, m38a.ptr, m38a.len: u64); os.exit(1); }; }; }; let scrutoff: i32 = 0; let scrutt: *syntax.node = nil; if (lhs != nil) { if (lhs.kind == syntax.nkind.N_IDENT) { let lc: *local = localfindnode(c, lhs.str); if (lc != nil) { scrutoff = lc.off; scrutt = resolvetagged(c, lc.tnode); } else { // #18 as-half (#263 ww-runtime-correct; cstage N_TYPEASSERT on a // global tagged ident spills uninitialized DX as the payload — // task #46). No BP slot: copy the box words from g(SB) into a // fresh @asrt_spill so the tag-check + payload load below index // off memory like a local. cs!=ww residual until #46 lands. let gt: *syntax.node = letvartnode(c, lhs.str); scrutt = resolvetagged(c, gt); let gsz: i32 = matchspillsz(c, scrutt); scrutoff = localalloc(c, "@asrt_spill", gsz, nil); emitline("\tLEAQ\t"); emitsymname(c, lhs.str); emitline("(SB), AX\n"); let gk: i32 = 0; for (gk < gsz) { emitline("\tMOVQ\t"); emitdispreg(gk: i64, "AX"); emitline(", DX\n"); emitline("\tMOVQ\tDX, "); emitoff((scrutoff + gk): i64); emitline("(BP)\n"); gk += 8; }; }; } else { // Non-ident scrutinee (call result, arr[i], p.field, ?, // etc.). Mirror cgmatch's spill (cgenexpr.ww:1422-1460) // and cstage cmd/w6c/cgen.c:6300-6316: alloc an // `@asrt_spill` slot sized via matchspillsz, evaluate // the LHS, then copy the AX/DX/CX[/R8] return-ABI words // into the slot so the tag-check + payload load indexes // off memory like the IDENT path. Without this, scrutoff // stayed 0 and the tag read fell on (BP) — the saved-BP // word — and the payload read on +8(BP) — the return // address. Bug #200. scrutt = matchscrutt(c, lhs); let spillsz: i32 = matchspillsz(c, scrutt); scrutoff = localalloc(c, "@asrt_spill", spillsz, nil); if (taggedmemread(c, lhs)) { // #37: >32B box read — ADDRESS in AX; copy the // whole box from memory. Mirrors cstage. cgexpr(c, lhs); let ak37: i32 = 0; for (ak37 < spillsz) { emitline("\tMOVQ\t"); emitdispreg(ak37: i64, "AX"); emitline(", DX\n"); emitline("\tMOVQ\tDX, "); emitoff((scrutoff + ak37): i64); emitline("(BP)\n"); ak37 += 8; }; } else { // #37 (rule 7): >32B from a non-mem-based kind would // spill an unfilled cursor. Mirrors cstage. if (!isnullabletype(scrutt) && spillsz > TUPLE_GPCAP * 8) { let m37s: str = "#37: `as` on a >32B tagged value from a non-mem-based source unwired (rule 7)\n"; os.write(2, m37s.ptr, m37s.len: u64); os.exit(1); }; // Family C catch-all (rule 7): a widening tagged // cast source — loud. Mirrors cstage. { let acu: *syntax.tinfo = lhs.type_: *syntax.tinfo; acu = tichase(acu); if (lhs.kind == syntax.nkind.N_CAST && acu != nil && acu.kind == syntax.tykind.TY_TAGGED && acu.nullable == 0) { let m35s: str = "#35: tagged cast source shape unwired at `as` (rule 7)\n"; os.write(2, m35s.ptr, m35s.len: u64); os.exit(1); }; }; cgexpr(c, lhs); emitline("\tMOVQ\tAX, "); emitoff(scrutoff: i64); emitline("(BP)\n"); if (!isnullabletype(scrutt)) { emitline("\tMOVQ\tDX, "); emitoff((scrutoff + 8): i64); emitline("(BP)\n"); // Mirror cstage cmd/w6c/cgen.c:6313-6315: only CX // → +16 when slot_size > 16. The 4-word case // (R8 → +24, slot_size > 24) is the cgmatch shape // (cgenexpr.ww:1454-1458, cmd/w6c/cgen.c:5988-5990) // but cstage cgtypeassert omits it; preserve the // asymmetry rather than diverge from rule 10 // byte-id. Filed inline as a cstage twin task. if (spillsz > 16) { emitline("\tMOVQ\tCX, "); emitoff((scrutoff + 16): i64); emitline("(BP)\n"); }; }; }; }; }; let want: i32 = cgtagvariantidx(c, scrutt, n.rhs); let okl: str = mklabel(c, "asrt_ok"); // Nullable `(*T | void)`: the slot word IS the pointer, not a tag. // The *T variant asserts non-null, the void variant asserts null; // AX keeps the pointer on the ok path (no slot+8 unwrap — the 8B // nullable slot has no second word). The pre-fix path compared the // POINTER against `want` (so a real pointer aborted, null passed) // and unwrapped a frame word past the slot. `want` stays RAW (no // clamp), mirroring cstage cgen.c N_TYPEASSERT nullable arm and // ww's own cgtypetest nullable fold (cgenexpr.ww). (task #17/F4) if (isnullabletype(scrutt)) { let ptrtag: i32 = nullableptrtag(scrutt); emitline("\tMOVQ\t"); emitoff(scrutoff: i64); emitline("(BP), AX\n"); emitline("\tCMPQ\t$0, AX\n"); if (want == ptrtag) { emitline("\tJNE\t"); } else { emitline("\tJE\t"); }; emitline(okl); emitline("\n\tMOVQ\t$1, DI\n\tMOVQ\t$60, AX\n\tSYSCALL\n"); emitlabel(okl); return; }; if (want < 0) { let mtt: str = "tagged typetest: type not in union (rule 7)\n"; os.write(2, mtt.ptr, mtt.len: u64); os.exit(1); }; 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: *syntax.node) void = { let srcfk: i32 = 0; if (n.lhs != nil) { let st: *syntax.tinfo = n.lhs.type_: *syntax.tinfo; if (syntax.typeisf32(st)) { srcfk = 1; } else { if (syntax.typeisfloat(st)) { srcfk = 2; }; }; }; 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); // str → []T: cgexpr left (AX=ptr, BX=len). Slice register // convention is (AX=ptr, BX=len, CX=cap); synthesise cap = len // so downstream arg-push / let-init paths see the canonical // triple. Type-keyed on the stamped src/dst types (dst-is-slice + // src-is-str), mirroring cstage cgen.c N_CAST (type_chase_named → // TY_SLICE/TY_STR). The prior local-ident-source gate (#19) missed // every non-local str source — global ident, struct field, call // result — leaving CX = the str's stale word-16 garbage cap. if (isslicetype(c, n.rhs)) { if (isstrtype(c, n.lhs)) { emitline("\tMOVQ\tBX, CX\n"); }; }; // 0=int, 1=f32, 2=f64. CVT picks one direction per combo; // int↔int casts narrow via an explicit clamp before the early // return so `(big_u64): u32` doesn't leak the upper 32 bits. // Hare semantics: `expr: T` truncates to T's bit width (mod 2^n). // Mirrors cmd/w6c/cgen.c's N_CAST clamp. Unsigned narrow clears // the upper bits via MOVL/ANDQ; signed narrow sign-extends via // MOVSBQ/MOVSWQ/MOVSXD reg-reg so the sign bit propagates. // // Identity-width identity-sign cast is a no-op at the machine- // int level: src and dst share both width and signedness, so the // natural slot/load already carries the right canonical 64-bit // shape. Skip the clamp in that case. Symmetric with cstage's // principled gate (#33). Replaces the previous N_TENUM lacuna in // this walker (the alias-step missed `N_TENUM`, so any cast to // an enum dst landed on tn==nil and skipped the clamp by // accident — task #25 mirrored that into cstage as a single-site // gate, and #33 retires both). The walker now follows N_TENUM // too so a narrow-to-enum cast (u32→enum-u8, i64→enum-i32) // resolves to the underlying primitive and the clamp fires — // fixing a silent miscompile in the process. if (srcfk == 0 && dstfk == 0) { let sz: i32 = 0; let is_unsigned: bool = false; typenodeprimresolved(c, n.rhs, &sz, &is_unsigned); let src_sz: i32 = 0; let src_unsigned: bool = false; exprprimresolved(c, n.lhs, &src_sz, &src_unsigned); let identity: bool = false; if (sz > 0) { if (src_sz == sz) { if (src_unsigned == is_unsigned) { identity = true; }; }; }; // Detect bool dst by walking n.rhs to the leaf TNAME. bool // keeps its dedicated ANDQ $255 contract regardless of // upstream shape; it stays off the identity path. let leaf_tn: *syntax.node = n.rhs; for (leaf_tn != nil) { let lk: syntax.nkind = leaf_tn.kind; if (lk == syntax.nkind.N_TBANG) { leaf_tn = leaf_tn.lhs; } else { if (lk == syntax.nkind.N_TENUM) { leaf_tn = leaf_tn.lhs; } else { if (lk == syntax.nkind.N_TNAME) { let lnm: str = leaf_tn.str; if (primsize(lnm) > 0) { break; }; let lal: *syntax.node = aliaslookup(c, lnm); if (lal == nil) { leaf_tn = nil; } else { leaf_tn = lal; }; } else { leaf_tn = nil; }; }; }; }; let is_bool: bool = false; if (leaf_tn != nil) { if (leaf_tn.kind == syntax.nkind.N_TNAME) { is_bool = syntax.streq(leaf_tn.str, "bool"); }; }; // Symmetric narrow on signed vs unsigned (task #5): // unsigned (incl. rune) clears upper bits; signed // sign-extends. bool is size 1 but neither — falls // through to its dedicated ANDQ $255 below. if (sz > 0) { if (sz < 8) { if (!is_bool) { if (!identity) { if (is_unsigned) { if (sz == 4) { emitline("\tMOVL\tAX, AX\n"); } else { let mask: i64 = 0xFFi64; if (sz == 2) { mask = 0xFFFFi64; }; emitline("\tANDQ\t$"); emitint(mask); emitline(", AX\n"); }; } else { if (sz == 1) { emitline("\tMOVSBQ\tAX, AX\n"); } else { if (sz == 2) { emitline("\tMOVSWQ\tAX, AX\n"); } else { if (sz == 4) { emitline("\tMOVSXD\tAX, AX\n"); }; }; }; }; }; }; }; }; if (is_bool) { emitline("\tANDQ\t$255, AX\n"); }; 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: *syntax.node) void = { // str IS []u8: the (ptr, len, cap) triple — ptr in AX, len in BX, // cap in CX. A static literal has no spare storage, so cap = len // (#1/Phase 3). Call sites that expect a str arg pick these up. let nstr: str = n.str; let lab: str = internstrlit(c, nstr); emitline("\tLEAQ\t"); emitbytes( lab.ptr, lab.len: u64); emitline("(SB), AX\n"); emitline("\tMOVQ\t$"); emitint(nstr.len: i64); emitline(", BX\n"); emitline("\tMOVQ\t$"); emitint(nstr.len: i64); emitline(", CX\n"); return; }; fn cgident(c: *cgen, n: *syntax.node) void = { let nm: str = n.str; let lc: *local = localfindnode(c, nm); if (lc != nil) { let off: i32 = lc.off; // #241: a tuple ident is a value — leave the whole tuple in the // register cursor (`yield t` / `return t` / `let q = t`), not // just word0 in AX. Mirror of cstage cgexpr N_IDENT tuple arm. let itu: *syntax.tinfo = nil; if (lc.tnode != nil) { itu = lc.tnode.type_: *syntax.tinfo; }; itu = tichase(itu); if (itu != nil) { if (itu.kind == syntax.tykind.TY_TUPLE) { cgtupleslottocursor(c, off, itu); return; }; }; // 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; }; // str / slice locals load (ptr[, len[, cap]]) through MOVQ // since the header is always 8B-clean. Scalar locals route // through localloadop so signed-narrow slots sign-extend // after a narrow deref-store. let isstr: bool = isstrtype(c, lc.tnode); let issl: bool = isslicetype(c, lc.tnode); let lop: str = "MOVQ"; if (!isstr) { if (!issl) { lop = localloadop(c, lc.tnode); }; }; emitline("\t"); emitline(lop); emitline("\t"); emitoff(off: i64); emitline("(BP), AX\n"); if (isstr) { // str IS []u8: load (ptr,len,cap) into AX/BX/CX, // identical to the slice arm below (#1/Phase 3). emitline("\tMOVQ\t"); emitoff((off + 8): i64); emitline("(BP), BX\n"); emitline("\tMOVQ\t"); emitoff((off + 16): i64); emitline("(BP), CX\n"); }; if (issl) { emitline("\tMOVQ\t"); emitoff((off + 8): i64); emitline("(BP), BX\n"); emitline("\tMOVQ\t"); emitoff((off + 16): i64); emitline("(BP), CX\n"); }; return; }; // Fn-name used as a value (e.g. `let f = some_fn;`) — LEAQ the // symbol address into AX. Classified by the checker-STAMPED // TY_FN type, never by a name lookup: the unit-wide fnrets // table matches a foreign same-leaf fn (main.v shadowing aa's // i32 global while cgen'ing aa.getv — the #55 cgen-side // sibling), where the stamp already resolved via // scopelookupprefer. Arm order TY_FN → def → let mirrors // cstage cgexpr N_IDENT (cmd/w6c/cgen.c:4315-4374); same // predicate-to-stamp conversion as the #14 F7-c7 `&fn` arm // (cgen.ww nodefnptr). emitfnname still handles ffiresolve + // module-mangling, so a body-less FFI binding emits its // @symbol() name. let fu: *syntax.tinfo = tichase(n.type_: *syntax.tinfo); if (fu != nil && fu.kind == syntax.tykind.TY_FN) { emitline("\tLEAQ\t"); emitfnname(c, nm, c.curmod); emitline("(SB), AX\n"); return; }; // Top-level `def` constant — load from its DATA symbol. // Str defs (rhs N_STRLIT) aren't laid out at a SB symbol; the // MOVQ symname(SB) fallback below would emit a bogus reference // (e.g. `alpha.MSG(SB)`, never DATAW-defined). Strlit-inline // the (LEAQ ptr, MOVQ $len) pair instead, mirroring cstage // Sdef walk #1 N_IDENT bare-load (cmd/w6c/cgen.c). Filed #12. if (deflookup(c, nm)) { let drhs: *syntax.node = deflookuprhs(c, nm); if (drhs != nil) { if (drhs.kind == syntax.nkind.N_STRLIT) { let bytes: str = drhs.str; let lab: str = internstrlit(c, bytes); emitline("\tLEAQ\t"); emitbytes( lab.ptr, lab.len: u64); emitline("(SB), AX\n"); emitline("\tMOVQ\t$"); emitint(bytes.len: i64); emitline(", BX\n"); // str IS []u8: cap = len for a static def literal // (#1/Phase 3). emitline("\tMOVQ\t$"); emitint(bytes.len: i64); emitline(", CX\n"); return; }; }; // Float def: load via LEAQ + MOVSS/MOVSD into X0, same shape // as the let-float arm below — MOVSS/MOVSD have no D_EXTERN // operand form. Pre-#129 fell through to the MOVQ-AX // integer-convention fallback, leaving X0 untouched (#129 // LOAD-side twin of the emitfloatlitdata DATA-side SSoT). if (isfloattype(c, n)) { let mov: str = "MOVSD"; if (isf32type(c, n)) { mov = "MOVSS"; }; emitline("\tLEAQ\t"); emitfnname(c, nm, c.curmod); emitline("(SB), CX\n"); emitline("\t"); emitline(mov); emitline("\t(CX), X0\n"); return; }; emitline("\tMOVQ\t"); emitfnname(c, nm, c.curmod); 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)) { // C-t3 (#48, rule 7): a GLOBAL tuple as a first-class VALUE // (`let q = g;` / `return g;` / `f(g)`) has no slot-to-cursor // path (cgtupleslottocursor is BP-relative) — pre-fix it fell // to the scalar MOVQ below, loading word0 only, and the // receive read a STALE cursor for words 1+. Element reads // (g.N) are the supported surface. Mirrors the cstage cgexpr // non-local ident guard. let gtt: *syntax.node = letvartnode(c, nm); for (gtt != nil && gtt.kind == syntax.nkind.N_TNAME) { gtt = aliaslookup(c, gtt.str); }; if (gtt != nil) { if (gtt.kind == syntax.nkind.N_TTUPLE) { let mgt: str = "#48: global tuple as a first-class value unwired (element reads only; rule 7)\n"; os.write(2, mgt.ptr, mgt.len: u64); os.exit(1); }; }; let isstr: bool = letvarisstr(c, nm); let issl: bool = letvarisslice(c, nm); if (isstr || issl) { // str IS []u8: both str and slice carry a third 8B // (cap); load it unconditionally. The address holder CX // is overwritten by the cap as the last step, after // ptr/len are already loaded (#1/Phase 3). emitline("\tLEAQ\t"); emitfnname(c, nm, c.curmod); emitline("(SB), CX\n"); emitline("\tMOVQ\t(CX), AX\n"); emitline("\tMOVQ\t8(CX), BX\n"); emitline("\tMOVQ\t16(CX), CX\n"); return; }; // Float global: same LEAQ-indirect shape, since MOVSS/ // MOVSD have no D_EXTERN operand form in w6a. Signed-narrow // scalar globals route through the same LEAQ scratch since // MOVSXD/MOVSWQ/MOVSBQ also have no D_EXTERN form. let lvtnode: *syntax.node = nil; let lv: *letvar = c.lets; for (lv != nil) { if (syntax.streq(lv.name, nm)) { // #135: an inferred-float global's tnode is the // defaultinferredlets-renamed "f64"/"f32" N_TNAME whose // .type_ is unstamped (cgen can't build tinfo), so the // isfloattype stamp-read misses it. Fall back to the // name keyword (the letfloatprim SSoT letemitsize uses) // so the float load fires for inferred as for explicit. let isf: bool = isfloattype(c, lv.tnode); let is32: bool = isf32type(c, lv.tnode); if (!isf && lv.tnode != nil && lv.tnode.kind == syntax.nkind.N_TNAME) { let fsz: i32 = letfloatprim(lv.tnode.str); if (fsz > 0) { isf = true; }; if (fsz == 4) { is32 = true; }; }; if (isf) { let mov: str = "MOVSD"; if (is32) { mov = "MOVSS"; }; emitline("\tLEAQ\t"); emitfnname(c, nm, c.curmod); emitline("(SB), CX\n"); emitline("\t"); emitline(mov); emitline("\t(CX), X0\n"); return; }; lvtnode = lv.tnode; lv = nil; } else { lv = lv.lvnext; }; }; let glop: str = localloadop(c, lvtnode); if (syntax.streq(glop, "MOVQ")) { emitline("\tMOVQ\t"); emitfnname(c, nm, c.curmod); emitline("(SB), AX\n"); } else { emitline("\tLEAQ\t"); emitfnname(c, nm, c.curmod); emitline("(SB), CX\n"); emitline("\t"); emitline(glop); emitline("\t(CX), AX\n"); }; return; }; // #140: a `!void` error-singleton spelled as a VALUE (`return // toolong;`) is tag-only — the void payload has size 0 and no // storage, so it is neither a local, def, nor let. Emit NOTHING; // the enclosing widen arm stamps the variant tag. Mirrors cstage // cgen.c N_IDENT #140 guard. Every other unresolvable ident is // checker-rejected upstream — loud, never the old silent return. { let vu: *syntax.tinfo = tichase(n.type_: *syntax.tinfo); if (vu != nil && vu.kind == syntax.tykind.TY_VOID) { return; }; }; let mur: str = "cgident: unresolvable identifier (rule 7)\n"; os.write(2, mur.ptr, mur.len: u64); os.exit(1); }; // cgslicehdr — load the 24B slice/str header at base+0 into the // (AX=ptr, BX=len, CX=cap) triple. base holds the element address; // the load that targets base destroys it, so that word is emitted // LAST. Order otherwise mirrors the slice-field arm (len, cap, ptr). // Shared by the cgindex str-element arms (caller does the kind-gate) // and, later, the typeassert str-variant leaf (#9). c retained unused // for callsite symmetry with cstage cgslicehdr. fn cgslicehdr(c: *cgen, base: str) void = { if (!syntax.streq(base, "BX")) { emitmovqload(8i64, base, "BX"); }; if (!syntax.streq(base, "CX")) { emitmovqload(16i64, base, "CX"); }; if (!syntax.streq(base, "AX")) { emitmovqload(0i64, base, "AX"); }; if (syntax.streq(base, "BX")) { emitmovqload(8i64, base, "BX"); }; if (syntax.streq(base, "CX")) { emitmovqload(16i64, base, "CX"); }; if (syntax.streq(base, "AX")) { emitmovqload(0i64, base, "AX"); }; }; // dotchainaddr — emit the ADDRESS of a dot/ident lvalue chain into // `dstreg`, dereferencing pointer links mid-chain. Returns true on // success, false if a link isn't a struct / ptr-to-struct it can // resolve. Recursion mirrors the cstage read spine: for `x.f`, recurse // to &x, deref if x is a *struct (so dstreg holds the pointee base), // then add f's offset. Touches ONLY dstreg (no AX, no stack) — same // spill contract as dotbaseaddr. The chained-base arm of dotbaseaddr // (#253) is its sole caller. Cstage twin: cmd/w6c/cgen.c // `cg_dotchain_addr`. fn dotchainaddr(c: *cgen, n: *syntax.node, dstreg: str) bool = { if (n == nil) { return false; }; if (n.kind == syntax.nkind.N_IDENT) { let lc: *local = localfindnode(c, n.str); if (lc != nil) { emitline("\tLEAQ\t"); emitoff(lc.off: i64); emitline("(BP), "); emitline(dstreg); emitline("\n"); return true; }; // #256: carry cstage cg_dotchain_addr's `let_islet || // def_isstructdef` guard (never-silent ethos). Unreachable on // valid input — a struct-typed chain root is always local, a // let-global, or a struct def — so this adds zero divergent // asm; it just refuses to LEAQ name(SB) for a name that names // neither. deflookup is the broader def-registry twin (ww has no // struct-specific def predicate; harmless given unreachability). if (isletvar(c, n.str) || deflookup(c, n.str)) { emitline("\tLEAQ\t"); emitsymname(c, n.str); emitline("(SB), "); emitline(dstreg); emitline("\n"); return true; }; return false; }; if (n.kind != syntax.nkind.N_DOT) { return false; }; let x: *syntax.node = n.lhs; if (x == nil) { return false; }; let xu: *syntax.tinfo = x.type_: *syntax.tinfo; xu = tichase(xu); if (xu == nil) { return false; }; let xviaptr: bool = false; let st: *syntax.tinfo = nil; if (xu.kind == syntax.tykind.TY_PTR) { let p: *syntax.tinfo = xu.sub; p = tichase(p); if (p != nil) { if (p.kind == syntax.tykind.TY_STRUCT) { st = p; xviaptr = true; }; }; } else { if (xu.kind == syntax.tykind.TY_STRUCT) { st = xu; }; }; if (st == nil) { return false; }; let f: *syntax.tfield = st.fields; let foff: i64 = -1; for (f != nil) { if (syntax.streq(f.name, n.str)) { foff = f.offset: i64; break; }; f = f.tnext; }; if (foff < 0) { return false; }; if (!dotchainaddr(c, x, dstreg)) { return false; }; if (xviaptr) { emitline("\tMOVQ\t("); emitline(dstreg); emitline("), "); emitline(dstreg); emitline("\n"); }; if (foff != 0) { emitline("\tADDQ\t$"); emitint(foff); emitline(", "); emitline(dstreg); emitline("\n"); }; return true; }; // dotbaseaddr — emit `&(inner.field)` into `dstreg` when `base` is an // N_DOT with N_IDENT inner OR a chained N_DOT inner (#253: `o.p.m` / // `o.i.m` / `o.a.b.m`). Returns true if emitted; callers fall back // to `cgexpr(c, base); MOVQ AX, dstreg` on false. Cstage twin: // cmd/w6c/cgen.c `cg_dotbase_addr`. // // #135: cgexpr on an N_DOT whose .field is a `[N]T`-typed field auto- // derefs + loads the field's 8-byte VALUE as if it were a pointer. For // an LHS or index-base shape (`d.fld[i] = v` / `d.fld[i]` read / `d.fld // [i] OP= v`), the caller wants the field's ADDRESS — this helper // supplies it inline. Reusable primitive of the inverse template // `arr[i].field = v` (cstage cgen.c arr[i].field address-eval). // // #253: a chained inner (`inner` is itself an N_DOT) routes through // dotchainaddr to recover the container base — the pointer VALUE of // inner when inner is a *struct (viaptr), else the ADDRESS of inner — // then adds the field offset. Closes the array-field-base-address // family across every op (index r/w, addr-of, slice, compound). fn dotbaseaddr(c: *cgen, base: *syntax.node, dstreg: str) bool = { if (base == nil) { return false; }; if (base.kind != syntax.nkind.N_DOT) { return false; }; let inner: *syntax.node = base.lhs; if (inner == nil) { return false; }; let chained: bool = (inner.kind == syntax.nkind.N_DOT); if (inner.kind != syntax.nkind.N_IDENT && !chained) { return false; }; // #128b: module-qualified `mod.arr` where arr is an imported // top-level `let X: [N]T`. The checker leaves SK_USE module- // idents without a localfindnode entry; detect via letvartnode // resolving to N_TARRAY and emit LEAQ X(SB). Without this, the // cgindex fallback's cgexpr(base) auto-MOVQs the symbol's first // 8 bytes as if it were a pointer-var — wrong shape (cstage // sister fix in cg_dotbase_addr). N_IDENT-inner only — a chained // inner has a valid stamped type_ and routes through dotchainaddr. let lc: *local = nil; let isglobal: bool = false; if (!chained) { lc = localfindnode(c, inner.str); if (lc == nil) { // #128b is for a module-QUALIFIER inner (mod.arr): inner has no // usable struct type. cstage gates it on inner->type==NULL||ty_err // (cgen.c:2109). Without the gate a typed-struct global inner whose // FIELD shares a name with an unrelated global array hijacks it // (gs.fld -> LEAQ fld(SB)) — #20. A typed inner falls to #249 below. let ibu: *syntax.tinfo = tichase(inner.type_: *syntax.tinfo); if (ibu == nil || ibu.kind == syntax.tykind.TY_ERR) { let gt: *syntax.node = letvartnode(c, base.str); if (gt != nil && gt.kind == syntax.nkind.N_TARRAY) { emitline("\tLEAQ\t"); emitsymname(c, base.str); emitline("(SB), "); emitline(dstreg); emitline("\n"); return true; }; }; // #249 (sibling of #135): inner is a module-GLOBAL struct value // (let/def), not a local — lc is nil but inner.type_ is a valid // struct. Resolve the field below and emit a global base (LEAQ // name(SB)). A non-struct inner (e.g. an SK_USE module qualifier, // type ty_err) falls through the struct gate to `return false`. isglobal = true; }; }; let bu: *syntax.tinfo = inner.type_: *syntax.tinfo; bu = tichase(bu); if (bu == nil) { return false; }; let viaptr: bool = false; let structt: *syntax.tinfo = nil; if (bu.kind == syntax.tykind.TY_PTR) { let st: *syntax.tinfo = bu.sub; st = tichase(st); if (st != nil) { if (st.kind == syntax.tykind.TY_STRUCT) { structt = st; viaptr = true; }; }; } else { if (bu.kind == syntax.tykind.TY_STRUCT) { structt = bu; }; }; if (structt == nil) { return false; }; let f: *syntax.tfield = structt.fields; let foff: i64 = -1; let ft: *syntax.tinfo = nil; for (f != nil) { if (syntax.streq(f.name, base.str)) { foff = f.offset: i64; ft = f.type_; break; }; f = f.tnext; }; if (foff < 0) { return false; }; // Only fire on `[N]T` fields — for `*T` / `[]T` / `str` fields // the existing cgexpr(base) path correctly loads the pointer/ // header value; over-firing here would skip the deref. Cstage // twin gate at cg_dotbase_addr. ft = tichase(ft); if (ft == nil) { return false; }; if (ft.kind != syntax.tykind.TY_ARRAY) { return false; }; // #253: chained inner — compute the container base via the dot-chain // spine (pointer VALUE of inner when viaptr, else its ADDRESS), then // add the field offset. dotchainaddr keeps the spill contract. if (chained) { if (!dotchainaddr(c, inner, dstreg)) { return false; }; if (viaptr) { emitline("\tMOVQ\t("); emitline(dstreg); emitline("), "); emitline(dstreg); emitline("\n"); }; if (foff != 0) { emitline("\tADDQ\t$"); emitint(foff); emitline(", "); emitline(dstreg); emitline("\n"); }; return true; }; let innoff: i64 = 0; if (lc != nil) { innoff = lc.off: i64; }; if (viaptr) { emitline("\tMOVQ\t"); emitoff(innoff); emitline("(BP), "); emitline(dstreg); emitline("\n"); if (foff != 0) { emitline("\tADDQ\t$"); emitint(foff); emitline(", "); emitline(dstreg); emitline("\n"); }; } else if (isglobal) { // #249: LEAQ name(SB) + field offset. Mirror cstage // cg_dotbase_addr's global value-struct arm. emitline("\tLEAQ\t"); emitsymname(c, inner.str); emitline("(SB), "); emitline(dstreg); emitline("\n"); if (foff != 0) { emitline("\tADDQ\t$"); emitint(foff); emitline(", "); emitline(dstreg); emitline("\n"); }; } else { emitline("\tLEAQ\t"); emitoff(innoff + foff); emitline("(BP), "); emitline(dstreg); emitline("\n"); }; return true; }; // aggargsrcaddr — land the ADDRESS of an addressable aggregate (struct/ // array) call-arg source in dstreg (#271, mirror of cstage // aggarg_srcaddr). Reuses the closed #265/#268 let-init-copy dispatch: // local ident slot (LEAQ off(BP)), module-let global (LEAQ name(SB)), // deref operand (cgexpr of the pointer), N_DOT field (dotchainaddr, // #253), N_INDEX element of an N_IDENT array base (the &base[i] spine, // #252/#270). Returns false for an uncovered source kind (caller loud- // stops, rule 7). The CALL source is handled at the push site. fn aggargsrcaddr(c: *cgen, src: *syntax.node, dst: str) bool = { if (src.kind == syntax.nkind.N_UN) { if (src.op == syntax.tkind.TK_STAR) { cgexpr(c, src.lhs); if (!syntax.streq(dst, "AX")) { emitline("\tMOVQ\tAX, "); emitline(dst); emitline("\n"); }; return true; }; }; if (src.kind == syntax.nkind.N_IDENT) { let lc: *local = localfindnode(c, src.str); if (lc != nil) { emitline("\tLEAQ\t"); emitoff(lc.off: i64); emitline("(BP), "); emitline(dst); emitline("\n"); return true; }; // global value source. Gated to a module-`let` (letvartnode, // the cstage let_islet twin); a const array/struct `def` // aggregate ARG is untested + out of scope (#274; both stages // loud-stop, rule-10 aligned). if (letvartnode(c, src.str) != nil) { emitline("\tLEAQ\t"); emitsymname(c, src.str); emitline("(SB), "); emitline(dst); emitline("\n"); return true; }; return false; }; if (src.kind == syntax.nkind.N_DOT) { if (dotchainaddr(c, src, dst)) { return true; }; // task #6: an N_INDEX link inside the dot chain // (handles[k].result) — the chain walker has no index hop // (AX-clean spill contract for dotbaseaddr); the place // resolver is the C4/#40 fallback, and the failed walk is // emission-free so the fallback starts clean. return cgplaceaddr(c, src, dst); }; if (src.kind == syntax.nkind.N_INDEX) { // task #6 sibling: slice-element / non-ident-array base // (use(sl[k])) — the same C4/#40 place-resolver fallback // as the N_DOT arm; the missed walk is emission-free. let base: *syntax.node = src.lhs; let idx: *syntax.node = src.rhs; if (base == nil) { return cgplaceaddr(c, src, dst); }; if (base.kind != syntax.nkind.N_IDENT) { return cgplaceaddr(c, src, dst); }; let bu: *syntax.tinfo = base.type_: *syntax.tinfo; bu = tichase(bu); if (bu == nil) { return cgplaceaddr(c, src, dst); }; if (bu.kind != syntax.tykind.TY_ARRAY) { return cgplaceaddr(c, src, dst); }; let esz: i32 = 1; if (bu.sub != nil) { esz = bu.sub.size: i32; }; cgexpr(c, idx); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", CX\n"); emitline("\tIMULQ\tCX, AX\n"); }; let boff: *local = localfindnode(c, base.str); if (boff != nil) { emitline("\tLEAQ\t"); emitoff(boff.off: i64); emitline("(BP), BX\n"); } else { emitline("\tLEAQ\t"); emitsymname(c, base.str); emitline("(SB), BX\n"); }; emitline("\tADDQ\tBX, AX\n"); if (!syntax.streq(dst, "AX")) { emitline("\tMOVQ\tAX, "); emitline(dst); emitline("\n"); }; return true; }; return false; }; // aggcopy — the ONE place-resolved mem-to-mem aggregate copy: sz // bytes (SI) → (BX) via AX, a MOVQ run plus a 4/2/1 sized tail. // Extracted verbatim from the C1.25 assign-resolver tail so every // aggregate copy position (resolver field store, #49 ident reassign, // #49 structlit fill-field) funnels through one loop — close-by- // construction, no per-site width logic to skew. Mirror of cstage // cg_aggcopy. fn aggcopy(c: *cgen, sz: i32) void = { let k: i32 = 0; for (k + 8 <= sz) { emitline("\tMOVQ\t"); emitoff(k: i64); emitline("(SI), AX\n"); emitline("\tMOVQ\tAX, "); emitoff(k: i64); emitline("(BX)\n"); k += 8; }; if (k + 4 <= sz) { emitline("\tMOVL\t"); emitoff(k: i64); emitline("(SI), AX\n"); emitline("\tMOVL\tAX, "); emitoff(k: i64); emitline("(BX)\n"); k += 4; }; if (k + 2 <= sz) { emitline("\tMOVW\t"); emitoff(k: i64); emitline("(SI), AX\n"); emitline("\tMOVW\tAX, "); emitoff(k: i64); emitline("(BX)\n"); k += 2; }; if (k + 1 <= sz) { emitline("\tMOVB\t"); emitoff(k: i64); emitline("(SI), AX\n"); emitline("\tMOVB\tAX, "); emitoff(k: i64); emitline("(BX)\n"); k += 1; }; }; // cgaggregstore — ww twin of cstage cgen.c cg_agg_reg_store. The ONE // register-cursor aggregate materialise: an in-cap (<=24B, GP-class) // struct/array/tuple already held in the {AX,DX,CX} return cursor is // stored into basereg+disp. full=sz/8 exact-8B MOVQ words land straight // in; the sz%8 tail is one sized MOVB/MOVW/MOVL for {1,2,4} (BOTH // branches). Extracted from the cstage site-E #10 template so every // narrow-tail materialise funnels through one place — close-by- // construction (task #14). // // dest_padded forks ONLY the {3,5,6,7} tail (no GP sub-register exists // for those widths and w6a has no shift): // dest_padded == "the dest is a ceil-8/round8 slot (a scratch, or a // #75 let/local aggregate slot) so an 8B tail over-store stays // in-bounds." Post-#9: packed struct fields + array elements => false; // let/local/scratch slots => true. It CANNOT be derived — paddedness // is routing knowledge the caller owns. // true -> a single full MOVQ of the cursor tail eightbyte (= the // #10 template; the over-store lands in the slot's pad). // false -> MOVQ the cursor tail eightbyte into the helper's own // ceil-8 @tagscr pad, then a sized aggcopy of the tail bytes // into the dest so a packed field / array element is never // overrun. // PRECONDITION (caller-owned, stays per-site): the value is already in // AX/DX/CX, the dest base is resolved into basereg+disp, and the // float-class (#165) / over-cap-sret (#234) loud-stops have already // fired. Clobbers SI/BX/AX only on the dest_padded==false detour (the // value words are in memory by then). fn cgaggregstore(c: *cgen, basereg: str, disp: i32, sz: i32, dest_padded: bool) void = { let full: i32 = sz / 8; let tail: i32 = sz - full * 8; let i: i32 = 0; for (i < full) { let reg: str = "AX"; if (i == 1) { reg = "DX"; }; if (i == 2) { reg = "CX"; }; emitline("\tMOVQ\t"); emitline(reg); emitline(", "); emitdispreg((disp + i * 8): i64, basereg); emitline("\n"); i += 1; }; if (tail == 0) { return; }; let treg: str = "AX"; if (full == 1) { treg = "DX"; }; if (full == 2) { treg = "CX"; }; if (tail == 1 || tail == 2 || tail == 4) { let top: str = "MOVB"; if (tail == 4) { top = "MOVL"; }; if (tail == 2) { top = "MOVW"; }; emitline("\t"); emitline(top); emitline("\t"); emitline(treg); emitline(", "); emitdispreg((disp + full * 8): i64, basereg); emitline("\n"); return; }; if (dest_padded) { emitline("\tMOVQ\t"); emitline(treg); emitline(", "); emitdispreg((disp + full * 8): i64, basereg); emitline("\n"); return; }; let pad: i32 = tagscradd(c, 8); emitline("\tMOVQ\t"); emitline(treg); emitline(", "); emitdispreg(pad: i64, "BP"); emitline("\n"); emitline("\tLEAQ\t"); emitdispreg(pad: i64, "BP"); emitline(", SI\n"); emitline("\tLEAQ\t"); emitdispreg((disp + full * 8): i64, basereg); emitline(", BX\n"); aggcopy(c, tail); }; // copysrcnatsize — natural byte width of a whole-struct copy's SOURCE // operand, read from the source node's stamped tinfo (tichase peels // NAMED). #71: the four whole-struct field-copy sites must move this many // bytes, NOT structinfo.totsize (slot-padded, round-8) which over-copies // into slot padding and clobbers a natural-offset successor once #44 packs // it there. Reads the source NODE's type, never fi.foff/fi.fsz or // structinfo, so #71 stays independent of #44's registerstruct offset // change. Equals cstage's `str_fu->size` (cmd/w6c/cgen.c:5302 SSoT) — the // field struct's aligned r.size (check.ww N_TSTRUCT), distinct from the // existing structnaturalsize (which reads structinfo max(foff+fsz), a // #44-coupled source). The ragged-tail completeness shared by both stages' // field copies is a separate class, tracked under #73 / the aggcopy // emitter choke-point. fn copysrcnatsize(c: *cgen, src: *syntax.node) i32 = { if (src == nil || src.type_ == nil) { let msg: str = "#71: whole-struct copy source has no stamped tinfo\n"; os.write(2, msg.ptr, msg.len: u64); os.exit(1); }; let ti: *syntax.tinfo = tichase(src.type_: *syntax.tinfo); if (ti == nil) { let msg: str = "#71: whole-struct copy source tinfo chase yielded nil\n"; os.write(2, msg.ptr, msg.len: u64); os.exit(1); }; return ti.size: i32; }; // cgplaceaddr — compute the ADDRESS of an arbitrary place (lvalue) // expression into dstreg; returns true when the shape is wired, false // otherwise (the caller loud-stops — rule 7, never a silent drop). // F6 resolver, commit C1 — mirror of cstage cmd/w6c/cgen.c // cgplaceaddr: `(*p)[i].f` as N_UN(STAR) root, N_INDEX hop over a // slice/array place, N_DOT struct-field hop with one deref for a // *struct base. C2 (F4 read-walker) adds the N_IDENT root (local / // let / DATA-backed def) so indexed-ident spines resolve too. All // type keys come off the checker-STAMPED tinfo (.type_), never tnode // names — the #209/#211 discipline. Enumerated arms still win at // every dispatch site (they are checked first), so shapes that worked // pre-C1 keep their asm. ADDRESS COMPUTATION ONLY — every call-site // keeps its own load/store/copy emission. Clobbers AX/CX (cgexpr on // index / pointer operands) and balances its own PUSHQ/POPQ; dstreg // must not be AX or CX. fn cgplaceaddr(c: *cgen, n: *syntax.node, dstreg: str) bool = { if (n == nil) { return false; }; if (n.kind == syntax.nkind.N_IDENT) { let lc: *local = localfindnode(c, n.str); if (lc != nil) { emitline("\tLEAQ\t"); emitoff(lc.off: i64); emitline("(BP), "); emitline(dstreg); emitline("\n"); return true; }; let gok: bool = isletvar(c, n.str); if (!gok) { if (defvarstructinfo(c, n.str) != nil) { gok = true; }; }; if (!gok) { let dtn: *syntax.node = defvartnode(c, n.str); if (dtn != nil) { if (dtn.kind == syntax.nkind.N_TARRAY) { gok = true; }; }; }; if (gok) { emitline("\tLEAQ\t"); emitsymname(c, n.str); emitline("(SB), "); emitline(dstreg); emitline("\n"); return true; }; return false; }; if (n.kind == syntax.nkind.N_UN) { if (n.op != syntax.tkind.TK_STAR) { return false; }; // &(*e) is e's value — no load. cgexpr(c, n.lhs); emitline("\tMOVQ\tAX, "); emitline(dstreg); emitline("\n"); return true; }; if (n.kind == syntax.nkind.N_INDEX) { let base: *syntax.node = n.lhs; let idx: *syntax.node = n.rhs; if (base == nil || idx == nil) { return false; }; // C2: any addressable base — recursion decides (deref / // ident / dot / index spine). Ident-rooted shapes with // enumerated arms never reach the resolver (those arms // dispatch first), so their asm is untouched. let bu: *syntax.tinfo = base.type_: *syntax.tinfo; bu = tichase(bu); if (bu == nil) { return false; }; // Auto-deref: a *[N]T indexed base takes one pointer hop — // the (*hs)[k] form made implicit (checker already stamps // the element type). Load shape is the slice .ptr word's. let viaptr: bool = false; if (bu.kind == syntax.tykind.TY_PTR) { let p: *syntax.tinfo = bu.sub; p = tichase(p); if (p != nil) { if (p.kind == syntax.tykind.TY_ARRAY) { viaptr = true; }; }; }; if (!viaptr && bu.kind != syntax.tykind.TY_SLICE && bu.kind != syntax.tykind.TY_ARRAY) { return false; }; let et: *syntax.tinfo = n.type_: *syntax.tinfo; et = tichase(et); if (et == nil) { return false; }; let esz: i32 = et.size: i32; cgexpr(c, idx); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", CX\n"); emitline("\tIMULQ\tCX, AX\n"); }; emitline("\tPUSHQ\tAX\n"); if (!cgplaceaddr(c, base, dstreg)) { return false; }; // A slice place holds the {ptr,len,cap} header — the // element base is its .ptr word; an array place IS the // element storage; a *[N]T place holds the array address. if (bu.kind == syntax.tykind.TY_SLICE || viaptr) { emitline("\tMOVQ\t("); emitline(dstreg); emitline("), "); emitline(dstreg); emitline("\n"); }; emitline("\tPOPQ\tAX\n"); emitline("\tADDQ\tAX, "); emitline(dstreg); emitline("\n"); return true; }; if (n.kind == syntax.nkind.N_DOT) { let base: *syntax.node = n.lhs; if (base == nil) { return false; }; let bu: *syntax.tinfo = base.type_: *syntax.tinfo; bu = tichase(bu); if (bu == nil) { return false; }; let viaptr: bool = false; let st: *syntax.tinfo = nil; if (bu.kind == syntax.tykind.TY_PTR) { let p: *syntax.tinfo = bu.sub; p = tichase(p); if (p != nil) { if (p.kind == syntax.tykind.TY_STRUCT) { st = p; viaptr = true; }; }; } else { if (bu.kind == syntax.tykind.TY_STRUCT) { st = bu; }; }; if (st == nil) { return false; }; let f: *syntax.tfield = st.fields; let foff: i64 = -1; for (f != nil) { if (syntax.streq(f.name, n.str)) { foff = f.offset: i64; break; }; f = f.tnext; }; if (foff < 0) { return false; }; if (!cgplaceaddr(c, base, dstreg)) { return false; }; if (viaptr) { emitline("\tMOVQ\t("); emitline(dstreg); emitline("), "); emitline(dstreg); emitline("\n"); }; if (foff != 0) { emitline("\tADDQ\t$"); emitint(foff); emitline(", "); emitline(dstreg); emitline("\n"); }; return true; }; return false; }; fn cgindex(c: *cgen, n: *syntax.node) void = { let base: *syntax.node = n.lhs; let idx: *syntax.node = n.rhs; // Direct non-ident index bases that match none of the typed arms // below (e.g. a cast-expression base) keep this 8B default — // cs!=ww for narrow elements. Team task #19 (#61-residual B). let esz: i32 = 8; let signed_elem: bool = false; // #119: float element loads route to MOVSS/MOVSD into X0, not the // integer loadopsz into AX. float_elem/f32_elem are set per-branch // from the SAME tinfo esz reads — never a fresh node-stamp (#121). let float_elem: bool = false; let f32_elem: bool = false; // #156 (PREREQ-1 read-half): element is itself an array ([N][M]T → // element [M]T) → leave the sub-array's ADDRESS in the result reg // instead of dereferencing; the outer index adds its offset and the // final scalar element dereferences. Sister of #135. Mirrors cstage // esubu->kind == TY_ARRAY. Node-based (elemisarrayc) for ident bases, // n.type_ tinfo-based for N_DOT/N_INDEX bases — same source split as // esz above. let elem_isarray: bool = false; // #1/Phase 3: str and slice are both 24B (and a >16B struct is // 24B+ too), so the header branches below MUST gate on KIND // (elemisstr/elemisslice, mirroring cstage's elem_is_str|| // elem_is_slice), not a bare `esz == primtypesize("str")` size // check — a size gate would route a plain >16B struct into the // 3-word {ptr,len,cap} load and diverge from cstage (#60 collision // class; sentinel 754). let elemisstr: bool = false; let elemisslice: bool = false; // #60 (alias arc #5): the base's declared type is an N_IDENT alias // (`type arr = [4]int`). The tnode walks below see only the N_TNAME // leaf — esz fell to the 1-sentinel and the base classified as a // POINTER (MOVQ + no IMULQ → SEGV / prefix-luck reads). Set once // here, consumed by the elem-fact override, the etn fallback and // the LEAQ-vs-MOVQ base classify. let basealias: 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 == syntax.nkind.N_IDENT) { let bn: str = base.str; baselocal = localfindnode(c, bn); if (baselocal != nil) { esz = elemsizeofc(c, baselocal.tnode); signed_elem = elemissignedc(c, baselocal.tnode); float_elem = elemisfloatc(c, baselocal.tnode); f32_elem = elemisf32c(c, baselocal.tnode); elem_isarray = elemisarrayc(c, baselocal.tnode); } else { let tn: *syntax.node = letvartnode(c, bn); // #129 A.3: array-typed defs now have DATA storage; // resolve their base via the same N_TARRAY path as // lets. defvartnode is the def-side sister of // letvartnode (parallel to defvarstructinfo at the // A.2 cgdot widening site). if (tn == nil) { tn = defvartnode(c, bn); }; if (tn != nil) { // #10: dispatch esz + base-materialization off the // global's RESOLVED type via elemsizeofc, NOT an // N_TARRAY/N_TPTR kind whitelist. A global str (tnode // N_TNAME "str") / slice (N_TSLICE) matched NEITHER old // arm, so esz stayed at the default 8 and the base fell // to the wide-header fallback below (8B stride + full- // word MOVQ) instead of loading the .ptr + an element- // width load. cstage dispatches uniformly off // idx_eff(lhs->type)->sub->size (cmd/w6c/cgen.c // N_INDEX); the sister fn cgslice (this file) already // resolves esz via elemsizeofc and the base via // N_TARRAY?LEAQ:MOVQ name(SB) with no kind gate. Align // cgindex UP to that template: any indexable global // resolves esz off the type table, N_TARRAY -> LEAQ (the // symbol IS the storage), every other -> MOVQ name(SB) // (the symbol's first word IS the .ptr). The existing // isglobalptr emission (the loadopsz path below) then // yields the cstage-identical MOVZBQ for a str byte // (esz=1). globalname = bn; esz = elemsizeofc(c, tn); signed_elem = elemissignedc(c, tn); float_elem = elemisfloatc(c, tn); f32_elem = elemisf32c(c, tn); elem_isarray = elemisarrayc(c, tn); if (tn.kind == syntax.nkind.N_TARRAY) { isglobalarr = true; } else { isglobalptr = true; }; }; }; // #60: element facts off the chased checker-stamped // tinfos — cstage reads them via type_chase_named/ // idx_eff uniformly (cmd/w6c/cgen.c N_INDEX). let bt60: *syntax.tinfo = base.type_: *syntax.tinfo; if (bt60 != nil) { if (bt60.kind == syntax.tykind.TY_NAMED) { basealias = true; }; }; if (basealias) { let et60: *syntax.tinfo = tichase(n.type_: *syntax.tinfo); if (et60 != nil) { esz = et60.size: i32; signed_elem = syntax.typeissigned(et60); float_elem = syntax.typeisfloat(et60); f32_elem = syntax.typeisf32(et60); }; // global base: array-vs-pointer re-keyed off the // chased BASE kind (cstage isglobal && u->kind == // TY_ARRAY → LEAQ). Runtime-unreachable until the // alias-typed global DATA emit lands (#77/#78); // kept so the read leg is already cs-aligned. if (isglobalarr || isglobalptr) { let bu60: *syntax.tinfo = tichase(bt60); if (bu60 != nil) { isglobalarr = bu60.kind == syntax.tykind.TY_ARRAY; isglobalptr = !isglobalarr; }; }; }; // Alias-typed ELEMENT under an ident base (`[2]row`, // row = [3]int): elemisarrayc's node walk can't see // through the element's N_TNAME — the chased stamped // element tinfo is the authority (cstage gates on // esubu = type_chase_named(esub) == TY_ARRAY). if (!elem_isarray) { elem_isarray = tinfoisarray(n.type_: *syntax.tinfo); }; } else { if (base.kind == syntax.nkind.N_INDEX) { // #60: chained `names[i][k]` — n.type_ is the checker- // stamped outer element tinfo (indexresult over the inner // index's value type). cstage reads base->type->sub->size // for esz (cmd/w6c/cgen.c:2070-2071). Drops the // indexvaluetnode walk. // #22 (F7-c3): also stamp elemisstr/elemisslice off the same // chased element tinfo. Without them a chained index whose // element is a str/slice (`m[i][k]` over [N][M]str) loaded // only the ptr word — the 24B/16B header (len/cap) was // dropped (stale BX/CX) → garbage .len downstream. cstage's // idx_eff path classifies the element uniformly via // type_isstr/type_isslice (cmd/w6c/cgen.c); align ww UP by // reading the SAME stamp the esz read above uses. CLASS-N: // the corpus has no chained str/slice element, so the prior // byte-id is preserved (this only fires on the missed shape). let et: *syntax.tinfo = n.type_: *syntax.tinfo; if (et != nil) { esz = et.size: i32; signed_elem = syntax.typeissigned(et); elemisstr = syntax.typeisstr(et); elemisslice = syntax.typeisslice(et); float_elem = syntax.typeisfloat(et); f32_elem = syntax.typeisf32(et); elem_isarray = tinfoisarray(et); }; } else { // Every OTHER non-ident base — N_DOT (`s.arr[i]`), N_UN-deref // (`(*p)[i]`, #61), N_CAST (`(e:*[N]T)[i]`, #19old), N_CALL // (`f()[i]`), N_SLICE (`s[a:b][i]`), N_TYPEASSERT // (`(v as *[N]T)[i]`), … — derives esz/stride/load-width/ // signedness from the checker-stamped index-RESULT tinfo // n.type_ (the element T: u32->4). cstage reads it UNIFORMLY // via idx_eff(base->type)->sub->size with NO node-kind gate // (cmd/w6c/cgen.c:3517-18); wwstage's prior node-kind whitelist // (DOT/UN/CAST only) silently left N_CALL/N_SLICE/N_TYPEASSERT // (and any future base kind) at the 8B default — wrong stride // AND full-word MOVQ load for narrow elements. One stamped-tinfo // read mirrors cstage and closes the class by construction // (#19old + its CALL/SLICE/TYPEASSERT residuals). esz via // dt.size (type table, rule-13). Signedness from the same tinfo // so a signed-narrow element sign-extends on load (loadopsz keys // on (signed,sz); cstage's fldloadop reads it from the element // type — align ww up, #255). The base ADDRESS materialization // below is unchanged; only the stride/load-width was wrong. let dt: *syntax.tinfo = n.type_: *syntax.tinfo; if (dt != nil) { esz = dt.size: i32; signed_elem = syntax.typeissigned(dt); elemisstr = syntax.typeisstr(dt); elemisslice = syntax.typeisslice(dt); float_elem = syntax.typeisfloat(dt); f32_elem = syntax.typeisf32(dt); }; elem_isarray = tinfoisarray(dt); };}; }; // 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 == syntax.nkind.N_IDENT) { let bl: *local = baselocal; let etn: *syntax.node = nil; // idxelemtn drills `*[N]T` to the pointee array's own // element (#61) — an undrilled etn classified the whole // array, missing tagged/str/slice elements behind a // pointer-to-array base. if (bl != nil) { etn = idxelemtn(bl.tnode); } else { etn = idxelemtn(letvartnode(c, base.str)); // #8/GAP-B: a def-global str/slice-array base lives // in c.defs, not c.lets — letvartnode misses it → // etn nil → elem mis-classified scalar, dropping the // 3-word slice-header load (returns element ADDR not // .len; cstage's Type-based classify loads the full // header, the proven let form). defvartnode is the // def-side sister — same fallback as the base-address // resolution at cgenexpr.ww:1762. if (etn == nil) { etn = idxelemtn(defvartnode(c, base.str)); }; }; // #60: an alias-NAMED base has no element tnode // (idxelemtn sees the N_TNAME leaf, nil) — classify // off the stamped index-result n.type_, the same // source the N_DOT/N_INDEX arms read. if (basealias) { etn = n; }; if (istaggedtype(c, etn)) { if (!isnullabletype(etn)) { elem_tagged = true; elem_slot_sz = slotsize(c, etn); esz = elem_slot_sz; }; }; elemisstr = isstrtype(c, etn); elemisslice = isslicetype(c, etn); }; // Any NON-ident base (`x.o[i]`, chained `m[i][j]`, `(*p)[i]`, // call `f()[i]`, slice `s[a:b][i]`, typeassert …): the element // tinfo is n.type_ (the checker-stamped indexresult), same source // the esz/str/slice/float flags read above. Mirror cstage // cgen.c:8101 `esubu->kind == TY_TAGGED` — classified for ANY // base, NOT gated on a base-kind whitelist, and NOT excluding the // nullable fold (slot_sz=8 degrades the copy arm to one MOVQ, // matching cstage's fallback `MOVQ AX,BX; MOVQ (BX),AX`). // #261: without the classify an N_DOT-base tagged element fell to // the scalar loadopsz path and dropped the tag/payload-high word; // #23 (F7-c6): the prior N_DOT/N_INDEX/N_UN whitelist still missed // an N_CALL / N_SLICE base (`f()[i]` / `s[a:b][i]`) → lone // `MOVQ (AX),AX` (tag only) vs cstage's full 4-word cursor // (cs rc=50 / ww rc=40). Dropping the whitelist for the stamp read // closes the base-kind class by construction. if (base.kind != syntax.nkind.N_IDENT) { let dt: *syntax.tinfo = n.type_: *syntax.tinfo; if (dt != nil) { if (syntax.typeistagged(dt)) { elem_tagged = true; elem_slot_sz = dt.size: i32; esz = elem_slot_sz; }; }; }; }; // #121 leg (b): whole TUPLE element `let e = tbl[i]`. Classify off // the chased index-result tinfo (n.type_, the same source the N_DOT/ // N_INDEX arms read). gptotal = sum(tupeslot/8); the per-base arms // below fill that many cursor words (tupreg) from the element // address. Mirrors cstage cgen.c N_INDEX TY_TUPLE arms. let elem_tuple: bool = false; let tuple_nwords: i32 = 0; { let eti: *syntax.tinfo = tichase(n.type_: *syntax.tinfo); if (eti != nil) { if (eti.kind == syntax.tykind.TY_TUPLE) { elem_tuple = true; // ww tuples store elements in .tupleelems, not .params. let tpw: *syntax.ttupleelem = eti.tupleelems; for (tpw != nil) { tuple_nwords += tupeslot(tpw.type_) / 8; tpw = tpw.tnext; }; };}; }; 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"); // #156: array element ([N][M]T) → leave the sub-array ADDRESS // in AX (BX holds base+idx*esz); nested index dereferences. if (elem_isarray) { emitline("\tMOVQ\tBX, AX\n"); return; }; if (elem_tagged) { // #37: >32B box — ADDRESS in AX (the taggedmemread // convention); the 4-reg cursor walk below would // truncate past payload word 2. Mirrors cstage. if (elem_slot_sz > TUPLE_GPCAP * 8) { emitline("\tMOVQ\tBX, AX\n"); return; }; 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; }; // #121 leg (b): whole TUPLE element — fill gptotal cursor words // from the element address (BX); descending so AX loads last, // mirroring the tagged arm. Over-cap leaves the ADDRESS in AX. if (elem_tuple) { if (tuple_nwords > TUPLE_GPCAP) { emitline("\tMOVQ\tBX, AX\n"); return; }; let kw: i32 = tuple_nwords - 1; for (kw >= 0) { emitline("\tMOVQ\t"); emitdispreg((kw * 8): i64, "BX"); emitline(", "); emitline(tupreg(kw)); emitline("\n"); kw -= 1; }; return; }; // str/slice element: load the full (ptr, len, cap) header into // (AX, BX, CX) — both are 24B since #1, so cap must survive. // Kind-gate on isstrtype||isslicetype, never size==24 (a >16B // struct is 24B+ too but takes the struct-copy path). Base is BX. if (elemisstr || elemisslice) { cgslicehdr(c, "BX"); return; }; // #119: float element → MOVSS/MOVSD into X0 (the consumer's // ADDSD/MOVSD spill machinery already expects X0); the integer // loadopsz below would leave it in AX and the SSE side reads // stale. Twin of cgen.c:2014's scalar-float global load. if (float_elem) { let fop1: str = "MOVSD"; if (f32_elem) { fop1 = "MOVSS"; }; emitline("\t"); emitline(fop1); emitline("\t(BX), X0\n"); return; }; let lop1: str = loadopsz(signed_elem, esz); emitline("\t"); emitline(lop1); emitline("\t(BX), AX\n"); return; }; if (baselocal != nil) { let tn: *syntax.node = baselocal.tnode; let isarray: bool = false; if (tn != nil) { if (tn.kind == syntax.nkind.N_TARRAY) { isarray = true; }; }; // #60: alias-NAMED base — LEAQ-vs-MOVQ off the chased stamped // kind (cstage u->kind == TY_ARRAY, cmd/w6c/cgen.c N_INDEX). if (basealias) { let bu60: *syntax.tinfo = tichase(base.type_: *syntax.tinfo); if (bu60 != nil) { isarray = bu60.kind == syntax.tykind.TY_ARRAY; }; }; 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"); // #156: array element ([N][M]T) → leave the sub-array ADDRESS // in AX (BX holds base+idx*esz); nested index dereferences. if (elem_isarray) { emitline("\tMOVQ\tBX, AX\n"); return; }; if (elem_tagged) { // #37: >32B box — ADDRESS in AX (the taggedmemread // convention); the 4-reg cursor walk below would // truncate past payload word 2. Mirrors cstage. if (elem_slot_sz > TUPLE_GPCAP * 8) { emitline("\tMOVQ\tBX, AX\n"); return; }; 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; }; // #121 leg (b): whole TUPLE element — twin of the global arm // above (base in BX). Over-cap leaves the ADDRESS in AX. if (elem_tuple) { if (tuple_nwords > TUPLE_GPCAP) { emitline("\tMOVQ\tBX, AX\n"); return; }; let kw: i32 = tuple_nwords - 1; for (kw >= 0) { emitline("\tMOVQ\t"); emitdispreg((kw * 8): i64, "BX"); emitline(", "); emitline(tupreg(kw)); emitline("\n"); kw -= 1; }; return; }; // str/slice element: full (ptr, len, cap) header into (AX, BX, CX); // cap must survive (#1). Kind-gate, never size==24. Base BX. if (elemisstr || elemisslice) { cgslicehdr(c, "BX"); return; }; // #119: float element → X0 (see the global arm above). if (float_elem) { let fop2: str = "MOVSD"; if (f32_elem) { fop2 = "MOVSS"; }; emitline("\t"); emitline(fop2); emitline("\t(BX), X0\n"); return; }; let lop2: str = loadopsz(signed_elem, esz); emitline("\t"); emitline(lop2); emitline("\t(BX), AX\n"); return; }; // #135: N_DOT base on `[N]T` field needs the field's ADDRESS, // not its value. cgexpr would auto-deref + load the 8-byte value // as if it were a pointer. dotbaseaddr emits the address inline. emitline("\tPUSHQ\tAX\n"); if (!dotbaseaddr(c, base, "AX")) { cgexpr(c, base); }; emitline("\tPOPQ\tBX\n"); emitline("\tADDQ\tBX, AX\n"); // #156: array element ([N][M]T) → AX already holds &elem // (base+idx*esz); a nested index dereferences. See ident arms. if (elem_isarray) { return; }; if (elem_tagged) { // AX holds the element address. Copy to BX (loading slot+0 // into AX clobbers it), then read slot words. #48: the >24 // R8 word was missing ONLY in this fallback arm (both ident // arms have it) — a >24B-slot element via a non-ident base // under-read the cursor and the match spill stored stale R8. // Mirrors cstage cgen.c:9106-9117. // #37: >32B box — AX already holds the element address; // leave it (taggedmemread). Mirrors cstage. if (elem_slot_sz > TUPLE_GPCAP * 8) { return; }; emitline("\tMOVQ\tAX, BX\n"); 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; }; // #121 leg (b) via fallback base: whole TUPLE element. AX holds the // element address — copy to BX (the cursor fill into AX clobbers it), // then fill the gptotal cursor words. Over-cap leaves AX as the addr. if (elem_tuple) { if (tuple_nwords > TUPLE_GPCAP) { return; }; emitline("\tMOVQ\tAX, BX\n"); let kw: i32 = tuple_nwords - 1; for (kw >= 0) { emitline("\tMOVQ\t"); emitdispreg((kw * 8): i64, "BX"); emitline(", "); emitline(tupreg(kw)); emitline("\n"); kw -= 1; }; return; }; // str/slice element via fallback base: full (ptr, len, cap) header // into (AX, BX, CX); cap must survive (#1). Kind-gate, never // size==24. Base AX. if (elemisstr || elemisslice) { cgslicehdr(c, "AX"); return; }; // #119: float element → X0 (see the global arm above). The base // address is in AX; MOVSS/MOVSD reads the element into X0. if (float_elem) { let fop3: str = "MOVSD"; if (f32_elem) { fop3 = "MOVSS"; }; emitline("\t"); emitline(fop3); emitline("\t(AX), X0\n"); return; }; let lop3: str = loadopsz(signed_elem, esz); emitline("\t"); emitline(lop3); emitline("\t(AX), AX\n"); return; }; // cgbasecap — load the capacity of a sub-slice's UNDERLYING storage // into `dst` for the #20 cap = base_cap - lo formula (drew: harec // eval.c:1017 slice cap-=start / eval.c:1024 array cap=length-start; // ensure.ha:4-8 distinct capacity field). array [N]T -> N (literal); // slice/str -> the .capacity word in the header at +16 (mirrors the // hi-default +8 length dispatch, emitted unconditionally). Returns // false when base_cap isn't cleanly available so the caller keeps the // prior cap=len: a non-ident base (its header cap was discarded; // recomputing would re-evaluate a possibly side-effecting base -- #74, // which also owns the pre-existing defaulted-hi len gap there), or // a GLOBAL str base (no +16 load here, #73 -- matching the cstage // carve-out keeps both stages byte-identical). cstage twin: // cmd/w6c/cgen.c cg_base_cap. fn cgbasecap(c: *cgen, base: *syntax.node, dst: str) bool = { if (base == nil) { return false; }; if (base.kind != syntax.nkind.N_IDENT) { return false; }; let baselocal: *local = localfindnode(c, base.str); if (baselocal != nil) { let tn: *syntax.node = baselocal.tnode; if (tn == nil) { return false; }; if (tn.kind == syntax.nkind.N_TARRAY) { let lenn: *syntax.node = tn.rhs; if (lenn != nil && lenn.kind == syntax.nkind.N_INTLIT) { emitline("\tMOVQ\t$"); emituint(lenn.uval); emitline(", "); emitline(dst); emitline("\n"); return true; }; // #21: a def/const array dim — non-N_INTLIT — reads its cap // from the stamped array tinfo (rule-13), the cap twin of the // default-hi fix; pre-fix cgbasecap returned false here and the // caller fell to cap=len (cs computes base_cap-lo from bu->alen). let abt: *syntax.tinfo = tichase(base.type_: *syntax.tinfo); if (abt != nil && abt.kind == syntax.tykind.TY_ARRAY) { emitline("\tMOVQ\t$"); emitint(abt.alen: i64); emitline(", "); emitline(dst); emitline("\n"); return true; }; return false; }; if (tn.kind == syntax.nkind.N_TSLICE) { emitline("\tMOVQ\t"); emitoff((baselocal.off + 16): i64); emitline("(BP), "); emitline(dst); emitline("\n"); return true; }; if (tn.kind == syntax.nkind.N_TNAME) { if (syntax.streq(tn.str, "str")) { emitline("\tMOVQ\t"); emitoff((baselocal.off + 16): i64); emitline("(BP), "); emitline(dst); emitline("\n"); return true; }; }; // #60: alias-NAMED base — chased kind (cstage cg_base_cap // receives bu pre-chased by the N_SLICE arm, cgen.c:1888). let bt60: *syntax.tinfo = base.type_: *syntax.tinfo; if (bt60 != nil) { if (bt60.kind == syntax.tykind.TY_NAMED) { let bu60: *syntax.tinfo = tichase(bt60); if (bu60 != nil) { if (bu60.kind == syntax.tykind.TY_ARRAY) { emitline("\tMOVQ\t$"); emitint(bu60.alen: i64); emitline(", "); emitline(dst); emitline("\n"); return true; }; if (bu60.kind == syntax.tykind.TY_SLICE || bu60.kind == syntax.tykind.TY_STR) { emitline("\tMOVQ\t"); emitoff((baselocal.off + 16): i64); emitline("(BP), "); emitline(dst); emitline("\n"); return true; }; }; };}; return false; }; let gt: *syntax.node = letvartnode(c, base.str); if (gt == nil) { return false; }; if (gt.kind == syntax.nkind.N_TARRAY) { let lenn: *syntax.node = gt.rhs; if (lenn != nil && lenn.kind == syntax.nkind.N_INTLIT) { emitline("\tMOVQ\t$"); emituint(lenn.uval); emitline(", "); emitline(dst); emitline("\n"); return true; }; // #21: def/const global array dim cap — twin of the local arm. let abt: *syntax.tinfo = tichase(base.type_: *syntax.tinfo); if (abt != nil && abt.kind == syntax.tykind.TY_ARRAY) { emitline("\tMOVQ\t$"); emitint(abt.alen: i64); emitline(", "); emitline(dst); emitline("\n"); return true; }; return false; }; if (gt.kind == syntax.nkind.N_TSLICE) { emitline("\tLEAQ\t"); emitsymname(c, base.str); emitline("(SB), "); emitline(dst); emitline("\n"); emitline("\tMOVQ\t16("); emitline(dst); emitline("), "); emitline(dst); emitline("\n"); return true; }; // #60: alias-NAMED global base — chased kind; global STR keeps // the #73 cap=len carve-out (cstage isglobal && TY_STR → 0). // Runtime-unreachable until #77/#78 global DATA. let gbt60: *syntax.tinfo = base.type_: *syntax.tinfo; if (gbt60 != nil) { if (gbt60.kind == syntax.tykind.TY_NAMED) { let gbu60: *syntax.tinfo = tichase(gbt60); if (gbu60 != nil) { if (gbu60.kind == syntax.tykind.TY_ARRAY) { emitline("\tMOVQ\t$"); emitint(gbu60.alen: i64); emitline(", "); emitline(dst); emitline("\n"); return true; }; if (gbu60.kind == syntax.tykind.TY_SLICE) { emitline("\tLEAQ\t"); emitsymname(c, base.str); emitline("(SB), "); emitline(dst); emitline("\n"); emitline("\tMOVQ\t16("); emitline(dst); emitline("), "); emitline(dst); emitline("\n"); return true; }; }; };}; return false; }; // cgslice — `base[lo:hi]` as a slice value. Leaves (AX=base+lo*esz, // BX=hi-lo, CX=base_cap-lo) so callers can route to a slice slot, // return, or arg with the same triple ABI. cap is the storage // remaining to the base's end (#20, Go/Hare-identical) via cgbasecap. // ptr advances by BYTES (lo*esz, #76; ref/hare/rt/ensure.ha:30 // membsz-unit); esz from the type table, mirroring the cgindex idiom. // slicebaseesz — element width of a sliceable base, exactly mirroring the // esz cascade cgslice computes inline for ptr-scaling (baselocal/globaltn → // elemsizeofc; N_DOT field → dotbu.sub.size; N_ARRLIT → elemsizeofc; alias- // NAMED override → chased sub.size). The #145 slice-copy-assign arm scales // the COUNT by this same width, so it MUST match cgslice's ptr scaling // byte-for-byte (cgslice supplies the dst ptr). Cstage twin: the N_SLICE-LHS // arm in cgen.c N_ASSIGN reuses the read-path one-liner directly. fn slicebaseesz(c: *cgen, base: *syntax.node) i32 = { if (base == nil) { return 1; }; let esz: i32 = 1; if (base.kind == syntax.nkind.N_IDENT) { let bl: *local = localfindnode(c, base.str); if (bl != nil) { esz = elemsizeofc(c, bl.tnode); } else { let gt: *syntax.node = letvartnode(c, base.str); if (gt != nil) { esz = elemsizeofc(c, gt); }; }; let bt: *syntax.tinfo = base.type_: *syntax.tinfo; if (bt != nil) { if (bt.kind == syntax.tykind.TY_NAMED) { let bu60: *syntax.tinfo = tichase(bt); let es60: *syntax.tinfo = tichase(bu60.sub); if (es60 != nil) { esz = es60.size: i32; }; };}; } else { if (base.kind == syntax.nkind.N_DOT) { let db: *syntax.tinfo = tichase(base.type_: *syntax.tinfo); if (db != nil) { if (db.sub != nil) { esz = db.sub.size: i32; }; }; } else { if (base.kind == syntax.nkind.N_ARRLIT) { esz = elemsizeofc(c, base.lhs); };};}; return esz; }; fn cgslice(c: *cgen, n: *syntax.node) void = { let base: *syntax.node = n.lhs; let lo: *syntax.node = n.rhs; let hi: *syntax.node = n.cond; let baselocal: *local = nil; let globaltn: *syntax.node = nil; let globalname: str; globalname.ptr = nil; globalname.len = 0; if (base != nil) { if (base.kind == syntax.nkind.N_IDENT) { baselocal = localfindnode(c, base.str); if (baselocal == nil) { let gt: *syntax.node = letvartnode(c, base.str); if (gt != nil) { globaltn = gt; globalname = base.str; }; }; }; }; // #252: an N_DOT `[N]T`-field base (`s.obuf[lo:hi]`) carries no // tnode — resolve esz / default-hi / base-address from the checker- // stamped element tinfo on base.type_ instead. Cstage twin reads // base->type (cgen.c N_SLICE esz + bu->kind==TY_ARRAY default-hi). let dotbu: *syntax.tinfo = nil; if (base != nil) { if (base.kind == syntax.nkind.N_DOT) { dotbu = base.type_: *syntax.tinfo; dotbu = tichase(dotbu); };}; // #31: an N_ARRLIT base (the desugared one-step `let xs:[]T=[..]` // borrow — the ONLY context that reaches here; call-arg/return/assign // loud-reject at the checker, #33) has no storage. Its [count]T type // NODE is stashed on base.lhs by checkletassign's #25 re-stamp; size / // count come NODE-wise (elemsizeofc / .rhs intlit), because wwstage // narrow-primitive tinfos are unsized (#8). Cstage twin reads base->type // (its Type IS sized). let arrlittn: *syntax.node = nil; if (base != nil) { if (base.kind == syntax.nkind.N_ARRLIT) { arrlittn = base.lhs; };}; // #60 (alias arc #5): alias-NAMED N_IDENT base — the tnode reads // below see only the N_TNAME leaf (esz 1-sentinel, MOVQ base, // $0 default-hi, cap=len). All four reads re-key off the chased // stamped tinfo, mirroring cstage N_SLICE's single // bu = type_chase_named(base->type) source (cmd/w6c/cgen.c). let basealias: bool = false; let bu60: *syntax.tinfo = nil; if (base != nil) { if (base.kind == syntax.nkind.N_IDENT) { let bt60: *syntax.tinfo = base.type_: *syntax.tinfo; if (bt60 != nil) { if (bt60.kind == syntax.tykind.TY_NAMED) { basealias = true; bu60 = tichase(bt60); };}; };}; // esz from the type table for an N_IDENT base (#76; mirrors the // cgindex idiom) or an N_DOT array/slice-field base (#252: scale by // the field's element width, not esz=1 — silently wrong for non-u8). // Other non-ident bases stay esz=1 -> ptr unscaled. let esz: i32 = 1; if (baselocal != nil) { esz = elemsizeofc(c, baselocal.tnode); } else { if (globaltn != nil) { esz = elemsizeofc(c, globaltn); } else { if (dotbu != nil && dotbu.sub != nil) { esz = dotbu.sub.size: i32; } else { if (arrlittn != nil) { esz = elemsizeofc(c, arrlittn); };};};}; if (bu60 != nil) { let es60: *syntax.tinfo = tichase(bu60.sub); if (es60 != nil) { esz = es60.size: i32; }; }; if (baselocal != nil) { let tn: *syntax.node = baselocal.tnode; let isarray: bool = false; if (tn != nil) { if (tn.kind == syntax.nkind.N_TARRAY) { isarray = true; }; }; // #60: alias-NAMED base — chased kind (see cgindex twin). if (bu60 != nil) { isarray = bu60.kind == syntax.tykind.TY_ARRAY; }; 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 (globaltn != nil) { // Top-level let: [N]T → LEAQ name(SB); pointer/slice/str // → MOVQ name(SB) (the symbol holds the {ptr,len,cap} or // {ptr,len} or pointer value). let gisarr: bool = globaltn.kind == syntax.nkind.N_TARRAY; // #60: alias-NAMED base — chased kind; runtime-unreachable // until #77/#78 global DATA (see cgindex twin). if (bu60 != nil) { gisarr = bu60.kind == syntax.tykind.TY_ARRAY; }; if (gisarr) { emitline("\tLEAQ\t"); emitsymname(c, globalname); emitline("(SB), AX\n"); } else { emitline("\tMOVQ\t"); emitsymname(c, globalname); emitline("(SB), AX\n"); }; } else { if (base != nil && base.kind == syntax.nkind.N_ARRLIT && arrlittn != nil) { // #31: materialise the array literal into a FRESH per-borrow // @slicescr stack slot (distinct slot per borrow — a borrow's // backing must outlive the lowering, so it can't share a cached // slot; localalloc is always-fresh, mirror of cstage local_alloc), // fill it via the shared element-fill, then LEAQ the slot as base. // Size/count NODE-wise off the stashed [count]T tnode (#8: tinfo // primitive sizes are 0). Escape (WHY, rob): a `let xs:[]T=[..]; // return xs;` returns a slice into this frame slot, freed on // return = dangling — IDENTICAL to the named-array borrow and // Hare-consistent (no escape analysis / GC / heap promotion; a // local borrowed past its frame is a footgun, not promoted). let cnt: i32 = 0; if (arrlittn.rhs != nil) { if (arrlittn.rhs.kind == syntax.nkind.N_INTLIT) { cnt = arrlittn.rhs.uval: i32; }; }; let bsz: i32 = elemsizeofc(c, arrlittn) * cnt; if (bsz < 1) { bsz = 1; }; let scr: i32 = localalloc(c, "@slicescr", bsz, nil); cgarrlitfillbp(c, arrlittn, base, scr); emitline("\tLEAQ\t"); emitoff(scr: i64); emitline("(BP), AX\n"); } else { if (base != nil) { // #252: N_DOT `[N]T`-field base → field ADDRESS via // dotbaseaddr (LEAQ), not the auto-deref VALUE load cgexpr // emits. Sibling of the #135 read-side. if (!dotbaseaddr(c, base, "AX")) { cgexpr(c, base); }; };};};}; emitline("\tPUSHQ\tAX\n"); if (lo != nil) { cgexpr(c, lo); } else { emitline("\tMOVQ\t$0, AX\n"); }; emitline("\tPUSHQ\tAX\n"); if (hi != nil) { cgexpr(c, hi); } else { if (baselocal != nil) { let tn: *syntax.node = baselocal.tnode; let handled: bool = false; if (tn != nil) { if (tn.kind == syntax.nkind.N_TARRAY) { let lenn: *syntax.node = tn.rhs; if (lenn != nil && lenn.kind == syntax.nkind.N_INTLIT) { emitline("\tMOVQ\t$"); emituint(lenn.uval); emitline(", AX\n"); handled = true; } else { // #21: a def/const array dim (`[MAX]u8`) is not an // N_INTLIT node, so the literal read above misses it // (MOVQ $0 default-hi -> len 0 / underflow, exit 255). // Read the resolved length from the stamped array // tinfo (rule-13), mirroring cstage's bu->alen. let abt: *syntax.tinfo = tichase(base.type_: *syntax.tinfo); if (abt != nil && abt.kind == syntax.tykind.TY_ARRAY) { emitline("\tMOVQ\t$"); emitint(abt.alen: i64); emitline(", AX\n"); handled = true; }; }; } else { if (tn.kind == syntax.nkind.N_TSLICE) { emitline("\tMOVQ\t"); emitoff((baselocal.off + 8): i64); emitline("(BP), AX\n"); handled = true; } else { if (tn.kind == syntax.nkind.N_TNAME) { if (syntax.streq(tn.str, "str")) { emitline("\tMOVQ\t"); emitoff((baselocal.off + 8): i64); emitline("(BP), AX\n"); handled = true; }; };};}; }; // #60: alias-NAMED base default-hi — chased kind (cstage // N_SLICE hi-default reads bu uniformly: TY_ARRAY → $alen, // TY_SLICE/TY_STR → len word at +8). if (!handled && bu60 != nil) { if (bu60.kind == syntax.tykind.TY_ARRAY) { emitline("\tMOVQ\t$"); emitint(bu60.alen: i64); emitline(", AX\n"); handled = true; } else { if (bu60.kind == syntax.tykind.TY_SLICE || bu60.kind == syntax.tykind.TY_STR) { emitline("\tMOVQ\t"); emitoff((baselocal.off + 8): i64); emitline("(BP), AX\n"); handled = true; };}; }; if (!handled) { emitline("\tMOVQ\t$0, AX\n"); }; } else { if (globaltn != nil) { let handled: bool = false; if (globaltn.kind == syntax.nkind.N_TARRAY) { let lenn: *syntax.node = globaltn.rhs; if (lenn != nil && lenn.kind == syntax.nkind.N_INTLIT) { emitline("\tMOVQ\t$"); emituint(lenn.uval); emitline(", AX\n"); handled = true; } else { // #21: a def/const global array dim — non-N_INTLIT, read the // resolved length off the stamped array tinfo (rule-13), the // twin of the local arm above (cstage's bu->alen). let abt: *syntax.tinfo = tichase(base.type_: *syntax.tinfo); if (abt != nil && abt.kind == syntax.tykind.TY_ARRAY) { emitline("\tMOVQ\t$"); emitint(abt.alen: i64); emitline(", AX\n"); handled = true; }; }; } else { if (globaltn.kind == syntax.nkind.N_TSLICE) { emitline("\tLEAQ\t"); emitsymname(c, globalname); emitline("(SB), CX\n"); emitline("\tMOVQ\t8(CX), AX\n"); handled = true; };}; // #60: alias-NAMED global base default-hi — chased kind; // runtime-unreachable until #77/#78 global DATA (cstage // emits LEAQ+8 for global SLICE/STR alike). if (!handled && bu60 != nil) { if (bu60.kind == syntax.tykind.TY_ARRAY) { emitline("\tMOVQ\t$"); emitint(bu60.alen: i64); emitline(", AX\n"); handled = true; } else { if (bu60.kind == syntax.tykind.TY_SLICE || bu60.kind == syntax.tykind.TY_STR) { emitline("\tLEAQ\t"); emitsymname(c, globalname); emitline("(SB), CX\n"); emitline("\tMOVQ\t8(CX), AX\n"); handled = true; };}; }; if (!handled) { emitline("\tMOVQ\t$0, AX\n"); }; } else { if (dotbu != nil && dotbu.kind == syntax.tykind.TY_ARRAY) { // #252: default-hi `s.obuf[lo:]` on a struct array-field → // element count from the field's array tinfo. Cstage twin // cgexpr_int(bu->alen) (cgen.c N_SLICE default-hi). emitline("\tMOVQ\t$"); emitint(dotbu.alen: i64); emitline(", AX\n"); } else { if (dotbu != nil && (dotbu.kind == syntax.tykind.TY_SLICE || dotbu.kind == syntax.tykind.TY_STR)) { // slice/str FIELD base (#252's slice twin): the old // fall-through emitted $0 — silent 0/negative len on BOTH // stages, byteid-blind. Re-evaluate the field read for its // header (pure place: call inners loud-reject upstream; // cgdot's slice/str arms leave AX=ptr, BX=len, CX=cap for // every supported inner) and take .len. Scratch regs are // dead at the hi step. Cstage twin: cgen.c N_SLICE. cgexpr(c, base); emitline("\tMOVQ\tBX, AX\n"); } else { if (arrlittn != nil) { // #31: default-hi for the arrlit base = its element count (the // stashed [count]T tnode's .rhs intlit). let hc: i64 = 0i64; if (arrlittn.rhs != nil) { if (arrlittn.rhs.kind == syntax.nkind.N_INTLIT) { hc = arrlittn.rhs.uval: i64; }; }; emitline("\tMOVQ\t$"); emitint(hc); emitline(", AX\n"); } else { emitline("\tMOVQ\t$0, AX\n"); };};};};};}; emitline("\tMOVQ\tAX, BX\n"); emitline("\tPOPQ\tCX\n"); emitline("\tPOPQ\tAX\n"); // ptr = base + lo*esz (#76; ensure.ha:30 membsz-unit). // DX=lo*esz; CX=lo PRESERVED for len + cap (#20). if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", DX\n"); emitline("\tIMULQ\tCX, DX\n"); emitline("\tADDQ\tDX, AX\n"); } else { emitline("\tADDQ\tCX, AX\n"); }; emitline("\tSUBQ\tCX, BX\n"); // cap = base_cap - lo (#20); CX=lo, BX=len here. if (cgbasecap(c, base, "DX")) { emitline("\tSUBQ\tCX, DX\n"); emitline("\tMOVQ\tDX, CX\n"); } else { emitline("\tMOVQ\tBX, CX\n"); }; }; // matcharmwant — variant tag a non-nullable match pattern selects on // the scrutinee's tagged union; 0 when unresolvable (cstage's tag<0 // clamp, cmd/w6c/cgen.c:11124). Serves the single-pattern arm and each // `case T1 | T2` alt identically. fn matcharmwant(c: *cgen, scrutt: *syntax.node, pat: *syntax.node) i32 = { let want: i32 = 0; if (scrutt != nil) { // #67: gate on the stamped tinfo, not the node kind // — matchscrutt now returns the scrutinee node itself // for an N_DOT field (its .type_ is the tagged tinfo) // rather than the resolved N_TTAGGED node. if (istaggedtype(c, scrutt)) { let r: i32 = -1; let pattype: *syntax.tinfo = pat.type_: *syntax.tinfo; if (pattype != nil) { // #179: kind-agnostic dispatch on the resolved // tinfo. Cstage cg_tag_for_variant works on // Type, so N_TPTR / N_TFN / N_TPTR(N_TFN) case- // patterns all reach typeeq; the prior pat.kind // gate dropped them to r=-1 → tag 0 collapse. // Slice arm still goes through flatslicevariantidx // (task #19 untyped-elem fallback when typeeq // can't match a (scalar | []T) shape). if (syntax.typeisslice(pattype)) { r = flatslicevariantidx(c, scrutt, pat.lhs); } else { r = flatvariantidxt(scrutt.type_: *syntax.tinfo, pattype, false); }; }; if (r < 0) { let mmt: str = "tagged match: case type not in union (rule 7)\n"; os.write(2, mmt.ptr, mmt.len: u64); os.exit(1); }; want = r; }; }; return want; }; fn cgmatch(c: *cgen, n: *syntax.node) void = { // 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). // Family C (#35): identity-cast peel — see the cgtypetest twin. let scrut: *syntax.node = taggedidcastpeel(c, n.lhs); let scrutoff: i32 = 0; let scrutt: *syntax.node = nil; if (scrut != nil) { if (scrut.kind == syntax.nkind.N_IDENT) { let lc: *local = localfindnode(c, scrut.str); if (lc != nil) { scrutoff = lc.off; scrutt = resolvetagged(c, lc.tnode); } else { if (isletvar(c, scrut.str)) { // #87: a tagged-union GLOBAL scrutinee — the box lives // in static DATA at name(SB), not the BP frame. // localfindnode returns nil and the dispatch would read // saved BP as the tag (garbage). The PLAIN-tagged twin // of cstage #78 SB-resolution: LEAQ the address, copy // the box into an @match_spill slot indexed off BP. let gtt: *syntax.node = resolvetagged(c, letvartnode(c, scrut.str)); if (gtt != nil && !isnullabletype(gtt)) { scrutt = gtt; let gspill: i32 = matchspillsz(c, gtt); scrutoff = localalloc(c, "@match_spill", gspill, nil); emitline("\tLEAQ\t"); emitfnname(c, scrut.str, c.curmod); emitline("(SB), AX\n"); let gk: i32 = 0; for (gk < gspill) { emitline("\tMOVQ\t"); emitdispreg(gk: i64, "AX"); emitline(", DX\n"); emitline("\tMOVQ\tDX, "); emitoff((scrutoff + gk): i64); emitline("(BP)\n"); gk += 8; }; }; }; }; } else { // M1 (#25): match on a tagged field of a BARE-ident VALUE // struct reads the box IN PLACE at base.off + field.offset // — the box (tag@+0, word0@+8, word1@+16) is contiguous in // the parent frame, so no @match_spill copy. Verbatim mirror // of cstage N_MATCH's in-place arm (cmd/w6c/cgen.c:10241- // 10296): gate on N_DOT with a bare-IDENT base whose stamped // type chases to TY_STRUCT and whose field resolves by name. // A *ptr-field base (`match (h.e)`, h:*struct) chases to // TY_PTR, the by-name scan misses, and it falls to the spill // arm below — exactly as cstage, no separate deref guard. // #29: the in-place arm is also gated on a CONFIRMED-LOCAL // base (bglobal below). A global VALUE-struct base // (`match (g.field)`) has localfind==0, so the M1 base.off // would land in the saved-BP/return-addr region — both // stages emitted `MOVQ (BP),AX` (gate-blind both-wrong). The // global-base predicate `localfind==0 && isletvar` (cstage // twin: let_islet, cgen.c:2000) routes it through to the // spill `else`, which resolves g(SB). align-DOWN to cstage // (rule 10): both stages emit TEXT $32 on the local-field // case and the same g(SB) spill on the global-field case. let mfld: *syntax.tfield = nil; if (scrut.kind == syntax.nkind.N_DOT && scrut.lhs != nil && scrut.lhs.kind == syntax.nkind.N_IDENT && scrut.lhs.type_ != nil) { let bu: *syntax.tinfo = tichase(scrut.lhs.type_: *syntax.tinfo); if (bu != nil && bu.kind == syntax.tykind.TY_STRUCT) { let fl: *syntax.tfield = bu.fields; for (fl != nil) { if (syntax.streq(fl.name, scrut.str)) { mfld = fl; break; }; fl = fl.tnext; }; }; }; let bglobal: bool = false; if (mfld != nil) { bglobal = (localfind(c, scrut.lhs.str) == 0) && isletvar(c, scrut.lhs.str); }; if (mfld != nil && !bglobal) { let foff: i32 = mfld.offset: i32; scrutoff = localfind(c, scrut.lhs.str) + foff; scrutt = matchscrutt(c, scrut); } else { // Non-ident scrutinee (call result, arr[i], p.field, // ?, etc.). Spill into an `@match_spill` scratch slot // and dispatch off it. Tagged returns (N_CALL) follow // the AX:DX:CX[:R8] convention; tagged-element loads // (N_INDEX) and tagged-field loads (N_DOT, fixed by // #28) produce the same triple. Nullable returns are // single-word (AX = ptr); only +0 is read. // Scrutinee type + spill size resolved through matchscrutt // / matchspillsz at first use (#15) — see cgenutil.ww // (task #9 align-down to cstage). scrutt = matchscrutt(c, scrut); let spillsz: i32 = matchspillsz(c, scrutt); scrutoff = localalloc(c, "@match_spill", spillsz, nil); // #38b: sret-classified tagged call scrutinee — pass // the scrut slot itself as the sret dest and skip the // cursor spill; downstream tag dispatch / case-let // binds already read the slot from memory. Mirrors // cstage cgen.c N_MATCH. let msret: i32 = 0; if (scrut.kind == syntax.nkind.N_CALL) { msret = callsretsize(c, scrut); }; if (msret > 0) { c.sretdestoff = scrutoff; cgexpr(c, scrut); c.sretdestoff = 0; } else { if (taggedmemread(c, scrut)) { // #37: >32B box read (insts[pc], t.N) — cgexpr left // its ADDRESS in AX; copy the whole box from memory. // Mirrors cstage cgmatch. cgexpr(c, scrut); let mk37: i32 = 0; for (mk37 < spillsz) { emitline("\tMOVQ\t"); emitdispreg(mk37: i64, "AX"); emitline(", DX\n"); emitline("\tMOVQ\tDX, "); emitoff((scrutoff + mk37): i64); emitline("(BP)\n"); mk37 += 8; }; } else { // #37 (rule 7): a >32B box from a kind with no mem-read // convention would spill the cursor it never filled — // loud, not garbage. Mirrors cstage. if (!isnullabletype(scrutt) && spillsz > TUPLE_GPCAP * 8) { let m37m: str = "#37: >32B tagged match scrutinee from a non-mem-based source unwired (rule 7)\n"; os.write(2, m37m.ptr, m37m.len: u64); os.exit(1); }; // #37 (rule 7) stamped twin: matchscrutt returns nil // for kinds it can't resolve (deref/cast/...), so // spillsz defaults under cap and the guard above is // blind there. cstage sizes the spill from the // stamped s->type, so it louds — key on scrut.type_ // to match. Surfaced by reviewer-37's `match (*p)` // probe on a 56B box. let ms37: *syntax.tinfo = scrut.type_: *syntax.tinfo; ms37 = tichase(ms37); if (ms37 != nil && ms37.kind == syntax.tykind.TY_TAGGED && ms37.size: i32 > TUPLE_GPCAP * 8) { let m37n: str = "#37: >32B tagged match scrutinee from a non-mem-based source unwired (rule 7)\n"; os.write(2, m37n.ptr, m37n.len: u64); os.exit(1); }; // Family C catch-all (rule 7): a widening tagged // cast scrutinee has no cursor — loud. Mirrors // cstage cgmatch. if (scrut.kind == syntax.nkind.N_CAST && ms37 != nil && ms37.kind == syntax.tykind.TY_TAGGED && ms37.nullable == 0) { let m35m: str = "#35: tagged cast source shape unwired at match (rule 7)\n"; os.write(2, m35m.ptr, m35m.len: u64); os.exit(1); }; 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"); // CX/R8 writes gated on spill size so 1-word- // payload variants (slot 16B) don't bump the // frame past the tag+word0 the receiver reads. // Mirrors cmd/w6c/cgen.c cgmatch's // `if (slot_size > 16)` / `> 24` guards. if (spillsz > 16) { emitline("\tMOVQ\tCX, "); emitoff((scrutoff + 16): i64); emitline("(BP)\n"); }; if (spillsz > 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: *syntax.node = n.list; for (cs != nil) { let nxt: str = mklabel(c, "match_next"); let pat: *syntax.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 == syntax.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 = matcharmwant(c, scrutt, pat); emitline("\tMOVQ\t"); emitoff(scrutoff: i64); emitline("(BP), AX\n"); if (cs.list != nil) { // Multi-pattern `case T1 | T2 =>`: any // matching alt funnels into one body // label. Mirrors cmd/w6c/cgen.c:11118. let bodyl: str = mklabel(c, "match_body"); emitline("\tCMPQ\t$"); emitint(want: i64); emitline(", AX\n"); emitline("\tJE\t"); emitline(bodyl); emitline("\n"); let alt: *syntax.node = cs.list; for (alt != nil) { let awant: i32 = matcharmwant(c, scrutt, alt); emitline("\tCMPQ\t$"); emitint(awant: i64); emitline(", AX\n"); emitline("\tJE\t"); emitline(bodyl); emitline("\n"); alt = alt.next; }; emitline("\tJMP\t"); emitline(nxt); emitline("\n"); emitlabel(bodyl); } else { emitline("\tCMPQ\t$"); emitint(want: i64); emitline(", AX\n"); emitline("\tJNE\t"); emitline(nxt); emitline("\n"); }; }; }; 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 == syntax.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 { // Size the bind from the variant's declared // layout. slotsize covers str (16), []T (24), // N_TNAME named struct (si.totsize), aliases, // tuples, primitives (8). Hardcoding str/slice // + fall-through-8 dropped the high words of a // TY_STRUCT variant (e.g. only v.x reached the // bind for `case let v: pair`, project #31); // mirrors cstage's `bu->size` fallback in // cgen.c cgmatch. let bsz: i32 = slotsize(c, pat); if (bsz <= 0) { bsz = 8; }; // 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); // Word-by-word copy. Round bsz up to 8 in case // a non-multiple-of-8 struct size leaked through // (registerstruct already pads totsize, but be // defensive — same shape as cstage's nwords = // (bsz + 7) / 8). let nwords: i32 = (bsz + 7) / 8; let bw: i32 = 0; for (bw < nwords) { emitline("\tMOVQ\t"); emitoff((scrutoff + 8 + 8 * bw): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\tAX, "); emitoff((voff + 8 * bw): i64); emitline("(BP)\n"); bw += 1; }; }; }; }; 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 cgptrfieldload(c: *cgen, fi: *fieldinfo) void = { // #15 — load struct field `fi` from a *struct base already in BX // (a local ptr's MOVQ off(BP) value, or a module-global ptr's // MOVQ name(SB) value). Shared by the local and global *struct // field-read arms in cgdot; mirrors cstage cgen.c N_DOT *struct // field tail. BX is never a load target, so order is harmless. if (istaggedtype(c, fi.tnode)) { let tsz: i32 = slotsize(c, fi.tnode); // cxlast=false: BX is not a cursor target here, so match // cstage's direct *struct-ptr arm and load CX@+16 before // R8@+24 in strict offset order (#17). cgloadtaggedfield(c, "BX", fi.foff, tsz, false); return; }; // str IS []u8 — same 3-word {ptr,len,cap} as a slice field: load // (ptr, len, cap) into (AX, BX, CX); .len LAST so the earlier reads // still index off BX (#1/Phase 3 collapse). if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) { emitline("\tMOVQ\t"); emitdispreg(fi.foff: i64, "BX"); emitline(", AX\n"); emitline("\tMOVQ\t"); emitdispreg((fi.foff + 16): i64, "BX"); emitline(", CX\n"); emitline("\tMOVQ\t"); emitdispreg((fi.foff + 8): i64, "BX"); emitline(", BX\n"); return; }; if (isfloattype(c, fi.tnode)) { // f64/f32 via *struct: route through X0. 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"); return; }; let op: str = fieldloadop(c, fi); emitline("\t"); emitline(op); emitline("\t"); emitdispreg(fi.foff: i64, "BX"); emitline(", AX\n"); }; // cgptrfieldloadtf — tfield twin of cgptrfieldload (#31): load struct // field `tf` from a *struct base already in BX, resolving offset + field // type off the checker-STAMPED tfield (tf.offset/tf.type_) instead of the // name-keyed fieldinfo. The read-side choke-point shared by the R1 (local) // and R2 (global) *struct field-read arms; a cross-module same-leaf // collision that mis-resolved structlookupchain(inner) cannot reach a // stamped tinfo. Emission is byte-identical to cgptrfieldload (the #21 R0 // substitution table): syntax.typeis*(tf.type_) replaces is*type(fi.tnode), // tichase(tf.type_).size replaces slotsize/fsz. BX is never a load target, // so the str/slice triple writes BX (the base) LAST. fn cgptrfieldloadtf(c: *cgen, tf: *syntax.tfield) void = { let foff: i32 = tf.offset: i32; let ftraw: *syntax.tinfo = tf.type_; if (syntax.typeistagged(ftraw)) { let ftc: *syntax.tinfo = tichase(ftraw); let tsz: i32 = 0; if (ftc != nil) { tsz = ftc.size: i32; }; cgloadtaggedfield(c, "BX", foff, tsz, false); return; }; if (syntax.typeisstr(ftraw) || syntax.typeisslice(ftraw)) { emitline("\tMOVQ\t"); emitdispreg(foff: i64, "BX"); emitline(", AX\n"); emitline("\tMOVQ\t"); emitdispreg((foff + 16): i64, "BX"); emitline(", CX\n"); emitline("\tMOVQ\t"); emitdispreg((foff + 8): i64, "BX"); emitline(", BX\n"); return; }; if (syntax.typeisfloat(ftraw)) { let mov: str = "MOVSD"; if (syntax.typeisf32(ftraw)) { mov = "MOVSS"; }; emitline("\t"); emitline(mov); emitline("\t"); emitdispreg(foff: i64, "BX"); emitline(", X0\n"); return; }; let ftc: *syntax.tinfo = tichase(ftraw); let fsz: i32 = 0; if (ftc != nil) { fsz = ftc.size: i32; }; let op: str = loadopsz(syntax.typeissigned(ftraw), fsz); emitline("\t"); emitline(op); emitline("\t"); emitdispreg(foff: i64, "BX"); emitline(", AX\n"); }; // emitchainbase — load the chained-DOT root BASE into CX for the viacx // (global or `*T`-root) spine. Mirrors cstage cgen.c's uniform base // resolution: a global is reached via LEAQ name(SB),CX; a `*struct` // ROOT then derefs that address once (MOVQ (CX),CX) — covering the // global `*struct` root, where BOTH flags hold (#16) — while a LOCAL // `*T` root loads its frame slot (MOVQ off(BP),CX). The leaf load then // indexes at totaloff off CX. fn emitchainbase(c: *cgen, ptrroot: bool, isglobal: bool, rootoff: i32, rootname: str) void = { if (isglobal) { emitline("\tLEAQ\t"); emitsymname(c, rootname); emitline("(SB), CX\n"); if (ptrroot) { emitline("\tMOVQ\t(CX), CX\n"); }; } else { emitline("\tMOVQ\t"); emitoff(rootoff: i64); emitline("(BP), CX\n"); }; }; fn cgdot(c: *cgen, n: *syntax.node) void = { let lhs: *syntax.node = n.lhs; let fld: str = n.str; // `(*p).f` read retarget: parser produces n.lhs = N_UN(STAR, // IDENT(p)). Substitute the inner IDENT as dotlhs so the // pointer-auto-deref branch (lhs.kind == N_IDENT && N_TPTR // tnode) fires the same as `p.f`. Mirror of the N_ASSIGN N_DOT // lhs retarget in cgassign. Enum-leaf lookup above and // chained-N_DOT branches below keep checking raw lhs since // (*p) is neither shape; remaining addressable bases use the // shared place resolver. let dotlhs: *syntax.node = lhs; if (dotlhs != nil) { if (dotlhs.kind == syntax.nkind.N_UN) { if (dotlhs.op == syntax.tkind.TK_STAR) { if (dotlhs.lhs != nil) { if (dotlhs.lhs.kind == syntax.nkind.N_IDENT) { dotlhs = dotlhs.lhs; }; }; }; }; }; // Enum member access: `EnumName.MEMBER` or `pkg.EnumName.MEMBER` // → inline the pre-computed constant. `pkg.Enum.MEMBER` keeps // `pkg` so enumlookupmod can prefer the explicit module on a // leaf collision; bare `Enum.MEMBER` falls back to c.curmod via // enumlookup's same-module-first walk. if (lhs != nil) { let etname: str; let etmod: str; etname.ptr = nil; etname.len = 0; etmod.ptr = nil; etmod.len = 0; if (lhs.kind == syntax.nkind.N_IDENT) { etname = lhs.str; }; if (lhs.kind == syntax.nkind.N_DOT) { if (lhs.lhs != nil) { if (lhs.lhs.kind == syntax.nkind.N_IDENT) { etname = lhs.str; etmod = lhs.lhs.str; }; }; }; if (etname.len > 0) { let en: *enumtype = enumlookupmod(c, etname, etmod); if (en != nil) { let v: u64; if (enummemberval(en, fld, &v)) { emitline("\tMOVQ\t$"); emitint(v: i64); emitline(", AX\n"); return; }; }; }; }; if (dotlhs != nil) { if (dotlhs.kind == syntax.nkind.N_IDENT) { let nm: str = dotlhs.str; let lc: *local = localfindnode(c, nm); if (lc != nil) { let tn: *syntax.node = lc.tnode; // Receiver is a NAMED alias chain — peel via aliaslookup // until tn exposes a non-N_TNAME kind (or a struct alias). // Without this, `type vs = *vt` leaves lkind == N_TNAME and // structlookupchain misses (vs is not a struct), so we fall // through to the SB-global fallback and emit a wrong // `MOVQ (SB), AX`. Mirror cstage type_chase_named // (cmd/w6c/cgen.c:144-155); LOOP, not single-peel — Phase-N // builds NAMED chains (project_tinfo_lossy_nominal). Stops // at struct aliases so the existing direct-struct arm below // stays byte-id with pre-fix #22 callers. #191. // // #223: the break is MODULE-AWARE. cstage type_chase_named // follows the resolved NAMED.under pointer (module-correct); // wwstage re-resolves by name (lossy), so a same-module // alias whose leaf collides with a FOREIGN struct of the // same name (io.stream = *vtable vs memio.stream struct) // would wrongly halt the peel at the foreign struct via // structlookup's any-module fallback → field load drops to a // bogus `MOVQ (SB)`. Sibling of #208 (lossy name-keyed // resolution leaking to a global leaf). Break ONLY on a // same-module struct (a genuine struct-value receiver); a // same-module alias keeps peeling; a foreign leaf (in // neither registry for c.curmod) falls back to the prior // any-module heuristic. #21/#224: the direct-struct arm // below no longer name-keys — it resolves field offsets off // the stamped struct tinfo (dotlhs.type_), closing the // cross-module same-leaf STRUCT mis-read there. This peel + // same-module break still scopes the *struct (N_TPTR) and // array arms, which remain structlookupchain-keyed (out of // the #21 scoped slice). for (tn != nil && tn.kind == syntax.nkind.N_TNAME) { if (structsamemod(c, tn.str) != nil) { break; }; let nx: *syntax.node = aliassamemod(c, tn.str); if (nx == nil) { if (structlookup(c, tn.str) != nil) { break; }; nx = aliaslookup(c, tn.str); if (nx == nil) { break; }; }; tn = nx; }; // A registered local with NO type node means // inference missed upstream — emitting nothing // leaves the consumer reading stale AX, and // falling through would emit a bogus // `MOVQ (SB)` (the #191 hazard). Loud. if (tn == nil) { let mdt: str = "cgdot: local receiver has no type node (rule 7)\n"; os.write(2, mdt.ptr, mdt.len: u64); os.exit(1); }; let lkind: syntax.nkind = tn.kind; // Pointer-to-struct: deref then field load. if (lkind == syntax.nkind.N_TPTR) { // #31: resolve the *struct field OFFSET + type off the // checker-STAMPED receiver tinfo (tichase(dotlhs.type_) // → .sub pointee), NOT the name-keyed // structlookupchain(inner). Under a cross-module same- // leaf collision inner is a bare leaf that mis-resolves // to a FOREIGN same-leaf struct → wrong offset/load-op // (the inferred-local READ row, ww=28). The stamped tinfo // carries the right layout; mirror cstage // type_chase_named(bu->sub)->fields (cgen.c:506-512) + // the #21 R0 template. Non-struct pointees (*str/*slice/ // *[N]T) are not TY_STRUCT → fall through to the pseudo- // field arms below (guard, never a bare-leaf fallback). // Read choke-point cgptrfieldloadtf shared with the R2 // global arm. Stage the *struct base in BX (not a load // target), then field-load. let sti: *syntax.tinfo = nil; if (dotlhs != nil) { sti = tichase(dotlhs.type_: *syntax.tinfo); }; if (sti != nil && sti.kind == syntax.tykind.TY_PTR) { sti = tichase(sti.sub); }; if (sti != nil) { if (sti.kind == syntax.tykind.TY_STRUCT) { let tf: *syntax.tfield = sti.fields; for (tf != nil) { if (syntax.streq(tf.name, fld)) { emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); cgptrfieldloadtf(c, tf); return; }; tf = tf.tnext; }; }; }; }; // Direct struct local: field load at off+foff. if (lkind == syntax.nkind.N_TNAME) { // #21/#224: resolve the field OFFSET + field type off // the checker-STAMPED struct tinfo // (tichase(dotlhs.type_).fields), NOT the name-keyed // structlookupchain(tn). On a cross-module same-leaf // collision lc.tnode is a bare leaf that structlookup // mis-resolves to a FOREIGN same-leaf struct → fields // read at the WRONG offsets / wrong load-op (the #224 // direct-struct arm flagged at the peel-loop comment // above). The stamped tinfo carries the right layout // regardless of leaf collision; mirrors cstage's // `t->fields` walk (cgen.c N_DOT, type-keyed) — align // ww UP. tfield {name, type_, offset} is the tinfo twin // of fieldinfo {fname, tnode, foff}; the dispatch keys // off syntax.typeis* on the field tinfo, byte-id with // the prior is*type(fi.tnode)=typeis*(fi.tnode.type_). let sbu: *syntax.tinfo = nil; if (dotlhs != nil) { sbu = tichase(dotlhs.type_: *syntax.tinfo); }; if (sbu != nil) { if (sbu.kind == syntax.tykind.TY_STRUCT) { let tf: *syntax.tfield = sbu.fields; for (tf != nil) { if (syntax.streq(tf.name, fld)) { let foff: i32 = tf.offset: i32; let ftraw: *syntax.tinfo = tf.type_; // tagged-union field: AX=tag, DX=word0, // CX=word1[, R8=word2]; slot = ti.size // (slotsize's TAGGED arm, cgenutil.ww:2680). if (syntax.typeistagged(ftraw)) { let ftc: *syntax.tinfo = tichase(ftraw); let tsz: i32 = 0; if (ftc != nil) { tsz = ftc.size: i32; }; cgloadtaggedfield(c, "BP", lc.off + foff, tsz, true); return; }; // str IS []u8 — 3-word {ptr,len,cap} into // (AX,BX,CX). Base is BP so order is harmless. if (syntax.typeisstr(ftraw) || syntax.typeisslice(ftraw)) { emitline("\tMOVQ\t"); emitoff((lc.off + foff): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\t"); emitoff((lc.off + foff + 8): i64); emitline("(BP), BX\n"); emitline("\tMOVQ\t"); emitoff((lc.off + foff + 16): i64); emitline("(BP), CX\n"); } else { if (syntax.typeisfloat(ftraw)) { // f64/f32 field: route through X0. let mov: str = "MOVSD"; if (syntax.typeisf32(ftraw)) { mov = "MOVSS"; }; emitline("\t"); emitline(mov); emitline("\t"); emitoff((lc.off + foff): i64); emitline("(BP), X0\n"); } else { let ftc: *syntax.tinfo = tichase(ftraw); let fsz: i32 = 0; if (ftc != nil) { fsz = ftc.size: i32; }; let op: str = loadopsz(syntax.typeissigned(ftraw), fsz); emitline("\t"); emitline(op); emitline("\t"); emitoff((lc.off + foff): i64); emitline("(BP), AX\n"); }; }; return; }; tf = tf.tnext; }; }; }; }; // Array pseudo-fields: `.ptr` is the array's // address (LEAQ); `.len` is the static element // count (immediate). if (lkind == syntax.nkind.N_TARRAY) { if (syntax.streq(fld, "ptr")) { emitline("\tLEAQ\t"); emitoff(lc.off: i64); emitline("(BP), AX\n"); return; }; if (syntax.streq(fld, "len")) { let lenn: *syntax.node = tn.rhs; let alen: i64 = 0i64; if (lenn != nil && lenn.kind == syntax.nkind.N_INTLIT) { alen = lenn.uval: i64; } else { // #56: def/const dim — resolve from the // stamped array tinfo (rule-13), the field- // read twin of the #21 cgslice fix. The dim // node is an N_IDENT(def), not N_INTLIT, so // the literal read above defaults 0; cstage // reads the resolved bu->alen. let abt: *syntax.tinfo = tichase(tn.type_: *syntax.tinfo); if (abt != nil && abt.kind == syntax.tykind.TY_ARRAY) { alen = abt.alen: 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: scalar in an // 8B slot, str in 24B — str IS []u8, #1/Phase 3). For a // str element, load (ptr, len, cap) into (AX, BX, CX), // the canonical slice-header ABI. No slice-element // sibling here, so the triple is hand-authored; base is // BP (frame, not a target reg) so ptr/len/cap order has // no clobber risk. if (lkind == syntax.nkind.N_TTUPLE) { let idx: i32 = fldnumidx(fld); if (idx >= 0) { let tp: *syntax.node = tn.list; let foff: i32 = 0; let i: i32 = 0; for (i < idx) { if (tp == nil) { i = idx; } else { // C-t0/#22: slot stride // (tupeslot accessor). foff += tupeslotn(tp.lhs); tp = tp.next; i += 1; }; }; if (tp != nil) { let tpt: *syntax.node = tp.lhs; // str IS []u8, and a slice is the same 24B // {ptr,len,cap} header — load all three words // into (AX, BX, CX). #28: pre-fix the gate was // str-only, so a SLICE tuple element fell to // the scalar tail below (one ptr word; len/cap // took stale registers). base is BP (frame), // so the triple order has no clobber risk. if (isstrtype(c, tpt) || isslicetype(c, tpt)) { 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"); emitline("\tMOVQ\t"); emitoff((lc.off + foff + 16): i64); emitline("(BP), CX\n"); return; }; // f64/f32 tuple field must ride X0 via // MOVSD/MOVSS; the integer load op left it // in AX (#103 FACE Z). Mirrors the float // local load above and cstage cgen.c:1462, // 1838 (the #96 pattern). if (isfloattype(c, tpt)) { let mov: str = "MOVSD"; if (isf32type(c, tpt)) { mov = "MOVSS"; }; emitline("\t"); emitline(mov); emitline("\t"); emitoff((lc.off + foff): i64); emitline("(BP), X0\n"); return; }; // #22a: tagged element — load the box // into the tagged value regs (AX=tag, // DX/CX/R8=payload), the cursor the // is/as spill + match read. Byte-id // twin of cstage's N_DOT TY_TUPLE // tagged arm. if (istaggedtype(c, tpt)) { let eslot: i32 = tupeslotn(tpt); // #37: a >32B box overruns the // 4-reg cursor — leave its // ADDRESS in AX (taggedmemread, // the sret-receive convention); // consumers copy from memory. // Replaces the #22b loud bound. // Mirrors cstage. if (eslot > TUPLE_GPCAP * 8) { emitline("\tLEAQ\t"); emitoff((lc.off + foff): i64); emitline("(BP), AX\n"); return; }; let k: i32 = 0; for (k < eslot / 8) { emitline("\tMOVQ\t"); emitoff((lc.off + foff + k * 8): i64); emitline("(BP), "); emitline(tupreg(k)); emitline("\n"); k += 1; }; return; }; // C-t0: load at the element's NATURAL // width (narrow MOVL/MOVSXD/... at the // slot base), not the 8B slot width — // byte-id twin of cstage's fldloadop // in the N_DOT TY_TUPLE arm. let nsz: i32 = 8; let tpti: *syntax.tinfo = tpt.type_: *syntax.tinfo; if (tpti != nil) { nsz = tpti.size: i32; }; let op: str = tnodeloadop(c, tpt, nsz); 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 (syntax.streq(fld, "ptr")) { delta = 0; }; if (syntax.streq(fld, "len")) { delta = 8; }; if (syntax.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 == syntax.nkind.N_TPTR) { let inner: *syntax.node = tn.lhs; let innerkind: syntax.nkind = syntax.nkind.N_NONE; if (inner != nil) { innerkind = inner.kind; }; let innerstr: bool = false; if (innerkind == syntax.nkind.N_TNAME) { if (syntax.streq(inner.str, "str")) { innerstr = true; }; }; if (innerkind == syntax.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; }; }; }; }; // #15 — module-GLOBAL ptr receiver `gp.f` (no local slot, so the // local arm above is skipped): load the pointer VALUE from name(SB) // into BX, then field load. Read twin of the #6 store fix; mirrors // cstage cgen.c N_DOT pointer-to-{struct,slice/str} SB base load. A // global VALUE struct/slice/array is served by the dedicated global // arms below, so only a *T receiver lands here. if (dotlhs != nil) { if (dotlhs.kind == syntax.nkind.N_IDENT) { // localfindnode==nil mirrors cstage's off==0 guard: a // LOCAL ptr (incl. one shadowing a global let) stays on // the BP-relative local arm above; only a true module // global lands here. if (localfindnode(c, dotlhs.str) == nil && isletvar(c, dotlhs.str)) { let gnm: str = dotlhs.str; let gtn: *syntax.node = letvartnode(c, gnm); // Peel a NAMED alias chain to expose N_TPTR, the // same module-aware peel as the local arm (#191/#223). for (gtn != nil && gtn.kind == syntax.nkind.N_TNAME) { if (structsamemod(c, gtn.str) != nil) { break; }; let nx: *syntax.node = aliassamemod(c, gtn.str); if (nx == nil) { if (structlookup(c, gtn.str) != nil) { break; }; nx = aliaslookup(c, gtn.str); if (nx == nil) { break; }; }; gtn = nx; }; if (gtn != nil) { if (gtn.kind == syntax.nkind.N_TPTR) { let inner: *syntax.node = gtn.lhs; // #31: stamped-tinfo *struct field resolution // (tichase(dotlhs.type_)->.sub), the read choke-point // twin of the R1 local arm -- see cgptrfieldloadtf. A // global *struct decl is qualified (let gp: *m1.pair) // -> non-reddenable; converted for close-by-construction // (byte-id with the name-keyed path). Non-struct // pointees fall through to the str/slice arm below. let sti: *syntax.tinfo = nil; if (dotlhs != nil) { sti = tichase(dotlhs.type_: *syntax.tinfo); }; if (sti != nil && sti.kind == syntax.tykind.TY_PTR) { sti = tichase(sti.sub); }; if (sti != nil) { if (sti.kind == syntax.tykind.TY_STRUCT) { let tf: *syntax.tfield = sti.fields; for (tf != nil) { if (syntax.streq(tf.name, fld)) { emitline("\tMOVQ\t"); emitsymname(c, gnm); emitline("(SB), BX\n"); cgptrfieldloadtf(c, tf); return; }; tf = tf.tnext; }; }; }; // Pointer to str/slice (`*[]u8`, `*str`): deref // name(SB), then load at delta within the header. let delta: i32 = -1; if (syntax.streq(fld, "ptr")) { delta = 0; }; if (syntax.streq(fld, "len")) { delta = 8; }; if (syntax.streq(fld, "cap")) { delta = 16; }; if (delta >= 0) { let innerkind: syntax.nkind = syntax.nkind.N_NONE; if (inner != nil) { innerkind = inner.kind; }; let innerstr: bool = false; if (innerkind == syntax.nkind.N_TNAME) { if (syntax.streq(inner.str, "str")) { innerstr = true; }; }; if (innerkind == syntax.nkind.N_TSLICE) { innerstr = true; }; if (innerstr) { emitline("\tMOVQ\t"); emitsymname(c, gnm); emitline("(SB), BX\n"); emitline("\tMOVQ\t"); emitdispreg(delta: i64, "BX"); emitline(", 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 == syntax.nkind.N_IDENT) { let drhs: *syntax.node = deflookuprhs(c, lhs.str); if (drhs != nil) { if (drhs.kind == syntax.nkind.N_STRLIT) { let bytes: str = drhs.str; if (syntax.streq(fld, "ptr")) { let lab: str = internstrlit(c, bytes); emitline("\tLEAQ\t"); emitbytes( lab.ptr, lab.len: u64); emitline("(SB), AX\n"); return; }; if (syntax.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 == syntax.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 (syntax.streq(fld, "ptr")) { delta = 0; }; if (syntax.streq(fld, "len")) { delta = 8; }; // str IS []u8: .cap is valid on a str global too, // not slice-only — mirrors cstage (#1/Phase 3, #11). if (syntax.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 [N]T global pseudo-fields (#7): `.len` is the static // element count (immediate from the array type node's length child); // `.ptr` is the array's base address (LEAQ name(SB)). Without this a // module-level array's `x.len` falls to the module-qualified SB // fallback below and mis-emits `MOVQ len(SB), AX` (linker: undefined // reference to len). Mirror of the local-array arm above and cstage // cg_base_cap's `aimm(bu->alen)` immediate (cgen.c:1692). if (lhs != nil) { if (lhs.kind == syntax.nkind.N_IDENT) { let gtn: *syntax.node = letvartnode(c, lhs.str); if (gtn != nil) { if (gtn.kind == syntax.nkind.N_TARRAY) { if (syntax.streq(fld, "ptr")) { emitline("\tLEAQ\t"); emitsymname(c, lhs.str); emitline("(SB), AX\n"); return; }; if (syntax.streq(fld, "len")) { let lenn: *syntax.node = gtn.rhs; let alen: i64 = 0i64; if (lenn != nil && lenn.kind == syntax.nkind.N_INTLIT) { alen = lenn.uval: i64; } else { // #56: def/const dim on a let-global array — // resolve from the stamped array tinfo // (rule-13), twin of the local arm above. let abt: *syntax.tinfo = tichase(gtn.type_: *syntax.tinfo); if (abt != nil && abt.kind == syntax.tykind.TY_ARRAY) { alen = abt.alen: i64; }; }; emitline("\tMOVQ\t$"); emitint(alen); emitline(", AX\n"); return; }; }; }; }; }; // GAP-A (#7 def-twin): `def NAME: [N]T = arrlit;` `.len` = static // elem count. The let-global arm above resolves via letvartnode // (c.lets only); a def lives in c.defs, misses it, and falls to the // SB fallback → MOVQ len(SB) (w6l: undefined 'len'). cstage cgen.c // emits MOVQ $alen here. defvartnode is the def-side mirror of // letvartnode (returns dtnode = the N_TARRAY whose .rhs length child // is #11-stamped). `.ptr` mirrors the let-global arm above: the // array's backing pointer ≡ &A[0] = LEAQ name(SB) (drew #13, // .ai/drew-gapa-ptr-ruling.md; GAP-A.ptr now fixed cstage-side too, // so both stages byte-id). `.cap` stays unmirrored — arrays have no // .cap (both checkers reject, task GAP-A.cap). if (lhs != nil) { if (lhs.kind == syntax.nkind.N_IDENT) { if (syntax.streq(fld, "ptr")) { let dtn: *syntax.node = defvartnode(c, lhs.str); if (dtn != nil) { if (dtn.kind == syntax.nkind.N_TARRAY) { emitline("\tLEAQ\t"); emitsymname(c, lhs.str); emitline("(SB), AX\n"); return; }; }; }; if (syntax.streq(fld, "len")) { let dtn: *syntax.node = defvartnode(c, lhs.str); if (dtn != nil) { if (dtn.kind == syntax.nkind.N_TARRAY) { let lenn: *syntax.node = dtn.rhs; let alen: i64 = 0i64; if (lenn != nil && lenn.kind == syntax.nkind.N_INTLIT) { alen = lenn.uval: i64; } else { // #56: def/const dim on a def array — // resolve from the stamped array tinfo // (rule-13), twin of the let-global arm above. let abt: *syntax.tinfo = tichase(dtn.type_: *syntax.tinfo); if (abt != nil && abt.kind == syntax.tykind.TY_ARRAY) { alen = abt.alen: i64; }; }; emitline("\tMOVQ\t$"); emitint(alen); emitline(", AX\n"); return; }; }; }; }; }; // Top-level TUPLE global positional read (C-t3, #48): `g.N` — // LEAQ name(SB) into CX, then load at the element's SLOT offset // (C-t0 layout), the element's natural width. Mirrors the local // N_TTUPLE arm above and cstage's N_DOT TY_TUPLE global base. // Pre-C-t3 the tuple global wasn't in collectlets at all (no // DATA) and the module-leaf fallback mis-emitted the field index // as a symbol (`MOVQ 0(SB), AX`). if (lhs != nil) { if (lhs.kind == syntax.nkind.N_IDENT) { let gtt: *syntax.node = letvartnode(c, lhs.str); for (gtt != nil && gtt.kind == syntax.nkind.N_TNAME) { gtt = aliaslookup(c, gtt.str); }; if (gtt != nil) { if (gtt.kind == syntax.nkind.N_TTUPLE) { let gidx: i32 = fldnumidx(fld); if (gidx >= 0) { let gtp: *syntax.node = gtt.list; let gfoff: i32 = 0; let gi: i32 = 0; for (gi < gidx) { if (gtp == nil) { gi = gidx; } else { // C-t0/#22: slot stride // (tupeslot accessor). gfoff += tupeslotn(gtp.lhs); gtp = gtp.next; gi += 1; }; }; if (gtp != nil) { let gpt: *syntax.node = gtp.lhs; emitline("\tLEAQ\t"); emitsymname(c, lhs.str); emitline("(SB), CX\n"); if (isstrtype(c, gpt)) { // CX (the base) is written LAST so // it survives the +0/+8 reads. emitline("\tMOVQ\t"); emitdispreg((gfoff + 0): i64, "CX"); emitline(", AX\n"); emitline("\tMOVQ\t"); emitdispreg((gfoff + 8): i64, "CX"); emitline(", BX\n"); emitline("\tMOVQ\t"); emitdispreg((gfoff + 16): i64, "CX"); emitline(", CX\n"); return; }; if (isfloattype(c, gpt)) { let mov: str = "MOVSD"; if (isf32type(c, gpt)) { mov = "MOVSS"; }; emitline("\t"); emitline(mov); emitline("\t"); emitdispreg(gfoff: i64, "CX"); emitline(", X0\n"); return; }; let gnsz: i32 = 8; let gpti: *syntax.tinfo = gpt.type_: *syntax.tinfo; if (gpti != nil) { gnsz = gpti.size: i32; }; let gop: str = tnodeloadop(c, gpt, gnsz); emitline("\t"); emitline(gop); emitline("\t"); emitdispreg(gfoff: 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. // #129 A.2: also handles struct-typed `def`s via defvarstructinfo; // emitstructdata gives them DATA storage at name(SB), and this // LEAQ-and-offset shape mirrors the let path. Pre-A.2 the def // fell through to the integer-let MOVQ catch-all (reading garbage // from the wrong offset). if (lhs != nil) { if (lhs.kind == syntax.nkind.N_IDENT) { // #31: resolve the global value-struct field OFFSET + type // off the checker-STAMPED ident tinfo (tichase(lhs.type_)), // NOT name-keyed letvarstructinfo/defvarstructinfo. A global // decl is always qualified (let g: m1.pair) -> non-reddenable; // converted for close-by-construction (byte-id). Covers both // let- and def-struct globals (both stamp lhs.type_). Mirrors // cstage type-keyed N_DOT global arm + the #21 R0 template. let sti: *syntax.tinfo = tichase(lhs.type_: *syntax.tinfo); if (sti != nil) { if (sti.kind == syntax.tykind.TY_STRUCT) { let tf: *syntax.tfield = sti.fields; for (tf != nil) { if (syntax.streq(tf.name, fld)) { let foff: i32 = tf.offset: i32; let ftraw: *syntax.tinfo = tf.type_; emitline("\tLEAQ\t"); emitsymname(c, lhs.str); emitline("(SB), CX\n"); if (syntax.typeistagged(ftraw)) { let ftc: *syntax.tinfo = tichase(ftraw); let tsz: i32 = 0; if (ftc != nil) { tsz = ftc.size: i32; }; cgloadtaggedfield(c, "CX", foff, tsz, true); return; }; // slice joins the str header load (#263: cstage // N_DOT chain arm keys TY_STR||TY_SLICE — the // same 24B {ptr,len,cap}); the scalar tail below // pulled ONLY .ptr, so len(g.buf) read garbage. if (syntax.typeisstr(ftraw) || syntax.typeisslice(ftraw)) { emitline("\tMOVQ\t"); emitdispreg(foff: i64, "CX"); emitline(", AX\n"); emitline("\tMOVQ\t"); emitdispreg((foff + 8): i64, "CX"); emitline(", BX\n"); emitline("\tMOVQ\t"); emitdispreg((foff + 16): i64, "CX"); emitline(", CX\n"); } else { if (syntax.typeisfloat(ftraw)) { let mov: str = "MOVSD"; if (syntax.typeisf32(ftraw)) { mov = "MOVSS"; }; emitline("\t"); emitline(mov); emitline("\t"); emitdispreg(foff: i64, "CX"); emitline(", X0\n"); } else { let ftc: *syntax.tinfo = tichase(ftraw); let fsz: i32 = 0; if (ftc != nil) { fsz = ftc.size: i32; }; let op: str = loadopsz(syntax.typeissigned(ftraw), fsz); emitline("\t"); emitline(op); emitline("\t"); emitdispreg(foff: i64, "CX"); emitline(", AX\n"); }; }; return; }; tf = tf.tnext; }; }; }; }; }; // `arr[i].field` — element-then-field through a `[N]*S` / `[N]S` // (and slice/`*[N]S`) base. Without this the cgen falls through // to the module-qualified SB fallback below and emits // `MOVQ (SB), AX` (linker: `undefined reference to `). // One branch covers both shapes: compute `&arr[i]` into BX, then // either deref (`*Struct` element) or move-to-AX (value `Struct` // element), so the leaf load is `(field.offset)(AX)` either way. // Bypasses cgindex deliberately — cgindex's final MOVQ would // truncate a value-struct element to 8 bytes. // C3 (task #8): keyed on the checker-stamped tinfo (lhs.type_ / // idxbase.type_), not tnode KINDs + structlookup-by-name — the // name-key dropped any base typed via an N_TNAME alias (`type // result = []capture`, F10) into the silent fallbacks below. // Mirrors cstage cgen.c case N_DOT N_INDEX-lhs arm 1:1; #209/#211 // name-keyed→tinfo-SSoT cluster. if (lhs != nil) { if (lhs.kind == syntax.nkind.N_INDEX) { let idxbase: *syntax.node = lhs.lhs; if (idxbase != nil) { if (idxbase.kind == syntax.nkind.N_IDENT) { let elemt: *syntax.tinfo = lhs.type_: *syntax.tinfo; let elemu: *syntax.tinfo = elemt; elemu = tichase(elemu); let st: *syntax.tinfo = nil; let viaptr: bool = false; if (elemu != nil) { if (elemu.kind == syntax.tykind.TY_PTR) { let pin: *syntax.tinfo = elemu.sub; pin = tichase(pin); if (pin != nil) { if (pin.kind == syntax.tykind.TY_STRUCT) { st = pin; viaptr = true; };}; } else { if (elemu.kind == syntax.tykind.TY_STRUCT) { st = elemu; };}; }; if (st != nil) { let fnd: *syntax.tfield = nil; let fwalk: *syntax.tfield = st.fields; for (fwalk != nil) { if (syntax.streq(fwalk.name, fld)) { fnd = fwalk; break; }; fwalk = fwalk.tnext; }; let bu: *syntax.tinfo = idxbase.type_: *syntax.tinfo; bu = tichase(bu); let baseisarray: bool = false; let baseok: bool = false; if (bu != nil) { if (bu.kind == syntax.tykind.TY_ARRAY) { baseisarray = true; baseok = true; }; if (bu.kind == syntax.tykind.TY_SLICE) { baseok = true; }; if (bu.kind == syntax.tykind.TY_PTR) { baseok = true; }; }; let lc: *local = localfindnode(c, idxbase.str); // #21 (READ twin of #11): a module-GLOBAL base makes // localfindnode return nil, so this field-offset-aware // branch was skipped and `g[i].field` fell through to // the module-qualified fallback below (garbage — no // main.g load at all). Classify via the let registry / // array-typed def registry (cstage let_islet || // def_isarraydef) and dispatch the base load by shape: // array -> LEAQ name(SB) (the symbol IS the storage), // slice/ptr -> MOVQ name(SB) (the symbol's first word // IS the .ptr). Mirrors cstage cgen.c's #21 arm. let isglobal: bool = false; if (lc == nil) { if (isletvar(c, idxbase.str)) { isglobal = true; } else { let dtn: *syntax.node = defvartnode(c, idxbase.str); if (dtn != nil) { if (dtn.kind == syntax.nkind.N_TARRAY) { isglobal = true; }; }; }; }; if (fnd != nil && baseok && (lc != nil || isglobal)) { let esz: i32 = elemt.size: i32; cgexpr(c, lhs.rhs); // idx → AX if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", CX\n"); emitline("\tIMULQ\tCX, AX\n"); }; if (isglobal) { if (baseisarray) { emitline("\tLEAQ\t"); emitsymname(c, idxbase.str); emitline("(SB), BX\n"); } else { emitline("\tMOVQ\t"); emitsymname(c, idxbase.str); emitline("(SB), BX\n"); }; } else { if (baseisarray) { emitline("\tLEAQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); } else { emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); }; }; emitline("\tADDQ\tAX, BX\n"); if (viaptr) { emitline("\tMOVQ\t(BX), AX\n"); } else { emitline("\tMOVQ\tBX, AX\n"); }; let foff: i64 = fnd.offset: i64; let ft: *syntax.tinfo = fnd.type_; let fu: *syntax.tinfo = ft; fu = tichase(fu); // #270-1a: an `[N]T`-typed field of an // array element (`a[i].m[j]`) — leave the // field's ADDRESS, a base for the outer // index, NEVER deref. AX holds &a[i]; the // field address is &a[i]+foff. The #135 // read-side for `d.m[i]`, applied to an // array-element base. Without this an array // field fell to the scalar load below and loaded // its first 8 bytes as a value → garbage // base → SEGFAULT in the outer index. if (fu != nil && fu.kind == syntax.tykind.TY_ARRAY) { if (foff != 0) { emitline("\tADDQ\t$"); emitint(foff); emitline(", AX\n"); }; return; }; if (fu != nil && (fu.kind == syntax.tykind.TY_STR || fu.kind == syntax.tykind.TY_SLICE)) { // str/slice: the 3-word {ptr,len,cap} // slice header (#1). AX holds the // element base, so load .ptr (which // targets AX) LAST. Matches the // caseB *struct slice arm and // cgslicehdr(D_AX). emitline("\tMOVQ\t"); emitdispreg(foff + 8, "AX"); emitline(", BX\n"); emitline("\tMOVQ\t"); emitdispreg(foff + 16, "AX"); emitline(", CX\n"); emitline("\tMOVQ\t"); emitdispreg(foff, "AX"); emitline(", AX\n"); return; }; if (syntax.typeisfloat(ft)) { let mov: str = "MOVSD"; if (syntax.typeisf32(ft)) { mov = "MOVSS"; }; emitline("\t"); emitline(mov); emitline("\t"); emitdispreg(foff, "AX"); emitline(", X0\n"); return; }; // #58: a TAGGED field of an indexed array // element (`xs[i].f`). AX holds &xs[i]; load // the box cursor (AX=tag, DX/CX/R8=payload) // mirroring taggedmemread's <=32B convention, // tag LAST (it clobbers the base AX). Without // this arm the field fell to the scalar load // below, reading only the tag word and leaving // the payload cursor (DX) stale (`xs[i].f as // T` read garbage; #38a INDEX-spine residual). // >32B box: a wide-box union (largest variant // >32B) IS constructible via a NARROW variant // (not unbuildable as earlier triage assumed; // #54/#23 fires only on STRUCT-LITERAL // payloads), but the mem-based read (LEAQ // foff(AX),AX) is not yet wired here — so this // arm LOUD-STOPS rather than silently reading a // truncated box (rule 7, the #41 untested-arm // trap), byte-id-neutral. Reachable + pinned // expect-loud (test/wcc/944 cfail rows). When // #114 wires it, that commit replaces this with // the LEAQ box-address emission + a >32B value // pin row. Mirrors cstage cgen.c. if (fu != nil && fu.kind == syntax.tykind.TY_TAGGED) { let bsz: i32 = fu.size: i32; if (bsz > TUPLE_GPCAP * 8) { let m58r: str = "#58: >32B tagged-field indexed read unreachable until #114\n"; os.write(2, m58r.ptr, m58r.len: u64); os.exit(1); }; if (bsz > 24) { emitline("\tMOVQ\t"); emitdispreg(foff + 24, "AX"); emitline(", R8\n"); }; if (bsz > 16) { emitline("\tMOVQ\t"); emitdispreg(foff + 16, "AX"); emitline(", CX\n"); }; if (bsz > 8) { emitline("\tMOVQ\t"); emitdispreg(foff + 8, "AX"); emitline(", DX\n"); }; emitline("\tMOVQ\t"); emitdispreg(foff, "AX"); emitline(", AX\n"); return; }; let fsz: i32 = 8; if (ft != nil) { fsz = ft.size: i32; }; let lop: str = loadopsz(syntax.typeissigned(ft), fsz); emitline("\t"); emitline(lop); emitline("\t"); emitdispreg(foff, "AX"); emitline(", AX\n"); return; }; }; };}; }; }; // #121 leg (a): `tbl[i].N` — a positional FIELD of an indexed // TUPLE element. The struct N_INDEX-lhs block above handles // struct / ptr-to-struct elements; a tuple element fell through // to the read-resolver and died LOUD. Resolve &tbl[i] via the // place-spine (cgplaceaddr, the #116 mechanism) into BX→AX, then // read the field at addr+foff reusing the per-element-kind arms // the local N_TTUPLE block wires: str-triple / float-X0 / fn-or- // scalar loadopsz. Narrow (rob Q3): tagged / nested-aggregate // fields stay LOUD. Mirrors cstage cgen.c leg-(a) arm 1:1. if (lhs != nil) { if (lhs.kind == syntax.nkind.N_INDEX) { let eu: *syntax.tinfo = tichase(lhs.type_: *syntax.tinfo); if (eu != nil) { if (eu.kind == syntax.tykind.TY_TUPLE) { let idx: i32 = fldnumidx(fld); if (idx >= 0) { // ww tuples store elements in .tupleelems // (ttupleelem chain), NOT .params (that is // fn-only). foff via tupeslot accumulation = // cstage tuple_eslot, byte-id. let tp: *syntax.ttupleelem = eu.tupleelems; let foff: i32 = 0; let i: i32 = 0; for (i < idx) { if (tp == nil) { i = idx; } else { foff += tupeslot(tp.type_); tp = tp.tnext; i += 1; }; }; if (tp != nil) { let ft: *syntax.tinfo = tp.type_; let fu: *syntax.tinfo = tichase(ft); if (fu != nil && (fu.kind == syntax.tykind.TY_TAGGED || fu.kind == syntax.tykind.TY_STRUCT || fu.kind == syntax.tykind.TY_TUPLE || fu.kind == syntax.tykind.TY_ARRAY)) { let mt: str = "#121: aggregate/tagged tuple-element field read off an indexed base unwired\n"; os.write(2, mt.ptr, mt.len: u64); os.exit(1); }; if (!cgplaceaddr(c, lhs, "BX")) { let mp: str = "#121: indexed tuple base not place-resolvable\n"; os.write(2, mp.ptr, mp.len: u64); os.exit(1); }; emitline("\tMOVQ\tBX, AX\n"); if (syntax.typeisfloat(ft)) { let mov: str = "MOVSD"; if (syntax.typeisf32(ft)) { mov = "MOVSS"; }; emitline("\t"); emitline(mov); emitline("\t"); emitdispreg(foff: i64, "AX"); emitline(", X0\n"); return; }; if (fu != nil && (fu.kind == syntax.tykind.TY_STR || fu.kind == syntax.tykind.TY_SLICE)) { emitline("\tMOVQ\t"); emitdispreg((foff + 8): i64, "AX"); emitline(", BX\n"); emitline("\tMOVQ\t"); emitdispreg((foff + 16): i64, "AX"); emitline(", CX\n"); emitline("\tMOVQ\t"); emitdispreg(foff: i64, "AX"); emitline(", AX\n"); return; }; let fsz: i32 = 8; if (ft != nil) { fsz = ft.size: i32; }; let lop: str = loadopsz(syntax.typeissigned(ft), fsz); emitline("\t"); emitline(lop); emitline("\t"); emitdispreg(foff: i64, "AX"); emitline(", AX\n"); return; }; }; };}; }; }; // 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` for the 8B case; // signed-narrow leaves route through LEAQ + localloadop so a // prior narrow deref-store doesn't leave stale upper bytes. Same // fallback the C cgen takes when bt is NULL/tyerr. if (lhs != nil) { if (lhs.kind == syntax.nkind.N_IDENT) { // `let p = mod.fn` — fn rvalue via N_DOT. Mirror of // cstage cgdot's TY_FN branch (mafn with module hint). // Without this the MOVQ leaf(SB) fallback below would // load 8 bytes of fn-prologue code into AX instead of // the fn address. // lhs.str is the explicit module hint so a same-leaf // def in another module (head of c.fnrets) can't shadow // the explicit qualifier (#17 N_DOT-arm omission audit). // Fn value vs data read: classified by the STAMPED // TY_FN type, not fnretlookupmod — its leaf fallback // matches a curmod fn against a foreign scalar def // (`mod.MSG` under a curmod `fn MSG`) and emits LEAQ // where cstage MOVQs the data. #55 cgen-side class. let du: *syntax.tinfo = tichase(n.type_: *syntax.tinfo); if (du != nil && du.kind == syntax.tykind.TY_FN) { emitline("\tLEAQ\t"); emitfnname(c, fld, usehint(c, lhs.str)); emitline("(SB), AX\n"); return; }; // `mod.MSG` where MSG is `def MSG: str = "..."` — // strlit-inline matches cstage Sdef walk #2 in // cmd/w6c/cgen.c N_DOT mod-qualified. Without this // the MOVQ leaf(SB) fallback emits a bogus ref // (`alpha.MSG(SB)`, never DATAW-defined). lhs.str is // the explicit module hint — a 3rd-module qualifier // `alpha.MSG` from gamma needs alpha (not c.curmod) // to beat a head-of-c.defs beta.MSG collision (#11). let drhs: *syntax.node = deflookuprhsmod(c, fld, usehint(c, lhs.str)); if (drhs != nil) { if (drhs.kind == syntax.nkind.N_STRLIT) { let bytes: str = drhs.str; let lab: str = internstrlit(c, bytes); emitline("\tLEAQ\t"); emitbytes( lab.ptr, lab.len: u64); emitline("(SB), AX\n"); emitline("\tMOVQ\t$"); emitint(bytes.len: i64); emitline(", BX\n"); return; }; }; let mqop: str = localloadop(c, letvartnode(c, fld)); // #229: thread the dotted module (lhs.str), not c.curmod // — the non-preferring emitsymname mis-mangled `aa.v` onto // a same-leaf global. The TY_FN branch above already // threads lhs.str via emitfnname. if (syntax.streq(mqop, "MOVQ")) { emitline("\tMOVQ\t"); emitfnname(c, fld, usehint(c, lhs.str)); emitline("(SB), AX\n"); } else { emitline("\tLEAQ\t"); emitfnname(c, fld, usehint(c, lhs.str)); emitline("(SB), CX\n"); emitline("\t"); emitline(mqop); emitline("\t(CX), AX\n"); }; return; }; }; // Chained N_DOT spine through value-struct fields (any depth). // Walks the spine to a root ident, summing field offsets, then // emits ONE load at base + total_off. Also handles a slice/str // pseudo-field leaf (`b.buf.len`): the walk lands on the slice/ // str header and slicedelta picks ptr/len/cap. Mirror of cstage // cgen.c's chained-DOT read branch. Without this, depth ≥ 3 // shapes (`v.a.a.a`) and `b.buf.len` fall through to the non- // ident-base pseudo branch below — which would cgexpr the inner // (loading only .ptr into AX) and shuffle stale BX into AX. // Placed BEFORE the .ptr/.len fast paths so the chain wins. if (lhs != nil) { if (lhs.kind == syntax.nkind.N_DOT) { let rootname: str = ""; let rootoff: i32 = 0; let totaloff: i32 = 0; let leaftype: *syntax.tinfo = nil; let slicedelta: i32 = -1; let isglobal: bool = false; let ptrroot: bool = false; let pok: bool = dotchainresolve(c, n, &rootname, &rootoff, &totaloff, &leaftype, &slicedelta, &isglobal, &ptrroot); if (pok) { // `*T` root: load the pointer slot once into CX, // then index every leaf at total_off off CX. Same // emit shape as the global path (LEAQ → CX) — only // the loader instruction differs. let viacx: bool = isglobal || ptrroot; if (slicedelta >= 0) { if (viacx) { emitchainbase(c, ptrroot, isglobal, rootoff, rootname); emitline("\tMOVQ\t"); emitdispreg((totaloff + slicedelta): i64, "CX"); emitline(", AX\n"); } else { emitline("\tMOVQ\t"); emitoff((rootoff + totaloff + slicedelta): i64); emitline("(BP), AX\n"); }; return; }; // tagged leaf (#38a): load the box into the tagged // cursor via cgloadtaggedfield (AX=tag, DX=word0, // R8=word2 before CX=word1 — CX may be the base; // >32B box leaves its ADDRESS in AX, the #37 // convention) — the single-dot tagged-field arm // verbatim. Pre-#38a the scalar loadopsz tail pulled // ONE word (the tag): is-tests passed by tag-luck // while as/match/let consumers read stale payload // registers (ken x5c: o.r.min as size added DX). if (syntax.typeistagged(leaftype)) { let tlu: *syntax.tinfo = leaftype; tlu = tichase(tlu); let ttsz: i32 = tlu.size: i32; if (viacx) { emitchainbase(c, ptrroot, isglobal, rootoff, rootname); cgloadtaggedfield(c, "CX", totaloff, ttsz, true); } else { cgloadtaggedfield(c, "BP", rootoff + totaloff, ttsz, true); }; return; }; if (syntax.typeisstr(leaftype) || syntax.typeisslice(leaftype)) { // str/slice leaf: load all three header words into // (AX=ptr, BX=len, CX=cap). str IS []u8 — the same 24B // {ptr,len,cap} header. #29: the str leaf used to load // only ptr+len here (cap dropped → a junk strlit before // the chain left CX stale); merged into the slice arm so // both load the full triple, both stages (#263). For the // viacx path (global or `*T` root) CX is the base; load // .cap LAST so the base survives the earlier reads. For // BP-rooted locals the registers do not alias so order is // free. if (viacx) { emitchainbase(c, ptrroot, isglobal, rootoff, rootname); emitline("\tMOVQ\t"); emitdispreg(totaloff: i64, "CX"); emitline(", AX\n"); emitline("\tMOVQ\t"); emitdispreg((totaloff + 8): i64, "CX"); emitline(", BX\n"); emitline("\tMOVQ\t"); emitdispreg((totaloff + 16): i64, "CX"); emitline(", CX\n"); } else { emitline("\tMOVQ\t"); emitoff((rootoff + totaloff): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\t"); emitoff((rootoff + totaloff + 8): i64); emitline("(BP), BX\n"); emitline("\tMOVQ\t"); emitoff((rootoff + totaloff + 16): i64); emitline("(BP), CX\n"); }; return; }; if (syntax.typeisfloat(leaftype)) { let mov: str = "MOVSD"; if (syntax.typeisf32(leaftype)) { mov = "MOVSS"; }; if (viacx) { emitchainbase(c, ptrroot, isglobal, rootoff, rootname); emitline("\t"); emitline(mov); emitline("\t"); emitdispreg(totaloff: i64, "CX"); emitline(", X0\n"); } else { emitline("\t"); emitline(mov); emitline("\t"); emitoff((rootoff + totaloff): i64); emitline("(BP), X0\n"); }; return; }; let lop: str = loadopsz(syntax.typeissigned(leaftype), leaftype.slotsize: i32); if (viacx) { emitchainbase(c, ptrroot, isglobal, rootoff, rootname); emitline("\t"); emitline(lop); emitline("\t"); emitdispreg(totaloff: i64, "CX"); emitline(", AX\n"); } else { emitline("\t"); emitline(lop); emitline("\t"); emitoff((rootoff + totaloff): i64); emitline("(BP), AX\n"); }; return; }; }; }; // Non-ident base pseudo-field: e.g. `"abc".ptr` / `"abc".len`. // A string literal is TY_UNTYPED_STR, so it misses the typed // slice/str gate above and lands here. Evaluate the str-producing // expression — that leaves (AX=ptr, BX=len). Then `.ptr` returns // AX as is; `.len` shuffles BX→AX. cstage cgen.c was aligned UP // to this shuffle in #14 (it had returned the ptr for `.len`). // C2 (F4): gated to a slice/str/untyped-str STAMPED base (or an // N_STRLIT, which ww types as `str` anyway) — pre-C2 this arm was // shape-blind, so a STRUCT field that merely shares a pseudo-field // NAME behind a non-ident spine took the offset-blind cgexpr path // while cstage (type-gated) routes it to the read-resolver. { let pbu: *syntax.tinfo = nil; if (lhs != nil) { pbu = lhs.type_: *syntax.tinfo; }; pbu = tichase(pbu); let pbok: bool = false; if (pbu != nil) { if (pbu.kind == syntax.tykind.TY_SLICE || pbu.kind == syntax.tykind.TY_STR || pbu.kind == syntax.tykind.TY_UNTYPED_STR) { pbok = true; }; }; if (lhs != nil) { if (lhs.kind == syntax.nkind.N_STRLIT) { pbok = true; }; }; if (pbok) { if (syntax.streq(fld, "ptr")) { cgexpr(c, lhs); return; }; if (syntax.streq(fld, "len")) { cgexpr(c, lhs); emitline("\tMOVQ\tBX, AX\n"); return; }; // .cap on a non-ident base (indexed element `t[i].cap`, // call, dot-slice): cgexpr leaves the full {ptr,len,cap} // header via cgslicehdr — shuffle CX→AX. The shuffle // fires ONLY for a TYPED slice/str base (kind TY_SLICE/ // TY_STR after NAMED-chase) that is NOT a bare string // literal: N_STRLIT's cgexpr loads only AX=ptr/BX=len // (cgen.ww), never a CX cap, so `"abc".cap` must return // AX unshuffled. cstage reaches that outcome by typing // N_STRLIT as untyped_str (cgen.c catch-all, no cap // shuffle); the wwstage checker types N_STRLIT as `str` // instead (check.ww:2322 vs cstage check.c:1079 — // divergence filed separately), so the TY_STR kind-gate // alone would wrongly fire. The N_STRLIT exclusion keeps // this byte-identical with cstage. #13 read-fix, sibling // of the #20 store. if (syntax.streq(fld, "cap")) { cgexpr(c, lhs); if (lhs != nil && lhs.kind != syntax.nkind.N_STRLIT) { if (pbu != nil && (pbu.kind == syntax.tykind.TY_SLICE || pbu.kind == syntax.tykind.TY_STR)) { emitline("\tMOVQ\tCX, 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 == syntax.nkind.N_DOT) { // #70 (#12): inner-struct layout via the stamped lhs.type_ // (peel *→struct) + tinfo.fields, replacing dotinnerstructptr's // structinfo walk. Gate is strict-equal to the deleted helper: // fire only when the chain root is a LOCAL ident AND every dot // in the chain resolves through a *struct (dotinnerstructptr // recursed per level on a *struct field and bailed on a by- // value-struct intermediate). Reproducing that exactly avoids // an untested widening past cstage; a deliberate widen, if ever // wanted, is a future task with its own probe. Global-root // chains stay in their pre-existing shared base-eval breakage // (filed #27), untouched here. let croot: *syntax.node = lhs; let allptr: bool = true; for (croot != nil && croot.kind == syntax.nkind.N_DOT) { let ct: *syntax.tinfo = croot.type_: *syntax.tinfo; ct = tichase(ct); let okp: bool = false; if (ct != nil) { if (ct.kind == syntax.tykind.TY_PTR) { let cs: *syntax.tinfo = ct.sub; cs = tichase(cs); if (cs != nil) { if (cs.kind == syntax.tykind.TY_STRUCT) { okp = true; }; }; }; }; if (!okp) { allptr = false; }; croot = croot.lhs; }; let it: *syntax.tinfo = nil; if (allptr && croot != nil && croot.kind == syntax.nkind.N_IDENT && localfindnode(c, croot.str) != nil) { it = lhs.type_: *syntax.tinfo; }; it = tichase(it); if (it != nil) { if (it.kind == syntax.tykind.TY_PTR) { let st: *syntax.tinfo = it.sub; st = tichase(st); if (st != nil) { if (st.kind == syntax.tykind.TY_STRUCT) { let tf: *syntax.tfield = st.fields; for (tf != nil) { if (syntax.streq(tf.name, fld)) { let ft: *syntax.tinfo = tf.type_; cgexpr(c, lhs); // AX = ptr to inner struct // tagged leaf (#38a): AX holds the // *struct base and the tagged cursor // targets AX (tag) — stage the base in // BX, then cgloadtaggedfield (cstage // chained-*struct twin; ken b8: the // scalar tail read stale DX as payload). if (syntax.typeistagged(ft)) { let plu: *syntax.tinfo = ft; plu = tichase(plu); emitline("\tMOVQ\tAX, BX\n"); cgloadtaggedfield(c, "BX", tf.offset: i32, plu.size: i32, true); return; }; // str IS []u8 — same 3-word {ptr,len,cap} // as a slice field: load (ptr, len, cap) // into (AX, BX, CX). AX is the *struct // base, so load .ptr (which targets // AX) LAST. str folds onto the slice // arm (#1/Phase 3 collapse; cite cstage // cgen.c N_DOT chained *struct caseB). if (syntax.typeisstr(ft) || syntax.typeisslice(ft)) { emitline("\tMOVQ\t"); emitdispreg((tf.offset + 8u64): i64, "AX"); emitline(", BX\n"); emitline("\tMOVQ\t"); emitdispreg((tf.offset + 16u64): i64, "AX"); emitline(", CX\n"); emitline("\tMOVQ\t"); emitdispreg(tf.offset: i64, "AX"); emitline(", AX\n"); return; }; // f64/f32 chained field: route through X0. if (syntax.typeisfloat(ft)) { let mov: str = "MOVSD"; if (syntax.typeisf32(ft)) { mov = "MOVSS"; }; emitline("\t"); emitline(mov); emitline("\t"); emitdispreg(tf.offset: i64, "AX"); emitline(", X0\n"); return; }; let lop: str = loadopsz(syntax.typeissigned(ft), ft.slotsize: i32); emitline("\t"); emitline(lop); emitline("\t"); emitdispreg(tf.offset: i64, "AX"); emitline(", AX\n"); return; }; tf = tf.tnext; }; }; }; }; }; }; }; // 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`. // Kept as a fallback below the generalized walker above (placed // earlier in cgdot) to preserve byte-identical output on shapes // it already handles. if (lhs != nil) { if (lhs.kind == syntax.nkind.N_DOT) { let inner: *syntax.node = lhs.lhs; let innerfld: str = lhs.str; if (inner != nil) { if (inner.kind == syntax.nkind.N_IDENT) { let lc: *local = localfindnode(c, inner.str); if (lc != nil) { if (lc.tnode != nil) { let tn: *syntax.node = lc.tnode; let lkind: syntax.nkind = tn.kind; let outname: str; outname.ptr = nil; outname.len = 0; let isptr: bool = false; if (lkind == syntax.nkind.N_TNAME) { outname = tn.str; }; if (lkind == syntax.nkind.N_TPTR) { let pe: *syntax.node = tn.lhs; if (pe != nil) { if (pe.kind == syntax.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 (syntax.streq(ofi.fname, innerfld)) { let oft: *syntax.node = ofi.tnode; if (oft != nil) { if (oft.kind == syntax.nkind.N_TNAME) { if (aliasprimsize(c, oft.str) == 0) { let isi: *structinfo = structlookup(c, oft.str); if (isi != nil) { let ffi: *fieldinfo = isi.fields; for (ffi != nil) { if (syntax.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(c, 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. C2 (F4): gated to UNTYPED // chains only — pre-C2 it swallowed every unmatched dot-over-dot // chain, turning a TYPED depth-2 read behind an index/deref spine // into a silent global read of a colliding leaf symbol. if (lhs != nil) { if (lhs.kind == syntax.nkind.N_DOT) { let sbt: *syntax.tinfo = lhs.type_: *syntax.tinfo; if (sbt == nil || sbt.kind == syntax.tykind.TY_ERR) { emitline("\tMOVQ\t"); emitsymname(c, fld); emitline("(SB), AX\n"); return; }; }; }; // cstage reads ptr-chained fields (`a.p.f`, any root) in its // chained-*struct arm; wwstage's #70 mirror above is gated // root-local — the uncovered remainder (global / indexed roots) // must not take the resolver (its sequence differs from cstage's // arm → cs≠ww). Loud; the wwstage alignment is filed as task // #37. if (lhs != nil) { if (lhs.kind == syntax.nkind.N_DOT) { let pgu: *syntax.tinfo = lhs.type_: *syntax.tinfo; pgu = tichase(pgu); if (pgu != nil) { if (pgu.kind == syntax.tykind.TY_PTR) { let mp: str = "cgdot: ptr-chained field read unwired in wwstage (task #37)\n"; os.write(2, mp.ptr, mp.len: u64); os.exit(1); }; }; }; }; // C2 read-resolver (F4 + FA3): a TYPED N_DOT read no enumerated // arm matched — depth-2+ chains and slice/str/scalar fields behind // index/deref spines. Address via cgplaceaddr (the C1 resolver), // leaf load emitted here by kind. Leaf kinds with no canonical // register convention in expr position stay LOUD; any shape the // resolver can't address dies LOUD (rule 7) — the pre-C2 tail // silently emitted NOTHING. Mirror of cstage cgen.c case N_DOT // read-resolver tail. { let rdt: *syntax.tinfo = n.type_: *syntax.tinfo; let rdu: *syntax.tinfo = rdt; rdu = tichase(rdu); if (rdu != nil) { if (rdu.kind == syntax.tykind.TY_TAGGED) { let mt: str = "read-resolver: tagged field read not wired (rule-7)\n"; os.write(2, mt.ptr, mt.len: u64); os.exit(1); }; // LET-position aggregate leaves route through cglet's // resolver copy (C4, task #7) before cgexpr ever sees // them; this loud guards the remaining non-let expr // positions (no register convention for a >8B leaf), // symmetric with cstage's read-resolver tail. if (rdu.kind == syntax.tykind.TY_STRUCT || rdu.kind == syntax.tykind.TY_TUPLE) { let ma: str = "read-resolver: aggregate field read not wired (rule-7)\n"; os.write(2, ma.ptr, ma.len: u64); os.exit(1); }; }; if (!cgplaceaddr(c, n, "BX")) { let mu: str = "unsupported field-read shape\n"; os.write(2, mu.ptr, mu.len: u64); os.exit(1); }; if (syntax.typeisfloat(rdt)) { let mov: str = "MOVSD"; if (syntax.typeisf32(rdt)) { mov = "MOVSS"; }; emitline("\t"); emitline(mov); emitline("\t(BX), X0\n"); return; }; if (rdu != nil && rdu.kind == syntax.tykind.TY_ARRAY) { // `[N]T` leaf: leave the field ADDRESS — a base for // an outer index, never a value (#270-1a semantics). emitline("\tMOVQ\tBX, AX\n"); return; }; if (rdu != nil && (rdu.kind == syntax.tykind.TY_STR || rdu.kind == syntax.tykind.TY_SLICE)) { // str IS []u8 — 3-word {ptr,len,cap} into (AX, BX, // CX). BX is the place base, so load .len (which // targets BX) LAST. emitline("\tMOVQ\t(BX), AX\n"); emitline("\tMOVQ\t16(BX), CX\n"); emitline("\tMOVQ\t8(BX), BX\n"); return; }; let lop: str = "MOVQ"; if (rdt != nil) { lop = loadopsz(syntax.typeissigned(rdt), rdt.slotsize: i32); }; emitline("\t"); emitline(lop); emitline("\t(BX), AX\n"); return; }; }; fn cgun(c: *cgen, n: *syntax.node) void = { let fk: i32 = 0; if (n.lhs != nil) { let lt: *syntax.tinfo = n.lhs.type_: *syntax.tinfo; if (syntax.typeisf32(lt)) { fk = 1; } else { if (syntax.typeisfloat(lt)) { fk = 2; }; }; }; if (n.op == syntax.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 == syntax.tkind.TK_AMP) { let opnd: *syntax.node = n.lhs; if (opnd != nil) { if (opnd.kind == syntax.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; }; // #180: address-of a top-level fn name. Twin of // the N_IDENT value-of-fn read-arm in cgident // (LEAQ + emitfnname(c, nm, c.curmod)). Previously // fell through silently — the AX-store at the // assign site picked up whatever AX held. // Gate on the stamped TY_FN (cstage cgen.c amp // arm parity), not the name table — the leaf // fallback matches a foreign same-leaf fn // (#55 cgen-side class). let ou: *syntax.tinfo = tichase(opnd.type_: *syntax.tinfo); if (ou != nil && ou.kind == syntax.tykind.TY_FN) { emitline("\tLEAQ\t"); emitfnname(c, nm, c.curmod); emitline("(SB), AX\n"); return; }; if (isletvar(c, nm)) { emitline("\tLEAQ\t"); emitsymname(c, nm); emitline("(SB), AX\n"); return; }; // #149/#147: address-of a top-level def with DATA // storage. emitdefs / emitstructdata / emitarraydata // all emit to emitsymname(name), so the address is // the same LEAQ name(SB) as a let. Address-of twin of // A.2/A.3's LOAD-side widening. if (defisaddressable(c, opnd)) { emitline("\tLEAQ\t"); emitsymname(c, nm); emitline("(SB), AX\n"); return; }; // rule-7: the name IS a def but has no DATA symbol // (str def inlined, or computed-rhs float like // `def NAN = 0.0/0.0`). Loud, not a wild deref. if (deflookup(c, nm)) { let m1: str = "ww: cannot take address of non-addressable def '"; os.write(2, m1.ptr, m1.len: u64); os.write(2, nm.ptr, nm.len: u64); let m2: str = "': no DATA symbol (str/computed-rhs def; #149/#147)\n"; os.write(2, m2.ptr, m2.len: u64); os.exit(1); }; return; }; // Address-of through a DOT chain. Mirror of cstage // cgen.c TK_AMP N_DOT branch. Three shapes converge // here, all returning an 8B address (no fldloadop — // just LEAQ / MOVQ+LEAQ). // // 1. Value-struct fields, any depth (`&o.f`, // `&o.i.a`, `&o.a.b.c`) and slice/str pseudo-field // tail (`&s.len`, `&b.buf.len`): the chained // (depth ≥ 2) case reuses dotchainresolve; the // single-DOT case is handled below by inspecting // the IDENT base's tnode. Byte-identical to the // cstage spine walker for both depths. // 2. Pointer-field (`&p.f` where p:*T): single-DOT // only; spine walker aborts on the *T base. Load // p into AX, then LEAQ field_off(AX), AX. Mirror // of the read at cgdot 1144. if (opnd.kind == syntax.nkind.N_DOT) { // Shape 1 chained: depth-≥2 via dotchainresolve. // `opnd.lhs.kind == N_DOT` gates the helper at // nsteps ≥ 2 (matches the read path's gate). if (opnd.lhs != nil) { if (opnd.lhs.kind == syntax.nkind.N_DOT) { let rootname: str = ""; let rootoff: i32 = 0; let totaloff: i32 = 0; let leaftype: *syntax.tinfo = nil; let slicedelta: i32 = -1; let isglobal: bool = false; let ptrroot: bool = false; let pok: bool = dotchainresolve(c, opnd, &rootname, &rootoff, &totaloff, &leaftype, &slicedelta, &isglobal, &ptrroot); // `&` through a `*T`-rooted chain is a // separate shape (would need MOVQ + LEAQ // disp(CX), AX). Not exercised by current // callers — skip and fall through. if (ptrroot) { pok = false; }; if (pok) { let extra: i32 = 0; if (slicedelta >= 0) { extra = slicedelta; }; if (isglobal) { emitline("\tLEAQ\t"); emitsymname(c, rootname); emitline("(SB), CX\n"); emitline("\tLEAQ\t"); emitdispreg((totaloff + extra): i64, "CX"); emitline(", AX\n"); } else { emitline("\tLEAQ\t"); emitoff((rootoff + totaloff + extra): i64); emitline("(BP), AX\n"); }; return; }; }; }; // Shape 1/2 single-DOT on an IDENT base. Inspect // the base's tnode to pick value-struct vs slice/ // str pseudo vs pointer-field. if (opnd.lhs != nil) { if (opnd.lhs.kind == syntax.nkind.N_IDENT) { let basenm: str = opnd.lhs.str; let fld: str = opnd.str; let lc: *local = localfindnode(c, basenm); if (lc != nil) { let tn: *syntax.node = lc.tnode; let lkind: syntax.nkind = syntax.nkind.N_NONE; if (tn != nil) { lkind = tn.kind; }; // Pointer-field: &p.f where p:*T. // #102 (ken B6-c3 re-attribution): an // alias-NAMED pointee misses the bare // name-keyed lookup, so &p.f fell to the // generic cgplaceaddr route — runtime- // correct but byte-divergent from the // dedicated shape cs pins post-B6-c3. // structlookupchain (#22) chases the alias // chain; plain rows short-circuit at its // structlookup head, byte-id by // construction. if (lkind == syntax.nkind.N_TPTR) { // #31: &p.f offset off the stamped *struct tinfo // (tichase(opnd.lhs.type_)->.sub), NOT structlookupchain(inner). // Twin of the R1 read arm (inferred-local ADDR row). Non-struct // pointees fall through to the slice/str pseudo arm below. let sti: *syntax.tinfo = nil; if (opnd.lhs != nil) { sti = tichase(opnd.lhs.type_: *syntax.tinfo); }; if (sti != nil && sti.kind == syntax.tykind.TY_PTR) { sti = tichase(sti.sub); }; if (sti != nil) { if (sti.kind == syntax.tykind.TY_STRUCT) { let tf: *syntax.tfield = sti.fields; for (tf != nil) { if (syntax.streq(tf.name, fld)) { let foff: i32 = tf.offset: i32; emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), AX\n"); emitline("\tLEAQ\t"); emitdispreg(foff: i64, "AX"); emitline(", AX\n"); return; }; tf = tf.tnext; }; }; }; }; // Value-struct local: &o.f. // #102 review-found sibling: same // alias-blind miss one leg below the // &p.f gate — an alias-NAMED value // struct fell to the generic route. // Same chase, same construction. if (lkind == syntax.nkind.N_TNAME) { // #31: &o.f offset off the stamped value-struct tinfo // (tichase(opnd.lhs.type_)), NOT structlookupchain(tn). Non- // struct (str alias) falls through to the slice/str arm below. let sti: *syntax.tinfo = nil; if (opnd.lhs != nil) { sti = tichase(opnd.lhs.type_: *syntax.tinfo); }; if (sti != nil) { if (sti.kind == syntax.tykind.TY_STRUCT) { let tf: *syntax.tfield = sti.fields; for (tf != nil) { if (syntax.streq(tf.name, fld)) { let foff: i32 = tf.offset: i32; emitline("\tLEAQ\t"); emitoff((lc.off + foff): i64); emitline("(BP), AX\n"); return; }; tf = tf.tnext; }; }; }; }; // Slice/str pseudo-field on a local: // &s.ptr / &s.len / &s.cap. Delta is // 0/8/16 — matches the spine walker. let delta: i32 = -1; if (syntax.streq(fld, "ptr")) { delta = 0; }; if (syntax.streq(fld, "len")) { delta = 8; }; if (syntax.streq(fld, "cap")) { delta = 16; }; if (delta >= 0) { let isslor: bool = false; if (lkind == syntax.nkind.N_TSLICE) { isslor = true; }; if (lkind == syntax.nkind.N_TNAME) { if (syntax.streq(tn.str, "str")) { isslor = true; }; }; if (isslor) { emitline("\tLEAQ\t"); emitoff((lc.off + delta): i64); emitline("(BP), AX\n"); return; }; }; }; // Global root: top-level let, either a // struct or a slice/str. if (isletvar(c, basenm)) { // #31: global &g.f offset off the stamped value-struct tinfo // (tichase(opnd.lhs.type_)), NOT name-keyed letvarstructinfo. // Global decls are qualified -> non-reddenable; converted for // close-by-construction (byte-id). let gsti: *syntax.tinfo = nil; if (opnd.lhs != nil) { gsti = tichase(opnd.lhs.type_: *syntax.tinfo); }; if (gsti != nil) { if (gsti.kind == syntax.tykind.TY_STRUCT) { let tf: *syntax.tfield = gsti.fields; for (tf != nil) { if (syntax.streq(tf.name, fld)) { let foff: i32 = tf.offset: i32; emitline("\tLEAQ\t"); emitsymname(c, basenm); emitline("(SB), CX\n"); emitline("\tLEAQ\t"); emitdispreg(foff: i64, "CX"); emitline(", AX\n"); return; }; tf = tf.tnext; }; }; }; let isstr: bool = letvarisstr(c, basenm); let issl: bool = letvarisslice(c, basenm); if (isstr || issl) { let gdelta: i32 = -1; if (syntax.streq(fld, "ptr")) { gdelta = 0; }; if (syntax.streq(fld, "len")) { gdelta = 8; }; // str IS []u8: &str.cap is valid too, not slice-only // — mirrors cstage (#1/Phase 3, #11). if (syntax.streq(fld, "cap")) { gdelta = 16; }; if (gdelta >= 0) { emitline("\tLEAQ\t"); emitsymname(c, basenm); emitline("(SB), CX\n"); emitline("\tLEAQ\t"); emitdispreg(gdelta: i64, "CX"); emitline(", AX\n"); return; }; }; }; }; }; // #149 Shape 2: `&mod.G` module-qualified address-of // of an exported global (let or def). The base is an // N_IDENT that's neither a local nor a global let, so // it's an SK_USE module qualifier; LEAQ the leaf // symbol. Kind-agnostic (covers cross-module &let / // &def / &scalar) — the address-of twin of the value- // read mod-qual path (cgenexpr.ww). A fn leaf resolves // via emitfnname (fn address), mirroring that read // path's TY_FN branch. if (opnd.lhs != nil) { if (opnd.lhs.kind == syntax.nkind.N_IDENT) { let basenm: str = opnd.lhs.str; if (localfindnode(c, basenm) == nil) { // A def base (`&Pdef.field`) is NOT a module // qualifier: cstage's Shape-2 gate (base // type_ == NULL/ty_err) excludes it because // the checker types a def-struct/def-array // base, but the ww gate (not-local && not-let) // does not. Without this guard a def base would // mis-LEAQ the field leaf (e.g. `y(SB)`) while // cstage silent-drops, breaking cs==ww (rule // 10). Excluding defs restores byte-id; the // `&def.field` silent-drop itself is a separate // pre-#149 gap (file as #150-family). if (!isletvar(c, basenm) && !deflookup(c, basenm)) { let fld: str = opnd.str; let frt: *syntax.node = fnretlookupmod(c, fld, usehint(c, basenm)); if (frt != nil) { emitline("\tLEAQ\t"); emitfnname(c, fld, usehint(c, basenm)); emitline("(SB), AX\n"); return; }; // #229: dotted-module value mangle // (basenm), twin of the read — so // &aa.v takes aa's global, not a // same-leaf collision. emitline("\tLEAQ\t"); emitfnname(c, fld, usehint(c, basenm)); emitline("(SB), AX\n"); return; }; }; }; }; // C2 (F4 family, reviewer-A route): address-of // through an indexed/deref dot spine — the // arms above root only at idents. Route the // place address through cgplaceaddr (read-twin // in cgdot). Any remaining shape dies LOUD: // the pre-C2 silent drop left stale AX as the // "address" — a gate-blind SEGFAULT at the // deref. Mirror of cstage TK_AMP tail. if (cgplaceaddr(c, opnd, "BX")) { emitline("\tMOVQ\tBX, AX\n"); return; }; let mam: str = "unsupported address-of shape\n"; os.write(2, mam.ptr, mam.len: u64); os.exit(1); }; if (opnd.kind == syntax.nkind.N_INDEX) { // &base[i] = base + i*esz, no dereference. let base: *syntax.node = opnd.lhs; let idx: *syntax.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 == syntax.nkind.N_IDENT) { baselocal = localfindnode(c, base.str); if (baselocal != nil) { esz = elemsizeofc(c, baselocal.tnode); let tn: *syntax.node = baselocal.tnode; if (tn != nil) { if (tn.kind == syntax.nkind.N_TARRAY) { isarr = true; }; }; } else { // #11: addr-of twin of the #10 cgindex read // fix. Dispatch esz + base load off the // global's RESOLVED type, NOT an N_TARRAY/ // N_TPTR kind whitelist — a global str (tnode // N_TNAME) / slice (N_TSLICE) matched NEITHER // old arm, so esz stayed at the default 8 and // the base fell to the complex-base fallback, // yielding a wide-stride &s[i]. cstage's // TK_AMP N_INDEX (cmd/w6c/cgen.c) is uniform: // esz=bu->sub->size, base is_arr?LEAQ:MOVQ // name(SB) (a str/slice's .ptr IS the symbol's // first word). Align UP, mirroring cgindex. let tn: *syntax.node = letvartnode(c, base.str); // #94: a def-array base resolves via // defvartnode (the def-side sister), the same // fallback cgindex's read-side already takes // (cgenexpr.ww:1762). Without it &D[i] over a // def array fell to the complex-base value-load // below (MOVQ name(SB) = D[0] not the address), // divergent from cstage's zero-base SEGV — both // wild. The N_TARRAY tnode classifies isglobalarr // → LEAQ name(SB), the cstage-identical base. if (tn == nil) { tn = defvartnode(c, base.str); }; if (tn != nil) { globalname = base.str; esz = elemsizeofc(c, tn); if (tn.kind == syntax.nkind.N_TARRAY) { isglobalarr = true; } else { isglobalptr = true; }; }; }; // #82: the tnode-kind classify above misses an // alias-typed base (N_TNAME) — base materialized // as MOVQ (element-0 VALUE) instead of LEAQ, a // wild pointer. Re-key arrayness off the chased // checker-stamped base type, the cgindex #60 // idiom (cgenexpr.ww:1800-1820); cstage already // classifies off type_chase_named uniformly // (cmd/w6c/cgen.c:4172-4188). TY_NAMED gate // keeps non-alias rows byte-id by construction. let bt82: *syntax.tinfo = base.type_: *syntax.tinfo; let basealias82: bool = false; if (bt82 != nil) { if (bt82.kind == syntax.tykind.TY_NAMED) { basealias82 = true; }; }; if (basealias82) { let bu82: *syntax.tinfo = tichase(bt82); if (bu82 != nil) { if (baselocal != nil) { isarr = bu82.kind == syntax.tykind.TY_ARRAY; }; if (isglobalarr || isglobalptr) { isglobalarr = bu82.kind == syntax.tykind.TY_ARRAY; isglobalptr = !isglobalarr; }; }; }; } else { if (base.kind == syntax.nkind.N_DOT || (base.kind == syntax.nkind.N_UN && base.op == syntax.tkind.TK_STAR)) { // `&p.ptr[i]`: stride is the checker-stamped // element tinfo's natural size, mirroring // cgindex's N_DOT arm so &p.ptr[i] and // p.ptr[i] agree. cstage idx_eff(base->type) // ->sub->size (cmd/w6c/cgen.c:3517-18). #72. // N_UN deref base (`&(*p)[i]`, #61 C): same // stamped source; cstage reads base->type // uniformly. let dt: *syntax.tinfo = opnd.type_: *syntax.tinfo; if (dt != nil) { esz = dt.size: i32; }; };}; }; 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, restore idx into BX. // Mirrors cstage's lean three-line shape // (cmd/w6c/cgen.c TK_AMP N_INDEX complex // base 2104-2107); the prior MOVQ AX, BX // + POPQ AX scratch shuffle was rule-10 // verbose-defensive on the wwstage side // with no semantic asymmetry (task #21). // #252: an N_DOT `[N]T`-field base needs the // field ADDRESS (dotbaseaddr LEAQ), not the // auto-deref VALUE load cgexpr emits. Sibling // of the #135 read-side wiring. emitline("\tPUSHQ\tAX\n"); if (!dotbaseaddr(c, base, "AX")) { cgexpr(c, base); }; emitline("\tPOPQ\tBX\n"); };};}; emitline("\tADDQ\tBX, AX\n"); return; }; // C2: deref-rooted (`&(*p)`) and other non-ident // operands — resolver-or-loud, the same tail as the // N_DOT arm above (cstage has ONE shared tail for // both). if (cgplaceaddr(c, opnd, "BX")) { emitline("\tMOVQ\tBX, AX\n"); return; }; let mam2: str = "unsupported address-of shape\n"; os.write(2, mam2.ptr, mam2.len: u64); os.exit(1); }; return; }; cgexpr(c, n.lhs); if (n.op == syntax.tkind.TK_MINUS) { emitline("\tNEGQ\tAX\n"); return; }; if (n.op == syntax.tkind.TK_TILDE) { emitline("\tNOTQ\tAX\n"); // NOTQ inverts the whole 64-bit register; clamp narrow // unsigned results to type width so subsequent 64-bit // compares against typed literals agree. u32 uses MOVL r,r // (zero-extends upper 32) because ANDQ $0xFFFFFFFF would // sign-extend imm32 to all-ones and act as a no-op. if (nodeisunsigned(c, n.lhs)) { let w: i32 = nodeprimwidth(c, n.lhs); if (w == 1) { emitline("\tANDQ\t$255, AX\n"); }; if (w == 2) { emitline("\tANDQ\t$65535, AX\n"); }; if (w == 4) { emitline("\tMOVL\tAX, AX\n"); }; }; return; }; if (n.op == syntax.tkind.TK_STAR) { // #185: deref of *fn — the pointer value IS the fn address. // cgexpr(n.lhs) left AX = fn-addr; a generic MOVQ (AX),AX // would load the first instruction word and a subsequent // CALL would segfault. Mirror ref/harec/src/check.c // expr_call's STORAGE_POINTER→STORAGE_FUNCTION skip. // #61 C: same skip for an ARRAY pointee — an array value IS // its address everywhere in this cgen (#270-1a), so `*p` on // `*[N]T` leaves AX = p's value. The scalar load below // pulled a[0]'s VALUE and `(*p)[i]` then dereferenced it as // the index base — a wild pointer, SIGSEGV on both stages. let rti: *syntax.tinfo = n.type_: *syntax.tinfo; rti = tichase(rti); if (rti != nil && rti.kind == syntax.tykind.TY_FN) { return; }; if (rti != nil && rti.kind == syntax.tykind.TY_ARRAY) { return; }; // Family C (#35/#46): a tagged box behind *p joins the // mem-based class at ANY size (taggedmemread) — AX = p's // value IS the box address. The scalar load below pulled // word0 (the tag) and every cursor consumer transported // garbage payload words — silent-wrong both stages (ken // f35/D3a/D3b). The nullable one-word fold stays a scalar // deref. Mirrors cstage N_UN TK_STAR. if (rti != nil && rti.kind == syntax.tykind.TY_TAGGED) { if (rti.nullable == 0 && rti.size: i32 > 8) { return; }; }; // C1b: a whole str/slice loaded BY VALUE through *str / // *[]T — AX (the operand value) IS the 24B {ptr,len,cap} // header address. The scalar load below pulled ONLY word0 // (.ptr); .len/.cap were then stored from stale BX/CX, so // len(*p) read garbage — byte-id-blind on both stages. Reuse // the same 3-word header load as the slice-field / cgindex // str-element arms. if (rti != nil) { if (rti.kind == syntax.tykind.TY_STR || rti.kind == syntax.tykind.TY_SLICE) { cgslicehdr(c, "AX"); return; }; }; // f64/f32 result rides X0 (SSE), not AX — an integer MOVQ // strands the value off the float ABI and the caller's // MOVSD X0 reads stale bits (#96). Mirrors the float // field/ident load idiom. if (isfloattype(c, n)) { let mov: str = "MOVSD"; if (isf32type(c, n)) { mov = "MOVSS"; }; emitline("\t"); emitline(mov); emitline("\t(AX), X0\n"); } else { // Load-twin of the landed signed-narrow-scalar-reads // sweep (selfhost/CLAUDE.md "Signed-narrow scalar // reads sign-extend honestly"); TK_STAR was the // omitted site, refiled as #116. A raw MOVQ pulls 8B // through a narrow `*iN` and overlaps the next element // — the `*p` value reads honest only when the caller's // sink truncates (i32 store, i32 return). Width- // preserving sinks (CMPQ, 64-bit arith) saw garbage in // the high bytes. localloadop keys MOVSXD/MOVSWQ/ // MOVSBQ + MOVL/MOVZWQ/MOVZBQ off n.type_; n is the // deref expression, n.type_ is the pointee tinfo // (check.ww unoptype TK_STAR L1871-1886 with // TY_NAMED/TY_ENUM peel pre-folded by // tinfofornode/typeissigned), the same shape the // float arm above feeds isfloattype. let lop: str = localloadop(c, n); emitline("\t"); emitline(lop); emitline("\t(AX), AX\n"); }; return; }; if (n.op == syntax.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; }; // cgstreqpush — push one str operand's (len, then ptr) header words for // the rt_streq content-compare in cgbin. Mirrors cstage cgen.c:4568-4615: // an ident loads its 2-word header (ptr+len, NOT cap) from name(SB) for a // module-global str (#154 let_islet branch) or BP+off for a local; a // non-ident evals via cgexpr (AX=ptr, BX=len) and pushes BX then AX. Push // order is len then ptr so the matching POPQ pops ptr first. fn cgstreqpush(c: *cgen, op: *syntax.node) void = { if (op.kind == syntax.nkind.N_IDENT) { let nm: str = op.str; let lc: *local = localfindnode(c, nm); if (lc == nil && isletvar(c, nm)) { emitline("\tLEAQ\t"); emitfnname(c, nm, c.curmod); emitline("(SB), BX\n"); emitline("\tMOVQ\t8(BX), AX\n"); emitline("\tPUSHQ\tAX\n"); emitline("\tMOVQ\t(BX), AX\n"); emitline("\tPUSHQ\tAX\n"); return; }; if (lc == nil && deflookup(c, nm)) { // A str def has no name(SB) header. Its ordinary expression // load materialises the literal as AX=ptr, BX=len. cgexpr(c, op); emitline("\tPUSHQ\tBX\n"); emitline("\tPUSHQ\tAX\n"); return; }; let off: i32 = 0; if (lc != nil) { off = lc.off; }; emitline("\tMOVQ\t"); emitoff((off + 8): i64); emitline("(BP), AX\n"); emitline("\tPUSHQ\tAX\n"); emitline("\tMOVQ\t"); emitoff(off: i64); emitline("(BP), AX\n"); emitline("\tPUSHQ\tAX\n"); return; }; cgexpr(c, op); emitline("\tPUSHQ\tBX\n"); emitline("\tPUSHQ\tAX\n"); }; fn cgbin(c: *cgen, n: *syntax.node) void = { // Short-circuit `&&` / `||`. Operands are bool (0/1); the type // checker enforces it. Eval LHS into AX, branch over RHS on the // short-circuit polarity, otherwise eval RHS into AX. The // surviving AX is the result. Must precede any eager-eval path // below — `if (p != nil && p.x > 0)` would segfault on a nil // deref otherwise. Byte-identical to cmd/w6c/cgen.c N_BIN. if (n.op == syntax.tkind.TK_AND || n.op == syntax.tkind.TK_OR) { let prefix: str = "andend"; let jshrt: str = "JE"; if (n.op == syntax.tkind.TK_OR) { prefix = "orend"; jshrt = "JNE"; }; let end: str = mklabel(c, prefix); cgexpr(c, n.lhs); emitline("\tCMPQ\t$0, AX\n"); emitline("\t"); emitline(jshrt); emitline("\t"); emitline(end); emitline("\n"); cgexpr(c, n.rhs); emitlabel(end); return; }; // #146 (#154 ww-twin): str ==/!= is a CONTENT compare via rt_streq, // not a ptr compare. Must run before the generic eager-eval tail // below collapses each str header to its ptr word (AX). Push rhs // then lhs (len, ptr each); POPQ DI/SI/DX/CX lands a.ptr,a.len, // b.ptr,b.len per rt/streq.s; CALL rt_streq -> AX in {0,1}; XOR 1 // for !=. The gate reads the checker stamp (typeisstr) exactly like // cstage node_isstr. Byte-identical to cstage cbinop (cmd/w6c/ // cgen.c:4564-4623). cstage was fixed by #154; this is its mirror. if ((n.op == syntax.tkind.TK_EQ || n.op == syntax.tkind.TK_NEQ) && n.lhs != nil && n.rhs != nil && syntax.typeisstr(n.lhs.type_: *syntax.tinfo) && syntax.typeisstr(n.rhs.type_: *syntax.tinfo)) { cgstreqpush(c, n.rhs); cgstreqpush(c, n.lhs); emitline("\tPOPQ\tDI\n\tPOPQ\tSI\n\tPOPQ\tDX\n\tPOPQ\tCX\n"); emitline("\tCALL\trt_streq(SB)\n"); if (n.op == syntax.tkind.TK_NEQ) { emitline("\tXORQ\t$1, AX\n"); }; return; }; 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. // Value-class read off the checker stamp (n.type_) — the SSoT // shared with cstage cgen.c node_isfloat / type_isf32. The armed // asserttyped bail (check.ww) guarantees every checked value-node // is stamped, so the read can't see a nil-typed float operand; // the sibling-evidence loud-aborts that used to pin that contract // are therefore dead and removed. let lfk: i32 = 0; if (n.lhs != nil) { let llt: *syntax.tinfo = n.lhs.type_: *syntax.tinfo; if (syntax.typeisf32(llt)) { lfk = 1; } else { if (syntax.typeisfloat(llt)) { lfk = 2; }; }; }; let rfk: i32 = 0; if (n.rhs != nil) { let rrt: *syntax.tinfo = n.rhs.type_: *syntax.tinfo; if (syntax.typeisf32(rrt)) { rfk = 1; } else { if (syntax.typeisfloat(rrt)) { rfk = 2; }; }; }; let fk: i32 = lfk; if (fk == 0) { fk = rfk; }; if (fk != 0) { let mov: str = "MOVSD"; if (fk == 1) { mov = "MOVSS"; }; if (n.op == syntax.tkind.TK_PLUS || n.op == syntax.tkind.TK_MINUS || n.op == syntax.tkind.TK_STAR || n.op == syntax.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 == syntax.tkind.TK_MINUS) { op = "SUBSD"; }; if (n.op == syntax.tkind.TK_STAR) { op = "MULSD"; }; if (n.op == syntax.tkind.TK_SLASH) { op = "DIVSD"; }; if (fk == 1) { if (n.op == syntax.tkind.TK_PLUS) { op = "ADDSS"; }; if (n.op == syntax.tkind.TK_MINUS) { op = "SUBSS"; }; if (n.op == syntax.tkind.TK_STAR) { op = "MULSS"; }; if (n.op == syntax.tkind.TK_SLASH) { op = "DIVSS"; }; }; emitline("\t"); emitline(op); emitline("\tX1, X0\n"); return; }; let isfcmp: bool = false; if (n.op == syntax.tkind.TK_EQ) { isfcmp = true; }; if (n.op == syntax.tkind.TK_NEQ) { isfcmp = true; }; if (n.op == syntax.tkind.TK_LT) { isfcmp = true; }; if (n.op == syntax.tkind.TK_LE) { isfcmp = true; }; if (n.op == syntax.tkind.TK_GT) { isfcmp = true; }; if (n.op == syntax.tkind.TK_GE) { isfcmp = true; }; 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"); // IEEE-754: UCOMISD/SS sets PF=ZF=CF=1 on unordered (a // NaN operand). Any relop with a NaN operand is // unordered -> `!=` true, the other five false. PF must // steer `!=`/`==`/`<`/`<=` (#97): JNE keys on ZF=0 so // `nan != nan` came out false; JE/JB/JBE fire on the // unordered ZF/CF. `>`/`>=` (JA/JAE) need CF=0, which // unordered never gives, so they are ALREADY NaN-correct // and stay byte-identical to the pre-#97 single-template // arm — no redundant PF guard. if (n.op == syntax.tkind.TK_NEQ) { // not-equal OR unordered -> true let t: str = mklabel(c, "ct"); let e: str = mklabel(c, "ce"); emitline("\tJNE\t"); emitline(t); emitline("\n"); emitline("\tJP\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; }; if (n.op == syntax.tkind.TK_EQ || n.op == syntax.tkind.TK_LT || n.op == syntax.tkind.TK_LE) { // unordered -> false; otherwise the ordered Jcc decides. let jcc: str = "JE"; if (n.op == syntax.tkind.TK_LT) { jcc = "JB"; }; if (n.op == syntax.tkind.TK_LE) { jcc = "JBE"; }; let fl: str = mklabel(c, "cf"); let t: str = mklabel(c, "ct"); let e: str = mklabel(c, "ce"); emitline("\tJP\t"); emitline(fl); emitline("\n"); emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n"); emitlabel(fl); emitline("\tMOVQ\t$0, AX\n"); emitline("\tJMP\t"); emitline(e); emitline("\n"); emitlabel(t); emitline("\tMOVQ\t$1, AX\n"); emitlabel(e); return; }; // `>`/`>=`: JA/JAE already reject unordered (CF=1), so // keep the pre-#97 single-template shape verbatim. let jcc: str = "JA"; if (n.op == syntax.tkind.TK_GE) { jcc = "JAE"; }; 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 == syntax.tkind.TK_PLUS) { emitline("\tADDQ\tBX, AX\n"); return; }; if (n.op == syntax.tkind.TK_MINUS) { emitline("\tSUBQ\tBX, AX\n"); return; }; if (n.op == syntax.tkind.TK_STAR) { emitline("\tIMULQ\tBX, AX\n"); return; }; if (n.op == syntax.tkind.TK_SLASH) { // Signed IDIV reads dividend from RDX:RAX; CQO sign-extends // RAX. Zero-filling DX would treat a negative RAX as a huge // positive 128-bit value. Unsigned DIV needs RDX zero. if (unsignd) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tBX\n"); } else { emitline("\tCQO\n"); emitline("\tIDIVQ\tBX\n"); }; return; }; if (n.op == syntax.tkind.TK_PERCENT) { if (unsignd) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tBX\n"); } else { emitline("\tCQO\n"); emitline("\tIDIVQ\tBX\n"); }; emitline("\tMOVQ\tDX, AX\n"); return; }; if (n.op == syntax.tkind.TK_AMP) { emitline("\tANDQ\tBX, AX\n"); return; }; if (n.op == syntax.tkind.TK_PIPE) { emitline("\tORQ\tBX, AX\n"); return; }; if (n.op == syntax.tkind.TK_CARET) { emitline("\tXORQ\tBX, AX\n"); return; }; if (n.op == syntax.tkind.TK_LSHIFT) { emitline("\tMOVQ\tBX, CX\n"); emitline("\tSHLQ\tCX, AX\n"); return; }; if (n.op == syntax.tkind.TK_RSHIFT) { // #136: signed RSHIFT → SAR (arithmetic, sign-extends MSB); // unsigned → SHR (logical, zero-fill). `unsignd` derived above // at cgbin head from nodeisunsigned(lhs) || nodeisunsigned(rhs). emitline("\tMOVQ\tBX, CX\n"); if (unsignd) { emitline("\tSHRQ\tCX, AX\n"); } else { emitline("\tSARQ\tCX, AX\n"); }; return; }; // TK_AND / TK_OR handled with short-circuit codegen at the top of // cgbin — they never reach this eager-eval tail. // 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 == syntax.tkind.TK_EQ) { iscmp = true; jcc = "JE"; }; if (n.op == syntax.tkind.TK_NEQ) { iscmp = true; jcc = "JNE"; }; if (n.op == syntax.tkind.TK_LT) { iscmp = true; if (unsignd) { jcc = "JB"; } else { jcc = "JL"; }; }; if (n.op == syntax.tkind.TK_LE) { iscmp = true; if (unsignd) { jcc = "JBE"; } else { jcc = "JLE"; }; }; if (n.op == syntax.tkind.TK_GT) { iscmp = true; if (unsignd) { jcc = "JA"; } else { jcc = "JG"; }; }; if (n.op == syntax.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_malloc, 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. // // Task #30: result is the graduated `(*T | nomem)` tagged-pointer // pair (AX=tag, DX=ptr). rt_malloc now returns 0 on OOM // (rt/alloc.s); branch on AX to emit the nomem variant (tag=1, // DX=0) or the success variant (tag=0, DX=ptr) after the // value-init stores complete. Callers wrap with `!` / `?` to // consume the union. fn cgalloc(c: *cgen, n: *syntax.node) void = { let v: *syntax.node = n.list; let sz: i32 = 8; let si: *structinfo = nil; if (v.kind == syntax.nkind.N_STRUCTLIT) { // #26: chase the alias chain on the literal's type ref so an // alias head (`type pt = point; alloc(pt{...})`) resolves to // the underlying struct's layout — name-keyed structlookup on // the syntactic head missed it (under-alloc $8 + zero field // stores). Mirrors cstage cgen.c:8223-8240 (type_default chases // named before sizing/walking fields); same alias-chase helper // the #22 sites use. si = structlookupchain(c, v.lhs); if (si != nil) { sz = si.totsize; }; }; let okl: str = mklabel(c, "alloc_ok"); let donel: str = mklabel(c, "alloc_done"); emitline("\tMOVQ\t$"); emitint(sz: i64); emitline(", DI\n"); emitline("\tCALL\t"); emitline(allocresolve(c)); emitline("(SB)\n"); emitline("\tCMPQ\t$0, AX\n"); emitline("\tJNE\t"); emitline(okl); emitline("\n"); emitline("\tMOVQ\t$1, AX\n"); emitline("\tMOVQ\t$0, DX\n"); emitline("\tJMP\t"); emitline(donel); emitline("\n"); emitlabel(okl); emitline("\tPUSHQ\tAX\n"); if (v.kind == syntax.nkind.N_STRUCTLIT) { if (si != nil) { // C7c: route the heap field-fill through the shared // structlit helper in mode 3 (DST_PTR_SP) — base reloaded // from the just-pushed heap ptr at (SP). The prior inline // loop had only float/str/scalar arms, so a nested // struct/array/tuple field VALUE (an inner N_STRUCTLIT / // N_ARRLIT) fell to the scalar tail and stored AX=0 over // the whole inner slot, dropping its leaves. The helper // recurses to arbitrary depth and reuses the // tagged/call/str/slice/array/agg arms, closing the class // symmetrically with cstage's DST_PTR_SP arm. Byte-id to // the old inline loop for the float/str/scalar fields the // corpus actually allocs (same (SP) reload, disp=0). cgstructlitfill(c, si, v, 3, 0, "", 0); }; } else { // #57 (retained cs!=ww, deferred): scalar/ptr alloc keeps the // default-8 size + MOVQ store; cstage sizes the scalar exactly // ($1/MOVB for u8) and the latent alloc(str|slice) wants 24B not 8. // Byte-id-visible, runtime-benign; not folded into the #26 arm. 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\tDX\n"); emitline("\tMOVQ\t$0, AX\n"); emitlabel(donel); }; // cgappendgrow — FA1 (#15): append() header-place grow, cgplaceaddr's // append consumer. direct = ident-local header in the frame (BP-disp — // the legacy emission, kept byte-identical); indirect = header address // pre-spilled to @apphdrscr by the resolver. len+=1, &hdr→DI, esz→SI, // CALL rt_ensure. In indirect mode the len bump goes through DI so the // loaded address doubles as the call argument. Mirrors cstage // cg_append_grow. fn cgappendgrow(c: *cgen, direct: bool, off: i32, scr: i32, esz: i32) void = { if (direct) { emitline("\tADDQ\t$1, "); emitoff((off + 8): i64); emitline("(BP)\n"); emitline("\tLEAQ\t"); emitoff(off: i64); emitline("(BP), DI\n"); } else { emitline("\tMOVQ\t"); emitoff(scr: i64); emitline("(BP), DI\n"); emitline("\tADDQ\t$1, 8(DI)\n"); }; emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", SI\n"); emitline("\tCALL\trt_ensure(SB)\n"); }; // cgappendslot — post-rt_ensure slot address: CX = (len-1)*esz, // dst = .ptr + CX. Clobbers AX (the IMUL immediate) and CX, like the // emission it replaces; dst must not be AX or CX. Mirrors cstage // cg_append_slot. fn cgappendslot(c: *cgen, direct: bool, off: i32, scr: i32, esz: i32, dst: str) void = { if (direct) { emitline("\tMOVQ\t"); emitoff((off + 8): i64); emitline("(BP), CX\n"); } else { emitline("\tMOVQ\t"); emitoff(scr: i64); emitline("(BP), "); emitline(dst); emitline("\n"); emitline("\tMOVQ\t8("); emitline(dst); emitline("), 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"); }; if (direct) { emitline("\tMOVQ\t"); emitoff(off: i64); emitline("(BP), "); emitline(dst); emitline("\n"); } else { emitline("\tMOVQ\t("); emitline(dst); emitline("), "); emitline(dst); emitline("\n"); }; emitline("\tADDQ\tCX, "); emitline(dst); emitline("\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: *syntax.node) void = { let sn: *syntax.node = n.list; if (sn == nil) { return; }; // FA1 (#15): the old `sn.kind != N_IDENT → return` and // `snlocal == nil → return` gates were SILENT zero-emission // (gate-blind cs≠ww: cstage 0-defaulted the header base and // corrupted the caller frame instead). A non-ident-local target // now resolves its header address through cgplaceaddr; a shape // the resolver can't address is loud. let sndirect: bool = false; let sn_off: i32 = 0; let snlocal: *local = nil; if (sn.kind == syntax.nkind.N_IDENT) { snlocal = localfindnode(c, sn.str); if (snlocal != nil) { sndirect = true; sn_off = snlocal.off; }; }; let esz: i32 = 0; let etnode: *syntax.node = nil; let sti: *syntax.tinfo = nil; if (sndirect) { // #34: esz off the DECLARED slice local's stamped tnode via // elemsizeofc — bare elemsizeof returns the 8 sentinel for a // named tagged/struct element (the #8 family; cgappend was never // upgraded), under-feeding rt_ensure's membsz AND mis-striding // the slot index vs cstage's su->sub->size. esz = elemsizeofc(c, snlocal.tnode); if (snlocal.tnode != nil) { let stk: syntax.nkind = snlocal.tnode.kind; if (stk == syntax.nkind.N_TSLICE) { etnode = snlocal.tnode.lhs; }; if (stk == syntax.nkind.N_TARRAY) { etnode = snlocal.tnode.lhs; }; if (stk == syntax.nkind.N_TPTR) { etnode = snlocal.tnode.lhs; }; sti = snlocal.tnode.type_: *syntax.tinfo; }; } else { // FA1: `*p` has no declared tnode — key esz/element kind off // the checker-STAMPED target tinfo (#209/#211 discipline), the // same source cstage reads (sn->type → su->sub->size). sti = sn.type_: *syntax.tinfo; let fsti: *syntax.tinfo = sti; fsti = tichase(fsti); if (fsti != nil && fsti.sub != nil) { esz = fsti.sub.size: i32; }; if (esz <= 0) { let m15z: str = "#15: append() target element size unresolved (rule-7)\n"; os.write(2, m15z.ptr, m15z.len: u64); os.exit(1); }; }; let store_op: str = tnodestoreop(c, etnode, esz); // #34 element-kind store dispatch: the scalar 1-word store below // silently gutted every wide element (str/slice 24B header, // tagged box, struct body). Kind off the stamped slice tinfo — // the value node's literal tinfo is the #25/#31 esz=0 trap. // Mirrors cstage cgen.c's append arm + the #270/#12/#20 // array-literal element dispatch (cgarrlitfillbp). sti = tichase(sti); let esubti: *syntax.tinfo = nil; if (sti != nil) { esubti = sti.sub; }; // FA1: pre-peel handle — the indirect struct-lit fill keys its // structinfo off the NAMED element tinfo's name (the same leaf // structlookupchain resolves from the declared tnode). let esubnamed: *syntax.tinfo = esubti; esubti = tichase(esubti); let elstr: bool = esubti != nil && esubti.kind == syntax.tykind.TY_STR; let elslice: bool = esubti != nil && esubti.kind == syntax.tykind.TY_SLICE; let eltagged: bool = esubti != nil && esubti.kind == syntax.tykind.TY_TAGGED; let elstruct: bool = esubti != nil && esubti.kind == syntax.tykind.TY_STRUCT; let elwide: bool = elstr || elslice || eltagged || elstruct; if (!elwide && esz > 8) { let m34k: str = "#34: append() element kind unsupported (rule-7)\n"; os.write(2, m34k.ptr, m34k.len: u64); os.exit(1); }; let snscr: i32 = 0; if (!sndirect) { if (!cgplaceaddr(c, sn, "BX")) { let m15p: str = "#15: append() target place unsupported (rule-7)\n"; os.write(2, m15p.ptr, m15p.len: u64); os.exit(1); }; // Spill across rt_ensure: realloc moves .ptr, never the // header, so the slot stays valid for every later reload. // Fresh slot per SITE via localalloc (NOT localadd: its `@` // dedup would share one slot per fn, and a nested // append-through-pointer inside a value expression — // match-yield arm — would clobber the outer's spilled header // address: silent cross-slice corruption. Mirrors cstage's // never-deduping local_alloc at the same point.) snscr = localalloc(c, "@apphdrscr", 8, nil); emitline("\tMOVQ\tBX, "); emitoff(snscr: i64); emitline("(BP)\n"); }; let vn: *syntax.node = sn.next; for (vn != nil) { if (vn.kind == syntax.nkind.N_SPREAD) { // #34 review: a non-ident/non-local spread source used // to be silently SKIPPED here while cstage fell past // its spread arm into the single-value stores with the // N_SPREAD node (garbage store) — divergent. let it: *syntax.node = vn.lhs; // #35: only a {ptr,len,cap}-headered source reads as a // header below; a [N]T array place IS its storage — the // ident path used to read its first 16 data bytes as // ptr/len, silently. Loud until wired (task #27); str // shares the slice header layout. let itu: *syntax.tinfo = nil; if (it != nil) { itu = it.type_: *syntax.tinfo; }; itu = tichase(itu); if (itu == nil || (itu.kind != syntax.tykind.TY_SLICE && itu.kind != syntax.tykind.TY_STR)) { let m35p: str = "#35: append() spread source shape unsupported (rule-7)\n"; os.write(2, m35p.ptr, m35p.len: u64); os.exit(1); }; let it_off: i32 = 0; let itscr: i32 = 0; if (it.kind == syntax.nkind.N_IDENT) { let itlocal: *local = localfindnode(c, it.str); if (itlocal != nil) { it_off = itlocal.off; }; }; if (it_off == 0) { // #35: place-chain source (deref spine, indexed // chain, global ident — the regex.ha:569/820 dup // shapes) resolves its header ADDRESS through // cgplaceaddr ONCE, pre-grow: the chain's rvalues // run exactly once (the #49 split's pre-grow // half) and every iteration re-reads .ptr/.len // THROUGH the spilled header post-grow (the live // re-derivation half). A header reached through a // buffer the grow reallocs keeps Hare's // stale-base hole — see the #49 comment below. // Rvalue sources (CALL, slicing exprs) have no // place — loud, task #27. Fresh spill slot per // SITE for the same nesting reason as @apphdrscr // above. if (!cgplaceaddr(c, it, "BX")) { let m35q: str = "#35: append() spread source shape unsupported (rule-7)\n"; os.write(2, m35q.ptr, m35q.len: u64); os.exit(1); }; itscr = localalloc(c, "@appsprscr", 8, nil); emitline("\tMOVQ\tBX, "); emitoff(itscr: i64); emitline("(BP)\n"); }; let load_op: str = tnodeloadop(c, etnode, esz); if (!sndirect) { // FA1: no etnode behind `*p` — signedness off the // stamped element tinfo, the predicate cstage's // fldloadop applies to su->sub. load_op = loadopsz(syntax.typeissigned(esubti), esz); }; 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"); if (itscr != 0) { emitline("\tMOVQ\t"); emitoff(itscr: i64); emitline("(BP), DX\n"); emitline("\tMOVQ\t8(DX), DX\n"); } else { emitline("\tMOVQ\t"); emitoff((it_off + 8): i64); emitline("(BP), DX\n"); }; emitline("\tCMPQ\tDX, CX\n"); emitline("\tJGE\t"); emitline(le); emitline("\n"); if (elwide) { // #34: a spread element is already a fully-formed // T in the source slice (tag included), so a // whole-width word-copy is the store — no boxing. // Grow FIRST: rt_ensure may realloc, so both // addresses are recomputed from the slice headers // after the call (i reloads from the counter // slot; CX was clobbered). cgappendgrow(c, sndirect, sn_off, snscr, esz); emitline("\tMOVQ\t(SP), CX\n"); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", AX\n"); emitline("\tIMULQ\tAX, CX\n"); }; if (itscr != 0) { emitline("\tMOVQ\t"); emitoff(itscr: i64); emitline("(BP), BX\n"); emitline("\tMOVQ\t(BX), BX\n"); } else { emitline("\tMOVQ\t"); emitoff(it_off: i64); emitline("(BP), BX\n"); }; emitline("\tADDQ\tCX, BX\n"); cgappendslot(c, sndirect, sn_off, snscr, esz, "DX"); let wk: i32 = 0; for (wk + 8 <= esz) { emitline("\tMOVQ\t"); emitdispreg(wk: i64, "BX"); emitline(", AX\n"); emitline("\tMOVQ\tAX, "); emitdispreg(wk: i64, "DX"); emitline("\n"); wk += 8; }; if (wk + 4 <= esz) { emitline("\tMOVL\t"); emitdispreg(wk: i64, "BX"); emitline(", AX\n"); emitline("\tMOVL\tAX, "); emitdispreg(wk: i64, "DX"); emitline("\n"); wk += 4; }; if (wk + 2 <= esz) { emitline("\tMOVW\t"); emitdispreg(wk: i64, "BX"); emitline(", AX\n"); emitline("\tMOVW\tAX, "); emitdispreg(wk: i64, "DX"); emitline("\n"); wk += 2; }; if (wk + 1 <= esz) { emitline("\tMOVB\t"); emitdispreg(wk: i64, "BX"); emitline(", AX\n"); emitline("\tMOVB\tAX, "); emitdispreg(wk: i64, "DX"); emitline("\n"); wk += 1; }; 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; }; if (itscr != 0) { emitline("\tMOVQ\t"); emitoff(itscr: i64); emitline("(BP), BX\n"); emitline("\tMOVQ\t(BX), BX\n"); } else { 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"); cgappendgrow(c, sndirect, sn_off, snscr, esz); cgappendslot(c, sndirect, sn_off, snscr, esz, "BX"); 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; }; if (elstr || elslice) { // #34: 24B {ptr,len,cap} header. cgexpr leaves // AX/BX/CX; all three must survive rt_ensure. dst // lands in DX, NOT BX — the pops put the element // .len back in BX (the #24 register discipline). cgexpr(c, vn); emitline("\tPUSHQ\tAX\n"); emitline("\tPUSHQ\tBX\n"); emitline("\tPUSHQ\tCX\n"); cgappendgrow(c, sndirect, sn_off, snscr, esz); cgappendslot(c, sndirect, sn_off, snscr, esz, "DX"); emitline("\tPOPQ\tCX\n"); emitline("\tPOPQ\tBX\n"); emitline("\tPOPQ\tAX\n"); emitline("\tMOVQ\tAX, (DX)\n"); emitline("\tMOVQ\tBX, 8(DX)\n"); emitline("\tMOVQ\tCX, 16(DX)\n"); vn = vn.next; continue; }; if (eltagged || elstruct) { // #34: no register form survives rt_ensure for these. // struct: grow FIRST, then fill through the dst pointer // (literal fill / ident word-copy). tagged: #50 — the // #12 widen choke-point cgexprs the value internally, // so boxing must run PRE-grow (Hare's argument order: // a `xs.len` read in v sees the pre-append len, like // the scalar arm); box into a frame scratch, grow, // raw-copy the finished box in. // #49 (#35's single-element sibling): a place-chain // source (indexed field `threads[i].root_capture` // regex.ha:819, deref spine, computed index) SPLITS // around the grow per the #49 ruling: the chain's // rvalues (deref-root pointer expr, index expr) // evaluate exactly once PRE-grow — an index reading // the slice header sees the pre-append len, Hare's // argument order — and only the BASE re-derives // POST-grow from the live storage, so a self-append // source re-roots in the post-realloc buffer. harec // resolves an aggregate source address wholly PRE-grow // (gen.c: gen_load returns the address for // STORAGE_STRUCT, gen_store copies after rt.ensure) — // a use-after-free under a reclaiming allocator; per // #263 we align to the runtime-correct side, not the // reference. A pointer ALIASING the grown buffer keeps // Hare's own stale-base hole (sound today only because // rt/malloc.ww never reclaims). Spec not vendored // (ref/hare/docs = man pages only), spec-silence // assumed — re-verify if the spec is ever vendored. // Bounded shapes: root (local/global ident | deref) + // at most one index + trailing direct fields; all else // stays on the #34 loud exit (incl. CALL rvalues, the // #42-style bound). let aplace: bool = false; let afld: i32 = 0; let aidxesz: i32 = 0; let abaseslice: bool = false; let arootoff: i32 = 0; let asroot: i32 = 0; let asoff: i32 = 0; let aroot: *syntax.node = nil; let aidx: *syntax.node = nil; if (elstruct && vn.kind != syntax.nkind.N_STRUCTLIT && vn.kind != syntax.nkind.N_IDENT && vn.kind != syntax.nkind.N_CALL) { let ch: *syntax.node = vn; let aok: bool = true; for (aok && ch.kind == syntax.nkind.N_DOT) { let ab: *syntax.node = ch.lhs; let af: *syntax.tfield = nil; if (ab != nil) { let abu: *syntax.tinfo = ab.type_: *syntax.tinfo; abu = tichase(abu); if (abu != nil && abu.kind == syntax.tykind.TY_STRUCT) { let fl: *syntax.tfield = abu.fields; for (fl != nil) { if (syntax.streq(fl.name, ch.str)) { af = fl; break; }; fl = fl.tnext; }; }; }; if (af == nil) { aok = false; break; }; afld += af.offset: i32; ch = ab; }; if (aok && ch.kind == syntax.nkind.N_INDEX) { let ab: *syntax.node = ch.lhs; let aet: *syntax.tinfo = ch.type_: *syntax.tinfo; aet = tichase(aet); let abu: *syntax.tinfo = nil; if (ab != nil) { abu = ab.type_: *syntax.tinfo; abu = tichase(abu); }; if (ab == nil || abu == nil || aet == nil || (abu.kind != syntax.tykind.TY_SLICE && abu.kind != syntax.tykind.TY_ARRAY)) { aok = false; } else { abaseslice = abu.kind == syntax.tykind.TY_SLICE; aidxesz = aet.size: i32; aidx = ch.rhs; ch = ab; }; }; if (aok) { if (ch.kind == syntax.nkind.N_IDENT) { let rl: *local = localfindnode(c, ch.str); if (rl != nil) { arootoff = rl.off; } else { let gok: bool = isletvar(c, ch.str); if (!gok) { if (defvarstructinfo(c, ch.str) != nil) { gok = true; }; }; if (!gok) { let dtn: *syntax.node = defvartnode(c, ch.str); if (dtn != nil) { if (dtn.kind == syntax.nkind.N_TARRAY) { gok = true; }; }; }; if (!gok) { aok = false; }; }; } else { if (ch.kind != syntax.nkind.N_UN || ch.op != syntax.tkind.TK_STAR) { aok = false; }; }; }; if (!aok) { let m34p: str = "#34: append() struct element source shape unsupported (rule-7)\n"; os.write(2, m34p.ptr, m34p.len: u64); os.exit(1); }; aroot = ch; if (aroot.kind == syntax.nkind.N_UN) { asroot = localadd(c, "@appendsroot", 8, nil); cgexpr(c, aroot.lhs); emitline("\tMOVQ\tAX, "); emitoff(asroot: i64); emitline("(BP)\n"); }; if (aidx != nil) { asoff = localadd(c, "@appendsoff", 8, nil); cgexpr(c, aidx); if (aidxesz > 1) { emitline("\tMOVQ\t$"); emitint(aidxesz: i64); emitline(", CX\n"); emitline("\tIMULQ\tCX, AX\n"); }; emitline("\tMOVQ\tAX, "); emitoff(asoff: i64); emitline("(BP)\n"); }; aplace = true; }; if (eltagged) { // Fresh slot per SITE, not the shared per-size // scratch: the box must stay live across // rt_ensure, and a nested append inside the // value expression would clobber a dedup'd // slot (the @apphdrscr rationale; #25/#31). let tgscr: i32 = localalloc(c, "@apptagscr", esz, nil); emitline("\tXORQ\tAX, AX\n"); let zk: i32 = 0; for (zk < esz) { emitline("\tMOVQ\tAX, "); emitoff((tgscr + zk): i64); emitline("(BP)\n"); zk += 8; }; cgwidentaggedstore(c, esubti, vn, "BP", tgscr, esz); cgappendgrow(c, sndirect, sn_off, snscr, esz); cgappendslot(c, sndirect, sn_off, snscr, esz, "BX"); let ck: i32 = 0; for (ck < esz) { emitline("\tMOVQ\t"); emitoff((tgscr + ck): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\tAX, "); emitdispreg(ck: i64, "BX"); emitline("\n"); ck += 8; }; vn = vn.next; continue; }; if (vn.kind == syntax.nkind.N_CALL) { // #34 close: a struct CALL rvalue element. // Evaluate the call FIRST into a fresh per-site // scratch (the STRUCTLIT arm's pre-grow // discipline: arg exprs must see the pre-append // len, and the value must survive rt_ensure), // receiving by the N_LET matrix (#23 sret / // #171a float / #107 odd-tail / #169 GP), then // grow, slot, sized-ladder scratch->slot. let stscr: i32 = localalloc(c, "@appendstructscr", esz, nil); let scs: i32 = callsretsize(c, vn); if (scs > 0) { c.sretdestoff = stscr; cgexpr(c, vn); c.sretdestoff = 0; } else { let sfc: i32 = structfloatclassti(esubti); if (sfc != 0) { cgexpr(c, vn); let nb: i32 = sfc & 15; let gpcur: i32 = 0; let ssecur: i32 = 0; let e: i32 = 0; for (e < nb) { let issse: bool = (sfc & (16 << e)) != 0; if (issse) { emitline("\tMOVSD\t"); emitline(tupsse(ssecur)); emitline(", "); emitoff((stscr + e * 8): i64); emitline("(BP)\n"); ssecur += 1; } else { emitline("\tMOVQ\t"); emitline(tupreg(gpcur)); emitline(", "); emitoff((stscr + e * 8): i64); emitline("(BP)\n"); gpcur += 1; }; e += 1; }; } else { if (esz == 3 || esz == 5 || esz == 6 || esz == 7) { cgexpr(c, vn); emitline("\tMOVQ\tAX, "); emitoff(stscr: i64); emitline("(BP)\n"); } else { cgexpr(c, vn); cgaggregstore(c, "BP", stscr, esz, true); }; }; }; cgappendgrow(c, sndirect, sn_off, snscr, esz); cgappendslot(c, sndirect, sn_off, snscr, esz, "BX"); let ck: i32 = 0; for (ck + 8 <= esz) { emitline("\tMOVQ\t"); emitoff((stscr + ck): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\tAX, "); emitdispreg(ck: i64, "BX"); emitline("\n"); ck += 8; }; if (ck + 4 <= esz) { emitline("\tMOVL\t"); emitoff((stscr + ck): i64); emitline("(BP), AX\n"); emitline("\tMOVL\tAX, "); emitdispreg(ck: i64, "BX"); emitline("\n"); ck += 4; }; if (ck + 2 <= esz) { emitline("\tMOVW\t"); emitoff((stscr + ck): i64); emitline("(BP), AX\n"); emitline("\tMOVW\tAX, "); emitdispreg(ck: i64, "BX"); emitline("\n"); ck += 2; }; if (ck + 1 <= esz) { emitline("\tMOVB\t"); emitoff((stscr + ck): i64); emitline("(BP), AX\n"); emitline("\tMOVB\tAX, "); emitdispreg(ck: i64, "BX"); emitline("\n"); ck += 1; }; vn = vn.next; continue; }; if (vn.kind == syntax.nkind.N_STRUCTLIT) { // #59 (#50's eval-order kin): resolve the struct // info, then fill the literal into a fresh // per-SITE scratch (must stay live across // rt_ensure, and a nested append in a field expr // would clobber a dedup'd slot — the @apptagscr // rationale, #25/#31) BEFORE the grow, so the // field exprs see the pre-grow len. Then grow, // slot, raw-copy scratch->slot (mirror the #50 // tagged arm above). let esi: *structinfo = structlookupchain(c, etnode); if (esi == nil && !sndirect && esubnamed != nil) { // FA1: no declared tnode to chain through — // the stamped NAMED element tinfo carries the // same leaf structlookupchain would resolve. if (esubnamed.kind == syntax.tykind.TY_NAMED) { esi = structlookup(c, esubnamed.name); }; }; if (esi == nil) { let m34s: str = "#34: append() struct element has no structinfo (rule-7)\n"; os.write(2, m34s.ptr, m34s.len: u64); os.exit(1); }; let stscr: i32 = localalloc(c, "@appendstructscr", esz, nil); cgstructlitfillbp(c, esi, vn, stscr); cgappendgrow(c, sndirect, sn_off, snscr, esz); cgappendslot(c, sndirect, sn_off, snscr, esz, "BX"); // Descending 8/4/2/1 ladder, not an 8B-word // loop: a plain struct's esz rounds to maxalign // (check.c:916), not 8, so a sub-8B / non-8B- // multiple element packs at its own stride — an // 8B copy of the last slot writes past the slice // buffer (the tagged arm above is safe only // because boxes are 8B-padded; #59). Mirrors the // N_IDENT source arm below. let ck: i32 = 0; for (ck + 8 <= esz) { emitline("\tMOVQ\t"); emitoff((stscr + ck): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\tAX, "); emitdispreg(ck: i64, "BX"); emitline("\n"); ck += 8; }; if (ck + 4 <= esz) { emitline("\tMOVL\t"); emitoff((stscr + ck): i64); emitline("(BP), AX\n"); emitline("\tMOVL\tAX, "); emitdispreg(ck: i64, "BX"); emitline("\n"); ck += 4; }; if (ck + 2 <= esz) { emitline("\tMOVW\t"); emitoff((stscr + ck): i64); emitline("(BP), AX\n"); emitline("\tMOVW\tAX, "); emitdispreg(ck: i64, "BX"); emitline("\n"); ck += 2; }; if (ck + 1 <= esz) { emitline("\tMOVB\t"); emitoff((stscr + ck): i64); emitline("(BP), AX\n"); emitline("\tMOVB\tAX, "); emitdispreg(ck: i64, "BX"); emitline("\n"); ck += 1; }; vn = vn.next; continue; }; cgappendgrow(c, sndirect, sn_off, snscr, esz); cgappendslot(c, sndirect, sn_off, snscr, esz, "BX"); if (vn.kind == syntax.nkind.N_IDENT) { let sl: *local = localfindnode(c, vn.str); if (sl == nil) { let m34i: str = "#34: append() struct element source ident is not a local (rule-7)\n"; os.write(2, m34i.ptr, m34i.len: u64); os.exit(1); }; let soff: i32 = sl.off; let ck: i32 = 0; for (ck + 8 <= esz) { emitline("\tMOVQ\t"); emitoff((soff + ck): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\tAX, "); emitdispreg(ck: i64, "BX"); emitline("\n"); ck += 8; }; if (ck + 4 <= esz) { emitline("\tMOVL\t"); emitoff((soff + ck): i64); emitline("(BP), AX\n"); emitline("\tMOVL\tAX, "); emitdispreg(ck: i64, "BX"); emitline("\n"); ck += 4; }; if (ck + 2 <= esz) { emitline("\tMOVW\t"); emitoff((soff + ck): i64); emitline("(BP), AX\n"); emitline("\tMOVW\tAX, "); emitdispreg(ck: i64, "BX"); emitline("\n"); ck += 2; }; if (ck + 1 <= esz) { emitline("\tMOVB\t"); emitoff((soff + ck): i64); emitline("(BP), AX\n"); emitline("\tMOVB\tAX, "); emitdispreg(ck: i64, "BX"); emitline("\n"); ck += 1; }; vn = vn.next; continue; }; if (aplace) { // phase 2: dst slot to @appendscr, base from // the live storage, stashed offsets back on // top. let pscroff: i32 = localadd(c, "@appendscr", 8, nil); emitline("\tMOVQ\tBX, "); emitoff(pscroff: i64); emitline("(BP)\n"); if (aroot.kind == syntax.nkind.N_IDENT) { if (arootoff != 0) { emitline("\tLEAQ\t"); emitoff(arootoff: i64); emitline("(BP), BX\n"); } else { emitline("\tLEAQ\t"); emitsymname(c, aroot.str); emitline("(SB), BX\n"); }; } else { emitline("\tMOVQ\t"); emitoff(asroot: i64); emitline("(BP), BX\n"); }; if (aidx != nil) { if (abaseslice) { emitline("\tMOVQ\t(BX), BX\n"); }; emitline("\tMOVQ\t"); emitoff(asoff: i64); emitline("(BP), AX\n"); emitline("\tADDQ\tAX, BX\n"); }; if (afld != 0) { emitline("\tADDQ\t$"); emitint(afld: i64); emitline(", BX\n"); }; emitline("\tMOVQ\t"); emitoff(pscroff: i64); emitline("(BP), DX\n"); let pk: i32 = 0; for (pk + 8 <= esz) { emitline("\tMOVQ\t"); emitdispreg(pk: i64, "BX"); emitline(", AX\n"); emitline("\tMOVQ\tAX, "); emitdispreg(pk: i64, "DX"); emitline("\n"); pk += 8; }; if (pk + 4 <= esz) { emitline("\tMOVL\t"); emitdispreg(pk: i64, "BX"); emitline(", AX\n"); emitline("\tMOVL\tAX, "); emitdispreg(pk: i64, "DX"); emitline("\n"); pk += 4; }; if (pk + 2 <= esz) { emitline("\tMOVW\t"); emitdispreg(pk: i64, "BX"); emitline(", AX\n"); emitline("\tMOVW\tAX, "); emitdispreg(pk: i64, "DX"); emitline("\n"); pk += 2; }; if (pk + 1 <= esz) { emitline("\tMOVB\t"); emitdispreg(pk: i64, "BX"); emitline(", AX\n"); emitline("\tMOVB\tAX, "); emitdispreg(pk: i64, "DX"); emitline("\n"); pk += 1; }; vn = vn.next; continue; }; let m34e: str = "#34: append() struct element source shape unsupported (rule-7)\n"; os.write(2, m34e.ptr, m34e.len: u64); os.exit(1); }; cgexpr(c, vn); emitline("\tPUSHQ\tAX\n"); cgappendgrow(c, sndirect, sn_off, snscr, esz); cgappendslot(c, sndirect, sn_off, snscr, esz, "BX"); emitline("\tPOPQ\tAX\n"); emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n"); vn = vn.next; }; return; }; // cgdelete — Hare `delete(xs[i])`: single-element slice removal, the // delete-half of #35. Shift [i+1..len) down one stride, len -= 1, cap // unchanged. The move is a same-type whole-stride byte copy: src and // dst are elements of the SAME slice, so no boxing/coercion exists for // any element kind (str/slice 24B header, tagged box, struct body) — // one word-copy loop serves all kinds, unlike append's value-store // dispatch (#34) which boxes from a foreign source. Ascending j keeps // src (j+1) ahead of dst (j), the safe memmove-down direction. Mirrors // cmd/w6c/cgen.c's N_CALL delete arm instruction-for-instruction // (rule-10 byte-id). fn cgdelete(c: *cgen, n: *syntax.node) void = { let d: *syntax.node = n.list; // N_INDEX, checker-validated let base: *syntax.node = d.lhs; // esz off the STAMPED base tinfo (#34/#48 discipline — never the // value node). Peel TY_NAMED on indexable AND element, mirroring // cstage's type_chase_named on both (#8 family). let sti: *syntax.tinfo = base.type_: *syntax.tinfo; sti = tichase(sti); let esub: *syntax.tinfo = nil; if (sti != nil) { esub = sti.sub; }; esub = tichase(esub); let esz: i32 = 0; if (esub != nil) { esz = esub.size: i32; }; if (esz <= 0) { let m35a: str = "#35: delete() element size unresolved (rule-7)\n"; os.write(2, m35a.ptr, m35a.len: u64); os.exit(1); }; let hdr_lea: bool = false; let hdr_off: i32 = 0; let hdr_ok: bool = false; if (base.kind == syntax.nkind.N_IDENT) { let lc: *local = localfindnode(c, base.str); if (lc != nil) { hdr_lea = true; hdr_off = lc.off; hdr_ok = true; }; }; // (*p)[i]: the header lives behind a local ptr-to-slice — the // regex fold-2b delete_thread shape (threads: *[]thread). if (!hdr_ok && base.kind == syntax.nkind.N_UN) { if (base.op == syntax.tkind.TK_STAR && base.lhs != nil) { if (base.lhs.kind == syntax.nkind.N_IDENT) { let pc: *local = localfindnode(c, base.lhs.str); if (pc != nil) { hdr_off = pc.off; hdr_ok = true; }; }; }; }; if (!hdr_ok) { let m35b: str = "#35: delete() base shape unsupported (rule-7: local slice ident or deref-of-local only)\n"; os.write(2, m35b.ptr, m35b.len: u64); os.exit(1); }; cgexpr(c, d.rhs); // AX = i emitline("\tPUSHQ\tAX\n"); if (hdr_lea) { emitline("\tLEAQ\t"); } else { emitline("\tMOVQ\t"); }; emitoff(hdr_off: i64); emitline("(BP), AX\n"); emitline("\tPUSHQ\tAX\n"); let dll: str = mklabel(c, "del_l"); let dle: str = mklabel(c, "del_e"); emitlabel(dll); emitline("\tMOVQ\t(SP), DX\n"); emitline("\tMOVQ\t8(SP), CX\n"); emitline("\tMOVQ\t8(DX), BX\n"); emitline("\tSUBQ\t$1, BX\n"); emitline("\tCMPQ\tBX, CX\n"); emitline("\tJGE\t"); emitline(dle); emitline("\n"); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", AX\n"); emitline("\tIMULQ\tAX, CX\n"); }; emitline("\tMOVQ\t(DX), BX\n"); emitline("\tADDQ\tCX, BX\n"); let dk: i32 = 0; for (dk + 8 <= esz) { emitline("\tMOVQ\t"); emitdispreg((esz + dk): i64, "BX"); emitline(", AX\n"); emitline("\tMOVQ\tAX, "); emitdispreg(dk: i64, "BX"); emitline("\n"); dk += 8; }; if (dk + 4 <= esz) { emitline("\tMOVL\t"); emitdispreg((esz + dk): i64, "BX"); emitline(", AX\n"); emitline("\tMOVL\tAX, "); emitdispreg(dk: i64, "BX"); emitline("\n"); dk += 4; }; if (dk + 2 <= esz) { emitline("\tMOVW\t"); emitdispreg((esz + dk): i64, "BX"); emitline(", AX\n"); emitline("\tMOVW\tAX, "); emitdispreg(dk: i64, "BX"); emitline("\n"); dk += 2; }; if (dk + 1 <= esz) { emitline("\tMOVB\t"); emitdispreg((esz + dk): i64, "BX"); emitline(", AX\n"); emitline("\tMOVB\tAX, "); emitdispreg(dk: i64, "BX"); emitline("\n"); dk += 1; }; emitline("\tADDQ\t$1, 8(SP)\n"); emitline("\tJMP\t"); emitline(dll); emitline("\n"); emitlabel(dle); emitline("\tMOVQ\t(SP), DX\n"); emitline("\tSUBQ\t$1, 8(DX)\n"); emitline("\tADDQ\t$16, SP\n"); }; // cgdeleterange — Hare `delete(xs[lo..hi])` (ww spells the range // `xs[lo:hi]`): range slice removal, the fold-5a prereq P2 // (ref/hare/regex/regex.ha:333 delete(jump_idxs[group_level][..]); // harec ref/harec/src/check.c:1994 EXPR_SLICE). Shift [hi..len) down // count = hi-lo strides, len -= count, cap unchanged; lo defaults 0, // hi defaults len. delete(xs[:]) never enters the copy loop (lo+count // == len at entry) and zeroes len. The per-element move is cgdelete's // same-slice whole-stride word copy with a DYNAMIC src offset // (count*esz via a src register) instead of the constant one-stride. // Ascending j keeps src >= dst, the safe memmove-down direction. // Bounds are implicit (no range check, matching cgdelete and the rest // of cgen). Mirrors cmd/w6c/cgen.c's N_CALL delete range arm // instruction-for-instruction (rule-10 byte-id). fn cgdeleterange(c: *cgen, n: *syntax.node) void = { let d: *syntax.node = n.list; // N_SLICE, checker-validated let base: *syntax.node = d.lhs; // esz off the STAMPED base tinfo (#34/#48 discipline — never the // value node). Peel TY_NAMED on indexable AND element, mirroring // cstage's type_chase_named on both (#8 family). let sti: *syntax.tinfo = base.type_: *syntax.tinfo; sti = tichase(sti); let esub: *syntax.tinfo = nil; if (sti != nil) { esub = sti.sub; }; esub = tichase(esub); let esz: i32 = 0; if (esub != nil) { esz = esub.size: i32; }; if (esz <= 0) { let m35a: str = "#35: delete() element size unresolved (rule-7)\n"; os.write(2, m35a.ptr, m35a.len: u64); os.exit(1); }; let hdr_lea: bool = false; let hdr_off: i32 = 0; let hdr_ok: bool = false; let hdr_idx: bool = false; let osz: i32 = 0; if (base.kind == syntax.nkind.N_IDENT) { let lc: *local = localfindnode(c, base.str); if (lc != nil) { hdr_lea = true; hdr_off = lc.off; hdr_ok = true; }; }; // (*p)[lo:hi]: header behind a local ptr-to-slice — cgdelete's // regex_shape twin. if (!hdr_ok && base.kind == syntax.nkind.N_UN) { if (base.op == syntax.tkind.TK_STAR && base.lhs != nil) { if (base.lhs.kind == syntax.nkind.N_IDENT) { let pc: *local = localfindnode(c, base.lhs.str); if (pc != nil) { hdr_off = pc.off; hdr_ok = true; }; }; }; }; // xs[g][lo:hi]: the header IS element g of an outer local slice — // the fold-5a consumer shape (ref/hare/regex/regex.ha:333 // delete(jump_idxs[group_level][..])). Outer stride = the inner // header type's own table size (sti). if (!hdr_ok && base.kind == syntax.nkind.N_INDEX) { if (base.lhs != nil) { if (base.lhs.kind == syntax.nkind.N_IDENT) { let oc: *local = localfindnode(c, base.lhs.str); if (oc != nil) { hdr_idx = true; hdr_off = oc.off; if (sti != nil) { osz = sti.size: i32; }; if (osz <= 0) { let m35c: str = "#35: delete() outer element size unresolved (rule-7)\n"; os.write(2, m35c.ptr, m35c.len: u64); os.exit(1); }; hdr_ok = true; }; }; }; }; if (!hdr_ok) { let m35b: str = "#35: delete() range base shape unsupported (rule-7: local slice ident, deref-of-local, or indexed local slice only)\n"; os.write(2, m35b.ptr, m35b.len: u64); os.exit(1); }; if (hdr_idx) { cgexpr(c, base.rhs); if (osz > 1) { emitline("\tMOVQ\t$"); emitint(osz: i64); emitline(", CX\n"); emitline("\tIMULQ\tCX, AX\n"); }; emitline("\tMOVQ\t"); emitoff(hdr_off: i64); emitline("(BP), CX\n"); emitline("\tADDQ\tCX, AX\n"); } else { if (hdr_lea) { emitline("\tLEAQ\t"); } else { emitline("\tMOVQ\t"); }; emitoff(hdr_off: i64); emitline("(BP), AX\n"); }; emitline("\tPUSHQ\tAX\n"); if (d.rhs != nil) { cgexpr(c, d.rhs); } else { emitline("\tMOVQ\t$0, AX\n"); }; emitline("\tPUSHQ\tAX\n"); if (d.cond != nil) { cgexpr(c, d.cond); } else { emitline("\tMOVQ\t8(SP), CX\n"); emitline("\tMOVQ\t8(CX), AX\n"); }; emitline("\tMOVQ\t(SP), CX\n"); emitline("\tSUBQ\tCX, AX\n"); emitline("\tPUSHQ\tAX\n"); let rll: str = mklabel(c, "rdl_l"); let rle: str = mklabel(c, "rdl_e"); emitlabel(rll); emitline("\tMOVQ\t16(SP), DX\n"); emitline("\tMOVQ\t8(SP), CX\n"); emitline("\tMOVQ\t(SP), AX\n"); emitline("\tADDQ\tCX, AX\n"); emitline("\tMOVQ\t8(DX), BX\n"); emitline("\tCMPQ\tBX, AX\n"); emitline("\tJGE\t"); emitline(rle); emitline("\n"); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", AX\n"); emitline("\tIMULQ\tAX, CX\n"); }; emitline("\tMOVQ\t(DX), BX\n"); emitline("\tADDQ\tCX, BX\n"); emitline("\tMOVQ\t(SP), CX\n"); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", AX\n"); emitline("\tIMULQ\tAX, CX\n"); }; emitline("\tADDQ\tBX, CX\n"); let rk: i32 = 0; for (rk + 8 <= esz) { emitline("\tMOVQ\t"); emitdispreg(rk: i64, "CX"); emitline(", AX\n"); emitline("\tMOVQ\tAX, "); emitdispreg(rk: i64, "BX"); emitline("\n"); rk += 8; }; if (rk + 4 <= esz) { emitline("\tMOVL\t"); emitdispreg(rk: i64, "CX"); emitline(", AX\n"); emitline("\tMOVL\tAX, "); emitdispreg(rk: i64, "BX"); emitline("\n"); rk += 4; }; if (rk + 2 <= esz) { emitline("\tMOVW\t"); emitdispreg(rk: i64, "CX"); emitline(", AX\n"); emitline("\tMOVW\tAX, "); emitdispreg(rk: i64, "BX"); emitline("\n"); rk += 2; }; if (rk + 1 <= esz) { emitline("\tMOVB\t"); emitdispreg(rk: i64, "CX"); emitline(", AX\n"); emitline("\tMOVB\tAX, "); emitdispreg(rk: i64, "BX"); emitline("\n"); rk += 1; }; emitline("\tADDQ\t$1, 8(SP)\n"); emitline("\tJMP\t"); emitline(rll); emitline("\n"); emitlabel(rle); emitline("\tMOVQ\t16(SP), DX\n"); emitline("\tMOVQ\t(SP), AX\n"); emitline("\tMOVQ\t8(DX), BX\n"); emitline("\tSUBQ\tAX, BX\n"); emitline("\tMOVQ\tBX, 8(DX)\n"); emitline("\tADDQ\t$24, SP\n"); }; // cginsert — Hare `insert(xs[idx], v)`: delete()'s twin, the insert-half // of #35. Insert v BEFORE idx; idx==len is a legal end-insert. Lowered // as a DESUGAR to append(xs, v) + a rotate-right of [idx, len): cgappend // contributes grow (rt_ensure) and the whole #34 value-store dispatch // (scalar / str-slice header / tagged widen / struct fill) verbatim — // one boxing choke-point, byte-id by construction — landing v at slot // len-1; the rotate then moves it home through an esz frame scratch. // The rotate is cgdelete's shift loop in reverse (descending j keeps // src j behind dst j+1, the safe memmove-up direction) and, like // delete's, is a same-slice whole-stride raw byte move — no boxing // exists for any element kind. idx evaluates BEFORE the grow (Hare's // left-to-right operand order: insert(xs[len(xs)], v) sees the pre-grow // len); v's evaluation point inherits append's per-kind rules. Bounds // are implicit (no index check, matching delete). Mirrors cmd/w6c/ // cgen.c's N_CALL insert arm instruction-for-instruction (rule-10 // byte-id). fn cginsert(c: *cgen, n: *syntax.node) void = { let d: *syntax.node = n.list; // N_INDEX, checker-validated let base: *syntax.node = d.lhs; let v: *syntax.node = d.next; // esz off the STAMPED base tinfo (#34/#48 discipline — never the // value node). Peel TY_NAMED on indexable AND element, mirroring // cstage's type_chase_named on both (#8 family). let sti: *syntax.tinfo = base.type_: *syntax.tinfo; sti = tichase(sti); let esub: *syntax.tinfo = nil; if (sti != nil) { esub = sti.sub; }; esub = tichase(esub); let esz: i32 = 0; if (esub != nil) { esz = esub.size: i32; }; if (esz <= 0) { let m35c: str = "#35: insert() element size unresolved (rule-7)\n"; os.write(2, m35c.ptr, m35c.len: u64); os.exit(1); }; let hdr_lea: bool = false; let hdr_off: i32 = 0; let hdr_ok: bool = false; if (base.kind == syntax.nkind.N_IDENT) { let lc: *local = localfindnode(c, base.str); if (lc != nil) { hdr_lea = true; hdr_off = lc.off; hdr_ok = true; }; }; // (*p)[i]: the header lives behind a local ptr-to-slice — // delete's regex_shape twin. if (!hdr_ok && base.kind == syntax.nkind.N_UN) { if (base.op == syntax.tkind.TK_STAR && base.lhs != nil) { if (base.lhs.kind == syntax.nkind.N_IDENT) { let pc: *local = localfindnode(c, base.lhs.str); if (pc != nil) { hdr_off = pc.off; hdr_ok = true; }; }; }; }; if (!hdr_ok) { let m35d: str = "#35: insert() base shape unsupported (rule-7: local slice ident or deref-of-local only)\n"; os.write(2, m35d.ptr, m35d.len: u64); os.exit(1); }; // Fresh esz-sized slot per SITE (esz varies; an @-name dedup // would mis-share across element types). Allocated BEFORE the // cgappend body's own scratch allocs — cstage order. let insscr: i32 = localalloc(c, "@insscr", esz, nil); cgexpr(c, d.rhs); // AX = idx emitline("\tPUSHQ\tAX\n"); // Desugar in place and route through cgappend: cgen is // single-pass, base is an lhs node (never on a sibling chain), // and the checker has already validated this call — the mutation // is dead after this emission. The callee rename keeps the AST // consistent with cstage's re-dispatch. n.lhs.str = "append"; n.list = base; base.next = v; cgappend(c, n); if (hdr_lea) { emitline("\tLEAQ\t"); } else { emitline("\tMOVQ\t"); }; emitoff(hdr_off: i64); emitline("(BP), AX\n"); emitline("\tPUSHQ\tAX\n"); emitline("\tMOVQ\tAX, DX\n"); emitline("\tMOVQ\t8(DX), 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(DX), BX\n"); emitline("\tADDQ\tCX, BX\n"); let ik: i32 = 0; for (ik + 8 <= esz) { emitline("\tMOVQ\t"); emitdispreg(ik: i64, "BX"); emitline(", AX\n"); emitline("\tMOVQ\tAX, "); emitoff((insscr + ik): i64); emitline("(BP)\n"); ik += 8; }; if (ik + 4 <= esz) { emitline("\tMOVL\t"); emitdispreg(ik: i64, "BX"); emitline(", AX\n"); emitline("\tMOVL\tAX, "); emitoff((insscr + ik): i64); emitline("(BP)\n"); ik += 4; }; if (ik + 2 <= esz) { emitline("\tMOVW\t"); emitdispreg(ik: i64, "BX"); emitline(", AX\n"); emitline("\tMOVW\tAX, "); emitoff((insscr + ik): i64); emitline("(BP)\n"); ik += 2; }; if (ik + 1 <= esz) { emitline("\tMOVB\t"); emitdispreg(ik: i64, "BX"); emitline(", AX\n"); emitline("\tMOVB\tAX, "); emitoff((insscr + ik): i64); emitline("(BP)\n"); ik += 1; }; emitline("\tMOVQ\t8(DX), AX\n"); emitline("\tSUBQ\t$2, AX\n"); emitline("\tPUSHQ\tAX\n"); let ill: str = mklabel(c, "ins_l"); let ile: str = mklabel(c, "ins_e"); emitlabel(ill); emitline("\tMOVQ\t(SP), CX\n"); emitline("\tMOVQ\t16(SP), DX\n"); emitline("\tCMPQ\tDX, CX\n"); emitline("\tJL\t"); emitline(ile); emitline("\n"); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", AX\n"); emitline("\tIMULQ\tAX, CX\n"); }; emitline("\tMOVQ\t8(SP), DX\n"); emitline("\tMOVQ\t(DX), BX\n"); emitline("\tADDQ\tCX, BX\n"); ik = 0; for (ik + 8 <= esz) { emitline("\tMOVQ\t"); emitdispreg(ik: i64, "BX"); emitline(", AX\n"); emitline("\tMOVQ\tAX, "); emitdispreg((esz + ik): i64, "BX"); emitline("\n"); ik += 8; }; if (ik + 4 <= esz) { emitline("\tMOVL\t"); emitdispreg(ik: i64, "BX"); emitline(", AX\n"); emitline("\tMOVL\tAX, "); emitdispreg((esz + ik): i64, "BX"); emitline("\n"); ik += 4; }; if (ik + 2 <= esz) { emitline("\tMOVW\t"); emitdispreg(ik: i64, "BX"); emitline(", AX\n"); emitline("\tMOVW\tAX, "); emitdispreg((esz + ik): i64, "BX"); emitline("\n"); ik += 2; }; if (ik + 1 <= esz) { emitline("\tMOVB\t"); emitdispreg(ik: i64, "BX"); emitline(", AX\n"); emitline("\tMOVB\tAX, "); emitdispreg((esz + ik): i64, "BX"); emitline("\n"); ik += 1; }; emitline("\tSUBQ\t$1, (SP)\n"); emitline("\tJMP\t"); emitline(ill); emitline("\n"); emitlabel(ile); emitline("\tMOVQ\t16(SP), CX\n"); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", AX\n"); emitline("\tIMULQ\tAX, CX\n"); }; emitline("\tMOVQ\t8(SP), DX\n"); emitline("\tMOVQ\t(DX), BX\n"); emitline("\tADDQ\tCX, BX\n"); ik = 0; for (ik + 8 <= esz) { emitline("\tMOVQ\t"); emitoff((insscr + ik): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\tAX, "); emitdispreg(ik: i64, "BX"); emitline("\n"); ik += 8; }; if (ik + 4 <= esz) { emitline("\tMOVL\t"); emitoff((insscr + ik): i64); emitline("(BP), AX\n"); emitline("\tMOVL\tAX, "); emitdispreg(ik: i64, "BX"); emitline("\n"); ik += 4; }; if (ik + 2 <= esz) { emitline("\tMOVW\t"); emitoff((insscr + ik): i64); emitline("(BP), AX\n"); emitline("\tMOVW\tAX, "); emitdispreg(ik: i64, "BX"); emitline("\n"); ik += 2; }; if (ik + 1 <= esz) { emitline("\tMOVB\t"); emitoff((insscr + ik): i64); emitline("(BP), AX\n"); emitline("\tMOVB\tAX, "); emitdispreg(ik: i64, "BX"); emitline("\n"); ik += 1; }; emitline("\tADDQ\t$24, SP\n"); }; fn cgcall(c: *cgen, n: *syntax.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: *syntax.node = n.lhs; if (callee != nil) { if (callee.kind == syntax.nkind.N_IDENT) { // abort([msg]) / assert(cond[, msg]) — checker-tagged // builtins (callee TY_ERR is the routing key, cstage's // `lhs->type == ty_err` at cmd/w6c/cgen.c:6618,6645); // lower to rt_abort. A user-shadowed abort/assert is // untagged and stays on the regular call path (#58). let bti: *syntax.tinfo = callee.type_: *syntax.tinfo; if (bti != nil && bti.kind == syntax.tykind.TY_ERR) { if (syntax.streq(callee.str, "abort")) { if (n.list != nil) { cgexpr(c, n.list); emitline("\tMOVQ\tAX, DI\n"); emitline("\tMOVQ\tBX, SI\n"); } else { emitline("\tMOVQ\t$0, DI\n"); emitline("\tMOVQ\t$0, SI\n"); }; emitline("\tCALL\trt_abort(SB)\n"); return; }; if (syntax.streq(callee.str, "assert") && n.list != nil) { cgexpr(c, n.list); let skip: str = mklabel(c, "as"); emitline("\tCMPQ\t$0, AX\n"); emitline("\tJNE\t"); emitline(skip); emitline("\n"); let msg: *syntax.node = n.list.next; if (msg != nil) { cgexpr(c, msg); emitline("\tMOVQ\tAX, DI\n"); emitline("\tMOVQ\tBX, SI\n"); } else { emitline("\tMOVQ\t$0, DI\n"); emitline("\tMOVQ\t$0, SI\n"); }; emitline("\tCALL\trt_abort(SB)\n"); emitlabel(skip); return; }; }; if (syntax.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_malloc // + per-field stores. Mirrors cmd/w6c/cgen.c's N_CALL // alloc path. // // Same-module-scope guard: skip the builtin when a fn // `alloc` is declared in the current module (lib/os and // rt/ensure both shadow it). Mirrors cstage check.c's // scope_lookup_prefer gating on the `abort` precedent; // without it, the bare same-module call lands in the // typed-builtin path and shadows the user decl. Task #23. if (syntax.streq(callee.str, "alloc")) { if (n.list != nil) { if (!samemodfn(c, "alloc")) { cgalloc(c, n); return; }; }; }; // `len(x)` Hare builtin — mirror cmd/w6c/cgen.c N_CALL "len" // arm. Required for byte-id when compiler-imported lib code // uses len(fixedarray) (e.g. lib/strconv/decimal.ha's // `len(d.digits)` over the [800]u8 field). Without this // intercept wwstage falls through to a regular CALL len(SB) // while cstage folds to `MOVQ $alen, AX` — rule-10 byte-id // break (#131). // // Dispatch (#10/#41): enumerated fast-paths keep their // pre-fix asm (ident local/global, #235 tuple element, #19 // indexed element, TY_ARRAY const fold), then ONE uniform // header-place route via cgplaceaddr (.len at place+8) for // every other slice/str place — the arm enumeration leaked // four siblings (#235 → #19 → F2 → FA2/FB1), each new operand // shape falling to a cgexpr fallback that returned the slice // DATA POINTER as the length. Non-place operands (call // result, slicing expr, string literal — previously the same // silent ptr-garbage) die LOUD per rule 7. if (syntax.streq(callee.str, "len")) { if (n.list != nil) { let a: *syntax.node = n.list; let at: *syntax.tinfo = a.type_: *syntax.tinfo; let u: *syntax.tinfo = at; u = tichase(u); let hdrish: bool = false; if (u != nil) { if (u.kind == syntax.tykind.TY_SLICE || u.kind == syntax.tykind.TY_STR) { hdrish = true; }; }; if (hdrish && a.kind == syntax.nkind.N_IDENT) { let lc: *local = localfindnode(c, a.str); if (lc != nil) { emitline("\tMOVQ\t"); emitoff((lc.off + 8): i64); emitline("(BP), AX\n"); return; }; // #231: str/slice GLOBAL — the // local-only path above lacked it, // so cgexpr fell through and left // AX=.ptr (not .len). The .len word // lives at the global's address+8; // route the LEAQ through the post-#1 // value mangle (c.curmod) so a // private same-leaf global isn't // mis-resolved. if (isletvar(c, a.str)) { emitline("\tLEAQ\t"); emitfnname(c, a.str, c.curmod); emitline("(SB), CX\n"); emitline("\tMOVQ\t8(CX), AX\n"); return; }; // non-local non-let ident (DATA-backed // def): the old arm fell to the silent // cgexpr fallback. Falls to the // resolver route below. }; // #235: len() of a tuple-element slice/str // (`len(t.N)`). Kept as an enumerated arm: // tuples are not resolver-addressable // (cgplaceaddr has no TY_TUPLE hop — that gap // is #238). Load the element's .len word // directly at BP + element_off + 8, mirroring // the N_IDENT slice arm above and the // tuple-field-offset walk (cgenexpr.ww N_TTUPLE). if (hdrish && a.kind == syntax.nkind.N_DOT && a.lhs != nil && a.lhs.kind == syntax.nkind.N_IDENT) { let lc: *local = localfindnode(c, a.lhs.str); if (lc != nil) { let tn: *syntax.node = lc.tnode; for (tn != nil && tn.kind == syntax.nkind.N_TNAME) { tn = aliaslookup(c, tn.str); }; if (tn != nil) { if (tn.kind == syntax.nkind.N_TTUPLE) { let idx: i32 = fldnumidx(a.str); if (idx >= 0) { let tp: *syntax.node = tn.list; let foff: i32 = 0; let i: i32 = 0; for (i < idx) { if (tp == nil) { i = idx; } else { // C-t0/#22: slot // stride (tupeslot). foff += tupeslotn(tp.lhs); tp = tp.next; i += 1; }; }; if (tp != nil) { emitline("\tMOVQ\t"); emitoff((lc.off + foff + 8): i64); emitline("(BP), AX\n"); return; }; }; }; }; }; // C-t3 (#48): GLOBAL tuple len(g.N) — // LEAQ name(SB) into CX, .len word at // the SLOT offset + 8. Graduates the // C5 loud-stop this shape previously // hit; twin of the cgdot global-tuple // arm and cstage's #235 global base. if (lc == nil) { let gtn: *syntax.node = letvartnode(c, a.lhs.str); for (gtn != nil && gtn.kind == syntax.nkind.N_TNAME) { gtn = aliaslookup(c, gtn.str); }; if (gtn != nil) { if (gtn.kind == syntax.nkind.N_TTUPLE) { let gidx: i32 = fldnumidx(a.str); if (gidx >= 0) { let gtp: *syntax.node = gtn.list; let gfoff: i32 = 0; let gi: i32 = 0; for (gi < gidx) { if (gtp == nil) { gi = gidx; } else { // C-t0/#22: slot // stride (tupeslot). gfoff += tupeslotn(gtp.lhs); gtp = gtp.next; gi += 1; }; }; if (gtp != nil) { emitline("\tLEAQ\t"); emitsymname(c, a.lhs.str); emitline("(SB), CX\n"); emitline("\tMOVQ\t"); emitdispreg((gfoff + 8): i64, "CX"); emitline(", AX\n"); return; }; }; }; }; }; // struct-field N_DOT (`len(s.field)`): the // old fallback returned .ptr as the length. // Falls to the resolver route below. }; // #19: len() of an INDEXED str/slice element // (`len(xs[i])`). The N_INDEX str/slice load // leaves AX=.ptr, BX=.len, CX=.cap — the bare // cgexpr fallback returned AX (the ptr) AS the // length. Shuffle BX (the len word) into AX, // the same MOVQ BX,AX shape as the #14 .len // pseudo-field fix. Same family as #18 (shared // cstage==wwstage gap, not rule-10). if (hdrish && a.kind == syntax.nkind.N_INDEX) { cgexpr(c, a); emitline("\tMOVQ\tBX, AX\n"); return; }; if (u != nil) { if (u.kind == syntax.tykind.TY_ARRAY) { emitline("\tMOVQ\t$"); emitint(u.alen: i64); emitline(", AX\n"); return; }; }; // #10 (F2) + #41 (FA2/FB1): ONE uniform // header-place route for every other slice/str // place — resolve the operand's header address // (cgplaceaddr: deref / index / dot spines) and // read the .len word at +8. if (hdrish) { if (cgplaceaddr(c, a, "BX")) { emitline("\tMOVQ\t8(BX), AX\n"); return; }; }; let mlen: str = "#10/#41: len() operand shape not place-resolvable (rule-7)\n"; os.write(2, mlen.ptr, mlen.len: u64); os.exit(1); }; }; // free(x) — documented NO-OP, mirror of cstage's cgexpr // N_CALL free arm (#27): ww has no free by design // (rt/alloc.s:30 — the bump allocator cannot reclaim a // mid-chunk pointer; process exit does). The operand is // still evaluated — Hare's free(expr) evaluates expr — // so Hare code ports verbatim with its side effects // intact. Pre-#27 wwstage fell through to a generic // CALL free → undefined reference at link. if (syntax.streq(callee.str, "free")) { if (n.list != nil) { if (n.list.next == nil) { cgexpr(c, n.list); return; }; }; }; // delete(xs[i]) / delete(xs[lo:hi]) — #35 // delete-half + fold-5a P2 range form; mirror of // cstage cgen.c's N_CALL delete arm (which branches // on d->kind == N_SLICE internally). if (syntax.streq(callee.str, "delete")) { if (n.list != nil) { if (n.list.next == nil) { if (n.list.kind == syntax.nkind.N_SLICE) { cgdeleterange(c, n); return; }; cgdelete(c, n); return; }; }; }; // insert(xs[idx], v) — #35 insert-half; mirror of // cstage cgen.c's N_CALL insert arm. if (syntax.streq(callee.str, "insert")) { if (n.list != nil) { if (n.list.next != nil) { if (n.list.next.next == nil) { cginsert(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. // // N_DOT (`mod.fn(...)`) covers cross-module calls; pre-#28 wwstage // only handled N_IDENT, leaving N_DOT calls without widening // detection — pushargsrev then fell through to the N_IDENT-slice // fast path and dropped the variant tag word on widened slice args. // Cstage finds params via the checker-set `n->lhs->type`, sidestepping // the name-driven registry entirely (cmd/w6c/cgen.c:4161-4165). let calleeparams: *syntax.node = nil; if (callee != nil) { // #59.8: a LOCAL fn-ptr callee (bare local or struct-field) // reads its own N_TFN params — the registry lookup below // mis-hits when the local's name shadows the current module // (lib/log's `log.println(log, args...)`), handing the // module fn's signature to arg prep while the CALL target // resolved the field. One resolver for both (fnptrcalleetfn). let lft: *syntax.node = fnptrcalleetfn(c, callee); if (lft != nil) { calleeparams = lft.list; } else { if (callee.kind == syntax.nkind.N_IDENT) { calleeparams = fnparamslookup(c, callee.str); } else { if (callee.kind == syntax.nkind.N_DOT) { let cmod: str; cmod.ptr = nil; cmod.len = 0; if (callee.lhs != nil) { if (callee.lhs.kind == syntax.nkind.N_IDENT) { cmod = callee.lhs.str; }; }; calleeparams = fnparamslookupmod(c, callee.str, cmod); }; }; }; }; // Hare-style variadic last param: gather N tail args into a // frame-resident [N]T (vararg_d slot) plus a 24B slice // descriptor (vararg_sl slot), 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. Slot // names come from mklabel (mirrors cstage cgen.c:5427/5431) so // the shared labelseq advances in lockstep — vararg_d only when // nvar>0, vararg_sl always — keeping later match labels aligned. { let nfixed_v: i32 = 0; let varp: *syntax.node = callee_variadic_param(c, callee, &nfixed_v); if (varp != nil) { let nargs0: i32 = 0; let aw: *syntax.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: *syntax.node = n.list; let kk: i32 = 0; for (kk < nfixed_v) { aaf = aaf.next; kk += 1; }; if (aaf != nil) { if (aaf.kind == syntax.nkind.N_SPREAD) { forwarding = true; }; }; }; if (forwarding) { let prev: *syntax.node = nil; let cur2: *syntax.node = n.list; let kk2: i32 = 0; for (kk2 < nfixed_v) { prev = cur2; cur2 = cur2.next; kk2 += 1; }; let inner: *syntax.node = cur2.lhs; if (inner != nil) { inner.next = nil; }; if (prev == nil) { n.list = inner; } else { prev.next = inner; }; } else { // Use raw element size, not stack-padded // slotsize. cstage cmd/w6c/cgen.c cgcall // gathers a `T...` slice at velem->size stride // (MOVL for u32, MOVB for u8); the callee // `arg[i]` reads at the same raw stride. wwstage // previously sized through slotsize which pads // scalars to 8, mismatching the stride at the // callee read site — runtime miscompile in // `(rune...)` callees per #36. // check.ww installparams promotes varp.lhs to // []T (mirrors cstage check.c:455 tp->type // wrap). Element predicates / esz read varp.lhs // .lhs; Ken's gate: only deref when the wrap // shape is confirmed N_TSLICE (mirrors cstage // cgen.c:4352 `vsu->kind == TY_SLICE` guard). let velem: *syntax.node = varp.lhs; if (varp.lhs != nil && varp.lhs.kind == syntax.nkind.N_TSLICE) { velem = varp.lhs.lhs; }; let esz: i32 = 8; if (velem != nil) { if (velem.kind == syntax.nkind.N_TNAME) { let ps: i32 = aliasprimsize(c, velem.str); if (ps > 0) { esz = ps; } else { esz = slotsize(c, velem); }; } else { esz = slotsize(c, velem); }; }; if (esz < 1) { esz = 1; }; // #38b: a >48B tagged variadic ELEMENT would // need the memory convention inside the vararg // gather buffer — unwired (rule 7). cstage twin // guards before its v_is_tagged gather. if (velem != nil) { if (taggedmemargsize(velem.type_: *syntax.tinfo) > 0) { let mv: str = "#38b: >48B tagged variadic element unwired\n"; os.write(2, mv.ptr, mv.len: u64); os.exit(1); }; }; let velemtagged: bool = istaggedtype(c, velem); let velemstr: bool = isstrtype(c, velem); let velemslice: bool = isslicetype(c, velem); let doff: i32 = 0; if (nvar > 0) { // mklabel, not a separate vararg counter, so // labelseq advances with cstage cgen.c:5427 — the // slot name never reaches asm, but the shared // counter numbers later match labels. let dname: str = mklabel(c, "vararg_d"); doff = localadd(c, dname, nvar * esz, nil); }; // #60: vararg gather builds a {ptr,len,cap} slice // descriptor — route through tyslicesize so #34's // slice-header bump propagates here. varp.lhs is // already the []T wrap from installparams, so we // consume it directly (re-slicewrap → [][]T). // vararg_sl always allocated (cstage cgen.c:5431), // bumping labelseq whether or not nvar>0. let sname: str = mklabel(c, "vararg_sl"); let soff: i32 = localadd(c, sname, tyslicesize(): i32, varp.lhs); let aa2: *syntax.node = n.list; let kk3: i32 = 0; for (kk3 < nfixed_v) { aa2 = aa2.next; kk3 += 1; }; let j: i32 = 0; let prevarg: *syntax.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) { // dst is the per-element tagged type; // pass velem (cstage cgen.c:4382 passes // velem, not the slice wrap vsu). cgwidentaggedstore(c, velem.type_: *syntax.tinfo, aa2, "BP", 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 = tnodestoreop(c, varp.lhs, esz); 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: *syntax.node = syntax.newnode(syntax.nkind.N_IDENT, "", 0, 0); sn.str = sname; // Synthesised after the checker has run, so the // asserttyped bail (check.ww) never stamps it. // Stamp the variadic param's []T slice tinfo // (resolvefnbody resolve-walks varp.lhs) so the // downstream value-class reads see a non-nil // stamp — the one cgen node the bail can't cover. sn.type_ = varp.lhs.type_; if (prevarg == nil) { n.list = sn; } else { prevarg.next = sn; }; }; }; }; // C-variadic (bare `...`) detection: drives the SysV §3.5.7 AL= // XMM-count emit (below, before CALL) and the f32→f64 variadic- // tail promotion in pushargsrev (#14). cvarnfixed is the fixed- // param count, or -1 when the callee is not C-variadic. Mirror of // cstage's `cu && cu->kind == TY_FN && cu->variadic` gate. let cvarnfixed: i32 = -1; { let nfx: i32 = 0; if (calleecvariadic(c, callee, &nfx)) { cvarnfixed = nfx; }; }; // #38b: two-phase push — MEMORY-class (>48B tagged) args staged // first so they sit BELOW every register-class word; the pop loop // drains a strict prefix and never touches them. memwords feeds // the caller-cleanup ADDQ (with the mix guard below). let memwords: i32 = pushargsrev(c, n.list, calleeparams, true, 0, cvarnfixed); let nargs: i32 = pushargsrev(c, n.list, calleeparams, false, 0, cvarnfixed); // sret call (#23): callee returns plain TY_STRUCT > 24B. The // dest pointer lands in RDI; start intidx at 1 to skip RDI in // the user-arg pop loop and emit `LEAQ off(BP), DI` AFTER all // pops have finished (so they don't clobber RDI). The dest off // is either the receive site's slot (c.sretdestoff, propagated // from cglet / cgassign ident) or the per-fn @sretscr discard // slot, sized at first use per #15/#26c. let sretcs: i32 = callsretsize(c, n); let sretcalloff: i32 = 0; // #220: GLOBAL dest — RDI gets `LEAQ name(SB)` below; no @sretscr // slot (the callee writes the struct straight into g's storage). let sretdestn: *syntax.node = nil; if (sretcs > 0) { if (c.sretdestnode != nil) { sretdestn = c.sretdestnode; c.sretdestnode = nil; } else { if (c.sretdestoff != 0) { sretcalloff = c.sretdestoff; c.sretdestoff = 0; } else { sretcalloff = localadd(c, "@sretscr", sretcs, nil); };}; }; // 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; if (sretcs > 0) { intidx = 1; }; let fpidx: i32 = 0; let a: *syntax.node = n.list; let dparam: *syntax.node = calleeparams; let popped: i32 = 0; let stackslots: i32 = 0; let argidx: i32 = 0; for (a != nil) { // argidx is this arg's 0-based position; captured before any // continue so the C-variadic f32-promotion check below tracks // the push-side argidx for every arg shape (#14). let curargidx: i32 = argidx; argidx += 1; // #38b: MEMORY-class arg — its words sit below the pop // region and stay on the stack for the callee; nothing to // drain. Same param-keyed-else-arg-keyed detection as // pushargsrev (a widened concrete arg is mem-class only // via its param). let dmemsz: i32 = 0; if (dparam != nil) { if (dparam.kind == syntax.nkind.N_PARAM) { if (dparam.op != syntax.tkind.TK_ELLIPSIS) { if (dparam.lhs != nil) { dmemsz = taggedmemargsize(dparam.lhs.type_: *syntax.tinfo); }; }; }; }; if (dmemsz == 0) { dmemsz = taggedmemargsize(a.type_: *syntax.tinfo); }; if (dmemsz > 0) { if (dparam != nil) { dparam = dparam.next; }; a = a.next; continue; }; // Widen-first pop (#30/#48): a concrete arg widened into a // tagged-union param was pushed as slotsize/8 GP words (tag + // payload). Drain those words into the INTEGER arg cursor // BEFORE the float check below — else a widened f64-source box // gets misclassified as a float arg (its tag word drained into // X0, #48), and a float arg FOLLOWING a widened arg reads the // widened box's leftover payload word (#30). Mirror of cstage's // precomputed widen[i] branch (cmd/w6c/cgen.c cgcall, popped // before node_isfloat). argtaggedwidensz is the shared SSoT with // pushargsrev's push count. let dwsz: i32 = argtaggedwidensz(c, a, dparam); if (dwsz >= 16) { let dwb: i32 = dwsz / 8; let dwk: i32 = 0; for (dwk < dwb) { if (intidx < 6) { emitline("\tPOPQ\t"); emitline(argregname(intidx)); emitline("\n"); intidx += 1; } else { stackslots += 1; }; popped += 1; dwk += 1; }; if (dparam != nil) { dparam = dparam.next; }; a = a.next; continue; }; let fk: i32 = 0; if (a != nil) { let at: *syntax.tinfo = a.type_: *syntax.tinfo; if (syntax.typeisf32(at)) { fk = 1; } else { if (syntax.typeisfloat(at)) { fk = 2; }; }; }; if (fk != 0) { let mov: str = "MOVSD"; if (fk == 1) { mov = "MOVSS"; }; // #14: a C-variadic-tail f32 was promoted to f64 at push // (CVTSS2SD + MOVSD), so its slot reloads MOVSD. Mirror // cstage cmd/w6c/cgen.c promote_f32 pop. if (cvarnfixed >= 0 && curargidx >= cvarnfixed && fk == 1) { mov = "MOVSD"; }; 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 { // #68: drain over the PARAM tuple element widths for a // tuple LITERAL arg (the declared-tagged box words pop // together with the i64 that follows), mirroring the // param-aware send; else the source tuple type. let dptt: *syntax.node = nil; if (a.kind == syntax.nkind.N_TUPLE) { if (dparam != nil) { if (dparam.kind == syntax.nkind.N_PARAM && dparam.lhs != nil) { let dtn2: *syntax.node = dparam.lhs; for (dtn2 != nil && dtn2.kind == syntax.nkind.N_TNAME) { dtn2 = aliaslookup(c, dtn2.str); }; if (dtn2 != nil) { if (dtn2.kind == syntax.nkind.N_TTUPLE) { dptt = dtn2; }; }; }; }; }; let tuparg: *syntax.node = dptt; if (tuparg == nil) { tuparg = nodetuplearg(c, a); }; if (tuparg != nil) { // #163/#32: drain the tuple's staged words (slot+0 // pushed first) into the SysV arg cursor by SysV class // — a float MOVSD/MOVSS off (SP) into the next XMM, // else POPQ into the next INTEGER arg reg; a slice/str // its 3 words, a declared-tagged box its eslot words // (#68). Reg overflow loud-stops (rule 7); the // partial-spill stitch is out of scope (twin of #164). // C-t2: nodetuplearg admits ident/literal/unwrap // sources; a literal's elements are VALUE exprs, // classified the way the @tupargscr restage classified // them (kind-discriminated twin walk). The param-aware // path (dptt) walks declared element TYPE nodes. let tuplit: bool = false; if (dptt == nil) { if (tuparg.kind == syntax.nkind.N_TUPLE) { tuplit = true; }; }; let p: *syntax.node = tuparg.list; for (p != nil) { let et: *syntax.node = p.lhs; if (tuplit) { et = p; }; if (isfloattype(c, et)) { if (fpidx >= 8) { let msg: str = "tuple arg float element overflows SSE arg regs (X0..X7); stitch out of scope, see #163\n"; os.write(2, msg.ptr, msg.len: u64); os.exit(1); }; let mov: str = "MOVSD"; if (isf32type(c, et)) { mov = "MOVSS"; }; emitline("\t"); emitline(mov); emitline("\t(SP), "); emitline(fargregname(fpidx)); emitline("\n"); emitline("\tADDQ\t$8, SP\n"); fpidx += 1; popped += 1; } else { // eslot — full slot split; on the declared // path tupeslotn reads the element TYPE node // directly (#68 box-aware), else wide-vs-scalar // off the literal VALUE node (#22 accessor scale). let eb: i32 = 1; if (tuplit) { let wide: bool = nodeisstr(c, et) || nodeisslice(c, et); if (wide) { eb = (tyslicesize() / 8i64): i32; }; } else { eb = tupeslotn(et) / 8; }; if (intidx + eb > 6) { let msg: str = "tuple arg element overflows integer arg regs (DI/SI/DX/CX/R8/R9); stitch out of scope, see #163\n"; os.write(2, msg.ptr, msg.len: u64); os.exit(1); }; let k: i32 = 0; for (k < eb) { emitline("\tPOPQ\t"); emitline(argregname(intidx)); emitline("\n"); intidx += 1; popped += 1; k += 1; }; }; p = p.next; }; } else { let stfc: i32 = 0; if (a.kind == syntax.nkind.N_IDENT) { let lc: *local = localfindnode(c, a.str); if (lc != nil) { stfc = structfloatclass(c, lc.tnode); } else { // #31 EXCEPTION (align-UP): a module-global float-bearing // struct arg — lc is nil, so the local-only stfc stayed 0 and // the drain fell to all-GP (X0 never loaded). Key the float- // class off the global's declared tnode (letvartnode), the same // SysV classification the local path uses. cstage keys // structfloatclass off the operand TYPE, not a local slot. let gtn: *syntax.node = letvartnode(c, a.str); if (gtn != nil) { stfc = structfloatclass(c, gtn); }; }; }; if (stfc != 0) { // #165: float-bearing struct arg — drain by SysV // eightbyte class: a lone-f64 eightbyte MOVSD off // (SP) into the next XMM (X0..X7), a pure-INT // eightbyte POPQ into the next INTEGER arg reg // (DI/SI/..). The struct-ident push staged raw slot // words (class-independent); only the drain differs. // Gated to qualifying floats; all-int + f32-packed // keep the generic pop below. Reg overflow loud- // stops (rule 7), the partial-spill stitch out of // scope (#163 twin). let nb: i32 = stfc & 15; let e: i32 = 0; for (e < nb) { let issse: bool = (stfc & (16 << e)) != 0; if (issse) { if (fpidx >= 8) { let msg: str = "float struct arg eightbyte overflows SSE arg regs (X0..X7); stitch out of scope, see #165\n"; os.write(2, msg.ptr, msg.len: u64); os.exit(1); }; emitline("\tMOVSD\t(SP), "); emitline(fargregname(fpidx)); emitline("\n"); emitline("\tADDQ\t$8, SP\n"); fpidx += 1; } else { if (intidx >= 6) { let msg: str = "float struct arg eightbyte overflows integer arg regs (DI/SI/DX/CX/R8/R9); stitch out of scope, see #165\n"; os.write(2, msg.ptr, msg.len: u64); os.exit(1); }; emitline("\tPOPQ\t"); emitline(argregname(intidx)); emitline("\n"); intidx += 1; }; popped += 1; e += 1; }; } else { let extra: i32 = 0; // str IS []u8: 3-word arg, same as slice (#1/Phase 3). if (nodeisstr(c, a)) { extra = 2; }; if (nodeisslice(c, a)) { extra = 2; }; // #21: tagged-CALL arg was pushed AX/DX/CX/R8 high→low // by pushargsrev; size the per-arg pop to match so the // next arg's POPQ doesn't land on residual tag/payload // words and shift intidx out of sync. let tcs: i32 = taggedcallslot(c, a); if (tcs > 0) { extra = tcs / 8 - 1; }; // #271: array / >16B-struct / non-ident 16B-struct // aggregate arg — pushargsrev staged ceil(sz/8) words; // drain exactly that many so intidx tracks per-arg // (the ≤16B struct IDENT case is the stfc branch // above). Mirror of cstage node_isaggarg drain arm. let aggsz: i32 = aggargsizetn(a.type_: *syntax.tinfo); if (aggsz > 0) { extra = (aggsz + 7) / 8 - 1; }; let words: i32 = 1 + extra; let w: i32 = 0; for (w < words) { if (intidx < 6) { emitline("\tPOPQ\t"); emitline(argregname(intidx)); emitline("\n"); intidx += 1; } else { stackslots += 1; }; popped += 1; w += 1; }; }; }; }; if (dparam != nil) { dparam = dparam.next; }; 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; }; // #38b: MEMORY-class args and register-overflow spill words cannot // coexist — the callee's positive-BP cursor walks params in // declaration order, but the residual region puts spill words // below every mem copy. Loud-stop (rule 7); cgfnparams holds the // mirror check. The merged count feeds the caller-cleanup ADDQ. if (memwords > 0 && stackslots > 0) { let mm: str = "#38b: >48B tagged arg mixed with register-overflow stack args unwired\n"; os.write(2, mm.ptr, mm.len: u64); os.exit(1); }; stackslots += memwords; // `callee` is already in scope from line 2827; reuse it. Pre-#32 // silent-redecl masked the second `let callee` here as a no-op // (same value, same fn-body scope post-#27). 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 == syntax.nkind.N_IDENT) { let cn: str = callee.str; if (localfindnode(c, cn) != nil) { isfnptrcall = true; }; }; // #181-cgen: a non-named callee (`(*f)(...)` → N_UN TK_STAR, // or any other expression-valued fn) is an indirect call. // cgexpr the callee into AX; CALL AX. Mirrors cstage's // default-fallthrough at cmd/w6c/cgen.c:5918-5921 which // catches every callee shape that isn't a bare-IDENT module // fn or N_DOT module-qualified call. Pre-fix wwstage emitted // `CALL (SB)` (empty symbol) for the N_UN-callee shape — the // IDENT/DOT name-emit branches missed and isfnptrcall stayed // false. if (callee.kind != syntax.nkind.N_IDENT && callee.kind != syntax.nkind.N_DOT) { isfnptrcall = true; }; if (callee.kind == syntax.nkind.N_DOT) { // #59.8: same resolver as the arg-classification sites // (fnptrcalleetfn) so CALL target and arg prep cannot // disagree within one call. if (fnptrcalleetfn(c, callee) != nil) { isfnptrcall = true; }; }; }; // sret hidden first-arg (#23): load &dest into RDI AFTER all // user-arg pops have finished — intidx started at 1 so RDI was // never written. The CALL emit follows immediately. // // Forwarding (task #9 follow-up): when outer's `return f();` // forwards through an sret callee, source RDI from outer's // saved @sretarg — inner writes directly into outer's caller- // prealloc dest. No temporary in outer's frame. The @sretscr // slot stays reserved for byte-id with cstage; it goes unused // on the forwarding branch. if (sretcs > 0) { if (c.sretforward != 0) { let sretargoff: i32 = localfind(c, "@sretarg"); emitline("\tMOVQ\t"); emitoff(sretargoff: i64); emitline("(BP), DI\n"); c.sretforward = 0; } else { if (sretdestn != nil) { // #220: sret into a GLOBAL — RDI = &g(SB). emitline("\tLEAQ\t"); emitsymname(c, sretdestn.str); emitline("(SB), DI\n"); } else { emitline("\tLEAQ\t"); emitoff(sretcalloff: i64); emitline("(BP), DI\n"); };}; }; // SysV §3.5.7: a C-variadic call sets AL to the number of vector // (XMM) regs used to pass the variable float args — the callee // gates its xmm-save-area stores on `test %al,%al`, so a wrong AL // makes va_arg(double) read garbage. fpidx is the XMM cursor // (capped at 8 in the pop loop). Emitted after any sret-RDI LEAQ, // right before CALL. Mirror cstage cmd/w6c/cgen.c (the imm→reg MOVQ // idiom carries AL since MOVL-imm has no w6a encoding; AL = low byte, // fpidx <= 8). Ref ref/qbe/amd64/sysv.c:384. if (cvarnfixed >= 0) { if (fpidx > 0) { emitline("\tMOVQ\t$"); emitint(fpidx: i64); emitline(", AX\n"); } else { emitline("\tXORQ\tAX, AX\n"); }; }; 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 == syntax.nkind.N_IDENT) { // Bare `f()` — same-module by ww's resolver, // so c.curmod is the disambiguation hint. calleename = callee.str; emitfnname(c, calleename, c.curmod); } else { if (callee.kind == syntax.nkind.N_DOT) { // `m.f()` — pass the explicit module bareword // so cross-module same-leaf exports resolve. calleename = callee.str; let hint: str; hint.ptr = nil; hint.len = 0; if (callee.lhs != nil) { if (callee.lhs.kind == syntax.nkind.N_IDENT) { hint = usehint(c, callee.lhs.str); }; }; emitfnname(c, calleename, hint); };}; }; 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"); }; // str IS []u8: a str-returning callee leaves AX=ptr, BX=len, // CX=cap — same as a slice, so there is no receive-side shuffle // (#1/Phase 3). return; }; fn cgassign(c: *cgen, n: *syntax.node) void = { let lhs: *syntax.node = n.lhs; // #16: a single-dot aggregate-field unwrap store whose base is a // module-GLOBAL value-struct (`g.f = mk()!`) or a CHAINED struct // field (`o.m.f = mk()!`). The #12 single-dot arm covered only a // LOCAL / via-ptr base (its enclosing block requires localfindnode // != nil), so the global case fell to the generic single-word store // (DROPPED w1/w2) and the chained case never reached any field arm // (SILENT both-stage, byte-id blind). Route the dest ADDRESS through // cgplaceaddr (global LEAQ root + chained deref+offset spine) and // feed the SAME {AX,DX,CX} producer-shift materialise. cgplaceaddr // clobbers AX/CX, so it runs BEFORE cgexpr(rhs) and the address is // saved across the call. In-cap struct field only (#12 scope); // float/over-cap loud-stop at the producer. Local/via-ptr single-dot // stays on the #12 arm. Mirrors cstage cgen.c N_ASSIGN #16 arm. if (lhs != nil) { if (lhs.kind == syntax.nkind.N_DOT && lhs.lhs != nil && n.op == syntax.tkind.TK_ASSIGN && n.rhs != nil && (n.rhs.kind == syntax.nkind.N_TRYUNW || n.rhs.kind == syntax.nkind.N_TRYPROP)) { let db: *syntax.node = lhs.lhs; let chained: bool = db.kind == syntax.nkind.N_DOT; let globalbase: bool = false; if (db.kind == syntax.nkind.N_IDENT) { if (localfindnode(c, db.str) == nil) { if (isletvar(c, db.str)) { let dbu: *syntax.tinfo = tichase(db.type_: *syntax.tinfo); if (dbu != nil) { if (dbu.kind == syntax.tykind.TY_STRUCT) { globalbase = true; }; }; }; }; }; if (chained || globalbase) { let fu: *syntax.tinfo = tichase(lhs.type_: *syntax.tinfo); if (fu != nil) { if (fu.kind == syntax.tykind.TY_STRUCT) { let ssz: i32 = fu.size: i32; if (ssz <= 24) { // #14: choke-point now stores every in-cap tail; 3/5/6/7 no longer dropped to a lone narrow MOV. if (!cgplaceaddr(c, lhs, "BX")) { let m16: str = "#16: global/chained aggregate unwrap field dest unresolved (cgplaceaddr)\n"; os.write(2, m16.ptr, m16.len: u64); os.exit(1); }; // Fresh per lowering: cgexpr(rhs) may recursively // emit another #16 assignment while this address is // live. Sharing one per-fn slot would let the inner // assignment overwrite the outer receiver. let dstscr: i32 = localalloc(c, "@unwrapdst", 8, nil); emitline("\tMOVQ\tBX, "); emitoff(dstscr: i64); emitline("(BP)\n"); cgexpr(c, n.rhs); emitline("\tMOVQ\t"); emitoff(dstscr: i64); emitline("(BP), BX\n"); cgaggregstore(c, "BX", 0, ssz, false); return; }; }; }; }; }; }; // #145 (c1.5a): bulk slice-copy-assign `s.arr[lo:hi] = bs` (LHS is // N_SLICE). No legacy cgassign arm catches N_SLICE — the statement // silently emitted nothing (both stages, byte-id-green, #263-class). // Twin of cstage cgen.c N_ASSIGN N_SLICE arm (full WHY there). // cgexpr(lhs) routes to cgslice and leaves AX = dst ptr (base+lo*esz), // BX = hi-lo (element count), CX = cap; slicebaseesz gives the SAME // esz cgslice scaled the ptr by, so BX*esz is the byte count. Then a // runtime-counted byte-granular copy from bs.ptr — byte loop because // the length is RUNTIME (w6a has no REP/MOVSB). Only plain `=`. The // Hare len(bs)==hi-lo assert is task #149 (rule-7: documented, not a // c2 blocker — appendlit's lengths are equal by construction). if (lhs != nil) { if (lhs.kind == syntax.nkind.N_SLICE && n.op == syntax.tkind.TK_ASSIGN) { let esz: i32 = slicebaseesz(c, lhs.lhs); cgexpr(c, lhs); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", DX\n"); emitline("\tIMULQ\tDX, BX\n"); }; emitline("\tPUSHQ\tAX\n"); emitline("\tPUSHQ\tBX\n"); cgexpr(c, n.rhs); emitline("\tMOVQ\tAX, SI\n"); emitline("\tPOPQ\tCX\n"); emitline("\tPOPQ\tDI\n"); let top: str = mklabel(c, "scpy"); let end: str = mklabel(c, "scpe"); emitlabel(top); emitline("\tCMPQ\t$0, CX\n"); emitline("\tJLE\t"); emitline(end); emitline("\n"); emitline("\tMOVB\t(SI), AX\n"); emitline("\tMOVB\tAX, (DI)\n"); emitline("\tADDQ\t$1, SI\n"); emitline("\tADDQ\t$1, DI\n"); emitline("\tSUBQ\t$1, CX\n"); emitline("\tJMP\t"); emitline(top); emitline("\n"); emitlabel(end); return; };}; // #20 (task): struct-lit rhs into an INDEXED struct element — // `a[i] = pt{...}`, `(*ts)[i].caps[k] = capture{...}` — a // DEREF place (`*p = pt{...}`) or an indexed-base FIELD place // (`a[i].f = pt{...}`, same class) skips the legacy arms and // routes to the resolver aggregate arm below (the single // @placescr funnel). The legacy arms' rhs handling (#270-1b // ident/dot/deref gate; deref scalar store; the a[i].f // fldstoreop tail) let the lit fall to a scalar tail: // cgexpr(N_STRUCTLIT) emits nothing (AX=0) and one MOVQ zeroed // the place's first word — every field silently dropped, a // leading str header trashed. let placeslit: bool = false; let placedotidx: bool = false; if (lhs != nil) { if (lhs.kind == syntax.nkind.N_DOT && lhs.lhs != nil) { if (lhs.lhs.kind == syntax.nkind.N_INDEX) { placedotidx = true; }; }; if ((lhs.kind == syntax.nkind.N_INDEX || (lhs.kind == syntax.nkind.N_UN && lhs.op == syntax.tkind.TK_STAR) || placedotidx) && n.op == syntax.tkind.TK_ASSIGN && n.rhs != nil) { if (n.rhs.kind == syntax.nkind.N_STRUCTLIT) { let iet: *syntax.tinfo = lhs.type_: *syntax.tinfo; iet = tichase(iet); if (iet != nil) { if (iet.kind == syntax.tykind.TY_STRUCT) { placeslit = true; }; }; }; }; }; // #49 (#31-A fold): an aggregate pointee diverts the whole // deref-assign to the resolver aggregate arm below — the scalar // tail there stored ONE word of `*p = s` (#31-A); tuple-lit // (#31-E) and call (#31-G) rhs now die loud there instead of // silently truncating. str/slice pointees keep their 3-word arm // (byte-id-pinned). Mirror of cstage deref_agg. let derefagg: bool = false; if (lhs != nil) { if (lhs.kind == syntax.nkind.N_UN && lhs.op == syntax.tkind.TK_STAR && n.op == syntax.tkind.TK_ASSIGN) { let du: *syntax.tinfo = lhs.type_: *syntax.tinfo; du = tichase(du); if (du != nil) { if (du.kind == syntax.tykind.TY_STRUCT || du.kind == syntax.tykind.TY_ARRAY || du.kind == syntax.tykind.TY_TUPLE) { derefagg = true; }; }; }; }; // 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 == syntax.nkind.N_IDENT) { if (lhs.str.len == 0) { if (n.op == syntax.tkind.TK_ASSIGN) { cgexpr(c, n.rhs); return; }; }; }; }; // Task #32: an array-LITERAL rhs at assignment is unwired for // EVERY place kind (ident reassign, index, deref, dot) — only // decl-init fills. Pre-#32 the same scalar tail zeroed one // word silently; die loud until the fill lands. Slice-typed // places are already loud in the checker. if (n.op == syntax.tkind.TK_ASSIGN && lhs != nil && n.rhs != nil) { if (n.rhs.kind == syntax.nkind.N_ARRLIT) { let alt: *syntax.tinfo = lhs.type_: *syntax.tinfo; alt = tichase(alt); if (alt != nil) { if (alt.kind == syntax.tykind.TY_ARRAY) { let mal: str = "array-literal store at assignment unwired (task #32)\n"; os.write(2, mal.ptr, mal.len: u64); os.exit(1); }; }; }; }; // A plain aggregate field-to-field assignment is a memory copy, not // a scalar expression/store. The direct-field arms below enumerate // CALL, STRUCTLIT and local IDENT producers; an addressable DOT/INDEX/ // deref rhs fell through, so a 16-byte time.instant copied one word. // Resolve both places through the existing address funnels and use the // canonical tail-aware aggregate copier. Calls/literals/unwraps and // tagged/str/slice fields remain on their specialized ABI paths. if (lhs != nil && n.rhs != nil && n.op == syntax.tkind.TK_ASSIGN) { if (lhs.kind == syntax.nkind.N_DOT && lhs.lhs != nil && (lhs.lhs.kind == syntax.nkind.N_IDENT || lhs.lhs.kind == syntax.nkind.N_DOT || (lhs.lhs.kind == syntax.nkind.N_UN && lhs.lhs.op == syntax.tkind.TK_STAR))) { let au: *syntax.tinfo = lhs.type_: *syntax.tinfo; au = tichase(au); let memrhs: bool = n.rhs.kind == syntax.nkind.N_IDENT || n.rhs.kind == syntax.nkind.N_DOT || n.rhs.kind == syntax.nkind.N_INDEX || (n.rhs.kind == syntax.nkind.N_UN && n.rhs.op == syntax.tkind.TK_STAR); if (au != nil && memrhs) { if (au.kind == syntax.tykind.TY_STRUCT || au.kind == syntax.tykind.TY_ARRAY || au.kind == syntax.tykind.TY_TUPLE) { if (!cgplaceaddr(c, lhs, "BX")) { let md: str = "aggregate field destination unresolved\n"; os.write(2, md.ptr, md.len: u64); os.exit(1); }; emitline("\tPUSHQ\tBX\n"); if (!aggargsrcaddr(c, n.rhs, "SI")) { let ms: str = "aggregate field source unresolved\n"; os.write(2, ms.ptr, ms.len: u64); os.exit(1); }; emitline("\tPOPQ\tBX\n"); aggcopy(c, au.size: i32); return; }; }; }; }; // #21: a COMPOUND op on a whole tagged-union IDENT (`g OP= v` // with g:(int|bool)) is nonsense — the ident load-combine-store // tail below reads and writes one word of the {payload,tag} box, // corrupting the tag. Reject loud here, the ident twin of the #18 // deref / #133 index rejects; the byte-id twin of the cstage // guard. Plain `=` (the tagged-ident reassign arm just below) is // untouched. if (lhs != nil) { if (lhs.kind == syntax.nkind.N_IDENT && n.op != syntax.tkind.TK_ASSIGN) { let itu: *syntax.tinfo = tichase(lhs.type_: *syntax.tinfo); if (itu != nil) { if (itu.kind == syntax.tykind.TY_TAGGED) { let m21: str = "ident compound on tagged not wired (#21/rule-7)\n"; os.write(2, m21.ptr, m21.len: u64); os.exit(1); }; }; }; }; // 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 == syntax.nkind.N_IDENT) { if (n.op == syntax.tkind.TK_ASSIGN) { let lc: *local = localfindnode(c, lhs.str); if (lc != nil) { if (istaggedtype(c, lc.tnode)) { // #38b: an sret-classified tagged CALL // result is in memory, not the cursor — // an exact-type reassign sret's into the // local's own slot; a widening receive // needs mem-to-mem tag-remap (#40). // Mirrors cstage cgen.c N_ASSIGN tagged // arm + the generic sret receive. let asret: i32 = 0; if (n.rhs != nil) { if (n.rhs.kind == syntax.nkind.N_CALL) { asret = callsretsize(c, n.rhs); }; }; if (asret > 0) { let aru: *syntax.tinfo = n.rhs.type_: *syntax.tinfo; aru = tichase(aru); let alu: *syntax.tinfo = lc.tnode.type_: *syntax.tinfo; alu = tichase(alu); let aexact: bool = false; if (aru != nil && aru == alu) { aexact = true; } else { if (syntax.typeeq(n.rhs.type_: *syntax.tinfo, lc.tnode.type_: *syntax.tinfo)) { aexact = true; }; }; if (!aexact) { let m40d: str = "#40: sret-class call result cannot be widened into a tagged slot (mem-to-mem widen unwired)\n"; os.write(2, m40d.ptr, m40d.len: u64); os.exit(1); }; c.sretdestoff = lc.off; cgexpr(c, n.rhs); c.sretdestoff = 0; return; }; let lsz: i32 = slotsize(c, lc.tnode); cgwidentaggedstore(c, lc.tnode.type_: *syntax.tinfo, n.rhs, "BP", lc.off, lsz); return; }; }; // #38b: sret receive into a tagged GLOBAL // lvalue unwired (rule 7; cstage twin fatals). if (lc == nil && n.rhs != nil) { if (n.rhs.kind == syntax.nkind.N_CALL) { let gru: *syntax.tinfo = lhs.type_: *syntax.tinfo; gru = tichase(gru); if (gru != nil && gru.kind == syntax.tykind.TY_TAGGED && callsretsize(c, n.rhs) > 0) { let m38g: str = "#38b: sret receive into a tagged GLOBAL lvalue unwired\n"; os.write(2, m38g.ptr, m38g.len: u64); os.exit(1); }; }; }; // #32 (#263 ww-runtime-correct): tagged-union GLOBAL reassign // `g = expr`. No BP slot — LEAQ g(SB),BX then the shared widener // stores tag+payload off BX (mirror the local arm above + the // global-struct-field tagged arm at :10220). Pre-fix the generic // scalar store below clobbered the tag word. cstage drops the // store entirely — cs!=ww residual until the cstage half (#41). if (lc == nil) { let gtn: *syntax.node = letvartnode(c, lhs.str); if (gtn != nil) { if (istaggedtype(c, gtn)) { let gsz: i32 = slotsize(c, gtn); emitline("\tLEAQ\t"); emitsymname(c, lhs.str); emitline("(SB), BX\n"); cgwidentaggedstore(c, gtn.type_: *syntax.tinfo, n.rhs, "BX", 0, gsz); 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. // Retained gap: an aggregate >8B rhs (ident, tuple-lit, call) // truncates to one word here — task #31 A/E/G; struct-lit // diverts at the placeslit gate, array-lit dies loud (#32). if (lhs != nil) { if (lhs.kind == syntax.nkind.N_UN) { if (lhs.op == syntax.tkind.TK_STAR) { if (n.op == syntax.tkind.TK_ASSIGN && !placeslit && !derefagg) { let inner: *syntax.node = lhs.lhs; // #17: tagged-union pointee. The single-store // tail below writes rhs into the tag word only, // dropping the payload and corrupting the union. // Materialise the widened value (tag + payload // words, nullable fold, tag remap) into the shared // @tagscr scratch via cgwidentaggedstore, then // word-copy scratch -> *p. Mirror of the runtime- // index tagged element arm (cgenexpr.ww:8768) and // the cstage twin (cmd/w6c/cgen.c #17 deref arm). let du: *syntax.tinfo = tichase(lhs.type_: *syntax.tinfo); if (du != nil && du.kind == syntax.tykind.TY_TAGGED) { let ssz: i32 = du.size: i32; let scr: i32 = tagscradd(c, ssz); emitline("\tXORQ\tAX, AX\n"); let zk: i32 = 0; for (zk < ssz) { emitline("\tMOVQ\tAX, "); emitoff((scr + zk): i64); emitline("(BP)\n"); zk += 8; }; cgwidentaggedstore(c, du, n.rhs, "BP", scr, ssz); cgexpr(c, inner); emitline("\tMOVQ\tAX, BX\n"); let ck: i32 = 0; for (ck < ssz) { emitline("\tMOVQ\t"); emitoff((scr + ck): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\tAX, "); emitoff(ck: i64); emitline("(BX)\n"); ck += 8; }; return; }; let elemstr: bool = false; let elemfloat: bool = false; let elemf32: bool = false; let storeop: str = "MOVQ"; if (inner != nil) { if (inner.kind == syntax.nkind.N_IDENT) { let lc: *local = localfindnode(c, inner.str); if (lc != nil) { let tn: *syntax.node = lc.tnode; if (tn != nil) { if (tn.kind == syntax.nkind.N_TPTR) { let pe: *syntax.node = tn.lhs; if (pe != nil) { if (pe.kind == syntax.nkind.N_TNAME) { if (syntax.streq(pe.str, "str")) { elemstr = true; } else { if (syntax.streq(pe.str, "f64")) { elemfloat = true; } else { if (syntax.streq(pe.str, "f32")) { elemfloat = true; elemf32 = true; } else { // This site owns its own ps==0 // typenodeprimresolved chase below — routing through // aliasprimsize would double-resolve and regress #11. let ps: i32 = primsize(pe.str); // #11: primsize is name-keyed and // misses a `!`/enum/name alias // (`type errno = !i32`); peel it to // the underlying primitive width so // the store narrows, as cstage's // type-resolved pointee does // (cgen.c:4647-4652). Residual: // non-ident pointers + str/float-alias // deref-store widths stay name-blind // (#10 wwstage->tinfo SSoT). if (ps == 0) { let uns: bool = false; typenodeprimresolved(c, pe, &ps, &uns); }; if (ps == 1) { storeop = "MOVB"; } else { if (ps == 2) { storeop = "MOVW"; } else { if (ps == 4) { storeop = "MOVL"; }; }; }; }; }; }; }; // A slice IS the same 3-word {ptr,len,cap} // header as str (ref/hare/rt/ensure.ha:4-8), // so `*p = sliceval` takes str's stash+store // path (#79; precedent cgenstmt.ww:1631, // cgenexpr.ww:1684). LIKE str this is // alias-BLIND: a slice-alias `*Foo` / non-ident // deref-store stays 1-word, the SAME divergence // str carries; resolved-vs-syntactic detection // is unified UP in #80, not patched here. if (pe.kind == syntax.nkind.N_TSLICE) { elemstr = true; }; }; }; }; }; }; }; 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; }; // str IS []u8: PUSHQ AX (ptr) first, then // PUSHQ BX (len) + PUSHQ CX (cap) across the // pointer eval which clobbers BX/CX. Pop drains // cap (top) → 16(BX), then len, then ptr → 0(BX) // with len → 8(BX) (#1/Phase 3). emitline("\tPUSHQ\tAX\n"); if (elemstr) { emitline("\tPUSHQ\tBX\n"); emitline("\tPUSHQ\tCX\n"); }; cgexpr(c, inner); emitline("\tMOVQ\tAX, BX\n"); if (elemstr) { emitline("\tPOPQ\tCX\n"); emitline("\tMOVQ\tCX, 16(BX)\n"); 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; }; }; }; }; // `*p OP= v` — compound assign through a pointer deref. The // plain-assign branch above only fires for TK_ASSIGN; without // this, compound ops fall through and emit nothing (silent // no-op — exactly the trap that broke fmt.println). Mirror of // cmd/w6c/cgen.c's N_UN/TK_STAR compound branch. if (lhs != nil) { if (lhs.kind == syntax.nkind.N_UN) { if (lhs.op == syntax.tkind.TK_STAR) { // Size gate (mirror cstage cgen.c handled=sz∈{1,2,4,8}): // a tagged (or any non-scalar) pointee is not a // meaningful compound target — skip this single-word // store-and-return arm so `*p OP= v` on *tagged falls // through to the assign-resolver's loud TY_TAGGED reject // (#18). Without it the MOVQ default below clobbers the // tag word and returns: a silent miscompile. let psz: i32 = 8; let lt: *syntax.tinfo = lhs.type_: *syntax.tinfo; if (lt != nil) { psz = lt.size: i32; }; let scalarpointee: bool = (psz == 1 || psz == 2 || psz == 4 || psz == 8); if (n.op != syntax.tkind.TK_ASSIGN && scalarpointee) { let inner: *syntax.node = lhs.lhs; let loadop: str = "MOVQ"; let storeop: str = "MOVQ"; // Pointee node for the lhs-sign side of the /= // and %= dispatch. Mirror of cstage's `vt` at // cmd/w6c/cgen.c's TK_STAR-compound branch. let pe: *syntax.node = nil; if (inner != nil) { if (inner.kind == syntax.nkind.N_IDENT) { let lc: *local = localfindnode(c, inner.str); if (lc != nil) { let tn: *syntax.node = lc.tnode; if (tn != nil) { if (tn.kind == syntax.nkind.N_TPTR) { pe = tn.lhs; if (pe != nil) { let ps: i32 = fieldsize(c, pe); if (ps == 1 || ps == 2 || ps == 4) { loadop = tnodeloadop(c, pe, ps); storeop = tnodestoreop(c, pe, ps); }; }; }; }; }; }; }; cgexpr(c, n.rhs); emitline("\tPUSHQ\tAX\n"); cgexpr(c, inner); emitline("\tMOVQ\tAX, BX\n"); emitline("\t"); emitline(loadop); emitline("\t(BX), AX\n"); emitline("\tPOPQ\tCX\n"); // Post-63332fe: /= and %= via CQO/IDIVQ on the // signed arm and MOVQ-zero/DIVQ on the unsigned // arm. Pre-fix the default branch silently stored // rhs into *p (combineop = MOVQ shape). // #136: lift unsignd above the SLASHEQ block so // RSHIFTEQ can route SHRQ vs SARQ on the same key. let unsignd: bool = false; if (pe != nil) { unsignd = syntax.typeisunsigned(pe.type_: *syntax.tinfo); }; if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); }; if (n.op == syntax.tkind.TK_SLASHEQ || n.op == syntax.tkind.TK_PERCENTEQ) { if (unsignd) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); } else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); }; if (n.op == syntax.tkind.TK_PERCENTEQ) { emitline("\tMOVQ\tDX, AX\n"); }; emitline("\t"); emitline(storeop); emitline("\tAX, (BX)\n"); return; }; let combineop: str = "MOVQ"; if (n.op == syntax.tkind.TK_PLUSEQ) { combineop = "ADDQ"; } else { if (n.op == syntax.tkind.TK_MINUSEQ) { combineop = "SUBQ"; } else { if (n.op == syntax.tkind.TK_STAREQ) { combineop = "IMULQ"; } else { if (n.op == syntax.tkind.TK_AMPEQ) { combineop = "ANDQ"; } else { if (n.op == syntax.tkind.TK_PIPEEQ) { combineop = "ORQ"; } else { if (n.op == syntax.tkind.TK_CARETEQ) { combineop = "XORQ"; } else { if (n.op == syntax.tkind.TK_LSHIFTEQ) { combineop = "SHLQ"; } else { if (n.op == syntax.tkind.TK_RSHIFTEQ) { // #136: signed RSHIFTEQ → SARQ. if (unsignd) { combineop = "SHRQ"; } else { combineop = "SARQ"; }; }; }; }; }; }; }; }; }; // all 10 compound tokens enumerated above // (/= %= returned earlier) — a MOVQ // combineop is the old rhs-plain-store // legacy fallback. Rule 7. if (syntax.streq(combineop, "MOVQ")) { let mdc: str = "deref compound: unknown compound op (rule 7)\n"; os.write(2, mdc.ptr, mdc.len: u64); os.exit(1); }; emitline("\t"); emitline(combineop); emitline("\tCX, AX\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 == syntax.nkind.N_INDEX && !placeslit) { if (n.op == syntax.tkind.TK_ASSIGN) { let base: *syntax.node = lhs.lhs; let idx: *syntax.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: *syntax.node = nil; let basealias: bool = false; if (base != nil) { if (base.kind == syntax.nkind.N_IDENT) { let bn: str = base.str; baselocal = localfindnode(c, bn); if (baselocal != nil) { esz = elemsizeofc(c, baselocal.tnode); // idxelemtn drills `*[N]T` to the pointee // array's element (#61): an undrilled elemtn // made the width chooser believe the element // IS the whole array (N*8B aggregate copy // from an 8B source — frame smash). elemtn = idxelemtn(baselocal.tnode); } else { // #11: store/compound twin of the #10 cgindex // read fix. A global str/slice element store hit // the same kind whitelist — N_TNAME (str) / // N_TSLICE matched NEITHER arm, so esz stayed 8 // and the store emitted a full-word MOVQ — an // 8-byte OUT-OF-BOUNDS write past a 1-byte // element — instead of MOVB. cstage // (cmd/w6c/cgen.c N_INDEX store) dispatches esz // off idx_eff->sub->size + the elem-kind flags // off eff->sub uniformly, base is_arr?LEAQ:MOVQ // name(SB). Align UP and resolve elemtn exactly // like the local branch above (element node for // ARRAY/SLICE/PTR; nil for str so tnodestoreop // picks MOVB on the store arm, and the compound // arm's str/slice hard-error still fires on a // []str element). let tn: *syntax.node = letvartnode(c, bn); if (tn != nil) { globalname = bn; esz = elemsizeofc(c, tn); // idxelemtn: `*[N]T` drill, see the // local branch above (#61). elemtn = idxelemtn(tn); if (tn.kind == syntax.nkind.N_TARRAY) { isglobalarr = true; } else { isglobalptr = true; }; }; }; } else { if (base.kind == syntax.nkind.N_DOT || (base.kind == syntax.nkind.N_UN && base.op == syntax.tkind.TK_STAR)) { // lhs.type_ is the checker-stamped element tinfo // of the N_INDEX: esz is its natural size and the // tagged-element gate (below) reads the same // .type_ — same idiom as cgindex's n.type_ read // (#60/#72). cstage idx_eff(base->type)->sub->size // (cmd/w6c/cgen.c:3517-18). N_UN deref base // (`(*p)[i] = v`, #61 C): same stamped source. let dt: *syntax.tinfo = lhs.type_: *syntax.tinfo; if (dt != nil) { esz = dt.size: i32; elemtn = lhs; }; } else { if (base.kind == syntax.nkind.N_INDEX) { // Chained-write write-side parallel of the // cgindex N_INDEX-base arm (#24): `names[i][k] // = v` (names: **u8) — outer element is u8 so // the store is MOVB, not MOVQ. lhs.type_ is the // checker-stamped outer element tinfo; esz is // its natural size and the gate reads it via // .type_. Drops the indexvaluetnode walk // (#69/#61d, mirror #60). cstage: esz = // idx_eff(base->type)->sub->size // (cmd/w6c/cgen.c:3517-3518). let et: *syntax.tinfo = lhs.type_: *syntax.tinfo; if (et != nil) { esz = et.size: i32; elemtn = lhs; }; };};}; }; // #60 (write spine): alias-NAMED N_IDENT base — the // tnode walk above is blind (esz 1-sentinel, elemtn // nil → pointer-treated base, wrong-stride store). // Adopt the stamped-element idiom of the N_DOT/ // N_INDEX arms (lhs.type_ IS the element tinfo); // cstage N_INDEX store reads idx_eff(base->type)->sub // uniformly (cmd/w6c/cgen.c:3517-3518). if (base != nil) { if (base.kind == syntax.nkind.N_IDENT) { let bt60: *syntax.tinfo = base.type_: *syntax.tinfo; if (bt60 != nil) { if (bt60.kind == syntax.tykind.TY_NAMED) { basealias = true; }; }; if (basealias) { let et60: *syntax.tinfo = tichase(lhs.type_: *syntax.tinfo); if (et60 != nil) { esz = et60.size: i32; elemtn = lhs; }; // see cgindex twin: cs-aligned, runtime- // unreachable until #77/#78 global DATA. if (isglobalarr || isglobalptr) { let bu60: *syntax.tinfo = tichase(bt60); if (bu60 != nil) { isglobalarr = bu60.kind == syntax.tykind.TY_ARRAY; isglobalptr = !isglobalarr; }; }; }; }; }; // 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 same-size tagged-arr // stores in the function; first-use sizes the slot // (#15/#26c, size-keyed by #44). if (elemtn != nil) { if (istaggedtype(c, elemtn)) { let slot_sz: i32 = slotsize(c, elemtn); let scroff: i32 = tagscradd(c, slot_sz); // 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.type_: *syntax.tinfo, n.rhs, "BP", 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: *syntax.node = baselocal.tnode; let isarr: bool = false; if (tn != nil) { if (tn.kind == syntax.nkind.N_TARRAY) { isarr = true; }; }; // #60: alias-NAMED base — chased kind // (see cgindex twin). if (basealias) { let bu60: *syntax.tinfo = tichase(base.type_: *syntax.tinfo); if (bu60 != nil) { isarr = bu60.kind == syntax.tykind.TY_ARRAY; }; }; 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 { if (dotbaseaddr(c, base, "BX")) { // #259: N_DOT base resolved inline to // the field address; cgexpr fallback // would auto-deref + load the array // field as a VALUE (broken shape). dst // BX keeps the scaled index live in AX. } 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; }; }; // #234: over-cap sret STORE into an indexed lvalue — // `arr[i] = wide();` STORE-twin of the Fold-B sret RECEIVE // (a937d67). c.sretdestoff is a STATIC BP-relative offset, so // only a CONSTANT index into a LOCAL value array yields a // static dest slot (off + idx*esz) the callee can sret // straight into. Every other indexed form — runtime index, // slice/ptr base, N_DOT-base (`s.arr[i]`), chained (`a[i][k]`), // global base — needs the runtime RDI-pointer dest variant // deferred to #234-tail and HARD-STOPS loud (rule 7). Mirror of // cstage cgen.c (#234) N_INDEX arm. // // Gate ENTRY on callsretsize(n.rhs) — the callee-return-type // SSoT (cgenutil.ww) the receive sites use — NOT on // sretretsize(elemtn): elemtn is only a type node for an // N_IDENT base, but a value node for N_DOT (4695) / chained // (4710), which fell to sretretsize=0 and let those forms // drop SILENTLY through to the truncating store. The callee // return type equals the dest-element type (checker-guaranteed), // so the verdict is byte-identical to cstage's cg_sret_retsize. // The base-shape split below then loud-stops every non-local- // array form, base-kind-independent. if (n.rhs != nil && n.rhs.kind == syntax.nkind.N_CALL && callsretsize(c, n.rhs) > 0) { let islocalarr: bool = false; if (baselocal != nil) { let btn: *syntax.node = baselocal.tnode; if (btn != nil) { if (btn.kind == syntax.nkind.N_TARRAY) { islocalarr = true; }; }; }; let constidx: bool = false; let cidx: i32 = 0; if (idx != nil) { if (idx.kind == syntax.nkind.N_INTLIT) { constidx = true; cidx = idx.uval: i32; }; }; if (!islocalarr || !constidx) { let m234: str = "#234-tail: over-cap tuple sret store to non-local/dynamic-index dest unsupported\n"; os.write(2, m234.ptr, m234.len: u64); os.exit(1); }; c.sretdestoff = baselocal.off + cidx * esz; cgexpr(c, n.rhs); c.sretdestoff = 0; return; }; // C2c / #31-G: an IN-CAP aggregate-returning CALL into // an indexed element `a[i] = mk()`. The #234 arm above // only fires for an OVER-cap (sret) return; the #270-1b // arm below copies from a source ADDRESS (which a call // result has none). An in-cap (<=24B) struct/array/tuple // return leaves AX/DX/CX per the #4 cgreturn ABI but fell // to the 1-word scalar store (AX only) — dropping DX/CX // (the documented-but-unfixed #31-G). Materialise the // return into a frame scratch (the AX/DX/CX receive shape, // cstage cgen.c:3434), THEN compute &a[i] and word-copy // scratch -> dest. Scratch-first (not a dest spill across // the call) keeps the call at the frame's natural // alignment. esz>8 non-str/non-slice IS struct/array/tuple // here (tagged returned above). In-cap only // (callsretsize==0). Mirror of cstage cgen.c C2c arm. // #12: an unwrap `mk()!` / `r?` whose success variant // is an in-cap struct/array rides the SAME {AX,DX,CX} // payload shape as the call return (the producer shift // at cgtrytaggedshift materialises it); admit it // alongside N_CALL so the materialise + copy just works. // callsretsize==0 holds (non-call → 0); the float/over- // cap loud-stops live at the producer. if ((n.rhs.kind == syntax.nkind.N_CALL || n.rhs.kind == syntax.nkind.N_TRYUNW || n.rhs.kind == syntax.nkind.N_TRYPROP) && esz > 8 && !isstrtype(c, elemtn) && !isslicetype(c, elemtn) && callsretsize(c, n.rhs) == 0) { let scrc2: i32 = tagscradd(c, esz); cgexpr(c, n.rhs); // call -> AX/DX/CX // AX/DX/CX -> scratch (mirror cstage cgen.c:3434). cgaggregstore(c, "BP", scrc2, esz, true); // dest &a[i] -> BX (#121 / #270-1b base resolve) cgexpr(c, idx); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", CX\n"); emitline("\tIMULQ\tCX, AX\n"); }; emitline("\tPUSHQ\tAX\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 tn2: *syntax.node = baselocal.tnode; let isarr2: bool = false; if (tn2 != nil) { if (tn2.kind == syntax.nkind.N_TARRAY) { isarr2 = true; }; }; if (basealias) { let bu60: *syntax.tinfo = tichase(base.type_: *syntax.tinfo); if (bu60 != nil) { isarr2 = bu60.kind == syntax.tykind.TY_ARRAY; }; }; if (isarr2) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); emitline("(BP), BX\n"); } else { emitline("\tMOVQ\t"); emitoff(baselocal.off: i64); emitline("(BP), BX\n"); }; } else { if (dotbaseaddr(c, base, "BX")) { } else { cgexpr(c, base); emitline("\tMOVQ\tAX, BX\n"); };};};}; emitline("\tPOPQ\tAX\n"); emitline("\tADDQ\tAX, BX\n"); // word-copy scratch -> dest (tail-aware, #270-1b copy) let kc: i32 = 0; for (kc + 8 <= esz) { emitline("\tMOVQ\t"); emitoff((scrc2 + kc): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\tAX, "); emitoff(kc: i64); emitline("(BX)\n"); kc += 8; }; if (kc + 4 <= esz) { emitline("\tMOVL\t"); emitoff((scrc2 + kc): i64); emitline("(BP), AX\n"); emitline("\tMOVL\tAX, "); emitoff(kc: i64); emitline("(BX)\n"); kc += 4; }; if (kc + 2 <= esz) { emitline("\tMOVW\t"); emitoff((scrc2 + kc): i64); emitline("(BP), AX\n"); emitline("\tMOVW\tAX, "); emitoff(kc: i64); emitline("(BX)\n"); kc += 2; }; if (kc + 1 <= esz) { emitline("\tMOVB\t"); emitoff((scrc2 + kc): i64); emitline("(BP), AX\n"); emitline("\tMOVB\tAX, "); emitoff(kc: i64); emitline("(BX)\n"); kc += 1; }; return; }; // #121 (write-face of leg-b): a tuple-LITERAL rhs into // an indexed element `a[i] = (3,4)`. A literal has no // source ADDRESS, so the ident/dot/deref copy arm below // can't reach it — it fell to the 1-word scalar store // tail (word0 only; the read-luck masked it until leg-b's // correct read). Materialise the literal into a frame // scratch via the cglet in-cap path (cgtuplelittocursor + // tupstore), then word-copy scratch → &a[i]. NARROW: // N_TTUPLE-literal rhs, N_IDENT base (idxelemtn gives the // element N_TTUPLE), in-cap. Mirror of cstage cgen.c #121 // store arm; the materialise + base-resolve are the // cglet / #270-1b byte-id twins. if (n.rhs.kind == syntax.nkind.N_TUPLE && esz > 8 && base != nil && base.kind == syntax.nkind.N_IDENT && elemtn != nil && elemtn.kind == syntax.nkind.N_TTUPLE && sretretsize(c, elemtn) == 0) { let scr121: i32 = tagscradd(c, esz); cgtuplelittocursor(c, n.rhs, elemtn); let gpc: i32 = 0; let ssc: i32 = 0; let eo: i32 = 0; let q121: *syntax.node = elemtn.list; for (q121 != nil) { let qt: *syntax.node = q121.lhs; let isflt: bool = isfloattype(c, qt); let es: i32 = tupeslotn(qt); tupstore(c, gpc, ssc, scr121 + eo, es, qt); if (isflt) { ssc += 1; } else { gpc += es / 8; }; eo += es; q121 = q121.next; }; // dest &a[i] → BX (#270-1b base resolve) cgexpr(c, idx); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", CX\n"); emitline("\tIMULQ\tCX, AX\n"); }; emitline("\tPUSHQ\tAX\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 tn2: *syntax.node = baselocal.tnode; let isarr2: bool = false; if (tn2 != nil) { if (tn2.kind == syntax.nkind.N_TARRAY) { isarr2 = true; }; }; if (basealias) { let bu60: *syntax.tinfo = tichase(base.type_: *syntax.tinfo); if (bu60 != nil) { isarr2 = bu60.kind == syntax.tykind.TY_ARRAY; }; }; if (isarr2) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); emitline("(BP), BX\n"); } else { emitline("\tMOVQ\t"); emitoff(baselocal.off: i64); emitline("(BP), BX\n"); }; } else { if (dotbaseaddr(c, base, "BX")) { } else { cgexpr(c, base); emitline("\tMOVQ\tAX, BX\n"); };};};}; emitline("\tPOPQ\tAX\n"); emitline("\tADDQ\tAX, BX\n"); let kk2: i32 = 0; for (kk2 < esz) { emitline("\tMOVQ\t"); emitoff((scr121 + kk2): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\tAX, "); emitoff(kk2: i64); emitline("(BX)\n"); kk2 += 8; }; return; }; // #270-1b: aggregate (struct/array/tuple >8B) element // STORE `a[i] = val`. The scalar store path below copies // only the first 8 bytes (tnodestoreop MOVQ) — a silent // truncation. Compute &a[i] (dest) and the rhs SOURCE // address, then word-copy esz bytes: the WRITE-twin of // the #268 let-init copy loop. Source shapes mirror that // loop (ident, N_DOT field via dotchainaddr, N_INDEX via // cgplaceaddr, `*p` deref); struct-lit sources divert at // the placeslit gate above (#20), array-lit dies loud // (task #32), and // a by-value call result still falls to the scalar tail // — RAX-only store, task #31-G. esz>8 // non-str/non-slice IS a struct/array/tuple here (the // tagged element already returned above; floats are ≤8). let aggsrc: bool = (n.rhs.kind == syntax.nkind.N_IDENT) || (n.rhs.kind == syntax.nkind.N_DOT) || (n.rhs.kind == syntax.nkind.N_INDEX) || (n.rhs.kind == syntax.nkind.N_UN && n.rhs.op == syntax.tkind.TK_STAR); if (esz > 8 && !isstrtype(c, elemtn) && !isslicetype(c, elemtn) && aggsrc) { 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 tn2: *syntax.node = baselocal.tnode; let isarr2: bool = false; if (tn2 != nil) { if (tn2.kind == syntax.nkind.N_TARRAY) { isarr2 = true; }; }; // #60: alias-NAMED base — chased kind (see cgindex twin). if (basealias) { let bu60: *syntax.tinfo = tichase(base.type_: *syntax.tinfo); if (bu60 != nil) { isarr2 = bu60.kind == syntax.tykind.TY_ARRAY; }; }; if (isarr2) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); emitline("(BP), BX\n"); } else { emitline("\tMOVQ\t"); emitoff(baselocal.off: i64); emitline("(BP), BX\n"); }; } else { if (dotbaseaddr(c, base, "BX")) { // N_DOT array-field base resolved inline. } else { cgexpr(c, base); emitline("\tMOVQ\tAX, BX\n"); };};};}; emitline("\tPOPQ\tAX\n"); // scaled idx emitline("\tADDQ\tAX, BX\n"); emitline("\tPUSHQ\tBX\n"); // spill dest // rhs source address → SI if (n.rhs.kind == syntax.nkind.N_UN && n.rhs.op == syntax.tkind.TK_STAR) { cgexpr(c, n.rhs.lhs); emitline("\tMOVQ\tAX, SI\n"); } else { if (n.rhs.kind == syntax.nkind.N_IDENT) { let sl: *local = localfindnode(c, n.rhs.str); if (sl != nil) { emitline("\tLEAQ\t"); emitoff(sl.off: i64); emitline("(BP), SI\n"); } else { emitline("\tLEAQ\t"); emitsymname(c, n.rhs.str); emitline("(SB), SI\n"); }; } else { // task #6: the chain walker // declines an N_INDEX link // (emission-free) — its result // was unchecked here, so the // copy below read through a // stale SI. Fall to the // generic place resolver. let dok: bool = false; if (n.rhs.kind == syntax.nkind.N_DOT) { dok = dotchainaddr(c, n.rhs, "SI"); }; if (!dok) { if (!cgplaceaddr(c, n.rhs, "SI")) { let msrc: str = "indexed aggregate assignment source unresolved\n"; os.write(2, msrc.ptr, msrc.len: u64); os.exit(1); }; }; };}; emitline("\tPOPQ\tBX\n"); // dest let kc: i32 = 0; for (kc + 8 <= esz) { emitline("\tMOVQ\t"); emitoff(kc: i64); emitline("(SI), AX\n"); emitline("\tMOVQ\tAX, "); emitoff(kc: i64); emitline("(BX)\n"); kc += 8; }; if (kc + 4 <= esz) { emitline("\tMOVL\t"); emitoff(kc: i64); emitline("(SI), AX\n"); emitline("\tMOVL\tAX, "); emitoff(kc: i64); emitline("(BX)\n"); kc += 4; }; if (kc + 2 <= esz) { emitline("\tMOVW\t"); emitoff(kc: i64); emitline("(SI), AX\n"); emitline("\tMOVW\tAX, "); emitoff(kc: i64); emitline("(BX)\n"); kc += 2; }; if (kc + 1 <= esz) { emitline("\tMOVB\t"); emitoff(kc: i64); emitline("(SI), AX\n"); emitline("\tMOVB\tAX, "); emitoff(kc: i64); emitline("(BX)\n"); kc += 1; }; return; }; cgexpr(c, n.rhs); // value → AX // str/slice: spill cap (CX) + len (BX) before // computing the index so the post-index store can // pop all three. str=24B (#1/Phase 3) collides with // slice=24B, so this MUST gate on kind (cstage's // elem_is_str||elem_is_slice, cmd/w6c/cgen.c:3581), // never a bare esz==24: a >16B struct is also >=24B // but takes the struct-copy path, not this 3-word // {ptr,len,cap} store. Write-side mirror of the // cgindex read-path gate (#7/754). if (isstrtype(c, elemtn) || isslicetype(c, elemtn)) { emitline("\tPUSHQ\tCX\n"); emitline("\tPUSHQ\tBX\n"); }; // Float element: spill X0 (not AX — AX is junk for // floats) across the idx/base eval. A call-index // (a[geti()]=v) clobbers X0 and would otherwise lose // the value. Mirrors the *p=v float deref store // twin in cgassign (#125). let spisfloat: bool = isfloattype(c, elemtn); let spmov: str = "MOVSD"; if (isf32type(c, elemtn)) { spmov = "MOVSS"; }; if (spisfloat) { emitline("\tSUBQ\t$8, SP\n"); emitline("\t"); emitline(spmov); emitline("\tX0, (SP)\n"); } else { 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: *syntax.node = baselocal.tnode; let isarray: bool = false; if (tn != nil) { if (tn.kind == syntax.nkind.N_TARRAY) { isarray = true; }; }; // #60: alias-NAMED base — chased kind (see cgindex twin). if (basealias) { let bu60: *syntax.tinfo = tichase(base.type_: *syntax.tinfo); if (bu60 != nil) { isarray = bu60.kind == syntax.tykind.TY_ARRAY; }; }; 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 { if (dotbaseaddr(c, base, "BX")) { // #135: N_DOT base address-of-field inline. } else { cgexpr(c, base); emitline("\tMOVQ\tAX, BX\n"); };};};}; emitline("\tPOPQ\tAX\n"); // scaled idx emitline("\tADDQ\tAX, BX\n"); // Reload value: float reloads X0 from the spill slot; // non-float pops AX. Twin of the value-spill site // above (#125). if (spisfloat) { emitline("\t"); emitline(spmov); emitline("\t(SP), X0\n"); emitline("\tADDQ\t$8, SP\n"); } else { emitline("\tPOPQ\tAX\n"); // value }; // str/slice: pop the saved len + cap and store // all three words. Kind-gate, not size — see the // spill site above (#1/Phase 3, #7/754). if (isstrtype(c, elemtn) || isslicetype(c, elemtn)) { emitline("\tMOVQ\tAX, (BX)\n"); emitline("\tPOPQ\tCX\n"); emitline("\tMOVQ\tCX, 8(BX)\n"); emitline("\tPOPQ\tCX\n"); emitline("\tMOVQ\tCX, 16(BX)\n"); return; }; // float element → store FROM X0 (MOVSS/MOVSD): cgexpr // left the value in X0, and the value-spill pair // above keeps X0 live across the idx/base eval so // a call-index (a[geti()]=v) doesn't lose it (#125). // For f32 the #104 CVTSD2SS narrowing only touches X0, // so the AX store below would write raw double low- // bits, garbage for f32 (#122, mirrors cstage cgen.c // arr[i]= float store). if (isfloattype(c, elemtn)) { let fmov: str = "MOVSD"; if (isf32type(c, elemtn)) { fmov = "MOVSS"; }; emitline("\t"); emitline(fmov); emitline("\tX0, (BX)\n"); return; }; let isop: str = tnodestoreop(c, elemtn, esz); emitline("\t"); emitline(isop); emitline("\tAX, (BX)\n"); return; }; // Compound assign on an indexed scalar element // (`arr[i] OP= v`). Pre-#133 the outer `if (n.op == // TK_ASSIGN)` had no else and non-ASSIGN ops fell off // the cgassign function emitting NOTHING — silent // no-op. Mirror the chained-pointer-field compound // template at cmd/w6c/cgen.c:3281-3317: same address // computation as the ASSIGN arm above, then // tnodeloadop(BX)→AX, POP rhs→CX, combine, tnodestoreop. // Float / str / slice / tagged element compound stays // unwired — cstage's compound template never carried // those payload kinds. Same shape gate as the cstage // branch (cgen.c #133). if (n.op != syntax.tkind.TK_ASSIGN) { let base: *syntax.node = lhs.lhs; let idx: *syntax.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: *syntax.node = nil; if (base != nil) { if (base.kind == syntax.nkind.N_IDENT) { let bn: str = base.str; baselocal = localfindnode(c, bn); if (baselocal != nil) { esz = elemsizeofc(c, baselocal.tnode); // idxelemtn drills `*[N]T` to the pointee // array's element (#61): an undrilled elemtn // made the width chooser believe the element // IS the whole array (N*8B aggregate copy // from an 8B source — frame smash). elemtn = idxelemtn(baselocal.tnode); } else { // #11: store/compound twin of the #10 cgindex // read fix. A global str/slice element store hit // the same kind whitelist — N_TNAME (str) / // N_TSLICE matched NEITHER arm, so esz stayed 8 // and the store emitted a full-word MOVQ — an // 8-byte OUT-OF-BOUNDS write past a 1-byte // element — instead of MOVB. cstage // (cmd/w6c/cgen.c N_INDEX store) dispatches esz // off idx_eff->sub->size + the elem-kind flags // off eff->sub uniformly, base is_arr?LEAQ:MOVQ // name(SB). Align UP and resolve elemtn exactly // like the local branch above (element node for // ARRAY/SLICE/PTR; nil for str so tnodestoreop // picks MOVB on the store arm, and the compound // arm's str/slice hard-error still fires on a // []str element). let tn: *syntax.node = letvartnode(c, bn); if (tn != nil) { globalname = bn; esz = elemsizeofc(c, tn); // idxelemtn: `*[N]T` drill, see the // local branch above (#61). elemtn = idxelemtn(tn); if (tn.kind == syntax.nkind.N_TARRAY) { isglobalarr = true; } else { isglobalptr = true; }; }; }; } else { if (base.kind == syntax.nkind.N_DOT || (base.kind == syntax.nkind.N_UN && base.op == syntax.tkind.TK_STAR)) { // N_UN deref base (`(*p)[i] OP= v`, #61 C): // same stamped source as the store arm. let dt: *syntax.tinfo = lhs.type_: *syntax.tinfo; if (dt != nil) { esz = dt.size: i32; elemtn = lhs; }; } else { if (base.kind == syntax.nkind.N_INDEX) { let et: *syntax.tinfo = lhs.type_: *syntax.tinfo; if (et != nil) { esz = et.size: i32; elemtn = lhs; }; };};}; }; // #60 (compound spine): alias-NAMED N_IDENT base — // same stamped-element adoption as the store arm. let basealias: bool = false; if (base != nil) { if (base.kind == syntax.nkind.N_IDENT) { let bt60: *syntax.tinfo = base.type_: *syntax.tinfo; if (bt60 != nil) { if (bt60.kind == syntax.tykind.TY_NAMED) { basealias = true; }; }; if (basealias) { let et60: *syntax.tinfo = tichase(lhs.type_: *syntax.tinfo); if (et60 != nil) { esz = et60.size: i32; elemtn = lhs; }; if (isglobalarr || isglobalptr) { let bu60: *syntax.tinfo = tichase(bt60); if (bu60 != nil) { isglobalarr = bu60.kind == syntax.tykind.TY_ARRAY; isglobalptr = !isglobalarr; }; }; }; }; }; // #133-expanded: hard-error unwired payload kinds // LOUD (rule-7) — replaces prior silent skip. if (elemtn != nil) { if (istaggedtype(c, elemtn)) { let msg: str = "indexed-lvalue compound on tagged element not wired (#133/rule-7)\n"; os.write(2, msg.ptr, msg.len: u64); os.exit(1); }; if (isstrtype(c, elemtn)) { let msg: str = "indexed-lvalue compound on str element not wired (#133/rule-7)\n"; os.write(2, msg.ptr, msg.len: u64); os.exit(1); }; if (isslicetype(c, elemtn)) { let msg: str = "indexed-lvalue compound on slice element not wired (#133/rule-7)\n"; os.write(2, msg.ptr, msg.len: u64); os.exit(1); }; if (isfloattype(c, elemtn)) { let msg: str = "indexed-lvalue compound on float element not wired (#133/rule-7)\n"; os.write(2, msg.ptr, msg.len: u64); os.exit(1); }; }; cgexpr(c, n.rhs); emitline("\tPUSHQ\tAX\n"); cgexpr(c, idx); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", CX\n"); emitline("\tIMULQ\tCX, AX\n"); }; emitline("\tPUSHQ\tAX\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: *syntax.node = baselocal.tnode; let isarray: bool = false; if (tn != nil) { if (tn.kind == syntax.nkind.N_TARRAY) { isarray = true; }; }; // #60: alias-NAMED base — chased kind (see cgindex twin). if (basealias) { let bu60: *syntax.tinfo = tichase(base.type_: *syntax.tinfo); if (bu60 != nil) { isarray = bu60.kind == syntax.tykind.TY_ARRAY; }; }; 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 { if (dotbaseaddr(c, base, "BX")) { // #135: N_DOT base address-of-field inline. } else { cgexpr(c, base); emitline("\tMOVQ\tAX, BX\n"); };};};}; emitline("\tPOPQ\tAX\n"); emitline("\tADDQ\tAX, BX\n"); let lop: str = tnodeloadop(c, elemtn, esz); emitline("\t"); emitline(lop); emitline("\t(BX), AX\n"); emitline("\tPOPQ\tCX\n"); // #133-expanded: all 10 integer compound ops wired. // SLASHEQ/PERCENTEQ: CQO+IDIVQ (signed) or zero-DX+ // DIVQ (unsigned). LSHIFTEQ via SHLQ; RSHIFTEQ via // SARQ (signed) or SHRQ (unsigned) per #136. // Signedness from elemtn.type_. let unsignd_c: bool = false; if (elemtn != nil) { if (elemtn.type_ != nil) { unsignd_c = syntax.typeisunsigned(elemtn.type_: *syntax.tinfo); }; }; let wired: bool = false; if (n.op == syntax.tkind.TK_PLUSEQ) { emitline("\tADDQ\tCX, AX\n"); wired = true; }; if (n.op == syntax.tkind.TK_MINUSEQ) { emitline("\tSUBQ\tCX, AX\n"); wired = true; }; if (n.op == syntax.tkind.TK_STAREQ) { emitline("\tIMULQ\tCX, AX\n"); wired = true; }; if (n.op == syntax.tkind.TK_AMPEQ) { emitline("\tANDQ\tCX, AX\n"); wired = true; }; if (n.op == syntax.tkind.TK_PIPEEQ) { emitline("\tORQ\tCX, AX\n"); wired = true; }; if (n.op == syntax.tkind.TK_CARETEQ) { emitline("\tXORQ\tCX, AX\n"); wired = true; }; if (n.op == syntax.tkind.TK_SLASHEQ) { if (unsignd_c) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); } else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); }; wired = true; }; if (n.op == syntax.tkind.TK_PERCENTEQ) { if (unsignd_c) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); } else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); }; emitline("\tMOVQ\tDX, AX\n"); wired = true; }; if (n.op == syntax.tkind.TK_LSHIFTEQ) { emitline("\tSHLQ\tCX, AX\n"); wired = true; }; if (n.op == syntax.tkind.TK_RSHIFTEQ) { if (unsignd_c) { emitline("\tSHRQ\tCX, AX\n"); } else { emitline("\tSARQ\tCX, AX\n"); }; wired = true; }; if (!wired) { let msg: str = "indexed-lvalue compound: unknown compound op (#133/rule-7)\n"; os.write(2, msg.ptr, msg.len: u64); os.exit(1); }; let sop: str = tnodestoreop(c, elemtn, esz); emitline("\t"); emitline(sop); emitline("\tAX, (BX)\n"); return; }; }; }; // `arr[i].field = v`: N_DOT lhs whose lhs is N_INDEX. Symmetric // write-side of the cgdot N_INDEX-lhs branch added for task #8. // Compute &arr[i] inline (LEAQ for `[N]Struct`, MOVQ for // `[N]*Struct` / `[]Struct` / `*Struct`), deref once when the // element is `*Struct`, then store rhs at field.offset(addr). // Without this both shapes silently drop the store — there is no // existing wwstage branch for N_DOT(N_INDEX,...) lhs at all (the // N_INDEX-lhs branch above handles bare `arr[i] = v`, not the // field write). if (lhs != nil) { if (lhs.kind == syntax.nkind.N_DOT && lhs.lhs != nil && lhs.lhs.kind == syntax.nkind.N_INDEX && !placeslit) { let idxbase: *syntax.node = lhs.lhs.lhs; let idx: *syntax.node = lhs.lhs.rhs; let fld2: str = lhs.str; if (idxbase != nil) { if (idxbase.kind == syntax.nkind.N_IDENT) { if (idx != nil) { let lc: *local = localfindnode(c, idxbase.str); if (lc != nil) { if (lc.tnode != nil) { let tn: *syntax.node = lc.tnode; // idxelemtn: `*[N]T` drills to the pointee // array's element (#61). let elemt: *syntax.node = idxelemtn(tn); let baseisarray: bool = tn.kind == syntax.nkind.N_TARRAY; let snode: *syntax.node = nil; let viaptr: bool = false; if (elemt != nil) { if (elemt.kind == syntax.nkind.N_TPTR) { let inner: *syntax.node = elemt.lhs; if (inner != nil) { if (inner.kind == syntax.nkind.N_TNAME) { snode = inner; viaptr = true; };}; } else { if (elemt.kind == syntax.nkind.N_TNAME) { snode = elemt; };}; }; // #102 (ken B6-c3 re-attribution): an alias-NAMED // element misses the bare name-keyed lookup, so // `arr[i].f = v` fell to the generic place route — // runtime-correct but byte-divergent from the // dedicated shape cs pins post-B6-c3 (the READ twin // above already chases via tichase, task #8). // structlookupchain (#22) chases the alias chain; // plain rows short-circuit at its structlookup // head, byte-id by construction. esz stays sound: // elemsizeofc reads the chased stamped tinfo (#8). if (snode != nil) { let si: *structinfo = structlookupchain(c, snode); if (si != nil) { let fi: *fieldinfo = si.fields; for (fi != nil) { if (syntax.streq(fi.fname, fld2)) { let esz: i32 = elemsizeofc(c, tn); // f64/f32: rhs in X0. Spill to stack, // compute &arr[i] in BX (deref if *T), // then reload X0 and MOVSD/MOVSS. if (n.op == syntax.tkind.TK_ASSIGN) { 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, idx); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", CX\n"); emitline("\tIMULQ\tCX, AX\n"); }; if (baseisarray) { emitline("\tLEAQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); } else { emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); }; emitline("\tADDQ\tAX, BX\n"); if (viaptr) { emitline("\tMOVQ\t(BX), 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; }; // str/slice: rhs leaves AX=ptr, // BX=len, CX=cap (#1/Phase 3). Spill // all three across the index/address // computation (IMULQ's CX scratch // clobbers cap), stage &arr[i] in DX // off the str AX/BX/CX convention // (mirrors s.f=v), then store the full // triple at foff+0/+8/+16. if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) { cgexpr(c, n.rhs); emitline("\tPUSHQ\tCX\n"); emitline("\tPUSHQ\tBX\n"); emitline("\tPUSHQ\tAX\n"); cgexpr(c, idx); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", CX\n"); emitline("\tIMULQ\tCX, AX\n"); }; if (baseisarray) { emitline("\tLEAQ\t"); emitoff(lc.off: i64); emitline("(BP), DX\n"); } else { emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), DX\n"); }; emitline("\tADDQ\tAX, DX\n"); if (viaptr) { emitline("\tMOVQ\t(DX), DX\n"); }; emitline("\tPOPQ\tAX\n"); emitline("\tPOPQ\tBX\n"); emitline("\tPOPQ\tCX\n"); emitline("\tMOVQ\tAX, "); emitdispreg(fi.foff: i64, "DX"); emitline("\n"); emitline("\tMOVQ\tBX, "); emitdispreg((fi.foff + 8): i64, "DX"); emitline("\n"); emitline("\tMOVQ\tCX, "); emitdispreg((fi.foff + 16): i64, "DX"); emitline("\n"); return; }; // #58: a TAGGED field of an indexed // array element (`xs[i].f = v`). The // scalar store below would write the // raw unboxed rhs into the TAG slot — // never boxing, never writing the // payload (box-corruption, the #38a // write-twin). BOX (mirror the #24 // tagged-field-assign tag lookup, // taggedvariantindext) + STORE spine // (mirror the co-located str/slice // 3-word arm above): cgexpr the // payload, spill across the index/ // address computation, compute // &xs[i]->BX, store the variant tag // (constant) at foff+0 and the scalar // payload at foff+8. Only a SCALAR- // payload variant (box <=16B) store // is wired here. A >16B / multi-word / // float-payload union field IS // constructible (a wide box, built via // a NARROW variant — not unbuildable as // earlier triage assumed; #54/#23 fires // only on STRUCT-LITERAL payloads), but // its box+memcpy store arm is not yet // wired, so it LOUD-STOPS rather than // silently corrupting the box (rule 7, // the #41 untested-arm trap), byte-id- // neutral. Reachable + pinned expect- // loud (test/wcc/944 cfail rows). When // #114 wires them, that commit replaces // these stops with the real box+memcpy // emission + value pin rows. Mirrors // cstage cgen.c. if (istaggedtype(c, fi.tnode)) { let bsz: i32 = slotsize(c, fi.tnode); if (bsz > TUPLE_GPCAP * 8) { let m58s: str = "#58: >32B tagged-field indexed store unreachable until #114\n"; os.write(2, m58s.ptr, m58s.len: u64); os.exit(1); }; if (bsz > 16) { let m58m: str = "#58: multi-word tagged-field indexed store unreachable until #114\n"; os.write(2, m58m.ptr, m58m.len: u64); os.exit(1); }; if (syntax.typeisfloat(n.rhs.type_: *syntax.tinfo)) { let m58f: str = "#58: float-payload tagged-field indexed store unreachable until #114\n"; os.write(2, m58f.ptr, m58f.len: u64); os.exit(1); }; cgexpr(c, n.rhs); emitline("\tPUSHQ\tAX\n"); cgexpr(c, idx); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", CX\n"); emitline("\tIMULQ\tCX, AX\n"); }; if (baseisarray) { emitline("\tLEAQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); } else { emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); }; emitline("\tADDQ\tAX, BX\n"); if (viaptr) { emitline("\tMOVQ\t(BX), BX\n"); }; emitline("\tPOPQ\tAX\n"); let v58tag: i32 = taggedvariantindext(c, fi.tnode.type_: *syntax.tinfo, n.rhs); if (v58tag < 0) { let mws: str = "tagged widen: no variant tag for source (rule 7)\n"; os.write(2, mws.ptr, mws.len: u64); os.exit(1); }; emitline("\tMOVQ\t$"); emitint(v58tag: i64); emitline(", "); emitdispreg(fi.foff: i64, "BX"); emitline("\n"); emitline("\tMOVQ\tAX, "); emitdispreg((fi.foff + 8): i64, "BX"); emitline("\n"); return; }; // #11: an in-cap aggregate-returning CALL into // an AGGREGATE field of an indexed element // `arr[i].f = mk()`. The scalar default below // stores only AX (eb0), dropping DX/CX — a // SILENT both-stage field-drop, the field-of- // indexed twin of C2c's whole-element // arr[i]=mk() arm (cgenexpr.ww :9100). Scratch- // first materialise of the AX/DX/CX return (not // a PUSHQ spill — keeps the CALL at the frame's // 16B alignment and survives an idx that itself // contains a call), reuse the scalar arm's // &arr[i]->BX address computation verbatim, then // word-copy scratch to fi.foff(BX). tsz from the // type table (fieldsize, rule 13). In-cap only // (callsretsize==0); over-cap sret-into-field // LOUD-STOPS (#11c/#234, task #8) and a float- // bearing aggregate LOUD-STOPS (#165/#171 — a // pure-float return eightbyte rides X0/X1 which // the GP AX/DX/CX cursor cannot read). Mirrors // cstage cgen.c. // #12: an unwrap `arr[i].f = mk()!` rides the same // {AX,DX,CX} shape (producer shift) — admit it // alongside N_CALL; the #11b non-call arm below // excludes the unwrap kinds so this arm is the SOLE // handler. callsretsize==0 for a non-call, so an // over-cap unwrap stays loud at the producer. if (n.op == syntax.tkind.TK_ASSIGN && n.rhs != nil && (n.rhs.kind == syntax.nkind.N_CALL || n.rhs.kind == syntax.nkind.N_TRYUNW || n.rhs.kind == syntax.nkind.N_TRYPROP)) { if (callsretsize(c, n.rhs) > 0) { let m11o: str = "#11c/#234: over-cap (sret) aggregate field receive arr[i].f=mk() unwired (cs!=ww; task #8)\n"; os.write(2, m11o.ptr, m11o.len: u64); os.exit(1); }; // structfloatclass mirrors the return- // side SSE routing (cgenstmt.ww #171a); // a field typed DIRECTLY as a tuple // misses it (non-N_TNAME) yet the bare- // tuple return routes floats to tupsse — // guard it too so neither stage silently // stores X0 garbage through the GP cursor. let sse11: bool = structfloatclass(c, fi.tnode) != 0; if (!sse11) { let rt11: *syntax.node = resolvetype(c, fi.tnode); if (rt11 != nil) { if (rt11.kind == syntax.nkind.N_TTUPLE) { let q11: *syntax.node = rt11.list; for (q11 != nil) { if (isfloattype(c, q11.lhs)) { sse11 = true; }; q11 = q11.next; }; };}; }; if (sse11) { let m11f: str = "#11/#165: float-bearing aggregate field receive arr[i].f=mk() unwired (SSE return eightbyte; #171)\n"; os.write(2, m11f.ptr, m11f.len: u64); os.exit(1); }; // fi.fsz is the field's NATURAL size // (tf.type_.size, cgenutil.ww :2736) — // the byte-id twin of cstage's // fsz=ft->size. fieldsize() returns the // slot-PADDED size (16 for a 12B // 3×i32), which both diverges from // cstage AND a full-MOVQ tail on it // would smash the next field (g at +12). let tsz: i32 = fi.fsz; if (tsz > 8) { // The sub-8 tail materialise stores the FULL // 8-byte register (MOVQ) into a ceil-8-padded // scratch (tagscradd -> localadd rounds to 8): // the over-stored high bytes land in the pad and // the scratch->dest copy reads only tsz bytes, so // every in-cap tail (incl. 3/5/6/7) is exact // without an immediate-shift cascade (w6a has no // SHRQ $imm). Shares the C2c whole-element // materialise (:9100). #10; mirrors cstage cgen.c. let scr11: i32 = tagscradd(c, tsz); cgexpr(c, n.rhs); // AX/DX/CX -> scratch: #14 folds this materialise into the choke- // point (dest_padded=true; the scratch IS a ceil-8 slot so the // tail eightbyte over-store lands in its pad — byte-identical to // the prior inline form). cgaggregstore(c, "BP", scr11, tsz, true); // &arr[i] -> BX (verbatim scalar arm) cgexpr(c, idx); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", CX\n"); emitline("\tIMULQ\tCX, AX\n"); }; if (baseisarray) { emitline("\tLEAQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); } else { emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); }; emitline("\tADDQ\tAX, BX\n"); if (viaptr) { emitline("\tMOVQ\t(BX), BX\n"); }; // word-copy scratch -> fi.foff(BX), // tail-aware (C2c copy); foff on every // eightbyte, sub-8 tail stays sub-8. let kc11: i32 = 0; for (kc11 + 8 <= tsz) { emitline("\tMOVQ\t"); emitoff((scr11 + kc11): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\tAX, "); emitdispreg((fi.foff + kc11): i64, "BX"); emitline("\n"); kc11 += 8; }; if (kc11 + 4 <= tsz) { emitline("\tMOVL\t"); emitoff((scr11 + kc11): i64); emitline("(BP), AX\n"); emitline("\tMOVL\tAX, "); emitdispreg((fi.foff + kc11): i64, "BX"); emitline("\n"); kc11 += 4; }; if (kc11 + 2 <= tsz) { emitline("\tMOVW\t"); emitoff((scr11 + kc11): i64); emitline("(BP), AX\n"); emitline("\tMOVW\tAX, "); emitdispreg((fi.foff + kc11): i64, "BX"); emitline("\n"); kc11 += 2; }; if (kc11 + 1 <= tsz) { emitline("\tMOVB\t"); emitoff((scr11 + kc11): i64); emitline("(BP), AX\n"); emitline("\tMOVB\tAX, "); emitdispreg((fi.foff + kc11): i64, "BX"); emitline("\n"); kc11 += 1; }; return; }; // tsz<=8 in-cap aggregate returns wholly // in AX; the scalar default's MOVQ/MOVL AX // store is the correct 1-word receive. }; // #11b: a NON-call AGGREGATE source into an aggregate // field of an indexed element `arr[i].f = src` (src an // ident / .g / index). The scalar default below loads // only the source's FIRST word into AX and stores ONE // word — dropping the rest (a SILENT both-stage member // drop, the non-call twin of the #11 in-cap CALL arm // above; byte-id blind). Unlike #11's GP AX/DX/CX cursor // the source is a MEMORY address, so the shared mem-to- // mem aggcopy transports EVERY byte: a sub-8 tail (MOVL/ // MOVW/MOVB) and float bits copy verbatim, so NO tail/ // float/over-cap loud-stop is needed here (those #11 // stops were register-cursor artefacts). Reuse the // block's own &arr[i] spine (proven for [N]S / *[N]S / // []S by the sibling arms) -> BX + fi.foff, then funnel // through aggargsrcaddr (src -> SI) + aggcopy — the ONE // copy emitter the non-indexed bases use (DRY, rule 8). // tsz natural (fi.fsz, type table). Mirrors cstage cgen.c. // #12: exclude the unwrap kinds — the #11 arm above is // their sole handler (they ride the {AX,DX,CX} register // cursor, NOT a source address, so aggargsrcaddr can't // reach them). Pre-#12 an N_TRYUNW matched != N_CALL and // loud-stopped here; now it materialises in the #11 arm. if (n.op == syntax.tkind.TK_ASSIGN && n.rhs != nil && n.rhs.kind != syntax.nkind.N_CALL && n.rhs.kind != syntax.nkind.N_TRYUNW && n.rhs.kind != syntax.nkind.N_TRYPROP) { let fk11b: *syntax.tinfo = tichase(fi.tnode.type_: *syntax.tinfo); let isagg11b: bool = false; if (fk11b != nil) { if (fk11b.kind == syntax.tykind.TY_STRUCT || fk11b.kind == syntax.tykind.TY_ARRAY || fk11b.kind == syntax.tykind.TY_TUPLE) { isagg11b = true; }; }; if (isagg11b) { let tsz11b: i32 = fi.fsz; if (tsz11b > 8) { // &arr[i].f -> BX (verbatim sibling-arm spine) cgexpr(c, idx); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", CX\n"); emitline("\tIMULQ\tCX, AX\n"); }; if (baseisarray) { emitline("\tLEAQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); } else { emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); }; emitline("\tADDQ\tAX, BX\n"); if (viaptr) { emitline("\tMOVQ\t(BX), BX\n"); }; if (fi.foff != 0) { emitline("\tADDQ\t$"); emitint(fi.foff: i64); emitline(", BX\n"); }; // spill dest across the source-address resolution // (the #270-1b order: aggargsrcaddr clobbers BX). emitline("\tPUSHQ\tBX\n"); if (!aggargsrcaddr(c, n.rhs, "SI")) { let m11b: str = "#11b: aggregate field receive arr[i].f=src - source shape unwired (rule-7)\n"; os.write(2, m11b.ptr, m11b.len: u64); os.exit(1); }; emitline("\tPOPQ\tBX\n"); aggcopy(c, tsz11b); return; }; // tsz<=8 aggregate: one word, the scalar default's // single store is the correct copy. }; }; // scalar plain `=` cgexpr(c, n.rhs); emitline("\tPUSHQ\tAX\n"); cgexpr(c, idx); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", CX\n"); emitline("\tIMULQ\tCX, AX\n"); }; if (baseisarray) { emitline("\tLEAQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); } else { emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); }; emitline("\tADDQ\tAX, BX\n"); if (viaptr) { emitline("\tMOVQ\t(BX), BX\n"); }; emitline("\tPOPQ\tAX\n"); let sop: str = fieldstoreop(c, fi); emitline("\t"); emitline(sop); emitline("\tAX, "); emitdispreg(fi.foff: i64, "BX"); emitline("\n"); return; }; // compound: rhs→push; compute struct // addr→BX (deref if *T); push addr; // load old field→AX; pop addr→BX, // rhs→CX; combine; store. #33/#263: // all 10 integer ops wired (was 6 → // SLASHEQ/PERCENTEQ/LSHIFTEQ/RSHIFTEQ // silently no-op'd in BOTH stages); // float/str/slice/tagged field hard- // errors LOUD. Mirrors cstage cgen.c // arr[i].field compound twin. if (istaggedtype(c, fi.tnode)) { let m: str = "arr[i].field compound on tagged field not wired (#33/rule-7)\n"; os.write(2, m.ptr, m.len: u64); os.exit(1); }; if (isstrtype(c, fi.tnode)) { let m: str = "arr[i].field compound on str field not wired (#33/rule-7)\n"; os.write(2, m.ptr, m.len: u64); os.exit(1); }; if (isslicetype(c, fi.tnode)) { let m: str = "arr[i].field compound on slice field not wired (#33/rule-7)\n"; os.write(2, m.ptr, m.len: u64); os.exit(1); }; if (isfloattype(c, fi.tnode)) { let m: str = "arr[i].field compound on float field not wired (#33/rule-7)\n"; os.write(2, m.ptr, m.len: u64); os.exit(1); }; cgexpr(c, n.rhs); emitline("\tPUSHQ\tAX\n"); cgexpr(c, idx); if (esz > 1) { emitline("\tMOVQ\t$"); emitint(esz: i64); emitline(", CX\n"); emitline("\tIMULQ\tCX, AX\n"); }; if (baseisarray) { emitline("\tLEAQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); } else { emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); }; emitline("\tADDQ\tAX, BX\n"); if (viaptr) { emitline("\tMOVQ\t(BX), BX\n"); }; emitline("\tPUSHQ\tBX\n"); let lop: str = fieldloadop(c, fi); emitline("\t"); emitline(lop); emitline("\t"); emitdispreg(fi.foff: i64, "BX"); emitline(", AX\n"); emitline("\tPOPQ\tBX\n"); emitline("\tPOPQ\tCX\n"); let unsignd_x: bool = false; if (fi.tnode != nil) { if (fi.tnode.type_ != nil) { unsignd_x = syntax.typeisunsigned(fi.tnode.type_: *syntax.tinfo); }; }; let wired_x: bool = false; if (n.op == syntax.tkind.TK_PLUSEQ) { emitline("\tADDQ\tCX, AX\n"); wired_x = true; }; if (n.op == syntax.tkind.TK_MINUSEQ) { emitline("\tSUBQ\tCX, AX\n"); wired_x = true; }; if (n.op == syntax.tkind.TK_STAREQ) { emitline("\tIMULQ\tCX, AX\n"); wired_x = true; }; if (n.op == syntax.tkind.TK_AMPEQ) { emitline("\tANDQ\tCX, AX\n"); wired_x = true; }; if (n.op == syntax.tkind.TK_PIPEEQ) { emitline("\tORQ\tCX, AX\n"); wired_x = true; }; if (n.op == syntax.tkind.TK_CARETEQ) { emitline("\tXORQ\tCX, AX\n"); wired_x = true; }; if (n.op == syntax.tkind.TK_SLASHEQ) { if (unsignd_x) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); } else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); }; wired_x = true; }; if (n.op == syntax.tkind.TK_PERCENTEQ) { if (unsignd_x) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); } else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); }; emitline("\tMOVQ\tDX, AX\n"); wired_x = true; }; if (n.op == syntax.tkind.TK_LSHIFTEQ) { emitline("\tSHLQ\tCX, AX\n"); wired_x = true; }; if (n.op == syntax.tkind.TK_RSHIFTEQ) { if (unsignd_x) { emitline("\tSHRQ\tCX, AX\n"); } else { emitline("\tSARQ\tCX, AX\n"); }; wired_x = true; }; if (!wired_x) { let m: str = "arr[i].field compound: unknown op (#33/rule-7)\n"; os.write(2, m.ptr, m.len: u64); os.exit(1); }; let sop2: str = fieldstoreop(c, fi); emitline("\t"); emitline(sop2); emitline("\tAX, "); emitdispreg(fi.foff: i64, "BX"); emitline("\n"); return; }; fi = fi.finext; }; }; }; };}; }; };}; }; }; // 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). Base accepts the // explicit-deref form `(*p).f = ...` (parser N_UN(STAR, IDENT)) // by retargeting to the inner IDENT so the via_ptr branch fires // the same as auto-deref `p.f = v`. Other addressable bases use // the shared place resolver below. if (lhs != nil) { if (lhs.kind == syntax.nkind.N_DOT) { let base: *syntax.node = lhs.lhs; let fld: str = lhs.str; if (base != nil) { if (base.kind == syntax.nkind.N_UN) { if (base.op == syntax.tkind.TK_STAR) { if (base.lhs != nil) { if (base.lhs.kind == syntax.nkind.N_IDENT) { base = base.lhs; }; }; }; }; if (base.kind == syntax.nkind.N_IDENT) { let bn: str = base.str; let lc: *local = localfindnode(c, bn); if (lc != nil) { let tn: *syntax.node = lc.tnode; let lkind: syntax.nkind = syntax.nkind.N_NONE; if (tn != nil) { lkind = tn.kind; }; // Pointer-to-struct: deref then store. if (lkind == syntax.nkind.N_TPTR) { // #31: resolve the *struct field OFFSET + type off the // checker-STAMPED receiver tinfo (tichase(base.type_)->.sub), // NOT the name-keyed leaf lookup. A cross-module same-leaf // collision makes the bare leaf mis-resolve to a FOREIGN // same-leaf struct -> the field is STORED at the wrong // offset/width (the inferred-local ptr-WRITE row). The // stamped tinfo carries the right layout; mirror cstage // type_chase_named(bu->sub)->fields. The in-loop #32 nested // struct-receive/structlit/ident sub-arms close by // construction (the tf walk has no name lookup). Non-struct // pointees fall through to the str/slice arm. let sti: *syntax.tinfo = tichase(base.type_: *syntax.tinfo); if (sti != nil && sti.kind == syntax.tykind.TY_PTR) { sti = tichase(sti.sub); }; if (sti != nil) { if (sti.kind == syntax.tykind.TY_STRUCT) { let tf: *syntax.tfield = sti.fields; for (tf != nil) { if (syntax.streq(tf.name, fld)) { let foff: i32 = tf.offset: i32; let ftraw: *syntax.tinfo = tf.type_; let fu: *syntax.tinfo = tichase(ftraw); // Tagged-union field via *struct base — full slot // rewrite via cgwidentaggedstore basereg="BX". Pre-#26 // fell through to the scalar store and dropped tag // + payload. if (n.op == syntax.tkind.TK_ASSIGN && syntax.typeistagged(ftraw)) { let fsz: i32 = fu.size: i32; emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); cgwidentaggedstore(c, ftraw, n.rhs, "BX", foff, fsz); return; }; // #234-tail: via-ptr (`p.f`) sret field STORE. The dest must // be a runtime RDI pointer (the BP-relative c.sretdestoff // can't name `p.f`); deferred. HARD-STOP loud, never fall // through to the truncating generic store (rule 7). if (n.op == syntax.tkind.TK_ASSIGN && n.rhs != nil && n.rhs.kind == syntax.nkind.N_CALL && sretretsizetn(c, ftraw) > 0) { let m234: str = "#234-tail: over-cap tuple sret store to via-ptr field dest unsupported\n"; os.write(2, m234.ptr, m234.len: u64); os.exit(1); }; // struct-typed field via *struct base — three // rhs shapes (call/structlit added with #5; // closes #27 marker here): // N_IDENT: word-copy from rhs slot. // N_CALL: cgexpr → AX/DX/CX per #4's cgreturn // ABI; load *struct ptr into BX after the // call, sized stores per the ABI size. // N_STRUCTLIT: field-walk; reload BX before // each store so cgexpr can clobber AX/BX. // register RECV reads AX/DX/CX at 8-byte // granularity — size via structabisize (cstage // SSoT lu->size, check.c:760; cgen.c:7720 // sz=lu->size at the receive twin). #12: an // unwrap `p.f = mk()!` rides the same {AX,DX,CX} // shape (producer shift) — admit it alongside // N_CALL; float/over-cap loud-stop at the producer. if (n.op == syntax.tkind.TK_ASSIGN && n.rhs != nil && (n.rhs.kind == syntax.nkind.N_CALL || n.rhs.kind == syntax.nkind.N_TRYUNW || n.rhs.kind == syntax.nkind.N_TRYPROP)) { if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) { let ssz: i32 = fu.size: i32; if (ssz <= 24) { // #14: choke-point now stores every in-cap tail; 3/5/6/7 no longer dropped to a lone narrow MOV. cgexpr(c, n.rhs); emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); cgaggregstore(c, "BX", foff, ssz, false); return; }; }; }; // #18: delegate to cgstructlitfill so a nested struct- // typed structlit value recurses instead of dropping // its trailing bytes. mode=1 (DST_PTR_LOCAL) reloads BX // from lc.off(BP) before zero-fill and before every // field store. if (n.op == syntax.tkind.TK_ASSIGN && n.rhs != nil && n.rhs.kind == syntax.nkind.N_STRUCTLIT) { if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) { cgstructlitfilltn(c, fu, n.rhs, 1, lc.off, "", foff); return; }; }; if (n.op == syntax.tkind.TK_ASSIGN && n.rhs != nil && n.rhs.kind == syntax.nkind.N_IDENT) { let srhs: *local = localfindnode(c, n.rhs.str); if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) { if (srhs != nil) { emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); let ssz: i32 = copysrcnatsize(c, n.rhs); // #71: natural source size, not slot-padded totsize let k: i32 = 0; for (k + 8 <= ssz) { emitline("\tMOVQ\t"); emitoff((srhs.off + k): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\tAX, "); emitdispreg((foff + k): i64, "BX"); emitline("\n"); k += 8; }; if (k + 4 <= ssz) { emitline("\tMOVL\t"); emitoff((srhs.off + k): i64); emitline("(BP), AX\n"); emitline("\tMOVL\tAX, "); emitdispreg((foff + k): i64, "BX"); emitline("\n"); k += 4; }; if (k + 2 <= ssz) { emitline("\tMOVW\t"); emitoff((srhs.off + k): i64); emitline("(BP), AX\n"); emitline("\tMOVW\tAX, "); emitdispreg((foff + k): i64, "BX"); emitline("\n"); k += 2; }; if (k + 1 <= ssz) { emitline("\tMOVB\t"); emitoff((srhs.off + k): i64); emitline("(BP), AX\n"); emitline("\tMOVB\tAX, "); emitdispreg((foff + k): i64, "BX"); emitline("\n"); k += 1; }; return; };}; }; if (n.op != syntax.tkind.TK_ASSIGN) { // compound: load current value emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); let lop: str = loadopsz(syntax.typeissigned(ftraw), fu.size: i32); emitline("\t"); emitline(lop); emitline("\t"); emitdispreg(foff: i64, "BX"); emitline(", BX\n"); emitline("\tPUSHQ\tBX\n"); }; cgexpr(c, n.rhs); if (n.op != syntax.tkind.TK_ASSIGN) { emitline("\tPOPQ\tBX\n"); // PLUSEQ is commutative; MINUSEQ // needs lhs - rhs (BX is old lhs, // AX is rhs). cgdotfieldhardstoptn(c, ftraw); let uns34: bool = false; uns34 = syntax.typeisunsigned(ftraw); cgdotfieldcombine(c, n.op, uns34); }; if (n.op == syntax.tkind.TK_ASSIGN) { // str/slice field via *struct: str IS []u8, so both // store the full 3-word {ptr,len,cap} from (AX,BX,CX). // CX holds cap, so stage the struct addr in DX and // store at foff/+8/+16 (#1/Phase 3). if (syntax.typeisstr(ftraw) || syntax.typeisslice(ftraw)) { emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), DX\n"); emitline("\tMOVQ\tAX, "); emitdispreg(foff: i64, "DX"); emitline("\n"); emitline("\tMOVQ\tBX, "); emitdispreg((foff + 8): i64, "DX"); emitline("\n"); emitline("\tMOVQ\tCX, "); emitdispreg((foff + 16): i64, "DX"); emitline("\n"); return; }; // f64/f32 plain `=` via *struct: cgexpr left the // value in X0. Reload struct ptr and MOVSD/MOVSS. if (syntax.typeisfloat(ftraw)) { let mov: str = "MOVSD"; if (syntax.typeisf32(ftraw)) { mov = "MOVSS"; }; emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); emitline("\t"); emitline(mov); emitline("\tX0, "); emitdispreg(foff: i64, "BX"); emitline("\n"); return; }; }; emitline("\tMOVQ\t"); emitoff(lc.off: i64); emitline("(BP), BX\n"); let sop: str = storeopsz(fu.size: i32); emitline("\t"); emitline(sop); emitline("\tAX, "); emitdispreg(foff: i64, "BX"); emitline("\n"); return; }; tf = tf.tnext; }; }; }; }; // Direct struct local: store at off+foff. if (lkind == syntax.nkind.N_TNAME) { // #31: resolve the value-struct field OFFSET + type off the // checker-STAMPED receiver tinfo (tichase(base.type_)), NOT // the name-keyed leaf lookup. A cross-module same-leaf // collision makes the bare leaf mis-resolve to a FOREIGN // same-leaf struct -> the field is STORED at the wrong // offset/width (the inferred-local val-WRITE row, ww=107). // The stamped tinfo carries the right layout; mirror cstage // type_chase_named(base->type)->fields. The in-loop #32 // nested struct-receive/structlit/ident sub-arms close by // construction (the tf walk has no name lookup). let sti: *syntax.tinfo = tichase(base.type_: *syntax.tinfo); if (sti != nil && sti.kind == syntax.tykind.TY_STRUCT) { let tf: *syntax.tfield = sti.fields; for (tf != nil) { if (syntax.streq(tf.name, fld)) { let foff: i32 = tf.offset: i32; let ftraw: *syntax.tinfo = tf.type_; let fu: *syntax.tinfo = tichase(ftraw); // Tagged-union field in a direct struct local — // full slot rewrite at (lc.off + foff)(BP) // via cgwidentaggedstore basereg="BP". Pre-#26 // fell through and dropped tag + payload. if (n.op == syntax.tkind.TK_ASSIGN && syntax.typeistagged(ftraw)) { let fsz: i32 = fu.size: i32; cgwidentaggedstore(c, ftraw, n.rhs, "BP", lc.off + foff, fsz); return; }; // #234: over-cap sret STORE into a LOCAL struct field — // `s.f = wide();` where f's type returns via sret // (sretretsize > 0: a >24B struct OR an over-cap tuple). // STORE-twin of the Fold-B sret RECEIVE (a937d67): point // the callee's hidden RDI dest at the field slot // (c.sretdestoff = lc.off + foff) so it writes the // WHOLE value there, never the truncating generic store // below. Mirror of cstage cgen.c (#234) field local arm. if (n.op == syntax.tkind.TK_ASSIGN && n.rhs != nil && n.rhs.kind == syntax.nkind.N_CALL && sretretsizetn(c, ftraw) > 0) { c.sretdestoff = lc.off + foff; cgexpr(c, n.rhs); c.sretdestoff = 0; return; }; // struct-typed field on a direct struct // local — three rhs shapes (call/structlit // added with #5; closes #27 marker here): // N_IDENT: word-copy from rhs slot. // N_CALL: cgexpr → AX/DX/CX; sized stores // directly at (lc.off+foff)(BP). // N_STRUCTLIT: field-walk; each inner // field stored at +foff+inner_foff(BP). // BP-rel direct, no addr scratch needed. #12: an // unwrap `b.f = mk()!` rides the same {AX,DX,CX} // shape (producer shift) — admit it alongside // N_CALL; float/over-cap loud-stop at the producer. if (n.op == syntax.tkind.TK_ASSIGN && n.rhs != nil && (n.rhs.kind == syntax.nkind.N_CALL || n.rhs.kind == syntax.nkind.N_TRYUNW || n.rhs.kind == syntax.nkind.N_TRYPROP)) { if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) { let ssz: i32 = fu.size: i32; if (ssz <= 24) { // #14: choke-point now stores every in-cap tail; 3/5/6/7 no longer dropped to a lone narrow MOV. cgexpr(c, n.rhs); cgaggregstore(c, "BP", lc.off + foff, ssz, false); return; }; }; }; // #18: delegate to cgstructlitfill so a nested struct- // typed structlit value recurses instead of dropping // its trailing bytes. mode=0 (DST_BP) — direct BP-rel, // no BX reload. if (n.op == syntax.tkind.TK_ASSIGN && n.rhs != nil && n.rhs.kind == syntax.nkind.N_STRUCTLIT) { if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) { cgstructlitfilltn(c, fu, n.rhs, 0, 0, "", lc.off + foff); return; }; }; if (n.op == syntax.tkind.TK_ASSIGN && n.rhs != nil && n.rhs.kind == syntax.nkind.N_IDENT) { let srhs: *local = localfindnode(c, n.rhs.str); if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) { if (srhs != nil) { let ssz: i32 = copysrcnatsize(c, n.rhs); // #71: natural source size, not slot-padded totsize let k: i32 = 0; for (k + 8 <= ssz) { emitline("\tMOVQ\t"); emitoff((srhs.off + k): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\tAX, "); emitoff((lc.off + foff + k): i64); emitline("(BP)\n"); k += 8; }; if (k + 4 <= ssz) { emitline("\tMOVL\t"); emitoff((srhs.off + k): i64); emitline("(BP), AX\n"); emitline("\tMOVL\tAX, "); emitoff((lc.off + foff + k): i64); emitline("(BP)\n"); k += 4; }; if (k + 2 <= ssz) { emitline("\tMOVW\t"); emitoff((srhs.off + k): i64); emitline("(BP), AX\n"); emitline("\tMOVW\tAX, "); emitoff((lc.off + foff + k): i64); emitline("(BP)\n"); k += 2; }; if (k + 1 <= ssz) { emitline("\tMOVB\t"); emitoff((srhs.off + k): i64); emitline("(BP), AX\n"); emitline("\tMOVB\tAX, "); emitoff((lc.off + foff + k): i64); emitline("(BP)\n"); k += 1; }; return; };}; }; if (n.op != syntax.tkind.TK_ASSIGN) { // Compound on direct struct-local // scalar field: load current → push // → eval rhs → combine → store // (mirror cgen.c:3477 local arm). let lop: str = loadopsz(syntax.typeissigned(ftraw), fu.size: i32); emitline("\t"); emitline(lop); emitline("\t"); emitoff((lc.off + foff): i64); emitline("(BP), BX\n"); emitline("\tPUSHQ\tBX\n"); }; cgexpr(c, n.rhs); if (n.op != syntax.tkind.TK_ASSIGN) { emitline("\tPOPQ\tBX\n"); // PLUSEQ commutes; MINUSEQ needs // lhs-rhs (BX old lhs, AX rhs). cgdotfieldhardstoptn(c, ftraw); let uns34: bool = false; uns34 = syntax.typeisunsigned(ftraw); cgdotfieldcombine(c, n.op, uns34); }; // str/slice field direct: str IS []u8, so both store the // full 3-word {ptr,len,cap} from (AX,BX,CX) at +0/+8/+16. // BP base, no scratch reload needed; the generic fldstoreop // below would write only AX, dropping .len/.cap (#1/Phase 3). if (syntax.typeisstr(ftraw) || syntax.typeisslice(ftraw)) { emitline("\tMOVQ\tAX, "); emitoff((lc.off + foff): i64); emitline("(BP)\n"); emitline("\tMOVQ\tBX, "); emitoff((lc.off + foff + 8): i64); emitline("(BP)\n"); emitline("\tMOVQ\tCX, "); emitoff((lc.off + foff + 16): i64); emitline("(BP)\n"); return; }; // f64/f32 direct struct local store: route via X0. if (syntax.typeisfloat(ftraw)) { let mov: str = "MOVSD"; if (syntax.typeisf32(ftraw)) { mov = "MOVSS"; }; emitline("\t"); emitline(mov); emitline("\tX0, "); emitoff((lc.off + foff): i64); emitline("(BP)\n"); return; }; let sop: str = storeopsz(fu.size: i32); emitline("\t"); emitline(sop); emitline("\tAX, "); emitoff((lc.off + foff): i64); emitline("(BP)\n"); return; }; tf = tf.tnext; }; }; }; // str/slice pseudo-field assignment. let delta: i32 = -1; if (syntax.streq(fld, "ptr")) { delta = 0; }; if (syntax.streq(fld, "len")) { delta = 8; }; if (syntax.streq(fld, "cap")) { delta = 16; }; if (delta >= 0) { if (lkind == syntax.nkind.N_TPTR) { let inner: *syntax.node = tn.lhs; let innerkind: syntax.nkind = syntax.nkind.N_NONE; if (inner != nil) { innerkind = inner.kind; }; let innerstr: bool = false; if (innerkind == syntax.nkind.N_TNAME) { if (syntax.streq(inner.str, "str")) { innerstr = true; }; }; if (innerkind == syntax.nkind.N_TSLICE) { innerstr = true; }; if (innerstr) { if (n.op != syntax.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. cgdotfieldcombine(c, n.op, false); 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; }; }; if (n.op != syntax.tkind.TK_ASSIGN) { // Compound on local `str|slice` pseudo-field: // load current → push → eval rhs → combine → // store (mirror cgen.c:3235 local arm). emitline("\tMOVQ\t"); emitoff((lc.off + delta): i64); emitline("(BP), BX\n"); emitline("\tPUSHQ\tBX\n"); cgexpr(c, n.rhs); emitline("\tPOPQ\tBX\n"); cgdotfieldcombine(c, n.op, false); emitline("\tMOVQ\tAX, "); emitoff((lc.off + delta): i64); emitline("(BP)\n"); return; }; cgexpr(c, n.rhs); emitline("\tMOVQ\tAX, "); emitoff((lc.off + delta): i64); emitline("(BP)\n"); return; }; }; }; }; }; }; // Top-level global field assignment: `g.f = expr;` and `g.f += expr;` // for a struct field or str/slice pseudo-field. Reached when the local // lookup misses but the IDENT base is a registered module `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 == syntax.nkind.N_DOT) { let base: *syntax.node = lhs.lhs; let fld: str = lhs.str; if (base != nil) { if (base.kind == syntax.nkind.N_IDENT) { let bn: str = base.str; if (localfindnode(c, bn) == nil) { let btype: *syntax.tinfo = base.type_: *syntax.tinfo; if (isletvar(c, bn) && (syntax.typeisstr(btype) || syntax.typeisslice(btype))) { let delta: i32 = -1; if (syntax.streq(fld, "ptr")) { delta = 0; }; if (syntax.streq(fld, "len")) { delta = 8; }; if (syntax.streq(fld, "cap")) { delta = 16; }; if (delta >= 0) { if (n.op != syntax.tkind.TK_ASSIGN) { emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); emitline("\tMOVQ\t"); emitdispreg(delta: i64, "BX"); emitline(", BX\n"); emitline("\tPUSHQ\tBX\n"); }; cgexpr(c, n.rhs); if (n.op != syntax.tkind.TK_ASSIGN) { emitline("\tPOPQ\tBX\n"); cgdotfieldcombine(c, n.op, false); }; emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); emitline("\tMOVQ\tAX, "); emitdispreg(delta: i64, "BX"); emitline("\n"); return; }; }; // #31: resolve the global value-struct field OFFSET + type // off the checker-STAMPED receiver tinfo (tichase(base.type_)), // NOT the name-keyed global-struct leaf lookup. A global decl // is qualified -> non-reddenable; converted for close-by- // construction (byte-id). Mirror cstage type-keyed N_DOT global // arm. The in-loop #32 nested struct-receive/structlit/ident // sub-arms close by construction (the tf walk has no name // lookup). let sti: *syntax.tinfo = tichase(base.type_: *syntax.tinfo); if (sti != nil && sti.kind == syntax.tykind.TY_STRUCT) { let tf: *syntax.tfield = sti.fields; for (tf != nil) { if (syntax.streq(tf.name, fld)) { let foff: i32 = tf.offset: i32; let ftraw: *syntax.tinfo = tf.type_; let fu: *syntax.tinfo = tichase(ftraw); // #234-tail: GLOBAL (`g.f`) sret field STORE. c.sretdestoff is // BP-relative only and can't name a global slot; the runtime // RDI-pointer dest variant is deferred. HARD-STOP loud, never // the truncating generic store (rule 7). if (n.op == syntax.tkind.TK_ASSIGN && n.rhs != nil && n.rhs.kind == syntax.nkind.N_CALL && sretretsizetn(c, ftraw) > 0) { let m234: str = "#234-tail: over-cap tuple sret store to global field dest unsupported\n"; os.write(2, m234.ptr, m234.len: u64); os.exit(1); }; // struct-typed field on a global struct base — // three rhs shapes (call/structlit added with // #5; closes #27 marker here): // N_IDENT: word-copy from rhs slot. // N_CALL: cgexpr → AX/DX/CX; LEAQ base into BX // after call, sized stores per natural size. // N_STRUCTLIT: field-walk; reload BX per store. if (n.op == syntax.tkind.TK_ASSIGN && n.rhs != nil && n.rhs.kind == syntax.nkind.N_CALL) { if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) { let ssz: i32 = fu.size: i32; if (ssz <= 24) { // #14: choke-point now stores every in-cap tail; 3/5/6/7 no longer dropped to a lone narrow MOV. cgexpr(c, n.rhs); emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); cgaggregstore(c, "BX", foff, ssz, false); return; }; }; }; // #18: delegate to cgstructlitfill so a nested struct- // typed structlit value recurses instead of dropping // its trailing bytes. mode=2 (DST_GLOBAL) reloads BX // via LEAQ bn(SB) before zero-fill and before every // field store. if (n.op == syntax.tkind.TK_ASSIGN && n.rhs != nil && n.rhs.kind == syntax.nkind.N_STRUCTLIT) { if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) { cgstructlitfilltn(c, fu, n.rhs, 2, 0, bn, foff); return; }; }; if (n.op == syntax.tkind.TK_ASSIGN && n.rhs != nil && n.rhs.kind == syntax.nkind.N_IDENT) { let srhs: *local = localfindnode(c, n.rhs.str); if (fu != nil && fu.kind == syntax.tykind.TY_STRUCT) { if (srhs != nil) { emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); let ssz: i32 = copysrcnatsize(c, n.rhs); // #71: natural source size, not slot-padded totsize let k: i32 = 0; for (k + 8 <= ssz) { emitline("\tMOVQ\t"); emitoff((srhs.off + k): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\tAX, "); emitdispreg((foff + k): i64, "BX"); emitline("\n"); k += 8; }; if (k + 4 <= ssz) { emitline("\tMOVL\t"); emitoff((srhs.off + k): i64); emitline("(BP), AX\n"); emitline("\tMOVL\tAX, "); emitdispreg((foff + k): i64, "BX"); emitline("\n"); k += 4; }; if (k + 2 <= ssz) { emitline("\tMOVW\t"); emitoff((srhs.off + k): i64); emitline("(BP), AX\n"); emitline("\tMOVW\tAX, "); emitdispreg((foff + k): i64, "BX"); emitline("\n"); k += 2; }; if (k + 1 <= ssz) { emitline("\tMOVB\t"); emitoff((srhs.off + k): i64); emitline("(BP), AX\n"); emitline("\tMOVB\tAX, "); emitdispreg((foff + k): i64, "BX"); emitline("\n"); k += 1; }; return; };}; }; // #129: tagged-union field on global struct. LEAQ // base(SB) into BX then the shared widener handles // every rhs shape. Mirrors cstage cgen.c:4902 // is_global arm. Without this the generic TK_ASSIGN // below truncates to 1 word, silently dropping tag // and payload. if (n.op == syntax.tkind.TK_ASSIGN && syntax.typeistagged(ftraw)) { let fsz: i32 = fu.size: i32; emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); cgwidentaggedstore(c, ftraw, n.rhs, "BX", foff, fsz); return; }; if (n.op == syntax.tkind.TK_ASSIGN) { cgexpr(c, n.rhs); if (syntax.typeisstr(ftraw) || syntax.typeisslice(ftraw)) { // str OR slice field: str IS []u8, so // both store the full {ptr,len,cap} // header (cstage cgen.c:5055 gates // TY_STR||TY_SLICE the same; without the // slice arm this dropped to the 1-word // scalar store below). cgexpr left // (AX=ptr, BX=len, CX=cap). CX holds cap, // so stage the base addr in DX and store // all three words (#1/Phase 3). emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), DX\n"); emitline("\tMOVQ\tAX, "); emitdispreg(foff: i64, "DX"); emitline("\n"); emitline("\tMOVQ\tBX, "); emitdispreg((foff + 8): i64, "DX"); emitline("\n"); emitline("\tMOVQ\tCX, "); emitdispreg((foff + 16): i64, "DX"); emitline("\n"); return; }; // f64/f32 plain `=` on global struct field: value is // in X0; LEAQ the base into BX and MOVSD/MOVSS. if (syntax.typeisfloat(ftraw)) { let mov: str = "MOVSD"; if (syntax.typeisf32(ftraw)) { mov = "MOVSS"; }; emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); emitline("\t"); emitline(mov); emitline("\tX0, "); emitdispreg(foff: i64, "BX"); emitline("\n"); return; }; let sop: str = storeopsz(fu.size: i32); emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); emitline("\t"); emitline(sop); emitline("\tAX, "); emitdispreg(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 = loadopsz(syntax.typeissigned(ftraw), fu.size: i32); emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); emitline("\t"); emitline(lop); emitline("\t"); emitdispreg(foff: i64, "BX"); emitline(", BX\n"); emitline("\tPUSHQ\tBX\n"); cgexpr(c, n.rhs); emitline("\tPOPQ\tBX\n"); cgdotfieldhardstoptn(c, ftraw); let uns34: bool = false; uns34 = syntax.typeisunsigned(ftraw); cgdotfieldcombine(c, n.op, uns34); let sop: str = storeopsz(fu.size: i32); emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); emitline("\t"); emitline(sop); emitline("\tAX, "); emitdispreg(foff: i64, "BX"); emitline("\n"); return; }; tf = tf.tnext; }; }; }; }; }; }; }; // #8 (rule-10 byte-id): scalar field STORE through a module-GLOBAL // `*struct` pointer base (`gp.f = v`; gp is isletvar — not a local, // not a value-struct). The local-ptr arm (lc!=nil) and the value- // struct global arm (letvarstructinfo, N_TNAME-only) both miss it, so // it fell to the F6 cgplaceaddr route which folds the field offset via // `ADDQ $foff, BX` + a plain `MOVQ AX,(BX)`; cstage's via_ptr global // scalar arm (cgen.c #47, boff==0 && let_islet) emits the DISPLACEMENT // store ` AX, foff(BX)`. Align ww UP. Plain `=` scalar only — // str/slice/tagged/float/struct fields keep their F6 route; compound // (`OP=`) stays on F6 (the latent via_ptr global LOAD is #60/#61, // deferred). The PUSHQ/POPQ AX bracketing the base load mirrors cstage // byte-for-byte (rhs in AX survives the LEAQ/MOVQ, which touch only BX). if (lhs != nil) { if (lhs.kind == syntax.nkind.N_DOT && n.op == syntax.tkind.TK_ASSIGN) { let base: *syntax.node = lhs.lhs; let fld: str = lhs.str; // `(*gp).f` parses as N_DOT over N_UN(STAR, IDENT); retarget // to the inner IDENT so it fires identically to `gp.f` // (mirror of the local-ptr arm + cstage retarget). if (base != nil) { if (base.kind == syntax.nkind.N_UN) { if (base.op == syntax.tkind.TK_STAR) { if (base.lhs != nil) { if (base.lhs.kind == syntax.nkind.N_IDENT) { base = base.lhs; }; }; }; }; }; if (base != nil) { if (base.kind == syntax.nkind.N_IDENT) { let bn: str = base.str; if (localfindnode(c, bn) == nil) { let tn: *syntax.node = letvartnode(c, bn); if (tn != nil) { if (tn.kind == syntax.nkind.N_TPTR) { // #31: scalar global *struct field store -- resolve offset + // scalar-ness off the stamped *struct tinfo // (tichase(base.type_)->.sub), NOT structlookupchain(inner). // Non-scalar fields (str/slice/tagged/float/nested-struct) // keep their F6 cgplaceaddr route (scalar=false -> fall // through). Global *struct decls are qualified -> non- // reddenable; convert for close-by-construction (byte-id). let sti: *syntax.tinfo = tichase(base.type_: *syntax.tinfo); if (sti != nil && sti.kind == syntax.tykind.TY_PTR) { sti = tichase(sti.sub); }; if (sti != nil) { if (sti.kind == syntax.tykind.TY_STRUCT) { let tf: *syntax.tfield = sti.fields; for (tf != nil) { if (syntax.streq(tf.name, fld)) { let foff: i32 = tf.offset: i32; let ftraw: *syntax.tinfo = tf.type_; // scalar only: str/slice/tagged/float/struct fields // keep their F6 route (cstage type-keys the same). let scalar: bool = true; if (syntax.typeistagged(ftraw)) { scalar = false; }; if (syntax.typeisstr(ftraw) || syntax.typeisslice(ftraw)) { scalar = false; }; if (syntax.typeisfloat(ftraw)) { scalar = false; }; let ftc: *syntax.tinfo = tichase(ftraw); if (ftc != nil) { if (ftc.kind == syntax.tykind.TY_STRUCT) { scalar = false; }; }; if (scalar) { cgexpr(c, n.rhs); emitline("\tPUSHQ\tAX\n"); emitline("\tLEAQ\t"); emitsymname(c, bn); emitline("(SB), BX\n"); emitline("\tMOVQ\t"); emitdispreg(0i64, "BX"); emitline(", BX\n"); emitline("\tPOPQ\tAX\n"); let fsz: i32 = 0; if (ftc != nil) { fsz = ftc.size: i32; }; let sop: str = "MOVQ"; if (fsz == 1) { sop = "MOVB"; } else { if (fsz == 2) { sop = "MOVW"; } else { if (fsz == 4) { sop = "MOVL"; }; }; }; emitline("\t"); emitline(sop); emitline("\tAX, "); emitdispreg(foff: i64, "BX"); emitline("\n"); return; }; }; tf = tf.tnext; }; }; }; }; }; }; }; }; }; }; // 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 == syntax.nkind.N_DOT) { let base: *syntax.node = lhs.lhs; let fld: str = lhs.str; if (base != nil) { if (base.kind == syntax.nkind.N_DOT) { // #70 (#12): inner-struct layout via the stamped // base.type_ (peel *→struct) + tinfo.fields, // replacing dotinnerstructptr's structinfo walk. // Gate is strict-equal to the deleted helper: fire // only when the chain root is a LOCAL ident AND every // dot resolves through a *struct (dotinnerstructptr // recursed per level on a *struct field, bailing on a // by-value-struct intermediate). Reproducing that // exactly avoids an untested widening past cstage. // Global-root chains stay in their pre-existing shared // base-eval breakage (filed #27). let croot: *syntax.node = base; let allptr: bool = true; for (croot != nil && croot.kind == syntax.nkind.N_DOT) { let ct: *syntax.tinfo = croot.type_: *syntax.tinfo; ct = tichase(ct); let okp: bool = false; if (ct != nil) { if (ct.kind == syntax.tykind.TY_PTR) { let cs: *syntax.tinfo = ct.sub; cs = tichase(cs); if (cs != nil) { if (cs.kind == syntax.tykind.TY_STRUCT) { okp = true; }; }; }; }; if (!okp) { allptr = false; }; croot = croot.lhs; }; let it: *syntax.tinfo = nil; if (allptr && croot != nil && croot.kind == syntax.nkind.N_IDENT && localfindnode(c, croot.str) != nil) { it = base.type_: *syntax.tinfo; }; it = tichase(it); if (it != nil) { if (it.kind == syntax.tykind.TY_PTR) { let st: *syntax.tinfo = it.sub; st = tichase(st); if (st != nil) { if (st.kind == syntax.tykind.TY_STRUCT) { let tf: *syntax.tfield = st.fields; for (tf != nil) { if (syntax.streq(tf.name, fld)) { let ft: *syntax.tinfo = tf.type_; if (n.op == syntax.tkind.TK_ASSIGN) { // tagged leaf (#38a): eval the *struct // base into BX, then the shared widener // (it spills BX across its internal // cgexpr) — same base-then-widen order // as the single-dot via-ptr arm. The // scalar tail below stored ONE sized // word at the field offset: the rhs // landed in the TAG slot (ken b8). if (syntax.typeistagged(ft)) { let flu: *syntax.tinfo = ft; flu = tichase(flu); cgexpr(c, base); emitline("\tMOVQ\tAX, BX\n"); cgwidentaggedstore(c, flu, n.rhs, "BX", tf.offset: i32, flu.size: i32); return; }; if (syntax.typeisstr(ft) || syntax.typeisslice(ft)) { // str/slice: rhs leaves AX=ptr, // BX=len, CX=cap (#1/Phase 3). Spill // all three across the base-expr eval // (it may clobber any reg), stage the // *struct ptr in DX off the str // AX/BX/CX convention (mirrors s.f=v), // then store the full triple at // foff+0/+8/+16. cgexpr(c, n.rhs); emitline("\tPUSHQ\tCX\n"); emitline("\tPUSHQ\tBX\n"); emitline("\tPUSHQ\tAX\n"); cgexpr(c, base); emitline("\tMOVQ\tAX, DX\n"); emitline("\tPOPQ\tAX\n"); emitline("\tPOPQ\tBX\n"); emitline("\tPOPQ\tCX\n"); emitline("\tMOVQ\tAX, "); emitdispreg(tf.offset: i64, "DX"); emitline("\n"); emitline("\tMOVQ\tBX, "); emitdispreg((tf.offset + 8u64): i64, "DX"); emitline("\n"); emitline("\tMOVQ\tCX, "); emitdispreg((tf.offset + 16u64): i64, "DX"); 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 (syntax.typeisfloat(ft)) { let mov: str = "MOVSD"; if (syntax.typeisf32(ft)) { 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(tf.offset: 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 = tnodestoreop(c, n.rhs, ft.slotsize: i32); emitline("\t"); emitline(sop); emitline("\tAX, "); emitdispreg(tf.offset: i64, "BX"); emitline("\n"); return; }; // #133-expanded site 3: chained-pointer- // field compound. Pre-#133-expanded the // wwstage chained-DOT-spine branch only // handled TK_ASSIGN; compound ops on a // chained-*struct.field shape (e.g. // `d.i.v += 7`) silently emitted nothing. // cstage cgen.c:3281-3317 handles this // (now-expanded for the same 10 ops + // hard-errors); this is its rule-10 twin. // All 10 integer compound ops wired; // float/str/slice/tagged field-type // hard-errors LOUD. Signed RSHIFTEQ uses // SARQ (signed) or SHRQ (unsigned) per #136. if (n.op != syntax.tkind.TK_ASSIGN) { if (syntax.typeisstr(ft)) { let m: str = "chained-ptr-field compound on str element not wired (#133/rule-7)\n"; os.write(2, m.ptr, m.len: u64); os.exit(1); }; if (syntax.typeisslice(ft)) { let m: str = "chained-ptr-field compound on slice element not wired (#133/rule-7)\n"; os.write(2, m.ptr, m.len: u64); os.exit(1); }; if (syntax.typeisfloat(ft)) { let m: str = "chained-ptr-field compound on float element not wired (#133/rule-7)\n"; os.write(2, m.ptr, m.len: u64); os.exit(1); }; if (syntax.typeistagged(ft)) { let m: str = "chained-ptr-field compound on tagged element not wired (#133/rule-7)\n"; os.write(2, m.ptr, m.len: u64); os.exit(1); }; cgexpr(c, n.rhs); emitline("\tPUSHQ\tAX\n"); cgexpr(c, base); emitline("\tPUSHQ\tAX\n"); let fsz: i32 = ft.slotsize: i32; let unsignd_f: bool = syntax.typeisunsigned(ft); let lopf: str = loadopsz(!unsignd_f, fsz); emitline("\t"); emitline(lopf); emitline("\t"); emitdispreg(tf.offset: i64, "AX"); emitline(", AX\n"); emitline("\tPOPQ\tBX\n"); emitline("\tPOPQ\tCX\n"); let wired_f: bool = false; if (n.op == syntax.tkind.TK_PLUSEQ) { emitline("\tADDQ\tCX, AX\n"); wired_f = true; }; if (n.op == syntax.tkind.TK_MINUSEQ) { emitline("\tSUBQ\tCX, AX\n"); wired_f = true; }; if (n.op == syntax.tkind.TK_STAREQ) { emitline("\tIMULQ\tCX, AX\n"); wired_f = true; }; if (n.op == syntax.tkind.TK_AMPEQ) { emitline("\tANDQ\tCX, AX\n"); wired_f = true; }; if (n.op == syntax.tkind.TK_PIPEEQ) { emitline("\tORQ\tCX, AX\n"); wired_f = true; }; if (n.op == syntax.tkind.TK_CARETEQ) { emitline("\tXORQ\tCX, AX\n"); wired_f = true; }; if (n.op == syntax.tkind.TK_SLASHEQ) { if (unsignd_f) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); } else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); }; wired_f = true; }; if (n.op == syntax.tkind.TK_PERCENTEQ) { if (unsignd_f) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); } else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); }; emitline("\tMOVQ\tDX, AX\n"); wired_f = true; }; if (n.op == syntax.tkind.TK_LSHIFTEQ) { emitline("\tSHLQ\tCX, AX\n"); wired_f = true; }; if (n.op == syntax.tkind.TK_RSHIFTEQ) { if (unsignd_f) { emitline("\tSHRQ\tCX, AX\n"); } else { emitline("\tSARQ\tCX, AX\n"); }; wired_f = true; }; if (!wired_f) { let m: str = "chained-ptr-field compound: unknown op (#133/rule-7)\n"; os.write(2, m.ptr, m.len: u64); os.exit(1); }; let sopf: str = tnodestoreop(c, n.rhs, fsz); emitline("\t"); emitline(sopf); emitline("\tAX, "); emitdispreg(tf.offset: i64, "BX"); emitline("\n"); return; }; }; tf = tf.tnext; }; }; }; }; }; }; }; }; }; // Chained N_DOT spine write through value-struct fields (any // depth) — `o.i.a = 10`, `v.a.b.c = …`. Also handles a slice/str // pseudo-field leaf (`b.buf.len = 5`). Mirror of cstage cgen.c's // chained-DOT write branch. Without this, depth ≥ 3 writes and // the slice/str pseudo-field write through a value-struct chain // silently emit no store. Only plain `=` is wired. if (lhs != nil) { if (lhs.kind == syntax.nkind.N_DOT && lhs.lhs != nil && lhs.lhs.kind == syntax.nkind.N_DOT && n.op == syntax.tkind.TK_ASSIGN) { let rootname: str = ""; let rootoff: i32 = 0; let totaloff: i32 = 0; let leaftype: *syntax.tinfo = nil; let slicedelta: i32 = -1; let isglobal: bool = false; let ptrroot: bool = false; let yok: bool = dotchainresolve(c, lhs, &rootname, &rootoff, &totaloff, &leaftype, &slicedelta, &isglobal, &ptrroot); // A global `*struct` root chained STORE: cstage stores // via the address-compute spine (LEAQ name(SB),BX; MOVQ // (BX),BX; ADDQ; MOVQ AX,(BX)), NOT the READ path's // offset-fold — so the fold spine below would diverge. // Decline and fall through to the address-spine mirror, // exactly as the addr-of path declines a ptr-rooted chain // (cgenexpr.ww:5114). A LOCAL `*T` root (ptrroot && // !isglobal) DOES fold in both stages and stays. (#16) if (isglobal && ptrroot) { yok = false; }; if (yok) { // `*T` root and global share the CX-based emit: // loader runs AFTER cgexpr(rhs) so AX/BX/X0 stay // intact, then stores at total_off off CX. let viacx: bool = isglobal || ptrroot; if (slicedelta >= 0) { cgexpr(c, n.rhs); if (viacx) { emitchainbase(c, ptrroot, isglobal, rootoff, rootname); emitline("\tMOVQ\tAX, "); emitdispreg((totaloff + slicedelta): i64, "CX"); emitline("\n"); } else { emitline("\tMOVQ\tAX, "); emitoff((rootoff + totaloff + slicedelta): i64); emitline("(BP)\n"); }; return; }; // tagged leaf (#38a): full slot rewrite via the shared // widener — the single-dot tagged-field arm verbatim // (cgwidentaggedstore spills the BX base itself). The // scalar tail below stored ONE sized word at the field // offset: the rhs landed in the TAG slot and the payload // kept its old bytes (ken x5d: `o.r.min = 8: size` left // `is size` false). Only plain `=` reaches this walker // (TK_ASSIGN gate above). if (syntax.typeistagged(leaftype)) { let wlu: *syntax.tinfo = leaftype; wlu = tichase(wlu); let wtsz: i32 = wlu.size: i32; if (viacx) { if (ptrroot) { emitline("\tMOVQ\t"); emitoff(rootoff: i64); emitline("(BP), BX\n"); } else { emitline("\tLEAQ\t"); emitsymname(c, rootname); emitline("(SB), BX\n"); }; cgwidentaggedstore(c, wlu, n.rhs, "BX", totaloff, wtsz); } else { cgwidentaggedstore(c, wlu, n.rhs, "BP", rootoff + totaloff, wtsz); }; return; }; if (syntax.typeisstr(leaftype) || syntax.typeisslice(leaftype)) { // str/slice: store ptr/len/cap. cgexpr leaves // CX=cap, so the viacx base goes in DX (not CX) to // avoid clobbering it — same as the single-dot str // field store (#1/Phase 3). cgexpr(c, n.rhs); if (viacx) { if (ptrroot) { emitline("\tMOVQ\t"); emitoff(rootoff: i64); emitline("(BP), DX\n"); } else { emitline("\tLEAQ\t"); emitsymname(c, rootname); emitline("(SB), DX\n"); }; emitline("\tMOVQ\tAX, "); emitdispreg(totaloff: i64, "DX"); emitline("\n"); emitline("\tMOVQ\tBX, "); emitdispreg((totaloff + 8): i64, "DX"); emitline("\n"); emitline("\tMOVQ\tCX, "); emitdispreg((totaloff + 16): i64, "DX"); emitline("\n"); } else { emitline("\tMOVQ\tAX, "); emitoff((rootoff + totaloff): i64); emitline("(BP)\n"); emitline("\tMOVQ\tBX, "); emitoff((rootoff + totaloff + 8): i64); emitline("(BP)\n"); emitline("\tMOVQ\tCX, "); emitoff((rootoff + totaloff + 16): i64); emitline("(BP)\n"); }; return; }; // #234: over-cap sret STORE into a chained // value-struct leaf — `o.a.b = wide();` // (sretretsize > 0: a >24B struct OR an // over-cap tuple). Point the callee's hidden // RDI dest at the leaf slot so it writes the // WHOLE value there; pre-#234 this fell to // the scalar tail and stored ONE word (the // sret dest pointer). c.sretdestoff is // BP-relative ONLY, so a ptr-root/global // chain needs the runtime RDI-pointer dest // variant deferred to #234-tail and HARD- // STOPS loud (rule 7). if (n.rhs != nil && n.rhs.kind == syntax.nkind.N_CALL && sretretsizetn(c, leaftype) > 0) { if (viacx) { let m234: str = "#234-tail: over-cap tuple sret store to chained ptr/global dest unsupported\n"; os.write(2, m234.ptr, m234.len: u64); os.exit(1); }; c.sretdestoff = rootoff + totaloff; cgexpr(c, n.rhs); c.sretdestoff = 0; return; }; // TY_STRUCT terminal: three rhs shapes: // - N_IDENT: word-copy from the rhs local slot // (cgexpr is skipped — no whole-struct register // convention for an arbitrary local). // - N_CALL (added with #5): cgexpr leaves the // value in AX/DX/CX per #4's cgreturn ABI; sized // stores write only the declared field size. // cgreturn touches only AX/DX/CX so for // ptrroot/global we load the dst addr into BX // (not CX) after the call to keep CX as the // third value word. // - N_STRUCTLIT (added with #5): field-by-field // store; for ptrroot/global the dst addr is // reloaded into BX before each store so cgexpr // can clobber AX/BX between fields. // #71: the struct-terminal cases below still drive the // structinfo machinery (structnaturalsize / // cgstructlitfill), so recover the struct NAME from the // leaf tinfo's TY_NAMED wrapper. Peeled-TY_STRUCT + // structlookup!=nil is byte-equal to the old `N_TNAME && // primsize==0 && structlookup` guard: a named non-struct // (tagged/alias) peels to a non-STRUCT kind, and // structlookup decides struct-ness off the same declared // name either way. let leafstruct: bool = false; let leafname: str = ""; if (leaftype != nil) { let lp: *syntax.tinfo = leaftype; lp = tichase(lp); if (lp != nil) { if (lp.kind == syntax.tykind.TY_STRUCT) { leafstruct = true; }; }; if (leaftype.kind == syntax.tykind.TY_NAMED) { leafname = leaftype.name; }; }; if (n.rhs != nil && n.rhs.kind == syntax.nkind.N_CALL && leafstruct) { let lsi: *structinfo = structlookup(c, leafname); if (lsi != nil) { // register RECV reads AX/DX/CX at 8-byte // granularity — size via structabisize // (cstage SSoT lu->size, check.c:760). let lsz: i32 = structabisize(lsi); if (lsz <= 24) { // #14: choke-point now stores every in-cap tail; 3/5/6/7 no longer dropped to a lone narrow MOV. cgexpr(c, n.rhs); if (viacx) { if (ptrroot) { emitline("\tMOVQ\t"); emitoff(rootoff: i64); emitline("(BP), BX\n"); } else { emitline("\tLEAQ\t"); emitsymname(c, rootname); emitline("(SB), BX\n"); }; }; if (viacx) { cgaggregstore(c, "BX", totaloff, lsz, false); } else { cgaggregstore(c, "BP", rootoff + totaloff, lsz, false); }; return; }; }; }; // #18: delegate to cgstructlitfill so a nested struct- // typed structlit value recurses instead of dropping // its trailing bytes. mode picks the dst flavor: // ptrroot → mode=1 (DST_PTR_LOCAL), reload BX from // rootoff(BP). // isglobal → mode=2 (DST_GLOBAL), reload BX via // LEAQ rootname(SB). // else → mode=0 (DST_BP), direct BP-rel, no reload. if (n.rhs != nil && n.rhs.kind == syntax.nkind.N_STRUCTLIT && leafstruct) { let lsi: *structinfo = structlookup(c, leafname); if (lsi != nil) { let dmode: i32 = 0; let ddisp: i32 = rootoff + totaloff; if (ptrroot) { dmode = 1; ddisp = totaloff; }; if (isglobal) { dmode = 2; ddisp = totaloff; }; cgstructlitfill(c, lsi, n.rhs, dmode, rootoff, rootname, ddisp); return; }; }; if (n.rhs != nil && n.rhs.kind == syntax.nkind.N_IDENT && leafstruct) { let ssi: *structinfo = structlookup(c, leafname); let srhs: *local = localfindnode(c, n.rhs.str); if (ssi != nil) { if (srhs != nil) { if (viacx) { if (ptrroot) { emitline("\tMOVQ\t"); emitoff(rootoff: i64); emitline("(BP), CX\n"); } else { emitline("\tLEAQ\t"); emitsymname(c, rootname); emitline("(SB), CX\n"); }; }; let ssz: i32 = copysrcnatsize(c, n.rhs); // #71: natural source size, not slot-padded totsize let k: i32 = 0; for (k + 8 <= ssz) { emitline("\tMOVQ\t"); emitoff((srhs.off + k): i64); emitline("(BP), AX\n"); if (viacx) { emitline("\tMOVQ\tAX, "); emitdispreg((totaloff + k): i64, "CX"); emitline("\n"); } else { emitline("\tMOVQ\tAX, "); emitoff((rootoff + totaloff + k): i64); emitline("(BP)\n"); }; k += 8; }; if (k + 4 <= ssz) { emitline("\tMOVL\t"); emitoff((srhs.off + k): i64); emitline("(BP), AX\n"); if (viacx) { emitline("\tMOVL\tAX, "); emitdispreg((totaloff + k): i64, "CX"); emitline("\n"); } else { emitline("\tMOVL\tAX, "); emitoff((rootoff + totaloff + k): i64); emitline("(BP)\n"); }; k += 4; }; if (k + 2 <= ssz) { emitline("\tMOVW\t"); emitoff((srhs.off + k): i64); emitline("(BP), AX\n"); if (viacx) { emitline("\tMOVW\tAX, "); emitdispreg((totaloff + k): i64, "CX"); emitline("\n"); } else { emitline("\tMOVW\tAX, "); emitoff((rootoff + totaloff + k): i64); emitline("(BP)\n"); }; k += 2; }; if (k + 1 <= ssz) { emitline("\tMOVB\t"); emitoff((srhs.off + k): i64); emitline("(BP), AX\n"); if (viacx) { emitline("\tMOVB\tAX, "); emitdispreg((totaloff + k): i64, "CX"); emitline("\n"); } else { emitline("\tMOVB\tAX, "); emitoff((rootoff + totaloff + k): i64); emitline("(BP)\n"); }; k += 1; }; return; };}; }; if (syntax.typeisfloat(leaftype)) { let mov: str = "MOVSD"; if (syntax.typeisf32(leaftype)) { mov = "MOVSS"; }; cgexpr(c, n.rhs); if (viacx) { emitchainbase(c, ptrroot, isglobal, rootoff, rootname); emitline("\t"); emitline(mov); emitline("\tX0, "); emitdispreg(totaloff: i64, "CX"); emitline("\n"); } else { emitline("\t"); emitline(mov); emitline("\tX0, "); emitoff((rootoff + totaloff): i64); emitline("(BP)\n"); }; return; }; // Scalar leaf store-op by size — the same size→op // dispatch fieldstoreop used on the leaf fieldinfo, now // keyed on the leaf tinfo's slot width (#71). let sop: str = "MOVQ"; if (leaftype != nil) { let ssz: i32 = leaftype.slotsize: i32; if (ssz == 1) { sop = "MOVB"; } else { if (ssz == 2) { sop = "MOVW"; } else { if (ssz == 4) { sop = "MOVL"; }; }; }; }; cgexpr(c, n.rhs); if (viacx) { if (ptrroot) { emitline("\tMOVQ\t"); emitoff(rootoff: i64); emitline("(BP), CX\n"); } else { emitline("\tLEAQ\t"); emitsymname(c, rootname); emitline("(SB), CX\n"); }; emitline("\t"); emitline(sop); emitline("\tAX, "); emitdispreg(totaloff: i64, "CX"); emitline("\n"); } else { emitline("\t"); emitline(sop); emitline("\tAX, "); emitoff((rootoff + totaloff): i64); emitline("(BP)\n"); }; return; }; }; }; // 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. // Kept as fallback below the generalized walker for any shape // the walker doesn't recognize. if (lhs != nil) { if (lhs.kind == syntax.nkind.N_DOT) { let base: *syntax.node = lhs.lhs; let fld: str = lhs.str; if (base != nil) { if (base.kind == syntax.nkind.N_DOT) { let inner: *syntax.node = base.lhs; let innerfld: str = base.str; if (inner != nil) { if (inner.kind == syntax.nkind.N_IDENT) { let lc: *local = localfindnode(c, inner.str); if (lc != nil) { if (lc.tnode != nil) { let tn: *syntax.node = lc.tnode; let lkind: syntax.nkind = tn.kind; let outname: str; outname.ptr = nil; outname.len = 0; let isptr: bool = false; if (lkind == syntax.nkind.N_TNAME) { outname = tn.str; }; if (lkind == syntax.nkind.N_TPTR) { let pe: *syntax.node = tn.lhs; if (pe != nil) { if (pe.kind == syntax.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 (syntax.streq(ofi.fname, innerfld)) { let oft: *syntax.node = ofi.tnode; if (oft != nil) { if (oft.kind == syntax.nkind.N_TNAME) { if (aliasprimsize(c, oft.str) == 0) { let isi: *structinfo = structlookup(c, oft.str); if (isi != nil) { let ffi: *fieldinfo = isi.fields; for (ffi != nil) { if (syntax.streq(ffi.fname, fld)) { if (n.op == syntax.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(c, 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 == syntax.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. // C1: a name that is neither a local nor a // let dies LOUD — the pre-C1 return dropped // the whole statement silently (cstage twin: // the N_ASSIGN IDENT-tail fatal). if (!isletvar(c, nm)) { let mi1: str = "unsupported assign target: unresolved identifier '"; os.write(2, mi1.ptr, mi1.len: u64); os.write(2, nm.ptr, nm.len: u64); let mi2: str = "'\n"; os.write(2, mi2.ptr, mi2.len: u64); os.exit(1); }; // 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; let lvftn: *syntax.node = nil; for (lvf != nil) { if (syntax.streq(lvf.name, nm)) { isfg = isfloattype(c, lvf.tnode); isf32g = isf32type(c, lvf.tnode); lvftn = 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 == syntax.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 == syntax.tkind.TK_PLUSEQ) { fop = addf; }; if (n.op == syntax.tkind.TK_MINUSEQ) { fop = subf; }; if (n.op == syntax.tkind.TK_STAREQ) { fop = mulf; }; if (n.op == syntax.tkind.TK_SLASHEQ) { fop = divf; }; if (fop.len == 0) { // Non-SSE compound on a float lvalue is // checker-rejected (modulo/bitwise // integer-only); a survivor here means a // checker gap — loud, never the old // plain-store of rhs that dropped the op. let mfc: str = "float compound: op has no SSE lowering (rule 7)\n"; os.write(2, mfc.ptr, mfc.len: u64); os.exit(1); }; 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; }; // #220: `g = f();` where g is a GLOBAL aggregate >24B // (struct or #272 array). No BP slot for the sret dest, // so route RDI to g's symbol; the callee writes straight // into g's storage. The scalar store below would emit a // truncated `MOVQ AX, g(SB)` and drop the body. Mirror // of the C cgen N_ASSIGN global arm (cmd/w6c/cgen.c). if (n.op == syntax.tkind.TK_ASSIGN && n.rhs != nil && n.rhs.kind == syntax.nkind.N_CALL && lvftn != nil) { let gsz: i32 = 0; if (lvftn.kind == syntax.nkind.N_TNAME) { // #31: size off the stamped tinfo, not structlookup(lvftn.str). // Non-reddenable (global decl qualified); convert for // close-by-construction. gsz = structabisizetn(lvftn.type_: *syntax.tinfo); }; if (lvftn.kind == syntax.nkind.N_TARRAY) { let gat: *syntax.tinfo = lvftn.type_: *syntax.tinfo; gat = tichase(gat); if (gat != nil) { gsz = gat.size: i32; }; }; if (gsz > 24) { let rscs: i32 = callsretsize(c, n.rhs); if (rscs > 0) { c.sretdestnode = lhs; cgexpr(c, n.rhs); c.sretdestnode = nil; return; }; }; }; // #272: `g = f();` where g is a GLOBAL ARRAY ≤24B. // The callee leaves AX/DX/CX (#272 reg-return; an array // is never float-class, so AX/DX/CX is always the // transport); the scalar store below would truncate to // MOVQ AX, g(SB). LEAQ the symbol into DI, store the // full+tail words. Mirror of cstage cgen.c ≤24B global // arm. #276: a ≤24B STRUCT global receive can be // float-class (X0/X1) so it stays at its pre-existing // behaviour — no consumer (rule-10 aligned with cstage; // note non-float struct globals also truncate symmetrically // here, byte-id-clean — #276 covers both). if (n.op == syntax.tkind.TK_ASSIGN && n.rhs != nil && n.rhs.kind == syntax.nkind.N_CALL && lvftn != nil && lvftn.kind == syntax.nkind.N_TARRAY) { let aggsz: i32 = 0; let aat: *syntax.tinfo = lvftn.type_: *syntax.tinfo; aat = tichase(aat); if (aat != nil) { aggsz = aat.size: i32; }; if (aggsz > 0 && aggsz <= 24) { cgexpr(c, n.rhs); emitline("\tLEAQ\t"); emitsymname(c, nm); emitline("(SB), DI\n"); // #14: route through the choke-point so a 3/5/6/7 tail is no // longer dropped to a lone MOVB (was silent both-stage). A global // symbol is non-padded => dest_padded=false (its scratch detour // copies exactly `tail` bytes). cgaggregstore(c, "DI", 0, aggsz, false); return; }; }; // #49 (global twin): aggregate module-let reassign — // `g = a` / `g = pt{...}`. Same funnel as the local // arm: structlit → mode-2 (DST_GLOBAL) fill; call → // loud (#276: ≤24B struct global receive was a // documented symmetric fall-through, now loud); // addressable rhs → aggargsrcaddr + LEAQ g(SB), BX // + aggcopy. Pre-#49 every shape fell to the scalar // tail below: one MOVQ AX, g(SB). if (n.op == syntax.tkind.TK_ASSIGN && lvftn != nil) { let gau: *syntax.tinfo = lvftn.type_: *syntax.tinfo; gau = tichase(gau); if (gau != nil) { if (gau.kind == syntax.tykind.TY_STRUCT || gau.kind == syntax.tykind.TY_ARRAY || gau.kind == syntax.tykind.TY_TUPLE) { if (n.rhs != nil && n.rhs.kind == syntax.nkind.N_STRUCTLIT) { // #31: fill off the stamped struct tinfo (gau = // tichase(lvftn.type_)), NOT structlookup(lvftn.str). A // global decl is qualified -> non-reddenable; converted // for close-by-construction (byte-id). The W4b nested // in-loop structlookup sub-arms close by construction // (cgstructlitfilltn recurses on tichase(tf.type_)). if (gau.kind != syntax.tykind.TY_STRUCT) { // wwstage-only bail (anonymous // type; the @placescr precedent). let m49d: str = "assign: structlit layout unresolved (rule-7)\n"; os.write(2, m49d.ptr, m49d.len: u64); os.exit(1); }; cgstructlitfilltn(c, gau, n.rhs, 2, 0, nm, 0); return; }; if (n.rhs != nil && n.rhs.kind == syntax.nkind.N_CALL) { let m49e: str = "assign: aggregate call receive shape unwired (task #49/#276/rule-7)\n"; os.write(2, m49e.ptr, m49e.len: u64); os.exit(1); }; if (aggargsrcaddr(c, n.rhs, "SI")) { emitline("\tLEAQ\t"); emitsymname(c, nm); emitline("(SB), BX\n"); aggcopy(c, gau.size: i32); return; }; let m49f: str = "assign: aggregate rhs shape unwired (task #49/rule-7)\n"; os.write(2, m49f.ptr, m49f.len: u64); os.exit(1); }; }; }; cgexpr(c, n.rhs); if (n.op == syntax.tkind.TK_ASSIGN) { // str/slice top-level let: str IS []u8, so both store the // full 3-word {ptr,len,cap}. Stash cap in DI before LEAQ // overwrites CX, then store ptr/len/cap via &name(SB) // (#1/Phase 3). if (letvarisstr(c, nm) || 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; }; // Compound RMW for a top-level let: load through // LEAQ + localloadop when the slot is narrow so // a prior `*(&letname): *iN` deref-store doesn't // leave stale upper bytes feeding the combine. let glop: str = localloadop(c, lvftn); if (syntax.streq(glop, "MOVQ")) { emitline("\tMOVQ\t"); emitsymname(c, nm); emitline("(SB), BX\n"); } else { emitline("\tLEAQ\t"); emitsymname(c, nm); emitline("(SB), CX\n"); emitline("\t"); emitline(glop); emitline("\t(CX), BX\n"); }; if (n.op == syntax.tkind.TK_PLUSEQ) { emitline("\tADDQ\tAX, BX\n"); } else { if (n.op == syntax.tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, BX\n"); } else { if (n.op == syntax.tkind.TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); } else { if (n.op == syntax.tkind.TK_AMPEQ) { emitline("\tANDQ\tAX, BX\n"); } else { if (n.op == syntax.tkind.TK_PIPEEQ) { emitline("\tORQ\tAX, BX\n"); } else { if (n.op == syntax.tkind.TK_CARETEQ) { emitline("\tXORQ\tAX, BX\n"); } else { if (n.op == syntax.tkind.TK_LSHIFTEQ) { emitline("\tMOVQ\tAX, CX\n"); emitline("\tSHLQ\tCX, BX\n"); } else { if (n.op == syntax.tkind.TK_RSHIFTEQ) { // #136: signed RSHIFTEQ → SARQ. let unsignd_r: bool = false; if (lvftn != nil) { if (lvftn.type_ != nil) { unsignd_r = syntax.typeisunsigned(lvftn.type_: *syntax.tinfo); }; }; if (!unsignd_r) { unsignd_r = nodeisunsigned(c, n.rhs); }; emitline("\tMOVQ\tAX, CX\n"); if (unsignd_r) { emitline("\tSHRQ\tCX, BX\n"); } else { emitline("\tSARQ\tCX, BX\n"); }; } // Post-63332fe: /= and %= for a top-level // let. Same shape as the IDENT-local path: // park rhs in CX, slot value (BX) into AX, // CQO (or zero DX), IDIVQ (or DIVQ) CX, // ferry AX or DX back to BX for the shared // store-BX tail below. else { if (n.op == syntax.tkind.TK_SLASHEQ || n.op == syntax.tkind.TK_PERCENTEQ) { let unsignd: bool = false; if (lvftn != nil) { unsignd = syntax.typeisunsigned(lvftn.type_: *syntax.tinfo); }; if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); }; emitline("\tMOVQ\tAX, CX\n"); emitline("\tMOVQ\tBX, AX\n"); if (unsignd) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); } else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); }; if (n.op == syntax.tkind.TK_SLASHEQ) { emitline("\tMOVQ\tAX, BX\n"); } else { emitline("\tMOVQ\tDX, BX\n"); }; } else { // all 10 compound tokens enumerated // above (rule 7). let mgc: str = "global compound: unknown compound op (rule 7)\n"; os.write(2, mgc.ptr, mgc.len: u64); os.exit(1); };};};};};};};};}; emitline("\tMOVQ\tBX, "); emitsymname(c, nm); emitline("(SB)\n"); return; }; // #10 Fold B: over-cap tuple reassign `t = f();`. t's // slot (off) IS the caller-prealloc dest; the callee // writes the whole tuple through hidden RDI. Keys on // callsretsize (the shared sret SSoT) for a tuple- // returning call — rettupleof distinguishes it from a // >24B struct, which keeps its own size-aware recv // below. Mirrors the cstage N_ASSIGN-ident over-cap arm. if (n.op == syntax.tkind.TK_ASSIGN && n.rhs != nil && n.rhs.kind == syntax.nkind.N_CALL) { let rtup: *syntax.node = rettupleof(c, n.rhs); if (rtup != nil) { let rscs: i32 = callsretsize(c, n.rhs); if (rscs > 0) { c.sretdestoff = off; cgexpr(c, n.rhs); c.sretdestoff = 0; 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); }; // Struct-typed local reassignment: `s = expr;` where s // is a TY_STRUCT local of size <=24B. Two rhs shapes // (mirrors cglet's N_STRUCTLIT and the call-result // receive branch): // - N_STRUCTLIT: walk fields, store at off+foff // directly. ASYMMETRY-safe (no register copy from // the caller; values come from cgexpr). // - N_CALL: cgexpr → AX/DX/CX, sized stores per the // declared struct size — MOVQ for full 8B chunks // plus MOVL/MOVW/MOVB tail. See cglet receive // site for the ASYMMETRY rationale. // Struct-IDENT word-copy rhs (s = p) is left unwired; // #5 is scoped to receive-side of #4 (calls + literals). // fsz dispatch uses the explicit {1→MOVB, 4→MOVL, else // MOVQ} pattern (not fieldstoreop) to match cstage // cgen.c N_ASSIGN byte-identically — wwstage's // fieldstoreop returns MOVW for fsz==2 which cstage // doesn't emit (tracked separately as the cstage/ // wwstage MOVW divergence task). if (lcn != nil) { let lctn: *syntax.node = lcn.tnode; let lcsname: str; lcsname.ptr = nil; lcsname.len = 0; if (lctn != nil) { if (lctn.kind == syntax.nkind.N_TNAME) { lcsname = lctn.str; }; }; if (lcsname.len > 0) { let lcsi: *structinfo = structlookup(c, lcsname); if (lcsi != nil) { // register RECV reads AX/DX/CX at 8-byte // granularity — size via structabisize (cstage // N_ASSIGN-IDENT branch sets sz = lu->size, // cgen.c:4704; check.c:760 SSoT). The pre- // #169 structnaturalsize shorts struct{i64,i32} // (natural 12, ABI 16) to MOVQ+MOVL where // cstage writes MOVQ+MOVQ. let lcnsz: i32 = structabisize(lcsi); if (n.op == syntax.tkind.TK_ASSIGN) { if (n.rhs != nil && n.rhs.kind == syntax.nkind.N_STRUCTLIT) { // Delegate to the shared BP-relative // structlit fill helper. Handles // TK_ELLIPSIS autofill + per-field // walk; nested struct-typed values // recurse via the helper (#17 fix). // Helper uses the explicit {1→MOVB, // 4→MOVL, else MOVQ} sized-store // dispatch (NOT fieldstoreop) to stay // byte-identical with cstage pending // #13 (fsz==2 MOVW divergence). See // cgstructlitfillbp docstring. cgstructlitfillbp(c, lcsi, n.rhs, off); return; }; if (n.rhs != nil && n.rhs.kind == syntax.nkind.N_CALL) { // sret receive (#23): plain // TY_STRUCT > 24B from a CALL. // `s` is the prealloc dest; the // callee writes through hidden RDI // directly into off(BP). Mirror of // cglet's sret branch. if (lcnsz > 24) { let rscs: i32 = callsretsize(c, n.rhs); if (rscs > 0) { c.sretdestoff = off; cgexpr(c, n.rhs); c.sretdestoff = 0; return; }; }; let lcsz: i32 = lcnsz; if (lcsz <= 24) { // #14: choke-point now stores every in-cap tail; 3/5/6/7 no longer dropped to a lone narrow MOV. cgexpr(c, n.rhs); cgaggregstore(c, "BP", off, lcsz, false); return; }; }; }; }; }; }; // #267: array return-by-value RECV — `c = mk()` where c is // an array local. Arrays ride the struct reg/sret recv path. // >24B sret keys on callsretsize (the shared SSoT, c's slot // IS the prealloc dest); ≤24B arrives in AX/DX/CX, sized // stores. Array natural size (tinfo.size = sub.size*len) // mirrors cstage lu->size. No structfloatclass (pure-int // element arrays). if (lcn != nil && n.op == syntax.tkind.TK_ASSIGN && n.rhs != nil && n.rhs.kind == syntax.nkind.N_CALL) { let acati: *syntax.tinfo = nil; if (lcn.tnode != nil) { acati = lcn.tnode.type_: *syntax.tinfo; }; acati = tichase(acati); if (acati != nil && acati.kind == syntax.tykind.TY_ARRAY) { let lcsz: i32 = acati.size: i32; if (lcsz > 24) { let rscs: i32 = callsretsize(c, n.rhs); if (rscs > 0) { c.sretdestoff = off; cgexpr(c, n.rhs); c.sretdestoff = 0; return; }; }; if (lcsz <= 24) { // #14: choke-point now stores every in-cap tail; 3/5/6/7 no longer dropped to a lone narrow MOV. cgexpr(c, n.rhs); cgaggregstore(c, "BP", off, lcsz, false); return; }; }; }; // 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 == syntax.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 == syntax.tkind.TK_PLUSEQ) { fop = addf; }; if (n.op == syntax.tkind.TK_MINUSEQ) { fop = subf; }; if (n.op == syntax.tkind.TK_STAREQ) { fop = mulf; }; if (n.op == syntax.tkind.TK_SLASHEQ) { fop = divf; }; if (fop.len == 0) { // Non-SSE compound on a float lvalue is // checker-rejected (modulo/bitwise // integer-only); a survivor here means a // checker gap — loud, never the old // plain-store of rhs that dropped the op. let mfc: str = "float compound: op has no SSE lowering (rule 7)\n"; os.write(2, mfc.ptr, mfc.len: u64); os.exit(1); }; 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; }; // #49: aggregate (struct/array/tuple) IDENT // reassignment — any rhs shape that missed the // dedicated arms above (struct-lit fill, call // receive, sret) funnels through the ONE mem-to-mem // copy (aggargsrcaddr → SI, dst → BX, aggcopy), or // dies loud. Pre-#49 it fell to the scalar tail // below and word0-copied `b = a` (cstage cgen.c // N_ASSIGN aggregate-ident twin). Kind keys off the // checker-STAMPED tinfo (#209/#211 discipline). if (n.op == syntax.tkind.TK_ASSIGN) { let agu: *syntax.tinfo = nil; if (lcn != nil) { if (lcn.tnode != nil) { agu = lcn.tnode.type_: *syntax.tinfo; }; }; agu = tichase(agu); if (agu != nil) { if (agu.kind == syntax.tykind.TY_STRUCT || agu.kind == syntax.tykind.TY_ARRAY || agu.kind == syntax.tykind.TY_TUPLE) { if (n.rhs != nil && n.rhs.kind == syntax.nkind.N_STRUCTLIT) { // wwstage-only bail: the fill is // structinfo-keyed; a literal // reaching past the name-keyed arm // above has no registry entry // (anonymous struct type). Loud, // rule 7 (the resolver @placescr // precedent); cstage fills from // Type directly. let m49a: str = "assign: structlit layout unresolved (rule-7)\n"; os.write(2, m49a.ptr, m49a.len: u64); os.exit(1); }; if (n.rhs != nil && n.rhs.kind == syntax.nkind.N_CALL) { let m49b: str = "assign: aggregate call receive shape unwired (task #49/#276/rule-7)\n"; os.write(2, m49b.ptr, m49b.len: u64); os.exit(1); }; if (aggargsrcaddr(c, n.rhs, "SI")) { emitline("\tLEAQ\t"); emitoff(off: i64); emitline("(BP), BX\n"); aggcopy(c, agu.size: i32); return; }; let m49c: str = "assign: aggregate rhs shape unwired (task #49/rule-7)\n"; os.write(2, m49c.ptr, m49c.len: u64); os.exit(1); }; }; }; cgexpr(c, n.rhs); if (n.op == syntax.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"); }; // str IS []u8: store the cap word too, identical to // the slice store (#1/Phase 3). if (lcstr || lcsl) { emitline("\tMOVQ\tCX, "); emitoff((off + 16): i64); emitline("(BP)\n"); }; return; }; // Pick the load width for compound RMW. Signed-narrow // locals must sign-extend the slot before the combine // — ADDQ/SUBQ on amem reads 8B raw, which is wrong // after a 4B deref-store leaves the upper bytes stale. let llop: str = "MOVQ"; if (lcn != nil) { llop = localloadop(c, lcn.tnode); }; if (syntax.streq(llop, "MOVQ")) { if (n.op == syntax.tkind.TK_PLUSEQ) { emitline("\tADDQ\tAX, "); emitoff(off: i64); emitline("(BP)\n"); return; }; if (n.op == syntax.tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, "); emitoff(off: i64); emitline("(BP)\n"); return; }; }; // Generic compound: load → combine in BX → store. emitline("\t"); emitline(llop); emitline("\t"); emitoff(off: i64); emitline("(BP), BX\n"); if (n.op == syntax.tkind.TK_PLUSEQ) { emitline("\tADDQ\tAX, BX\n"); }; if (n.op == syntax.tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, BX\n"); }; if (n.op == syntax.tkind.TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); }; if (n.op == syntax.tkind.TK_AMPEQ) { emitline("\tANDQ\tAX, BX\n"); }; if (n.op == syntax.tkind.TK_PIPEEQ) { emitline("\tORQ\tAX, BX\n"); }; if (n.op == syntax.tkind.TK_CARETEQ) { emitline("\tXORQ\tAX, BX\n"); }; if (n.op == syntax.tkind.TK_LSHIFTEQ) { emitline("\tMOVQ\tAX, CX\n"); emitline("\tSHLQ\tCX, BX\n"); }; if (n.op == syntax.tkind.TK_RSHIFTEQ) { // #136: signed RSHIFTEQ → SARQ. let unsignd_r: bool = false; if (lcn != nil) { if (lcn.tnode != nil) { if (lcn.tnode.type_ != nil) { unsignd_r = syntax.typeisunsigned(lcn.tnode.type_: *syntax.tinfo); }; }; }; if (!unsignd_r) { unsignd_r = nodeisunsigned(c, n.rhs); }; emitline("\tMOVQ\tAX, CX\n"); if (unsignd_r) { emitline("\tSHRQ\tCX, BX\n"); } else { emitline("\tSARQ\tCX, BX\n"); }; }; // Post-63332fe: /= and %= for an IDENT local. Pre-fix // fell through with no case, so BX (still holding the // freshly loaded slot value) was stored back unchanged // — a silent no-op rather than the natural rhs-only // shape the global/deref siblings took. Park rhs in // CX, slot value (BX) into AX, CQO/IDIVQ, ferry AX // (quotient) or DX (remainder) back to BX. if (n.op == syntax.tkind.TK_SLASHEQ || n.op == syntax.tkind.TK_PERCENTEQ) { let unsignd: bool = false; if (lcn != nil) { if (lcn.tnode != nil) { unsignd = syntax.typeisunsigned(lcn.tnode.type_: *syntax.tinfo); }; }; if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); }; emitline("\tMOVQ\tAX, CX\n"); emitline("\tMOVQ\tBX, AX\n"); if (unsignd) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); } else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); }; if (n.op == syntax.tkind.TK_SLASHEQ) { emitline("\tMOVQ\tAX, BX\n"); } else { emitline("\tMOVQ\tDX, BX\n"); }; }; // all 10 compound tokens enumerated above — an 11th // stored the untouched loaded value back (silent // no-op). Rule 7. if (n.op != syntax.tkind.TK_PLUSEQ && n.op != syntax.tkind.TK_MINUSEQ && n.op != syntax.tkind.TK_STAREQ && n.op != syntax.tkind.TK_AMPEQ && n.op != syntax.tkind.TK_PIPEEQ && n.op != syntax.tkind.TK_CARETEQ && n.op != syntax.tkind.TK_LSHIFTEQ && n.op != syntax.tkind.TK_RSHIFTEQ && n.op != syntax.tkind.TK_SLASHEQ && n.op != syntax.tkind.TK_PERCENTEQ) { let mlc: str = "local compound: unknown compound op (rule 7)\n"; os.write(2, mlc.ptr, mlc.len: u64); os.exit(1); }; emitline("\tMOVQ\tBX, "); emitoff(off: i64); emitline("(BP)\n"); return; }; }; // F6 (cgplaceaddr, commit C1): an N_DOT lvalue none of the // enumerated arms above matched — today the deref-rooted spine // `(*p)[i].f = v` / `OP= v`. Base-address derivation routes // through cgplaceaddr; the load/store emission stays here. Any // N_DOT shape the resolver can't address dies LOUD below: the // pre-C1 fall-off-the-function tail silently emitted NOTHING // (rhs unevaluated). Mirror of the cstage cgen.c N_ASSIGN arm. // #20 (task): N_INDEX and N_UN(STAR) lvalues enroll too — only // the struct-lit-rhs diversion above reaches here (every other // indexed/deref shape returned from its legacy arm), and the // C1.25 aggregate branch fills via @placescr. if (lhs != nil) { if (lhs.kind == syntax.nkind.N_DOT || lhs.kind == syntax.nkind.N_INDEX || (lhs.kind == syntax.nkind.N_UN && lhs.op == syntax.tkind.TK_STAR)) { let ft: *syntax.tinfo = lhs.type_: *syntax.tinfo; let fu: *syntax.tinfo = ft; fu = tichase(fu); let fsz: i32 = 8; if (ft != nil) { fsz = ft.size: i32; }; if (syntax.typeisfloat(ft)) { let mf: str = "assign-resolver: float field not wired (rule-7)\n"; os.write(2, mf.ptr, mf.len: u64); os.exit(1); }; if (fu != nil) { if (fu.kind == syntax.tykind.TY_TAGGED) { let mt: str = "assign-resolver: tagged field not wired (rule-7)\n"; os.write(2, mt.ptr, mt.len: u64); os.exit(1); }; // C1.25 (#23): aggregate field STORE through the // resolver — run_thread's 40B capture store // `(*ts)[i].root_capture = capture{...}`. Dest // address from cgplaceaddr (BX), source address // in SI per rhs shape, then the #270-1b // word-copy tail (SI)→(BX). Pre-C1 a SILENT // no-op; C1 made it loud; this wires it // (loud-first, wire-next). Compound on an // aggregate is meaningless and stays loud. // Mirror of the cstage cgen.c C1.25 arm. if (fu.kind == syntax.tykind.TY_STRUCT || fu.kind == syntax.tykind.TY_ARRAY || fu.kind == syntax.tykind.TY_TUPLE) { if (n.op != syntax.tkind.TK_ASSIGN) { let mac: str = "assign-resolver: compound on aggregate field not wired (rule-7)\n"; os.write(2, mac.ptr, mac.len: u64); os.exit(1); }; if (n.rhs != nil) { if (n.rhs.kind == syntax.nkind.N_CALL) { // sret-class needs a runtime-RDI dest // (the #234-tail deferral); the ≤24B // reg-return receive is task #24. The // callee return type equals the field // type (checker-guaranteed), so // callsretsize gives cstage's // cg_sret_retsize(ft) verdict. if (callsretsize(c, n.rhs) > 0) { let mas: str = "assign-resolver: sret call into aggregate field unwired (#234-tail/rule-7)\n"; os.write(2, mas.ptr, mas.len: u64); os.exit(1); }; let ma24: str = "assign-resolver: call result into aggregate field unwired (task #24/rule-7)\n"; os.write(2, ma24.ptr, ma24.len: u64); os.exit(1); }; }; let placed: bool = false; let isslit: bool = false; if (n.rhs != nil) { if (n.rhs.kind == syntax.nkind.N_STRUCTLIT && fu.kind == syntax.tykind.TY_STRUCT) { isslit = true; }; }; if (isslit) { // @placescr — FRESH slot PER USE (the // @slicescr discipline via localalloc, // NOT the cached @tagscr table: a // cached slot is the #31 multi-live // corruption trap; rob ruling). Funnel // contract, #44 discipline: this arm is // the ONLY @placescr alloc site. Fill // handles nested literals (#18), tagged // fields, TK_ELLIPSIS autofill; the // value sits in memory, so the resolver // below may clobber AX/CX freely. let sname: str; sname.ptr = nil; sname.len = 0; if (ft.kind == syntax.tykind.TY_NAMED) { sname = ft.name; }; let si: *structinfo = nil; if (sname.len > 0) { si = structlookup(c, sname); }; if (si == nil) { // wwstage-only bail: the fill is // structinfo-keyed, so an anonymous- // struct field type has no registry // entry (cstage fills from Type // directly). Loud, rule 7. let man: str = "assign-resolver: structlit field layout unresolved (rule-7)\n"; os.write(2, man.ptr, man.len: u64); os.exit(1); }; let scr: i32 = localalloc(c, "@placescr", fsz, nil); cgstructlitfillbp(c, si, n.rhs, scr); placed = cgplaceaddr(c, lhs, "BX"); if (placed) { emitline("\tLEAQ\t"); emitoff(scr: i64); emitline("(BP), SI\n"); }; } else { // Addressable source — ident / global / // N_DOT chain / deref — via the closed // #265/#268 dispatch. Its N_INDEX arm // clobbers BX, so the dest spills around // it (the #270-1b order). Literal // arrays/tuples have no storage address // and stay loud. placed = cgplaceaddr(c, lhs, "BX"); if (placed) { emitline("\tPUSHQ\tBX\n"); if (!aggargsrcaddr(c, n.rhs, "SI")) { let mar: str = "assign-resolver: aggregate rhs shape unwired (rule-7)\n"; os.write(2, mar.ptr, mar.len: u64); os.exit(1); }; emitline("\tPOPQ\tBX\n"); }; }; if (!placed) { let mau: str = "unsupported assign target shape\n"; os.write(2, mau.ptr, mau.len: u64); os.exit(1); }; aggcopy(c, fsz); return; }; }; let fstrsl: bool = false; if (fu != nil) { if (fu.kind == syntax.tykind.TY_STR || fu.kind == syntax.tykind.TY_SLICE) { fstrsl = true; }; }; if (fstrsl) { if (n.op != syntax.tkind.TK_ASSIGN) { let ms: str = "assign-resolver: compound on str/slice field not wired (rule-7)\n"; os.write(2, ms.ptr, ms.len: u64); os.exit(1); }; // str IS []u8: store the whole {ptr,len,cap} // triple from (AX,BX,CX); the place address // goes in DX so the three pops survive // (#1/Phase 3). cgexpr(c, n.rhs); emitline("\tPUSHQ\tCX\n"); emitline("\tPUSHQ\tBX\n"); emitline("\tPUSHQ\tAX\n"); if (cgplaceaddr(c, lhs, "DX")) { emitline("\tPOPQ\tAX\n"); emitline("\tPOPQ\tBX\n"); emitline("\tPOPQ\tCX\n"); emitline("\tMOVQ\tAX, (DX)\n"); emitline("\tMOVQ\tBX, 8(DX)\n"); emitline("\tMOVQ\tCX, 16(DX)\n"); return; }; } else { if (n.op == syntax.tkind.TK_ASSIGN) { cgexpr(c, n.rhs); emitline("\tPUSHQ\tAX\n"); if (cgplaceaddr(c, lhs, "BX")) { emitline("\tPOPQ\tAX\n"); let sop: str = "MOVQ"; if (fsz == 1) { sop = "MOVB"; }; if (fsz == 2) { sop = "MOVW"; }; if (fsz == 4) { sop = "MOVL"; }; emitline("\t"); emitline(sop); emitline("\tAX, (BX)\n"); return; }; } else { // Compound: AX=old, CX=rhs, BX=addr — the same // register roles as the chained-ptr-field // compound template above. cgexpr(c, n.rhs); emitline("\tPUSHQ\tAX\n"); if (cgplaceaddr(c, lhs, "BX")) { let lop: str = loadopsz(syntax.typeissigned(ft), fsz); emitline("\t"); emitline(lop); emitline("\t(BX), AX\n"); emitline("\tPOPQ\tCX\n"); let unsignd: bool = syntax.typeisunsigned(ft); let wired: bool = false; if (n.op == syntax.tkind.TK_PLUSEQ) { emitline("\tADDQ\tCX, AX\n"); wired = true; }; if (n.op == syntax.tkind.TK_MINUSEQ) { emitline("\tSUBQ\tCX, AX\n"); wired = true; }; if (n.op == syntax.tkind.TK_STAREQ) { emitline("\tIMULQ\tCX, AX\n"); wired = true; }; if (n.op == syntax.tkind.TK_AMPEQ) { emitline("\tANDQ\tCX, AX\n"); wired = true; }; if (n.op == syntax.tkind.TK_PIPEEQ) { emitline("\tORQ\tCX, AX\n"); wired = true; }; if (n.op == syntax.tkind.TK_CARETEQ) { emitline("\tXORQ\tCX, AX\n"); wired = true; }; if (n.op == syntax.tkind.TK_SLASHEQ) { if (unsignd) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); } else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); }; wired = true; }; if (n.op == syntax.tkind.TK_PERCENTEQ) { if (unsignd) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); } else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); }; emitline("\tMOVQ\tDX, AX\n"); wired = true; }; if (n.op == syntax.tkind.TK_LSHIFTEQ) { emitline("\tSHLQ\tCX, AX\n"); wired = true; }; if (n.op == syntax.tkind.TK_RSHIFTEQ) { if (unsignd) { emitline("\tSHRQ\tCX, AX\n"); } else { emitline("\tSARQ\tCX, AX\n"); }; wired = true; }; if (!wired) { let mu: str = "assign-resolver: unknown compound op (rule-7)\n"; os.write(2, mu.ptr, mu.len: u64); os.exit(1); }; let sop: str = "MOVQ"; if (fsz == 1) { sop = "MOVB"; }; if (fsz == 2) { sop = "MOVW"; }; if (fsz == 4) { sop = "MOVL"; }; emitline("\t"); emitline(sop); emitline("\tAX, (BX)\n"); return; }; }; }; let mtl: str = "unsupported assign target shape\n"; os.write(2, mtl.ptr, mtl.len: u64); os.exit(1); }; }; // C1 residual (task #22): a non-DOT lvalue no arm above matched // still falls out SILENT here — known member: the str-base element // store family (`s[i] = v`: cstage drops, wwstage emits MOVB; // pre-existing gate-blind divergence) plus tuple-member writes. // The tail goes loud for the remaining kinds with #22. return; };