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:
@@ -300,16 +300,28 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
if (c.sretargoff != 0) {
|
||||
let scs: i32 = sretretsize(c, c.fnret);
|
||||
if (scs > 0) {
|
||||
// `return f();` from a sret callee would silent-
|
||||
// miscompile: cgexpr writes inner's result to
|
||||
// @sretscr but outer never copies into *@sretarg
|
||||
// and never sets RAX. Fail loud per CLAUDE.md
|
||||
// rule 7; the `let r = f(); return r;` workaround
|
||||
// is already wired and byte-id with cstage.
|
||||
// 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 c.sretforward), 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 (rhs.kind == nkind.N_CALL) {
|
||||
let m: str = "ww: cgreturn: sret return-forwarding for >24B struct not wired (task #23)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
c.sretforward = 1;
|
||||
cgexpr(c, rhs);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(c.sretargoff: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tBP, SP\n");
|
||||
emitline("\tPOPQ\tBP\n");
|
||||
emitline("\tRET\n");
|
||||
c.lastwasreturn = 1;
|
||||
return;
|
||||
};
|
||||
let okrhs: bool = false;
|
||||
if (rhs.kind == nkind.N_IDENT) { okrhs = true; };
|
||||
|
||||
Reference in New Issue
Block a user