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:
191
cmd/w6c/cgen.c
191
cmd/w6c/cgen.c
@@ -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). */
|
||||
|
||||
Reference in New Issue
Block a user