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:
@@ -1348,6 +1348,49 @@ fn structnaturalsize(si: *structinfo) i32 = {
|
||||
return n;
|
||||
};
|
||||
|
||||
// sretretsize — if `t` ultimately denotes a plain TY_STRUCT > 24B,
|
||||
// return its natural size; else 0. Tagged unions, tuples, str,
|
||||
// slices, scalars route through their existing register-return ABIs
|
||||
// (AX/DX/CX/[R8]) regardless of size. Task #23 mirrors cstage's
|
||||
// cg_sret_retsize predicate. Resolves N_TNAME → struct via structlookup
|
||||
// and unwraps one leading N_TBANG so `type box = !big;` still
|
||||
// triggers sret on the underlying big.
|
||||
export fn sretretsize(c: *cgen, t: *node) i32 = {
|
||||
if (t == nil) { return 0; };
|
||||
let r: *node = t;
|
||||
if (r.kind == nkind.N_TBANG) {
|
||||
r = r.lhs;
|
||||
if (r == nil) { return 0; };
|
||||
};
|
||||
if (r.kind != nkind.N_TNAME) { return 0; };
|
||||
// Primitives / aliased-to-primitives are never sret.
|
||||
if (primsize(r.str) > 0) { return 0; };
|
||||
if (streq(r.str, "str")) { return 0; };
|
||||
let si: *structinfo = structlookup(c, r.str);
|
||||
if (si == nil) { return 0; };
|
||||
let n: i32 = structnaturalsize(si);
|
||||
if (n <= 24) { return 0; };
|
||||
return n;
|
||||
};
|
||||
|
||||
// callsretsize — if N_CALL `n`'s callee returns a plain TY_STRUCT
|
||||
// > 24B, return its natural size; else 0. Wraps sretretsize over the
|
||||
// callee's resolved return type, used by cglet / cgassign receive
|
||||
// sites and cgcall to detect sret at the receive / emit boundaries.
|
||||
export fn callsretsize(c: *cgen, n: *node) i32 = {
|
||||
if (n == nil) { return 0; };
|
||||
if (n.kind != nkind.N_CALL) { return 0; };
|
||||
let callee: *node = n.lhs;
|
||||
if (callee == nil) { return 0; };
|
||||
let cn: str;
|
||||
cn.ptr = nil; cn.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) { cn = callee.str; };
|
||||
if (callee.kind == nkind.N_DOT) { cn = callee.str; };
|
||||
if (cn.len == 0) { return 0; };
|
||||
let rt: *node = fnretlookup(c, cn);
|
||||
return sretretsize(c, rt);
|
||||
};
|
||||
|
||||
fn structlookup(c: *cgen, name: str) *structinfo = {
|
||||
// Exact match first: bare-from-source struct names and already-
|
||||
// leafed lookups hit here directly.
|
||||
|
||||
Reference in New Issue
Block a user