Six fixes across the toolchain, surfaced by lib/lisp porting work.
1. f64 compound assigns (`acc += d`, `-=`, `*=`, `/=`). Both stages
load slot → X1, OP X0 into X1, store back (ADDSD/SUBSD/MULSD/
DIVSD are reg-reg only). Previous MOVSD-overwrite dropped the
OP. Locals and top-level lets.
2. Top-level `[N]u8` arrays + `&arr[i]`. let_emit_size grows a
TY_ARRAY branch so zero-init DATAW lands; cgindex / N_INDEX
store / `&base[i]` all detect a global array base and use
LEAQ name(SB) instead of LEAQ (BP). TK_AMP no longer pre-
evaluates the operand as a value-load — `&base[i]` computes
base + i*esz directly. Unblocks Hare's static-buffer pattern:
strconv.{u64,i64,f64}tos graduate to module-level `*_buf`
arrays and return owned views.
3. Cross-module `pkg.Enum.MEMBER`. Nested N_DOT chains that
don't fold to a known shape now emit `MOVQ <leaf>(SB), AX`
(mirrors the bare-IDENT unresolved fallback), so isolation
probes — and the test 990 cgen-match floor — stay consistent
across stages. strconv exposes `base` as a real `enum i32`;
callers updated. The `main` exemption (linker entry-point
keeps bare name even when not exported) mirrors C-side
collectmods into selfhost cgendecl.
4. Sum-typed parameter ABI. lib/bytes.{index,rindex} take
`(u8 | []u8)` needle; lib/strings.byteindex / rbyteindex take
`(str | rune)` needle (Hare-shaped; the byte-wise misnomer
`index` is dropped). tagged_arg_size cap bumps to 48 (6 int
regs), with a new partial-fit branch on the callee: when an
N-word tagged arg overflows remaining regs, fill what fits and
stitch the rest from positive BP offsets. scanlocals MCASE
handles slice binds (24B) and walks each arm with a saved /
restored seenmark set so two arms naming the same local each
get their own slot — matches cstage's per-arm scope reset.
5. 4-reg tagged-return ABI (AX=tag, DX=word0, CX=word1, R8=word2),
up from 3 regs. Slice-payload variants (`([]T | E)`, slot 32B)
round-trip ptr/len/cap end-to-end. Every receive site updates:
let-init via cgwidentaggedstore, match scrutinee spill, cgindex
tagged-element load (both N_IDENT and fallback bases),
pushargsrev tagged-ident arg (reads word count from slot size),
cgreturn slice variant in the shuffle path.
6. `expr: TaggedAlias` is a widening, not a re-interpret. C cgen +
selfhost cgwidentaggedstore peel an N_CAST whose destination IS
the union — so cgexpr's natural shape (str: AX=ptr, BX=len;
slice: AX=ptr, BX=len, CX=cap) is consumed by the matching
concrete-variant branch instead of being misread as a tagged
AX/DX/CX triple. Inner casts to a concrete variant (`7: i32`)
keep their type for proper tag lookup. `[N]Alias` arrays
resolve element size via slotsize + aliaslookup, and aliaslookup
strips a `pkg.` prefix so cross-module references work.
lib/fmt grows `formattable = (i64 | str | bool | rune)` plus
`printv` / `printlnv` taking an explicit `[]formattable` slice (the
receive side of Hare's `args: formattable...`). Call-site variadic
gather isn't wired — callers either hand-build the slice or compose
strconv.i64tos + strings.concat.
700_e2e: 114 → 123 rows (f64 compound, top-level u8 arrays + `&buf[i]`,
pkg.Enum.MEMBER, sum-typed (str|rune) and (u8|[]u8) params, 4-reg
slice-return ABI, formattable array). 26/26 tests, bootstrap stable
through ww4.
569 lines
17 KiB
Plaintext
569 lines
17 KiB
Plaintext
// selfhost/cmd/wcc/cgendecl.ww — split out of cgen.ww.
|
|
//
|
|
// Houses the top-level emission glue:
|
|
// - scanlocals: frame pre-scan that counts each local `let`
|
|
// - cgfnparams: parameter spilling per SysV
|
|
// - cgfn: fn prologue + body + epilogue
|
|
// - cgfile: file-level entry (the exported driver)
|
|
//
|
|
// Bundler pulls this in transitively via cgen.ww; consumers don't
|
|
// need to `use cgendecl;` directly.
|
|
|
|
use os;
|
|
use mem;
|
|
use ast;
|
|
use tok;
|
|
use typ;
|
|
use sym;
|
|
use strconv;
|
|
|
|
//
|
|
// Recursively walks the body to count every local `let`. Each gets a
|
|
// slot sized by slotsize(typ); 8-byte default. Match-bindings + for-
|
|
// init lets count too. Params are added by the cgfn driver.
|
|
|
|
fn scanlocals(c: *cgen, n: *node) i32 = {
|
|
if (n == nil) { return 0; };
|
|
let total: i32 = 0;
|
|
if (n.kind == nkind.N_LET) {
|
|
// Match localadd's rounding: < 8 bumps to 8, then 8-align.
|
|
// scanlocals must agree with localadd or the prologue
|
|
// SUBQ undersizes the frame and lets overflow into the
|
|
// caller's stack — corrupting whatever's at -frameSize..-1
|
|
// of the caller. Same-name re-declarations share the first
|
|
// slot (see scanseenmark / localadd).
|
|
if (!scanseenmark(c, n.str)) {
|
|
let sz: i32 = letslotsize(c, n);
|
|
if (sz < 8) { sz = 8; };
|
|
if ((sz & 7) != 0) { sz = (sz + 7) & ~7; };
|
|
total += sz;
|
|
};
|
|
// Carry the let's tnode into the stub so scanlocals can
|
|
// dispatch on type later in the walk — e.g. detecting
|
|
// `arr[i] = ...` where arr is a tagged-element array,
|
|
// which needs an @tagscr scratch slot reservation.
|
|
let stub: *local = localfindnode(c, n.str);
|
|
if (stub != nil) {
|
|
if (stub.tnode == nil) {
|
|
if (n.lhs != nil) { stub.tnode = n.lhs; };
|
|
};
|
|
};
|
|
};
|
|
// Multi-let from a tuple-returning call: each binding's size
|
|
// comes from its annotated type (l.lhs) when present, else from
|
|
// the rhs call's return-tuple element type. Marking via
|
|
// scanseenmark also dedupes the recursive descent into n.list
|
|
// so each child isn't counted again at the default 8B.
|
|
if (n.kind == nkind.N_MLET) {
|
|
let p0t: *node = nil;
|
|
let p1t: *node = nil;
|
|
if (n.rhs != nil) {
|
|
if (n.rhs.kind == nkind.N_CALL) {
|
|
let callee: *node = n.rhs.lhs;
|
|
if (callee != nil) {
|
|
let cnm: str;
|
|
cnm.ptr = nil; cnm.len = 0;
|
|
if (callee.kind == nkind.N_IDENT) { cnm = callee.str; };
|
|
if (callee.kind == nkind.N_DOT) { cnm = callee.str; };
|
|
if (cnm.len > 0) {
|
|
let rt: *node = fnretlookup(c, cnm);
|
|
if (rt != nil) {
|
|
if (rt.kind == nkind.N_TTUPLE) {
|
|
p0t = rt.list;
|
|
if (p0t != nil) { p1t = p0t.next; };
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
let l: *node = n.list;
|
|
let pt: *node = p0t;
|
|
let bidx: i32 = 0;
|
|
for (l != nil) {
|
|
if (!scanseenmark(c, l.str)) {
|
|
let t: *node = l.lhs;
|
|
if (t == nil) {
|
|
if (bidx == 0) { t = p0t; };
|
|
if (bidx == 1) { t = p1t; };
|
|
};
|
|
let sz: i32 = 8;
|
|
if (t != nil) { sz = slotsize(c, t); };
|
|
if (sz < 8) { sz = 8; };
|
|
if ((sz & 7) != 0) { sz = (sz + 7) & ~7; };
|
|
total += sz;
|
|
};
|
|
l = l.next;
|
|
bidx += 1;
|
|
};
|
|
};
|
|
// `switch` allocates an 8B scratch slot for the scrutinee so case
|
|
// bodies can spill through SP without losing it. The slot is named
|
|
// ".sw_<labelseq>" at cgen time — unique per switch — so it must
|
|
// not dedup. Count it here so the frame SUBQ matches.
|
|
if (n.kind == nkind.N_SWITCH) { total += 8; };
|
|
// `for (let x .. s)` allocates two 8B scratch slots — `.rgi_<seq>`
|
|
// (counter) and `.rgl_<seq>` (length) — plus one slot per binding.
|
|
// Per-binding sz defaults to 8 (covers scalar primitives + ptrs).
|
|
// `str` tuple-fields would need 16 — selfhost doesn't yet emit
|
|
// those, so the simple count tracks C cgen for current fixtures.
|
|
if (n.kind == nkind.N_FORRANGE) {
|
|
total += 16; // .rgi + .rgl scratch
|
|
if (n.list != nil) {
|
|
let m: *node = n.list;
|
|
for (m != nil) {
|
|
let bnm: str = m.str;
|
|
if (bnm.len == 0) {
|
|
total += 8; // discard binding still gets a slot
|
|
} else { if (!scanseenmark(c, bnm)) {
|
|
total += 8;
|
|
};};
|
|
m = m.next;
|
|
};
|
|
} else {
|
|
let bnm: str = n.str;
|
|
if (bnm.len == 0) {
|
|
total += 8;
|
|
} else { if (!scanseenmark(c, bnm)) {
|
|
total += 8;
|
|
};};
|
|
};
|
|
};
|
|
// `match (non-ident)` needs a 24B `@match_spill` scratch slot for
|
|
// cgmatch to land the AX:DX:CX return triple. Mirrors C cgen's
|
|
// localoff("@match_spill", ...). N_IDENT scrutinees read the slot
|
|
// directly off the local — no spill needed.
|
|
if (n.kind == nkind.N_MATCH) {
|
|
let sc: *node = n.lhs;
|
|
if (sc != nil) {
|
|
if (sc.kind != nkind.N_IDENT) { total += 24; };
|
|
};
|
|
};
|
|
// Match-arm binding (`case let v: T => ...`) gets a slot too.
|
|
// Crucially we do NOT dedup these against c.locals: C cgen
|
|
// handles a match as an expression with a by-value locals copy,
|
|
// so two separate matches in the same function each allocate
|
|
// their `v`/`e` slots fresh. Treating these as deduped would
|
|
// shrink the frame below what localadd then bumps it to.
|
|
if (n.kind == nkind.N_MCASE) {
|
|
let bn: str = n.str;
|
|
if (bn.len > 0) {
|
|
let pat: *node = n.lhs;
|
|
if (pat != nil) {
|
|
if (isstrtype(c, pat)) { total += 16; }
|
|
else { if (isslicetype(c, pat)) { total += 24; }
|
|
else { total += 8; }; };
|
|
};
|
|
};
|
|
// Match arms get a fresh local scope at emission time
|
|
// (cgmatch saves c.locals before each arm and restores
|
|
// after). scanlocals must mirror that: walk the arm
|
|
// body with a saved/restored seenmark set so two arms
|
|
// declaring the same name each get their own slot,
|
|
// matching the per-arm frame growth the emit phase
|
|
// produces.
|
|
if (n.body != nil) {
|
|
let saved: *local = c.locals;
|
|
total += scanlocals(c, n.body);
|
|
c.locals = saved;
|
|
};
|
|
return total;
|
|
};
|
|
// Tagged-arr/slice index store needs a 24B scratch slot
|
|
// (`@tagscr`) for cgwidentaggedstore to materialise the source
|
|
// in before copying to the element address. Reserved once per
|
|
// function (dedup'd via scanseenmark) regardless of how many
|
|
// tagged-arr stores the body contains.
|
|
if (n.kind == nkind.N_ASSIGN) {
|
|
let alhs: *node = n.lhs;
|
|
if (alhs != nil) {
|
|
if (alhs.kind == nkind.N_INDEX) {
|
|
let abase: *node = alhs.lhs;
|
|
if (abase != nil) {
|
|
if (abase.kind == nkind.N_IDENT) {
|
|
let lc: *local = localfindnode(c, abase.str);
|
|
let btn: *node = nil;
|
|
if (lc != nil) { btn = lc.tnode; }
|
|
else { btn = letvartnode(c, abase.str); };
|
|
if (btn != nil) {
|
|
let bk: nkind = btn.kind;
|
|
let etn: *node = nil;
|
|
if (bk == nkind.N_TARRAY) { etn = btn.lhs; };
|
|
if (bk == nkind.N_TSLICE) { etn = btn.lhs; };
|
|
if (bk == nkind.N_TPTR) { etn = btn.lhs; };
|
|
if (etn != nil) {
|
|
if (istaggedtype(c, etn)) {
|
|
if (!scanseenmark(c, "@tagscr")) {
|
|
total += 24;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// Tagged-union return with struct payload or tagged-subset
|
|
// source — cgreturn materialises in @tagscr then loads
|
|
// AX/DX/CX. Detect via the same rhsstructpayload predicate
|
|
// the cgen uses, so we only reserve when the cgen will
|
|
// actually emit a scratch-using path. `!void` / `!i32`
|
|
// aliases share N_STRUCTLIT shape but resolve to
|
|
// non-struct types — they fall through to scalar/str and
|
|
// don't need scratch.
|
|
if (n.kind == nkind.N_RETURN) {
|
|
if (c.fnret != nil) {
|
|
if (istaggedtype(c, c.fnret)) {
|
|
if (!isnullabletype(c.fnret)) {
|
|
let rhs: *node = n.lhs;
|
|
let needs: bool = false;
|
|
if (rhs != nil) {
|
|
let sn: str = rhsstructpayload(c, rhs);
|
|
if (sn.len > 0) { needs = true; };
|
|
if (rhs.kind == nkind.N_IDENT) {
|
|
let lc: *local = localfindnode(c, rhs.str);
|
|
if (lc != nil) {
|
|
if (istaggedtype(c, lc.tnode)) {
|
|
needs = true;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
if (needs) {
|
|
if (!scanseenmark(c, "@tagscr")) {
|
|
total += 24;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
// Call-site struct-payload widening uses @tagscr — when the
|
|
// arg is a struct literal/ident and the callee's param is
|
|
// tagged, pushargsrev materialises in scratch and pushes.
|
|
// Scalar / str args take the direct-push fast path (no
|
|
// scratch). Tagged-typed ident args also skip widening (the
|
|
// slot is already laid out, so pushargsrev pushes slot words
|
|
// directly). Both fast paths agree with C cgen bytewise, so
|
|
// only struct-payload sites get a scratch reservation.
|
|
if (n.kind == nkind.N_CALL) {
|
|
let callee: *node = n.lhs;
|
|
let cnm: str;
|
|
cnm.ptr = nil; cnm.len = 0;
|
|
if (callee != nil) {
|
|
if (callee.kind == nkind.N_IDENT) { cnm = callee.str; };
|
|
if (callee.kind == nkind.N_DOT) { cnm = callee.str; };
|
|
};
|
|
if (cnm.len > 0) {
|
|
let ps: *node = fnparamslookup(c, cnm);
|
|
let a: *node = n.list;
|
|
for (a != nil) {
|
|
if (ps == nil) { a = nil; }
|
|
else {
|
|
if (ps.kind == nkind.N_PARAM) {
|
|
let pt: *node = ps.lhs;
|
|
if (istaggedtype(c, pt)) {
|
|
if (!isnullabletype(pt)) {
|
|
let sn: str = rhsstructpayload(c, a);
|
|
if (sn.len > 0) {
|
|
let isidentstruct: bool = false;
|
|
if (a.kind == nkind.N_IDENT) {
|
|
// Struct ident as
|
|
// tagged arg — pushargsrev
|
|
// still routes through the
|
|
// scratch path.
|
|
isidentstruct = true;
|
|
};
|
|
let _u: bool = isidentstruct;
|
|
if (!scanseenmark(c, "@tagscr")) {
|
|
total += 24;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
if (a != nil) {
|
|
a = a.next;
|
|
ps = ps.next;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
if (n.lhs != nil) { total += scanlocals(c, n.lhs); };
|
|
if (n.rhs != nil) { total += scanlocals(c, n.rhs); };
|
|
if (n.cond != nil) { total += scanlocals(c, n.cond); };
|
|
if (n.body != nil) { total += scanlocals(c, n.body); };
|
|
if (n.els != nil) { total += scanlocals(c, n.els); };
|
|
if (n.list != nil) {
|
|
let m: *node = n.list;
|
|
for (m != nil) {
|
|
total += scanlocals(c, m);
|
|
m = m.next;
|
|
};
|
|
};
|
|
return total;
|
|
};
|
|
|
|
|
|
// ---- function-level cgen ---------------------------------------------
|
|
|
|
fn cgfnparams(c: *cgen, params: *node) void = {
|
|
let p: *node = params;
|
|
let idx: i32 = 0;
|
|
let fidx: i32 = 0;
|
|
// Cursor for args that overflow the SysV reg windows. Each
|
|
// stack-passed arg lives at 16+8*k(BP) — no spill, the local
|
|
// is registered with a *positive* offset pointing into the
|
|
// caller's frame. Mirrors C cgen's cg_stack_arg_cursor.
|
|
let stkcursor: i32 = 0;
|
|
for (p != nil) {
|
|
if (p.kind == nkind.N_PARAM) {
|
|
let nm: str = p.str;
|
|
if (isfloattype(c, p.lhs)) {
|
|
// Float param: SysV uses the XMM stream
|
|
// (X0..X7). 8B (f64) or 4B (f32) slot.
|
|
let fsz: i32 = 8;
|
|
if (isf32type(c, p.lhs)) { fsz = 4; };
|
|
if (fidx < 8) {
|
|
let off: i32 = localadd(c, nm, fsz, p.lhs);
|
|
let mov: str = "MOVSD";
|
|
if (fsz == 4) { mov = "MOVSS"; };
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\t");
|
|
emitline(fargregname(fidx));
|
|
emitline(", ");
|
|
emitoff(off: i64);
|
|
emitline("(BP)\n");
|
|
fidx += 1;
|
|
} else {
|
|
localaddstack(c, nm, p.lhs, 16 + stkcursor*8);
|
|
stkcursor += 1;
|
|
};
|
|
p = p.next;
|
|
continue;
|
|
};
|
|
if (istaggedtype(c, p.lhs)) {
|
|
let slot: i32 = slotsize(c, p.lhs);
|
|
let nw: i32 = slot / 8;
|
|
if (idx + nw <= 6) {
|
|
let off: i32 = localadd(c, nm, slot, p.lhs);
|
|
let w: i32 = 0;
|
|
for (w < nw) {
|
|
emitline("\tMOVQ\t");
|
|
emitline(argregname(idx));
|
|
emitline(", ");
|
|
emitoff((off + w*8): i64);
|
|
emitline("(BP)\n");
|
|
idx += 1;
|
|
w += 1;
|
|
};
|
|
} else { if (idx < 6 && nw > 1) {
|
|
// Partial fit: fill remaining regs, then read
|
|
// the tail from positive BP offsets. Mirrors
|
|
// the caller's greedy reg fill in pushargsrev.
|
|
let off: i32 = localadd(c, nm, slot, p.lhs);
|
|
let regs_left: i32 = 6 - idx;
|
|
let w: i32 = 0;
|
|
for (w < regs_left) {
|
|
emitline("\tMOVQ\t");
|
|
emitline(argregname(idx));
|
|
emitline(", ");
|
|
emitoff((off + w*8): i64);
|
|
emitline("(BP)\n");
|
|
idx += 1;
|
|
w += 1;
|
|
};
|
|
for (w < nw) {
|
|
emitline("\tMOVQ\t");
|
|
emitoff((16 + stkcursor*8): i64);
|
|
emitline("(BP), AX\n");
|
|
emitline("\tMOVQ\tAX, ");
|
|
emitoff((off + w*8): i64);
|
|
emitline("(BP)\n");
|
|
stkcursor += 1;
|
|
w += 1;
|
|
};
|
|
} else {
|
|
localaddstack(c, nm, p.lhs, 16 + stkcursor*8);
|
|
stkcursor += nw;
|
|
};};
|
|
} else { if (isslicetype(c, p.lhs)) {
|
|
if (idx + 3 <= 6) {
|
|
let off: i32 = localadd(c, nm, 24, p.lhs);
|
|
emitline("\tMOVQ\t");
|
|
emitline(argregname(idx));
|
|
emitline(", ");
|
|
emitoff(off: i64);
|
|
emitline("(BP)\n");
|
|
idx += 1;
|
|
emitline("\tMOVQ\t");
|
|
emitline(argregname(idx));
|
|
emitline(", ");
|
|
emitoff((off + 8): i64);
|
|
emitline("(BP)\n");
|
|
idx += 1;
|
|
emitline("\tMOVQ\t");
|
|
emitline(argregname(idx));
|
|
emitline(", ");
|
|
emitoff((off + 16): i64);
|
|
emitline("(BP)\n");
|
|
idx += 1;
|
|
} else {
|
|
localaddstack(c, nm, p.lhs, 16 + stkcursor*8);
|
|
stkcursor += 3;
|
|
};
|
|
} else { if (isstrtype(c, p.lhs)) {
|
|
if (idx + 2 <= 6) {
|
|
let off: i32 = localadd(c, nm, 16, p.lhs);
|
|
emitline("\tMOVQ\t");
|
|
emitline(argregname(idx));
|
|
emitline(", ");
|
|
emitoff(off: i64);
|
|
emitline("(BP)\n");
|
|
idx += 1;
|
|
emitline("\tMOVQ\t");
|
|
emitline(argregname(idx));
|
|
emitline(", ");
|
|
emitoff((off + 8): i64);
|
|
emitline("(BP)\n");
|
|
idx += 1;
|
|
} else {
|
|
localaddstack(c, nm, p.lhs, 16 + stkcursor*8);
|
|
stkcursor += 2;
|
|
};
|
|
} else {
|
|
if (idx < 6) {
|
|
let off: i32 = localadd(c, nm, 8, p.lhs);
|
|
emitline("\tMOVQ\t");
|
|
emitline(argregname(idx));
|
|
emitline(", ");
|
|
emitoff(off: i64);
|
|
emitline("(BP)\n");
|
|
idx += 1;
|
|
} else {
|
|
localaddstack(c, nm, p.lhs, 16 + stkcursor*8);
|
|
stkcursor += 1;
|
|
};
|
|
};};};
|
|
};
|
|
p = p.next;
|
|
};
|
|
};
|
|
|
|
fn cgfn(c: *cgen, fn_: *node) void = {
|
|
cgeninit(c, c.a);
|
|
c.fnname = fn_.str;
|
|
c.fnret = fn_.lhs;
|
|
|
|
emitline("TEXT ");
|
|
if (fn_.exported == 0) {
|
|
if (fn_.module.len > 0) {
|
|
let isffi: bool = false;
|
|
let a: *node = fn_.attr;
|
|
for (a != nil) {
|
|
if (a.kind == nkind.N_ATTR) {
|
|
let an: str = a.str;
|
|
if (streq(an, "symbol")) { isffi = true; };
|
|
};
|
|
a = a.next;
|
|
};
|
|
// `main` is the linker entry-point convention; even
|
|
// when not marked `export`, it must keep its bare
|
|
// name so w6l's _start can resolve `CALL main(SB)`.
|
|
// Mirror of cmd/w6c/cgen.c collectmods exemption.
|
|
let isentry: bool = streq(fn_.str, "main");
|
|
if (!isffi && !isentry) {
|
|
os.write(1, fn_.module.ptr, fn_.module.len: u64);
|
|
os.write(1, ".".ptr, 1u64);
|
|
};
|
|
};
|
|
};
|
|
let nm: str = fn_.str;
|
|
os.write(1, nm.ptr, nm.len: u64);
|
|
emitline(",$");
|
|
|
|
// Pre-scan total frame: 24 bytes per slice param, 16 per str
|
|
// param, 8 per other param, plus per-let from scanlocals.
|
|
// Seed c.locals with param-name stubs so scanlocals dedups a
|
|
// re-declared `let <name>` in the body against the param's
|
|
// slot (matches C cgen). Stubs get cleared before emission.
|
|
let scanp: *node = fn_.list;
|
|
let frame: i32 = 0;
|
|
for (scanp != nil) {
|
|
if (scanp.kind == nkind.N_PARAM) {
|
|
if (istaggedtype(c, scanp.lhs)) { frame += slotsize(c, scanp.lhs); }
|
|
else { if (isslicetype(c, scanp.lhs)) { frame += 24; }
|
|
else { if (isstrtype(c, scanp.lhs)) { frame += 16; }
|
|
else { frame += 8; }; }; };
|
|
scanseenmark(c, scanp.str);
|
|
};
|
|
scanp = scanp.next;
|
|
};
|
|
if (fn_.body != nil) { frame += scanlocals(c, fn_.body); };
|
|
// Drop the stubs so emission rebuilds c.locals with real offsets.
|
|
c.locals = nil;
|
|
if ((frame & 15) != 0) {
|
|
frame = (frame + 15) & ~15;
|
|
};
|
|
emitint(frame: i64);
|
|
emitline("\n");
|
|
|
|
emitline("\tPUSHQ\tBP\n");
|
|
emitline("\tMOVQ\tSP, BP\n");
|
|
emitline("\tSUBQ\t$");
|
|
emitint(frame: i64);
|
|
emitline(", SP\n");
|
|
|
|
cgfnparams(c, fn_.list);
|
|
c.lastwasreturn = 0;
|
|
if (fn_.body != nil) { cgstmt(c, fn_.body); };
|
|
|
|
if (c.lastwasreturn == 0) {
|
|
// Run any registered defers in LIFO order before the
|
|
// implicit return.
|
|
rundefers(c);
|
|
// Zero AX before the fall-through return — matches C cgen,
|
|
// which always emits this so void-returning fns don't leak
|
|
// a stale callee value to their caller.
|
|
emitline("\tMOVQ\t$0, AX\n");
|
|
emitline("\tMOVQ\tBP, SP\n");
|
|
emitline("\tPOPQ\tBP\n");
|
|
emitline("\tRET\n");
|
|
};
|
|
};
|
|
|
|
// ---- file-level entry ------------------------------------------------
|
|
|
|
export fn cgfile(c: *cgen, file: *node) void = {
|
|
if (file == nil) { return; };
|
|
c.strlits = nil;
|
|
c.strlitseq = 0;
|
|
collectaliases(c, file);
|
|
// Enums must register before structs — fieldsize on a tkind-typed
|
|
// field needs the enum's storage size, otherwise it falls back to
|
|
// 8 (wrong load width).
|
|
collectenums(c, file);
|
|
collectstructs(c, file);
|
|
collectdefs(c, file);
|
|
collectfnrets(c, file);
|
|
fficollect(c, file);
|
|
collectmods(c, file);
|
|
collectlets(c, file);
|
|
let d: *node = file.list;
|
|
for (d != nil) {
|
|
if (d.kind == nkind.N_FNDECL) {
|
|
if (d.body != nil) {
|
|
cgfn(c, d);
|
|
};
|
|
};
|
|
d = d.next;
|
|
};
|
|
letpreintern(c, file);
|
|
emitdatasection(c);
|
|
emitdefconstants(c, file);
|
|
emitletdataw(c, file);
|
|
};
|