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:
@@ -15493,7 +15493,7 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
||||
};
|
||||
// The producing call already satisfied #164's return caps;
|
||||
// guard anyway (tupstore indexes [AX,DX,CX,R8] / [X0,X1]).
|
||||
if (gptot > 4 || sstot > 2) {
|
||||
if (gptot > TUPLE_GPCAP || sstot > TUPLE_SSECAP) {
|
||||
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);
|
||||
@@ -16258,6 +16258,34 @@ export fn sretretsize(c: *cgen, t: *node) i32 = {
|
||||
r = r.lhs;
|
||||
if (r == nil) { return 0; };
|
||||
};
|
||||
if (r.kind == nkind.N_TTUPLE) {
|
||||
// #10: over-cap tuple → sret. Walk the element TYPE nodes
|
||||
// (pt.lhs) over the SAME caps the SEND/receive use; a float =
|
||||
// 1 SSE eightbyte, a slice/str its 3-word header, a scalar 1
|
||||
// GP word. Return the tuple's natural total size (tinfo.size,
|
||||
// the type table) so the callee returns via sret. Mirrors
|
||||
// cstage cg_sret_retsize TY_TUPLE arm; TUPLE_GPCAP/TUPLE_SSECAP
|
||||
// are the shared cap SSoT with the cgreturn SEND emitter.
|
||||
let ssecap: i32 = TUPLE_SSECAP;
|
||||
let gptotal: i32 = 0;
|
||||
let ssecount: i32 = 0;
|
||||
let pt: *node = r.list;
|
||||
for (pt != nil) {
|
||||
let et: *node = pt.lhs;
|
||||
if (isfloattype(c, et)) {
|
||||
ssecount = ssecount + 1;
|
||||
} else {
|
||||
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||
gptotal = gptotal + tupebytes(wide);
|
||||
};
|
||||
pt = pt.next;
|
||||
};
|
||||
if (gptotal > TUPLE_GPCAP || ssecount > ssecap) {
|
||||
let rti: *tinfo = r.type_: *tinfo;
|
||||
if (rti != nil) { return rti.size: i32; };
|
||||
};
|
||||
return 0;
|
||||
};
|
||||
if (r.kind != nkind.N_TNAME) { return 0; };
|
||||
// Primitives / aliased-to-primitives are never sret.
|
||||
if (primsize(r.str) > 0) { return 0; };
|
||||
@@ -26200,7 +26228,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
// loud-stop at their cap (rule-7): INTEGER 4, SSE 2. The SAME
|
||||
// class split drives the receive sites.
|
||||
if (rhs.kind == nkind.N_TUPLE) {
|
||||
let ssecap: i32 = 2; // X0,X1 per SysV
|
||||
let ssecap: i32 = TUPLE_SSECAP; // X0,X1 per SysV
|
||||
let gptotal: i32 = 0;
|
||||
let ssecount: i32 = 0;
|
||||
let e: *node = rhs.list;
|
||||
@@ -26213,17 +26241,78 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
};
|
||||
e = e.next;
|
||||
};
|
||||
if (gptotal > 4) { // AX,DX,CX,R8 capacity
|
||||
// pinned loud-stop, inline like cgen.ww:604 (cstage
|
||||
// uses fatal(), err.c) — surface, don't corrupt.
|
||||
let msg: str = "tuple return exceeds integer register-return ABI capacity (4 eightbytes: AX,DX,CX,R8); see return-ABI #10\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (ssecount > ssecap) {
|
||||
let msg: str = "tuple return exceeds SSE register-return ABI capacity (2 eightbytes: X0,X1); see return-ABI #10\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
if (gptotal > TUPLE_GPCAP || ssecount > ssecap) {
|
||||
// #10 Fold A: over-cap tuple returns via sret. The
|
||||
// prologue wired @sretarg (sretretsize agrees on the
|
||||
// caps — TUPLE_GPCAP/TUPLE_SSECAP, the shared SSoT),
|
||||
// holding the caller-prealloc dest. Store each element
|
||||
// through *(@sretarg)
|
||||
// at its packed layout offset (running sum of element
|
||||
// sizes from the return-type tuple node — the t.0/t.1
|
||||
// positional layout), 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). Byte-identical to cstage cgen.c
|
||||
// N_RETURN over-cap tuple arm.
|
||||
let saoff: i32 = localfind(c, "@sretarg");
|
||||
let pt: *node = nil;
|
||||
if (c.fnret != nil) { pt = c.fnret.list; };
|
||||
let we: *node = rhs.list;
|
||||
let foff: i32 = 0;
|
||||
for (we != nil) {
|
||||
let isflt: bool = isfloattype(c, we);
|
||||
let wide: bool = nodeisstr(c, we) || nodeisslice(c, we);
|
||||
let esz: i32 = 8;
|
||||
if (pt != nil) {
|
||||
let eti: *tinfo = pt.lhs.type_: *tinfo;
|
||||
if (eti != nil) { esz = eti.size: i32; };
|
||||
};
|
||||
cgexpr(c, we);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(saoff: i64);
|
||||
emitline("(BP), DX\n");
|
||||
if (isflt) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, we)) { mov = "MOVSS"; };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg(foff: i64, "DX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
if (wide) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(foff: i64, "DX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitdispreg((foff + 8): i64, "DX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitdispreg((foff + 16): i64, "DX");
|
||||
emitline("\n");
|
||||
} else {
|
||||
let sop: str = tnodestoreop(c, we, esz);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(foff: i64, "DX");
|
||||
emitline("\n");
|
||||
};
|
||||
};
|
||||
foff += esz;
|
||||
we = we.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(saoff: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tBP, SP\n");
|
||||
emitline("\tPOPQ\tBP\n");
|
||||
emitline("\tRET\n");
|
||||
c.lastwasreturn = 1;
|
||||
return;
|
||||
};
|
||||
let fscr: i32 = 0;
|
||||
if (ssecount > 0) {
|
||||
@@ -27581,7 +27670,7 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
||||
|
||||
if (n.rhs != nil) { cgexpr(c, n.rhs); };
|
||||
|
||||
let ssecap: i32 = 2; // X0,X1 per SysV
|
||||
let ssecap: i32 = TUPLE_SSECAP; // X0,X1 per SysV
|
||||
let gptotal: i32 = 0;
|
||||
let ssetotal: i32 = 0;
|
||||
let l: *node = n.list;
|
||||
@@ -27599,7 +27688,7 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
};
|
||||
if (gptotal > 4) { // AX,DX,CX,R8 capacity
|
||||
if (gptotal > TUPLE_GPCAP) { // AX,DX,CX,R8 capacity
|
||||
// pinned loud-stop, inline like cgen.ww:604 (cstage uses
|
||||
// fatal(), err.c) — surface, don't corrupt.
|
||||
let msg: str = "tuple destructure exceeds integer register-return ABI capacity (4 eightbytes: AX,DX,CX,R8); see return-ABI #10\n";
|
||||
@@ -27668,7 +27757,7 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
|
||||
cgexpr(c, rhs);
|
||||
|
||||
let ssecap: i32 = 2; // X0,X1 per SysV
|
||||
let ssecap: i32 = TUPLE_SSECAP; // X0,X1 per SysV
|
||||
let gptotal: i32 = 0;
|
||||
let ssetotal: i32 = 0;
|
||||
let l: *node = n.list;
|
||||
@@ -27688,7 +27777,7 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
l = l.next;
|
||||
if (pt != nil) { pt = pt.next; };
|
||||
};
|
||||
if (gptotal > 4) { // AX,DX,CX,R8 capacity
|
||||
if (gptotal > TUPLE_GPCAP) { // AX,DX,CX,R8 capacity
|
||||
// pinned loud-stop, inline like cgen.ww:604 (cstage uses
|
||||
// fatal(), err.c) — surface, don't corrupt.
|
||||
let msg: str = "tuple destructure exceeds integer register-return ABI capacity (4 eightbytes: AX,DX,CX,R8); see return-ABI #10\n";
|
||||
@@ -29110,6 +29199,15 @@ type enumtype = struct {
|
||||
def LOOP_MAX: i32 = 16;
|
||||
def DEFER_MAX: i32 = 16;
|
||||
|
||||
// The SysV register-return-ABI caps — the SINGLE SSoT shared by the sret
|
||||
// classifier (sretretsize over-cap-tuple arm) AND every emit/receive site
|
||||
// (cgreturn tuple SEND, cgmlet/cgmassign destructure, cgcall arg guard).
|
||||
// Classify and emit MUST agree on these, else a tuple gets classified
|
||||
// sret by one and in-reg by the other -> corruption. Mirrors cstage
|
||||
// cgen.c TUPLE_GPCAP/TUPLE_SSECAP (#10).
|
||||
def TUPLE_GPCAP: i32 = 4; // AX,DX,CX,R8
|
||||
def TUPLE_SSECAP: i32 = 2; // X0,X1
|
||||
|
||||
type cgen = struct {
|
||||
locals: *local,
|
||||
// atlocals — persistent registry of `@`-prefix scratch slots
|
||||
|
||||
Reference in New Issue
Block a user