w6c+wwstage: aggregate return from any addressable source (#272 commit-1)

The N_RETURN aggregate arms gated the return source on N_IDENT ||
N_STRUCTLIT; every other aggregate rvalue (array literal, o.field N_DOT,
a[i] N_INDEX, *p deref) fell through to the scalar-AX default = a silent
8-byte truncation. Both stages emitted byte-IDENTICAL wrong asm, so the
byte-id gate could not catch it (#263 class) — the fix converges on the
runtime oracle.

Mirror the arg-side closure #271 landed: both arms (≤24B @retscr and
>24B sret) now funnel N_ARRLIT through the literal element fill and
N_DOT/N_INDEX/deref through aggarg_srcaddr + the #265/#268 whole-
aggregate copy. Type-agnostic, so struct AND array returns are closed.
A close-by-construction loud-stop (rule 7) guards any future unhandled
aggregate source from reaching the scalar default.

Closes the callee-half of (b)/(c) and the addressable siblings. The
g = mk() global-receive caller-half is commit-2.

949_aggret_source_run pins the class: array-literal / N_DOT / N_INDEX /
deref / named-ident control / >24B-sret-deref / struct-field / struct-
deref, each summing all members (full readback) with per-row byte-id.
This commit is contained in:
2026-06-02 14:52:44 +09:00
parent 42dd70dc0c
commit 0d39129741
6 changed files with 1299 additions and 112 deletions

View File

@@ -9640,7 +9640,12 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
if (rt && (rt->kind == TY_STRUCT || rt->kind == TY_ARRAY)
&& (int)rt->size > 24
&& (n->lhs->kind == N_IDENT
|| n->lhs->kind == N_STRUCTLIT)) {
|| n->lhs->kind == N_STRUCTLIT
|| n->lhs->kind == N_ARRLIT
|| n->lhs->kind == N_DOT
|| n->lhs->kind == N_INDEX
|| (n->lhs->kind == N_UN
&& n->lhs->op == TK_STAR))) {
/* Natural size = max(foff + fsz) over declared
* fields; mirrors selfhost cgenutil.ww
* structnaturalsize / sretretsize. Pre-fix this
@@ -9668,7 +9673,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
cg_structlit_fill(c, locals, rt,
n->lhs, DST_PTR_LOCAL,
cg_sret_arg_off, NULL, 0);
} else {
} else if (n->lhs->kind == N_IDENT) {
/* N_IDENT: word-copy from rhs slot to
* *(@sretarg). Whole 8B words via MOVQ;
* trailing partial words via MOVL/MOVB
@@ -9704,6 +9709,50 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
amem(D_BX, k));
k += 1;
}
} else if (n->lhs->kind == N_ARRLIT) {
/* #272: a >24B array-literal return has no
* consumer and the ptr-relative element fill
* is untested. Loud-stop (rule 7) rather than
* fall to the scalar default. ≤24B is wired. */
fatal("#272: >24B array-literal return "
"unsupported (rule 7, no consumer)");
} else {
/* #272: N_DOT / N_INDEX / deref — land the
* source ADDRESS in SI FIRST (aggarg_srcaddr
* clobbers BX on its N_INDEX spine), THEN
* reload the dest ptr from @sretarg into BX
* and memcpy sz bytes — same #265/#268 copy
* shape as the ≤24B arm. Loud-stop any source
* the helper can't address. */
if (!aggarg_srcaddr(c, n->lhs, D_SI, *locals))
fatal("#272: aggregate return from "
"unsupported source kind %d",
n->lhs->kind);
ins2(c, A_MOVQ,
amem(D_BP, cg_sret_arg_off),
areg(D_BX));
int k = 0;
while (k + 8 <= sz) {
ins2(c, A_MOVQ, amem(D_SI, k),
areg(D_AX));
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BX, k));
k += 8;
}
while (k + 4 <= sz) {
ins2(c, A_MOVL, amem(D_SI, k),
areg(D_AX));
ins2(c, A_MOVL, areg(D_AX),
amem(D_BX, k));
k += 4;
}
while (k < sz) {
ins2(c, A_MOVB, amem(D_SI, k),
areg(D_AX));
ins2(c, A_MOVB, areg(D_AX),
amem(D_BX, k));
k += 1;
}
}
/* sret return: RAX = dest pointer. */
ins2(c, A_MOVQ,
@@ -9729,10 +9778,23 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
* for the same rationale. The ≤24B register-return
* ABI uses the same TY_STRUCT gate. */
Type *rt = type_chase_named(cg_ret_type);
/* #272: aggregate-return source-shape closure. Beyond the
* #267 N_IDENT/N_STRUCTLIT pair, every OTHER addressable
* aggregate rvalue (`return [..]` N_ARRLIT, `return o.f`
* N_DOT, `return a[i]` N_INDEX, `return *p` deref) fell to
* the scalar-AX default below = silent truncation. Funnel
* them through the SAME @retscr materialise the arg side
* closed in #271 (aggarg_srcaddr). N_CALL still passes
* through the tail (the callee already left AX/DX/CX). */
if (rt && (rt->kind == TY_STRUCT || rt->kind == TY_ARRAY)
&& rt->size <= 24
&& (n->lhs->kind == N_IDENT
|| n->lhs->kind == N_STRUCTLIT)) {
|| n->lhs->kind == N_STRUCTLIT
|| n->lhs->kind == N_ARRLIT
|| n->lhs->kind == N_DOT
|| n->lhs->kind == N_INDEX
|| (n->lhs->kind == N_UN
&& n->lhs->op == TK_STAR))) {
int sz = (int)rt->size;
/* Single-slot @retscr (#14): see tagged arm
* above for rationale. Fixed "@retscr" name
@@ -9762,7 +9824,71 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
* trailing bytes. */
cg_structlit_fill_bp(c, locals, rt,
n->lhs, scr);
} else {
} else if (n->lhs->kind == N_ARRLIT) {
/* #272: materialise the array literal into
* @retscr per element, mirroring the
* let-init N_ARRLIT scalar/float fill
* (cgen.c N_LET). Non-scalar elements
* (struct/array/str/slice) loud-stop: no
* return-by-value consumer exists (rule 7),
* and the let-init path already covers them
* for the addressable forms. */
Type *esub = rt->sub;
int esz = esub ? (int)esub->size : 1;
Type *esubu = type_chase_named(esub);
if ((esubu && (esubu->kind == TY_STRUCT
|| esubu->kind == TY_ARRAY
|| esubu->kind == TY_TUPLE))
|| type_isstr(esub)
|| type_isslice(esub))
fatal("#272: array-literal return "
"with non-scalar element "
"unsupported (rule 7, no "
"consumer)");
int isfl = type_isfloat(esub);
int fmov = type_isf32(esub) ? A_MOVSS
: A_MOVSD;
int op = A_MOVQ;
if (esz == 1) op = A_MOVB;
else if (esz == 2) op = A_MOVW;
else if (esz == 4) op = A_MOVL;
int idx = 0;
Node *last = NULL;
int repeat = 0;
for (Node *e = n->lhs->list; e;
e = e->next) {
if (e->kind == N_FIELD && e->str
&& strcmp(e->str, "...") == 0) {
repeat = 1;
break;
}
cgexpr(c, e, *locals);
if (isfl)
ins2(c, fmov, areg(D_X0),
amem(D_BP,
scr + idx * esz));
else
ins2(c, op, areg(D_AX),
amem(D_BP,
scr + idx * esz));
last = e;
idx++;
}
if (repeat && last)
while (idx < (int)rt->alen) {
if (isfl)
ins2(c, fmov,
areg(D_X0),
amem(D_BP,
scr + idx * esz));
else
ins2(c, op,
areg(D_AX),
amem(D_BP,
scr + idx * esz));
idx++;
}
} else if (n->lhs->kind == N_IDENT) {
/* N_IDENT: word-copy rhs slot into
* scratch. Whole 8B words via MOVQ;
* trailing partial word via MOVL/MOVB
@@ -9795,6 +9921,48 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
amem(D_BP, scr + k));
k += 1;
}
} else {
/* #272: N_DOT / N_INDEX / deref — land the
* source ADDRESS in SI via the #271 arg-side
* helper, then memcpy sz bytes into @retscr
* (the #265/#268 let-init copy shape). Loud-
* stop any source the helper can't address
* (rule 7); the gate above already excludes
* N_CALL (tail passthrough). */
if (!aggarg_srcaddr(c, n->lhs, D_SI,
*locals))
fatal("#272: aggregate return "
"from unsupported source "
"kind %d", n->lhs->kind);
int k = 0;
while (k + 8 <= sz) {
ins2(c, A_MOVQ, amem(D_SI, k),
areg(D_AX));
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, scr + k));
k += 8;
}
if (k + 4 <= sz) {
ins2(c, A_MOVL, amem(D_SI, k),
areg(D_AX));
ins2(c, A_MOVL, areg(D_AX),
amem(D_BP, scr + k));
k += 4;
}
if (k + 2 <= sz) {
ins2(c, A_MOVW, amem(D_SI, k),
areg(D_AX));
ins2(c, A_MOVW, areg(D_AX),
amem(D_BP, scr + k));
k += 2;
}
if (k + 1 <= sz) {
ins2(c, A_MOVB, amem(D_SI, k),
areg(D_AX));
ins2(c, A_MOVB, areg(D_AX),
amem(D_BP, scr + k));
k += 1;
}
}
/* #171a: float-bearing struct RETURN (the return
* twin of #165's param recv). A qualifying struct's
@@ -9840,6 +10008,21 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
break;
}
}
/* #272 close-by-construction: the addressable aggregate-return
* sources (IDENT/STRUCTLIT/ARRLIT/DOT/INDEX/deref) all route
* through the @retscr / *(@sretarg) arms above and break; an
* aggregate N_CALL passes through the cgexpr tail (the callee
* already left AX/DX/CX). Any OTHER aggregate rvalue reaching
* here would truncate to AX silently — loud-stop (rule 7) so a
* future unhandled shape is caught, not miscompiled. */
if (n->lhs && cg_ret_type) {
Type *rtc = type_chase_named(cg_ret_type);
if (rtc && (rtc->kind == TY_STRUCT || rtc->kind == TY_ARRAY)
&& n->lhs->kind != N_CALL)
fatal("#272: aggregate return reaches scalar default "
"(source kind %d) — unclosed shape",
n->lhs->kind);
}
if (n->lhs && node_isstr(n->lhs)) {
/* str IS []u8: AX=ptr, BX=len, CX=cap from cgexpr —
* no AX:DX shuffle, same as a slice (#1/Phase 3). */