// 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 == 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; }; }; // 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 == 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 { total += 8; }; }; }; }; 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; for (p != nil) { if (p.kind == N_PARAM) { let nm: str = p.str; if (istaggedtype(p.lhs)) { // tagged-union param: passed in 3 regs (tag, v0, v1), // 24-byte slot. 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 { if (isslicetype(c, p.lhs)) { // slice param: 3 regs (ptr, len, cap), 24-byte slot. 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 { if (isstrtype(c, p.lhs)) { // str param: passed in two regs (ptr, len). // Slot is 16 bytes; ptr at off+0, len at off+8. 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 { let off: i32 = localadd(c, nm, 8, p.lhs); emitline("\tMOVQ\t"); emitline(argregname(idx)); emitline(", "); emitoff(off: i64); emitline("(BP)\n"); idx += 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 == N_ATTR) { let an: str = a.str; if (streq(an, "symbol")) { isffi = true; }; }; a = a.next; }; if (!isffi) { 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 ` 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 == N_PARAM) { if (istaggedtype(scanp.lhs)) { frame += 24; } 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) { // 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); collectstructs(c, file); collectdefs(c, file); collectfnrets(c, file); fficollect(c, file); collectmods(c, file); let d: *node = file.list; for (d != nil) { if (d.kind == N_FNDECL) { if (d.body != nil) { cgfn(c, d); }; }; d = d.next; }; emitdatasection(c); emitdefconstants(c, file); };