w6c+wwstage: emit over-cap tuple return via sret callee-side (#10 Fold A)
A tuple return whose SysV register-return footprint exceeds the caps
(> 4 integer eightbytes or > 2 SSE eightbytes) previously LOUD-STOPPED
at the N_RETURN SEND. Fold A makes the CALLEE emit such a return through
the existing >24B-struct sret skeleton:
- classifier (cg_sret_retsize / sretretsize) grows a TY_TUPLE arm:
walk the element footprint over the SAME caps the SEND uses, and
return the tuple's natural total size (type table) when over-cap,
else 0. The gp/sse caps are factored to a single shared SSoT
(TUPLE_GPCAP / TUPLE_SSECAP — cgen.c macros in cstage, cgen.ww defs
in wwstage) consumed by the classifier AND every emit/receive site
(the SEND, the destructure guards, the cgcall arg guard) — so
classify and emit can't disagree in either stage.
- the SEND replaces the loud-stop with a write-through: cgexpr each
element, store it through *(@sretarg) at its packed layout offset
(the t.0/t.1 positional layout), each at its natural width so a
narrow tail stores MOVL/MOVB not an over-MOVQ (#169); the dest base
reloads into DX each step since a wide element clobbers AX/BX/CX.
Then the existing struct-sret epilogue (MOVQ @sretarg->AX; ret).
- the prologue already wires @sretarg when the classifier is nonzero.
The CALL/receive side is deliberately untouched: the N_MLET/N_MASSIGN
destructure loud-stops stay, so an over-cap tuple return is not yet
usefully callable. The end-to-end round-trip arrives with Fold B (#10-B).
Symmetric cstage (cmd/w6c/cgen.c) + wwstage (cgen.ww / cgenstmt.ww /
cgenutil.ww); combined.ww amalgams regenerated. Test 798 asserts the
callee now COMPILES (no loud-stop) and w6c vs w6c_ww .s byte-identical
across all-wide, str, narrow-tail, and float-over-cap shapes; no runtime
row (uncallable until Fold B). All 236 pass incl. 990-997 byte-id.
This commit is contained in:
130
cmd/w6c/cgen.c
130
cmd/w6c/cgen.c
@@ -159,18 +159,11 @@ type_chase_named(Type *t)
|
||||
return t;
|
||||
}
|
||||
|
||||
/* cg_sret_retsize — if `rt` is a plain TY_STRUCT > 24B, return its
|
||||
* natural size (the sret threshold); else 0. Tagged unions, tuples,
|
||||
* str, and slices route through their existing register-return ABIs
|
||||
* regardless of size. Task #23. */
|
||||
static int
|
||||
cg_sret_retsize(Type *rt)
|
||||
{
|
||||
rt = type_chase_named(rt);
|
||||
if (rt == NULL || rt->kind != TY_STRUCT) return 0;
|
||||
if ((int)rt->size <= 24) return 0;
|
||||
return (int)rt->size;
|
||||
}
|
||||
/* cg_sret_retsize — sret classifier; defined after the tuple register-
|
||||
* return helpers (tuple_rseq / tuple_ebytes / fld_isfloat) it consults
|
||||
* for the over-cap-tuple arm. Forward-declared here for the earlier
|
||||
* callers (cgcall, fn prologue). Task #23 / #10. */
|
||||
static int cg_sret_retsize(Type *rt);
|
||||
|
||||
static int
|
||||
node_isfloat(Node *n)
|
||||
@@ -242,6 +235,14 @@ static const int tuple_rseq[] = { D_AX, D_DX, D_CX, D_R8 };
|
||||
* return convergence (#171) is a call-site swap, not a redesign. */
|
||||
static const int tuple_sse_seq[] = { D_X0, D_X1 };
|
||||
|
||||
/* #10: the register-return-ABI caps — the SINGLE SSoT shared by the sret
|
||||
* classifier (cg_sret_retsize over-cap-tuple arm) AND every emit/receive
|
||||
* site (N_RETURN tuple SEND, N_MLET/N_MASSIGN destructure, cgcall guard).
|
||||
* Classify and emit MUST agree on these, else a tuple gets classified
|
||||
* sret by one and in-reg by the other → corruption. */
|
||||
#define TUPLE_GPCAP ((int)nelem(tuple_rseq))
|
||||
#define TUPLE_SSECAP ((int)nelem(tuple_sse_seq))
|
||||
|
||||
static int
|
||||
tuple_ebytes(int wide)
|
||||
{
|
||||
@@ -293,6 +294,41 @@ fld_isfloat(Type *t, int *isf32)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* cg_sret_retsize — sret classification by natural return size:
|
||||
* - plain TY_STRUCT > 24B → its natural size (the #23 threshold).
|
||||
* - TY_TUPLE whose SysV register-return footprint exceeds the caps
|
||||
* (> TUPLE_GPCAP integer eightbytes or > TUPLE_SSECAP float
|
||||
* eightbytes) → its natural total size, so the callee returns it
|
||||
* via sret instead of registers (#10). The element footprint walk
|
||||
* matches the N_RETURN tuple SEND exactly (a float = 1 SSE
|
||||
* eightbyte, a slice/str its 3-word header, a scalar 1 GP word).
|
||||
* Everything else (in-cap tuples, tagged unions, str, slices, scalars)
|
||||
* routes through its register-return ABI → 0. */
|
||||
static int
|
||||
cg_sret_retsize(Type *rt)
|
||||
{
|
||||
rt = type_chase_named(rt);
|
||||
if (rt == NULL) return 0;
|
||||
if (rt->kind == TY_STRUCT)
|
||||
return (int)rt->size <= 24 ? 0 : (int)rt->size;
|
||||
if (rt->kind == TY_TUPLE) {
|
||||
int gptotal = 0, ssecount = 0, f32;
|
||||
for (Tparam *p = rt->params; p; p = p->next) {
|
||||
Type *pu = type_chase_named(p->type);
|
||||
int wide = pu && (pu->kind == TY_SLICE
|
||||
|| pu->kind == TY_STR);
|
||||
if (fld_isfloat(p->type, &f32))
|
||||
ssecount++;
|
||||
else
|
||||
gptotal += tuple_ebytes(wide);
|
||||
}
|
||||
if (gptotal > TUPLE_GPCAP || ssecount > TUPLE_SSECAP)
|
||||
return (int)rt->size;
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* fld_issigned — true iff a sub-word field/element load needs sign
|
||||
* extension (i8 → MOVSBQ, i16 → MOVSWQ, i32 → MOVSXD). Follows NAMED
|
||||
* and ENUM aliases via type_isunsigned, then peels off the unsigned
|
||||
@@ -5843,10 +5879,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
/* 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]))
|
||||
if (gptot > TUPLE_GPCAP || sstot > TUPLE_SSECAP)
|
||||
fatal("tuple arg exceeds return-cursor ABI "
|
||||
"capacity; see #163/#164");
|
||||
if (cg_tupargscr == 0) {
|
||||
@@ -8687,9 +8720,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
* Both rows are loud-stopped at their cap (rule-7, never a
|
||||
* silent collide): INTEGER 4, SSE 2. The SAME class split
|
||||
* drives the receive sites. */
|
||||
int gpcap = (int)(sizeof tuple_rseq / sizeof tuple_rseq[0]);
|
||||
int ssecap = (int)(sizeof tuple_sse_seq
|
||||
/ sizeof tuple_sse_seq[0]);
|
||||
int ssecap = TUPLE_SSECAP;
|
||||
int gptotal = 0, ssecount = 0, f32;
|
||||
for (Node *e = n->lhs->list; e; e = e->next) {
|
||||
if (fld_isfloat(e->type, &f32))
|
||||
@@ -8698,14 +8729,49 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
gptotal += tuple_ebytes(node_isstr(e)
|
||||
|| node_isslice(e));
|
||||
}
|
||||
if (gptotal > gpcap)
|
||||
fatal("tuple return exceeds integer register-return "
|
||||
"ABI capacity (%d eightbytes: AX,DX,CX,R8); "
|
||||
"see return-ABI #10", gpcap);
|
||||
if (ssecount > ssecap)
|
||||
fatal("tuple return exceeds SSE register-return ABI "
|
||||
"capacity (%d eightbytes: X0,X1); "
|
||||
"see return-ABI #10", ssecap);
|
||||
if (gptotal > TUPLE_GPCAP || ssecount > ssecap) {
|
||||
/* #10 Fold A: over-cap tuple returns via sret. The
|
||||
* prologue wired @sretarg (cg_sret_retsize agrees on
|
||||
* the caps — the shared SSoT), holding the caller-
|
||||
* prealloc dest. Store each element through
|
||||
* *(@sretarg) at its packed layout offset (running
|
||||
* sum of element sizes — the t.0/t.1 positional
|
||||
* layout, N_DOT TY_TUPLE arm), each at its natural
|
||||
* width so a narrow tail doesn't over-MOVQ (#169);
|
||||
* the dest base is reloaded into DX each step since a
|
||||
* wide element's cgexpr clobbers AX/BX/CX. Then reuse
|
||||
* the struct-sret epilogue. The CALL/receive side
|
||||
* stays loud-stopped (#10 Fold B). */
|
||||
int foff = 0;
|
||||
for (Node *e = n->lhs->list; e; e = e->next) {
|
||||
int isflt = fld_isfloat(e->type, &f32);
|
||||
int wide = node_isstr(e) || node_isslice(e);
|
||||
int esz = e->type ? (int)e->type->size : 8;
|
||||
cgexpr(c, e, *locals);
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, cg_sret_arg_off), areg(D_DX));
|
||||
if (isflt)
|
||||
ins2(c, f32 ? A_MOVSS : A_MOVSD,
|
||||
areg(D_X0), amem(D_DX, foff));
|
||||
else if (wide) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_DX, foff + 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_DX, foff + 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_DX, foff + 16));
|
||||
} else
|
||||
ins2(c, fldstoreop(e->type, esz),
|
||||
areg(D_AX), amem(D_DX, foff));
|
||||
foff += esz;
|
||||
}
|
||||
ins2(c, A_MOVQ, amem(D_BP, cg_sret_arg_off),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
|
||||
ins1(c, A_POPQ, areg(D_BP));
|
||||
ins0(c, A_RET);
|
||||
break;
|
||||
}
|
||||
int fscr = 0;
|
||||
if (ssecount > 0) {
|
||||
if (cg_tupfscr != 0)
|
||||
@@ -8936,9 +9002,8 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
* INTEGER cursor into an 8B slot. Both rows loud-stop at their
|
||||
* cap. */
|
||||
cgexpr(c, n->rhs, *locals);
|
||||
int gpcap = (int)(sizeof tuple_rseq / sizeof tuple_rseq[0]);
|
||||
int ssecap = (int)(sizeof tuple_sse_seq
|
||||
/ sizeof tuple_sse_seq[0]);
|
||||
int gpcap = TUPLE_GPCAP;
|
||||
int ssecap = TUPLE_SSECAP;
|
||||
int gptotal = 0, ssetotal = 0, lf32;
|
||||
for (Node *l = n->list; l; l = l->next) {
|
||||
Type *t = l->type;
|
||||
@@ -8995,9 +9060,8 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
Type *rt = n->rhs ? n->rhs->type : NULL;
|
||||
Type *ru = (rt && rt->kind == TY_NAMED) ? rt->under : rt;
|
||||
Tparam *tp0 = (ru && ru->kind == TY_TUPLE) ? ru->params : NULL;
|
||||
int gpcap = (int)(sizeof tuple_rseq / sizeof tuple_rseq[0]);
|
||||
int ssecap = (int)(sizeof tuple_sse_seq
|
||||
/ sizeof tuple_sse_seq[0]);
|
||||
int gpcap = TUPLE_GPCAP;
|
||||
int ssecap = TUPLE_SSECAP;
|
||||
int gptotal = 0, ssetotal = 0, mf32;
|
||||
for (Tparam *tp = tp0; tp; tp = tp->next) {
|
||||
Type *u = (tp->type && tp->type->kind == TY_NAMED)
|
||||
|
||||
Reference in New Issue
Block a user