w6c+wwstage: route sret dest to the global symbol on struct-return into a global (#220)
Assigning a >24B by-value struct-return into a GLOBAL lvalue dropped the struct body: the sret dest was routed to a BP scratch temp and only the 8-byte return pointer was stored (`MOVQ AX, g(SB)`); the callee wrote the full struct to the scratch, which never reached the global. A BP-relative dest offset cannot name a global symbol. Pre-existing GATE-BLIND silent miscompile — both stages emit the same broken store, so byte-id (990-997) stays green while runtime is wrong — latent until the eFinal io surface put a global `cgoutstream: memio.stream` (>24B) on the path, where it made cgen.ww's self-built w6c_ww buffer every function body into a corrupt global (pos stayed 0) and emit prologue-only output. Fix, both stages, byte-identical: route the sret dest pointer to the global symbol so the callee writes the full struct through RDI straight into the global. cstage adds cg_sret_dest_sym, mirroring the existing str/slice global arm (skip the @sretscr scratch, emit `LEAQ masym(sym), DI`). wwstage carries the lhs IDENT node (sretdestnode) and emits `LEAQ name(SB), DI` via emitsymname — identical to cstage's symbol mangling, verified cs.s==ww.s on the probe and across 990-997. #211-family (by-value struct + global/pointer), but a distinct site: the cstage assignment-store into a global, not the wwstage call-return. N_LET-global static-init (`let g: T = mk()` at top level) is a separate, independently-broken path (#221) — link-fails for init-via-call, returns 0 for constant init — not the sret-receive gap and not on the eFinal path; deferred. test/wcc/940_global_sret_run: global assign (plus a branched callee to defeat const-fold), through-pointer mutation (the io vtable-callback shape that surfaced this), and local-init/assign regressions — runtime asserts on both stages (the net, since byte-id is gate-blind here) plus cs.s==ww.s. Discrimination confirmed by revert+rebuild: with the global arm disabled, global_assign emits the truncated store and exits 1.
This commit is contained in:
@@ -106,6 +106,11 @@ static int cg_tagscr_sz;
|
||||
* return value. No temporary in outer's frame. */
|
||||
static int cg_sret_arg_off;
|
||||
static int cg_sret_dest_off;
|
||||
/* #220: caller-side dest for an sret receive into a GLOBAL lvalue. A
|
||||
* BP-relative i32 offset (cg_sret_dest_off) can't name a top-level let,
|
||||
* so the symbol name is carried instead and emitted as LEAQ name(SB),DI.
|
||||
* Mutually exclusive with cg_sret_dest_off. */
|
||||
static const char *cg_sret_dest_sym;
|
||||
static int cg_sretscr_off;
|
||||
static int cg_sretscr_sz;
|
||||
static int cg_sret_forward;
|
||||
@@ -4798,6 +4803,18 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
cg_sret_dest_off = 0;
|
||||
break;
|
||||
}
|
||||
/* #220: `g = f();` where g is a GLOBAL struct >24B.
|
||||
* No BP slot to use as the sret dest, so route RDI
|
||||
* to g's symbol address. Mirrors the str/slice
|
||||
* global arm above (let_islet + LEAQ masym). The
|
||||
* scalar fall-through below would emit a truncated
|
||||
* 8-byte `MOVQ AX, g(SB)` and drop the struct body. */
|
||||
if (let_islet(n->lhs->str)) {
|
||||
cg_sret_dest_sym = n->lhs->str;
|
||||
cgexpr(c, n->rhs, locals);
|
||||
cg_sret_dest_sym = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Struct local reassignment: `s = expr;` where s is
|
||||
* a TY_STRUCT local of size <=24B. Two rhs shapes,
|
||||
@@ -5738,12 +5755,19 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* left RDI free — we never popped a user arg into it. */
|
||||
int sret_call_sz = 0;
|
||||
int sret_call_off = 0;
|
||||
const char *sret_dest_sym = NULL; /* #220 */
|
||||
{
|
||||
Type *ret = (cu && cu->kind == TY_FN)
|
||||
? cu->ret : NULL;
|
||||
sret_call_sz = cg_sret_retsize(ret);
|
||||
}
|
||||
if (sret_call_sz > 0) {
|
||||
if (sret_call_sz > 0 && cg_sret_dest_sym != NULL) {
|
||||
/* #220: GLOBAL dest — RDI gets LEAQ name(SB) below; no
|
||||
* @sretscr slot needed (the callee writes the struct
|
||||
* straight into g's storage). */
|
||||
sret_dest_sym = cg_sret_dest_sym;
|
||||
cg_sret_dest_sym = NULL;
|
||||
} else if (sret_call_sz > 0) {
|
||||
/* @sretscr is only needed when the result is dropped
|
||||
* (no `let x = f();` receiver wired the call's dest into
|
||||
* cg_sret_dest_off). Allocate first-use per #15/#26c
|
||||
@@ -5953,6 +5977,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
amem(D_BP, cg_sret_arg_off),
|
||||
areg(D_DI));
|
||||
cg_sret_forward = 0;
|
||||
} else if (sret_dest_sym != NULL) {
|
||||
/* #220: sret into a GLOBAL — RDI = &g(SB). */
|
||||
ins2(c, A_LEAQ, masym(c, sret_dest_sym),
|
||||
areg(D_DI));
|
||||
} else {
|
||||
ins2(c, A_LEAQ,
|
||||
amem(D_BP, sret_call_off),
|
||||
@@ -8947,6 +8975,7 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
||||
cg_tagscr_sz = 0;
|
||||
cg_sret_arg_off = 0;
|
||||
cg_sret_dest_off = 0;
|
||||
cg_sret_dest_sym = NULL;
|
||||
cg_sretscr_off = 0;
|
||||
cg_sretscr_sz = 0;
|
||||
cg_sret_forward = 0;
|
||||
|
||||
Reference in New Issue
Block a user