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:
2026-06-01 11:18:44 +09:00
parent fb62aa38f1
commit 19e6b68d03
8 changed files with 605 additions and 84 deletions

View File

@@ -491,7 +491,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);
@@ -1256,6 +1256,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; };