After the frontend consolidated into one syntax package (#74), wcc still referenced syntax symbols unqualified — residue of the old flat combined namespace, where bare refs resolved by accident. Under separate compilation Hare and Go both require the package qualifier, so those bare refs would not sep-resolve. Qualify every wcc reference to a syntax type, function, or enum member as syntax.X across the seven syntax-importing files. Resolution-only: the resolved symbol and emitted code are unchanged, so the two combined.ww regenerate textually but all five _ww binaries hold byte-for-byte. The struct-literal sites resolve via #76. This makes w6c fully separate-compilable.
665 lines
21 KiB
Plaintext
665 lines
21 KiB
Plaintext
// selfhost/cmd/wcc/cgendecl.ww — split out of cgen.ww.
|
|
//
|
|
// Houses the top-level emission glue:
|
|
// - cgfnparams: parameter spilling per SysV
|
|
// - cgfn: fn body emit (TEXT/SUBQ patched after body), prologue
|
|
// deferred via cgen.ww's cgoutstate so the frame size
|
|
// reflects every emit-time localadd (#15/#26c)
|
|
// - cgfile: file-level entry (the exported driver)
|
|
//
|
|
// Bundler pulls this in transitively via cgen.ww; consumers don't
|
|
// need to `use cgendecl;` directly.
|
|
|
|
package wcc;
|
|
|
|
import os;
|
|
import syntax;
|
|
import strconv;
|
|
|
|
|
|
// ---- function-level cgen ---------------------------------------------
|
|
|
|
fn cgfnparams(c: *cgen, params: *syntax.node) void = {
|
|
let p: *syntax.node = params;
|
|
// sret (#23): RDI is consumed by the hidden dest pointer
|
|
// (already spilled to @sretarg by cgfn); the first user param
|
|
// lands in SI.
|
|
let idx: i32 = 0;
|
|
if (localfind(c, "@sretarg") != 0) { idx = 1; };
|
|
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;
|
|
// #38b: words consumed by MEMORY-class (>48B tagged) params —
|
|
// post-walk consistency check against stkcursor.
|
|
let memwords: i32 = 0;
|
|
for (p != nil) {
|
|
if (p.kind == syntax.nkind.N_PARAM) {
|
|
let nm: str = p.str;
|
|
// Hare-style variadic `T...`: callee receives a []T
|
|
// slice (3 register words / 24B). p.lhs is already
|
|
// the []T wrap installed by check.ww installparams
|
|
// (mirrors cstage check.c:455 tp->type promotion), so
|
|
// we consume it directly — re-wrapping via slicewrap
|
|
// would yield [][]T.
|
|
if (p.op == syntax.tkind.TK_ELLIPSIS) {
|
|
let tn: *syntax.node = p.lhs;
|
|
if (idx + 3 <= 6) {
|
|
let off: i32 = localadd(c, nm, tyslicesize(): i32, tn);
|
|
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 { if (idx < 6) {
|
|
// Partial-fit stitch — variadic `T...` is a slice
|
|
// at the ABI boundary (the call site synthesises a
|
|
// 24B descriptor and pushes ptr/len/cap), so this
|
|
// mirrors the slice branch at cgendecl.ww:518.
|
|
let off: i32 = localadd(c, nm, tyslicesize(): i32, tn);
|
|
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 < 3) {
|
|
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, tn, 16 + stkcursor*8);
|
|
stkcursor += 3;
|
|
};};
|
|
p = p.next;
|
|
continue;
|
|
};
|
|
// #99: chase a TY_NAMED alias (multi-level) to its
|
|
// underlying tuple — the param twin of the cstage type.c
|
|
// type_chase_named tuple-arm. A bare (i64,i64) is N_TTUPLE
|
|
// (no chase); `type tp=(i64,i64)` is an N_TNAME resolved via
|
|
// aliaslookup. Without the chase the alias fell to the scalar
|
|
// path → 1 slot, SI dropped, t.1 garbage. Slot size + element
|
|
// walk source the RESOLVED node; localadd keeps the declared
|
|
// p.lhs so field reads chase identically to cstage (byte-id).
|
|
let tt99: *syntax.node = nil;
|
|
if (p.lhs != nil) {
|
|
tt99 = p.lhs;
|
|
for (tt99 != nil && tt99.kind == syntax.nkind.N_TNAME) {
|
|
tt99 = aliaslookup(c, tt99.str);
|
|
};
|
|
};
|
|
if (p.lhs != nil) { if (tt99 != nil && tt99.kind == syntax.nkind.N_TTUPLE) {
|
|
// #163: tuple PARAM receive (param twin of #164's
|
|
// return). Walk the tuple's elements over the SysV
|
|
// arg cursor — a float reads its XMM (X0..X7),
|
|
// everything else an INTEGER arg reg (DI/SI/..); a
|
|
// slice/str its 3-word {ptr,len,cap} — storing each
|
|
// into the param slot positionally (eoff steps by
|
|
// slotsize, matching the t.0/t.1 field-access walk +
|
|
// the SEND). Reg overflow loud-stops (rule 7); the
|
|
// partial-spill stitch is out of scope (twin of #164).
|
|
let off: i32 = localadd(c, nm, slotsize(c, p.lhs), p.lhs);
|
|
let eoff: i32 = 0;
|
|
let te: *syntax.node = tt99.list;
|
|
for (te != nil) {
|
|
let et: *syntax.node = te.lhs;
|
|
if (isfloattype(c, et)) {
|
|
if (fidx >= 8) {
|
|
let msg: str = "tuple param float element overflows SSE arg regs (X0..X7); stitch out of scope, see #163\n";
|
|
os.write(2, msg.ptr, msg.len: u64);
|
|
os.exit(1);
|
|
};
|
|
let mov: str = "MOVSD";
|
|
if (isf32type(c, et)) { mov = "MOVSS"; };
|
|
emitline("\t");
|
|
emitline(mov);
|
|
emitline("\t");
|
|
emitline(fargregname(fidx));
|
|
emitline(", ");
|
|
emitoff((off + eoff): i64);
|
|
emitline("(BP)\n");
|
|
fidx += 1;
|
|
} else {
|
|
let eb: i32 = tupeslotn(et) / 8;
|
|
if (idx + eb > 6) {
|
|
let msg: str = "tuple param element overflows integer arg regs (DI/SI/DX/CX/R8/R9); stitch out of scope, see #163\n";
|
|
os.write(2, msg.ptr, msg.len: u64);
|
|
os.exit(1);
|
|
};
|
|
let k: i32 = 0;
|
|
for (k < eb) {
|
|
emitline("\tMOVQ\t");
|
|
emitline(argregname(idx));
|
|
emitline(", ");
|
|
emitoff((off + eoff + k*8): i64);
|
|
emitline("(BP)\n");
|
|
idx += 1;
|
|
k += 1;
|
|
};
|
|
};
|
|
eoff += tupeslotn(et);
|
|
te = te.next;
|
|
};
|
|
p = p.next;
|
|
continue;
|
|
}; };
|
|
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;
|
|
};
|
|
let sfc: i32 = structfloatclass(c, p.lhs);
|
|
if (sfc != 0) {
|
|
// #165: float-bearing struct PARAM receive (param
|
|
// twin of #163's tuple). Classify each SysV
|
|
// eightbyte; a lone-f64 eightbyte reads its XMM
|
|
// (X0..X7), a pure-INT eightbyte its INTEGER arg reg
|
|
// (DI/SI/..), stored into the param slot at the
|
|
// 8-byte eightbyte stride. Gated to qualifying
|
|
// structs by structfloatclass — all-int + f32-packed
|
|
// fall through to the GP struct arm below (byte-id /
|
|
// #165b). Reg overflow loud-stops (rule 7).
|
|
let off: i32 = localadd(c, nm, structparamsize(c, p.lhs), p.lhs);
|
|
let nb: i32 = sfc & 15;
|
|
let e: i32 = 0;
|
|
for (e < nb) {
|
|
let issse: bool = (sfc & (16 << e)) != 0;
|
|
if (issse) {
|
|
if (fidx >= 8) {
|
|
let msg: str = "float struct param eightbyte overflows SSE arg regs (X0..X7); stitch out of scope, see #165\n";
|
|
os.write(2, msg.ptr, msg.len: u64);
|
|
os.exit(1);
|
|
};
|
|
emitline("\tMOVSD\t");
|
|
emitline(fargregname(fidx));
|
|
emitline(", ");
|
|
emitoff((off + e*8): i64);
|
|
emitline("(BP)\n");
|
|
fidx += 1;
|
|
} else {
|
|
if (idx >= 6) {
|
|
let msg: str = "float struct param eightbyte overflows integer arg regs (DI/SI/DX/CX/R8/R9); stitch out of scope, see #165\n";
|
|
os.write(2, msg.ptr, msg.len: u64);
|
|
os.exit(1);
|
|
};
|
|
emitline("\tMOVQ\t");
|
|
emitline(argregname(idx));
|
|
emitline(", ");
|
|
emitoff((off + e*8): i64);
|
|
emitline("(BP)\n");
|
|
idx += 1;
|
|
};
|
|
e += 1;
|
|
};
|
|
p = p.next;
|
|
continue;
|
|
};
|
|
if (istaggedtype(c, p.lhs)) {
|
|
let slot: i32 = slotsize(c, p.lhs);
|
|
let nw: i32 = slot / 8;
|
|
// #38b: MEMORY-class (>48B tagged) param — the
|
|
// caller staged the whole slot below the return
|
|
// address; read it in place at positive BP
|
|
// offsets. No spill, no frame growth, zero
|
|
// prologue bytes. Pre-fix this fell into the
|
|
// greedy stitch arm below while cstage received
|
|
// one scalar word (cs≠ww, silent).
|
|
// ref/qbe/amd64/sysv.c:80-85 / :411-426.
|
|
if (taggedmemargsize(p.lhs.type_: *syntax.tinfo) > 0) {
|
|
localaddstack(c, nm, p.lhs, 16 + stkcursor*8);
|
|
stkcursor += nw;
|
|
memwords += nw;
|
|
} else { 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, tyslicesize(): i32, 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 { if (idx < 6) {
|
|
// Partial-fit stitch — mirrors tagged at lines
|
|
// 440-469. Caller's pushargsrev greedy-fills the
|
|
// remaining argregs (ptr,len,cap order), the tail
|
|
// spills to +16+stkcursor*8(BP).
|
|
let off: i32 = localadd(c, nm, tyslicesize(): i32, 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 < 3) {
|
|
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 += 3;
|
|
};};
|
|
} else { if (isstrtype(c, p.lhs)) {
|
|
if (idx + 3 <= 6) {
|
|
// str IS []u8: 3-word param (ptr,len,cap), same as
|
|
// the slice arm above (#1/Phase 3). #60: route slot
|
|
// width through the primtypesize SSoT so #1's ty_str
|
|
// bump propagates here.
|
|
let off: i32 = localadd(c, nm, primtypesize("str"): i32, 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 { if (idx < 6) {
|
|
// Partial-fit stitch — mirrors the slice arm above.
|
|
// #60: same SSoT routing as the regs-fit arm above.
|
|
let off: i32 = localadd(c, nm, primtypesize("str"): i32, 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 < 3) {
|
|
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 += 3;
|
|
};};
|
|
} else { let stsz: i32 = structparamsize(c, p.lhs);
|
|
if (stsz > 0) {
|
|
// User-defined by-value struct ≤ 16B: 1 or 2
|
|
// integer eightbytes. Mirrors cstage's
|
|
// `struct_eb = (pu->size > 8) ? 2 : 1` and the
|
|
// matching reg/stack/stitch arms in cgen.c cgfn.
|
|
let nw: i32 = 1;
|
|
if (stsz > 8) { nw = 2; };
|
|
if (idx + nw <= 6) {
|
|
let off: i32 = localadd(c, nm, stsz, 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) {
|
|
let off: i32 = localadd(c, nm, stsz, 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 { let aggsz2: i32 = aggargsizetn(p.lhs.type_: *syntax.tinfo);
|
|
if (aggsz2 > 0) {
|
|
// #271: array / >16B-struct by-value param —
|
|
// received as ceil(sz/8) GP eightbytes, the
|
|
// callee twin of the generalised aggregate-arg
|
|
// push. Mirror of the cstage is_bigagg arm
|
|
// (regs-fit / partial-stitch / stack-spill).
|
|
let nw2: i32 = (aggsz2 + 7) / 8;
|
|
if (idx + nw2 <= 6) {
|
|
let off: i32 = localadd(c, nm, aggsz2, p.lhs);
|
|
let w: i32 = 0;
|
|
for (w < nw2) {
|
|
emitline("\tMOVQ\t");
|
|
emitline(argregname(idx));
|
|
emitline(", ");
|
|
emitoff((off + w*8): i64);
|
|
emitline("(BP)\n");
|
|
idx += 1;
|
|
w += 1;
|
|
};
|
|
} else { if (idx < 6) {
|
|
let off: i32 = localadd(c, nm, aggsz2, 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 < nw2) {
|
|
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 += nw2;
|
|
};};
|
|
} 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;
|
|
};
|
|
// #38b: a MEMORY-class tagged param cannot coexist with stack-
|
|
// spilled register-class params — both walk the same positive-BP
|
|
// cursor in declaration order while the caller's residual region
|
|
// puts spill words below every mem copy. Any non-mem cursor use
|
|
// leaves stkcursor past the mem words. Mirror of the cgcall
|
|
// caller-side check; loud-stop (rule 7).
|
|
if (memwords > 0 && stkcursor != memwords) {
|
|
let mp: str = "#38b: >48B tagged param mixed with stack-spilled params unwired\n";
|
|
os.write(2, mp.ptr, mp.len: u64);
|
|
os.exit(1);
|
|
};
|
|
};
|
|
|
|
fn cgfn(c: *cgen, fn_: *syntax.node) void = {
|
|
cgeninit(c);
|
|
c.fnname = fn_.str;
|
|
c.curmod = fn_.nmod;
|
|
c.fnret = fn_.lhs;
|
|
|
|
// sret callee (#23): return type is plain TY_STRUCT > 24B.
|
|
// Reserve 8B for @sretarg (holds the saved hidden RDI dest
|
|
// pointer); cgfnparams skips DI for user args, cgreturn writes
|
|
// through *(@sretarg) and returns @sretarg in RAX.
|
|
let sret_callee: bool = sretretsize(c, c.fnret) > 0;
|
|
|
|
// Capture the body into cgoutstate while c.frame grows under
|
|
// emit-time localadd calls (#15/#26c — wwstage dropped its
|
|
// scanlocals pre-pass to align DOWN with cstage's first-use
|
|
// pattern). The prologue (TEXT label, PUSHQ/MOVQ/SUBQ) emits
|
|
// after the body finishes so the frame size reflects every
|
|
// localadd. Mirrors cstage cmd/w6c/cgen.c cgfn which builds
|
|
// `subsp`/`text` Progs up front and patches their `from.offset`
|
|
// at the end via txt_emit.
|
|
cgout_enable();
|
|
|
|
if (sret_callee) {
|
|
let saoff: i32 = localadd(c, "@sretarg", 8, nil);
|
|
emitline("\tMOVQ\tDI, ");
|
|
emitoff(saoff: i64);
|
|
emitline("(BP)\n");
|
|
};
|
|
|
|
cgfnparams(c, fn_.list);
|
|
c.lastwasreturn = 0;
|
|
// Iterate the fn body's statements directly rather than dispatching
|
|
// the outermost N_BLOCK through cgstmt — cgblock now save/restores
|
|
// c.locals to scope inner shadows (post-#27), but the function body
|
|
// is not "an inner block": defers (queued during the body) and the
|
|
// implicit-return epilogue both call cgexpr after this loop and
|
|
// resolve identifiers via localfind, so the body's locals must
|
|
// still be in c.locals when we get there.
|
|
if (fn_.body != nil) {
|
|
if (fn_.body.kind == syntax.nkind.N_BLOCK) {
|
|
let s: *syntax.node = fn_.body.list;
|
|
for (s != nil) {
|
|
cgstmt(c, s);
|
|
s = s.next;
|
|
};
|
|
} else {
|
|
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 cstage,
|
|
// 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");
|
|
};
|
|
|
|
cgout_disable();
|
|
|
|
let frame: i32 = c.frame;
|
|
if ((frame & 15) != 0) { frame = (frame + 15) & ~15; };
|
|
|
|
// Emit the TEXT label via emitfnname so the def site picks up the
|
|
// same skip rule (FFI / `main` / empty-module) and the same module
|
|
// hint (this fn's own module) that the call sites use. M1 #32: the
|
|
// ROOT-unit main (imported==0) is the bare `_start` entry — emit it
|
|
// bare directly, mirroring collectmods' skip; without this its
|
|
// fn_.nmod hint would fall through modlookupforfn's first-leaf match
|
|
// onto an IMPORTED package's now-registered `pkg.main`.
|
|
emitline("TEXT ");
|
|
if (syntax.streq(fn_.str, "main") && fn_.imported == 0) {
|
|
emitbytes(fn_.str.ptr, fn_.str.len: u64);
|
|
} else {
|
|
emitfnname(c, fn_.str, fn_.nmod);
|
|
};
|
|
emitline(",$");
|
|
emitint(frame: i64);
|
|
emitline("\n");
|
|
|
|
emitline("\tPUSHQ\tBP\n");
|
|
emitline("\tMOVQ\tSP, BP\n");
|
|
emitline("\tSUBQ\t$");
|
|
emitint(frame: i64);
|
|
emitline(", SP\n");
|
|
|
|
cgout_flush();
|
|
};
|
|
|
|
// ---- file-level entry ------------------------------------------------
|
|
|
|
export fn cgfile(c: *cgen, file: *syntax.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);
|
|
defaultinferredlets(c, file);
|
|
collectlets(c, file);
|
|
let d: *syntax.node = file.list;
|
|
for (d != nil) {
|
|
if (d.kind == syntax.nkind.N_FNDECL) {
|
|
// #22 M3: emit code ONLY for this package's own decls. A
|
|
// `.wwi` dep fn is a body-less prototype that already skips
|
|
// (the body != nil gate); the explicit imported gate also
|
|
// covers a dep fn that still carries a body in a transitional
|
|
// unit, keeping the "emit iff imported==0" rule exception-free.
|
|
if (!(c.sepmode != 0 && d.imported != 0)) {
|
|
if (d.body != nil) {
|
|
cgfn(c, d);
|
|
};
|
|
};
|
|
};
|
|
d = d.next;
|
|
};
|
|
letpreintern(c, file);
|
|
emitdatasection(c);
|
|
emitdefconstants(c, file);
|
|
emitletdataw(c, file);
|
|
};
|