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); 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 (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) 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); };