wcc: tuple-param ABI via SSE/GP arg cursors (#163)

Tuples were unhandled as parameters — no tuple arm in arg-push, arg-pop, or
callee-recv in either stage — so a tuple param fell to the 1-GP-word else and
dropped all but its first element (integer tuple params too; floats doubly
lost). Add tuple-param arms (SEND push+pop, callee RECV) across both stages,
reusing #164's per-element SysV classify with the 6-GP (DI,SI,DX,CX,R8,R9) +
8-SSE (X0-X7) arg cursors. A frame slot @tupargscr decouples the producing
call's return cursor from the overlapping arg cursor (capture-before-clobber).
Overflow (>6 GP / >8 SSE) fails loud (rule 7). Scoped to the N_CALL producer;
first-class tuple values (ident/literal) remain a separate unimplemented gap.

Gate-blind (the bootstrap passes no tuple params) — covered by table-driven
probe 905, which proves pre-fix element-drop and the loud-stop.
This commit is contained in:
2026-05-27 22:13:46 +09:00
parent 153c7b3b46
commit 0465c423c1
8 changed files with 986 additions and 3 deletions

View File

@@ -490,6 +490,59 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
return rest + 1;
};
cgexpr(c, arg);
// #163: tuple ARG (param twin of #164's return). cgexpr left the
// tuple in the return-ABI cursor (AX/DX/CX/R8 + X0/X1); restage it
// into @tupargscr by SysV class (tupstore, the #164 helper) and push
// the slot words high->low so the pop drains slot+0 first into the
// SysV ARG cursor. The frame slot decouples the return-class regs
// from the overlapping arg-class regs. rettupleof scopes to an
// N_CALL producer (tuple idents/literals as values are a separate
// unimplemented gap; the SEND never pushes stale regs, rule 7).
let tuparg: *node = rettupleof(c, arg);
if (tuparg != nil) {
let gptot: i32 = 0;
let sstot: i32 = 0;
let tsz: i32 = 0;
let p: *node = tuparg.list;
for (p != nil) {
let et: *node = p.lhs;
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
if (isfloattype(c, et)) { sstot += 1; }
else { gptot += tupebytes(wide); };
tsz += slotsize(c, et);
p = p.next;
};
// The producing call already satisfied #164's return caps;
// guard anyway (tupstore indexes [AX,DX,CX,R8] / [X0,X1]).
if (gptot > 4 || sstot > 2) {
let msg: str = "tuple arg exceeds return-cursor ABI capacity; see #163/#164\n";
os.write(2, msg.ptr, msg.len: u64);
os.exit(1);
};
let scr: i32 = localadd(c, "@tupargscr", tsz, nil);
let gpcur: i32 = 0;
let ssecur: i32 = 0;
let eoff: i32 = 0;
p = tuparg.list;
for (p != nil) {
let et: *node = p.lhs;
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
tupstore(c, gpcur, ssecur, scr + eoff, wide, et);
if (isfloattype(c, et)) { ssecur += 1; }
else { gpcur += tupebytes(wide); };
eoff += slotsize(c, et);
p = p.next;
};
let w: i32 = tsz - 8;
for (w >= 0) {
emitline("\tMOVQ\t");
emitoff((scr + w): i64);
emitline("(BP), AX\n");
emitline("\tPUSHQ\tAX\n");
w -= 8;
};
return rest + tsz / 8;
};
if (nodeisslice(c, arg)) {
emitline("\tPUSHQ\tCX\n");
emitline("\tPUSHQ\tBX\n");