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:
187
cmd/w6c/cgen.c
187
cmd/w6c/cgen.c
@@ -46,6 +46,17 @@ static int cg_retscr;
|
||||
* @retscr single-slot convention + wwstage's `@tupfscr` '@'-prefix dedup;
|
||||
* 0 means "not yet allocated". */
|
||||
static int cg_tupfscr;
|
||||
/* Per-fn @tupargscr staging slot (#163, single-slot SSoT). A tuple
|
||||
* PASSED AS AN ARGUMENT (the param twin of #164's tuple return) is left
|
||||
* by its producing call in the return-ABI cursor (AX/DX/CX/R8 + X0/X1);
|
||||
* the SEND restages it into this slot positionally (tuple_store), then
|
||||
* pushes the slot words onto the stack so the pop drains them into the
|
||||
* SysV ARG cursor (DI/SI/.. + X0..X7). A frame slot decouples the
|
||||
* return-class regs (which overlap the arg-class regs) from the arg
|
||||
* placement. Reused per tuple arg (drained to the stack before the next
|
||||
* arg); 0 means "not yet allocated", cg_tupargscr_sz the cached width. */
|
||||
static int cg_tupargscr;
|
||||
static int cg_tupargscr_sz;
|
||||
/* Per-fn @-prefix scratch SSoT (task #26, follow-up to #15-cstage's
|
||||
* @retscr). Pre-#26 each site allocated a labelseq-stamped fresh slot
|
||||
* per call (mklabel "tagbase" / "tagscr" / "argscr" / "idxscr"); the
|
||||
@@ -191,6 +202,22 @@ node_isslice(Node *n)
|
||||
return n && type_isslice(n->type);
|
||||
}
|
||||
|
||||
/* node_tuplearg — the underlying TY_TUPLE Type of a tuple-typed argument
|
||||
* VALUE, else NULL. #163: scoped to an N_CALL producer — the only form
|
||||
* that leaves a tuple in the return-ABI cursor (AX/DX/CX/R8 + X0/X1, per
|
||||
* #164). A tuple ident / literal as a first-class value is a separate
|
||||
* unimplemented gap (`let t = (1,2)` does not materialise a slot today),
|
||||
* so the SEND restricts to the call form and loud-stops the rest rather
|
||||
* than push stale registers (rule 7, never a silent drop). */
|
||||
static Type *
|
||||
node_tuplearg(Node *n)
|
||||
{
|
||||
if (n == NULL || n->kind != N_CALL) return NULL;
|
||||
Type *t = n->type;
|
||||
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
||||
return (u && u->kind == TY_TUPLE) ? u : NULL;
|
||||
}
|
||||
|
||||
/* #83: positional tuple register-return ABI. Tuple elements ride
|
||||
* consecutive eightbytes over tuple_rseq[]; a slice/str rides its 3-word
|
||||
* {ptr,len,cap} header (ref/hare/rt/ensure.ha:4-8, ty_str->size SSoT), a
|
||||
@@ -5411,6 +5438,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
continue;
|
||||
}
|
||||
cgexpr(c, args[i], locals);
|
||||
Type *tuparg_push = node_tuplearg(args[i]);
|
||||
if (node_isfloat(args[i])) {
|
||||
/* f32 spills 4B (MOVSS), f64 8B (MOVSD): the SysV
|
||||
* float class drives the width per ref/qbe
|
||||
@@ -5449,6 +5477,72 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (sz > 8)
|
||||
ins1(c, A_PUSHQ, areg(D_DX));
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
} else if (tuparg_push) {
|
||||
/* #163: tuple ARG (param twin of #164's return).
|
||||
* cgexpr above left the tuple in the return-ABI
|
||||
* cursor; restage it into @tupargscr by SysV class
|
||||
* (tuple_store, the #164 helper), then push the slot
|
||||
* words high→low so the pop drains slot+0 first into
|
||||
* the ARG cursor. The frame slot decouples the
|
||||
* return-class regs (AX/DX/CX/R8 + X0/X1) from the
|
||||
* overlapping arg-class regs (DI/SI/.. + X0..X7). */
|
||||
int gpcur = 0, ssecur = 0, eoff = 0, ef32;
|
||||
int gptot = 0, sstot = 0, tsz = 0;
|
||||
for (Tparam *p = tuparg_push->params; p; p = p->next) {
|
||||
Type *pu = (p->type
|
||||
&& p->type->kind == TY_NAMED)
|
||||
? p->type->under : p->type;
|
||||
int wide = pu && (pu->kind == TY_SLICE
|
||||
|| pu->kind == TY_STR);
|
||||
if (fld_isfloat(p->type, &ef32))
|
||||
sstot++;
|
||||
else
|
||||
gptot += tuple_ebytes(wide);
|
||||
/* slot stride per element (sum == tuple slot
|
||||
* size); matches the wwstage slotsize() walk so
|
||||
* the @tupargscr width + reverse-push count agree
|
||||
* byte-for-byte. */
|
||||
tsz += wide ? (int)pu->size : 8;
|
||||
}
|
||||
/* The producing call already satisfied #164's
|
||||
* return caps; guard anyway (tuple_store indexes
|
||||
* tuple_rseq[4] / tuple_sse_seq[2]). */
|
||||
if (gptot > (int)(sizeof tuple_rseq
|
||||
/ sizeof tuple_rseq[0])
|
||||
|| sstot > (int)(sizeof tuple_sse_seq
|
||||
/ sizeof tuple_sse_seq[0]))
|
||||
fatal("tuple arg exceeds return-cursor ABI "
|
||||
"capacity; see #163/#164");
|
||||
if (cg_tupargscr == 0) {
|
||||
cg_tupargscr = local_alloc(c, &locals,
|
||||
"@tupargscr", tsz, cg_frame);
|
||||
cg_tupargscr_sz = tsz;
|
||||
} else if (tsz > cg_tupargscr_sz) {
|
||||
fatal("cgcall: @tupargscr cached sz %d, "
|
||||
"need %d (pinned offset can't grow; "
|
||||
"#163)", cg_tupargscr_sz, tsz);
|
||||
}
|
||||
for (Tparam *p = tuparg_push->params; p; p = p->next) {
|
||||
Type *pu = (p->type
|
||||
&& p->type->kind == TY_NAMED)
|
||||
? p->type->under : p->type;
|
||||
int wide = pu && (pu->kind == TY_SLICE
|
||||
|| pu->kind == TY_STR);
|
||||
int isflt = fld_isfloat(p->type, &ef32);
|
||||
tuple_store(c, p->type, wide, gpcur, ssecur,
|
||||
cg_tupargscr + eoff);
|
||||
if (isflt)
|
||||
ssecur++;
|
||||
else
|
||||
gpcur += tuple_ebytes(wide);
|
||||
eoff += wide ? (int)pu->size : 8;
|
||||
}
|
||||
for (int w = tsz - 8; w >= 0; w -= 8) {
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, cg_tupargscr + w),
|
||||
areg(D_AX));
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
}
|
||||
} else {
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
}
|
||||
@@ -5505,6 +5599,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* the callee via positive offsets from BP. The caller is
|
||||
* responsible for cleaning them up after CALL. */
|
||||
int ii = (sret_call_sz > 0) ? 1 : 0, fi = 0, stackslots = 0;
|
||||
Type *tu;
|
||||
for (int i = 0; i < argcount; i++) {
|
||||
if (widen[i]) {
|
||||
/* Pop widened tagged slot into arg-register
|
||||
@@ -5567,6 +5662,47 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
else
|
||||
stackslots++;
|
||||
}
|
||||
} else if ((tu = node_tuplearg(args[i])) != NULL) {
|
||||
/* #163: drain the tuple's staged words (pushed
|
||||
* slot+0 first) into the SysV arg cursor by SysV
|
||||
* class — a float MOVSD/MOVSS off (SP) into the
|
||||
* next XMM (X0..X7), everything else POPQ into the
|
||||
* next INTEGER arg reg (DI/SI/..); a slice/str its
|
||||
* 3-word {ptr,len,cap}. Reg overflow loud-stops
|
||||
* (rule 7): the partial-spill stitch is out of
|
||||
* scope (twin of #164's cap). */
|
||||
int ef32;
|
||||
for (Tparam *p = tu->params; p; p = p->next) {
|
||||
Type *pu = (p->type
|
||||
&& p->type->kind == TY_NAMED)
|
||||
? p->type->under : p->type;
|
||||
int wide = pu && (pu->kind == TY_SLICE
|
||||
|| pu->kind == TY_STR);
|
||||
if (fld_isfloat(p->type, &ef32)) {
|
||||
if (fi >= 8)
|
||||
fatal("tuple arg float "
|
||||
"element overflows SSE "
|
||||
"arg regs (X0..X7); "
|
||||
"stitch out of scope, "
|
||||
"see #163");
|
||||
ins2(c, ef32 ? A_MOVSS : A_MOVSD,
|
||||
amem(D_SP, 0),
|
||||
areg(sysv_fargregs[fi]));
|
||||
ins2(c, A_ADDQ, aimm(8),
|
||||
areg(D_SP));
|
||||
fi++;
|
||||
continue;
|
||||
}
|
||||
int eb = tuple_ebytes(wide);
|
||||
if (ii + eb > 6)
|
||||
fatal("tuple arg element "
|
||||
"overflows integer arg regs "
|
||||
"(DI/SI/DX/CX/R8/R9); stitch "
|
||||
"out of scope, see #163");
|
||||
for (int k = 0; k < eb; k++)
|
||||
ins1(c, A_POPQ,
|
||||
areg(sysv_argregs[ii++]));
|
||||
}
|
||||
} else {
|
||||
if (ii < 6) {
|
||||
ins1(c, A_POPQ, areg(sysv_argregs[ii]));
|
||||
@@ -8513,6 +8649,8 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
||||
cg_ret_type = fn->type ? fn->type->ret : NULL;
|
||||
cg_retscr = 0;
|
||||
cg_tupfscr = 0;
|
||||
cg_tupargscr = 0;
|
||||
cg_tupargscr_sz = 0;
|
||||
cg_tagbase = 0;
|
||||
cg_tagbase_sz = 0;
|
||||
cg_tagscr = 0;
|
||||
@@ -8575,6 +8713,55 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
||||
int is_tagged = tagged_sz > 0;
|
||||
int isf = cg_isfloat(pt);
|
||||
|
||||
/* #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} header — and
|
||||
* store each into the param's frame slot positionally (eoff
|
||||
* steps by the element's slot width: a slice/str 24B, else 8B,
|
||||
* matching the tuple-field-access offset walk + the SEND). Reg
|
||||
* overflow loud-stops (rule 7), the partial-spill stitch out of
|
||||
* scope (twin of #164's cap). Placed before the single-class
|
||||
* eightbytes logic below, which can't model a mixed GP/SSE
|
||||
* aggregate. */
|
||||
if (pu && pu->kind == TY_TUPLE) {
|
||||
int sz = (int)pu->size;
|
||||
int off = localoff(c, &locals, p->str, sz, &frame);
|
||||
int eoff = 0, ef32;
|
||||
for (Tparam *te = pu->params; te; te = te->next) {
|
||||
Type *teu = (te->type
|
||||
&& te->type->kind == TY_NAMED)
|
||||
? te->type->under : te->type;
|
||||
int wide = teu && (teu->kind == TY_SLICE
|
||||
|| teu->kind == TY_STR);
|
||||
if (fld_isfloat(te->type, &ef32)) {
|
||||
if (fargi >= 8)
|
||||
fatal("tuple param float element "
|
||||
"overflows SSE arg regs "
|
||||
"(X0..X7); stitch out of "
|
||||
"scope, see #163");
|
||||
ins2(c, ef32 ? A_MOVSS : A_MOVSD,
|
||||
areg(sysv_fargregs[fargi]),
|
||||
amem(D_BP, off + eoff));
|
||||
fargi++;
|
||||
eoff += 8;
|
||||
continue;
|
||||
}
|
||||
int eb = tuple_ebytes(wide);
|
||||
if (argi + eb > 6)
|
||||
fatal("tuple param element overflows "
|
||||
"integer arg regs (DI/SI/DX/CX/R8/"
|
||||
"R9); stitch out of scope, see #163");
|
||||
for (int k = 0; k < eb; k++, argi++)
|
||||
ins2(c, A_MOVQ,
|
||||
areg(sysv_argregs[argi]),
|
||||
amem(D_BP, off + eoff + k * 8));
|
||||
eoff += wide ? (int)teu->size : 8;
|
||||
}
|
||||
if (tp) tp = tp->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Args overflowing register classes live at positive offsets
|
||||
* from BP (16 + i*8). We register them as Locals at those
|
||||
* offsets, no spill needed. */
|
||||
|
||||
Reference in New Issue
Block a user