cstage+selfhost+test: wire sret return-forwarding (#9)
Class A compile-time fatal retirement — `return f()` from an sret callee bailed both stages with "sret return-forwarding for >24B struct not wired (task #23)" at every site, forcing every caller into a `let r = f(); return r;` workaround that materialised an intermediate >24B copy in outer's frame. Forwarding now elides the copy: outer reloads its own @sretarg into RDI for the inner CALL via `MOVQ @sretarg(BP), DI` (NOT `LEAQ <local>, DI`), inner writes directly into outer's caller-prealloc dest, RAX (inner's returned dest pointer per the sret discipline) is already outer's return value. Wires 2 sites × 2 stages (same triangle as #23): caller arg-shift in cgcall/pushargsrev gains an RDI-source switch via cg_sret_forward / c.sretforward; callee return-arm in cgreturn replaces the fail-loud abort with cgexpr-into-cgcall + epilogue. The @sretscr scratch slot is still pre-allocated on the forwarding branch (unused) — eliding would need AST-walk awareness in scanlocals; symmetric-allocate is the simpler path and keeps byte-id with non-forwarding callers. Latent surfaced and filed during probe (NOT in this commit's scope): multi-sret-receive in a single fn diverges between stages — cstage always allocates @sretscr on first sret CALL, wwstage only when sretdestoff == 0. Bootstrap stays green because the selfhost corpus has zero >1-sret-receive call sites. Tests: - 721_sret_struct_return gains 2 forwarding rows + a 4th asm- presence sentinel: at the inner CALL site inside outer fn, the RDI source must be `MOVQ -K(BP), DI` (reload of outer's saved @sretarg) NOT `LEAQ -K(BP), DI` (a temporary local would write inner's payload into outer's frame, not caller's dest). - 925_sret_struct_return_run gains 3 forwarding rows: simple quad forward, multi-arg inner (pair-by-value + scalar args alongside the hidden RDI), and slice-payload (decoder { i64, []u8 } — the utf8 iterator shape, asserts ptr/len/cap survive the @sretarg chain). 90/90 ok. 995_self_rebuild stays green (ww2==ww3==ww4 byte-id).
This commit is contained in:
@@ -54,11 +54,18 @@ static int cg_retscr;
|
||||
* cg_sretscr_off — per-fn @sretscr discard slot for sret CALLs whose
|
||||
* result is dropped (no named receiver). Single-slot
|
||||
* SSoT mirroring cg_retscr. Sized to the largest
|
||||
* discarded sret return type in the fn. */
|
||||
* discarded sret return type in the fn.
|
||||
* cg_sret_forward — set by cgreturn `return f();` from an sret callee
|
||||
* to signal cgcall: source RDI for inner from outer's
|
||||
* saved @sretarg (MOVQ) instead of LEAQ'ing a local
|
||||
* dest. Inner writes into outer's caller-prealloc;
|
||||
* inner's RAX (the dest pointer) is already outer's
|
||||
* return value. No temporary in outer's frame. */
|
||||
static int cg_sret_arg_off;
|
||||
static int cg_sret_dest_off;
|
||||
static int cg_sretscr_off;
|
||||
static int cg_sretscr_sz;
|
||||
static int cg_sret_forward;
|
||||
|
||||
/* Per-fn defer stack: pushed in registration order, popped (emitted)
|
||||
* in reverse at each return. */
|
||||
@@ -4547,10 +4554,27 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
/* sret hidden first-arg (#23): load &dest into RDI AFTER
|
||||
* all user-arg pops have finished — the pop loop started
|
||||
* its int-arg cursor at 1, so RDI was never written. */
|
||||
if (sret_call_sz > 0)
|
||||
ins2(c, A_LEAQ, amem(D_BP, sret_call_off),
|
||||
areg(D_DI));
|
||||
* its int-arg cursor at 1, so RDI was never written.
|
||||
*
|
||||
* Forwarding (task #9 follow-up): when outer's `return f();`
|
||||
* forwards through an sret callee, source RDI from outer's
|
||||
* saved @sretarg — inner writes directly into outer's
|
||||
* caller-prealloc dest. No temporary in outer's frame.
|
||||
* The @sretscr slot was still allocated above for byte-id
|
||||
* lockstep with wwstage's scanlocals reservation; it goes
|
||||
* unused on the forwarding branch. */
|
||||
if (sret_call_sz > 0) {
|
||||
if (cg_sret_forward) {
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, cg_sret_arg_off),
|
||||
areg(D_DI));
|
||||
cg_sret_forward = 0;
|
||||
} else {
|
||||
ins2(c, A_LEAQ,
|
||||
amem(D_BP, sret_call_off),
|
||||
areg(D_DI));
|
||||
}
|
||||
}
|
||||
/* SysV: variadic callees require AL to hold the count of
|
||||
* XMM regs used in the variable portion. We don't pass
|
||||
* floats yet, so AL=0 covers every case we emit. */
|
||||
@@ -6548,18 +6572,29 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
if (n->lhs && cg_ret_type && cg_sret_arg_off != 0) {
|
||||
Type *rt = cg_ret_type;
|
||||
if (rt->kind == TY_NAMED) rt = rt->under;
|
||||
/* `return f();` from a sret callee falls through the
|
||||
* arm below (rhs is N_CALL, not N_IDENT/N_STRUCTLIT)
|
||||
* and would silent-miscompile: cgexpr places inner's
|
||||
* result in @sretscr but outer never copies into
|
||||
* *@sretarg and never sets RAX. Fail loud per
|
||||
* CLAUDE.md rule 7; the workaround `let r = f();
|
||||
* return r;` is already wired and correct. */
|
||||
/* sret return-forwarding (task #9 follow-up to #23):
|
||||
* `return f();` where outer + inner both return the
|
||||
* same >24B struct shape. Outer's @sretarg already
|
||||
* holds its caller's prealloc dest; pass it to inner
|
||||
* in RDI (set by cgcall via cg_sret_forward), inner
|
||||
* writes directly there, inner's RAX (dest pointer)
|
||||
* is already outer's return value. The trailing
|
||||
* MOVQ @sretarg(BP), AX is redundant after inner's
|
||||
* RET but kept for byte-id symmetry with the
|
||||
* N_IDENT / N_STRUCTLIT arms below. */
|
||||
if (rt && rt->kind == TY_STRUCT
|
||||
&& (int)rt->size > 24
|
||||
&& n->lhs->kind == N_CALL)
|
||||
fatal("cgreturn: sret return-forwarding "
|
||||
"for >24B struct not wired (task #23)");
|
||||
&& n->lhs->kind == N_CALL) {
|
||||
cg_sret_forward = 1;
|
||||
cgexpr(c, n->lhs, *locals);
|
||||
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;
|
||||
}
|
||||
if (rt && rt->kind == TY_STRUCT
|
||||
&& (int)rt->size > 24
|
||||
&& (n->lhs->kind == N_IDENT
|
||||
@@ -7061,6 +7096,7 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
||||
cg_sret_dest_off = 0;
|
||||
cg_sretscr_off = 0;
|
||||
cg_sretscr_sz = 0;
|
||||
cg_sret_forward = 0;
|
||||
|
||||
int frame = 0;
|
||||
Local *locals = NULL;
|
||||
|
||||
Reference in New Issue
Block a user