cstage+selfhost+test: System V AMD64 sret discipline for >24B struct return (#23)
Class B shared miscompile pre-fix: cstage skipped the CALL emit at the
receive site (frame collapsed, exit 11); wwstage emitted CALL but
truncated 32B return to AX only (slice payload garbage, segfault on
g.b[0]). Both stages now lower plain TY_STRUCT > 24B through the SysV
sret discipline: caller pre-allocates dest, passes &dest in RDI as a
hidden first-arg (user args shift to SI/DX/CX/R8/R9/+stack), callee
saves RDI to @sretarg at the prologue and writes through it, returns
RDI in RAX. Surfaced by lib/encoding/utf8 pre-flight when the
Hoehrmann decoder (32B) hit 698_cgreturn_struct.c's OUT-OF-SCOPE
marker.
Scope: plain TY_STRUCT > 24B only — tagged unions, tuples, str, slice
keep their existing register-return ABIs. `return f()` forwarding
from a sret callee is fail-loud-not-wired (compile-time error in
both stages, follow-up filed); the workaround `let r = f(); return
r;` is wired and byte-identical. Discard-context calls (`f();` of an
sret-returning function) share a per-fn single-slot @sretscr;
consecutive discards reuse the same slot.
698_cgreturn_struct.c's OUT-OF-SCOPE marker retired in the same
commit; three positive rows (32B quad, 32B decoder, 40B five) now
assert the sret discipline across both stages via byte-id diff.
Tests:
- 721_sret_struct_return pins three asm-presence sentinels per
row: (a) LEAQ -K(BP), DI immediately before CALL at the receive
site, (b) MOVQ -K(BP), AX before RET in the callee (sret return-
the-pointer), (c) negative-assert no MOVQ AX, -K(BP) capture for
return type >8B. Three rows × both stages × cmp -s byte-id.
- 925_sret_struct_return_run runtime-pins 7 rows × 2 stages
including the collision row (25B+ struct BOTH returned AND passed
by-value as arg — catches arg-shift, sister site to #11), nested
struct payload, slice payload, reassign-receive, N_IDENT return
rhs.
89/89 ok. 995_self_rebuild stays green (ww2==ww3==ww4 byte-id).
This commit is contained in:
@@ -2991,6 +2991,24 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
let nargs: i32 = pushargsrev(c, n.list, calleeparams);
|
||||
// sret call (#23): callee returns plain TY_STRUCT > 24B. The
|
||||
// dest pointer lands in RDI; start intidx at 1 to skip RDI in
|
||||
// the user-arg pop loop and emit `LEAQ off(BP), DI` AFTER all
|
||||
// pops have finished (so they don't clobber RDI). The dest off
|
||||
// is either the receive site's slot (c.sretdestoff, propagated
|
||||
// from cglet / cgassign ident) or the per-fn @sretscr discard
|
||||
// slot reserved by scanlocals.
|
||||
let sretcs: i32 = callsretsize(c, n);
|
||||
let sretcalloff: i32 = 0;
|
||||
if (sretcs > 0) {
|
||||
if (c.sretdestoff != 0) {
|
||||
sretcalloff = c.sretdestoff;
|
||||
c.sretdestoff = 0;
|
||||
} else {
|
||||
sretcalloff = localadd(c, "@sretscr",
|
||||
c.sretscrsz, nil);
|
||||
};
|
||||
};
|
||||
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
||||
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else
|
||||
// pops into the int stream (DI..R9) per the SysV ABI. Walk the
|
||||
@@ -2999,6 +3017,7 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
// the remaining slots stay on the stack and the callee reads them
|
||||
// via 16+8*k(BP). Caller-cleanup is emitted after the CALL.
|
||||
let intidx: i32 = 0;
|
||||
if (sretcs > 0) { intidx = 1; };
|
||||
let fpidx: i32 = 0;
|
||||
let a: *node = n.list;
|
||||
let popped: i32 = 0;
|
||||
@@ -3129,6 +3148,14 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// sret hidden first-arg (#23): load &dest into RDI AFTER all
|
||||
// user-arg pops have finished — intidx started at 1 so RDI was
|
||||
// never written. The CALL emit follows immediately.
|
||||
if (sretcs > 0) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(sretcalloff: i64);
|
||||
emitline("(BP), DI\n");
|
||||
};
|
||||
if (isfnptrcall) {
|
||||
// Load fn-ptr field value into AX; CALL AX. We emit the
|
||||
// load AFTER the args have been popped (so AX/BX/etc
|
||||
@@ -5304,6 +5331,21 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
};
|
||||
if (n.rhs != nil
|
||||
&& n.rhs.kind == nkind.N_CALL) {
|
||||
// sret receive (#23): plain
|
||||
// TY_STRUCT > 24B from a CALL.
|
||||
// `s` is the prealloc dest; the
|
||||
// callee writes through hidden RDI
|
||||
// directly into off(BP). Mirror of
|
||||
// cglet's sret branch.
|
||||
if (lcnsz > 24) {
|
||||
let rscs: i32 = callsretsize(c, n.rhs);
|
||||
if (rscs > 0) {
|
||||
c.sretdestoff = off;
|
||||
cgexpr(c, n.rhs);
|
||||
c.sretdestoff = 0;
|
||||
return;
|
||||
};
|
||||
};
|
||||
let lcsz: i32 = lcnsz;
|
||||
if (lcsz <= 24) {
|
||||
let tlm: i32 = lcsz - (lcsz / 8) * 8;
|
||||
|
||||
Reference in New Issue
Block a user