Files
ww/selfhost/cmd/wcc/cgendecl.ww

663 lines
21 KiB
Plaintext

package wcc;
import os;
import syntax;
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.cursource = fn_.sourceid;
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);
if (c.initdispatchsymbol.len > 0 && syntax.streq(fn_.str, "main")
&& fn_.imported == 0 && c.sepisdep == 0) {
emitline("\tCALL\t");
emitbytes(c.initdispatchsymbol.ptr, c.initdispatchsymbol.len: u64);
emitline("(SB)\n");
};
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) {
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 module hint (this fn's own module) the call sites use. M1 #32:
// the ROOT-unit main (imported==0) is the bare `_start` entry — emit
// it bare directly, mirroring collectmods' skip. Irreducibly name-
// based: a `package main` primary's decls are MODULED "main" (the
// package clause sets curmod; cmd/ww/main.c:394), so main is NOT a
// bare-module decl — dropping this carve-out mangles it to `main.main`
// (undefined `main`). #84's bare-module handling is the orthogonal
// axis (a package-LESS `//ww:module-reset` fn, nmod empty), resolved
// in modlookupforfn, NOT here.
// #99: gate on sepisdep — under sep a dep unit's main is imported==0
// (path-carrying `//ww:module-reset`, #57); only the root/link-entry
// unit (wwiout==nil, #69) keeps the bare label.
emitline("TEXT ");
if (fn_.linksym.len > 0) {
emitbytes(fn_.linksym.ptr, fn_.linksym.len: u64);
} else { if (syntax.streq(fn_.str, "main") && fn_.imported == 0 && c.sepisdep == 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();
};
fn cgfile(c: *cgen, file: *syntax.node) i32 = {
cgwritefailed = 0;
if (file == nil) { return 0; };
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);
emitinitbackings(file.list);
emitletdataw(c, file);
return cgwritefailed;
};