`type tkind = enum i32 { TK_NONE = 0, TK_EOF = 1, ... TK_LAST = 86 }`
replaces the 87-line `def TK_*: i32 = N` cluster in lib/ww/lex/tok.ww.
Numeric values explicit so 990_selfhost's byte-diff against the C-side
`Tkind` enum still passes.
All ~270 reference sites in lib/ww and selfhost/cmd/{wcc,wwdump}
sed-renamed `TK_X` → `tkind.TK_X`. Struct fields (`tok.kind`,
`parser.curkind`) intentionally kept as `i32` — making them `tkind`
shifted some byte-positions in the cgen output and broke 990/993/995
byte-identity probes without an obvious win.
To make the rename non-cascading on every signature, type_assignable
and unify_arith in cmd/wcc/check+type relax to allow enum ↔ int
mixing when storage matches (a `tkind` value flows into an `i32`
slot and vice versa, no explicit cast). This deviates from Hare's
strict enum semantics; doc'd as an explicit pragmatic relaxation
for the compiler's internal enum-shaped kinds. External user code
can still get the type-safety benefit if they declare their
parameters with the enum type.
combined.ww files regenerated by ww build.
1584 lines
46 KiB
Plaintext
1584 lines
46 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
|
|
// (N_INTLIT, N_RUNELIT, N_TRUE/FALSE/NIL, N_CAST) stay inline.
|
|
//
|
|
// The remainder of cgen lives in cgen.ww (foundation: types, emit
|
|
// primitives, the collect* tables, FFI/module maps) and cgenstmt.ww
|
|
// (cgstmt).
|
|
//
|
|
// `use cgenexpr;` is unnecessary at consumer sites — cgen.ww imports
|
|
// this file, so any caller of cgen transitively gets cgexpr.
|
|
|
|
use os;
|
|
use mem;
|
|
use ast;
|
|
use tok;
|
|
use typ;
|
|
use sym;
|
|
use strconv;
|
|
|
|
fn cgexpr(c: *cgen, n: *node) void = {
|
|
if (n == nil) { return; };
|
|
let k: i32 = n.kind;
|
|
|
|
if (k == N_INTLIT) {
|
|
// Print signed (i64), not unsigned (u64). C cgen uses
|
|
// `$%lld` so 64-bit constants with bit 63 set show up as
|
|
// negative — e.g. FNV-1a's offset basis prints as
|
|
// $-3750763034362895579, not $14695981039346656037.
|
|
emitline("\tMOVQ\t$");
|
|
emitint(n.uval: i64);
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
if (k == N_RUNELIT) {
|
|
emitline("\tMOVQ\t$");
|
|
emitint(n.uval: i64);
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
if (k == N_STRLIT) { cgstrlit(c, n); return; };
|
|
if (k == N_TRUE) {
|
|
emitline("\tMOVQ\t$1, AX\n");
|
|
return;
|
|
};
|
|
if (k == N_FALSE) {
|
|
emitline("\tMOVQ\t$0, AX\n");
|
|
return;
|
|
};
|
|
if (k == N_NIL) {
|
|
emitline("\tMOVQ\t$0, AX\n");
|
|
return;
|
|
};
|
|
if (k == 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 == N_IDENT) { cgident(c, n); return; };
|
|
|
|
if (k == N_INDEX) { cgindex(c, n); return; };
|
|
|
|
if (k == N_MATCH) { cgmatch(c, n); return; };
|
|
|
|
if (k == N_CAST) {
|
|
// Type casts are mostly no-ops at the asm level for our
|
|
// integer-shaped operands. Evaluate the source; AX holds
|
|
// the bits unchanged. (Sign- or zero-extending narrow loads
|
|
// to wider types is the loader's job, not cast's, in this
|
|
// minimal cgen.)
|
|
cgexpr(c, n.lhs);
|
|
return;
|
|
};
|
|
|
|
if (k == N_DOT) { cgdot(c, n); return; };
|
|
|
|
if (k == N_UN) { cgun(c, n); return; };
|
|
|
|
if (k == N_BIN) { cgbin(c, n); return; };
|
|
|
|
if (k == N_CALL) { cgcall(c, n); return; };
|
|
|
|
if (k == N_ASSIGN) { cgassign(c, n); return; };
|
|
|
|
if (k == N_TRYPROP) { cgtryprop(c, n); return; };
|
|
if (k == N_TRYUNW) { cgtryunw(c, n); return; };
|
|
if (k == N_TYPETEST) { cgtypetest(c, n); return; };
|
|
if (k == N_TYPEASSERT) { cgtypeassert(c, n); return; };
|
|
};
|
|
|
|
// cgtagvariantidx — find the 0-based variant index of `vt` inside the
|
|
// tagged-union type expression `tagged`. -1 if `tagged` isn't an
|
|
// N_TTAGGED or no variant matches. Mirrors the lookup that cgmatch
|
|
// does inline; pulled out so `is` / `as` can reuse it.
|
|
fn cgtagvariantidx(tagged: *node, vt: *node) i32 = {
|
|
if (tagged == nil) { return -1; };
|
|
if (vt == nil) { return -1; };
|
|
if (tagged.kind != N_TTAGGED) { return -1; };
|
|
let want: str;
|
|
want.ptr = nil; want.len = 0;
|
|
if (vt.kind == N_TNAME) { want = vt.str; };
|
|
if (want.len == 0) { return -1; };
|
|
let v: *node = tagged.list;
|
|
let idx: i32 = 0;
|
|
for (v != nil) {
|
|
if (v.kind == N_TNAME) {
|
|
if (streq(v.str, want)) { return idx; };
|
|
};
|
|
v = v.next;
|
|
idx += 1;
|
|
};
|
|
return -1;
|
|
};
|
|
|
|
// 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 == N_CALL) {
|
|
let callee: *node = n.lhs.lhs;
|
|
if (callee != nil) {
|
|
let cname: str;
|
|
cname.ptr = nil; cname.len = 0;
|
|
if (callee.kind == N_IDENT) { cname = callee.str; };
|
|
if (callee.kind == N_DOT) { cname = callee.str; };
|
|
if (cname.len > 0) {
|
|
let rt: *node = fnretlookup(c, cname);
|
|
if (rt != nil) {
|
|
if (rt.kind == N_TTAGGED) {
|
|
let first: *node = rt.list;
|
|
if (first != nil) {
|
|
if (isstrtype(c, first)) {
|
|
succisstr = true;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
if (succisstr) {
|
|
emitline("\tMOVQ\tCX, BX\n");
|
|
};
|
|
emitline("\tMOVQ\tDX, AX\n");
|
|
return;
|
|
};
|
|
|
|
// cgtryunw — `e!` aborts on the error variant via exit(1). Legacy
|
|
// semantics (success tag = 0).
|
|
fn cgtryunw(c: *cgen, n: *node) void = {
|
|
cgexpr(c, n.lhs);
|
|
let cl: str = mklabel(c, "tryunw_ok");
|
|
emitline("\tCMPQ\t$0, AX\n");
|
|
emitline("\tJE\t");
|
|
emitline(cl);
|
|
emitline("\n");
|
|
emitline("\tMOVQ\t$1, DI\n\tMOVQ\t$60, AX\n\tSYSCALL\n");
|
|
emitlabel(cl);
|
|
// Unwrap success value. (Same shuffle pattern as cgtryprop.)
|
|
let succisstr: bool = false;
|
|
if (n.lhs != nil) {
|
|
if (n.lhs.kind == N_CALL) {
|
|
let callee: *node = n.lhs.lhs;
|
|
if (callee != nil) {
|
|
let cname: str;
|
|
cname.ptr = nil; cname.len = 0;
|
|
if (callee.kind == N_IDENT) { cname = callee.str; };
|
|
if (callee.kind == N_DOT) { cname = callee.str; };
|
|
if (cname.len > 0) {
|
|
let rt: *node = fnretlookup(c, cname);
|
|
if (rt != nil) {
|
|
if (rt.kind == N_TTAGGED) {
|
|
let first: *node = rt.list;
|
|
if (first != nil) {
|
|
if (isstrtype(c, first)) {
|
|
succisstr = true;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
if (succisstr) {
|
|
emitline("\tMOVQ\tCX, BX\n");
|
|
};
|
|
emitline("\tMOVQ\tDX, AX\n");
|
|
return;
|
|
};
|
|
|
|
fn cgtypetest(c: *cgen, n: *node) void = {
|
|
// `e is T` — load the lhs's tag, compare against T's variant
|
|
// index, set AX = (tag == idx). Result type is bool.
|
|
//
|
|
// Slot resolution is inlined (rather than factored into a helper
|
|
// with output parameters): wwstage cgen has a trap with i32
|
|
// stored via *i32 in this context — direct assignment of the
|
|
// local works, indirection through &scrutoff drops sign bits.
|
|
let lhs: *node = n.lhs;
|
|
let scrutoff: i32 = 0;
|
|
let scrutt: *node = nil;
|
|
if (lhs != nil) {
|
|
if (lhs.kind == N_IDENT) {
|
|
let lc: *local = localfindnode(c, lhs.str);
|
|
if (lc != nil) {
|
|
scrutoff = lc.off;
|
|
scrutt = resolvetype(c, lc.tnode);
|
|
};
|
|
};
|
|
};
|
|
let want: i32 = cgtagvariantidx(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 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: i32 = e.kind;
|
|
if (k == N_DOT) {
|
|
if (e.lhs != nil) {
|
|
if (e.lhs.kind == N_IDENT) {
|
|
if (enumlookup(c, e.lhs.str) != nil) { return true; };
|
|
};
|
|
};
|
|
};
|
|
if (k == N_IDENT) {
|
|
let lc: *local = localfindnode(c, e.str);
|
|
if (lc != nil) {
|
|
if (lc.tnode != nil) {
|
|
if (lc.tnode.kind == N_TNAME) {
|
|
if (enumlookup(c, lc.tnode.str) != nil) { return true; };
|
|
};
|
|
};
|
|
};
|
|
};
|
|
if (k == N_BIN) {
|
|
if (isenumexpr(c, e.lhs)) { return true; };
|
|
if (isenumexpr(c, e.rhs)) { return true; };
|
|
};
|
|
if (k == 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 == N_TENUM) { return true; };
|
|
if (t.kind == 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 == N_IDENT) {
|
|
let lc: *local = localfindnode(c, lhs.str);
|
|
if (lc != nil) {
|
|
scrutoff = lc.off;
|
|
scrutt = resolvetype(c, lc.tnode);
|
|
};
|
|
};
|
|
};
|
|
let want: i32 = cgtagvariantidx(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 cgstrlit(c: *cgen, n: *node) void = {
|
|
// Result is the (ptr, len) pair: ptr in AX, len in BX. Call
|
|
// sites that expect a str arg pick these up directly.
|
|
let nstr: str = n.str;
|
|
let lab: str = internstrlit(c, nstr);
|
|
emitline("\tLEAQ\t");
|
|
os.write(1, lab.ptr, lab.len: u64);
|
|
emitline("(SB), AX\n");
|
|
emitline("\tMOVQ\t$");
|
|
emitint(nstr.len: i64);
|
|
emitline(", BX\n");
|
|
return;
|
|
};
|
|
|
|
fn cgident(c: *cgen, n: *node) void = {
|
|
let nm: str = n.str;
|
|
let lc: *local = localfindnode(c, nm);
|
|
if (lc != nil) {
|
|
let off: i32 = lc.off;
|
|
emitline("\tMOVQ\t");
|
|
emitoff(off: i64);
|
|
emitline("(BP), AX\n");
|
|
// str local: also load the len half into BX.
|
|
if (isstrtype(c, lc.tnode)) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff((off + 8): i64);
|
|
emitline("(BP), BX\n");
|
|
};
|
|
// slice local: load (ptr, len, cap) into (AX, BX, CX).
|
|
if (isslicetype(c, lc.tnode)) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff((off + 8): i64);
|
|
emitline("(BP), BX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitoff((off + 16): i64);
|
|
emitline("(BP), CX\n");
|
|
};
|
|
return;
|
|
};
|
|
// Top-level `def` constant — load from its DATA symbol.
|
|
if (deflookup(c, nm)) {
|
|
emitline("\tMOVQ\t");
|
|
emitsymname(c, nm);
|
|
emitline("(SB), AX\n");
|
|
return;
|
|
};
|
|
// Fn-name used as a value (e.g. `let f = some_fn;` or
|
|
// `... = some_fn;`). LEAQ the symbol address into AX. The
|
|
// emitsymname helper handles ffiresolve and module-mangling
|
|
// in one go, so a body-less FFI binding emits the C symbol
|
|
// it was declared with via @symbol(), not the ww-side ident.
|
|
let rt: *node = fnretlookup(c, nm);
|
|
if (rt != nil) {
|
|
emitline("\tLEAQ\t");
|
|
emitsymname(c, nm);
|
|
emitline("(SB), AX\n");
|
|
return;
|
|
};
|
|
return;
|
|
};
|
|
|
|
fn cgindex(c: *cgen, n: *node) void = {
|
|
// Element-size-aware load: u8-element bases use MOVZBQ,
|
|
// 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 baselocal: *local = nil;
|
|
if (base != nil) {
|
|
if (base.kind == N_IDENT) {
|
|
let bn: str = base.str;
|
|
baselocal = localfindnode(c, bn);
|
|
if (baselocal != nil) { esz = elemsizeof(baselocal.tnode); };
|
|
} else { if (base.kind == N_DOT) {
|
|
esz = indexbaseesz(c, base);
|
|
};};
|
|
};
|
|
cgexpr(c, idx);
|
|
if (esz > 1) {
|
|
emitline("\tMOVQ\t$");
|
|
emitint(esz: i64);
|
|
emitline(", CX\n");
|
|
emitline("\tIMULQ\tCX, AX\n");
|
|
};
|
|
if (baselocal != nil) {
|
|
let tn: *node = baselocal.tnode;
|
|
let isarray: bool = false;
|
|
if (tn != nil) { if (tn.kind == 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");
|
|
// str element (16B): load (ptr, len) into (AX, BX) so
|
|
// the value flows through the str-rhs convention.
|
|
if (esz == 16) {
|
|
emitline("\tMOVQ\t8(BX), CX\n");
|
|
emitline("\tMOVQ\t(BX), AX\n");
|
|
emitline("\tMOVQ\tCX, BX\n");
|
|
return;
|
|
};
|
|
if (esz == 1) { emitline("\tMOVZBQ\t(BX), AX\n"); }
|
|
else { emitline("\tMOVQ\t(BX), AX\n"); };
|
|
return;
|
|
};
|
|
// Generic fallback when base isn't a plain ident.
|
|
emitline("\tPUSHQ\tAX\n");
|
|
cgexpr(c, base);
|
|
emitline("\tPOPQ\tBX\n");
|
|
emitline("\tADDQ\tBX, AX\n");
|
|
if (esz == 16) {
|
|
emitline("\tMOVQ\t8(AX), BX\n");
|
|
emitline("\tMOVQ\t(AX), AX\n");
|
|
return;
|
|
};
|
|
if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); }
|
|
else { emitline("\tMOVQ\t(AX), AX\n"); };
|
|
return;
|
|
};
|
|
|
|
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 == N_IDENT) {
|
|
let lc: *local = localfindnode(c, scrut.str);
|
|
if (lc != nil) {
|
|
scrutoff = lc.off;
|
|
scrutt = resolvetype(c, lc.tnode);
|
|
};
|
|
};
|
|
};
|
|
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);
|
|
// 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 == N_TPTR) { cur_tag = ptr_tag; }
|
|
else { if (ptr_tag == 0) { cur_tag = 1; }; };
|
|
emitline("\tMOVQ\t");
|
|
emitoff(scrutoff: i64);
|
|
emitline("(BP), AX\n");
|
|
emitline("\tCMPQ\t$0, AX\n");
|
|
if (cur_tag == ptr_tag) {
|
|
emitline("\tJE\t");
|
|
} else {
|
|
emitline("\tJNE\t");
|
|
};
|
|
emitline(nxt);
|
|
emitline("\n");
|
|
} else {
|
|
let want: i32 = 0;
|
|
if (scrutt != nil) {
|
|
if (scrutt.kind == N_TTAGGED) {
|
|
let patname: str;
|
|
patname.ptr = nil; patname.len = 0;
|
|
if (pat.kind == N_TNAME) { patname = pat.str; };
|
|
let v: *node = scrutt.list;
|
|
let idx: i32 = 0;
|
|
let found: bool = false;
|
|
for (v != nil) {
|
|
if (v.kind == N_TNAME) {
|
|
if (streq(v.str, patname)) {
|
|
want = idx;
|
|
found = true;
|
|
v = nil;
|
|
};
|
|
};
|
|
if (v != nil) {
|
|
v = v.next;
|
|
idx += 1;
|
|
};
|
|
};
|
|
if (!found) { want = 0; };
|
|
};
|
|
};
|
|
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 == N_TPTR) {
|
|
let voff: i32 = localalloc(c, bn, 8, pat);
|
|
emitline("\tMOVQ\t");
|
|
emitoff(scrutoff: i64);
|
|
emitline("(BP), AX\n");
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitoff(voff: i64);
|
|
emitline("(BP)\n");
|
|
};
|
|
} else {
|
|
let bsz: i32 = 8;
|
|
if (isstrtype(c, pat)) { bsz = 16; };
|
|
// 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);
|
|
emitline("\tMOVQ\t");
|
|
emitoff((scrutoff + 8): i64);
|
|
emitline("(BP), AX\n");
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitoff(voff: i64);
|
|
emitline("(BP)\n");
|
|
if (bsz == 16) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff((scrutoff + 16): i64);
|
|
emitline("(BP), AX\n");
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitoff((voff + 8): i64);
|
|
emitline("(BP)\n");
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// Body. Match arms are statements; we cgstmt them.
|
|
if (cs.body != nil) { cgstmt(c, cs.body); };
|
|
emitline("\tJMP\t");
|
|
emitline(endl);
|
|
emitline("\n");
|
|
emitlabel(nxt);
|
|
cs = cs.next;
|
|
};
|
|
emitlabel(endl);
|
|
if (c.yieldtop > 0) { c.yieldtop -= 1; };
|
|
return;
|
|
};
|
|
|
|
fn cgdot(c: *cgen, n: *node) void = {
|
|
let lhs: *node = n.lhs;
|
|
let fld: str = n.str;
|
|
// Enum member access: `EnumName.MEMBER` or `pkg.EnumName.MEMBER`
|
|
// → inline the pre-computed constant. With driver-side
|
|
// concatenation, both forms key off the leaf type name.
|
|
if (lhs != nil) {
|
|
let etname: str;
|
|
etname.ptr = nil; etname.len = 0;
|
|
if (lhs.kind == N_IDENT) {
|
|
etname = lhs.str;
|
|
};
|
|
if (lhs.kind == N_DOT) {
|
|
if (lhs.lhs != nil) {
|
|
if (lhs.lhs.kind == N_IDENT) {
|
|
etname = lhs.str;
|
|
};
|
|
};
|
|
};
|
|
if (etname.len > 0) {
|
|
let en: *enumtype = enumlookup(c, etname);
|
|
if (en != nil) {
|
|
let v: u64;
|
|
if (enummemberval(en, fld, &v)) {
|
|
emitline("\tMOVQ\t$");
|
|
emitint(v: i64);
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
if (lhs != nil) {
|
|
if (lhs.kind == N_IDENT) {
|
|
let nm: str = lhs.str;
|
|
let lc: *local = localfindnode(c, nm);
|
|
if (lc != nil) {
|
|
let tn: *node = lc.tnode;
|
|
let lkind: i32 = -1;
|
|
if (tn != nil) { lkind = tn.kind; };
|
|
// Pointer-to-struct: deref then field load.
|
|
if (lkind == N_TPTR) {
|
|
let inner: *node = tn.lhs;
|
|
let sname: str;
|
|
sname.ptr = nil; sname.len = 0;
|
|
if (inner != nil) {
|
|
if (inner.kind == N_TNAME) {
|
|
sname = inner.str;
|
|
};
|
|
};
|
|
if (sname.len > 0) {
|
|
let si: *structinfo = structlookup(c, sname);
|
|
if (si != nil) {
|
|
let fi: *fieldinfo = si.fields;
|
|
for (fi != nil) {
|
|
let fn_: str = fi.fname;
|
|
if (streq(fn_, fld)) {
|
|
// str field via *struct: load len into a
|
|
// scratch first (so loading ptr into AX
|
|
// last leaves (AX=ptr, BX=len)).
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), BX\n");
|
|
if (isstrtype(c, fi.tnode)) {
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg((fi.foff + 8): i64, "BX");
|
|
emitline(", CX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg(fi.foff: i64, "BX");
|
|
emitline(", AX\n");
|
|
emitline("\tMOVQ\tCX, BX\n");
|
|
} else {
|
|
let op: str = fieldloadop(fi);
|
|
emitline("\t");
|
|
emitline(op);
|
|
emitline("\t");
|
|
emitdispreg(fi.foff: i64, "BX");
|
|
emitline(", AX\n");
|
|
};
|
|
return;
|
|
};
|
|
fi = fi.finext;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// Direct struct local: field load at off+foff.
|
|
if (lkind == N_TNAME) {
|
|
let sname: str = tn.str;
|
|
let si: *structinfo = structlookup(c, sname);
|
|
if (si != nil) {
|
|
let fi: *fieldinfo = si.fields;
|
|
for (fi != nil) {
|
|
let fn_: str = fi.fname;
|
|
if (streq(fn_, fld)) {
|
|
// str field: load both halves so chained
|
|
// `.ptr` / `.len` see (AX=ptr, BX=len).
|
|
if (isstrtype(c, fi.tnode)) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff((lc.off + fi.foff): i64);
|
|
emitline("(BP), AX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitoff((lc.off + fi.foff + 8): i64);
|
|
emitline("(BP), BX\n");
|
|
} else {
|
|
let op: str = fieldloadop(fi);
|
|
emitline("\t");
|
|
emitline(op);
|
|
emitline("\t");
|
|
emitoff((lc.off + fi.foff): i64);
|
|
emitline("(BP), AX\n");
|
|
};
|
|
return;
|
|
};
|
|
fi = fi.finext;
|
|
};
|
|
};
|
|
};
|
|
// Array pseudo-fields: `.ptr` is the array's
|
|
// address (LEAQ); `.len` is the static element
|
|
// count (immediate).
|
|
if (lkind == 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 == N_INTLIT) { alen = lenn.uval: i64; };
|
|
};
|
|
emitline("\tMOVQ\t$");
|
|
emitint(alen);
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
};
|
|
// Hare-style tuple positional access: `t.0`, `t.1`.
|
|
// Walk the tuple element type list summing slotsize
|
|
// (matches the (scalar, str) init layout which puts
|
|
// the scalar in an 8B slot and the str in 16B). For
|
|
// a str element, load both halves into (AX, BX) so
|
|
// chains like `t.1.len` propagate correctly.
|
|
if (lkind == N_TTUPLE) {
|
|
let idx: i32 = fldnumidx(fld);
|
|
if (idx >= 0) {
|
|
let tp: *node = tn.list;
|
|
let foff: i32 = 0;
|
|
let i: i32 = 0;
|
|
for (i < idx) {
|
|
if (tp == nil) { i = idx; }
|
|
else {
|
|
foff += slotsize(c, tp);
|
|
tp = tp.next;
|
|
i += 1;
|
|
};
|
|
};
|
|
if (tp != nil) {
|
|
if (isstrtyperaw(tp)) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff((lc.off + foff + 0): i64);
|
|
emitline("(BP), AX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitoff((lc.off + foff + 8): i64);
|
|
emitline("(BP), BX\n");
|
|
return;
|
|
};
|
|
let sz: i32 = slotsize(c, tp);
|
|
let op: str = "MOVQ";
|
|
if (sz == 1) { op = "MOVZBQ"; }
|
|
else { if (sz == 4) { op = "MOVL"; }; };
|
|
emitline("\t");
|
|
emitline(op);
|
|
emitline("\t");
|
|
emitoff((lc.off + foff): i64);
|
|
emitline("(BP), AX\n");
|
|
return;
|
|
};
|
|
};
|
|
};
|
|
// str/slice pseudo-fields .ptr/.len/.cap on a
|
|
// direct local: load at slot+delta.
|
|
let delta: i32 = -1;
|
|
if (streq(fld, "ptr")) { delta = 0; };
|
|
if (streq(fld, "len")) { delta = 8; };
|
|
if (streq(fld, "cap")) { delta = 16; };
|
|
if (delta >= 0) {
|
|
// Pointer to str/slice (`*[]u8`, `*str`):
|
|
// deref, then load at delta within the
|
|
// pointed-to header. C cgen does the same.
|
|
if (lkind == N_TPTR) {
|
|
let inner: *node = tn.lhs;
|
|
let innerkind: i32 = -1;
|
|
if (inner != nil) { innerkind = inner.kind; };
|
|
let innerstr: bool = false;
|
|
if (innerkind == N_TNAME) {
|
|
if (streq(inner.str, "str")) { innerstr = true; };
|
|
};
|
|
if (innerkind == 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 N_DOT off==0 / Sdef branch.
|
|
if (lhs != nil) {
|
|
if (lhs.kind == N_IDENT) {
|
|
let drhs: *node = deflookuprhs(c, lhs.str);
|
|
if (drhs != nil) {
|
|
if (drhs.kind == N_STRLIT) {
|
|
let bytes: str = drhs.str;
|
|
if (streq(fld, "ptr")) {
|
|
let lab: str = internstrlit(c, bytes);
|
|
emitline("\tLEAQ\t");
|
|
os.write(1, lab.ptr, lab.len: u64);
|
|
emitline("(SB), AX\n");
|
|
return;
|
|
};
|
|
if (streq(fld, "len")) {
|
|
emitline("\tMOVQ\t$");
|
|
emitint(bytes.len: i64);
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// Module-qualified value reference: `mod.name` where `mod`
|
|
// is N_IDENT bound as SK_USE and the leaf isn't a local.
|
|
// Treat as a SB symbol — `MOVQ leaf(SB), AX`. Same fallback
|
|
// the C cgen takes when bt is NULL/tyerr.
|
|
if (lhs != nil) {
|
|
if (lhs.kind == N_IDENT) {
|
|
emitline("\tMOVQ\t");
|
|
emitsymname(c, fld);
|
|
emitline("(SB), AX\n");
|
|
return;
|
|
};
|
|
};
|
|
// Non-ident base pseudo-field: e.g. `"abc".ptr` / `"abc".len`.
|
|
// Evaluate the str-producing expression — that leaves
|
|
// (AX=ptr, BX=len). Then `.ptr` returns AX as is; `.len`
|
|
// shuffles BX→AX. Mirrors what C cgen does (it just evaluates
|
|
// the literal and picks the half it wants).
|
|
if (streq(fld, "ptr")) { cgexpr(c, lhs); return; };
|
|
if (streq(fld, "len")) {
|
|
cgexpr(c, lhs);
|
|
emitline("\tMOVQ\tBX, AX\n");
|
|
return;
|
|
};
|
|
// Chained struct-field-via-ptr-via-ptr access:
|
|
// r.sym.val where r: *lrel, .sym: *lsym, .val: u64
|
|
// Inner DOT (`r.sym`) returns a *struct (a pointer-to-struct
|
|
// field). Outer DOT dereferences and reads `val`. Without this
|
|
// path the cgen falls through and AX retains whatever the
|
|
// inner expression left there — typically the *struct pointer
|
|
// itself, so reads silently get the pointer value instead of
|
|
// the field. (Showed up porting w6l/pass.ww.)
|
|
if (lhs != nil) {
|
|
if (lhs.kind == N_DOT) {
|
|
let innert: *node = dotinnerstructptr(c, lhs);
|
|
if (innert != nil) {
|
|
let sname: str = innert.str;
|
|
let si: *structinfo = structlookup(c, sname);
|
|
if (si != nil) {
|
|
let fi: *fieldinfo = si.fields;
|
|
for (fi != nil) {
|
|
if (streq(fi.fname, fld)) {
|
|
cgexpr(c, lhs); // AX = ptr to inner struct
|
|
let lop: str = fieldloadop(fi);
|
|
// str field: load both halves.
|
|
if (isstrtype(c, fi.tnode)) {
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg((fi.foff + 8): i64, "AX");
|
|
emitline(", BX\n");
|
|
emitline("\tMOVQ\t");
|
|
emitdispreg(fi.foff: i64, "AX");
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
emitline("\t");
|
|
emitline(lop);
|
|
emitline("\t");
|
|
emitdispreg(fi.foff: i64, "AX");
|
|
emitline(", AX\n");
|
|
return;
|
|
};
|
|
fi = fi.finext;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
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.
|
|
cgexpr(c, n.lhs);
|
|
if (n.op == tkind.TK_MINUS) { emitline("\tNEGQ\tAX\n"); return; };
|
|
if (n.op == tkind.TK_TILDE) { emitline("\tNOTQ\tAX\n"); return; };
|
|
if (n.op == tkind.TK_STAR) { emitline("\tMOVQ\t(AX), AX\n"); return; };
|
|
if (n.op == tkind.TK_AMP) {
|
|
let opnd: *node = n.lhs;
|
|
if (opnd != nil) {
|
|
if (opnd.kind == 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;
|
|
};
|
|
};
|
|
};
|
|
return;
|
|
};
|
|
if (n.op == tkind.TK_NOT) {
|
|
let t: str = mklabel(c, "tt");
|
|
let e: str = mklabel(c, "te");
|
|
emitline("\tCMPQ\t$0, AX\n");
|
|
emitline("\tJE\t"); emitline(t); emitline("\n");
|
|
emitline("\tMOVQ\t$0, AX\n");
|
|
emitline("\tJMP\t"); emitline(e); emitline("\n");
|
|
emitlabel(t);
|
|
emitline("\tMOVQ\t$1, AX\n");
|
|
emitlabel(e);
|
|
return;
|
|
};
|
|
return;
|
|
};
|
|
|
|
fn cgbin(c: *cgen, n: *node) void = {
|
|
let unsignd: bool = nodeisunsigned(c, n.lhs);
|
|
if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); };
|
|
|
|
cgexpr(c, n.rhs);
|
|
emitline("\tPUSHQ\tAX\n");
|
|
cgexpr(c, n.lhs);
|
|
emitline("\tPOPQ\tBX\n");
|
|
if (n.op == tkind.TK_PLUS) { emitline("\tADDQ\tBX, AX\n"); return; };
|
|
if (n.op == tkind.TK_MINUS) { emitline("\tSUBQ\tBX, AX\n"); return; };
|
|
if (n.op == tkind.TK_STAR) { emitline("\tIMULQ\tBX, AX\n"); return; };
|
|
if (n.op == tkind.TK_SLASH) {
|
|
emitline("\tMOVQ\t$0, DX\n");
|
|
if (unsignd) { emitline("\tDIVQ\tBX\n"); }
|
|
else { emitline("\tIDIVQ\tBX\n"); };
|
|
return;
|
|
};
|
|
if (n.op == tkind.TK_PERCENT) {
|
|
emitline("\tMOVQ\t$0, DX\n");
|
|
if (unsignd) { emitline("\tDIVQ\tBX\n"); }
|
|
else { emitline("\tIDIVQ\tBX\n"); };
|
|
emitline("\tMOVQ\tDX, AX\n");
|
|
return;
|
|
};
|
|
if (n.op == tkind.TK_AMP) { emitline("\tANDQ\tBX, AX\n"); return; };
|
|
if (n.op == tkind.TK_PIPE) { emitline("\tORQ\tBX, AX\n"); return; };
|
|
if (n.op == tkind.TK_CARET) { emitline("\tXORQ\tBX, AX\n"); return; };
|
|
if (n.op == tkind.TK_LSHIFT) {
|
|
emitline("\tMOVQ\tBX, CX\n");
|
|
emitline("\tSHLQ\tCX, AX\n");
|
|
return;
|
|
};
|
|
if (n.op == tkind.TK_RSHIFT) {
|
|
emitline("\tMOVQ\tBX, CX\n");
|
|
emitline("\tSHRQ\tCX, AX\n");
|
|
return;
|
|
};
|
|
if (n.op == tkind.TK_AND) { emitline("\tANDQ\tBX, AX\n"); return; };
|
|
if (n.op == tkind.TK_OR) { emitline("\tORQ\tBX, AX\n"); return; };
|
|
|
|
// Comparison: emit CMPQ, jump on signed/unsigned variant,
|
|
// materialise 0/1 in AX. Same shape as the C cgen.
|
|
let iscmp: bool = false;
|
|
let jcc: str = "";
|
|
if (n.op == tkind.TK_EQ) { iscmp = true; jcc = "JE"; };
|
|
if (n.op == tkind.TK_NEQ) { iscmp = true; jcc = "JNE"; };
|
|
if (n.op == tkind.TK_LT) { iscmp = true; if (unsignd) { jcc = "JB"; } else { jcc = "JL"; }; };
|
|
if (n.op == tkind.TK_LE) { iscmp = true; if (unsignd) { jcc = "JBE"; } else { jcc = "JLE"; }; };
|
|
if (n.op == tkind.TK_GT) { iscmp = true; if (unsignd) { jcc = "JA"; } else { jcc = "JG"; }; };
|
|
if (n.op == tkind.TK_GE) { iscmp = true; if (unsignd) { jcc = "JAE"; } else { jcc = "JGE"; }; };
|
|
if (iscmp) {
|
|
let t: str = mklabel(c, "ct");
|
|
let e: str = mklabel(c, "ce");
|
|
emitline("\tCMPQ\tBX, AX\n");
|
|
emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n");
|
|
emitline("\tMOVQ\t$0, AX\n");
|
|
emitline("\tJMP\t"); emitline(e); emitline("\n");
|
|
emitlabel(t);
|
|
emitline("\tMOVQ\t$1, AX\n");
|
|
emitlabel(e);
|
|
return;
|
|
};
|
|
return;
|
|
};
|
|
|
|
fn cgcall(c: *cgen, n: *node) void = {
|
|
let nargs: i32 = pushargsrev(c, n.list);
|
|
let i: i32 = 0;
|
|
for (i < nargs) {
|
|
emitline("\tPOPQ\t");
|
|
emitline(argregname(i));
|
|
emitline("\n");
|
|
i += 1;
|
|
};
|
|
let callee: *node = n.lhs;
|
|
let calleename: str;
|
|
calleename.ptr = nil; calleename.len = 0;
|
|
// Detect fn-pointer field call: `w.emit(args)` where `w` is
|
|
// a struct local and `emit` is an 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 == N_IDENT) {
|
|
let cn: str = callee.str;
|
|
if (localfindnode(c, cn) != nil) {
|
|
isfnptrcall = true;
|
|
};
|
|
};
|
|
if (callee.kind == N_DOT) {
|
|
let base: *node = callee.lhs;
|
|
let fld: str = callee.str;
|
|
if (base != nil) {
|
|
if (base.kind == 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: i32 = tn.kind;
|
|
let sname: str;
|
|
sname.ptr = nil; sname.len = 0;
|
|
if (lkind == N_TNAME) { sname = tn.str; };
|
|
if (lkind == N_TPTR) {
|
|
let inner: *node = tn.lhs;
|
|
if (inner != nil) {
|
|
if (inner.kind == 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 == N_TFN) {
|
|
isfnptrcall = true;
|
|
};
|
|
};
|
|
fi = nil;
|
|
} else {
|
|
fi = fi.finext;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
if (isfnptrcall) {
|
|
// Load fn-ptr field value into AX; CALL AX. We emit the
|
|
// load AFTER the args have been popped (so AX/BX/etc
|
|
// don't get clobbered by the field load before the pops).
|
|
// `popped args` left DI/SI/etc set; AX is free.
|
|
cgexpr(c, callee);
|
|
emitline("\tCALL\tAX\n");
|
|
} else {
|
|
emitline("\tCALL\t");
|
|
if (callee != nil) {
|
|
if (callee.kind == N_IDENT) {
|
|
calleename = callee.str;
|
|
emitsymname(c, calleename);
|
|
} else { if (callee.kind == N_DOT) {
|
|
calleename = callee.str;
|
|
emitsymname(c, calleename);
|
|
};};
|
|
};
|
|
emitline("(SB)\n");
|
|
};
|
|
// SysV returns 16-byte aggregates in (AX, DX). Our str
|
|
// convention is (AX, BX), so shuffle for str-returning calls.
|
|
if (calleename.len > 0) {
|
|
let rt: *node = fnretlookup(c, calleename);
|
|
if (isstrtype(c, rt)) {
|
|
emitline("\tMOVQ\tDX, BX\n");
|
|
};
|
|
};
|
|
return;
|
|
};
|
|
|
|
fn cgassign(c: *cgen, n: *node) void = {
|
|
let lhs: *node = n.lhs;
|
|
// Discard lvalue `_ = expr;` — evaluate rhs for side effects,
|
|
// write nothing. Detected by lhs being an N_IDENT with empty str
|
|
// (planted by parseprimary on the tkind.TK_UNDER token).
|
|
if (lhs != nil) {
|
|
if (lhs.kind == N_IDENT) {
|
|
if (lhs.str.len == 0) {
|
|
if (n.op == tkind.TK_ASSIGN) {
|
|
cgexpr(c, n.rhs);
|
|
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 == N_UN) {
|
|
if (lhs.op == tkind.TK_STAR) {
|
|
if (n.op == tkind.TK_ASSIGN) {
|
|
let inner: *node = lhs.lhs;
|
|
let elemstr: bool = false;
|
|
let storeop: str = "MOVQ";
|
|
if (inner != nil) {
|
|
if (inner.kind == N_IDENT) {
|
|
let lc: *local = localfindnode(c, inner.str);
|
|
if (lc != nil) {
|
|
let tn: *node = lc.tnode;
|
|
if (tn != nil) {
|
|
if (tn.kind == N_TPTR) {
|
|
let pe: *node = tn.lhs;
|
|
if (pe != nil) {
|
|
if (pe.kind == N_TNAME) {
|
|
if (streq(pe.str, "str")) { elemstr = true; }
|
|
else {
|
|
let ps: i32 = primsize(pe.str);
|
|
if (ps == 1) { storeop = "MOVB"; }
|
|
else { if (ps == 4) { storeop = "MOVL"; }; };
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
cgexpr(c, n.rhs);
|
|
// Push order matches C cgen
|
|
// (cmd/w6c/cgen.c:1033-1041): PUSHQ AX
|
|
// (ptr) first, then PUSHQ BX (len) if
|
|
// str, so the pop sequence is POP CX
|
|
// (len) → POP AX (ptr) → MOVQ AX,
|
|
// (BX) → MOVQ CX, 8(BX).
|
|
emitline("\tPUSHQ\tAX\n");
|
|
if (elemstr) { emitline("\tPUSHQ\tBX\n"); };
|
|
cgexpr(c, inner);
|
|
emitline("\tMOVQ\tAX, BX\n");
|
|
if (elemstr) {
|
|
emitline("\tPOPQ\tCX\n");
|
|
emitline("\tPOPQ\tAX\n");
|
|
emitline("\tMOVQ\tAX, (BX)\n");
|
|
emitline("\tMOVQ\tCX, 8(BX)\n");
|
|
return;
|
|
};
|
|
emitline("\tPOPQ\tAX\n");
|
|
emitline("\t");
|
|
emitline(storeop);
|
|
emitline("\tAX, (BX)\n");
|
|
return;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// Array/slice/ptr index store: `arr[i] = v;`. Element size
|
|
// from base.tnode picks MOVB vs MOVQ.
|
|
if (lhs != nil) {
|
|
if (lhs.kind == 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;
|
|
if (base != nil) {
|
|
if (base.kind == N_IDENT) {
|
|
let bn: str = base.str;
|
|
baselocal = localfindnode(c, bn);
|
|
if (baselocal != nil) {
|
|
esz = elemsizeof(baselocal.tnode);
|
|
};
|
|
} else { if (base.kind == N_DOT) {
|
|
esz = indexbaseesz(c, base);
|
|
};};
|
|
};
|
|
cgexpr(c, n.rhs); // value → AX
|
|
if (esz == 16) { emitline("\tPUSHQ\tBX\n"); };
|
|
emitline("\tPUSHQ\tAX\n");
|
|
cgexpr(c, idx); // idx → AX
|
|
if (esz > 1) {
|
|
emitline("\tMOVQ\t$");
|
|
emitint(esz: i64);
|
|
emitline(", CX\n");
|
|
emitline("\tIMULQ\tCX, AX\n");
|
|
};
|
|
emitline("\tPUSHQ\tAX\n"); // scaled idx
|
|
if (baselocal != nil) {
|
|
let tn: *node = baselocal.tnode;
|
|
let isarray: bool = false;
|
|
if (tn != nil) { if (tn.kind == N_TARRAY) { isarray = true; }; };
|
|
if (isarray) {
|
|
emitline("\tLEAQ\t");
|
|
emitoff(baselocal.off: i64);
|
|
emitline("(BP), BX\n");
|
|
} else {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(baselocal.off: i64);
|
|
emitline("(BP), BX\n");
|
|
};
|
|
} else {
|
|
cgexpr(c, base);
|
|
emitline("\tMOVQ\tAX, BX\n");
|
|
};
|
|
emitline("\tPOPQ\tAX\n"); // scaled idx
|
|
emitline("\tADDQ\tAX, BX\n");
|
|
emitline("\tPOPQ\tAX\n"); // value
|
|
if (esz == 16) {
|
|
emitline("\tMOVQ\tAX, (BX)\n");
|
|
emitline("\tPOPQ\tCX\n");
|
|
emitline("\tMOVQ\tCX, 8(BX)\n");
|
|
return;
|
|
};
|
|
if (esz == 1) { emitline("\tMOVB\tAX, (BX)\n"); }
|
|
else { emitline("\tMOVQ\tAX, (BX)\n"); };
|
|
return;
|
|
};
|
|
};
|
|
};
|
|
// Struct/ptr-to-struct field assignment: `s.f = expr;` or
|
|
// `p.f = expr;`. Only plain `=` is wired (compound on field
|
|
// is rare and not yet needed by our fixtures).
|
|
if (lhs != nil) {
|
|
if (lhs.kind == N_DOT) {
|
|
let base: *node = lhs.lhs;
|
|
let fld: str = lhs.str;
|
|
if (base != nil) {
|
|
if (base.kind == N_IDENT) {
|
|
let bn: str = base.str;
|
|
let lc: *local = localfindnode(c, bn);
|
|
if (lc != nil) {
|
|
let tn: *node = lc.tnode;
|
|
let lkind: i32 = -1;
|
|
if (tn != nil) { lkind = tn.kind; };
|
|
// Pointer-to-struct: deref then store.
|
|
if (lkind == N_TPTR) {
|
|
let inner: *node = tn.lhs;
|
|
let sname: str;
|
|
sname.ptr = nil; sname.len = 0;
|
|
if (inner != nil) {
|
|
if (inner.kind == N_TNAME) { sname = inner.str; };
|
|
};
|
|
if (sname.len > 0) {
|
|
let si: *structinfo = structlookup(c, sname);
|
|
if (si != nil) {
|
|
let fi: *fieldinfo = si.fields;
|
|
for (fi != nil) {
|
|
let fn_: str = fi.fname;
|
|
if (streq(fn_, fld)) {
|
|
if (n.op != tkind.TK_ASSIGN) {
|
|
// compound: load current value
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), BX\n");
|
|
let lop: str = fieldloadop(fi);
|
|
emitline("\t");
|
|
emitline(lop);
|
|
emitline("\t");
|
|
emitdispreg(fi.foff: i64, "BX");
|
|
emitline(", BX\n");
|
|
emitline("\tPUSHQ\tBX\n");
|
|
};
|
|
cgexpr(c, n.rhs);
|
|
if (n.op != tkind.TK_ASSIGN) {
|
|
emitline("\tPOPQ\tBX\n");
|
|
// PLUSEQ is commutative; MINUSEQ
|
|
// needs lhs - rhs (BX is old lhs,
|
|
// AX is rhs).
|
|
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); };
|
|
if (n.op == tkind.TK_MINUSEQ) {
|
|
emitline("\tSUBQ\tAX, BX\n");
|
|
emitline("\tMOVQ\tBX, AX\n");
|
|
};
|
|
};
|
|
// str field via *struct: rhs left
|
|
// (AX=ptr, BX=len). Use CX as the
|
|
// address scratch so we don't clobber
|
|
// the len half before storing it.
|
|
if (n.op == tkind.TK_ASSIGN) {
|
|
if (isstrtype(c, fi.tnode)) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), CX\n");
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitdispreg(fi.foff: i64, "CX");
|
|
emitline("\n");
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitdispreg((fi.foff + 8): i64, "CX");
|
|
emitline("\n");
|
|
return;
|
|
};
|
|
};
|
|
emitline("\tMOVQ\t");
|
|
emitoff(lc.off: i64);
|
|
emitline("(BP), BX\n");
|
|
let sop: str = fieldstoreop(fi);
|
|
emitline("\t");
|
|
emitline(sop);
|
|
emitline("\tAX, ");
|
|
emitdispreg(fi.foff: i64, "BX");
|
|
emitline("\n");
|
|
return;
|
|
};
|
|
fi = fi.finext;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// Direct struct local: store at off+foff.
|
|
if (lkind == N_TNAME) {
|
|
let sname: str = tn.str;
|
|
let si: *structinfo = structlookup(c, sname);
|
|
if (si != nil) {
|
|
let fi: *fieldinfo = si.fields;
|
|
for (fi != nil) {
|
|
let fn_: str = fi.fname;
|
|
if (streq(fn_, fld)) {
|
|
cgexpr(c, n.rhs);
|
|
let sop: str = fieldstoreop(fi);
|
|
emitline("\t");
|
|
emitline(sop);
|
|
emitline("\tAX, ");
|
|
emitoff((lc.off + fi.foff): i64);
|
|
emitline("(BP)\n");
|
|
return;
|
|
};
|
|
fi = fi.finext;
|
|
};
|
|
};
|
|
};
|
|
// str/slice pseudo-field assignment.
|
|
let delta: i32 = -1;
|
|
if (streq(fld, "ptr")) { delta = 0; };
|
|
if (streq(fld, "len")) { delta = 8; };
|
|
if (streq(fld, "cap")) { delta = 16; };
|
|
if (delta >= 0) {
|
|
if (lkind == N_TPTR) {
|
|
let inner: *node = tn.lhs;
|
|
let innerkind: i32 = -1;
|
|
if (inner != nil) { innerkind = inner.kind; };
|
|
let innerstr: bool = false;
|
|
if (innerkind == N_TNAME) {
|
|
if (streq(inner.str, "str")) { innerstr = true; };
|
|
};
|
|
if (innerkind == 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;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// 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 == N_DOT) {
|
|
let base: *node = lhs.lhs;
|
|
let fld: str = lhs.str;
|
|
if (base != nil) {
|
|
if (base.kind == N_DOT) {
|
|
let innert: *node = dotinnerstructptr(c, base);
|
|
if (innert != nil) {
|
|
let sname: str = innert.str;
|
|
let si: *structinfo = structlookup(c, sname);
|
|
if (si != nil) {
|
|
let fi: *fieldinfo = si.fields;
|
|
for (fi != nil) {
|
|
if (streq(fi.fname, fld)) {
|
|
if (n.op == tkind.TK_ASSIGN) {
|
|
if (isstrtype(c, fi.tnode)) {
|
|
// str rhs: AX=ptr, BX=len.
|
|
// Stash both, then load
|
|
// the struct ptr into CX
|
|
// and write both halves.
|
|
cgexpr(c, n.rhs);
|
|
emitline("\tPUSHQ\tBX\n");
|
|
emitline("\tPUSHQ\tAX\n");
|
|
cgexpr(c, base);
|
|
emitline("\tMOVQ\tAX, CX\n");
|
|
emitline("\tPOPQ\tAX\n");
|
|
emitline("\tPOPQ\tBX\n");
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitdispreg(fi.foff: i64, "CX");
|
|
emitline("\n");
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitdispreg((fi.foff + 8): i64, "CX");
|
|
emitline("\n");
|
|
return;
|
|
};
|
|
cgexpr(c, n.rhs);
|
|
emitline("\tPUSHQ\tAX\n");
|
|
cgexpr(c, base);
|
|
emitline("\tMOVQ\tAX, BX\n");
|
|
emitline("\tPOPQ\tAX\n");
|
|
let sop: str = fieldstoreop(fi);
|
|
emitline("\t");
|
|
emitline(sop);
|
|
emitline("\tAX, ");
|
|
emitdispreg(fi.foff: i64, "BX");
|
|
emitline("\n");
|
|
return;
|
|
};
|
|
};
|
|
fi = fi.finext;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// 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 == N_IDENT) {
|
|
let nm: str = lhs.str;
|
|
let off: i32 = localfind(c, nm);
|
|
if (off == 0) { return; };
|
|
// Detect str-typed local — assignment must store both
|
|
// halves (AX=ptr at +0, BX=len at +8).
|
|
let lcstr: bool = false;
|
|
let lcn: *local = localfindnode(c, nm);
|
|
if (lcn != nil) { lcstr = isstrtype(c, lcn.tnode); };
|
|
cgexpr(c, n.rhs);
|
|
if (n.op == tkind.TK_ASSIGN) {
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitoff(off: i64);
|
|
emitline("(BP)\n");
|
|
if (lcstr) {
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitoff((off + 8): i64);
|
|
emitline("(BP)\n");
|
|
};
|
|
return;
|
|
};
|
|
if (n.op == tkind.TK_PLUSEQ) {
|
|
emitline("\tADDQ\tAX, ");
|
|
emitoff(off: i64);
|
|
emitline("(BP)\n");
|
|
return;
|
|
};
|
|
if (n.op == tkind.TK_MINUSEQ) {
|
|
emitline("\tSUBQ\tAX, ");
|
|
emitoff(off: i64);
|
|
emitline("(BP)\n");
|
|
return;
|
|
};
|
|
// Generic compound: load → combine in BX → store.
|
|
emitline("\tMOVQ\t");
|
|
emitoff(off: i64);
|
|
emitline("(BP), BX\n");
|
|
if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); };
|
|
if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tAX, BX\n"); };
|
|
if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tAX, BX\n"); };
|
|
if (n.op == tkind.TK_CARETEQ) { emitline("\tXORQ\tAX, BX\n"); };
|
|
if (n.op == tkind.TK_LSHIFTEQ) {
|
|
emitline("\tMOVQ\tAX, CX\n");
|
|
emitline("\tSHLQ\tCX, BX\n");
|
|
};
|
|
if (n.op == tkind.TK_RSHIFTEQ) {
|
|
emitline("\tMOVQ\tAX, CX\n");
|
|
emitline("\tSHRQ\tCX, BX\n");
|
|
};
|
|
emitline("\tMOVQ\tBX, ");
|
|
emitoff(off: i64);
|
|
emitline("(BP)\n");
|
|
return;
|
|
};
|
|
};
|
|
return;
|
|
};
|
|
|
|
|