Both stages materialise a float literal as a 64-bit double in X0 (MOVQ bits -> MOVSD), ignoring the node type. For an f32-typed literal the downstream MOVSS reads the low 4 bytes of that double — garbage (0.0f for clean values, which is why 0.0 survived the bug and 951's f32 rows, which only assert NaN ordering, never caught it). Append CVTSD2SS X0,X0 at both literal sites (N_FLOATLIT + the float-typed N_INTLIT arm) when the node is f32-typed, so the value reaches X0 as a true single. Mirror in cgenexpr.ww (rule-10) and regen the w6c/wwdump combined.ww embeds. Covers literals carrying an explicit f32 type (the `f32` suffix and the no-decimal `8f32` N_INTLIT arm). An un-suffixed literal in an f32 context (`let x: f32 = 1.0`) stays ty_untyped_float through the checker, so its node is never f32-typed and this branch can't fire — that needs fold-2 (checker untyped-float -> f32 lowering, both checkers). 964_f32lit_run: cstage run + cs==ww byte-id probe over concrete f32 values (suffixed), the hole 951 leaves open.
6191 lines
201 KiB
Plaintext
6191 lines
201 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;
|
|
|
|
// cgfloatbits — materialise a float constant in X0: MOVQ the IEEE bits
|
|
// into AX, PUSH, MOVSD off the stack into X0. Shared by N_FLOATLIT (bits
|
|
// already in n.uval from the lexer's bitcast) and the f64/f32-typed
|
|
// N_INTLIT arm (#103 FACE X).
|
|
fn cgfloatbits(c: *cgen, bits: u64) void = {
|
|
emitline("\tMOVQ\t$");
|
|
emitint(bits: i64);
|
|
emitline(", AX\n");
|
|
emitline("\tPUSHQ\tAX\n");
|
|
emitline("\tMOVSD\t(SP), X0\n");
|
|
emitline("\tADDQ\t$8, SP\n");
|
|
};
|
|
|
|
fn cgexpr(c: *cgen, n: *node) void = {
|
|
if (n == nil) { return; };
|
|
let k: nkind = n.kind;
|
|
|
|
if (k == nkind.N_INTLIT) {
|
|
// A no-decimal `0f64`/`8f64` is an N_INTLIT carrying float
|
|
// TYPE; it must reach X0 like a true float literal, not the
|
|
// integer-immediate path (which strands it in AX and an SSE
|
|
// compare/mul reads a stale X0 — #103 FACE X). The bits are
|
|
// the IEEE pattern of the integer value, mirroring cstage's
|
|
// `(double)(long long)n->uval`; the (&fv):*u64 bitcast is the
|
|
// lex.ww idiom (lib/ww/lex/lex.ww).
|
|
if (isfloattype(c, n)) {
|
|
let fv: f64 = (n.uval: i64): f64;
|
|
let pu: *u64 = (&fv): *u64;
|
|
cgfloatbits(c, *pu);
|
|
// #104: cgfloatbits materialises a DOUBLE in X0; an
|
|
// f32-typed literal must narrow with hardware single-
|
|
// rounding so the downstream MOVSS reads a true single.
|
|
if (isf32type(c, n)) {
|
|
emitline("\tCVTSD2SS\tX0, X0\n");
|
|
};
|
|
return;
|
|
};
|
|
// Print signed (i64), not unsigned (u64). C cgen uses
|
|
// `$%lld` so 64-bit constants with bit 63 set show up as
|
|
// negative — e.g. FNV-1a's offset basis prints as
|
|
// $-3750763034362895579, not $14695981039346656037.
|
|
emitline("\tMOVQ\t$");
|
|
emitint(n.uval: i64);
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
if (k == nkind.N_FLOATLIT) {
|
|
// The bits come from n.uval — the parser populates it from
|
|
// the lexer's bitcast of t.fval.
|
|
cgfloatbits(c, n.uval);
|
|
// #104: narrow the double in X0 to single for an f32 literal.
|
|
if (isf32type(c, n)) {
|
|
emitline("\tCVTSD2SS\tX0, X0\n");
|
|
};
|
|
return;
|
|
};
|
|
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 lookup routes through the
|
|
// element-aware helper, which carries the loose first-slice-shape
|
|
// fallback (cstage type_assignable stand-in) that flatvariantidx's
|
|
// strict typeeq below doesn't. Task #19; #66 refresh.
|
|
if (vt.kind == nkind.N_TSLICE) {
|
|
return flatslicevariantidx(c, tagged, vt.lhs);
|
|
};
|
|
// #66 Phase-N step 3: match by typeeq on vt's stamped tinfo
|
|
// (flatvariantidx), not vt's surface name.
|
|
return flatvariantidx(c, tagged, vt);
|
|
};
|
|
|
|
// 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) {
|
|
// str IS []u8: success arrives DX=ptr, CX=len, R8=cap
|
|
// (slot 32B). Move len out before cap overwrites CX
|
|
// (#1/Phase 3).
|
|
emitline("\tMOVQ\tCX, BX\n");
|
|
emitline("\tMOVQ\tR8, CX\n");
|
|
};
|
|
emitline("\tMOVQ\tDX, AX\n");
|
|
return;
|
|
};
|
|
|
|
// cgtryunw — `e!` aborts on the error variant via exit(1). 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) {
|
|
// str IS []u8: success arrives DX=ptr, CX=len, R8=cap
|
|
// (slot 32B). Move len out before cap overwrites CX
|
|
// (#1/Phase 3).
|
|
emitline("\tMOVQ\tCX, BX\n");
|
|
emitline("\tMOVQ\tR8, CX\n");
|
|
};
|
|
emitline("\tMOVQ\tDX, AX\n");
|
|
return;
|
|
};
|
|
|
|
fn cgtypetest(c: *cgen, n: *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 = {
|
|
// str IS []u8: the (ptr, len, cap) triple — ptr in AX, len in BX,
|
|
// cap in CX. A static literal has no spare storage, so cap = len
|
|
// (#1/Phase 3). Call sites that expect a str arg pick these up.
|
|
let nstr: str = n.str;
|
|
let lab: str = internstrlit(c, nstr);
|
|
emitline("\tLEAQ\t");
|
|
emitbytes( lab.ptr, lab.len: u64);
|
|
emitline("(SB), AX\n");
|
|
emitline("\tMOVQ\t$");
|
|
emitint(nstr.len: i64);
|
|
emitline(", BX\n");
|
|
emitline("\tMOVQ\t$");
|
|
emitint(nstr.len: i64);
|
|
emitline(", CX\n");
|
|
return;
|
|
};
|
|
|
|
fn cgident(c: *cgen, n: *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) {
|
|
// str IS []u8: load (ptr,len,cap) into AX/BX/CX,
|
|
// identical to the slice arm below (#1/Phase 3).
|
|
emitline("\tMOVQ\t");
|
|
emitoff((off + 8): i64);
|
|
emitline("(BP), BX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitoff((off + 16): i64);
|
|
emitline("(BP), CX\n");
|
|
};
|
|
if (issl) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff((off + 8): i64);
|
|
emitline("(BP), BX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitoff((off + 16): i64);
|
|
emitline("(BP), CX\n");
|
|
};
|
|
return;
|
|
};
|
|
// 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");
|
|
// str IS []u8: cap = len for a static def literal
|
|
// (#1/Phase 3).
|
|
emitline("\tMOVQ\t$");
|
|
emitint(bytes.len: i64);
|
|
emitline(", CX\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) {
|
|
// str IS []u8: both str and slice carry a third 8B
|
|
// (cap); load it unconditionally. The address holder CX
|
|
// is overwritten by the cap as the last step, after
|
|
// ptr/len are already loaded (#1/Phase 3).
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, nm);
|
|
emitline("(SB), CX\n");
|
|
emitline("\tMOVQ\t(CX), AX\n");
|
|
emitline("\tMOVQ\t8(CX), BX\n");
|
|
emitline("\tMOVQ\t16(CX), CX\n");
|
|
return;
|
|
};
|
|
// Float global: same LEAQ-indirect shape, since MOVSS/
|
|
// MOVSD have no D_EXTERN operand form in w6a. Signed-narrow
|
|
// scalar globals route through the same LEAQ scratch since
|
|
// MOVSXD/MOVSWQ/MOVSBQ also have no D_EXTERN form.
|
|
let lvtnode: *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;
|
|
};
|
|
|
|
// cgslicehdr — load the 24B slice/str header at base+0 into the
|
|
// (AX=ptr, BX=len, CX=cap) triple. base holds the element address;
|
|
// the load that targets base destroys it, so that word is emitted
|
|
// LAST. Order otherwise mirrors the slice-field arm (len, cap, ptr).
|
|
// Shared by the cgindex str-element arms (caller does the kind-gate)
|
|
// and, later, the typeassert str-variant leaf (#9). c retained unused
|
|
// for callsite symmetry with cstage cgslicehdr.
|
|
fn cgslicehdr(c: *cgen, base: str) void = {
|
|
if (!streq(base, "BX")) { emitmovqload(8i64, base, "BX"); };
|
|
if (!streq(base, "CX")) { emitmovqload(16i64, base, "CX"); };
|
|
if (!streq(base, "AX")) { emitmovqload(0i64, base, "AX"); };
|
|
if (streq(base, "BX")) { emitmovqload(8i64, base, "BX"); };
|
|
if (streq(base, "CX")) { emitmovqload(16i64, base, "CX"); };
|
|
if (streq(base, "AX")) { emitmovqload(0i64, base, "AX"); };
|
|
};
|
|
|
|
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;
|
|
// #1/Phase 3: str and slice are both 24B (and a >16B struct is
|
|
// 24B+ too), so the header branches below MUST gate on KIND
|
|
// (elemisstr/elemisslice, mirroring cstage's elem_is_str||
|
|
// elem_is_slice), not a bare `esz == primtypesize("str")` size
|
|
// check — a size gate would route a plain >16B struct into the
|
|
// 3-word {ptr,len,cap} load and diverge from cstage (#60 collision
|
|
// class; sentinel 754).
|
|
let elemisstr: bool = false;
|
|
let elemisslice: bool = false;
|
|
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) {
|
|
// `s.ptr[i]` / struct-field index: stride is the
|
|
// checker-stamped element tinfo's natural size, the
|
|
// same idiom as the N_INDEX-base arm below (#60/#72).
|
|
// cstage idx_eff(base->type)->sub->size (cmd/w6c/
|
|
// cgen.c:3517-18). esz-only — N_DOT-base signedness
|
|
// stays unset, as before.
|
|
let dt: *tinfo = n.type_: *tinfo;
|
|
if (dt != nil) { esz = dt.size: i32; elemisstr = typeisstr(dt); elemisslice = typeisslice(dt); };
|
|
} else { if (base.kind == nkind.N_INDEX) {
|
|
// #60: chained `names[i][k]` — n.type_ is the checker-
|
|
// stamped outer element tinfo (indexresult over the inner
|
|
// index's value type). cstage reads base->type->sub->size
|
|
// for esz (cmd/w6c/cgen.c:2070-2071). Drops the
|
|
// indexvaluetnode walk.
|
|
let et: *tinfo = n.type_: *tinfo;
|
|
if (et != nil) {
|
|
esz = et.size: i32;
|
|
signed_elem = typeissigned(et);
|
|
};
|
|
};};};
|
|
};
|
|
// 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;
|
|
};
|
|
};
|
|
elemisstr = isstrtype(c, etn);
|
|
elemisslice = isslicetype(c, etn);
|
|
};
|
|
};
|
|
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;
|
|
};
|
|
// str/slice element: load the full (ptr, len, cap) header into
|
|
// (AX, BX, CX) — both are 24B since #1, so cap must survive.
|
|
// Kind-gate on isstrtype||isslicetype, never size==24 (a >16B
|
|
// struct is 24B+ too but takes the struct-copy path). Base is BX.
|
|
if (elemisstr || elemisslice) {
|
|
cgslicehdr(c, "BX");
|
|
return;
|
|
};
|
|
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/slice element: full (ptr, len, cap) header into (AX, BX, CX);
|
|
// cap must survive (#1). Kind-gate, never size==24. Base BX.
|
|
if (elemisstr || elemisslice) {
|
|
cgslicehdr(c, "BX");
|
|
return;
|
|
};
|
|
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;
|
|
};
|
|
// str/slice element via fallback base: full (ptr, len, cap) header
|
|
// into (AX, BX, CX); cap must survive (#1). Kind-gate, never
|
|
// size==24. Base AX.
|
|
if (elemisstr || elemisslice) {
|
|
cgslicehdr(c, "AX");
|
|
return;
|
|
};
|
|
let lop3: str = loadopsz(signed_elem, esz);
|
|
emitline("\t");
|
|
emitline(lop3);
|
|
emitline("\t(AX), AX\n");
|
|
return;
|
|
};
|
|
|
|
// cgbasecap — load the capacity of a sub-slice's UNDERLYING storage
|
|
// into `dst` for the #20 cap = base_cap - lo formula (drew: harec
|
|
// eval.c:1017 slice cap-=start / eval.c:1024 array cap=length-start;
|
|
// ensure.ha:4-8 distinct capacity field). array [N]T -> N (literal);
|
|
// slice/str -> the .capacity word in the header at +16 (mirrors the
|
|
// hi-default +8 length dispatch, emitted unconditionally). Returns
|
|
// false when base_cap isn't cleanly available so the caller keeps the
|
|
// prior cap=len: a non-ident base (its header cap was discarded;
|
|
// recomputing would re-evaluate a possibly side-effecting base -- #74,
|
|
// which also owns the pre-existing defaulted-hi len gap there), or
|
|
// a GLOBAL str base (no +16 load here, #73 -- matching the cstage
|
|
// carve-out keeps both stages byte-identical). cstage twin:
|
|
// cmd/w6c/cgen.c cg_base_cap.
|
|
fn cgbasecap(c: *cgen, base: *node, dst: str) bool = {
|
|
if (base == nil) { return false; };
|
|
if (base.kind != nkind.N_IDENT) { return false; };
|
|
let baselocal: *local = localfindnode(c, base.str);
|
|
if (baselocal != nil) {
|
|
let tn: *node = baselocal.tnode;
|
|
if (tn == nil) { return false; };
|
|
if (tn.kind == nkind.N_TARRAY) {
|
|
let lenn: *node = tn.rhs;
|
|
if (lenn == nil) { return false; };
|
|
if (lenn.kind != nkind.N_INTLIT) { return false; };
|
|
emitline("\tMOVQ\t$");
|
|
emituint(lenn.uval);
|
|
emitline(", ");
|
|
emitline(dst);
|
|
emitline("\n");
|
|
return true;
|
|
};
|
|
if (tn.kind == nkind.N_TSLICE) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff((baselocal.off + 16): i64);
|
|
emitline("(BP), ");
|
|
emitline(dst);
|
|
emitline("\n");
|
|
return true;
|
|
};
|
|
if (tn.kind == nkind.N_TNAME) {
|
|
if (streq(tn.str, "str")) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff((baselocal.off + 16): i64);
|
|
emitline("(BP), ");
|
|
emitline(dst);
|
|
emitline("\n");
|
|
return true;
|
|
};
|
|
};
|
|
return false;
|
|
};
|
|
let gt: *node = letvartnode(c, base.str);
|
|
if (gt == nil) { return false; };
|
|
if (gt.kind == nkind.N_TARRAY) {
|
|
let lenn: *node = gt.rhs;
|
|
if (lenn == nil) { return false; };
|
|
if (lenn.kind != nkind.N_INTLIT) { return false; };
|
|
emitline("\tMOVQ\t$");
|
|
emituint(lenn.uval);
|
|
emitline(", ");
|
|
emitline(dst);
|
|
emitline("\n");
|
|
return true;
|
|
};
|
|
if (gt.kind == nkind.N_TSLICE) {
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, base.str);
|
|
emitline("(SB), ");
|
|
emitline(dst);
|
|
emitline("\n");
|
|
emitline("\tMOVQ\t16(");
|
|
emitline(dst);
|
|
emitline("), ");
|
|
emitline(dst);
|
|
emitline("\n");
|
|
return true;
|
|
};
|
|
return false;
|
|
};
|
|
|
|
// cgslice — `base[lo:hi]` as a slice value. Leaves (AX=base+lo*esz,
|
|
// BX=hi-lo, CX=base_cap-lo) so callers can route to a slice slot,
|
|
// return, or arg with the same triple ABI. cap is the storage
|
|
// remaining to the base's end (#20, Go/Hare-identical) via cgbasecap.
|
|
// ptr advances by BYTES (lo*esz, #76; ref/hare/rt/ensure.ha:30
|
|
// membsz-unit); esz from the type table, mirroring the cgindex idiom.
|
|
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;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// esz from the type table for an N_IDENT base (#76; mirrors the
|
|
// cgindex idiom). Non-ident base stays esz=1 -> ptr unscaled,
|
|
// matching cstage's base->kind==N_IDENT gate.
|
|
let esz: i32 = 1;
|
|
if (baselocal != nil) {
|
|
esz = elemsizeofc(c, baselocal.tnode);
|
|
} else { if (globaltn != nil) {
|
|
esz = elemsizeofc(c, globaltn);
|
|
};};
|
|
// 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");
|
|
// ptr = base + lo*esz (#76; ensure.ha:30 membsz-unit).
|
|
// DX=lo*esz; CX=lo PRESERVED for len + cap (#20).
|
|
if (esz > 1) {
|
|
emitline("\tMOVQ\t$");
|
|
emitint(esz: i64);
|
|
emitline(", DX\n");
|
|
emitline("\tIMULQ\tCX, DX\n");
|
|
emitline("\tADDQ\tDX, AX\n");
|
|
} else {
|
|
emitline("\tADDQ\tCX, AX\n");
|
|
};
|
|
emitline("\tSUBQ\tCX, BX\n");
|
|
// cap = base_cap - lo (#20); CX=lo, BX=len here.
|
|
if (cgbasecap(c, base, "DX")) {
|
|
emitline("\tSUBQ\tCX, DX\n");
|
|
emitline("\tMOVQ\tDX, CX\n");
|
|
} else {
|
|
emitline("\tMOVQ\tBX, CX\n");
|
|
};
|
|
};
|
|
|
|
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) {
|
|
// #67: gate on the stamped tinfo, not the node kind
|
|
// — matchscrutt now returns the scrutinee node itself
|
|
// for an N_DOT field (its .type_ is the tagged tinfo)
|
|
// rather than the resolved N_TTAGGED node.
|
|
if (istaggedtype(c, scrutt)) {
|
|
let r: i32 = -1;
|
|
if (pat.kind == nkind.N_TNAME) {
|
|
r = flatvariantidx(c, scrutt, pat);
|
|
} 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 IS []u8 — same 3-word {ptr,len,cap}
|
|
// as a 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. str folds onto the slice
|
|
// arm (#1/Phase 3 collapse; cite cstage
|
|
// cgen.c N_DOT *struct S2).
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), BX\n");
|
|
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg(fi.foff: i64, "BX");
|
|
emitline(", AX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg((fi.foff + 16): i64, "BX");
|
|
emitline(", CX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg((fi.foff + 8): i64, "BX");
|
|
emitline(", BX\n");
|
|
} 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 IS []u8 — same 3-word {ptr,len,cap}
|
|
// as a slice field: load (ptr, len, cap)
|
|
// into (AX, BX, CX). Base is BP so no
|
|
// aliasing — order doesn't matter. str
|
|
// folds onto the slice arm (#1/Phase 3
|
|
// collapse; cite cstage cgen.c N_DOT S1).
|
|
if (isstrtype(c, fi.tnode) || isslicetype(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");
|
|
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: scalar in an
|
|
// 8B slot, str in 24B — str IS []u8, #1/Phase 3). For a
|
|
// str element, load (ptr, len, cap) into (AX, BX, CX),
|
|
// the canonical slice-header ABI. No slice-element
|
|
// sibling here, so the triple is hand-authored; base is
|
|
// BP (frame, not a target reg) so ptr/len/cap order has
|
|
// no clobber risk.
|
|
if (lkind == 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.lhs);
|
|
tp = tp.next;
|
|
i += 1;
|
|
};
|
|
};
|
|
if (tp != nil) {
|
|
let tpt: *node = tp.lhs;
|
|
if (isstrtype(c, tpt)) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff((lc.off + foff + 0): i64);
|
|
emitline("(BP), AX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitoff((lc.off + foff + 8): i64);
|
|
emitline("(BP), BX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitoff((lc.off + foff + 16): i64);
|
|
emitline("(BP), CX\n");
|
|
return;
|
|
};
|
|
// f64/f32 tuple field must ride X0 via
|
|
// MOVSD/MOVSS; the integer load op left it
|
|
// in AX (#103 FACE Z). Mirrors the float
|
|
// local load above and cstage cgen.c:1462,
|
|
// 1838 (the #96 pattern).
|
|
if (isfloattype(c, tpt)) {
|
|
let mov: str = "MOVSD";
|
|
if (isf32type(c, tpt)) { mov = "MOVSS"; };
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\t");
|
|
emitoff((lc.off + foff): i64);
|
|
emitline("(BP), X0\n");
|
|
return;
|
|
};
|
|
let sz: i32 = slotsize(c, tpt);
|
|
let op: str = tnodeloadop(c, tpt, 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; };
|
|
// str IS []u8: .cap is valid on a str global too,
|
|
// not slice-only — mirrors cstage (#1/Phase 3, #11).
|
|
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;
|
|
};
|
|
// str IS []u8 — 3-word {ptr,len,cap}, the local
|
|
// slice-field arm (BP) retargeted to the CX global
|
|
// base. cap→CX LAST: CX is the base, so .ptr/.len
|
|
// must read first. cstage folds local+global in one
|
|
// base_reg arm; ww splits them, so this global arm
|
|
// carries its own lift (filed divergence task).
|
|
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");
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg((fi.foff + 16): i64, "CX");
|
|
emitline(", CX\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) || isslicetype(c, fi.tnode)) {
|
|
// str/slice: the 3-word {ptr,len,cap}
|
|
// slice header (#1). AX holds the
|
|
// element base, so load .ptr (which
|
|
// targets AX) LAST. Matches the
|
|
// caseB *struct slice arm and
|
|
// cgslicehdr(D_AX).
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg((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;
|
|
};
|
|
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 leaftype: *tinfo = nil;
|
|
let slicedelta: i32 = -1;
|
|
let isglobal: bool = false;
|
|
let ptrroot: bool = false;
|
|
let pok: bool = dotchainresolve(c, n,
|
|
&rootname, &rootoff, &totaloff,
|
|
&leaftype, &slicedelta, &isglobal, &ptrroot);
|
|
if (pok) {
|
|
// `*T` root: load the pointer slot once into CX,
|
|
// then index every leaf at total_off off CX. Same
|
|
// emit shape as the global path (LEAQ → CX) — only
|
|
// the loader instruction differs.
|
|
let viacx: bool = isglobal || ptrroot;
|
|
if (slicedelta >= 0) {
|
|
if (viacx) {
|
|
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 (typeisstr(leaftype)) {
|
|
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 (typeisslice(leaftype)) {
|
|
// 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 (typeisfloat(leaftype)) {
|
|
let mov: str = "MOVSD";
|
|
if (typeisf32(leaftype)) { 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 = loadopsz(typeissigned(leaftype),
|
|
leaftype.slotsize: i32);
|
|
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) {
|
|
// #70 (#12): inner-struct layout via the stamped lhs.type_
|
|
// (peel *→struct) + tinfo.fields, replacing dotinnerstructptr's
|
|
// structinfo walk. Gate is strict-equal to the deleted helper:
|
|
// fire only when the chain root is a LOCAL ident AND every dot
|
|
// in the chain resolves through a *struct (dotinnerstructptr
|
|
// recursed per level on a *struct field and bailed on a by-
|
|
// value-struct intermediate). Reproducing that exactly avoids
|
|
// an untested widening past cstage; a deliberate widen, if ever
|
|
// wanted, is a future task with its own probe. Global-root
|
|
// chains stay in their pre-existing shared base-eval breakage
|
|
// (filed #27), untouched here.
|
|
let croot: *node = lhs;
|
|
let allptr: bool = true;
|
|
for (croot != nil && croot.kind == nkind.N_DOT) {
|
|
let ct: *tinfo = croot.type_: *tinfo;
|
|
for (ct != nil && ct.kind == tykind.TY_NAMED) { ct = ct.under; };
|
|
let okp: bool = false;
|
|
if (ct != nil) { if (ct.kind == tykind.TY_PTR) {
|
|
let cs: *tinfo = ct.sub;
|
|
for (cs != nil && cs.kind == tykind.TY_NAMED) { cs = cs.under; };
|
|
if (cs != nil) { if (cs.kind == tykind.TY_STRUCT) { okp = true; }; };
|
|
}; };
|
|
if (!okp) { allptr = false; };
|
|
croot = croot.lhs;
|
|
};
|
|
let it: *tinfo = nil;
|
|
if (allptr) { if (croot != nil) { if (croot.kind == nkind.N_IDENT) {
|
|
if (localfindnode(c, croot.str) != nil) {
|
|
it = lhs.type_: *tinfo;
|
|
};
|
|
}; }; };
|
|
for (it != nil && it.kind == tykind.TY_NAMED) { it = it.under; };
|
|
if (it != nil) { if (it.kind == tykind.TY_PTR) {
|
|
let st: *tinfo = it.sub;
|
|
for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; };
|
|
if (st != nil) { if (st.kind == tykind.TY_STRUCT) {
|
|
let tf: *tfield = st.fields;
|
|
for (tf != nil) {
|
|
if (streq(tf.name, fld)) {
|
|
let ft: *tinfo = tf.type_;
|
|
cgexpr(c, lhs); // AX = ptr to inner struct
|
|
// str IS []u8 — same 3-word {ptr,len,cap}
|
|
// as a slice field: load (ptr, len, cap)
|
|
// into (AX, BX, CX). AX is the *struct
|
|
// base, so load .ptr (which targets
|
|
// AX) LAST. str folds onto the slice
|
|
// arm (#1/Phase 3 collapse; cite cstage
|
|
// cgen.c N_DOT chained *struct caseB).
|
|
if (typeisstr(ft) || typeisslice(ft)) {
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg((tf.offset + 8u64): i64, "AX");
|
|
emitline(", BX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg((tf.offset + 16u64): i64, "AX");
|
|
emitline(", CX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg(tf.offset: i64, "AX");
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
// f64/f32 chained field: route through X0.
|
|
if (typeisfloat(ft)) {
|
|
let mov: str = "MOVSD";
|
|
if (typeisf32(ft)) { mov = "MOVSS"; };
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\t");
|
|
emitdispreg(tf.offset: i64, "AX");
|
|
emitline(", X0\n");
|
|
return;
|
|
};
|
|
let lop: str = loadopsz(typeissigned(ft), ft.slotsize: i32);
|
|
emitline("\t");
|
|
emitline(lop);
|
|
emitline("\t");
|
|
emitdispreg(tf.offset: i64, "AX");
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
tf = tf.tnext;
|
|
};
|
|
}; };
|
|
}; };
|
|
};
|
|
};
|
|
// Chained `(ident).f1.f2` read where f1 is a struct-by-value
|
|
// field. Mirror of the cgassign branch added for the same shape.
|
|
// Without this, `L.cur.kind` (cur a by-value struct of *L)
|
|
// falls into the SB-fallback and emits `MOVQ kind(SB), AX`.
|
|
// Kept as a fallback below the generalized walker above (placed
|
|
// earlier in cgdot) to preserve byte-identical output on shapes
|
|
// it already handles.
|
|
if (lhs != nil) {
|
|
if (lhs.kind == 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 leaftype: *tinfo = nil;
|
|
let slicedelta: i32 = -1;
|
|
let isglobal: bool = false;
|
|
let ptrroot: bool = false;
|
|
let pok: bool = dotchainresolve(c, opnd,
|
|
&rootname, &rootoff, &totaloff,
|
|
&leaftype, &slicedelta, &isglobal,
|
|
&ptrroot);
|
|
// `&` through a `*T`-rooted chain is a
|
|
// separate shape (would need MOVQ + LEAQ
|
|
// disp(CX), AX). Not exercised by current
|
|
// callers — skip and fall through.
|
|
if (ptrroot) { pok = false; };
|
|
if (pok) {
|
|
let extra: i32 = 0;
|
|
if (slicedelta >= 0) { extra = slicedelta; };
|
|
if (isglobal) {
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, rootname);
|
|
emitline("(SB), CX\n");
|
|
emitline("\tLEAQ\t");
|
|
emitdispreg((totaloff + extra): i64, "CX");
|
|
emitline(", AX\n");
|
|
} else {
|
|
emitline("\tLEAQ\t");
|
|
emitoff((rootoff + totaloff + extra): i64);
|
|
emitline("(BP), AX\n");
|
|
};
|
|
return;
|
|
};
|
|
};
|
|
};
|
|
// Shape 1/2 single-DOT on an IDENT base. Inspect
|
|
// the base's tnode to pick value-struct vs slice/
|
|
// str pseudo vs pointer-field.
|
|
if (opnd.lhs != nil) {
|
|
if (opnd.lhs.kind == 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; };
|
|
// str IS []u8: &str.cap is valid too, not slice-only
|
|
// — mirrors cstage (#1/Phase 3, #11).
|
|
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]`: stride is the checker-stamped
|
|
// element tinfo's natural size, mirroring
|
|
// cgindex's N_DOT arm so &p.ptr[i] and
|
|
// p.ptr[i] agree. cstage idx_eff(base->type)
|
|
// ->sub->size (cmd/w6c/cgen.c:3517-18). #72.
|
|
let dt: *tinfo = opnd.type_: *tinfo;
|
|
if (dt != nil) { esz = dt.size: i32; };
|
|
};};
|
|
};
|
|
cgexpr(c, idx);
|
|
if (esz > 1) {
|
|
emitline("\tMOVQ\t$");
|
|
emitint(esz: i64);
|
|
emitline(", CX\n");
|
|
emitline("\tIMULQ\tCX, AX\n");
|
|
};
|
|
if (isglobalarr) {
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, globalname);
|
|
emitline("(SB), BX\n");
|
|
} else { if (isglobalptr) {
|
|
emitline("\tMOVQ\t");
|
|
emitsymname(c, globalname);
|
|
emitline("(SB), BX\n");
|
|
} else { if (baselocal != nil) {
|
|
if (isarr) {
|
|
emitline("\tLEAQ\t");
|
|
emitoff(baselocal.off: i64);
|
|
emitline("(BP), BX\n");
|
|
} else {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(baselocal.off: i64);
|
|
emitline("(BP), BX\n");
|
|
};
|
|
} else {
|
|
// Complex base: spill scaled idx, eval
|
|
// base to AX, restore idx into BX.
|
|
// Mirrors cstage's lean three-line shape
|
|
// (cmd/w6c/cgen.c TK_AMP N_INDEX complex
|
|
// base 2104-2107); the prior MOVQ AX, BX
|
|
// + POPQ AX scratch shuffle was rule-10
|
|
// verbose-defensive on the wwstage side
|
|
// with no semantic asymmetry (task #21).
|
|
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) {
|
|
// f64/f32 result rides X0 (SSE), not AX — an integer MOVQ
|
|
// strands the value off the float ABI and the caller's
|
|
// MOVSD X0 reads stale bits (#96). Mirrors the float
|
|
// field/ident load idiom.
|
|
if (isfloattype(c, n)) {
|
|
let mov: str = "MOVSD";
|
|
if (isf32type(c, n)) { mov = "MOVSS"; };
|
|
emitline("\t"); emitline(mov); emitline("\t(AX), X0\n");
|
|
} else {
|
|
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;
|
|
if (n.op == tkind.TK_EQ) { isfcmp = true; };
|
|
if (n.op == tkind.TK_NEQ) { isfcmp = true; };
|
|
if (n.op == tkind.TK_LT) { isfcmp = true; };
|
|
if (n.op == tkind.TK_LE) { isfcmp = true; };
|
|
if (n.op == tkind.TK_GT) { isfcmp = true; };
|
|
if (n.op == tkind.TK_GE) { isfcmp = true; };
|
|
if (isfcmp) {
|
|
cgexpr(c, n.rhs);
|
|
emitline("\tSUBQ\t$8, SP\n");
|
|
emitline("\t"); emitline(mov); emitline("\tX0, (SP)\n");
|
|
cgexpr(c, n.lhs);
|
|
emitline("\t"); emitline(mov); emitline("\t(SP), X1\n");
|
|
emitline("\tADDQ\t$8, SP\n");
|
|
let ucomi: str = "UCOMISD";
|
|
if (fk == 1) { ucomi = "UCOMISS"; };
|
|
emitline("\t"); emitline(ucomi); emitline("\tX1, X0\n");
|
|
// IEEE-754: UCOMISD/SS sets PF=ZF=CF=1 on unordered (a
|
|
// NaN operand). Any relop with a NaN operand is
|
|
// unordered -> `!=` true, the other five false. PF must
|
|
// steer `!=`/`==`/`<`/`<=` (#97): JNE keys on ZF=0 so
|
|
// `nan != nan` came out false; JE/JB/JBE fire on the
|
|
// unordered ZF/CF. `>`/`>=` (JA/JAE) need CF=0, which
|
|
// unordered never gives, so they are ALREADY NaN-correct
|
|
// and stay byte-identical to the pre-#97 single-template
|
|
// arm — no redundant PF guard.
|
|
if (n.op == tkind.TK_NEQ) {
|
|
// not-equal OR unordered -> true
|
|
let t: str = mklabel(c, "ct");
|
|
let e: str = mklabel(c, "ce");
|
|
emitline("\tJNE\t"); emitline(t); emitline("\n");
|
|
emitline("\tJP\t"); emitline(t); emitline("\n");
|
|
emitline("\tMOVQ\t$0, AX\n");
|
|
emitline("\tJMP\t"); emitline(e); emitline("\n");
|
|
emitlabel(t);
|
|
emitline("\tMOVQ\t$1, AX\n");
|
|
emitlabel(e);
|
|
return;
|
|
};
|
|
if (n.op == tkind.TK_EQ || n.op == tkind.TK_LT ||
|
|
n.op == tkind.TK_LE) {
|
|
// unordered -> false; otherwise the ordered Jcc decides.
|
|
let jcc: str = "JE";
|
|
if (n.op == tkind.TK_LT) { jcc = "JB"; };
|
|
if (n.op == tkind.TK_LE) { jcc = "JBE"; };
|
|
let fl: str = mklabel(c, "cf");
|
|
let t: str = mklabel(c, "ct");
|
|
let e: str = mklabel(c, "ce");
|
|
emitline("\tJP\t"); emitline(fl); emitline("\n");
|
|
emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n");
|
|
emitlabel(fl);
|
|
emitline("\tMOVQ\t$0, AX\n");
|
|
emitline("\tJMP\t"); emitline(e); emitline("\n");
|
|
emitlabel(t);
|
|
emitline("\tMOVQ\t$1, AX\n");
|
|
emitlabel(e);
|
|
return;
|
|
};
|
|
// `>`/`>=`: JA/JAE already reject unordered (CF=1), so
|
|
// keep the pre-#97 single-template shape verbatim.
|
|
let jcc: str = "JA";
|
|
if (n.op == tkind.TK_GE) { jcc = "JAE"; };
|
|
let t: str = mklabel(c, "ct");
|
|
let e: str = mklabel(c, "ce");
|
|
emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n");
|
|
emitline("\tMOVQ\t$0, AX\n");
|
|
emitline("\tJMP\t"); emitline(e); emitline("\n");
|
|
emitlabel(t);
|
|
emitline("\tMOVQ\t$1, AX\n");
|
|
emitlabel(e);
|
|
return;
|
|
};
|
|
return;
|
|
};
|
|
|
|
cgexpr(c, n.rhs);
|
|
emitline("\tPUSHQ\tAX\n");
|
|
cgexpr(c, n.lhs);
|
|
emitline("\tPOPQ\tBX\n");
|
|
if (n.op == 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)) {
|
|
// str IS []u8: cgexpr leaves (AX=ptr,
|
|
// BX=len, CX=cap). Route the heap base
|
|
// through DX so all three survive — CX
|
|
// holds cap, BX holds len (#1/Phase 3).
|
|
emitline("\tMOVQ\t(SP), 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");
|
|
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.
|
|
// check.ww installparams promotes varp.lhs to
|
|
// []T (mirrors cstage check.c:455 tp->type
|
|
// wrap). Element predicates / esz read varp.lhs
|
|
// .lhs; Ken's gate: only deref when the wrap
|
|
// shape is confirmed N_TSLICE (mirrors cstage
|
|
// cgen.c:4352 `vsu->kind == TY_SLICE` guard).
|
|
let velem: *node = varp.lhs;
|
|
if (varp.lhs != nil
|
|
&& varp.lhs.kind == nkind.N_TSLICE) {
|
|
velem = varp.lhs.lhs;
|
|
};
|
|
let esz: i32 = 8;
|
|
if (velem != nil) {
|
|
if (velem.kind == nkind.N_TNAME) {
|
|
let ps: i32 = primsize(velem.str);
|
|
if (ps > 0) { esz = ps; }
|
|
else { esz = slotsize(c, velem); };
|
|
} else {
|
|
esz = slotsize(c, velem);
|
|
};
|
|
};
|
|
if (esz < 1) { esz = 1; };
|
|
let velemtagged: bool = istaggedtype(c, velem);
|
|
let velemstr: bool = isstrtype(c, velem);
|
|
let velemslice: bool = isslicetype(c, velem);
|
|
let doff: i32 = 0;
|
|
if (nvar > 0) {
|
|
doff = localadd(c, dname, nvar * esz, nil);
|
|
};
|
|
// #60: vararg gather builds a {ptr,len,cap} slice
|
|
// descriptor — route through tyslicesize so #34's
|
|
// slice-header bump propagates here. varp.lhs is
|
|
// already the []T wrap from installparams, so we
|
|
// consume it directly (re-slicewrap → [][]T).
|
|
let soff: i32 = localadd(c, sname, tyslicesize(): i32,
|
|
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) {
|
|
// dst is the per-element tagged type;
|
|
// pass velem (cstage cgen.c:4382 passes
|
|
// velem, not the slice wrap vsu).
|
|
cgwidentaggedstore(c, velem.type_: *tinfo,
|
|
aa2, "BP", slot, esz);
|
|
} else { if (velemstr) {
|
|
cgexpr(c, aa2);
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitoff(slot: i64);
|
|
emitline("(BP)\n");
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitoff((slot + 8): i64);
|
|
emitline("(BP)\n");
|
|
} else { if (velemslice) {
|
|
cgexpr(c, aa2);
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitoff(slot: i64);
|
|
emitline("(BP)\n");
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitoff((slot + 8): i64);
|
|
emitline("(BP)\n");
|
|
emitline("\tMOVQ\tCX, ");
|
|
emitoff((slot + 16): i64);
|
|
emitline("(BP)\n");
|
|
} else {
|
|
cgexpr(c, aa2);
|
|
let op: str = tnodestoreop(c, varp.lhs, esz);
|
|
emitline("\t");
|
|
emitline(op);
|
|
emitline("\tAX, ");
|
|
emitoff(slot: i64);
|
|
emitline("(BP)\n");
|
|
}; }; };
|
|
j += 1;
|
|
aa2 = aa2.next;
|
|
};
|
|
if (nvar > 0) {
|
|
emitline("\tLEAQ\t");
|
|
emitoff(doff: i64);
|
|
emitline("(BP), AX\n");
|
|
} else {
|
|
emitline("\tXORQ\tAX, AX\n");
|
|
};
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitoff(soff: i64);
|
|
emitline("(BP)\n");
|
|
emitline("\tMOVQ\t$");
|
|
emitint(nvar: i64);
|
|
emitline(", AX\n");
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitoff((soff + 8): i64);
|
|
emitline("(BP)\n");
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitoff((soff + 16): i64);
|
|
emitline("(BP)\n");
|
|
let sn: *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;
|
|
// str IS []u8: 3-word arg, same as slice (#1/Phase 3).
|
|
if (nodeisstr(c, a)) { extra = 2; };
|
|
if (nodeisslice(c, a)) { extra = 2; };
|
|
// #21: tagged-CALL arg was pushed AX/DX/CX/R8 high→low
|
|
// by pushargsrev; size the per-arg pop to match so the
|
|
// next arg's POPQ doesn't land on residual tag/payload
|
|
// words and shift intidx out of sync.
|
|
let tcs: i32 = taggedcallslot(c, a);
|
|
if (tcs > 0) { extra = tcs / 8 - 1; };
|
|
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");
|
|
};
|
|
// str IS []u8: a str-returning callee leaves AX=ptr, BX=len,
|
|
// CX=cap — same as a slice, so there is no receive-side shuffle
|
|
// (#1/Phase 3).
|
|
return;
|
|
};
|
|
|
|
fn cgassign(c: *cgen, n: *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.type_: *tinfo,
|
|
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"; }; };
|
|
}; }; };
|
|
};
|
|
// A slice IS the same 3-word {ptr,len,cap}
|
|
// header as str (ref/hare/rt/ensure.ha:4-8),
|
|
// so `*p = sliceval` takes str's stash+store
|
|
// path (#79; precedent cgenstmt.ww:1631,
|
|
// cgenexpr.ww:1684). LIKE str this is
|
|
// alias-BLIND: a slice-alias `*Foo` / non-ident
|
|
// deref-store stays 1-word, the SAME divergence
|
|
// str carries; resolved-vs-syntactic detection
|
|
// is unified UP in #80, not patched here.
|
|
if (pe.kind == nkind.N_TSLICE) { elemstr = true; };
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
cgexpr(c, n.rhs);
|
|
// `*p = v` for *f64 / *f32: value sits in X0. Spill
|
|
// to the stack, evaluate the pointer (clobbers AX),
|
|
// then reload X0 and MOVSD/MOVSS through the pointer.
|
|
if (elemfloat) {
|
|
let mov: str = "MOVSD";
|
|
if (elemf32) { mov = "MOVSS"; };
|
|
emitline("\tSUBQ\t$8, SP\n");
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\tX0, (SP)\n");
|
|
cgexpr(c, inner);
|
|
emitline("\tMOVQ\tAX, BX\n");
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\t(SP), X0\n");
|
|
emitline("\tADDQ\t$8, SP\n");
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\tX0, (BX)\n");
|
|
return;
|
|
};
|
|
// str IS []u8: PUSHQ AX (ptr) first, then
|
|
// PUSHQ BX (len) + PUSHQ CX (cap) across the
|
|
// pointer eval which clobbers BX/CX. Pop drains
|
|
// cap (top) → 16(BX), then len, then ptr → 0(BX)
|
|
// with len → 8(BX) (#1/Phase 3).
|
|
emitline("\tPUSHQ\tAX\n");
|
|
if (elemstr) {
|
|
emitline("\tPUSHQ\tBX\n");
|
|
emitline("\tPUSHQ\tCX\n");
|
|
};
|
|
cgexpr(c, inner);
|
|
emitline("\tMOVQ\tAX, BX\n");
|
|
if (elemstr) {
|
|
emitline("\tPOPQ\tCX\n");
|
|
emitline("\tMOVQ\tCX, 16(BX)\n");
|
|
emitline("\tPOPQ\tCX\n");
|
|
emitline("\tPOPQ\tAX\n");
|
|
emitline("\tMOVQ\tAX, (BX)\n");
|
|
emitline("\tMOVQ\tCX, 8(BX)\n");
|
|
return;
|
|
};
|
|
emitline("\tPOPQ\tAX\n");
|
|
emitline("\t");
|
|
emitline(storeop);
|
|
emitline("\tAX, (BX)\n");
|
|
return;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// `*p OP= v` — compound assign through a pointer deref. The
|
|
// plain-assign branch above only fires for TK_ASSIGN; without
|
|
// this, compound ops fall through and emit nothing (silent
|
|
// no-op — exactly the trap that broke fmt.println). Mirror of
|
|
// cmd/w6c/cgen.c's N_UN/TK_STAR compound branch.
|
|
if (lhs != nil) {
|
|
if (lhs.kind == 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 = false;
|
|
if (pe != nil) {
|
|
unsignd = typeisunsigned(pe.type_: *tinfo);
|
|
};
|
|
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) {
|
|
// lhs.type_ is the checker-stamped element tinfo
|
|
// of the N_INDEX: esz is its natural size and the
|
|
// tagged-element gate (below) reads the same
|
|
// .type_ — same idiom as cgindex's n.type_ read
|
|
// (#60/#72). cstage idx_eff(base->type)->sub->size
|
|
// (cmd/w6c/cgen.c:3517-18).
|
|
let dt: *tinfo = lhs.type_: *tinfo;
|
|
if (dt != nil) { esz = dt.size: i32; elemtn = lhs; };
|
|
} else { if (base.kind == nkind.N_INDEX) {
|
|
// Chained-write write-side parallel of the
|
|
// cgindex N_INDEX-base arm (#24): `names[i][k]
|
|
// = v` (names: **u8) — outer element is u8 so
|
|
// the store is MOVB, not MOVQ. lhs.type_ is the
|
|
// checker-stamped outer element tinfo; esz is
|
|
// its natural size and the gate reads it via
|
|
// .type_. Drops the indexvaluetnode walk
|
|
// (#69/#61d, mirror #60). cstage: esz =
|
|
// idx_eff(base->type)->sub->size
|
|
// (cmd/w6c/cgen.c:3517-3518).
|
|
let et: *tinfo = lhs.type_: *tinfo;
|
|
if (et != nil) {
|
|
esz = et.size: i32;
|
|
elemtn = 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.type_: *tinfo, n.rhs,
|
|
"BP", scroff, slot_sz);
|
|
cgexpr(c, idx);
|
|
if (slot_sz > 1) {
|
|
emitline("\tMOVQ\t$");
|
|
emitint(slot_sz: i64);
|
|
emitline(", CX\n");
|
|
emitline("\tIMULQ\tCX, AX\n");
|
|
};
|
|
if (isglobalarr) {
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, globalname);
|
|
emitline("(SB), BX\n");
|
|
} else { if (isglobalptr) {
|
|
emitline("\tMOVQ\t");
|
|
emitsymname(c, globalname);
|
|
emitline("(SB), BX\n");
|
|
} else { if (baselocal != nil) {
|
|
let tn: *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
|
|
// str/slice: spill cap (CX) + len (BX) before
|
|
// computing the index so the post-index store can
|
|
// pop all three. str=24B (#1/Phase 3) collides with
|
|
// slice=24B, so this MUST gate on kind (cstage's
|
|
// elem_is_str||elem_is_slice, cmd/w6c/cgen.c:3581),
|
|
// never a bare esz==24: a >16B struct is also >=24B
|
|
// but takes the struct-copy path, not this 3-word
|
|
// {ptr,len,cap} store. Write-side mirror of the
|
|
// cgindex read-path gate (#7/754).
|
|
if (isstrtype(c, elemtn) || isslicetype(c, elemtn)) {
|
|
emitline("\tPUSHQ\tCX\n");
|
|
emitline("\tPUSHQ\tBX\n");
|
|
};
|
|
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
|
|
// str/slice: pop the saved len + cap and store
|
|
// all three words. Kind-gate, not size — see the
|
|
// spill site above (#1/Phase 3, #7/754).
|
|
if (isstrtype(c, elemtn) || isslicetype(c, elemtn)) {
|
|
emitline("\tMOVQ\tAX, (BX)\n");
|
|
emitline("\tPOPQ\tCX\n");
|
|
emitline("\tMOVQ\tCX, 8(BX)\n");
|
|
emitline("\tPOPQ\tCX\n");
|
|
emitline("\tMOVQ\tCX, 16(BX)\n");
|
|
return;
|
|
};
|
|
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/slice: rhs leaves AX=ptr,
|
|
// BX=len, CX=cap (#1/Phase 3). Spill
|
|
// all three across the index/address
|
|
// computation (IMULQ's CX scratch
|
|
// clobbers cap), stage &arr[i] in DX
|
|
// off the str AX/BX/CX convention
|
|
// (mirrors s.f=v), then store the full
|
|
// triple at foff+0/+8/+16.
|
|
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
|
|
cgexpr(c, n.rhs);
|
|
emitline("\tPUSHQ\tCX\n");
|
|
emitline("\tPUSHQ\tBX\n");
|
|
emitline("\tPUSHQ\tAX\n");
|
|
cgexpr(c, idx);
|
|
if (esz > 1) {
|
|
emitline("\tMOVQ\t$");
|
|
emitint(esz: i64);
|
|
emitline(", CX\n");
|
|
emitline("\tIMULQ\tCX, AX\n");
|
|
};
|
|
if (baseisarray) {
|
|
emitline("\tLEAQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), DX\n");
|
|
} else {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), DX\n");
|
|
};
|
|
emitline("\tADDQ\tAX, DX\n");
|
|
if (viaptr) { emitline("\tMOVQ\t(DX), DX\n"); };
|
|
emitline("\tPOPQ\tAX\n");
|
|
emitline("\tPOPQ\tBX\n");
|
|
emitline("\tPOPQ\tCX\n");
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitdispreg(fi.foff: i64, "DX");
|
|
emitline("\n");
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitdispreg((fi.foff + 8): i64, "DX");
|
|
emitline("\n");
|
|
emitline("\tMOVQ\tCX, ");
|
|
emitdispreg((fi.foff + 16): i64, "DX");
|
|
emitline("\n");
|
|
return;
|
|
};
|
|
// 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.type_: *tinfo,
|
|
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");
|
|
};
|
|
};
|
|
if (n.op == tkind.TK_ASSIGN) {
|
|
// str/slice field via *struct: str IS []u8, so both
|
|
// store the full 3-word {ptr,len,cap} from (AX,BX,CX).
|
|
// CX holds cap, so stage the struct addr in DX and
|
|
// store at foff/+8/+16 (#1/Phase 3).
|
|
if (isstrtype(c, fi.tnode) || 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.type_: *tinfo,
|
|
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/slice field direct: str IS []u8, so both store the
|
|
// full 3-word {ptr,len,cap} from (AX,BX,CX) at +0/+8/+16.
|
|
// BP base, no scratch reload needed; the generic fldstoreop
|
|
// below would write only AX, dropping .len/.cap (#1/Phase 3).
|
|
if (isstrtype(c, fi.tnode) || 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)) {
|
|
// str IS []u8: cgexpr left
|
|
// (AX=ptr, BX=len, CX=cap). CX
|
|
// holds cap, so stage the base
|
|
// addr in DX and store all three
|
|
// words (#1/Phase 3).
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, bn);
|
|
emitline("(SB), DX\n");
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitdispreg(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 `=` 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) {
|
|
// #70 (#12): inner-struct layout via the stamped
|
|
// base.type_ (peel *→struct) + tinfo.fields,
|
|
// replacing dotinnerstructptr's structinfo walk.
|
|
// Gate is strict-equal to the deleted helper: fire
|
|
// only when the chain root is a LOCAL ident AND every
|
|
// dot resolves through a *struct (dotinnerstructptr
|
|
// recursed per level on a *struct field, bailing on a
|
|
// by-value-struct intermediate). Reproducing that
|
|
// exactly avoids an untested widening past cstage.
|
|
// Global-root chains stay in their pre-existing shared
|
|
// base-eval breakage (filed #27).
|
|
let croot: *node = base;
|
|
let allptr: bool = true;
|
|
for (croot != nil && croot.kind == nkind.N_DOT) {
|
|
let ct: *tinfo = croot.type_: *tinfo;
|
|
for (ct != nil && ct.kind == tykind.TY_NAMED) { ct = ct.under; };
|
|
let okp: bool = false;
|
|
if (ct != nil) { if (ct.kind == tykind.TY_PTR) {
|
|
let cs: *tinfo = ct.sub;
|
|
for (cs != nil && cs.kind == tykind.TY_NAMED) { cs = cs.under; };
|
|
if (cs != nil) { if (cs.kind == tykind.TY_STRUCT) { okp = true; }; };
|
|
}; };
|
|
if (!okp) { allptr = false; };
|
|
croot = croot.lhs;
|
|
};
|
|
let it: *tinfo = nil;
|
|
if (allptr) { if (croot != nil) { if (croot.kind == nkind.N_IDENT) {
|
|
if (localfindnode(c, croot.str) != nil) {
|
|
it = base.type_: *tinfo;
|
|
};
|
|
}; }; };
|
|
for (it != nil && it.kind == tykind.TY_NAMED) { it = it.under; };
|
|
if (it != nil) { if (it.kind == tykind.TY_PTR) {
|
|
let st: *tinfo = it.sub;
|
|
for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; };
|
|
if (st != nil) { if (st.kind == tykind.TY_STRUCT) {
|
|
let tf: *tfield = st.fields;
|
|
for (tf != nil) {
|
|
if (streq(tf.name, fld)) {
|
|
let ft: *tinfo = tf.type_;
|
|
if (n.op == tkind.TK_ASSIGN) {
|
|
if (typeisstr(ft) || typeisslice(ft)) {
|
|
// str/slice: rhs leaves AX=ptr,
|
|
// BX=len, CX=cap (#1/Phase 3). Spill
|
|
// all three across the base-expr eval
|
|
// (it may clobber any reg), stage the
|
|
// *struct ptr in DX off the str
|
|
// AX/BX/CX convention (mirrors s.f=v),
|
|
// then store the full triple at
|
|
// foff+0/+8/+16.
|
|
cgexpr(c, n.rhs);
|
|
emitline("\tPUSHQ\tCX\n");
|
|
emitline("\tPUSHQ\tBX\n");
|
|
emitline("\tPUSHQ\tAX\n");
|
|
cgexpr(c, base);
|
|
emitline("\tMOVQ\tAX, DX\n");
|
|
emitline("\tPOPQ\tAX\n");
|
|
emitline("\tPOPQ\tBX\n");
|
|
emitline("\tPOPQ\tCX\n");
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitdispreg(tf.offset: i64, "DX");
|
|
emitline("\n");
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitdispreg((tf.offset + 8u64): i64, "DX");
|
|
emitline("\n");
|
|
emitline("\tMOVQ\tCX, ");
|
|
emitdispreg((tf.offset + 16u64): i64, "DX");
|
|
emitline("\n");
|
|
return;
|
|
};
|
|
// f64/f32 chained plain `=`: cgexpr rhs left value in
|
|
// X0. Spill to stack so cgexpr(base) can use AX, then
|
|
// reload and MOVSD/MOVSS into the slot.
|
|
if (typeisfloat(ft)) {
|
|
let mov: str = "MOVSD";
|
|
if (typeisf32(ft)) { mov = "MOVSS"; };
|
|
cgexpr(c, n.rhs);
|
|
emitline("\tSUBQ\t$8, SP\n");
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\tX0, (SP)\n");
|
|
cgexpr(c, base);
|
|
emitline("\tMOVQ\tAX, BX\n");
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\t(SP), X0\n");
|
|
emitline("\tADDQ\t$8, SP\n");
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\tX0, ");
|
|
emitdispreg(tf.offset: i64, "BX");
|
|
emitline("\n");
|
|
return;
|
|
};
|
|
cgexpr(c, n.rhs);
|
|
emitline("\tPUSHQ\tAX\n");
|
|
cgexpr(c, base);
|
|
emitline("\tMOVQ\tAX, BX\n");
|
|
emitline("\tPOPQ\tAX\n");
|
|
let sop: str = tnodestoreop(c, n.rhs, ft.slotsize: i32);
|
|
emitline("\t");
|
|
emitline(sop);
|
|
emitline("\tAX, ");
|
|
emitdispreg(tf.offset: i64, "BX");
|
|
emitline("\n");
|
|
return;
|
|
};
|
|
};
|
|
tf = tf.tnext;
|
|
};
|
|
}; };
|
|
}; };
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// Chained N_DOT spine write through value-struct fields (any
|
|
// depth) — `o.i.a = 10`, `v.a.b.c = …`. Also handles a slice/str
|
|
// pseudo-field leaf (`b.buf.len = 5`). Mirror of cstage cgen.c's
|
|
// chained-DOT write branch. Without this, depth ≥ 3 writes and
|
|
// the slice/str pseudo-field write through a value-struct chain
|
|
// silently emit no store. Only plain `=` is wired.
|
|
if (lhs != nil) {
|
|
if (lhs.kind == 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 leaftype: *tinfo = nil;
|
|
let slicedelta: i32 = -1;
|
|
let isglobal: bool = false;
|
|
let ptrroot: bool = false;
|
|
let yok: bool = dotchainresolve(c, lhs,
|
|
&rootname, &rootoff, &totaloff,
|
|
&leaftype, &slicedelta, &isglobal, &ptrroot);
|
|
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 (typeisstr(leaftype) || typeisslice(leaftype)) {
|
|
// str/slice: store ptr/len/cap. cgexpr leaves
|
|
// CX=cap, so the viacx base goes in DX (not CX) to
|
|
// avoid clobbering it — same as the single-dot str
|
|
// field store (#1/Phase 3).
|
|
cgexpr(c, n.rhs);
|
|
if (viacx) {
|
|
if (ptrroot) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(rootoff: i64);
|
|
emitline("(BP), DX\n");
|
|
} else {
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, rootname);
|
|
emitline("(SB), DX\n");
|
|
};
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitdispreg(totaloff: i64, "DX");
|
|
emitline("\n");
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitdispreg((totaloff + 8): i64, "DX");
|
|
emitline("\n");
|
|
emitline("\tMOVQ\tCX, ");
|
|
emitdispreg((totaloff + 16): i64, "DX");
|
|
emitline("\n");
|
|
} else {
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitoff((rootoff + totaloff): i64);
|
|
emitline("(BP)\n");
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitoff((rootoff + totaloff + 8): i64);
|
|
emitline("(BP)\n");
|
|
emitline("\tMOVQ\tCX, ");
|
|
emitoff((rootoff + totaloff + 16): i64);
|
|
emitline("(BP)\n");
|
|
};
|
|
return;
|
|
};
|
|
// TY_STRUCT terminal: three rhs shapes:
|
|
// - N_IDENT: word-copy from the rhs local slot
|
|
// (cgexpr is skipped — no whole-struct register
|
|
// convention for an arbitrary local).
|
|
// - N_CALL (added with #5): cgexpr leaves the
|
|
// value in AX/DX/CX per #4's cgreturn ABI; sized
|
|
// stores write only the declared field size.
|
|
// cgreturn touches only AX/DX/CX so for
|
|
// ptrroot/global we load the dst addr into BX
|
|
// (not CX) after the call to keep CX as the
|
|
// third value word.
|
|
// - N_STRUCTLIT (added with #5): field-by-field
|
|
// store; for ptrroot/global the dst addr is
|
|
// reloaded into BX before each store so cgexpr
|
|
// can clobber AX/BX between fields.
|
|
// #71: the struct-terminal cases below still drive the
|
|
// structinfo machinery (structnaturalsize /
|
|
// cgstructlitfill), so recover the struct NAME from the
|
|
// leaf tinfo's TY_NAMED wrapper. Peeled-TY_STRUCT +
|
|
// structlookup!=nil is byte-equal to the old `N_TNAME &&
|
|
// primsize==0 && structlookup` guard: a named non-struct
|
|
// (tagged/alias) peels to a non-STRUCT kind, and
|
|
// structlookup decides struct-ness off the same declared
|
|
// name either way.
|
|
let leafstruct: bool = false;
|
|
let leafname: str = "";
|
|
if (leaftype != nil) {
|
|
let lp: *tinfo = leaftype;
|
|
for (lp != nil && lp.kind == tykind.TY_NAMED) {
|
|
lp = lp.under;
|
|
};
|
|
if (lp != nil) {
|
|
if (lp.kind == tykind.TY_STRUCT) { leafstruct = true; };
|
|
};
|
|
if (leaftype.kind == tykind.TY_NAMED) {
|
|
leafname = leaftype.name;
|
|
};
|
|
};
|
|
if (n.rhs != nil
|
|
&& n.rhs.kind == nkind.N_CALL
|
|
&& leafstruct) {
|
|
let lsi: *structinfo = structlookup(c, leafname);
|
|
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
|
|
&& leafstruct) {
|
|
let lsi: *structinfo = structlookup(c, leafname);
|
|
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
|
|
&& leafstruct) {
|
|
let ssi: *structinfo = structlookup(c, leafname);
|
|
let srhs: *local = localfindnode(c, n.rhs.str);
|
|
if (ssi != nil) { if (srhs != nil) {
|
|
if (viacx) {
|
|
if (ptrroot) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(rootoff: i64);
|
|
emitline("(BP), CX\n");
|
|
} else {
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, rootname);
|
|
emitline("(SB), CX\n");
|
|
};
|
|
};
|
|
let ssz: i32 = 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 (typeisfloat(leaftype)) {
|
|
let mov: str = "MOVSD";
|
|
if (typeisf32(leaftype)) { 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;
|
|
};
|
|
// Scalar leaf store-op by size — the same size→op
|
|
// dispatch fieldstoreop used on the leaf fieldinfo, now
|
|
// keyed on the leaf tinfo's slot width (#71).
|
|
let sop: str = "MOVQ";
|
|
if (leaftype != nil) {
|
|
let ssz: i32 = leaftype.slotsize: i32;
|
|
if (ssz == 1) { sop = "MOVB"; }
|
|
else { if (ssz == 2) { sop = "MOVW"; }
|
|
else { if (ssz == 4) { sop = "MOVL"; }; }; };
|
|
};
|
|
cgexpr(c, n.rhs);
|
|
if (viacx) {
|
|
if (ptrroot) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(rootoff: i64);
|
|
emitline("(BP), CX\n");
|
|
} else {
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, rootname);
|
|
emitline("(SB), CX\n");
|
|
};
|
|
emitline("\t");
|
|
emitline(sop);
|
|
emitline("\tAX, ");
|
|
emitdispreg(totaloff: i64, "CX");
|
|
emitline("\n");
|
|
} else {
|
|
emitline("\t");
|
|
emitline(sop);
|
|
emitline("\tAX, ");
|
|
emitoff((rootoff + totaloff): i64);
|
|
emitline("(BP)\n");
|
|
};
|
|
return;
|
|
};
|
|
};
|
|
};
|
|
// Chained `(ident).f1.f2 = v` where f1 is a struct-by-value
|
|
// field. The earlier chained-DOT branch handles f1: *T (deref
|
|
// then store). This handles f1: T (in-place sub-struct), which
|
|
// would otherwise silently emit no store — lispcore's lexer had
|
|
// to flatten `cur.kind`/`cur.ival`/... into top-level fields to
|
|
// work around it. Only plain `=` is wired; compound on a by-
|
|
// value sub-field hasn't surfaced.
|
|
// Kept as fallback below the generalized walker for any shape
|
|
// the walker doesn't recognize.
|
|
if (lhs != nil) {
|
|
if (lhs.kind == 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) {
|
|
// str/slice top-level let: str IS []u8, so both store the
|
|
// full 3-word {ptr,len,cap}. Stash cap in DI before LEAQ
|
|
// overwrites CX, then store ptr/len/cap via &name(SB)
|
|
// (#1/Phase 3).
|
|
if (letvarisstr(c, nm) || letvarisslice(c, nm)) {
|
|
emitline("\tMOVQ\tCX, DI\n");
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, nm);
|
|
emitline("(SB), CX\n");
|
|
emitline("\tMOVQ\tAX, (CX)\n");
|
|
emitline("\tMOVQ\tBX, 8(CX)\n");
|
|
emitline("\tMOVQ\tDI, 16(CX)\n");
|
|
return;
|
|
};
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitsymname(c, nm);
|
|
emitline("(SB)\n");
|
|
return;
|
|
};
|
|
// Compound RMW for a top-level let: load through
|
|
// LEAQ + localloadop when the slot is narrow so
|
|
// a prior `*(&letname): *iN` deref-store doesn't
|
|
// leave stale upper bytes feeding the combine.
|
|
let glop: str = localloadop(c, lvftn);
|
|
if (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 = false;
|
|
if (lvftn != nil) {
|
|
unsignd = typeisunsigned(lvftn.type_: *tinfo);
|
|
};
|
|
if (!unsignd) {
|
|
unsignd = nodeisunsigned(c, n.rhs);
|
|
};
|
|
emitline("\tMOVQ\tAX, CX\n");
|
|
emitline("\tMOVQ\tBX, AX\n");
|
|
if (unsignd) {
|
|
emitline("\tMOVQ\t$0, DX\n");
|
|
emitline("\tDIVQ\tCX\n");
|
|
} else {
|
|
emitline("\tCQO\n");
|
|
emitline("\tIDIVQ\tCX\n");
|
|
};
|
|
if (n.op == 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");
|
|
};
|
|
// str IS []u8: store the cap word too, identical to
|
|
// the slice store (#1/Phase 3).
|
|
if (lcstr || lcsl) {
|
|
emitline("\tMOVQ\tCX, ");
|
|
emitoff((off + 16): i64);
|
|
emitline("(BP)\n");
|
|
};
|
|
return;
|
|
};
|
|
// Pick the load width for compound RMW. Signed-narrow
|
|
// locals must sign-extend the slot before the combine
|
|
// — ADDQ/SUBQ on amem reads 8B raw, which is wrong
|
|
// after a 4B deref-store leaves the upper bytes stale.
|
|
let llop: str = "MOVQ";
|
|
if (lcn != nil) { llop = localloadop(c, lcn.tnode); };
|
|
if (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) {
|
|
if (lcn.tnode != nil) {
|
|
unsignd = typeisunsigned(lcn.tnode.type_: *tinfo);
|
|
};
|
|
};
|
|
if (!unsignd) {
|
|
unsignd = nodeisunsigned(c, n.rhs);
|
|
};
|
|
emitline("\tMOVQ\tAX, CX\n");
|
|
emitline("\tMOVQ\tBX, AX\n");
|
|
if (unsignd) {
|
|
emitline("\tMOVQ\t$0, DX\n");
|
|
emitline("\tDIVQ\tCX\n");
|
|
} else {
|
|
emitline("\tCQO\n");
|
|
emitline("\tIDIVQ\tCX\n");
|
|
};
|
|
if (n.op == 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;
|
|
};
|
|
|
|
|