amalloc has 0 callers post-γ-2; the *arena threaded through
newnode/newscope/newtype/prim/typesinit/type{ptr,slice,array,chan,
named}/lexinit/parserinit/joindotted/checkinit/arenau64tos/cgeninit
and the scope.a / tctx.a / lex.a / parser.a / checker.a / cgen.a
fields are vestigial.
Drop `import mem;` from 15 files, remove six struct fields, strip
*arena from 14 signatures, update ~120 call sites across lib/ww +
wcc + w6c + wwdump. selfhost/test/sym_link.ww fixture drops the
newarena/freearena probe; still exits 42 on scopedefine/scopelookup.
Both main.combined.ww auto-regenerated.
Comments retidied: typ.ww "once per arena" → "once per program";
parse.ww drops "arena-build" qualifier on joindotted; sym.ww drops
mem-sibling-imports rationale.
Verified 132/132 incl. 994_w6c_ww + 995_self_rebuild byte-identity
(the primary symmetric-stages gate).
5799 lines
185 KiB
Plaintext
5799 lines
185 KiB
Plaintext
// selfhost/cmd/wcc/cgenexpr.ww — split out of cgen.ww.
|
|
//
|
|
// cgexpr is a thin dispatcher over n.kind; each non-trivial branch
|
|
// lives in a per-kind helper (cgstrlit, cgident, cgindex, cgmatch,
|
|
// cgdot, cgun, cgbin, cgcall, cgassign). Trivial literal loads
|
|
// (nkind.N_INTLIT, nkind.N_RUNELIT, nkind.N_TRUE/FALSE/NIL, nkind.N_CAST) stay inline.
|
|
//
|
|
// The remainder of cgen lives in cgen.ww (foundation: types, emit
|
|
// primitives, the collect* tables, FFI/module maps) and cgenstmt.ww
|
|
// (cgstmt).
|
|
//
|
|
// `use cgenexpr;` is unnecessary at consumer sites — cgen.ww imports
|
|
// this file, so any caller of cgen transitively gets cgexpr.
|
|
|
|
package wcc;
|
|
|
|
import os;
|
|
import ast;
|
|
import tok;
|
|
import typ;
|
|
import sym;
|
|
import strconv;
|
|
|
|
fn cgexpr(c: *cgen, n: *node) void = {
|
|
if (n == nil) { return; };
|
|
let k: nkind = n.kind;
|
|
|
|
if (k == nkind.N_INTLIT) {
|
|
// Print signed (i64), not unsigned (u64). C cgen uses
|
|
// `$%lld` so 64-bit constants with bit 63 set show up as
|
|
// negative — e.g. FNV-1a's offset basis prints as
|
|
// $-3750763034362895579, not $14695981039346656037.
|
|
emitline("\tMOVQ\t$");
|
|
emitint(n.uval: i64);
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
if (k == nkind.N_FLOATLIT) {
|
|
// Materialise the f64 bit pattern in AX, push, then MOVSD it
|
|
// into X0. The bits come from n.uval — the parser populates
|
|
// it from the lexer's bitcast of t.fval, so this path stays
|
|
// integer-only (no SSE in the cgen source). The f32
|
|
// narrowing is handled at the consumer site, not here — the
|
|
// literal always carries the full double precision until
|
|
// typed by context.
|
|
emitline("\tMOVQ\t$");
|
|
emitint(n.uval: i64);
|
|
emitline(", AX\n");
|
|
emitline("\tPUSHQ\tAX\n");
|
|
emitline("\tMOVSD\t(SP), X0\n");
|
|
emitline("\tADDQ\t$8, SP\n");
|
|
return;
|
|
};
|
|
if (k == nkind.N_RUNELIT) {
|
|
emitline("\tMOVQ\t$");
|
|
emitint(n.uval: i64);
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
if (k == nkind.N_STRLIT) { cgstrlit(c, n); return; };
|
|
if (k == nkind.N_TRUE) {
|
|
emitline("\tMOVQ\t$1, AX\n");
|
|
return;
|
|
};
|
|
if (k == nkind.N_FALSE) {
|
|
emitline("\tMOVQ\t$0, AX\n");
|
|
return;
|
|
};
|
|
if (k == nkind.N_NIL) {
|
|
emitline("\tMOVQ\t$0, AX\n");
|
|
return;
|
|
};
|
|
if (k == nkind.N_VOIDLIT) {
|
|
// void value: zero-size, but the consumer's ABI expects a
|
|
// deterministic AX. Emit 0 like nil/false do.
|
|
emitline("\tMOVQ\t$0, AX\n");
|
|
return;
|
|
};
|
|
|
|
if (k == nkind.N_IDENT) { cgident(c, n); return; };
|
|
|
|
if (k == nkind.N_INDEX) { cgindex(c, n); return; };
|
|
|
|
if (k == nkind.N_SLICE) { cgslice(c, n); return; };
|
|
|
|
if (k == nkind.N_MATCH) { cgmatch(c, n); return; };
|
|
|
|
if (k == nkind.N_CAST) { cgcast(c, n); return; };
|
|
|
|
if (k == nkind.N_DOT) { cgdot(c, n); return; };
|
|
|
|
if (k == nkind.N_UN) { cgun(c, n); return; };
|
|
|
|
if (k == nkind.N_BIN) { cgbin(c, n); return; };
|
|
|
|
if (k == nkind.N_CALL) { cgcall(c, n); return; };
|
|
|
|
if (k == nkind.N_ASSIGN) { cgassign(c, n); return; };
|
|
|
|
if (k == nkind.N_TRYPROP) { cgtryprop(c, n); return; };
|
|
if (k == nkind.N_TRYUNW) { cgtryunw(c, n); return; };
|
|
if (k == nkind.N_TYPETEST) { cgtypetest(c, n); return; };
|
|
if (k == nkind.N_TYPEASSERT) { cgtypeassert(c, n); return; };
|
|
// Default fallback: produce a deterministic AX = 0. Mirrors
|
|
// the C cgen's `default: cgexpr_int(c, 0)` branch, which is
|
|
// what `return eof{};` (N_STRUCTLIT with an empty !void
|
|
// variant) silently relies on — without this AX carries a
|
|
// stale value into the tagged-union return shuffle.
|
|
emitline("\tMOVQ\t$0, AX\n");
|
|
};
|
|
|
|
// cgtagvariantidx — find the 0-based variant index of `vt` inside the
|
|
// tagged-union type expression `tagged`. -1 if `tagged` isn't an
|
|
// nkind.N_TTAGGED or no variant matches. Mirrors the lookup that cgmatch
|
|
// does inline; pulled out so `is` / `as` can reuse it.
|
|
fn cgtagvariantidx(c: *cgen, tagged: *node, vt: *node) i32 = {
|
|
if (tagged == nil) { return -1; };
|
|
if (vt == nil) { return -1; };
|
|
if (tagged.kind != nkind.N_TTAGGED) { return -1; };
|
|
// `is []T` / `as []T` — slice-shape variant lookup routes through
|
|
// the shape-aware helper so non-N_TNAME variant nodes (which
|
|
// flatvariantidx's name key can't see) resolve. Task #19.
|
|
if (vt.kind == nkind.N_TSLICE) {
|
|
return flatslicevariantidx(c, tagged, vt.lhs);
|
|
};
|
|
let want: str;
|
|
want.ptr = nil; want.len = 0;
|
|
if (vt.kind == nkind.N_TNAME) { want = vt.str; };
|
|
if (want.len == 0) { return -1; };
|
|
return flatvariantidx(c, tagged, want);
|
|
};
|
|
|
|
// cgtryprop — `e?` propagates the error variant up the stack.
|
|
// Legacy semantics only (success tag = 0). No tag remap; the
|
|
// selfhost code that uses ? today has the same variant order in
|
|
// operand and enclosing fn.
|
|
fn cgtryprop(c: *cgen, n: *node) void = {
|
|
cgexpr(c, n.lhs);
|
|
// AX = tag. If non-zero, this is an error; pop frame and RET.
|
|
let cl: str = mklabel(c, "tryprop_ok");
|
|
emitline("\tCMPQ\t$0, AX\n");
|
|
emitline("\tJE\t");
|
|
emitline(cl);
|
|
emitline("\n");
|
|
emitline("\tMOVQ\tBP, SP\n\tPOPQ\tBP\n\tRET\n");
|
|
emitlabel(cl);
|
|
// Success: unwrap value. Tag-only result was AX; the rest of
|
|
// the codegen expects the success value in AX (and BX for str).
|
|
// AX=tag, DX=val0, CX=val1 from the call ABI. For str success,
|
|
// shuffle (DX,CX) → (AX,BX); else move DX → AX.
|
|
let succisstr: bool = false;
|
|
if (n.lhs != nil) {
|
|
if (n.lhs.kind == nkind.N_CALL) {
|
|
let callee: *node = n.lhs.lhs;
|
|
if (callee != nil) {
|
|
let cname: str;
|
|
cname.ptr = nil; cname.len = 0;
|
|
let cmod: str;
|
|
cmod.ptr = nil; cmod.len = 0;
|
|
if (callee.kind == nkind.N_IDENT) {
|
|
cname = callee.str;
|
|
cmod = c.curmod;
|
|
};
|
|
if (callee.kind == nkind.N_DOT) {
|
|
cname = callee.str;
|
|
if (callee.lhs != nil) {
|
|
if (callee.lhs.kind == nkind.N_IDENT) {
|
|
cmod = callee.lhs.str;
|
|
};
|
|
};
|
|
};
|
|
if (cname.len > 0) {
|
|
let rtyp: *node = fnretlookupmod(c, cname, cmod);
|
|
if (rtyp != nil) {
|
|
if (rtyp.kind == nkind.N_TTAGGED) {
|
|
let first: *node = rtyp.list;
|
|
if (first != nil) {
|
|
if (isstrtype(c, first)) {
|
|
succisstr = true;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
if (succisstr) {
|
|
emitline("\tMOVQ\tCX, BX\n");
|
|
};
|
|
emitline("\tMOVQ\tDX, AX\n");
|
|
return;
|
|
};
|
|
|
|
// cgtryunw — `e!` aborts on the error variant via exit(1). Legacy
|
|
// semantics (success tag = 0).
|
|
fn cgtryunw(c: *cgen, n: *node) void = {
|
|
cgexpr(c, n.lhs);
|
|
let cl: str = mklabel(c, "tryunw_ok");
|
|
emitline("\tCMPQ\t$0, AX\n");
|
|
emitline("\tJE\t");
|
|
emitline(cl);
|
|
emitline("\n");
|
|
emitline("\tMOVQ\t$1, DI\n\tMOVQ\t$60, AX\n\tSYSCALL\n");
|
|
emitlabel(cl);
|
|
// Unwrap success value. (Same shuffle pattern as cgtryprop.)
|
|
let succisstr: bool = false;
|
|
if (n.lhs != nil) {
|
|
if (n.lhs.kind == nkind.N_CALL) {
|
|
let callee: *node = n.lhs.lhs;
|
|
if (callee != nil) {
|
|
let cname: str;
|
|
cname.ptr = nil; cname.len = 0;
|
|
let cmod: str;
|
|
cmod.ptr = nil; cmod.len = 0;
|
|
if (callee.kind == nkind.N_IDENT) {
|
|
cname = callee.str;
|
|
cmod = c.curmod;
|
|
};
|
|
if (callee.kind == nkind.N_DOT) {
|
|
cname = callee.str;
|
|
if (callee.lhs != nil) {
|
|
if (callee.lhs.kind == nkind.N_IDENT) {
|
|
cmod = callee.lhs.str;
|
|
};
|
|
};
|
|
};
|
|
if (cname.len > 0) {
|
|
let rtyp: *node = fnretlookupmod(c, cname, cmod);
|
|
if (rtyp != nil) {
|
|
if (rtyp.kind == nkind.N_TTAGGED) {
|
|
let first: *node = rtyp.list;
|
|
if (first != nil) {
|
|
if (isstrtype(c, first)) {
|
|
succisstr = true;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
if (succisstr) {
|
|
emitline("\tMOVQ\tCX, BX\n");
|
|
};
|
|
emitline("\tMOVQ\tDX, AX\n");
|
|
return;
|
|
};
|
|
|
|
fn cgtypetest(c: *cgen, n: *node) void = {
|
|
// `e is T` — load the lhs's tag, compare against T's variant
|
|
// index, set AX = (tag == idx). Result type is bool.
|
|
//
|
|
// Slot resolution is inlined (rather than factored into a helper
|
|
// with output parameters): wwstage cgen has a trap with i32
|
|
// stored via *i32 in this context — direct assignment of the
|
|
// local works, indirection through &scrutoff drops sign bits.
|
|
let lhs: *node = n.lhs;
|
|
let scrutoff: i32 = 0;
|
|
let scrutt: *node = nil;
|
|
if (lhs != nil) {
|
|
if (lhs.kind == nkind.N_IDENT) {
|
|
let lc: *local = localfindnode(c, lhs.str);
|
|
if (lc != nil) {
|
|
scrutoff = lc.off;
|
|
scrutt = resolvetagged(c, lc.tnode);
|
|
};
|
|
};
|
|
};
|
|
let want: i32 = cgtagvariantidx(c, scrutt, n.rhs);
|
|
if (want < 0) { want = 0; };
|
|
emitline("\tMOVQ\t");
|
|
emitoff(scrutoff: i64);
|
|
emitline("(BP), AX\n");
|
|
let nel: str = mklabel(c, "is_ne");
|
|
let dnl: str = mklabel(c, "is_done");
|
|
emitline("\tCMPQ\t$");
|
|
emitint(want: i64);
|
|
emitline(", AX\n");
|
|
emitline("\tJNE\t");
|
|
emitline(nel);
|
|
emitline("\n\tMOVQ\t$1, AX\n\tJMP\t");
|
|
emitline(dnl);
|
|
emitline("\n");
|
|
emitlabel(nel);
|
|
emitline("\tMOVQ\t$0, AX\n");
|
|
emitlabel(dnl);
|
|
return;
|
|
};
|
|
|
|
// isenumexpr — does this expression's static type resolve to an enum?
|
|
// Recognises enum-member access (`Foo.MEMBER`), enum-typed local
|
|
// idents, and nkind.N_BIN whose either operand is enum (so `R | W` flows
|
|
// through the cast pass-through too).
|
|
fn isenumexpr(c: *cgen, e: *node) bool = {
|
|
if (e == nil) { return false; };
|
|
let k: nkind = e.kind;
|
|
if (k == nkind.N_DOT) {
|
|
if (e.lhs != nil) {
|
|
if (e.lhs.kind == nkind.N_IDENT) {
|
|
if (enumlookup(c, e.lhs.str) != nil) { return true; };
|
|
};
|
|
};
|
|
};
|
|
if (k == nkind.N_IDENT) {
|
|
let lc: *local = localfindnode(c, e.str);
|
|
if (lc != nil) {
|
|
if (lc.tnode != nil) {
|
|
if (lc.tnode.kind == nkind.N_TNAME) {
|
|
if (enumlookup(c, lc.tnode.str) != nil) { return true; };
|
|
};
|
|
};
|
|
};
|
|
};
|
|
if (k == nkind.N_BIN) {
|
|
if (isenumexpr(c, e.lhs)) { return true; };
|
|
if (isenumexpr(c, e.rhs)) { return true; };
|
|
};
|
|
if (k == nkind.N_UN) {
|
|
if (isenumexpr(c, e.lhs)) { return true; };
|
|
};
|
|
return false;
|
|
};
|
|
|
|
fn isenumtype(c: *cgen, t: *node) bool = {
|
|
if (t == nil) { return false; };
|
|
if (t.kind == nkind.N_TENUM) { return true; };
|
|
if (t.kind == nkind.N_TNAME) {
|
|
if (enumlookup(c, t.str) != nil) { return true; };
|
|
};
|
|
return false;
|
|
};
|
|
|
|
fn cgtypeassert(c: *cgen, n: *node) void = {
|
|
// Enum ↔ integer: reinterpret-only. The LHS value already
|
|
// occupies AX (or AX:BX for str variants, irrelevant here);
|
|
// no tag/unwrap. Matches cmd/w6c/cgen.c's same short-circuit.
|
|
if (isenumexpr(c, n.lhs) || isenumtype(c, n.rhs)) {
|
|
cgexpr(c, n.lhs);
|
|
return;
|
|
};
|
|
// `e as T` — load tag, abort (exit 1) if tag != T's variant
|
|
// index, otherwise unwrap to T's ABI: scalar/ptr → AX, 16B
|
|
// str → (AX, BX). Mirrors cgmatch's slot-based value load.
|
|
// Slot resolution inlined; see cgtypetest comment.
|
|
let lhs: *node = n.lhs;
|
|
let scrutoff: i32 = 0;
|
|
let scrutt: *node = nil;
|
|
if (lhs != nil) {
|
|
if (lhs.kind == nkind.N_IDENT) {
|
|
let lc: *local = localfindnode(c, lhs.str);
|
|
if (lc != nil) {
|
|
scrutoff = lc.off;
|
|
scrutt = resolvetagged(c, lc.tnode);
|
|
};
|
|
};
|
|
};
|
|
let want: i32 = cgtagvariantidx(c, scrutt, n.rhs);
|
|
if (want < 0) { want = 0; };
|
|
let okl: str = mklabel(c, "asrt_ok");
|
|
emitline("\tMOVQ\t");
|
|
emitoff(scrutoff: i64);
|
|
emitline("(BP), AX\n");
|
|
emitline("\tCMPQ\t$");
|
|
emitint(want: i64);
|
|
emitline(", AX\n");
|
|
emitline("\tJE\t");
|
|
emitline(okl);
|
|
emitline("\n\tMOVQ\t$1, DI\n\tMOVQ\t$60, AX\n\tSYSCALL\n");
|
|
emitlabel(okl);
|
|
emitline("\tMOVQ\t");
|
|
emitoff((scrutoff + 8): i64);
|
|
emitline("(BP), AX\n");
|
|
if (isstrtype(c, n.rhs)) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff((scrutoff + 16): i64);
|
|
emitline("(BP), BX\n");
|
|
};
|
|
return;
|
|
};
|
|
|
|
fn cgcast(c: *cgen, n: *node) void = {
|
|
let srcfk: i32 = exprfloatkind(c, n.lhs);
|
|
let dstf64: bool = isfloattype(c, n.rhs);
|
|
let dstf32: bool = isf32type(c, n.rhs);
|
|
let dstfk: i32 = 0;
|
|
if (dstf32) { dstfk = 1; }
|
|
else { if (dstf64) { dstfk = 2; }; };
|
|
cgexpr(c, n.lhs);
|
|
// 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. Detect via dst-is-slice + src-ident's local-tnode
|
|
// being str (the common shape; non-ident sources rare).
|
|
if (isslicetype(c, n.rhs)) {
|
|
let srcstr: bool = false;
|
|
if (n.lhs != nil) {
|
|
if (n.lhs.kind == nkind.N_IDENT) {
|
|
let lc: *local = localfindnode(c, n.lhs.str);
|
|
if (lc != nil) {
|
|
if (isstrtype(c, lc.tnode)) { srcstr = true; };
|
|
};
|
|
};
|
|
};
|
|
if (srcstr) { 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: *node = n.rhs;
|
|
for (leaf_tn != nil) {
|
|
let lk: nkind = leaf_tn.kind;
|
|
if (lk == nkind.N_TBANG) { leaf_tn = leaf_tn.lhs; }
|
|
else { if (lk == nkind.N_TENUM) { leaf_tn = leaf_tn.lhs; }
|
|
else { if (lk == nkind.N_TNAME) {
|
|
let lnm: str = leaf_tn.str;
|
|
if (primsize(lnm) > 0) { break; };
|
|
let lal: *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 == nkind.N_TNAME) {
|
|
is_bool = 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: *node) void = {
|
|
// Result is the (ptr, len) pair: ptr in AX, len in BX. Call
|
|
// sites that expect a str arg pick these up directly.
|
|
let nstr: str = n.str;
|
|
let lab: str = internstrlit(c, nstr);
|
|
emitline("\tLEAQ\t");
|
|
emitbytes( lab.ptr, lab.len: u64);
|
|
emitline("(SB), AX\n");
|
|
emitline("\tMOVQ\t$");
|
|
emitint(nstr.len: i64);
|
|
emitline(", BX\n");
|
|
return;
|
|
};
|
|
|
|
fn cgident(c: *cgen, n: *node) void = {
|
|
let nm: str = n.str;
|
|
let lc: *local = localfindnode(c, nm);
|
|
if (lc != nil) {
|
|
let off: i32 = lc.off;
|
|
// Float local: MOVSS / MOVSD into X0. Skips the AX shuffle
|
|
// so consumers (cgbin, cgcast, return) pick up the SSE value
|
|
// directly.
|
|
if (isfloattype(c, lc.tnode)) {
|
|
let mov: str = "MOVSD";
|
|
if (isf32type(c, lc.tnode)) { mov = "MOVSS"; };
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\t");
|
|
emitoff(off: i64);
|
|
emitline("(BP), X0\n");
|
|
return;
|
|
};
|
|
// 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) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff((off + 8): i64);
|
|
emitline("(BP), BX\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;
|
|
};
|
|
// 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: *node = deflookuprhs(c, nm);
|
|
if (drhs != nil) {
|
|
if (drhs.kind == 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;
|
|
};
|
|
};
|
|
emitline("\tMOVQ\t");
|
|
emitsymname(c, nm);
|
|
emitline("(SB), AX\n");
|
|
return;
|
|
};
|
|
// Fn-name used as a value (e.g. `let f = some_fn;` or
|
|
// `... = some_fn;`). LEAQ the symbol address into AX. The
|
|
// emitfnname helper handles ffiresolve and module-mangling
|
|
// in one go, so a body-less FFI binding emits the C symbol
|
|
// it was declared with via @symbol(), not the ww-side ident.
|
|
// Bare ident → same-module by ww's resolver, hint with c.curmod.
|
|
let rtyp: *node = fnretlookup(c, nm);
|
|
if (rtyp != nil) {
|
|
emitline("\tLEAQ\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)) {
|
|
let isstr: bool = letvarisstr(c, nm);
|
|
let issl: bool = letvarisslice(c, nm);
|
|
if (isstr || issl) {
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, nm);
|
|
emitline("(SB), CX\n");
|
|
emitline("\tMOVQ\t(CX), AX\n");
|
|
emitline("\tMOVQ\t8(CX), BX\n");
|
|
if (issl) {
|
|
// Overwrites the address holder with the
|
|
// cap as the last step — CX is no longer
|
|
// needed once both ptr/len are loaded.
|
|
emitline("\tMOVQ\t16(CX), CX\n");
|
|
};
|
|
return;
|
|
};
|
|
// Float global: same LEAQ-indirect shape, since MOVSS/
|
|
// MOVSD have no D_EXTERN operand form in w6a. Signed-narrow
|
|
// scalar globals route through the same LEAQ scratch since
|
|
// MOVSXD/MOVSWQ/MOVSBQ also have no D_EXTERN form.
|
|
let lvtnode: *node = nil;
|
|
let lv: *letvar = c.lets;
|
|
for (lv != nil) {
|
|
if (streq(lv.name, nm)) {
|
|
if (isfloattype(c, lv.tnode)) {
|
|
let mov: str = "MOVSD";
|
|
if (isf32type(c, lv.tnode)) { mov = "MOVSS"; };
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, nm);
|
|
emitline("(SB), CX\n");
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\t(CX), X0\n");
|
|
return;
|
|
};
|
|
lvtnode = lv.tnode;
|
|
lv = nil;
|
|
} else {
|
|
lv = lv.lvnext;
|
|
};
|
|
};
|
|
let glop: str = localloadop(c, lvtnode);
|
|
if (streq(glop, "MOVQ")) {
|
|
emitline("\tMOVQ\t");
|
|
emitsymname(c, nm);
|
|
emitline("(SB), AX\n");
|
|
} else {
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, nm);
|
|
emitline("(SB), CX\n");
|
|
emitline("\t");
|
|
emitline(glop);
|
|
emitline("\t(CX), AX\n");
|
|
};
|
|
return;
|
|
};
|
|
return;
|
|
};
|
|
|
|
fn cgindex(c: *cgen, n: *node) void = {
|
|
// Element-size-aware load: u8 → MOVZBQ, i32 → MOVSXD, u32 → MOVL,
|
|
// str → (ptr, len) into (AX, BX), everything else → MOVQ. Fast
|
|
// path when the base is a bare ident (mem.ww shape).
|
|
let base: *node = n.lhs;
|
|
let idx: *node = n.rhs;
|
|
let esz: i32 = 8;
|
|
let signed_elem: bool = false;
|
|
let baselocal: *local = nil;
|
|
// Global `[N]T` array or `*T` pointer used as an index base.
|
|
// The local-ident lookup above misses it; we need LEAQ name(SB)
|
|
// (array, the symbol IS the storage) or MOVQ name(SB) (pointer,
|
|
// the symbol holds the address) to feed the addend.
|
|
let isglobalarr: bool = false;
|
|
let isglobalptr: bool = false;
|
|
let globalname: str;
|
|
globalname.ptr = nil; globalname.len = 0;
|
|
if (base != nil) {
|
|
if (base.kind == nkind.N_IDENT) {
|
|
let bn: str = base.str;
|
|
baselocal = localfindnode(c, bn);
|
|
if (baselocal != nil) {
|
|
esz = elemsizeofc(c, baselocal.tnode);
|
|
signed_elem = elemissignedc(c, baselocal.tnode);
|
|
} else {
|
|
let tn: *node = letvartnode(c, bn);
|
|
if (tn != nil) {
|
|
if (tn.kind == nkind.N_TARRAY) {
|
|
isglobalarr = true;
|
|
globalname = bn;
|
|
esz = elemsizeofc(c, tn);
|
|
signed_elem = elemissignedc(c, tn);
|
|
};
|
|
if (tn.kind == nkind.N_TPTR) {
|
|
isglobalptr = true;
|
|
globalname = bn;
|
|
esz = elemsizeofc(c, tn);
|
|
signed_elem = elemissignedc(c, tn);
|
|
};
|
|
};
|
|
};
|
|
} else { if (base.kind == nkind.N_DOT) {
|
|
esz = indexbaseesz(c, base);
|
|
} else { if (base.kind == nkind.N_INDEX) {
|
|
let bt: *node = indexvaluetnode(c, base);
|
|
if (bt != nil) {
|
|
esz = elemsizeofc(c, bt);
|
|
signed_elem = elemissignedc(c, bt);
|
|
};
|
|
};};};
|
|
};
|
|
// Tagged-union element: load slot words into (AX=tag, DX=val0,
|
|
// CX=val1) matching the tagged-return ABI so call-arg / let /
|
|
// match consumers see the same shape as a tagged-returning fn.
|
|
// Slot size = esz (8/16/24); nullable folded element is one
|
|
// word, which the fallthrough below handles via MOVQ AX.
|
|
let elem_tagged: bool = false;
|
|
let elem_slot_sz: i32 = esz;
|
|
if (base != nil) {
|
|
if (base.kind == nkind.N_IDENT) {
|
|
let bl: *local = baselocal;
|
|
let etn: *node = nil;
|
|
if (bl != nil) {
|
|
let btn: *node = bl.tnode;
|
|
if (btn != nil) {
|
|
let bk: nkind = btn.kind;
|
|
if (bk == nkind.N_TARRAY) { etn = btn.lhs; };
|
|
if (bk == nkind.N_TSLICE) { etn = btn.lhs; };
|
|
if (bk == nkind.N_TPTR) { etn = btn.lhs; };
|
|
};
|
|
} else {
|
|
let tn: *node = letvartnode(c, base.str);
|
|
if (tn != nil) {
|
|
let bk: nkind = tn.kind;
|
|
if (bk == nkind.N_TARRAY) { etn = tn.lhs; };
|
|
if (bk == nkind.N_TSLICE) { etn = tn.lhs; };
|
|
if (bk == nkind.N_TPTR) { etn = tn.lhs; };
|
|
};
|
|
};
|
|
if (istaggedtype(c, etn)) {
|
|
if (!isnullabletype(etn)) {
|
|
elem_tagged = true;
|
|
elem_slot_sz = slotsize(c, etn);
|
|
esz = elem_slot_sz;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
cgexpr(c, idx);
|
|
if (esz > 1) {
|
|
emitline("\tMOVQ\t$");
|
|
emitint(esz: i64);
|
|
emitline(", CX\n");
|
|
emitline("\tIMULQ\tCX, AX\n");
|
|
};
|
|
if (isglobalarr || isglobalptr) {
|
|
if (isglobalarr) {
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, globalname);
|
|
emitline("(SB), BX\n");
|
|
} else {
|
|
emitline("\tMOVQ\t");
|
|
emitsymname(c, globalname);
|
|
emitline("(SB), BX\n");
|
|
};
|
|
emitline("\tADDQ\tAX, BX\n");
|
|
if (elem_tagged) {
|
|
if (elem_slot_sz > 24) {
|
|
emitline("\tMOVQ\t24(BX), R8\n");
|
|
};
|
|
if (elem_slot_sz > 16) {
|
|
emitline("\tMOVQ\t16(BX), CX\n");
|
|
};
|
|
if (elem_slot_sz > 8) {
|
|
emitline("\tMOVQ\t8(BX), DX\n");
|
|
};
|
|
emitline("\tMOVQ\t(BX), AX\n");
|
|
return;
|
|
};
|
|
// #43: str element-stride routes through primtypesize so the
|
|
// 16-vs-24 dispatch tracks ty_str.size for #1.
|
|
if (esz == primtypesize("str"): i32) {
|
|
emitline("\tMOVQ\t8(BX), CX\n");
|
|
emitline("\tMOVQ\t(BX), AX\n");
|
|
emitline("\tMOVQ\tCX, BX\n");
|
|
return;
|
|
};
|
|
let lop1: str = loadopsz(signed_elem, esz);
|
|
emitline("\t");
|
|
emitline(lop1);
|
|
emitline("\t(BX), AX\n");
|
|
return;
|
|
};
|
|
if (baselocal != nil) {
|
|
let tn: *node = baselocal.tnode;
|
|
let isarray: bool = false;
|
|
if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isarray = true; }; };
|
|
if (isarray) {
|
|
emitline("\tLEAQ\t");
|
|
emitoff(baselocal.off: i64);
|
|
emitline("(BP), BX\n");
|
|
} else {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(baselocal.off: i64);
|
|
emitline("(BP), BX\n");
|
|
};
|
|
emitline("\tADDQ\tAX, BX\n");
|
|
if (elem_tagged) {
|
|
if (elem_slot_sz > 24) {
|
|
emitline("\tMOVQ\t24(BX), R8\n");
|
|
};
|
|
if (elem_slot_sz > 16) {
|
|
emitline("\tMOVQ\t16(BX), CX\n");
|
|
};
|
|
if (elem_slot_sz > 8) {
|
|
emitline("\tMOVQ\t8(BX), DX\n");
|
|
};
|
|
emitline("\tMOVQ\t(BX), AX\n");
|
|
return;
|
|
};
|
|
// str element (16B today): load (ptr, len) into (AX, BX) so
|
|
// the value flows through the str-rhs convention.
|
|
// #43: route via primtypesize so the stride tracks #1.
|
|
if (esz == primtypesize("str"): i32) {
|
|
emitline("\tMOVQ\t8(BX), CX\n");
|
|
emitline("\tMOVQ\t(BX), AX\n");
|
|
emitline("\tMOVQ\tCX, BX\n");
|
|
return;
|
|
};
|
|
let lop2: str = loadopsz(signed_elem, esz);
|
|
emitline("\t");
|
|
emitline(lop2);
|
|
emitline("\t(BX), AX\n");
|
|
return;
|
|
};
|
|
// Generic fallback when base isn't a plain ident.
|
|
emitline("\tPUSHQ\tAX\n");
|
|
cgexpr(c, base);
|
|
emitline("\tPOPQ\tBX\n");
|
|
emitline("\tADDQ\tBX, AX\n");
|
|
if (elem_tagged) {
|
|
// AX holds the element address. Copy to BX (loading slot+0
|
|
// into AX clobbers it), then read slot words.
|
|
emitline("\tMOVQ\tAX, BX\n");
|
|
if (elem_slot_sz > 16) {
|
|
emitline("\tMOVQ\t16(BX), CX\n");
|
|
};
|
|
if (elem_slot_sz > 8) {
|
|
emitline("\tMOVQ\t8(BX), DX\n");
|
|
};
|
|
emitline("\tMOVQ\t(BX), AX\n");
|
|
return;
|
|
};
|
|
// #43: str element-stride routes through primtypesize so the
|
|
// 16-vs-24 dispatch tracks ty_str.size for #1.
|
|
if (esz == primtypesize("str"): i32) {
|
|
emitline("\tMOVQ\t8(AX), BX\n");
|
|
emitline("\tMOVQ\t(AX), AX\n");
|
|
return;
|
|
};
|
|
let lop3: str = loadopsz(signed_elem, esz);
|
|
emitline("\t");
|
|
emitline(lop3);
|
|
emitline("\t(AX), AX\n");
|
|
return;
|
|
};
|
|
|
|
// cgslice — `base[lo:hi]` as a slice value. Leaves (AX=base+lo,
|
|
// BX=hi-lo, CX=hi-lo) so callers can route to a slice slot,
|
|
// return, or arg with the same triple ABI. Cap defaults to the
|
|
// new length; no syntax for a wider cap yet. Element scaling
|
|
// on the ptr isn't wired — non-u8 slices need a follow-up audit.
|
|
fn cgslice(c: *cgen, n: *node) void = {
|
|
let base: *node = n.lhs;
|
|
let lo: *node = n.rhs;
|
|
let hi: *node = n.cond;
|
|
let baselocal: *local = nil;
|
|
let globaltn: *node = nil;
|
|
let globalname: str;
|
|
globalname.ptr = nil; globalname.len = 0;
|
|
if (base != nil) {
|
|
if (base.kind == nkind.N_IDENT) {
|
|
baselocal = localfindnode(c, base.str);
|
|
if (baselocal == nil) {
|
|
let gt: *node = letvartnode(c, base.str);
|
|
if (gt != nil) {
|
|
globaltn = gt;
|
|
globalname = base.str;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// base address
|
|
if (baselocal != nil) {
|
|
let tn: *node = baselocal.tnode;
|
|
let isarray: bool = false;
|
|
if (tn != nil) {
|
|
if (tn.kind == nkind.N_TARRAY) { isarray = true; };
|
|
};
|
|
if (isarray) {
|
|
emitline("\tLEAQ\t");
|
|
emitoff(baselocal.off: i64);
|
|
emitline("(BP), AX\n");
|
|
} else {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(baselocal.off: i64);
|
|
emitline("(BP), AX\n");
|
|
};
|
|
} else { if (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).
|
|
if (globaltn.kind == nkind.N_TARRAY) {
|
|
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) {
|
|
cgexpr(c, base);
|
|
};};};
|
|
emitline("\tPUSHQ\tAX\n");
|
|
// lo (default 0)
|
|
if (lo != nil) { cgexpr(c, lo); }
|
|
else { emitline("\tMOVQ\t$0, AX\n"); };
|
|
emitline("\tPUSHQ\tAX\n");
|
|
// hi (default base length)
|
|
if (hi != nil) {
|
|
cgexpr(c, hi);
|
|
} else { if (baselocal != nil) {
|
|
let tn: *node = baselocal.tnode;
|
|
let handled: bool = false;
|
|
if (tn != nil) {
|
|
if (tn.kind == nkind.N_TARRAY) {
|
|
let lenn: *node = tn.rhs;
|
|
if (lenn != nil) {
|
|
if (lenn.kind == nkind.N_INTLIT) {
|
|
emitline("\tMOVQ\t$");
|
|
emituint(lenn.uval);
|
|
emitline(", AX\n");
|
|
handled = true;
|
|
};
|
|
};
|
|
} else { if (tn.kind == nkind.N_TSLICE) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff((baselocal.off + 8): i64);
|
|
emitline("(BP), AX\n");
|
|
handled = true;
|
|
} else { if (tn.kind == nkind.N_TNAME) {
|
|
if (streq(tn.str, "str")) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff((baselocal.off + 8): i64);
|
|
emitline("(BP), AX\n");
|
|
handled = true;
|
|
};
|
|
};};};
|
|
};
|
|
if (!handled) { emitline("\tMOVQ\t$0, AX\n"); };
|
|
} else { if (globaltn != nil) {
|
|
let handled: bool = false;
|
|
if (globaltn.kind == nkind.N_TARRAY) {
|
|
let lenn: *node = globaltn.rhs;
|
|
if (lenn != nil) {
|
|
if (lenn.kind == nkind.N_INTLIT) {
|
|
emitline("\tMOVQ\t$");
|
|
emituint(lenn.uval);
|
|
emitline(", AX\n");
|
|
handled = true;
|
|
};
|
|
};
|
|
} else { if (globaltn.kind == nkind.N_TSLICE) {
|
|
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 {
|
|
emitline("\tMOVQ\t$0, AX\n");
|
|
};};};
|
|
emitline("\tMOVQ\tAX, BX\n");
|
|
emitline("\tPOPQ\tCX\n");
|
|
emitline("\tPOPQ\tAX\n");
|
|
emitline("\tADDQ\tCX, AX\n");
|
|
emitline("\tSUBQ\tCX, BX\n");
|
|
emitline("\tMOVQ\tBX, CX\n");
|
|
};
|
|
|
|
fn cgmatch(c: *cgen, n: *node) void = {
|
|
// match (e) { case let v: T => stmt; ... }
|
|
//
|
|
// Read the tagged-union slot and dispatch by tag. Slot
|
|
// layout: [+0]=tag, [+8]=value0, [+16]=value1. Bindings
|
|
// (`case let v: T =>`) get a fresh local slot loaded from
|
|
// slot+8 (and slot+16 for str-typed payload).
|
|
let scrut: *node = n.lhs;
|
|
let scrutoff: i32 = 0;
|
|
let scrutt: *node = nil;
|
|
if (scrut != nil) {
|
|
if (scrut.kind == nkind.N_IDENT) {
|
|
let lc: *local = localfindnode(c, scrut.str);
|
|
if (lc != nil) {
|
|
scrutoff = lc.off;
|
|
scrutt = resolvetagged(c, lc.tnode);
|
|
};
|
|
} else {
|
|
// Non-ident scrutinee (call result, arr[i], 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);
|
|
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: *node = n.list;
|
|
for (cs != nil) {
|
|
let nxt: str = mklabel(c, "match_next");
|
|
let pat: *node = cs.lhs;
|
|
let nullable: bool = isnullabletype(scrutt);
|
|
// Per-arm scope: save c.locals before allocating the bind
|
|
// and restore after the body runs, so the arm's bind (and
|
|
// any nested lets) don't leak past the arm. Matches the
|
|
// checker's newscope/restore around N_MCASE. Without this,
|
|
// `let e: *T = ...; match (r) { case let e: str => ... };
|
|
// use e` would resolve `e` after the match to the inner
|
|
// str slot instead of the outer ptr.
|
|
let arm_locals_saved: *local = c.locals;
|
|
// Compute the variant tag for this arm. Default arm
|
|
// (no pattern) skips the tag check.
|
|
if (pat != nil) {
|
|
if (nullable) {
|
|
// Discriminator = pointer-vs-null.
|
|
// *T arm: skip if ptr == 0.
|
|
// void arm: skip if ptr != 0.
|
|
let ptr_tag: i32 = nullableptrtag(scrutt);
|
|
let cur_tag: i32 = 0;
|
|
if (pat.kind == nkind.N_TPTR) { cur_tag = ptr_tag; }
|
|
else { if (ptr_tag == 0) { cur_tag = 1; }; };
|
|
emitline("\tMOVQ\t");
|
|
emitoff(scrutoff: i64);
|
|
emitline("(BP), AX\n");
|
|
emitline("\tCMPQ\t$0, AX\n");
|
|
if (cur_tag == ptr_tag) {
|
|
emitline("\tJE\t");
|
|
} else {
|
|
emitline("\tJNE\t");
|
|
};
|
|
emitline(nxt);
|
|
emitline("\n");
|
|
} else {
|
|
let want: i32 = 0;
|
|
if (scrutt != nil) {
|
|
if (scrutt.kind == nkind.N_TTAGGED) {
|
|
let r: i32 = -1;
|
|
if (pat.kind == nkind.N_TNAME) {
|
|
r = flatvariantidx(c, scrutt, pat.str);
|
|
} else { if (pat.kind == nkind.N_TSLICE) {
|
|
// `case let s: []T =>` — pat.str is empty
|
|
// because the variant is a composite, so
|
|
// route through the slice-shape helper.
|
|
// Without this every (scalar | []T) match
|
|
// arm collapses to tag 0 (task #19).
|
|
r = flatslicevariantidx(c, scrutt, pat.lhs);
|
|
}; };
|
|
if (r >= 0) { want = r; };
|
|
};
|
|
};
|
|
emitline("\tMOVQ\t");
|
|
emitoff(scrutoff: i64);
|
|
emitline("(BP), AX\n");
|
|
emitline("\tCMPQ\t$");
|
|
emitint(want: i64);
|
|
emitline(", AX\n");
|
|
emitline("\tJNE\t");
|
|
emitline(nxt);
|
|
emitline("\n");
|
|
};
|
|
};
|
|
// Bind `let v: T` from the slot, if requested.
|
|
let bn: str = cs.str;
|
|
if (bn.len > 0) {
|
|
if (pat != nil) {
|
|
if (nullable) {
|
|
// Bind the pointer (or skip for the
|
|
// void arm, which has zero-size). The
|
|
// value IS slot+0.
|
|
if (pat.kind == nkind.N_TPTR) {
|
|
let voff: i32 = localalloc(c, bn, 8, pat);
|
|
emitline("\tMOVQ\t");
|
|
emitoff(scrutoff: i64);
|
|
emitline("(BP), AX\n");
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitoff(voff: i64);
|
|
emitline("(BP)\n");
|
|
};
|
|
} else {
|
|
// 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;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// Body. Match arms are statements; we cgstmt them.
|
|
if (cs.body != nil) { cgstmt(c, cs.body); };
|
|
// Restore the locals head — pop everything the arm pushed
|
|
// so post-match code resolves names to their original (outer)
|
|
// bindings.
|
|
c.locals = arm_locals_saved;
|
|
emitline("\tJMP\t");
|
|
emitline(endl);
|
|
emitline("\n");
|
|
emitlabel(nxt);
|
|
cs = cs.next;
|
|
};
|
|
emitlabel(endl);
|
|
if (c.yieldtop > 0) { c.yieldtop -= 1; };
|
|
return;
|
|
};
|
|
|
|
fn cgdot(c: *cgen, n: *node) void = {
|
|
let lhs: *node = n.lhs;
|
|
let fld: str = n.str;
|
|
// `(*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. v1 scope: N_IDENT inner only;
|
|
// (*expr).f follow-up task pending. Enum-leaf lookup above and
|
|
// chained-N_DOT branches below keep checking raw lhs since
|
|
// (*p) is neither shape.
|
|
let dotlhs: *node = lhs;
|
|
if (dotlhs != nil) {
|
|
if (dotlhs.kind == nkind.N_UN) {
|
|
if (dotlhs.op == tkind.TK_STAR) {
|
|
if (dotlhs.lhs != nil) {
|
|
if (dotlhs.lhs.kind == 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 == nkind.N_IDENT) {
|
|
etname = lhs.str;
|
|
};
|
|
if (lhs.kind == nkind.N_DOT) {
|
|
if (lhs.lhs != nil) {
|
|
if (lhs.lhs.kind == nkind.N_IDENT) {
|
|
etname = lhs.str;
|
|
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 == nkind.N_IDENT) {
|
|
let nm: str = dotlhs.str;
|
|
let lc: *local = localfindnode(c, nm);
|
|
if (lc != nil) {
|
|
let tn: *node = lc.tnode;
|
|
let lkind: nkind = nkind.N_NONE;
|
|
if (tn != nil) { lkind = tn.kind; };
|
|
// Pointer-to-struct: deref then field load.
|
|
if (lkind == nkind.N_TPTR) {
|
|
let inner: *node = tn.lhs;
|
|
let sname: str;
|
|
sname.ptr = nil; sname.len = 0;
|
|
if (inner != nil) {
|
|
if (inner.kind == nkind.N_TNAME) {
|
|
sname = inner.str;
|
|
};
|
|
};
|
|
if (sname.len > 0) {
|
|
// structlookupchain walks the alias chain on
|
|
// a miss so `*tokenizer` where tokenizer is
|
|
// a transitively-aliased struct still
|
|
// resolves to the underlying fieldinfo (#22).
|
|
let si: *structinfo = structlookupchain(c, inner);
|
|
if (si != nil) {
|
|
let fi: *fieldinfo = si.fields;
|
|
for (fi != nil) {
|
|
let fn_: str = fi.fname;
|
|
if (streq(fn_, fld)) {
|
|
// tagged-union field via *struct: stage
|
|
// the *struct in BX, then load the four
|
|
// payload regs via cgloadtaggedfield.
|
|
// BX isn't a target (AX/DX/CX/R8), so
|
|
// load order doesn't matter. Mirrors
|
|
// the direct-local branch above so the
|
|
// match / let-init / call-arg consumer
|
|
// shape is identical regardless of
|
|
// pointer rooting.
|
|
if (istaggedtype(c, fi.tnode)) {
|
|
let tsz: i32 = slotsize(c, fi.tnode);
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), BX\n");
|
|
cgloadtaggedfield(c, "BX",
|
|
fi.foff, tsz);
|
|
return;
|
|
};
|
|
// str field via *struct: load len into a
|
|
// scratch first (so loading ptr into AX
|
|
// last leaves (AX=ptr, BX=len)).
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), BX\n");
|
|
if (isstrtype(c, fi.tnode)) {
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg((fi.foff + 8): i64, "BX");
|
|
emitline(", CX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg(fi.foff: i64, "BX");
|
|
emitline(", AX\n");
|
|
emitline("\tMOVQ\tCX, BX\n");
|
|
} else { if (isslicetype(c, fi.tnode)) {
|
|
// slice field via *struct: load
|
|
// (ptr, len, cap) into (AX, BX, CX).
|
|
// BX holds the *struct pointer, so
|
|
// load .len LAST so the earlier
|
|
// reads still index off the base.
|
|
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");
|
|
} else { if (isfloattype(c, fi.tnode)) {
|
|
// f64/f32 via *struct: route through X0.
|
|
// MOVQ into AX leaves the SSE reg stale
|
|
// and any downstream consumer (arg
|
|
// pass, return, arithmetic) reads
|
|
// garbage.
|
|
let mov: str = "MOVSD";
|
|
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\t");
|
|
emitdispreg(fi.foff: i64, "BX");
|
|
emitline(", X0\n");
|
|
} else {
|
|
let op: str = fieldloadop(c, fi);
|
|
emitline("\t");
|
|
emitline(op);
|
|
emitline("\t");
|
|
emitdispreg(fi.foff: i64, "BX");
|
|
emitline(", AX\n");
|
|
}; }; };
|
|
return;
|
|
};
|
|
fi = fi.finext;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// Direct struct local: field load at off+foff.
|
|
if (lkind == nkind.N_TNAME) {
|
|
// structlookupchain walks the alias chain on
|
|
// miss so a transitively-aliased struct (`type
|
|
// b = a; a = struct`) still resolves to the
|
|
// underlying fieldinfo (#22).
|
|
let si: *structinfo = structlookupchain(c, tn);
|
|
if (si != nil) {
|
|
let fi: *fieldinfo = si.fields;
|
|
for (fi != nil) {
|
|
let fn_: str = fi.fname;
|
|
if (streq(fn_, fld)) {
|
|
// tagged-union field: emit the AX=tag,
|
|
// DX=word0, CX=word1[, R8=word2] load
|
|
// sequence so the match / let-init /
|
|
// call-arg consumers see the same shape
|
|
// as a tagged-returning fn. Pre-#28 fell
|
|
// through to the scalar fieldloadop and
|
|
// only AX (tag) was loaded — payload
|
|
// words came from whatever the caller
|
|
// left in DX/CX/R8.
|
|
if (istaggedtype(c, fi.tnode)) {
|
|
let tsz: i32 = slotsize(c, fi.tnode);
|
|
cgloadtaggedfield(c, "BP",
|
|
lc.off + fi.foff, tsz);
|
|
return;
|
|
};
|
|
// str field: load both halves so chained
|
|
// `.ptr` / `.len` see (AX=ptr, BX=len).
|
|
if (isstrtype(c, fi.tnode)) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff((lc.off + fi.foff): i64);
|
|
emitline("(BP), AX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitoff((lc.off + fi.foff + 8): i64);
|
|
emitline("(BP), BX\n");
|
|
} else { if (isslicetype(c, fi.tnode)) {
|
|
// slice field: load (ptr, len, cap)
|
|
// into (AX, BX, CX). Base is BP so
|
|
// no aliasing — order doesn't matter.
|
|
emitline("\tMOVQ\t");
|
|
emitoff((lc.off + fi.foff): i64);
|
|
emitline("(BP), AX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitoff((lc.off + fi.foff + 8): i64);
|
|
emitline("(BP), BX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitoff((lc.off + fi.foff + 16): i64);
|
|
emitline("(BP), CX\n");
|
|
} else { if (isfloattype(c, fi.tnode)) {
|
|
// f64/f32 field: route through X0.
|
|
let mov: str = "MOVSD";
|
|
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\t");
|
|
emitoff((lc.off + fi.foff): i64);
|
|
emitline("(BP), X0\n");
|
|
} else {
|
|
let op: str = fieldloadop(c, fi);
|
|
emitline("\t");
|
|
emitline(op);
|
|
emitline("\t");
|
|
emitoff((lc.off + fi.foff): i64);
|
|
emitline("(BP), AX\n");
|
|
}; }; };
|
|
return;
|
|
};
|
|
fi = fi.finext;
|
|
};
|
|
};
|
|
};
|
|
// Array pseudo-fields: `.ptr` is the array's
|
|
// address (LEAQ); `.len` is the static element
|
|
// count (immediate).
|
|
if (lkind == nkind.N_TARRAY) {
|
|
if (streq(fld, "ptr")) {
|
|
emitline("\tLEAQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), AX\n");
|
|
return;
|
|
};
|
|
if (streq(fld, "len")) {
|
|
let lenn: *node = tn.rhs;
|
|
let alen: i64 = 0i64;
|
|
if (lenn != nil) {
|
|
if (lenn.kind == nkind.N_INTLIT) { alen = lenn.uval: i64; };
|
|
};
|
|
emitline("\tMOVQ\t$");
|
|
emitint(alen);
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
};
|
|
// Hare-style tuple positional access: `t.0`, `t.1`.
|
|
// Walk the tuple element type list summing slotsize
|
|
// (matches the (scalar, str) init layout which puts
|
|
// the scalar in an 8B slot and the str in 16B). For
|
|
// a str element, load both halves into (AX, BX) so
|
|
// chains like `t.1.len` propagate correctly.
|
|
if (lkind == nkind.N_TTUPLE) {
|
|
let idx: i32 = fldnumidx(fld);
|
|
if (idx >= 0) {
|
|
let tp: *node = tn.list;
|
|
let foff: i32 = 0;
|
|
let i: i32 = 0;
|
|
for (i < idx) {
|
|
if (tp == nil) { i = idx; }
|
|
else {
|
|
foff += slotsize(c, tp);
|
|
tp = tp.next;
|
|
i += 1;
|
|
};
|
|
};
|
|
if (tp != nil) {
|
|
if (isstrtyperaw(tp)) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff((lc.off + foff + 0): i64);
|
|
emitline("(BP), AX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitoff((lc.off + foff + 8): i64);
|
|
emitline("(BP), BX\n");
|
|
return;
|
|
};
|
|
let sz: i32 = slotsize(c, tp);
|
|
let op: str = tnodeloadop(c, tp, sz);
|
|
emitline("\t");
|
|
emitline(op);
|
|
emitline("\t");
|
|
emitoff((lc.off + foff): i64);
|
|
emitline("(BP), AX\n");
|
|
return;
|
|
};
|
|
};
|
|
};
|
|
// str/slice pseudo-fields .ptr/.len/.cap on a
|
|
// direct local: load at slot+delta.
|
|
let delta: i32 = -1;
|
|
if (streq(fld, "ptr")) { delta = 0; };
|
|
if (streq(fld, "len")) { delta = 8; };
|
|
if (streq(fld, "cap")) { delta = 16; };
|
|
if (delta >= 0) {
|
|
// Pointer to str/slice (`*[]u8`, `*str`):
|
|
// deref, then load at delta within the
|
|
// pointed-to header. C cgen does the same.
|
|
if (lkind == nkind.N_TPTR) {
|
|
let inner: *node = tn.lhs;
|
|
let innerkind: nkind = nkind.N_NONE;
|
|
if (inner != nil) { innerkind = inner.kind; };
|
|
let innerstr: bool = false;
|
|
if (innerkind == nkind.N_TNAME) {
|
|
if (streq(inner.str, "str")) { innerstr = true; };
|
|
};
|
|
if (innerkind == nkind.N_TSLICE) { innerstr = true; };
|
|
if (innerstr) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), BX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg(delta: i64, "BX");
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
};
|
|
emitline("\tMOVQ\t");
|
|
emitoff((lc.off + delta): i64);
|
|
emitline("(BP), AX\n");
|
|
return;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// `def NAME: str = "..."` field access — inline the literal.
|
|
// Sdef-backed strs aren't laid out in memory, so falling
|
|
// through to the SB-load fallback below would mis-emit
|
|
// `MOVQ <field>(SB), AX` (looking up the field name as a
|
|
// symbol). Mirrors cmd/w6c/cgen.c nkind.N_DOT off==0 / Sdef branch.
|
|
if (lhs != nil) {
|
|
if (lhs.kind == nkind.N_IDENT) {
|
|
let drhs: *node = deflookuprhs(c, lhs.str);
|
|
if (drhs != nil) {
|
|
if (drhs.kind == nkind.N_STRLIT) {
|
|
let bytes: str = drhs.str;
|
|
if (streq(fld, "ptr")) {
|
|
let lab: str = internstrlit(c, bytes);
|
|
emitline("\tLEAQ\t");
|
|
emitbytes( lab.ptr, lab.len: u64);
|
|
emitline("(SB), AX\n");
|
|
return;
|
|
};
|
|
if (streq(fld, "len")) {
|
|
emitline("\tMOVQ\t$");
|
|
emitint(bytes.len: i64);
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// Top-level str/slice global field access — load .ptr / .len
|
|
// (and .cap for slices) via &name(SB) into CX, then MOVQ
|
|
// delta(CX), AX. Without this the module-qualified fallback
|
|
// below would mis-emit `MOVQ <field>(SB), AX`.
|
|
if (lhs != nil) {
|
|
if (lhs.kind == nkind.N_IDENT) {
|
|
if (isletvar(c, lhs.str)) {
|
|
let isstr: bool = letvarisstr(c, lhs.str);
|
|
let issl: bool = letvarisslice(c, lhs.str);
|
|
if (isstr || issl) {
|
|
let delta: i32 = -1;
|
|
if (streq(fld, "ptr")) { delta = 0; };
|
|
if (streq(fld, "len")) { delta = 8; };
|
|
if (issl) {
|
|
if (streq(fld, "cap")) { delta = 16; };
|
|
};
|
|
if (delta >= 0) {
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, lhs.str);
|
|
emitline("(SB), CX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg(delta: i64, "CX");
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// Top-level struct global field read — LEAQ name(SB), CX then
|
|
// load at fi.foff(CX). Mirrors the local "Direct struct local"
|
|
// branch above, swapping the BP frame slot for the global VA.
|
|
// Field-width-aware op handles MOVQ / MOVL / MOVZBQ / MOVSXD.
|
|
if (lhs != nil) {
|
|
if (lhs.kind == nkind.N_IDENT) {
|
|
let si: *structinfo = letvarstructinfo(c, lhs.str);
|
|
if (si != nil) {
|
|
let fi: *fieldinfo = si.fields;
|
|
for (fi != nil) {
|
|
if (streq(fi.fname, fld)) {
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, lhs.str);
|
|
emitline("(SB), CX\n");
|
|
// tagged-union field: load via the tagged-
|
|
// return ABI off CX. cgloadtaggedfield orders
|
|
// the loads so CX (word1 target) is written
|
|
// LAST — otherwise the base address would be
|
|
// trashed before the +24/R8 (slice variant)
|
|
// read could index off it. Pre-#28 fell
|
|
// through to fieldloadop and dropped payload.
|
|
if (istaggedtype(c, fi.tnode)) {
|
|
let tsz: i32 = slotsize(c, fi.tnode);
|
|
cgloadtaggedfield(c, "CX", fi.foff, tsz);
|
|
return;
|
|
};
|
|
if (isstrtype(c, fi.tnode)) {
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg(fi.foff: i64, "CX");
|
|
emitline(", AX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg((fi.foff + 8): i64, "CX");
|
|
emitline(", BX\n");
|
|
} else { if (isfloattype(c, fi.tnode)) {
|
|
// f64/f32 global field: route through X0.
|
|
let mov: str = "MOVSD";
|
|
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\t");
|
|
emitdispreg(fi.foff: i64, "CX");
|
|
emitline(", X0\n");
|
|
} else {
|
|
let op: str = fieldloadop(c, fi);
|
|
emitline("\t");
|
|
emitline(op);
|
|
emitline("\t");
|
|
emitdispreg(fi.foff: i64, "CX");
|
|
emitline(", AX\n");
|
|
}; };
|
|
return;
|
|
};
|
|
fi = fi.finext;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// `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 <fld>(SB), AX` (linker: `undefined reference to <fld>`).
|
|
// 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.
|
|
if (lhs != nil) {
|
|
if (lhs.kind == nkind.N_INDEX) {
|
|
let idxbase: *node = lhs.lhs;
|
|
if (idxbase != nil) { if (idxbase.kind == nkind.N_IDENT) {
|
|
let lc: *local = localfindnode(c, idxbase.str);
|
|
if (lc != nil) { if (lc.tnode != nil) {
|
|
let tn: *node = lc.tnode;
|
|
let elemt: *node = nil;
|
|
let baseisarray: bool = false;
|
|
let tk: nkind = tn.kind;
|
|
if (tk == nkind.N_TSLICE) { elemt = tn.lhs; };
|
|
if (tk == nkind.N_TARRAY) { elemt = tn.lhs; baseisarray = true; };
|
|
if (tk == nkind.N_TPTR) { elemt = tn.lhs; };
|
|
let sname: str;
|
|
sname.ptr = nil; sname.len = 0;
|
|
let viaptr: bool = false;
|
|
if (elemt != nil) {
|
|
if (elemt.kind == nkind.N_TPTR) {
|
|
let inner: *node = elemt.lhs;
|
|
if (inner != nil) { if (inner.kind == nkind.N_TNAME) {
|
|
sname = inner.str;
|
|
viaptr = true;
|
|
};};
|
|
} else { if (elemt.kind == nkind.N_TNAME) {
|
|
sname = elemt.str;
|
|
};};
|
|
};
|
|
if (sname.len > 0) {
|
|
let si: *structinfo = structlookup(c, sname);
|
|
if (si != nil) {
|
|
let fi: *fieldinfo = si.fields;
|
|
for (fi != nil) {
|
|
if (streq(fi.fname, fld)) {
|
|
let esz: i32 = elemsizeofc(c, tn);
|
|
cgexpr(c, lhs.rhs); // idx → AX
|
|
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), AX\n");
|
|
} else {
|
|
emitline("\tMOVQ\tBX, AX\n");
|
|
};
|
|
if (isstrtype(c, fi.tnode)) {
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg((fi.foff + 8): i64, "AX");
|
|
emitline(", BX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg(fi.foff: i64, "AX");
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
if (isfloattype(c, fi.tnode)) {
|
|
let mov: str = "MOVSD";
|
|
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\t");
|
|
emitdispreg(fi.foff: i64, "AX");
|
|
emitline(", X0\n");
|
|
return;
|
|
};
|
|
let lop: str = fieldloadop(c, fi);
|
|
emitline("\t");
|
|
emitline(lop);
|
|
emitline("\t");
|
|
emitdispreg(fi.foff: i64, "AX");
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
fi = fi.finext;
|
|
};
|
|
};
|
|
};
|
|
};};
|
|
};};
|
|
};
|
|
};
|
|
// Module-qualified value reference: `mod.name` where `mod`
|
|
// is nkind.N_IDENT bound as skind.SK_USE and the leaf isn't a local.
|
|
// Treat as a SB symbol — `MOVQ leaf(SB), AX` 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 == 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).
|
|
let frt: *node = fnretlookupmod(c, fld, lhs.str);
|
|
if (frt != nil) {
|
|
emitline("\tLEAQ\t");
|
|
emitfnname(c, fld, 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: *node = deflookuprhsmod(c, fld, lhs.str);
|
|
if (drhs != nil) {
|
|
if (drhs.kind == 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));
|
|
if (streq(mqop, "MOVQ")) {
|
|
emitline("\tMOVQ\t");
|
|
emitsymname(c, fld);
|
|
emitline("(SB), AX\n");
|
|
} else {
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, fld);
|
|
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 == nkind.N_DOT) {
|
|
let rootname: str = "";
|
|
let rootoff: i32 = 0;
|
|
let totaloff: i32 = 0;
|
|
let leaffi: *fieldinfo = nil;
|
|
let slicedelta: i32 = -1;
|
|
let isglobal: bool = false;
|
|
let ptrroot: bool = false;
|
|
let pok: bool = dotchainresolve(c, n,
|
|
&rootname, &rootoff, &totaloff,
|
|
&leaffi, &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) {
|
|
if (ptrroot) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(rootoff: i64);
|
|
emitline("(BP), CX\n");
|
|
} else {
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, rootname);
|
|
emitline("(SB), CX\n");
|
|
};
|
|
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;
|
|
};
|
|
if (isstrtype(c, leaffi.tnode)) {
|
|
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("\tMOVQ\t");
|
|
emitdispreg(totaloff: i64, "CX");
|
|
emitline(", AX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg((totaloff + 8): i64, "CX");
|
|
emitline(", BX\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");
|
|
};
|
|
return;
|
|
};
|
|
if (isslicetype(c, leaffi.tnode)) {
|
|
// Slice leaf: load all three header words into
|
|
// (AX=ptr, BX=len, CX=cap). 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
|
|
// don't alias so order is free.
|
|
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("\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 (isfloattype(c, leaffi.tnode)) {
|
|
let mov: str = "MOVSD";
|
|
if (isf32type(c, leaffi.tnode)) { mov = "MOVSS"; };
|
|
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(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 = fieldloadop(c, leaffi);
|
|
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(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`.
|
|
// Evaluate the str-producing expression — that leaves
|
|
// (AX=ptr, BX=len). Then `.ptr` returns AX as is; `.len`
|
|
// shuffles BX→AX. Mirrors what C cgen does (it just evaluates
|
|
// the literal and picks the half it wants).
|
|
if (streq(fld, "ptr")) { cgexpr(c, lhs); return; };
|
|
if (streq(fld, "len")) {
|
|
cgexpr(c, lhs);
|
|
emitline("\tMOVQ\tBX, AX\n");
|
|
return;
|
|
};
|
|
// Chained struct-field-via-ptr-via-ptr access:
|
|
// r.sym.val where r: *lrel, .sym: *lsym, .val: u64
|
|
// Inner DOT (`r.sym`) returns a *struct (a pointer-to-struct
|
|
// field). Outer DOT dereferences and reads `val`. Without this
|
|
// path the cgen falls through and AX retains whatever the
|
|
// inner expression left there — typically the *struct pointer
|
|
// itself, so reads silently get the pointer value instead of
|
|
// the field. (Showed up porting w6l/pass.ww.)
|
|
if (lhs != nil) {
|
|
if (lhs.kind == nkind.N_DOT) {
|
|
let innert: *node = dotinnerstructptr(c, lhs);
|
|
if (innert != nil) {
|
|
let sname: str = innert.str;
|
|
let si: *structinfo = structlookup(c, sname);
|
|
if (si != nil) {
|
|
let fi: *fieldinfo = si.fields;
|
|
for (fi != nil) {
|
|
if (streq(fi.fname, fld)) {
|
|
cgexpr(c, lhs); // AX = ptr to inner struct
|
|
// str field: load both halves.
|
|
if (isstrtype(c, fi.tnode)) {
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg((fi.foff + 8): i64, "AX");
|
|
emitline(", BX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg(fi.foff: i64, "AX");
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
// slice field: load (ptr, len, cap)
|
|
// into (AX, BX, CX). AX is the *struct
|
|
// base, so load .ptr (which targets
|
|
// AX) LAST.
|
|
if (isslicetype(c, fi.tnode)) {
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg((fi.foff + 8): i64, "AX");
|
|
emitline(", BX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg((fi.foff + 16): i64, "AX");
|
|
emitline(", CX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg(fi.foff: i64, "AX");
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
// f64/f32 chained field: route through X0.
|
|
if (isfloattype(c, fi.tnode)) {
|
|
let mov: str = "MOVSD";
|
|
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\t");
|
|
emitdispreg(fi.foff: i64, "AX");
|
|
emitline(", X0\n");
|
|
return;
|
|
};
|
|
let lop: str = fieldloadop(c, fi);
|
|
emitline("\t");
|
|
emitline(lop);
|
|
emitline("\t");
|
|
emitdispreg(fi.foff: i64, "AX");
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
fi = fi.finext;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// Chained `(ident).f1.f2` read where f1 is a struct-by-value
|
|
// field. Mirror of the cgassign branch added for the same shape.
|
|
// Without this, `L.cur.kind` (cur a by-value struct of *L)
|
|
// falls into the SB-fallback and emits `MOVQ kind(SB), AX`.
|
|
// 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 == nkind.N_DOT) {
|
|
let inner: *node = lhs.lhs;
|
|
let innerfld: str = lhs.str;
|
|
if (inner != nil) { if (inner.kind == nkind.N_IDENT) {
|
|
let lc: *local = localfindnode(c, inner.str);
|
|
if (lc != nil) { if (lc.tnode != nil) {
|
|
let tn: *node = lc.tnode;
|
|
let lkind: nkind = tn.kind;
|
|
let outname: str;
|
|
outname.ptr = nil; outname.len = 0;
|
|
let isptr: bool = false;
|
|
if (lkind == nkind.N_TNAME) { outname = tn.str; };
|
|
if (lkind == nkind.N_TPTR) {
|
|
let pe: *node = tn.lhs;
|
|
if (pe != nil) { if (pe.kind == nkind.N_TNAME) {
|
|
outname = pe.str;
|
|
isptr = true;
|
|
};};
|
|
};
|
|
if (outname.len > 0) {
|
|
let osi: *structinfo = structlookup(c, outname);
|
|
if (osi != nil) {
|
|
let ofi: *fieldinfo = osi.fields;
|
|
for (ofi != nil) {
|
|
if (streq(ofi.fname, innerfld)) {
|
|
let oft: *node = ofi.tnode;
|
|
if (oft != nil) { if (oft.kind == nkind.N_TNAME) {
|
|
if (primsize(oft.str) == 0) {
|
|
let isi: *structinfo = structlookup(c, oft.str);
|
|
if (isi != nil) {
|
|
let ffi: *fieldinfo = isi.fields;
|
|
for (ffi != nil) {
|
|
if (streq(ffi.fname, fld)) {
|
|
let totoff: i32 = ofi.foff + ffi.foff;
|
|
if (isstrtype(c, ffi.tnode)) {
|
|
if (isptr) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), CX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg((totoff + 8): i64, "CX");
|
|
emitline(", BX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg(totoff: i64, "CX");
|
|
emitline(", AX\n");
|
|
} else {
|
|
emitline("\tMOVQ\t");
|
|
emitoff((lc.off + totoff): i64);
|
|
emitline("(BP), AX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitoff((lc.off + totoff + 8): i64);
|
|
emitline("(BP), BX\n");
|
|
};
|
|
return;
|
|
};
|
|
if (isfloattype(c, ffi.tnode)) {
|
|
let mov: str = "MOVSD";
|
|
if (isf32type(c, ffi.tnode)) { mov = "MOVSS"; };
|
|
if (isptr) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), BX\n");
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\t");
|
|
emitdispreg(totoff: i64, "BX");
|
|
emitline(", X0\n");
|
|
} else {
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\t");
|
|
emitoff((lc.off + totoff): i64);
|
|
emitline("(BP), X0\n");
|
|
};
|
|
return;
|
|
};
|
|
let lop: str = fieldloadop(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 <leaf>(SB), AX` so the linker surfaces a
|
|
// clean undefined-symbol error on the leaf. Mirror of
|
|
// cmd/w6c/cgen.c N_DOT nested fallback.
|
|
if (lhs != nil) {
|
|
if (lhs.kind == nkind.N_DOT) {
|
|
emitline("\tMOVQ\t");
|
|
emitsymname(c, fld);
|
|
emitline("(SB), AX\n");
|
|
return;
|
|
};
|
|
};
|
|
return;
|
|
};
|
|
|
|
fn cgun(c: *cgen, n: *node) void = {
|
|
// Match C cgen ordering: evaluate operand first (load into AX),
|
|
// then apply the unary op. AMP / STAR override AX with the
|
|
// address / deref. The wasted load before AMP keeps our asm
|
|
// byte-identical to the C version.
|
|
let fk: i32 = exprfloatkind(c, n.lhs);
|
|
if (n.op == tkind.TK_MINUS && fk != 0) {
|
|
// Float negate: X0 = 0 - X0. Stash orig, load 0.0, subtract.
|
|
// Zero bit pattern equals 0.0 for both f32 and f64 so we
|
|
// reuse the integer-zero materialisation.
|
|
let mov: str = "MOVSD";
|
|
let sub: str = "SUBSD";
|
|
if (fk == 1) { mov = "MOVSS"; sub = "SUBSS"; };
|
|
cgexpr(c, n.lhs);
|
|
emitline("\tSUBQ\t$8, SP\n");
|
|
emitline("\t"); emitline(mov); emitline("\tX0, (SP)\n");
|
|
emitline("\tMOVQ\t$0, AX\n");
|
|
emitline("\tPUSHQ\tAX\n");
|
|
emitline("\t"); emitline(mov); emitline("\t(SP), X0\n");
|
|
emitline("\tADDQ\t$8, SP\n");
|
|
emitline("\t"); emitline(mov); emitline("\t(SP), X1\n");
|
|
emitline("\tADDQ\t$8, SP\n");
|
|
emitline("\t"); emitline(sub); emitline("\tX1, X0\n");
|
|
return;
|
|
};
|
|
// Address-of has its own evaluation strategy — we want the address
|
|
// of the operand, not its value. Special-case here so `&arr[i]`
|
|
// doesn't compile the value load and then discard it.
|
|
if (n.op == tkind.TK_AMP) {
|
|
let opnd: *node = n.lhs;
|
|
if (opnd != nil) {
|
|
if (opnd.kind == nkind.N_IDENT) {
|
|
let nm: str = opnd.str;
|
|
let off: i32 = localfind(c, nm);
|
|
if (off != 0) {
|
|
emitline("\tLEAQ\t");
|
|
emitoff(off: i64);
|
|
emitline("(BP), AX\n");
|
|
return;
|
|
};
|
|
if (isletvar(c, nm)) {
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, nm);
|
|
emitline("(SB), AX\n");
|
|
return;
|
|
};
|
|
return;
|
|
};
|
|
// 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 == 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 == nkind.N_DOT) {
|
|
let rootname: str = "";
|
|
let rootoff: i32 = 0;
|
|
let totaloff: i32 = 0;
|
|
let leaffi: *fieldinfo = nil;
|
|
let slicedelta: i32 = -1;
|
|
let isglobal: bool = false;
|
|
let ptrroot: bool = false;
|
|
let pok: bool = dotchainresolve(c, opnd,
|
|
&rootname, &rootoff, &totaloff,
|
|
&leaffi, &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 == 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: *node = lc.tnode;
|
|
let lkind: nkind = nkind.N_NONE;
|
|
if (tn != nil) { lkind = tn.kind; };
|
|
// Pointer-field: &p.f where p:*T.
|
|
if (lkind == nkind.N_TPTR) {
|
|
let inner: *node = tn.lhs;
|
|
let sname: str;
|
|
sname.ptr = nil; sname.len = 0;
|
|
if (inner != nil) {
|
|
if (inner.kind == nkind.N_TNAME) { sname = inner.str; };
|
|
};
|
|
if (sname.len > 0) {
|
|
let si: *structinfo = structlookup(c, sname);
|
|
if (si != nil) {
|
|
let fi: *fieldinfo = si.fields;
|
|
for (fi != nil) {
|
|
if (streq(fi.fname, fld)) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), AX\n");
|
|
emitline("\tLEAQ\t");
|
|
emitdispreg(fi.foff: i64, "AX");
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
fi = fi.finext;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// Value-struct local: &o.f.
|
|
if (lkind == nkind.N_TNAME) {
|
|
let sname: str = tn.str;
|
|
let si: *structinfo = structlookup(c, sname);
|
|
if (si != nil) {
|
|
let fi: *fieldinfo = si.fields;
|
|
for (fi != nil) {
|
|
if (streq(fi.fname, fld)) {
|
|
emitline("\tLEAQ\t");
|
|
emitoff((lc.off + fi.foff): i64);
|
|
emitline("(BP), AX\n");
|
|
return;
|
|
};
|
|
fi = fi.finext;
|
|
};
|
|
};
|
|
};
|
|
// 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 (streq(fld, "ptr")) { delta = 0; };
|
|
if (streq(fld, "len")) { delta = 8; };
|
|
if (streq(fld, "cap")) { delta = 16; };
|
|
if (delta >= 0) {
|
|
let isslor: bool = false;
|
|
if (lkind == nkind.N_TSLICE) { isslor = true; };
|
|
if (lkind == nkind.N_TNAME) {
|
|
if (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)) {
|
|
let gsi: *structinfo = letvarstructinfo(c, basenm);
|
|
if (gsi != nil) {
|
|
let fi: *fieldinfo = gsi.fields;
|
|
for (fi != nil) {
|
|
if (streq(fi.fname, fld)) {
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, basenm);
|
|
emitline("(SB), CX\n");
|
|
emitline("\tLEAQ\t");
|
|
emitdispreg(fi.foff: i64, "CX");
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
fi = fi.finext;
|
|
};
|
|
};
|
|
let isstr: bool = letvarisstr(c, basenm);
|
|
let issl: bool = letvarisslice(c, basenm);
|
|
if (isstr || issl) {
|
|
let gdelta: i32 = -1;
|
|
if (streq(fld, "ptr")) { gdelta = 0; };
|
|
if (streq(fld, "len")) { gdelta = 8; };
|
|
if (issl) { if (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;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// Fall through silently (mirrors cstage silent-
|
|
// drop fallback at the end of the TK_AMP block).
|
|
return;
|
|
};
|
|
if (opnd.kind == nkind.N_INDEX) {
|
|
// &base[i] = base + i*esz, no dereference.
|
|
let base: *node = opnd.lhs;
|
|
let idx: *node = opnd.rhs;
|
|
let esz: i32 = 8;
|
|
let isglobalarr: bool = false;
|
|
let isglobalptr: bool = false;
|
|
let globalname: str;
|
|
globalname.ptr = nil; globalname.len = 0;
|
|
let baselocal: *local = nil;
|
|
let isarr: bool = false;
|
|
if (base != nil) {
|
|
if (base.kind == nkind.N_IDENT) {
|
|
baselocal = localfindnode(c, base.str);
|
|
if (baselocal != nil) {
|
|
esz = elemsizeofc(c, baselocal.tnode);
|
|
let tn: *node = baselocal.tnode;
|
|
if (tn != nil) {
|
|
if (tn.kind == nkind.N_TARRAY) { isarr = true; };
|
|
};
|
|
} else {
|
|
let tn: *node = letvartnode(c, base.str);
|
|
if (tn != nil) {
|
|
if (tn.kind == nkind.N_TARRAY) {
|
|
isglobalarr = true;
|
|
globalname = base.str;
|
|
esz = elemsizeofc(c, tn);
|
|
};
|
|
if (tn.kind == nkind.N_TPTR) {
|
|
isglobalptr = true;
|
|
globalname = base.str;
|
|
esz = elemsizeofc(c, tn);
|
|
};
|
|
};
|
|
};
|
|
} else { if (base.kind == nkind.N_DOT) {
|
|
// `&p.ptr[i]` shape: stride is the element
|
|
// of the slice/struct-pointer field, not
|
|
// the default 8. Mirrors cgindex's N_DOT
|
|
// arm so &p.ptr[i] and p.ptr[i] agree.
|
|
esz = indexbaseesz(c, base);
|
|
};};
|
|
};
|
|
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).
|
|
emitline("\tPUSHQ\tAX\n");
|
|
cgexpr(c, base);
|
|
emitline("\tPOPQ\tBX\n");
|
|
};};};
|
|
emitline("\tADDQ\tBX, AX\n");
|
|
return;
|
|
};
|
|
};
|
|
return;
|
|
};
|
|
cgexpr(c, n.lhs);
|
|
if (n.op == tkind.TK_MINUS) { emitline("\tNEGQ\tAX\n"); return; };
|
|
if (n.op == tkind.TK_TILDE) {
|
|
emitline("\tNOTQ\tAX\n");
|
|
// 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 == tkind.TK_STAR) { emitline("\tMOVQ\t(AX), AX\n"); return; };
|
|
if (n.op == tkind.TK_NOT) {
|
|
let t: str = mklabel(c, "tt");
|
|
let e: str = mklabel(c, "te");
|
|
emitline("\tCMPQ\t$0, AX\n");
|
|
emitline("\tJE\t"); emitline(t); emitline("\n");
|
|
emitline("\tMOVQ\t$0, AX\n");
|
|
emitline("\tJMP\t"); emitline(e); emitline("\n");
|
|
emitlabel(t);
|
|
emitline("\tMOVQ\t$1, AX\n");
|
|
emitlabel(e);
|
|
return;
|
|
};
|
|
return;
|
|
};
|
|
|
|
fn cgbin(c: *cgen, n: *node) void = {
|
|
// 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 == tkind.TK_AND || n.op == tkind.TK_OR) {
|
|
let prefix: str = "andend";
|
|
let jshrt: str = "JE";
|
|
if (n.op == 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;
|
|
};
|
|
|
|
let unsignd: bool = nodeisunsigned(c, n.lhs);
|
|
if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); };
|
|
|
|
// Float arithmetic: both operands flow through X0. Spill rhs
|
|
// across the stack (SUBQ/MOVSD/MOVSD/ADDQ) since there's no
|
|
// general FP register saver. ADDSD/SUBSD/MULSD/DIVSD pick SS
|
|
// variants for f32. Comparison uses UCOMISD + JCC and falls
|
|
// out to the existing CMPQ-based path below.
|
|
let lfk: i32 = exprfloatkind(c, n.lhs);
|
|
let rfk: i32 = exprfloatkind(c, n.rhs);
|
|
let fk: i32 = lfk;
|
|
if (fk == 0) { fk = rfk; };
|
|
if (fk != 0) {
|
|
let mov: str = "MOVSD";
|
|
if (fk == 1) { mov = "MOVSS"; };
|
|
if (n.op == tkind.TK_PLUS ||
|
|
n.op == tkind.TK_MINUS ||
|
|
n.op == tkind.TK_STAR ||
|
|
n.op == tkind.TK_SLASH) {
|
|
cgexpr(c, n.rhs);
|
|
emitline("\tSUBQ\t$8, SP\n");
|
|
emitline("\t"); emitline(mov); emitline("\tX0, (SP)\n");
|
|
cgexpr(c, n.lhs);
|
|
emitline("\t"); emitline(mov); emitline("\t(SP), X1\n");
|
|
emitline("\tADDQ\t$8, SP\n");
|
|
let op: str = "ADDSD";
|
|
if (n.op == tkind.TK_MINUS) { op = "SUBSD"; };
|
|
if (n.op == tkind.TK_STAR) { op = "MULSD"; };
|
|
if (n.op == tkind.TK_SLASH) { op = "DIVSD"; };
|
|
if (fk == 1) {
|
|
if (n.op == tkind.TK_PLUS) { op = "ADDSS"; };
|
|
if (n.op == tkind.TK_MINUS) { op = "SUBSS"; };
|
|
if (n.op == tkind.TK_STAR) { op = "MULSS"; };
|
|
if (n.op == tkind.TK_SLASH) { op = "DIVSS"; };
|
|
};
|
|
emitline("\t"); emitline(op); emitline("\tX1, X0\n");
|
|
return;
|
|
};
|
|
let isfcmp: bool = false;
|
|
let jcc: str = "";
|
|
// UCOMISD/SS sets ZF/PF/CF; unordered (NaN) propagates as
|
|
// "not equal / not less". JA/JAE/JB/JBE keys off CF which
|
|
// matches the ordered comparisons we need.
|
|
if (n.op == tkind.TK_EQ) { isfcmp = true; jcc = "JE"; };
|
|
if (n.op == tkind.TK_NEQ) { isfcmp = true; jcc = "JNE"; };
|
|
if (n.op == tkind.TK_LT) { isfcmp = true; jcc = "JB"; };
|
|
if (n.op == tkind.TK_LE) { isfcmp = true; jcc = "JBE"; };
|
|
if (n.op == tkind.TK_GT) { isfcmp = true; jcc = "JA"; };
|
|
if (n.op == tkind.TK_GE) { isfcmp = true; jcc = "JAE"; };
|
|
if (isfcmp) {
|
|
cgexpr(c, n.rhs);
|
|
emitline("\tSUBQ\t$8, SP\n");
|
|
emitline("\t"); emitline(mov); emitline("\tX0, (SP)\n");
|
|
cgexpr(c, n.lhs);
|
|
emitline("\t"); emitline(mov); emitline("\t(SP), X1\n");
|
|
emitline("\tADDQ\t$8, SP\n");
|
|
let ucomi: str = "UCOMISD";
|
|
if (fk == 1) { ucomi = "UCOMISS"; };
|
|
emitline("\t"); emitline(ucomi); emitline("\tX1, X0\n");
|
|
let t: str = mklabel(c, "ct");
|
|
let e: str = mklabel(c, "ce");
|
|
emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n");
|
|
emitline("\tMOVQ\t$0, AX\n");
|
|
emitline("\tJMP\t"); emitline(e); emitline("\n");
|
|
emitlabel(t);
|
|
emitline("\tMOVQ\t$1, AX\n");
|
|
emitlabel(e);
|
|
return;
|
|
};
|
|
return;
|
|
};
|
|
|
|
cgexpr(c, n.rhs);
|
|
emitline("\tPUSHQ\tAX\n");
|
|
cgexpr(c, n.lhs);
|
|
emitline("\tPOPQ\tBX\n");
|
|
if (n.op == tkind.TK_PLUS) { emitline("\tADDQ\tBX, AX\n"); return; };
|
|
if (n.op == tkind.TK_MINUS) { emitline("\tSUBQ\tBX, AX\n"); return; };
|
|
if (n.op == tkind.TK_STAR) { emitline("\tIMULQ\tBX, AX\n"); return; };
|
|
if (n.op == tkind.TK_SLASH) {
|
|
// 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 == 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 == tkind.TK_AMP) { emitline("\tANDQ\tBX, AX\n"); return; };
|
|
if (n.op == tkind.TK_PIPE) { emitline("\tORQ\tBX, AX\n"); return; };
|
|
if (n.op == tkind.TK_CARET) { emitline("\tXORQ\tBX, AX\n"); return; };
|
|
if (n.op == tkind.TK_LSHIFT) {
|
|
emitline("\tMOVQ\tBX, CX\n");
|
|
emitline("\tSHLQ\tCX, AX\n");
|
|
return;
|
|
};
|
|
if (n.op == tkind.TK_RSHIFT) {
|
|
emitline("\tMOVQ\tBX, CX\n");
|
|
emitline("\tSHRQ\tCX, AX\n");
|
|
return;
|
|
};
|
|
// 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 == tkind.TK_EQ) { iscmp = true; jcc = "JE"; };
|
|
if (n.op == tkind.TK_NEQ) { iscmp = true; jcc = "JNE"; };
|
|
if (n.op == tkind.TK_LT) { iscmp = true; if (unsignd) { jcc = "JB"; } else { jcc = "JL"; }; };
|
|
if (n.op == tkind.TK_LE) { iscmp = true; if (unsignd) { jcc = "JBE"; } else { jcc = "JLE"; }; };
|
|
if (n.op == tkind.TK_GT) { iscmp = true; if (unsignd) { jcc = "JA"; } else { jcc = "JG"; }; };
|
|
if (n.op == tkind.TK_GE) { iscmp = true; if (unsignd) { jcc = "JAE"; } else { jcc = "JGE"; }; };
|
|
if (iscmp) {
|
|
let t: str = mklabel(c, "ct");
|
|
let e: str = mklabel(c, "ce");
|
|
emitline("\tCMPQ\tBX, AX\n");
|
|
emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n");
|
|
emitline("\tMOVQ\t$0, AX\n");
|
|
emitline("\tJMP\t"); emitline(e); emitline("\n");
|
|
emitlabel(t);
|
|
emitline("\tMOVQ\t$1, AX\n");
|
|
emitlabel(e);
|
|
return;
|
|
};
|
|
return;
|
|
};
|
|
|
|
// cgalloc — `alloc(value)` builtin lowering. Allocate sizeof(value)
|
|
// bytes via rt_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: *node) void = {
|
|
let v: *node = n.list;
|
|
let sz: i32 = 8;
|
|
let si: *structinfo = nil;
|
|
if (v.kind == nkind.N_STRUCTLIT) {
|
|
let trefn: *node = v.lhs;
|
|
let sname: str;
|
|
sname.ptr = nil; sname.len = 0;
|
|
if (trefn != nil) {
|
|
if (trefn.kind == nkind.N_IDENT) { sname = trefn.str; }
|
|
else { if (trefn.kind == nkind.N_TNAME) { sname = trefn.str; }; };
|
|
};
|
|
si = structlookup(c, sname);
|
|
if (si != nil) { sz = si.totsize; };
|
|
};
|
|
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(ffiresolve(c, "malloc"));
|
|
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 == nkind.N_STRUCTLIT) {
|
|
if (si != nil) {
|
|
let f: *node = v.list;
|
|
for (f != nil) {
|
|
if (f.kind == nkind.N_FIELD) {
|
|
let fname: str = f.str;
|
|
let fi: *fieldinfo = si.fields;
|
|
for (fi != nil) {
|
|
let fn_: str = fi.fname;
|
|
if (streq(fn_, fname)) {
|
|
cgexpr(c, f.lhs);
|
|
// alloc(T{ fval = v }) for f64/f32 field: cgexpr left
|
|
// the value in X0, not AX — route the store via MOVSD/MOVSS.
|
|
if (isfloattype(c, fi.tnode)) {
|
|
let mov: str = "MOVSD";
|
|
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
|
emitline("\tMOVQ\t(SP), BX\n");
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\tX0, ");
|
|
emitdispreg(fi.foff: i64, "BX");
|
|
emitline("\n");
|
|
fi = nil;
|
|
} else { if (isstrtype(c, fi.tnode)) {
|
|
// alloc(T{ fval = s }) for str field: cgexpr
|
|
// leaves (AX=ptr, BX=len). Use CX for the heap
|
|
// base so BX=len survives both stores. Mirrors
|
|
// cmd/w6c/cgen.c:4184-4190.
|
|
emitline("\tMOVQ\t(SP), CX\n");
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitdispreg(fi.foff: i64, "CX");
|
|
emitline("\n");
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitdispreg((fi.foff + 8): i64, "CX");
|
|
emitline("\n");
|
|
fi = nil;
|
|
} else {
|
|
emitline("\tMOVQ\t(SP), BX\n");
|
|
let sop: str = fieldstoreop(c, fi);
|
|
emitline("\t");
|
|
emitline(sop);
|
|
emitline("\tAX, ");
|
|
emitdispreg(fi.foff: i64, "BX");
|
|
emitline("\n");
|
|
fi = nil;
|
|
};};
|
|
} else {
|
|
fi = fi.finext;
|
|
};
|
|
};
|
|
};
|
|
f = f.next;
|
|
};
|
|
};
|
|
} else {
|
|
cgexpr(c, v);
|
|
emitline("\tMOVQ\t(SP), BX\n");
|
|
let sop: str = "MOVQ";
|
|
if (sz == 1) { sop = "MOVB"; }
|
|
else { if (sz == 4) { sop = "MOVL"; }; };
|
|
emitline("\t");
|
|
emitline(sop);
|
|
emitline("\tAX, (BX)\n");
|
|
};
|
|
emitline("\tPOPQ\tDX\n");
|
|
emitline("\tMOVQ\t$0, AX\n");
|
|
emitlabel(donel);
|
|
};
|
|
|
|
// cgappend — Hare-style `append(s, v)` / `append(s, items...)` lowering.
|
|
// Mirrors cmd/w6c/cgen.c's N_CALL append branch (rt::ensure model).
|
|
// Each value gets:
|
|
// ; cgexpr → AX
|
|
// ; PUSHQ AX
|
|
// ; ADDQ $1, s.len(BP)
|
|
// ; LEAQ s(BP), DI ; arg1 = &s
|
|
// ; MOVQ esz, SI ; arg2 = membsz
|
|
// ; CALL rt_ensure(SB)
|
|
// ; MOVQ s.len(BP), CX ; CX = new len
|
|
// ; SUBQ $1, CX ; CX = slot index
|
|
// ; [IMULQ esz, CX] ; byte offset (esz>1)
|
|
// ; MOVQ s.ptr(BP), BX
|
|
// ; ADDQ CX, BX
|
|
// ; POPQ AX
|
|
// ; MOV* AX, (BX) ; store (MOVB / MOVQ)
|
|
// nkind.N_SPREAD wraps the same body in a counted loop over items.len.
|
|
fn cgappend(c: *cgen, n: *node) void = {
|
|
let sn: *node = n.list;
|
|
if (sn == nil) { return; };
|
|
if (sn.kind != nkind.N_IDENT) { return; };
|
|
let snlocal: *local = localfindnode(c, sn.str);
|
|
if (snlocal == nil) { return; };
|
|
let sn_off: i32 = snlocal.off;
|
|
let esz: i32 = elemsizeof(snlocal.tnode);
|
|
let etnode: *node = nil;
|
|
if (snlocal.tnode != nil) {
|
|
let stk: nkind = snlocal.tnode.kind;
|
|
if (stk == nkind.N_TSLICE) { etnode = snlocal.tnode.lhs; };
|
|
if (stk == nkind.N_TARRAY) { etnode = snlocal.tnode.lhs; };
|
|
if (stk == nkind.N_TPTR) { etnode = snlocal.tnode.lhs; };
|
|
};
|
|
let store_op: str = tnodestoreop(c, etnode, esz);
|
|
|
|
let vn: *node = sn.next;
|
|
for (vn != nil) {
|
|
if (vn.kind == nkind.N_SPREAD) {
|
|
let it: *node = vn.lhs;
|
|
if (it == nil) { vn = vn.next; continue; };
|
|
if (it.kind != nkind.N_IDENT) { vn = vn.next; continue; };
|
|
let itlocal: *local = localfindnode(c, it.str);
|
|
if (itlocal == nil) { vn = vn.next; continue; };
|
|
let it_off: i32 = itlocal.off;
|
|
let load_op: str = tnodeloadop(c, etnode, 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");
|
|
emitline("\tMOVQ\t");
|
|
emitoff((it_off + 8): i64);
|
|
emitline("(BP), DX\n");
|
|
emitline("\tCMPQ\tDX, CX\n");
|
|
emitline("\tJGE\t"); emitline(le); emitline("\n");
|
|
emitline("\tMOVQ\t");
|
|
emitoff(it_off: i64);
|
|
emitline("(BP), BX\n");
|
|
if (esz > 1) {
|
|
emitline("\tMOVQ\t$");
|
|
emitint(esz: i64);
|
|
emitline(", AX\n");
|
|
emitline("\tIMULQ\tAX, CX\n");
|
|
};
|
|
emitline("\tADDQ\tCX, BX\n");
|
|
emitline("\t"); emitline(load_op); emitline("\t(BX), AX\n");
|
|
emitline("\tPUSHQ\tAX\n");
|
|
emitline("\tADDQ\t$1, ");
|
|
emitoff((sn_off + 8): i64);
|
|
emitline("(BP)\n");
|
|
emitline("\tLEAQ\t");
|
|
emitoff(sn_off: i64);
|
|
emitline("(BP), DI\n");
|
|
emitline("\tMOVQ\t$");
|
|
emitint(esz: i64);
|
|
emitline(", SI\n");
|
|
emitline("\tCALL\trt_ensure(SB)\n");
|
|
emitline("\tMOVQ\t");
|
|
emitoff((sn_off + 8): i64);
|
|
emitline("(BP), CX\n");
|
|
emitline("\tSUBQ\t$1, CX\n");
|
|
if (esz > 1) {
|
|
emitline("\tMOVQ\t$");
|
|
emitint(esz: i64);
|
|
emitline(", AX\n");
|
|
emitline("\tIMULQ\tAX, CX\n");
|
|
};
|
|
emitline("\tMOVQ\t");
|
|
emitoff(sn_off: i64);
|
|
emitline("(BP), BX\n");
|
|
emitline("\tADDQ\tCX, BX\n");
|
|
emitline("\tPOPQ\tAX\n");
|
|
emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n");
|
|
emitline("\tADDQ\t$1, (SP)\n");
|
|
emitline("\tJMP\t"); emitline(ll); emitline("\n");
|
|
emitlabel(le);
|
|
emitline("\tADDQ\t$8, SP\n");
|
|
vn = vn.next;
|
|
continue;
|
|
};
|
|
cgexpr(c, vn);
|
|
emitline("\tPUSHQ\tAX\n");
|
|
emitline("\tADDQ\t$1, ");
|
|
emitoff((sn_off + 8): i64);
|
|
emitline("(BP)\n");
|
|
emitline("\tLEAQ\t");
|
|
emitoff(sn_off: i64);
|
|
emitline("(BP), DI\n");
|
|
emitline("\tMOVQ\t$");
|
|
emitint(esz: i64);
|
|
emitline(", SI\n");
|
|
emitline("\tCALL\trt_ensure(SB)\n");
|
|
emitline("\tMOVQ\t");
|
|
emitoff((sn_off + 8): i64);
|
|
emitline("(BP), CX\n");
|
|
emitline("\tSUBQ\t$1, CX\n");
|
|
if (esz > 1) {
|
|
emitline("\tMOVQ\t$");
|
|
emitint(esz: i64);
|
|
emitline(", AX\n");
|
|
emitline("\tIMULQ\tAX, CX\n");
|
|
};
|
|
emitline("\tMOVQ\t");
|
|
emitoff(sn_off: i64);
|
|
emitline("(BP), BX\n");
|
|
emitline("\tADDQ\tCX, BX\n");
|
|
emitline("\tPOPQ\tAX\n");
|
|
emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n");
|
|
vn = vn.next;
|
|
};
|
|
return;
|
|
};
|
|
|
|
fn cgcall(c: *cgen, n: *node) void = {
|
|
// Hare-style `append(s, v)` / `append(s, items...)` builtin —
|
|
// special-cased before pushargsrev so the spread variant can run
|
|
// a counted loop over the items slice instead of a normal call.
|
|
let callee: *node = n.lhs;
|
|
if (callee != nil) {
|
|
if (callee.kind == nkind.N_IDENT) {
|
|
if (streq(callee.str, "append")) {
|
|
if (n.list != nil) {
|
|
if (n.list.next != nil) {
|
|
cgappend(c, n);
|
|
return;
|
|
};
|
|
};
|
|
};
|
|
// `alloc(value)` builtin: heap-init a fresh *T with the
|
|
// value's bytes. For struct literals, lower to rt_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 (streq(callee.str, "alloc")) {
|
|
if (n.list != nil) {
|
|
if (!samemodfn(c, "alloc")) {
|
|
cgalloc(c, n);
|
|
return;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
// Look up the callee's declared params for tagged-union widening.
|
|
// fn-pointer calls (callee is a local) don't get widening — the
|
|
// user must build the tagged value explicitly.
|
|
//
|
|
// 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: *node = nil;
|
|
if (callee != nil) {
|
|
if (callee.kind == nkind.N_IDENT) {
|
|
calleeparams = fnparamslookup(c, callee.str);
|
|
} else { if (callee.kind == nkind.N_DOT) {
|
|
let cmod: str;
|
|
cmod.ptr = nil; cmod.len = 0;
|
|
if (callee.lhs != nil) {
|
|
if (callee.lhs.kind == 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_<seq>`) plus a 24B slice
|
|
// descriptor (`@vararg_sl_<seq>`), 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. Per-call
|
|
// seq comes from c.varargseq bumped at gather emit (mirrors
|
|
// cstage's mklabel("vararg_d/sl") freshness).
|
|
{
|
|
let nfixed_v: i32 = 0;
|
|
let varp: *node = callee_variadic_param(c, callee, &nfixed_v);
|
|
if (varp != nil) {
|
|
let nargs0: i32 = 0;
|
|
let aw: *node = n.list;
|
|
for (aw != nil) { nargs0 += 1; aw = aw.next; };
|
|
let nvar: i32 = nargs0 - nfixed_v;
|
|
if (nvar < 0) { nvar = 0; };
|
|
let forwarding: bool = false;
|
|
if (nvar == 1) {
|
|
let aaf: *node = n.list;
|
|
let kk: i32 = 0;
|
|
for (kk < nfixed_v) {
|
|
aaf = aaf.next;
|
|
kk += 1;
|
|
};
|
|
if (aaf != nil) {
|
|
if (aaf.kind == nkind.N_SPREAD) {
|
|
forwarding = true;
|
|
};
|
|
};
|
|
};
|
|
if (forwarding) {
|
|
let prev: *node = nil;
|
|
let cur2: *node = n.list;
|
|
let kk2: i32 = 0;
|
|
for (kk2 < nfixed_v) {
|
|
prev = cur2;
|
|
cur2 = cur2.next;
|
|
kk2 += 1;
|
|
};
|
|
let inner: *node = cur2.lhs;
|
|
if (inner != nil) { inner.next = nil; };
|
|
if (prev == nil) { n.list = inner; }
|
|
else { prev.next = inner; };
|
|
} else {
|
|
let seq: i32 = c.varargseq;
|
|
c.varargseq += 1;
|
|
let dname: str = mkvarargname(c, "@vararg_d_", seq);
|
|
let sname: str = mkvarargname(c, "@vararg_sl_", seq);
|
|
// 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.
|
|
let esz: i32 = 8;
|
|
if (varp.lhs != nil) {
|
|
if (varp.lhs.kind == nkind.N_TNAME) {
|
|
let ps: i32 = primsize(varp.lhs.str);
|
|
if (ps > 0) { esz = ps; }
|
|
else { esz = slotsize(c, varp.lhs); };
|
|
} else {
|
|
esz = slotsize(c, varp.lhs);
|
|
};
|
|
};
|
|
if (esz < 1) { esz = 1; };
|
|
let velemtagged: bool = istaggedtype(c, varp.lhs);
|
|
let velemstr: bool = isstrtype(c, varp.lhs);
|
|
let velemslice: bool = isslicetype(c, varp.lhs);
|
|
let doff: i32 = 0;
|
|
if (nvar > 0) {
|
|
doff = localadd(c, dname, nvar * esz, nil);
|
|
};
|
|
// #60: vararg gather builds a {ptr,len,cap} slice
|
|
// descriptor — route through tyslicesize so #34's
|
|
// slice-header bump propagates here.
|
|
let soff: i32 = localadd(c, sname, tyslicesize(): i32,
|
|
slicewrap(c, varp.lhs));
|
|
let aa2: *node = n.list;
|
|
let kk3: i32 = 0;
|
|
for (kk3 < nfixed_v) {
|
|
aa2 = aa2.next;
|
|
kk3 += 1;
|
|
};
|
|
let j: i32 = 0;
|
|
let prevarg: *node = n.list;
|
|
if (nfixed_v == 0) { prevarg = nil; }
|
|
else {
|
|
let kk4: i32 = 0;
|
|
for (kk4 < nfixed_v - 1) {
|
|
prevarg = prevarg.next;
|
|
kk4 += 1;
|
|
};
|
|
};
|
|
for (aa2 != nil) {
|
|
let slot: i32 = doff + j * esz;
|
|
if (velemtagged) {
|
|
cgwidentaggedstore(c, varp.lhs,
|
|
aa2, "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: *node = newnode(nkind.N_IDENT,
|
|
"", 0, 0);
|
|
sn.str = sname;
|
|
if (prevarg == nil) { n.list = sn; }
|
|
else { prevarg.next = sn; };
|
|
};
|
|
};
|
|
};
|
|
let nargs: i32 = pushargsrev(c, n.list, calleeparams);
|
|
// 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;
|
|
if (sretcs > 0) {
|
|
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: *node = n.list;
|
|
let popped: i32 = 0;
|
|
let stackslots: i32 = 0;
|
|
for (a != nil) {
|
|
let fk: i32 = exprfloatkind(c, a);
|
|
if (fk != 0) {
|
|
let mov: str = "MOVSD";
|
|
if (fk == 1) { mov = "MOVSS"; };
|
|
if (fpidx < 8) {
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\t(SP), ");
|
|
emitline(fargregname(fpidx));
|
|
emitline("\n");
|
|
emitline("\tADDQ\t$8, SP\n");
|
|
fpidx += 1;
|
|
} else {
|
|
stackslots += 1;
|
|
};
|
|
popped += 1;
|
|
} else {
|
|
let extra: i32 = 0;
|
|
if (nodeisstr(c, a)) { extra = 1; };
|
|
if (nodeisslice(c, a)) { extra = 2; };
|
|
// #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; };
|
|
let words: i32 = 1 + extra;
|
|
let w: i32 = 0;
|
|
for (w < words) {
|
|
if (intidx < 6) {
|
|
emitline("\tPOPQ\t");
|
|
emitline(argregname(intidx));
|
|
emitline("\n");
|
|
intidx += 1;
|
|
} else {
|
|
stackslots += 1;
|
|
};
|
|
popped += 1;
|
|
w += 1;
|
|
};
|
|
};
|
|
a = a.next;
|
|
};
|
|
// Drain any remaining slots that the arg-walker didn't account
|
|
// for (tagged-union arg sizes > 8B, struct-by-value, etc.). The
|
|
// existing C cgen pops these into the int stream, so the worst
|
|
// case here is identical pre-port behaviour.
|
|
let i: i32 = popped;
|
|
for (i < nargs) {
|
|
if (intidx < 6) {
|
|
emitline("\tPOPQ\t");
|
|
emitline(argregname(intidx));
|
|
emitline("\n");
|
|
intidx += 1;
|
|
} else {
|
|
stackslots += 1;
|
|
};
|
|
i += 1;
|
|
};
|
|
// `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 == nkind.N_IDENT) {
|
|
let cn: str = callee.str;
|
|
if (localfindnode(c, cn) != nil) {
|
|
isfnptrcall = true;
|
|
};
|
|
};
|
|
if (callee.kind == nkind.N_DOT) {
|
|
let base: *node = callee.lhs;
|
|
let fld: str = callee.str;
|
|
if (base != nil) {
|
|
if (base.kind == nkind.N_IDENT) {
|
|
let bn: str = base.str;
|
|
let lc: *local = localfindnode(c, bn);
|
|
if (lc != nil) {
|
|
let tn: *node = lc.tnode;
|
|
if (tn != nil) {
|
|
let lkind: nkind = tn.kind;
|
|
let sname: str;
|
|
sname.ptr = nil; sname.len = 0;
|
|
if (lkind == nkind.N_TNAME) { sname = tn.str; };
|
|
if (lkind == nkind.N_TPTR) {
|
|
let inner: *node = tn.lhs;
|
|
if (inner != nil) {
|
|
if (inner.kind == nkind.N_TNAME) { sname = inner.str; };
|
|
};
|
|
};
|
|
if (sname.len > 0) {
|
|
let si: *structinfo = structlookup(c, sname);
|
|
if (si != nil) {
|
|
let fi: *fieldinfo = si.fields;
|
|
for (fi != nil) {
|
|
let fn_: str = fi.fname;
|
|
if (streq(fn_, fld)) {
|
|
let ft: *node = fi.tnode;
|
|
if (ft != nil) {
|
|
if (ft.kind == nkind.N_TFN) {
|
|
isfnptrcall = true;
|
|
};
|
|
};
|
|
fi = nil;
|
|
} else {
|
|
fi = fi.finext;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// 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 {
|
|
emitline("\tLEAQ\t");
|
|
emitoff(sretcalloff: i64);
|
|
emitline("(BP), DI\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 == 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 == 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 == nkind.N_IDENT) {
|
|
hint = 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");
|
|
};
|
|
// SysV returns 16-byte aggregates in (AX, DX). Our str
|
|
// convention is (AX, BX), so shuffle for str-returning calls.
|
|
// Route through fnretlookupmod: for N_DOT cross-module callees,
|
|
// the bare-leaf fnretlookup's same-module-first walk (#4e) would
|
|
// pick the caller-module's same-leaf fn — a str-returning
|
|
// caller-side `slice` over a []u8-returning `mod.slice` then
|
|
// emits a phantom MOVQ DX, BX after the cross-module CALL (#34).
|
|
if (calleename.len > 0) {
|
|
let cmod: str;
|
|
cmod.ptr = nil; cmod.len = 0;
|
|
if (callee != nil) {
|
|
if (callee.kind == nkind.N_IDENT) { cmod = c.curmod; };
|
|
if (callee.kind == nkind.N_DOT) {
|
|
if (callee.lhs != nil) {
|
|
if (callee.lhs.kind == nkind.N_IDENT) {
|
|
cmod = callee.lhs.str;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
let rtyp: *node = fnretlookupmod(c, calleename, cmod);
|
|
if (isstrtype(c, rtyp)) {
|
|
emitline("\tMOVQ\tDX, BX\n");
|
|
};
|
|
};
|
|
return;
|
|
};
|
|
|
|
fn cgassign(c: *cgen, n: *node) void = {
|
|
let lhs: *node = n.lhs;
|
|
// Discard lvalue `_ = expr;` — evaluate rhs for side effects,
|
|
// write nothing. Detected by lhs being an nkind.N_IDENT with empty str
|
|
// (planted by parseprimary on the tkind.TK_UNDER token).
|
|
if (lhs != nil) {
|
|
if (lhs.kind == nkind.N_IDENT) {
|
|
if (lhs.str.len == 0) {
|
|
if (n.op == tkind.TK_ASSIGN) {
|
|
cgexpr(c, n.rhs);
|
|
return;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// Tagged-union local reassignment: `r = expr;` where r has a
|
|
// tagged-union type. Delegate to cgwidentaggedstore (same path
|
|
// as cglet's tagged-init). Covers nullable fold, tagged source,
|
|
// struct payload, str payload, scalar payload, with tag remap.
|
|
if (lhs != nil) {
|
|
if (lhs.kind == nkind.N_IDENT) {
|
|
if (n.op == tkind.TK_ASSIGN) {
|
|
let lc: *local = localfindnode(c, lhs.str);
|
|
if (lc != nil) {
|
|
if (istaggedtype(c, lc.tnode)) {
|
|
let lsz: i32 = slotsize(c, lc.tnode);
|
|
cgwidentaggedstore(c, lc.tnode,
|
|
n.rhs, "BP", lc.off, lsz);
|
|
return;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// `*p = v` — deref-assign. Element width comes from the
|
|
// pointer's declared type. Mirrors C cgen: eval rhs (AX,
|
|
// and BX if str), push, eval pointer, pop value, store.
|
|
// We default to MOVQ (8B) since most fixtures use it; for
|
|
// `*bool` / `*u8` / `*i32` we narrow via the local's tnode.
|
|
if (lhs != nil) {
|
|
if (lhs.kind == nkind.N_UN) {
|
|
if (lhs.op == tkind.TK_STAR) {
|
|
if (n.op == tkind.TK_ASSIGN) {
|
|
let inner: *node = lhs.lhs;
|
|
let elemstr: bool = false;
|
|
let elemfloat: bool = false;
|
|
let elemf32: bool = false;
|
|
let storeop: str = "MOVQ";
|
|
if (inner != nil) {
|
|
if (inner.kind == nkind.N_IDENT) {
|
|
let lc: *local = localfindnode(c, inner.str);
|
|
if (lc != nil) {
|
|
let tn: *node = lc.tnode;
|
|
if (tn != nil) {
|
|
if (tn.kind == nkind.N_TPTR) {
|
|
let pe: *node = tn.lhs;
|
|
if (pe != nil) {
|
|
if (pe.kind == nkind.N_TNAME) {
|
|
if (streq(pe.str, "str")) { elemstr = true; }
|
|
else { if (streq(pe.str, "f64")) { elemfloat = true; }
|
|
else { if (streq(pe.str, "f32")) { elemfloat = true; elemf32 = true; }
|
|
else {
|
|
let ps: i32 = primsize(pe.str);
|
|
if (ps == 1) { storeop = "MOVB"; }
|
|
else { if (ps == 4) { storeop = "MOVL"; }; };
|
|
}; }; };
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
cgexpr(c, n.rhs);
|
|
// `*p = v` for *f64 / *f32: value sits in X0. Spill
|
|
// to the stack, evaluate the pointer (clobbers AX),
|
|
// then reload X0 and MOVSD/MOVSS through the pointer.
|
|
if (elemfloat) {
|
|
let mov: str = "MOVSD";
|
|
if (elemf32) { mov = "MOVSS"; };
|
|
emitline("\tSUBQ\t$8, SP\n");
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\tX0, (SP)\n");
|
|
cgexpr(c, inner);
|
|
emitline("\tMOVQ\tAX, BX\n");
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\t(SP), X0\n");
|
|
emitline("\tADDQ\t$8, SP\n");
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\tX0, (BX)\n");
|
|
return;
|
|
};
|
|
// Push order matches C cgen
|
|
// (cmd/w6c/cgen.c:1033-1041): PUSHQ AX
|
|
// (ptr) first, then PUSHQ BX (len) if
|
|
// str, so the pop sequence is POP CX
|
|
// (len) → POP AX (ptr) → MOVQ AX,
|
|
// (BX) → MOVQ CX, 8(BX).
|
|
emitline("\tPUSHQ\tAX\n");
|
|
if (elemstr) { emitline("\tPUSHQ\tBX\n"); };
|
|
cgexpr(c, inner);
|
|
emitline("\tMOVQ\tAX, BX\n");
|
|
if (elemstr) {
|
|
emitline("\tPOPQ\tCX\n");
|
|
emitline("\tPOPQ\tAX\n");
|
|
emitline("\tMOVQ\tAX, (BX)\n");
|
|
emitline("\tMOVQ\tCX, 8(BX)\n");
|
|
return;
|
|
};
|
|
emitline("\tPOPQ\tAX\n");
|
|
emitline("\t");
|
|
emitline(storeop);
|
|
emitline("\tAX, (BX)\n");
|
|
return;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// `*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 == nkind.N_UN) {
|
|
if (lhs.op == tkind.TK_STAR) {
|
|
if (n.op != tkind.TK_ASSIGN) {
|
|
let inner: *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: *node = nil;
|
|
if (inner != nil) {
|
|
if (inner.kind == nkind.N_IDENT) {
|
|
let lc: *local = localfindnode(c, inner.str);
|
|
if (lc != nil) {
|
|
let tn: *node = lc.tnode;
|
|
if (tn != nil) {
|
|
if (tn.kind == nkind.N_TPTR) {
|
|
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).
|
|
if (n.op == tkind.TK_SLASHEQ || n.op == tkind.TK_PERCENTEQ) {
|
|
let unsignd: bool = typenodeisunsignedc(c, pe);
|
|
if (!unsignd) {
|
|
unsignd = nodeisunsigned(c, n.rhs);
|
|
};
|
|
if (unsignd) {
|
|
emitline("\tMOVQ\t$0, DX\n");
|
|
emitline("\tDIVQ\tCX\n");
|
|
} else {
|
|
emitline("\tCQO\n");
|
|
emitline("\tIDIVQ\tCX\n");
|
|
};
|
|
if (n.op == tkind.TK_PERCENTEQ) {
|
|
emitline("\tMOVQ\tDX, AX\n");
|
|
};
|
|
emitline("\t");
|
|
emitline(storeop);
|
|
emitline("\tAX, (BX)\n");
|
|
return;
|
|
};
|
|
let combineop: str = "MOVQ";
|
|
if (n.op == tkind.TK_PLUSEQ) { combineop = "ADDQ"; }
|
|
else { if (n.op == tkind.TK_MINUSEQ) { combineop = "SUBQ"; }
|
|
else { if (n.op == tkind.TK_STAREQ) { combineop = "IMULQ"; }
|
|
else { if (n.op == tkind.TK_AMPEQ) { combineop = "ANDQ"; }
|
|
else { if (n.op == tkind.TK_PIPEEQ) { combineop = "ORQ"; }
|
|
else { if (n.op == tkind.TK_CARETEQ) { combineop = "XORQ"; }
|
|
else { if (n.op == tkind.TK_LSHIFTEQ) { combineop = "SHLQ"; }
|
|
else { if (n.op == tkind.TK_RSHIFTEQ) { combineop = "SHRQ"; };
|
|
}; }; }; }; }; }; };
|
|
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 == nkind.N_INDEX) {
|
|
if (n.op == tkind.TK_ASSIGN) {
|
|
let base: *node = lhs.lhs;
|
|
let idx: *node = lhs.rhs;
|
|
let esz: i32 = 8;
|
|
let baselocal: *local = nil;
|
|
let isglobalarr: bool = false;
|
|
let isglobalptr: bool = false;
|
|
let globalname: str;
|
|
globalname.ptr = nil; globalname.len = 0;
|
|
let elemtn: *node = nil;
|
|
if (base != nil) {
|
|
if (base.kind == nkind.N_IDENT) {
|
|
let bn: str = base.str;
|
|
baselocal = localfindnode(c, bn);
|
|
if (baselocal != nil) {
|
|
esz = elemsizeofc(c, baselocal.tnode);
|
|
let btn: *node = baselocal.tnode;
|
|
if (btn != nil) {
|
|
let bk: nkind = btn.kind;
|
|
if (bk == nkind.N_TARRAY) { elemtn = btn.lhs; };
|
|
if (bk == nkind.N_TSLICE) { elemtn = btn.lhs; };
|
|
if (bk == nkind.N_TPTR) { elemtn = btn.lhs; };
|
|
};
|
|
} else {
|
|
let tn: *node = letvartnode(c, bn);
|
|
if (tn != nil) {
|
|
if (tn.kind == nkind.N_TARRAY) {
|
|
isglobalarr = true;
|
|
globalname = bn;
|
|
esz = elemsizeofc(c, tn);
|
|
elemtn = tn.lhs;
|
|
};
|
|
if (tn.kind == nkind.N_TPTR) {
|
|
isglobalptr = true;
|
|
globalname = bn;
|
|
esz = elemsizeofc(c, tn);
|
|
elemtn = tn.lhs;
|
|
};
|
|
};
|
|
};
|
|
} else { if (base.kind == nkind.N_DOT) {
|
|
esz = indexbaseesz(c, base);
|
|
// Without this, the tagged-element gate
|
|
// below (keyed on elemtn) misses for
|
|
// `obj.arr[i] = v` over an [N]Tagged field
|
|
// and the store falls through to scalar —
|
|
// task #30, sister of the cgindex N_INDEX-
|
|
// base fix #24. indexvaluetnode now handles
|
|
// N_DOT base, so the element type drops out
|
|
// of the same helper.
|
|
let bt: *node = indexvaluetnode(c, lhs);
|
|
if (bt != nil) { elemtn = bt; };
|
|
} else { if (base.kind == nkind.N_INDEX) {
|
|
// Chained-write write-side parallel of the
|
|
// cgindex N_INDEX-base arm graduated in #24:
|
|
// `names[i][k] = v` (names: **u8) — outer
|
|
// base is the inner N_INDEX whose value-type
|
|
// is *u8, so the outer element is u8 and
|
|
// the store is MOVB, not MOVQ.
|
|
let bt: *node = indexvaluetnode(c, base);
|
|
if (bt != nil) {
|
|
esz = elemsizeofc(c, bt);
|
|
let bk2: nkind = bt.kind;
|
|
if (bk2 == nkind.N_TPTR) { elemtn = bt.lhs; };
|
|
if (bk2 == nkind.N_TSLICE) { elemtn = bt.lhs; };
|
|
if (bk2 == nkind.N_TARRAY) { elemtn = bt.lhs; };
|
|
};
|
|
};};};
|
|
};
|
|
// Tagged-union element: materialize source in a shared
|
|
// scratch slot via cgwidentaggedstore (handles struct /
|
|
// str / scalar / subset / nullable variants uniformly),
|
|
// then compute &arr[i] and byte-copy. The scratch
|
|
// (@tagscr) is reused across all tagged-arr stores in
|
|
// the function; first-use sizes the slot (#15/#26c).
|
|
if (elemtn != nil) {
|
|
if (istaggedtype(c, elemtn)) {
|
|
let slot_sz: i32 = slotsize(c, elemtn);
|
|
let scroff: i32 = localadd(c, "@tagscr",
|
|
slot_sz, nil);
|
|
// Pre-zero scratch (matches push helper).
|
|
emitline("\tXORQ\tAX, AX\n");
|
|
let zz: i32 = 0;
|
|
for (zz < slot_sz) {
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitoff((scroff + zz): i64);
|
|
emitline("(BP)\n");
|
|
zz += 8;
|
|
};
|
|
cgwidentaggedstore(c, elemtn, n.rhs,
|
|
"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: *node = baselocal.tnode;
|
|
let isarr: bool = false;
|
|
if (tn != nil) {
|
|
if (tn.kind == nkind.N_TARRAY) {
|
|
isarr = true;
|
|
};
|
|
};
|
|
if (isarr) {
|
|
emitline("\tLEAQ\t");
|
|
emitoff(baselocal.off: i64);
|
|
emitline("(BP), BX\n");
|
|
} else {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(baselocal.off: i64);
|
|
emitline("(BP), BX\n");
|
|
};
|
|
} else {
|
|
emitline("\tPUSHQ\tAX\n");
|
|
cgexpr(c, base);
|
|
emitline("\tMOVQ\tAX, BX\n");
|
|
emitline("\tPOPQ\tAX\n");
|
|
};};};
|
|
emitline("\tADDQ\tAX, BX\n");
|
|
let cc: i32 = 0;
|
|
for (cc < slot_sz) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff((scroff + cc): i64);
|
|
emitline("(BP), AX\n");
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitoff(cc: i64);
|
|
emitline("(BX)\n");
|
|
cc += 8;
|
|
};
|
|
return;
|
|
};
|
|
};
|
|
cgexpr(c, n.rhs); // value → AX
|
|
// #43: spill BX (str.len) before computing
|
|
// the index so the post-index store can pop
|
|
// it; the stride gate tracks ty_str.size.
|
|
if (esz == primtypesize("str"): i32) { emitline("\tPUSHQ\tBX\n"); };
|
|
emitline("\tPUSHQ\tAX\n");
|
|
cgexpr(c, idx); // idx → AX
|
|
if (esz > 1) {
|
|
emitline("\tMOVQ\t$");
|
|
emitint(esz: i64);
|
|
emitline(", CX\n");
|
|
emitline("\tIMULQ\tCX, AX\n");
|
|
};
|
|
emitline("\tPUSHQ\tAX\n"); // scaled idx
|
|
if (isglobalarr) {
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, globalname);
|
|
emitline("(SB), BX\n");
|
|
} else { if (isglobalptr) {
|
|
emitline("\tMOVQ\t");
|
|
emitsymname(c, globalname);
|
|
emitline("(SB), BX\n");
|
|
} else { if (baselocal != nil) {
|
|
let tn: *node = baselocal.tnode;
|
|
let isarray: bool = false;
|
|
if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isarray = true; }; };
|
|
if (isarray) {
|
|
emitline("\tLEAQ\t");
|
|
emitoff(baselocal.off: i64);
|
|
emitline("(BP), BX\n");
|
|
} else {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(baselocal.off: i64);
|
|
emitline("(BP), BX\n");
|
|
};
|
|
} else {
|
|
cgexpr(c, base);
|
|
emitline("\tMOVQ\tAX, BX\n");
|
|
};};};
|
|
emitline("\tPOPQ\tAX\n"); // scaled idx
|
|
emitline("\tADDQ\tAX, BX\n");
|
|
emitline("\tPOPQ\tAX\n"); // value
|
|
// #43: str-element write — pop the saved
|
|
// .len and store both halves. Stride gate
|
|
// routes through primtypesize for #1.
|
|
if (esz == primtypesize("str"): i32) {
|
|
emitline("\tMOVQ\tAX, (BX)\n");
|
|
emitline("\tPOPQ\tCX\n");
|
|
emitline("\tMOVQ\tCX, 8(BX)\n");
|
|
return;
|
|
};
|
|
let isop: str = tnodestoreop(c, elemtn, esz);
|
|
emitline("\t");
|
|
emitline(isop);
|
|
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 == nkind.N_DOT && lhs.lhs != nil
|
|
&& lhs.lhs.kind == nkind.N_INDEX) {
|
|
let idxbase: *node = lhs.lhs.lhs;
|
|
let idx: *node = lhs.lhs.rhs;
|
|
let fld2: str = lhs.str;
|
|
if (idxbase != nil) { if (idxbase.kind == nkind.N_IDENT) {
|
|
if (idx != nil) {
|
|
let lc: *local = localfindnode(c, idxbase.str);
|
|
if (lc != nil) { if (lc.tnode != nil) {
|
|
let tn: *node = lc.tnode;
|
|
let elemt: *node = nil;
|
|
let baseisarray: bool = false;
|
|
let tk: nkind = tn.kind;
|
|
if (tk == nkind.N_TSLICE) { elemt = tn.lhs; };
|
|
if (tk == nkind.N_TARRAY) { elemt = tn.lhs; baseisarray = true; };
|
|
if (tk == nkind.N_TPTR) { elemt = tn.lhs; };
|
|
let sname: str;
|
|
sname.ptr = nil; sname.len = 0;
|
|
let viaptr: bool = false;
|
|
if (elemt != nil) {
|
|
if (elemt.kind == nkind.N_TPTR) {
|
|
let inner: *node = elemt.lhs;
|
|
if (inner != nil) { if (inner.kind == nkind.N_TNAME) {
|
|
sname = inner.str;
|
|
viaptr = true;
|
|
};};
|
|
} else { if (elemt.kind == nkind.N_TNAME) {
|
|
sname = elemt.str;
|
|
};};
|
|
};
|
|
if (sname.len > 0) {
|
|
let si: *structinfo = structlookup(c, sname);
|
|
if (si != nil) {
|
|
let fi: *fieldinfo = si.fields;
|
|
for (fi != nil) {
|
|
if (streq(fi.fname, 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 == 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 rhs: AX=ptr, BX=len. Stash both,
|
|
// compute addr in CX so the pop pair
|
|
// restores AX/BX intact.
|
|
if (isstrtype(c, fi.tnode)) {
|
|
cgexpr(c, n.rhs);
|
|
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), CX\n");
|
|
} else {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), CX\n");
|
|
};
|
|
emitline("\tADDQ\tAX, CX\n");
|
|
if (viaptr) { emitline("\tMOVQ\t(CX), CX\n"); };
|
|
emitline("\tPOPQ\tAX\n");
|
|
emitline("\tPOPQ\tBX\n");
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitdispreg(fi.foff: i64, "CX");
|
|
emitline("\n");
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitdispreg((fi.foff + 8): i64, "CX");
|
|
emitline("\n");
|
|
return;
|
|
};
|
|
// 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. Float/str
|
|
// compound not wired.
|
|
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");
|
|
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tCX, AX\n"); };
|
|
if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tCX, AX\n"); };
|
|
if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tCX, AX\n"); };
|
|
if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tCX, AX\n"); };
|
|
if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tCX, AX\n"); };
|
|
if (n.op == tkind.TK_CARETEQ) { emitline("\tXORQ\tCX, AX\n"); };
|
|
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`. v1 scope: bare-IDENT inner.
|
|
if (lhs != nil) {
|
|
if (lhs.kind == nkind.N_DOT) {
|
|
let base: *node = lhs.lhs;
|
|
let fld: str = lhs.str;
|
|
if (base != nil) {
|
|
if (base.kind == nkind.N_UN) {
|
|
if (base.op == tkind.TK_STAR) {
|
|
if (base.lhs != nil) {
|
|
if (base.lhs.kind == nkind.N_IDENT) {
|
|
base = base.lhs;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
if (base.kind == nkind.N_IDENT) {
|
|
let bn: str = base.str;
|
|
let lc: *local = localfindnode(c, bn);
|
|
if (lc != nil) {
|
|
let tn: *node = lc.tnode;
|
|
let lkind: nkind = nkind.N_NONE;
|
|
if (tn != nil) { lkind = tn.kind; };
|
|
// Pointer-to-struct: deref then store.
|
|
if (lkind == nkind.N_TPTR) {
|
|
let inner: *node = tn.lhs;
|
|
let sname: str;
|
|
sname.ptr = nil; sname.len = 0;
|
|
if (inner != nil) {
|
|
if (inner.kind == nkind.N_TNAME) { sname = inner.str; };
|
|
};
|
|
if (sname.len > 0) {
|
|
// structlookupchain (#22) handles the
|
|
// alias-chain miss; same shape as the
|
|
// cgdot pointer-to-struct read site.
|
|
let si: *structinfo = structlookupchain(c, inner);
|
|
if (si != nil) {
|
|
let fi: *fieldinfo = si.fields;
|
|
for (fi != nil) {
|
|
let fn_: str = fi.fname;
|
|
if (streq(fn_, fld)) {
|
|
// 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 == tkind.TK_ASSIGN
|
|
&& istaggedtype(c, fi.tnode)) {
|
|
let fsz: i32 = slotsize(c, fi.tnode);
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), BX\n");
|
|
cgwidentaggedstore(c, fi.tnode,
|
|
n.rhs, "BX", fi.foff, fsz);
|
|
return;
|
|
};
|
|
// 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 natural struct size.
|
|
// N_STRUCTLIT: field-walk; reload BX before
|
|
// each store so cgexpr can clobber AX/BX.
|
|
// si.totsize is slot-padded; use
|
|
// structnaturalsize for the type-size query.
|
|
if (n.op == tkind.TK_ASSIGN
|
|
&& n.rhs != nil
|
|
&& n.rhs.kind == nkind.N_CALL
|
|
&& fi.tnode != nil
|
|
&& fi.tnode.kind == nkind.N_TNAME
|
|
&& primsize(fi.tnode.str) == 0) {
|
|
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
|
if (ssi != nil) {
|
|
let ssz: i32 = structnaturalsize(ssi);
|
|
if (ssz <= 24) {
|
|
let tlm: i32 = ssz - (ssz / 8) * 8;
|
|
if (tlm == 0 || tlm == 1
|
|
|| tlm == 2 || tlm == 4) {
|
|
cgexpr(c, n.rhs);
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), BX\n");
|
|
let full: i32 = ssz / 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((fi.foff + i * 8): i64, "BX");
|
|
emitline("\n");
|
|
i += 1;
|
|
};
|
|
if (tlm > 0) {
|
|
let top: str = "MOVB";
|
|
if (tlm == 4) { top = "MOVL"; };
|
|
if (tlm == 2) { top = "MOVW"; };
|
|
let treg: str = "AX";
|
|
if (full == 1) { treg = "DX"; };
|
|
if (full == 2) { treg = "CX"; };
|
|
emitline("\t");
|
|
emitline(top);
|
|
emitline("\t");
|
|
emitline(treg);
|
|
emitline(", ");
|
|
emitdispreg((fi.foff + full * 8): i64, "BX");
|
|
emitline("\n");
|
|
};
|
|
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 == tkind.TK_ASSIGN
|
|
&& n.rhs != nil
|
|
&& n.rhs.kind == nkind.N_STRUCTLIT
|
|
&& fi.tnode != nil
|
|
&& fi.tnode.kind == nkind.N_TNAME
|
|
&& primsize(fi.tnode.str) == 0) {
|
|
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
|
if (ssi != nil) {
|
|
let ssz: i32 = structnaturalsize(ssi);
|
|
cgstructlitfill(c, ssi, n.rhs, 1, lc.off, "",
|
|
fi.foff, ssz);
|
|
return;
|
|
};
|
|
};
|
|
if (n.op == tkind.TK_ASSIGN
|
|
&& n.rhs != nil
|
|
&& n.rhs.kind == nkind.N_IDENT
|
|
&& fi.tnode != nil
|
|
&& fi.tnode.kind == nkind.N_TNAME
|
|
&& primsize(fi.tnode.str) == 0) {
|
|
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
|
let srhs: *local = localfindnode(c, n.rhs.str);
|
|
if (ssi != nil) { if (srhs != nil) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), BX\n");
|
|
let ssz: i32 = ssi.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((fi.foff + k): i64, "BX");
|
|
emitline("\n");
|
|
k += 8;
|
|
};
|
|
if (k < ssz) {
|
|
let tail: i32 = ssz - k;
|
|
let lop: str = "MOVQ";
|
|
if (tail == 4) { lop = "MOVL"; }
|
|
else { if (tail == 1) { lop = "MOVB"; }; };
|
|
emitline("\t");
|
|
emitline(lop);
|
|
emitline("\t");
|
|
emitoff((srhs.off + k): i64);
|
|
emitline("(BP), AX\n");
|
|
emitline("\t");
|
|
emitline(lop);
|
|
emitline("\tAX, ");
|
|
emitdispreg((fi.foff + k): i64, "BX");
|
|
emitline("\n");
|
|
};
|
|
return;
|
|
};};
|
|
};
|
|
if (n.op != tkind.TK_ASSIGN) {
|
|
// compound: load current value
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), BX\n");
|
|
let lop: str = fieldloadop(c, fi);
|
|
emitline("\t");
|
|
emitline(lop);
|
|
emitline("\t");
|
|
emitdispreg(fi.foff: i64, "BX");
|
|
emitline(", BX\n");
|
|
emitline("\tPUSHQ\tBX\n");
|
|
};
|
|
cgexpr(c, n.rhs);
|
|
if (n.op != tkind.TK_ASSIGN) {
|
|
emitline("\tPOPQ\tBX\n");
|
|
// PLUSEQ is commutative; MINUSEQ
|
|
// needs lhs - rhs (BX is old lhs,
|
|
// AX is rhs).
|
|
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); };
|
|
if (n.op == tkind.TK_MINUSEQ) {
|
|
emitline("\tSUBQ\tAX, BX\n");
|
|
emitline("\tMOVQ\tBX, AX\n");
|
|
};
|
|
};
|
|
// str field via *struct: rhs left
|
|
// (AX=ptr, BX=len). Use CX as the
|
|
// address scratch so we don't clobber
|
|
// the len half before storing it.
|
|
if (n.op == tkind.TK_ASSIGN) {
|
|
if (isstrtype(c, fi.tnode)) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), CX\n");
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitdispreg(fi.foff: i64, "CX");
|
|
emitline("\n");
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitdispreg((fi.foff + 8): i64, "CX");
|
|
emitline("\n");
|
|
return;
|
|
};
|
|
// slice field via *struct: rhs left
|
|
// (AX=ptr, BX=len, CX=cap). CX is
|
|
// taken, so stage the struct addr
|
|
// in DX. Store all three words at
|
|
// foff/+8/+16.
|
|
if (isslicetype(c, fi.tnode)) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), DX\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;
|
|
};
|
|
// f64/f32 plain `=` via *struct: cgexpr left the
|
|
// value in X0. Reload struct ptr and MOVSD/MOVSS.
|
|
if (isfloattype(c, fi.tnode)) {
|
|
let mov: str = "MOVSD";
|
|
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), BX\n");
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\tX0, ");
|
|
emitdispreg(fi.foff: i64, "BX");
|
|
emitline("\n");
|
|
return;
|
|
};
|
|
};
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), BX\n");
|
|
let sop: str = fieldstoreop(c, fi);
|
|
emitline("\t");
|
|
emitline(sop);
|
|
emitline("\tAX, ");
|
|
emitdispreg(fi.foff: i64, "BX");
|
|
emitline("\n");
|
|
return;
|
|
};
|
|
fi = fi.finext;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// Direct struct local: store at off+foff.
|
|
if (lkind == nkind.N_TNAME) {
|
|
// structlookupchain (#22) — same shape
|
|
// as the cgdot direct-local read site.
|
|
let si: *structinfo = structlookupchain(c, tn);
|
|
if (si != nil) {
|
|
let fi: *fieldinfo = si.fields;
|
|
for (fi != nil) {
|
|
let fn_: str = fi.fname;
|
|
if (streq(fn_, fld)) {
|
|
// Tagged-union field in a direct struct local —
|
|
// full slot rewrite at (lc.off + fi.foff)(BP)
|
|
// via cgwidentaggedstore basereg="BP". Pre-#26
|
|
// fell through and dropped tag + payload.
|
|
if (n.op == tkind.TK_ASSIGN
|
|
&& istaggedtype(c, fi.tnode)) {
|
|
let fsz: i32 = slotsize(c, fi.tnode);
|
|
cgwidentaggedstore(c, fi.tnode,
|
|
n.rhs, "BP", lc.off + fi.foff, fsz);
|
|
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+fi.foff)(BP).
|
|
// N_STRUCTLIT: field-walk; each inner
|
|
// field stored at +fi.foff+inner_foff(BP).
|
|
// BP-rel direct, no addr scratch needed.
|
|
if (n.op == tkind.TK_ASSIGN
|
|
&& n.rhs != nil
|
|
&& n.rhs.kind == nkind.N_CALL
|
|
&& fi.tnode != nil
|
|
&& fi.tnode.kind == nkind.N_TNAME
|
|
&& primsize(fi.tnode.str) == 0) {
|
|
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
|
if (ssi != nil) {
|
|
let ssz: i32 = structnaturalsize(ssi);
|
|
if (ssz <= 24) {
|
|
let tlm: i32 = ssz - (ssz / 8) * 8;
|
|
if (tlm == 0 || tlm == 1
|
|
|| tlm == 2 || tlm == 4) {
|
|
cgexpr(c, n.rhs);
|
|
let full: i32 = ssz / 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(", ");
|
|
emitoff((lc.off + fi.foff + i * 8): i64);
|
|
emitline("(BP)\n");
|
|
i += 1;
|
|
};
|
|
if (tlm > 0) {
|
|
let top: str = "MOVB";
|
|
if (tlm == 4) { top = "MOVL"; };
|
|
if (tlm == 2) { top = "MOVW"; };
|
|
let treg: str = "AX";
|
|
if (full == 1) { treg = "DX"; };
|
|
if (full == 2) { treg = "CX"; };
|
|
emitline("\t");
|
|
emitline(top);
|
|
emitline("\t");
|
|
emitline(treg);
|
|
emitline(", ");
|
|
emitoff((lc.off + fi.foff + full * 8): i64);
|
|
emitline("(BP)\n");
|
|
};
|
|
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 == tkind.TK_ASSIGN
|
|
&& n.rhs != nil
|
|
&& n.rhs.kind == nkind.N_STRUCTLIT
|
|
&& fi.tnode != nil
|
|
&& fi.tnode.kind == nkind.N_TNAME
|
|
&& primsize(fi.tnode.str) == 0) {
|
|
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
|
if (ssi != nil) {
|
|
let ssz: i32 = structnaturalsize(ssi);
|
|
cgstructlitfill(c, ssi, n.rhs, 0, 0, "",
|
|
lc.off + fi.foff, ssz);
|
|
return;
|
|
};
|
|
};
|
|
if (n.op == tkind.TK_ASSIGN
|
|
&& n.rhs != nil
|
|
&& n.rhs.kind == nkind.N_IDENT
|
|
&& fi.tnode != nil
|
|
&& fi.tnode.kind == nkind.N_TNAME
|
|
&& primsize(fi.tnode.str) == 0) {
|
|
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
|
let srhs: *local = localfindnode(c, n.rhs.str);
|
|
if (ssi != nil) { if (srhs != nil) {
|
|
let ssz: i32 = ssi.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 + fi.foff + k): i64);
|
|
emitline("(BP)\n");
|
|
k += 8;
|
|
};
|
|
if (k < ssz) {
|
|
let tail: i32 = ssz - k;
|
|
let lop: str = "MOVQ";
|
|
if (tail == 4) { lop = "MOVL"; }
|
|
else { if (tail == 1) { lop = "MOVB"; }; };
|
|
emitline("\t");
|
|
emitline(lop);
|
|
emitline("\t");
|
|
emitoff((srhs.off + k): i64);
|
|
emitline("(BP), AX\n");
|
|
emitline("\t");
|
|
emitline(lop);
|
|
emitline("\tAX, ");
|
|
emitoff((lc.off + fi.foff + k): i64);
|
|
emitline("(BP)\n");
|
|
};
|
|
return;
|
|
};};
|
|
};
|
|
cgexpr(c, n.rhs);
|
|
// str field: cgexpr left (AX=ptr, BX=len);
|
|
// store both halves at +0/+8. Without this,
|
|
// `L.src = s` would only write the ptr and
|
|
// `L.src.len` would carry whatever was on the
|
|
// stack.
|
|
if (isstrtype(c, fi.tnode)) {
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitoff((lc.off + fi.foff): i64);
|
|
emitline("(BP)\n");
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitoff((lc.off + fi.foff + 8): i64);
|
|
emitline("(BP)\n");
|
|
return;
|
|
};
|
|
// slice field direct: cgexpr left
|
|
// (AX=ptr, BX=len, CX=cap); store all
|
|
// three at +0/+8/+16. The generic
|
|
// fldstoreop below would only write AX,
|
|
// dropping .len/.cap.
|
|
if (isslicetype(c, fi.tnode)) {
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitoff((lc.off + fi.foff): i64);
|
|
emitline("(BP)\n");
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitoff((lc.off + fi.foff + 8): i64);
|
|
emitline("(BP)\n");
|
|
emitline("\tMOVQ\tCX, ");
|
|
emitoff((lc.off + fi.foff + 16): i64);
|
|
emitline("(BP)\n");
|
|
return;
|
|
};
|
|
// f64/f32 direct struct local store: route via X0.
|
|
if (isfloattype(c, fi.tnode)) {
|
|
let mov: str = "MOVSD";
|
|
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\tX0, ");
|
|
emitoff((lc.off + fi.foff): i64);
|
|
emitline("(BP)\n");
|
|
return;
|
|
};
|
|
let sop: str = fieldstoreop(c, fi);
|
|
emitline("\t");
|
|
emitline(sop);
|
|
emitline("\tAX, ");
|
|
emitoff((lc.off + fi.foff): i64);
|
|
emitline("(BP)\n");
|
|
return;
|
|
};
|
|
fi = fi.finext;
|
|
};
|
|
};
|
|
};
|
|
// str/slice pseudo-field assignment.
|
|
let delta: i32 = -1;
|
|
if (streq(fld, "ptr")) { delta = 0; };
|
|
if (streq(fld, "len")) { delta = 8; };
|
|
if (streq(fld, "cap")) { delta = 16; };
|
|
if (delta >= 0) {
|
|
if (lkind == nkind.N_TPTR) {
|
|
let inner: *node = tn.lhs;
|
|
let innerkind: nkind = nkind.N_NONE;
|
|
if (inner != nil) { innerkind = inner.kind; };
|
|
let innerstr: bool = false;
|
|
if (innerkind == nkind.N_TNAME) {
|
|
if (streq(inner.str, "str")) { innerstr = true; };
|
|
};
|
|
if (innerkind == nkind.N_TSLICE) { innerstr = true; };
|
|
if (innerstr) {
|
|
if (n.op != tkind.TK_ASSIGN) {
|
|
// Compound on `(*str|*slice).field`: load
|
|
// current → push → eval rhs → combine → store.
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), BX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg(delta: i64, "BX");
|
|
emitline(", BX\n");
|
|
emitline("\tPUSHQ\tBX\n");
|
|
cgexpr(c, n.rhs);
|
|
emitline("\tPOPQ\tBX\n");
|
|
// PLUSEQ is commutative; MINUSEQ
|
|
// needs lhs - rhs.
|
|
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); };
|
|
if (n.op == tkind.TK_MINUSEQ) {
|
|
emitline("\tSUBQ\tAX, BX\n");
|
|
emitline("\tMOVQ\tBX, AX\n");
|
|
};
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), BX\n");
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitdispreg(delta: i64, "BX");
|
|
emitline("\n");
|
|
return;
|
|
};
|
|
cgexpr(c, n.rhs);
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), BX\n");
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitdispreg(delta: i64, "BX");
|
|
emitline("\n");
|
|
return;
|
|
};
|
|
};
|
|
cgexpr(c, n.rhs);
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitoff((lc.off + delta): i64);
|
|
emitline("(BP)\n");
|
|
return;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// Top-level struct global field assignment: `g.f = expr;` and
|
|
// `g.f += expr;` for a scalar/str field. Reached when the local
|
|
// lookup miss but the IDENT base is a registered struct `let`.
|
|
// LEAQ name(SB) into BX/CX takes the place of the frame slot
|
|
// addressing the local branches use. Compound (PLUSEQ/MINUSEQ)
|
|
// follows the same load → push → eval → combine → store shape
|
|
// as the via-ptr local path.
|
|
if (lhs != nil) {
|
|
if (lhs.kind == nkind.N_DOT) {
|
|
let base: *node = lhs.lhs;
|
|
let fld: str = lhs.str;
|
|
if (base != nil) {
|
|
if (base.kind == nkind.N_IDENT) {
|
|
let bn: str = base.str;
|
|
if (localfindnode(c, bn) == nil) {
|
|
let si: *structinfo = letvarstructinfo(c, bn);
|
|
if (si != nil) {
|
|
let fi: *fieldinfo = si.fields;
|
|
for (fi != nil) {
|
|
if (streq(fi.fname, fld)) {
|
|
// 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 == tkind.TK_ASSIGN
|
|
&& n.rhs != nil
|
|
&& n.rhs.kind == nkind.N_CALL
|
|
&& fi.tnode != nil
|
|
&& fi.tnode.kind == nkind.N_TNAME
|
|
&& primsize(fi.tnode.str) == 0) {
|
|
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
|
if (ssi != nil) {
|
|
let ssz: i32 = structnaturalsize(ssi);
|
|
if (ssz <= 24) {
|
|
let tlm: i32 = ssz - (ssz / 8) * 8;
|
|
if (tlm == 0 || tlm == 1
|
|
|| tlm == 2 || tlm == 4) {
|
|
cgexpr(c, n.rhs);
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, bn);
|
|
emitline("(SB), BX\n");
|
|
let full: i32 = ssz / 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((fi.foff + i * 8): i64, "BX");
|
|
emitline("\n");
|
|
i += 1;
|
|
};
|
|
if (tlm > 0) {
|
|
let top: str = "MOVB";
|
|
if (tlm == 4) { top = "MOVL"; };
|
|
if (tlm == 2) { top = "MOVW"; };
|
|
let treg: str = "AX";
|
|
if (full == 1) { treg = "DX"; };
|
|
if (full == 2) { treg = "CX"; };
|
|
emitline("\t");
|
|
emitline(top);
|
|
emitline("\t");
|
|
emitline(treg);
|
|
emitline(", ");
|
|
emitdispreg((fi.foff + full * 8): i64, "BX");
|
|
emitline("\n");
|
|
};
|
|
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 == tkind.TK_ASSIGN
|
|
&& n.rhs != nil
|
|
&& n.rhs.kind == nkind.N_STRUCTLIT
|
|
&& fi.tnode != nil
|
|
&& fi.tnode.kind == nkind.N_TNAME
|
|
&& primsize(fi.tnode.str) == 0) {
|
|
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
|
if (ssi != nil) {
|
|
let ssz: i32 = structnaturalsize(ssi);
|
|
cgstructlitfill(c, ssi, n.rhs, 2, 0, bn,
|
|
fi.foff, ssz);
|
|
return;
|
|
};
|
|
};
|
|
if (n.op == tkind.TK_ASSIGN
|
|
&& n.rhs != nil
|
|
&& n.rhs.kind == nkind.N_IDENT
|
|
&& fi.tnode != nil
|
|
&& fi.tnode.kind == nkind.N_TNAME
|
|
&& primsize(fi.tnode.str) == 0) {
|
|
let ssi: *structinfo = structlookup(c, fi.tnode.str);
|
|
let srhs: *local = localfindnode(c, n.rhs.str);
|
|
if (ssi != nil) { if (srhs != nil) {
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, bn);
|
|
emitline("(SB), BX\n");
|
|
let ssz: i32 = ssi.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((fi.foff + k): i64, "BX");
|
|
emitline("\n");
|
|
k += 8;
|
|
};
|
|
if (k < ssz) {
|
|
let tail: i32 = ssz - k;
|
|
let lop: str = "MOVQ";
|
|
if (tail == 4) { lop = "MOVL"; }
|
|
else { if (tail == 1) { lop = "MOVB"; }; };
|
|
emitline("\t");
|
|
emitline(lop);
|
|
emitline("\t");
|
|
emitoff((srhs.off + k): i64);
|
|
emitline("(BP), AX\n");
|
|
emitline("\t");
|
|
emitline(lop);
|
|
emitline("\tAX, ");
|
|
emitdispreg((fi.foff + k): i64, "BX");
|
|
emitline("\n");
|
|
};
|
|
return;
|
|
};};
|
|
};
|
|
if (n.op == tkind.TK_ASSIGN) {
|
|
cgexpr(c, n.rhs);
|
|
if (isstrtype(c, fi.tnode)) {
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, bn);
|
|
emitline("(SB), CX\n");
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitdispreg(fi.foff: i64, "CX");
|
|
emitline("\n");
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitdispreg((fi.foff + 8): i64, "CX");
|
|
emitline("\n");
|
|
return;
|
|
};
|
|
// f64/f32 plain `=` on global struct field: value is
|
|
// in X0; LEAQ the base into BX and MOVSD/MOVSS.
|
|
if (isfloattype(c, fi.tnode)) {
|
|
let mov: str = "MOVSD";
|
|
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, bn);
|
|
emitline("(SB), BX\n");
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\tX0, ");
|
|
emitdispreg(fi.foff: i64, "BX");
|
|
emitline("\n");
|
|
return;
|
|
};
|
|
let sop: str = fieldstoreop(c, fi);
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, bn);
|
|
emitline("(SB), BX\n");
|
|
emitline("\t");
|
|
emitline(sop);
|
|
emitline("\tAX, ");
|
|
emitdispreg(fi.foff: i64, "BX");
|
|
emitline("\n");
|
|
return;
|
|
};
|
|
// Compound on scalar field: load
|
|
// → push → eval rhs → combine →
|
|
// store. cgexpr clobbers BX, so
|
|
// re-LEAQ for the store.
|
|
let lop: str = fieldloadop(c, fi);
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, bn);
|
|
emitline("(SB), BX\n");
|
|
emitline("\t");
|
|
emitline(lop);
|
|
emitline("\t");
|
|
emitdispreg(fi.foff: i64, "BX");
|
|
emitline(", BX\n");
|
|
emitline("\tPUSHQ\tBX\n");
|
|
cgexpr(c, n.rhs);
|
|
emitline("\tPOPQ\tBX\n");
|
|
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); };
|
|
if (n.op == tkind.TK_MINUSEQ) {
|
|
emitline("\tSUBQ\tAX, BX\n");
|
|
emitline("\tMOVQ\tBX, AX\n");
|
|
};
|
|
let sop: str = fieldstoreop(c, fi);
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, bn);
|
|
emitline("(SB), BX\n");
|
|
emitline("\t");
|
|
emitline(sop);
|
|
emitline("\tAX, ");
|
|
emitdispreg(fi.foff: i64, "BX");
|
|
emitline("\n");
|
|
return;
|
|
};
|
|
fi = fi.finext;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// Chained `<expr>.field = v` where `<expr>` itself is a chain
|
|
// of dots resolving to a *struct. Mirrors the C cgen branch
|
|
// added to close trap 1 (cmd/w6c/cgen.c). Without this, only
|
|
// `local.field = v` and `local.fieldptr.field = v` get wired
|
|
// (the latter through the IDENT-base branch above) — chains
|
|
// like `s.last.snext = sy` (lib/ww/sym.ww) silently emit no
|
|
// store. Only plain `=` is wired here; chained compound on a
|
|
// pointer-field hasn't surfaced.
|
|
if (lhs != nil) {
|
|
if (lhs.kind == nkind.N_DOT) {
|
|
let base: *node = lhs.lhs;
|
|
let fld: str = lhs.str;
|
|
if (base != nil) {
|
|
if (base.kind == nkind.N_DOT) {
|
|
let innert: *node = dotinnerstructptr(c, base);
|
|
if (innert != nil) {
|
|
let sname: str = innert.str;
|
|
let si: *structinfo = structlookup(c, sname);
|
|
if (si != nil) {
|
|
let fi: *fieldinfo = si.fields;
|
|
for (fi != nil) {
|
|
if (streq(fi.fname, fld)) {
|
|
if (n.op == tkind.TK_ASSIGN) {
|
|
if (isstrtype(c, fi.tnode)) {
|
|
// str rhs: AX=ptr, BX=len.
|
|
// Stash both, then load
|
|
// the struct ptr into CX
|
|
// and write both halves.
|
|
cgexpr(c, n.rhs);
|
|
emitline("\tPUSHQ\tBX\n");
|
|
emitline("\tPUSHQ\tAX\n");
|
|
cgexpr(c, base);
|
|
emitline("\tMOVQ\tAX, CX\n");
|
|
emitline("\tPOPQ\tAX\n");
|
|
emitline("\tPOPQ\tBX\n");
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitdispreg(fi.foff: i64, "CX");
|
|
emitline("\n");
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitdispreg((fi.foff + 8): i64, "CX");
|
|
emitline("\n");
|
|
return;
|
|
};
|
|
// f64/f32 chained plain `=`: cgexpr rhs left value in
|
|
// X0. Spill to stack so cgexpr(base) can use AX, then
|
|
// reload and MOVSD/MOVSS into the slot.
|
|
if (isfloattype(c, fi.tnode)) {
|
|
let mov: str = "MOVSD";
|
|
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
|
cgexpr(c, n.rhs);
|
|
emitline("\tSUBQ\t$8, SP\n");
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\tX0, (SP)\n");
|
|
cgexpr(c, base);
|
|
emitline("\tMOVQ\tAX, BX\n");
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\t(SP), X0\n");
|
|
emitline("\tADDQ\t$8, SP\n");
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\tX0, ");
|
|
emitdispreg(fi.foff: i64, "BX");
|
|
emitline("\n");
|
|
return;
|
|
};
|
|
cgexpr(c, n.rhs);
|
|
emitline("\tPUSHQ\tAX\n");
|
|
cgexpr(c, base);
|
|
emitline("\tMOVQ\tAX, BX\n");
|
|
emitline("\tPOPQ\tAX\n");
|
|
let sop: str = fieldstoreop(c, fi);
|
|
emitline("\t");
|
|
emitline(sop);
|
|
emitline("\tAX, ");
|
|
emitdispreg(fi.foff: i64, "BX");
|
|
emitline("\n");
|
|
return;
|
|
};
|
|
};
|
|
fi = fi.finext;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// 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 == nkind.N_DOT && lhs.lhs != nil
|
|
&& lhs.lhs.kind == nkind.N_DOT
|
|
&& n.op == tkind.TK_ASSIGN) {
|
|
let rootname: str = "";
|
|
let rootoff: i32 = 0;
|
|
let totaloff: i32 = 0;
|
|
let leaffi: *fieldinfo = nil;
|
|
let slicedelta: i32 = -1;
|
|
let isglobal: bool = false;
|
|
let ptrroot: bool = false;
|
|
let yok: bool = dotchainresolve(c, lhs,
|
|
&rootname, &rootoff, &totaloff,
|
|
&leaffi, &slicedelta, &isglobal, &ptrroot);
|
|
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) {
|
|
if (ptrroot) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(rootoff: i64);
|
|
emitline("(BP), CX\n");
|
|
} else {
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, rootname);
|
|
emitline("(SB), CX\n");
|
|
};
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitdispreg((totaloff + slicedelta): i64, "CX");
|
|
emitline("\n");
|
|
} else {
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitoff((rootoff + totaloff + slicedelta): i64);
|
|
emitline("(BP)\n");
|
|
};
|
|
return;
|
|
};
|
|
if (isstrtype(c, leaffi.tnode)) {
|
|
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("\tMOVQ\tAX, ");
|
|
emitdispreg(totaloff: i64, "CX");
|
|
emitline("\n");
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitdispreg((totaloff + 8): i64, "CX");
|
|
emitline("\n");
|
|
} else {
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitoff((rootoff + totaloff): i64);
|
|
emitline("(BP)\n");
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitoff((rootoff + totaloff + 8): i64);
|
|
emitline("(BP)\n");
|
|
};
|
|
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.
|
|
if (n.rhs != nil
|
|
&& n.rhs.kind == nkind.N_CALL
|
|
&& leaffi.tnode != nil
|
|
&& leaffi.tnode.kind == nkind.N_TNAME
|
|
&& primsize(leaffi.tnode.str) == 0) {
|
|
let lsi: *structinfo = structlookup(c, leaffi.tnode.str);
|
|
if (lsi != nil) {
|
|
// si.totsize is slot-padded (rounded to 8);
|
|
// receive ABI needs the TYPE's natural size.
|
|
let lsz: i32 = structnaturalsize(lsi);
|
|
if (lsz <= 24) {
|
|
let tlm: i32 = lsz - (lsz / 8) * 8;
|
|
if (tlm == 0 || tlm == 1
|
|
|| tlm == 2 || tlm == 4) {
|
|
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");
|
|
};
|
|
};
|
|
let full: i32 = lsz / 8;
|
|
let i: i32 = 0;
|
|
for (i < full) {
|
|
let reg: str = "AX";
|
|
if (i == 1) { reg = "DX"; };
|
|
if (i == 2) { reg = "CX"; };
|
|
if (viacx) {
|
|
emitline("\tMOVQ\t");
|
|
emitline(reg);
|
|
emitline(", ");
|
|
emitdispreg((totaloff + i * 8): i64, "BX");
|
|
emitline("\n");
|
|
} else {
|
|
emitline("\tMOVQ\t");
|
|
emitline(reg);
|
|
emitline(", ");
|
|
emitoff((rootoff + totaloff + i * 8): i64);
|
|
emitline("(BP)\n");
|
|
};
|
|
i += 1;
|
|
};
|
|
if (tlm > 0) {
|
|
let top: str = "MOVB";
|
|
if (tlm == 4) { top = "MOVL"; };
|
|
if (tlm == 2) { top = "MOVW"; };
|
|
let treg: str = "AX";
|
|
if (full == 1) { treg = "DX"; };
|
|
if (full == 2) { treg = "CX"; };
|
|
if (viacx) {
|
|
emitline("\t");
|
|
emitline(top);
|
|
emitline("\t");
|
|
emitline(treg);
|
|
emitline(", ");
|
|
emitdispreg((totaloff + full * 8): i64, "BX");
|
|
emitline("\n");
|
|
} else {
|
|
emitline("\t");
|
|
emitline(top);
|
|
emitline("\t");
|
|
emitline(treg);
|
|
emitline(", ");
|
|
emitoff((rootoff + totaloff + full * 8): i64);
|
|
emitline("(BP)\n");
|
|
};
|
|
};
|
|
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 == nkind.N_STRUCTLIT
|
|
&& leaffi.tnode != nil
|
|
&& leaffi.tnode.kind == nkind.N_TNAME
|
|
&& primsize(leaffi.tnode.str) == 0) {
|
|
let lsi: *structinfo = structlookup(c, leaffi.tnode.str);
|
|
if (lsi != nil) {
|
|
// si.totsize is slot-padded (rounded to 8);
|
|
// receive ABI needs the TYPE's natural size.
|
|
let lsz: i32 = structnaturalsize(lsi);
|
|
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, lsz);
|
|
return;
|
|
};
|
|
};
|
|
if (n.rhs != nil
|
|
&& n.rhs.kind == nkind.N_IDENT
|
|
&& leaffi.tnode != nil
|
|
&& leaffi.tnode.kind == nkind.N_TNAME
|
|
&& primsize(leaffi.tnode.str) == 0) {
|
|
let ssi: *structinfo = structlookup(c, leaffi.tnode.str);
|
|
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 = ssi.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 < ssz) {
|
|
let tail: i32 = ssz - k;
|
|
let lop: str = "MOVQ";
|
|
if (tail == 4) { lop = "MOVL"; }
|
|
else { if (tail == 1) { lop = "MOVB"; }; };
|
|
emitline("\t");
|
|
emitline(lop);
|
|
emitline("\t");
|
|
emitoff((srhs.off + k): i64);
|
|
emitline("(BP), AX\n");
|
|
if (viacx) {
|
|
emitline("\t");
|
|
emitline(lop);
|
|
emitline("\tAX, ");
|
|
emitdispreg((totaloff + k): i64, "CX");
|
|
emitline("\n");
|
|
} else {
|
|
emitline("\t");
|
|
emitline(lop);
|
|
emitline("\tAX, ");
|
|
emitoff((rootoff + totaloff + k): i64);
|
|
emitline("(BP)\n");
|
|
};
|
|
};
|
|
return;
|
|
};};
|
|
};
|
|
if (isfloattype(c, leaffi.tnode)) {
|
|
let mov: str = "MOVSD";
|
|
if (isf32type(c, leaffi.tnode)) { mov = "MOVSS"; };
|
|
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(mov);
|
|
emitline("\tX0, ");
|
|
emitdispreg(totaloff: i64, "CX");
|
|
emitline("\n");
|
|
} else {
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\tX0, ");
|
|
emitoff((rootoff + totaloff): i64);
|
|
emitline("(BP)\n");
|
|
};
|
|
return;
|
|
};
|
|
let sop: str = fieldstoreop(c, leaffi);
|
|
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 == nkind.N_DOT) {
|
|
let base: *node = lhs.lhs;
|
|
let fld: str = lhs.str;
|
|
if (base != nil) { if (base.kind == nkind.N_DOT) {
|
|
let inner: *node = base.lhs;
|
|
let innerfld: str = base.str;
|
|
if (inner != nil) { if (inner.kind == nkind.N_IDENT) {
|
|
let lc: *local = localfindnode(c, inner.str);
|
|
if (lc != nil) { if (lc.tnode != nil) {
|
|
let tn: *node = lc.tnode;
|
|
let lkind: nkind = tn.kind;
|
|
let outname: str;
|
|
outname.ptr = nil; outname.len = 0;
|
|
let isptr: bool = false;
|
|
if (lkind == nkind.N_TNAME) { outname = tn.str; };
|
|
if (lkind == nkind.N_TPTR) {
|
|
let pe: *node = tn.lhs;
|
|
if (pe != nil) { if (pe.kind == nkind.N_TNAME) {
|
|
outname = pe.str;
|
|
isptr = true;
|
|
};};
|
|
};
|
|
if (outname.len > 0) {
|
|
let osi: *structinfo = structlookup(c, outname);
|
|
if (osi != nil) {
|
|
let ofi: *fieldinfo = osi.fields;
|
|
for (ofi != nil) {
|
|
if (streq(ofi.fname, innerfld)) {
|
|
let oft: *node = ofi.tnode;
|
|
if (oft != nil) { if (oft.kind == nkind.N_TNAME) {
|
|
if (primsize(oft.str) == 0) {
|
|
let isi: *structinfo = structlookup(c, oft.str);
|
|
if (isi != nil) {
|
|
let ffi: *fieldinfo = isi.fields;
|
|
for (ffi != nil) {
|
|
if (streq(ffi.fname, fld)) {
|
|
if (n.op == tkind.TK_ASSIGN) {
|
|
let totoff: i32 = ofi.foff + ffi.foff;
|
|
cgexpr(c, n.rhs);
|
|
if (isstrtype(c, ffi.tnode)) {
|
|
if (isptr) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), CX\n");
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitdispreg(totoff: i64, "CX");
|
|
emitline("\n");
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitdispreg((totoff + 8): i64, "CX");
|
|
emitline("\n");
|
|
} else {
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitoff((lc.off + totoff): i64);
|
|
emitline("(BP)\n");
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitoff((lc.off + totoff + 8): i64);
|
|
emitline("(BP)\n");
|
|
};
|
|
return;
|
|
};
|
|
if (isfloattype(c, ffi.tnode)) {
|
|
let mov: str = "MOVSD";
|
|
if (isf32type(c, ffi.tnode)) { mov = "MOVSS"; };
|
|
if (isptr) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), BX\n");
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\tX0, ");
|
|
emitdispreg(totoff: i64, "BX");
|
|
emitline("\n");
|
|
} else {
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\tX0, ");
|
|
emitoff((lc.off + totoff): i64);
|
|
emitline("(BP)\n");
|
|
};
|
|
return;
|
|
};
|
|
let sop: str = fieldstoreop(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 == nkind.N_IDENT) {
|
|
let nm: str = lhs.str;
|
|
let off: i32 = localfind(c, nm);
|
|
if (off == 0) {
|
|
// Top-level let target: RIP-relative store
|
|
// for `=`, or load→combine→store for the
|
|
// compound forms. For a str/slice global,
|
|
// take its address into CX and store both
|
|
// halves (plus cap for slice — stashed via
|
|
// DI since LEAQ overwrites CX); the asm has
|
|
// no `name+8(SB)` operand form.
|
|
if (!isletvar(c, nm)) { return; };
|
|
// Float global: rhs lands in X0; store via
|
|
// LEAQ+indirect since MOVSS/MOVSD have no
|
|
// D_EXTERN operand form.
|
|
let lvf: *letvar = c.lets;
|
|
let isfg: bool = false;
|
|
let isf32g: bool = false;
|
|
let lvftn: *node = nil;
|
|
for (lvf != nil) {
|
|
if (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 == tkind.TK_ASSIGN) {
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\tX0, (CX)\n");
|
|
return;
|
|
};
|
|
// Compound: X1 = load; X1 OP= X0; store X1.
|
|
// ADDSD/SUBSD/MULSD/DIVSD are register-register
|
|
// only, so we can't combine direct to memory.
|
|
let fop: str;
|
|
fop.ptr = nil; fop.len = 0;
|
|
if (n.op == tkind.TK_PLUSEQ) { fop = addf; };
|
|
if (n.op == tkind.TK_MINUSEQ) { fop = subf; };
|
|
if (n.op == tkind.TK_STAREQ) { fop = mulf; };
|
|
if (n.op == tkind.TK_SLASHEQ) { fop = divf; };
|
|
if (fop.len == 0) {
|
|
// Unsupported (e.g., %= on float):
|
|
// fall back to plain store of rhs.
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\tX0, (CX)\n");
|
|
return;
|
|
};
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\t(CX), X1\n");
|
|
emitline("\t");
|
|
emitline(fop);
|
|
emitline("\tX0, X1\n");
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\tX1, (CX)\n");
|
|
return;
|
|
};
|
|
cgexpr(c, n.rhs);
|
|
if (n.op == tkind.TK_ASSIGN) {
|
|
if (letvarisstr(c, nm)) {
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, nm);
|
|
emitline("(SB), CX\n");
|
|
emitline("\tMOVQ\tAX, (CX)\n");
|
|
emitline("\tMOVQ\tBX, 8(CX)\n");
|
|
return;
|
|
};
|
|
if (letvarisslice(c, nm)) {
|
|
emitline("\tMOVQ\tCX, DI\n");
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, nm);
|
|
emitline("(SB), CX\n");
|
|
emitline("\tMOVQ\tAX, (CX)\n");
|
|
emitline("\tMOVQ\tBX, 8(CX)\n");
|
|
emitline("\tMOVQ\tDI, 16(CX)\n");
|
|
return;
|
|
};
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitsymname(c, nm);
|
|
emitline("(SB)\n");
|
|
return;
|
|
};
|
|
// 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 (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");
|
|
};
|
|
let didcompound: bool = true;
|
|
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tAX, BX\n"); }
|
|
else { if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, BX\n"); }
|
|
else { if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); }
|
|
else { if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tAX, BX\n"); }
|
|
else { if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tAX, BX\n"); }
|
|
else { if (n.op == tkind.TK_CARETEQ) { emitline("\tXORQ\tAX, BX\n"); }
|
|
else { if (n.op == tkind.TK_LSHIFTEQ) {
|
|
emitline("\tMOVQ\tAX, CX\n");
|
|
emitline("\tSHLQ\tCX, BX\n");
|
|
}
|
|
else { if (n.op == tkind.TK_RSHIFTEQ) {
|
|
emitline("\tMOVQ\tAX, CX\n");
|
|
emitline("\tSHRQ\tCX, BX\n");
|
|
}
|
|
// 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 == tkind.TK_SLASHEQ || n.op == tkind.TK_PERCENTEQ) {
|
|
let unsignd: bool = typenodeisunsignedc(c, lvftn);
|
|
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 == tkind.TK_SLASHEQ) {
|
|
emitline("\tMOVQ\tAX, BX\n");
|
|
} else {
|
|
emitline("\tMOVQ\tDX, BX\n");
|
|
};
|
|
}
|
|
else {
|
|
// Unsupported compound: store rhs
|
|
// directly. Mirrors the local path's
|
|
// legacy fallback for unknown ops.
|
|
didcompound = false;
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitsymname(c, nm);
|
|
emitline("(SB)\n");
|
|
};};};};};};};};};
|
|
if (didcompound) {
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitsymname(c, nm);
|
|
emitline("(SB)\n");
|
|
};
|
|
return;
|
|
};
|
|
// Detect str/slice-typed local — assignment must store
|
|
// both halves (AX=ptr at +0, BX=len at +8) for str,
|
|
// plus the cap (CX at +16) for slice.
|
|
let lcstr: bool = false;
|
|
let lcsl: bool = false;
|
|
let lcn: *local = localfindnode(c, nm);
|
|
if (lcn != nil) {
|
|
lcstr = isstrtype(c, lcn.tnode);
|
|
lcsl = isslicetype(c, lcn.tnode);
|
|
};
|
|
let lcf: bool = false;
|
|
let lcf32: bool = false;
|
|
if (lcn != nil) {
|
|
lcf = isfloattype(c, lcn.tnode);
|
|
lcf32 = isf32type(c, lcn.tnode);
|
|
};
|
|
// 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: *node = lcn.tnode;
|
|
let lcsname: str;
|
|
lcsname.ptr = nil; lcsname.len = 0;
|
|
if (lctn != nil) {
|
|
if (lctn.kind == nkind.N_TNAME) {
|
|
lcsname = lctn.str;
|
|
};
|
|
};
|
|
if (lcsname.len > 0) {
|
|
let lcsi: *structinfo = structlookup(c, lcsname);
|
|
if (lcsi != nil) {
|
|
// si.totsize is slot-padded (rounded to 8);
|
|
// receive ABI needs the TYPE's natural size.
|
|
let lcnsz: i32 = structnaturalsize(lcsi);
|
|
if (n.op == tkind.TK_ASSIGN) {
|
|
if (n.rhs != nil
|
|
&& n.rhs.kind == 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 == 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) {
|
|
let tlm: i32 = lcsz - (lcsz / 8) * 8;
|
|
if (tlm == 0 || tlm == 1
|
|
|| tlm == 2 || tlm == 4) {
|
|
cgexpr(c, n.rhs);
|
|
let full: i32 = lcsz / 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(", ");
|
|
emitoff((off + i * 8): i64);
|
|
emitline("(BP)\n");
|
|
i += 1;
|
|
};
|
|
if (tlm > 0) {
|
|
let top: str = "MOVB";
|
|
if (tlm == 4) { top = "MOVL"; };
|
|
if (tlm == 2) { top = "MOVW"; };
|
|
let treg: str = "AX";
|
|
if (full == 1) { treg = "DX"; };
|
|
if (full == 2) { treg = "CX"; };
|
|
emitline("\t");
|
|
emitline(top);
|
|
emitline("\t");
|
|
emitline(treg);
|
|
emitline(", ");
|
|
emitoff((off + full * 8): i64);
|
|
emitline("(BP)\n");
|
|
};
|
|
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 == tkind.TK_ASSIGN) {
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\tX0, ");
|
|
emitoff(off: i64);
|
|
emitline("(BP)\n");
|
|
return;
|
|
};
|
|
let fop: str;
|
|
fop.ptr = nil; fop.len = 0;
|
|
if (n.op == tkind.TK_PLUSEQ) { fop = addf; };
|
|
if (n.op == tkind.TK_MINUSEQ) { fop = subf; };
|
|
if (n.op == tkind.TK_STAREQ) { fop = mulf; };
|
|
if (n.op == tkind.TK_SLASHEQ) { fop = divf; };
|
|
if (fop.len == 0) {
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\tX0, ");
|
|
emitoff(off: i64);
|
|
emitline("(BP)\n");
|
|
return;
|
|
};
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\t");
|
|
emitoff(off: i64);
|
|
emitline("(BP), X1\n");
|
|
emitline("\t");
|
|
emitline(fop);
|
|
emitline("\tX0, X1\n");
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\tX1, ");
|
|
emitoff(off: i64);
|
|
emitline("(BP)\n");
|
|
return;
|
|
};
|
|
cgexpr(c, n.rhs);
|
|
if (n.op == tkind.TK_ASSIGN) {
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitoff(off: i64);
|
|
emitline("(BP)\n");
|
|
if (lcstr || lcsl) {
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitoff((off + 8): i64);
|
|
emitline("(BP)\n");
|
|
};
|
|
if (lcsl) {
|
|
emitline("\tMOVQ\tCX, ");
|
|
emitoff((off + 16): i64);
|
|
emitline("(BP)\n");
|
|
};
|
|
return;
|
|
};
|
|
// 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 (streq(llop, "MOVQ")) {
|
|
if (n.op == tkind.TK_PLUSEQ) {
|
|
emitline("\tADDQ\tAX, ");
|
|
emitoff(off: i64);
|
|
emitline("(BP)\n");
|
|
return;
|
|
};
|
|
if (n.op == tkind.TK_MINUSEQ) {
|
|
emitline("\tSUBQ\tAX, ");
|
|
emitoff(off: i64);
|
|
emitline("(BP)\n");
|
|
return;
|
|
};
|
|
};
|
|
// Generic compound: load → combine in BX → store.
|
|
emitline("\t");
|
|
emitline(llop);
|
|
emitline("\t");
|
|
emitoff(off: i64);
|
|
emitline("(BP), BX\n");
|
|
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tAX, BX\n"); };
|
|
if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tAX, BX\n"); };
|
|
if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); };
|
|
if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tAX, BX\n"); };
|
|
if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tAX, BX\n"); };
|
|
if (n.op == tkind.TK_CARETEQ) { emitline("\tXORQ\tAX, BX\n"); };
|
|
if (n.op == tkind.TK_LSHIFTEQ) {
|
|
emitline("\tMOVQ\tAX, CX\n");
|
|
emitline("\tSHLQ\tCX, BX\n");
|
|
};
|
|
if (n.op == tkind.TK_RSHIFTEQ) {
|
|
emitline("\tMOVQ\tAX, CX\n");
|
|
emitline("\tSHRQ\tCX, BX\n");
|
|
};
|
|
// 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 == tkind.TK_SLASHEQ || n.op == tkind.TK_PERCENTEQ) {
|
|
let unsignd: bool = false;
|
|
if (lcn != nil) {
|
|
unsignd = typenodeisunsignedc(c, lcn.tnode);
|
|
};
|
|
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 == tkind.TK_SLASHEQ) {
|
|
emitline("\tMOVQ\tAX, BX\n");
|
|
} else {
|
|
emitline("\tMOVQ\tDX, BX\n");
|
|
};
|
|
};
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitoff(off: i64);
|
|
emitline("(BP)\n");
|
|
return;
|
|
};
|
|
};
|
|
return;
|
|
};
|
|
|
|
|