cgen: store all eightbytes when an in-cap aggregate call returns into an array element (#31-G)
`arr[i] = mk()` where mk returns an in-cap (<=24B) struct/tuple/array left the result in the #4 cgreturn registers (AX/DX/CX), but the N_ASSIGN-into-N_INDEX path had no arm for an N_CALL rhs, so it fell to the scalar store tail: only member 0 was written and the index scale clobbered CX. Both stages emitted byte-identical wrong code (the documented-but-silent #31-G gap), so the byte-id gate was blind to it. Add an in-cap N_CALL-rhs arm: materialise the return into a frame scratch first (keeping the CALL at the frame's natural 16B alignment), resolve &arr[i], then word-copy the full eightbyte count + sub-8 tail -- mirroring the #4 receive shape and the #270-1b copy. The eightbyte count derives from the element size in the type table. Over-cap returns (#234), non-call rhs (#270-1b) and tuple literals (#121) are unaffected; the sibling field/deref shapes stay loud (#24). Surfaced by the codegen miscompile hunt (finding C2c). Pinned by test/lang/idx_aggret_recv_test.ww (10 value-asserting rows: 2/3-eightbyte structs+tuples, array elem, sub-8 tail, const/runtime index, all four base shapes; reddens on revert).
This commit is contained in:
101
cmd/w6c/cgen.c
101
cmd/w6c/cgen.c
@@ -6731,6 +6731,107 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
cg_sret_dest_off = 0;
|
cg_sret_dest_off = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
/* C2c / #31-G: an IN-CAP aggregate-returning CALL into an
|
||||||
|
* indexed element `a[i] = mk()`. The #234 arm above only
|
||||||
|
* fires for an OVER-cap (sret/memory) return; the #270-1b
|
||||||
|
* arm below excludes N_CALL (it copies from a source
|
||||||
|
* ADDRESS, which a call result has none). An in-cap (≤24B)
|
||||||
|
* struct/array/tuple return leaves AX/DX/CX per the #4
|
||||||
|
* cgreturn ABI but fell through to the 1-word scalar store
|
||||||
|
* (AX only) — dropping DX/CX (a silent field-drop, the
|
||||||
|
* documented-but-unfixed #31-G). Materialise the return into
|
||||||
|
* a frame scratch (the AX/DX/CX receive shape at the N_LET
|
||||||
|
* call-rhs site, cgen.c:3434), THEN compute &a[i] and word-
|
||||||
|
* copy scratch → dest. Scratch-first (not a dest spill across
|
||||||
|
* the call) keeps the call at the frame's natural alignment.
|
||||||
|
* In-cap only (cg_sret_retsize==0). */
|
||||||
|
if ((is_arr || is_sl || is_ptr) && n->op == TK_ASSIGN
|
||||||
|
&& esubu && (esubu->kind == TY_STRUCT
|
||||||
|
|| esubu->kind == TY_ARRAY
|
||||||
|
|| esubu->kind == TY_TUPLE)
|
||||||
|
&& esz > 8
|
||||||
|
&& n->rhs->kind == N_CALL
|
||||||
|
&& cg_sret_retsize(esub) == 0) {
|
||||||
|
int scr = cg_tagscr_slot(c, &locals, esz);
|
||||||
|
cgexpr(c, n->rhs, locals); /* call → AX/DX/CX */
|
||||||
|
/* AX/DX/CX → scratch (mirror cgen.c:3434 receive). */
|
||||||
|
int regs[3] = { D_AX, D_DX, D_CX };
|
||||||
|
int full = esz / 8;
|
||||||
|
int tail = esz % 8;
|
||||||
|
for (int i = 0; i < full; i++)
|
||||||
|
ins2(c, A_MOVQ, areg(regs[i]),
|
||||||
|
amem(D_BP, scr + i * 8));
|
||||||
|
if (tail > 0) {
|
||||||
|
int op = (tail == 4) ? A_MOVL
|
||||||
|
: (tail == 2) ? A_MOVW : A_MOVB;
|
||||||
|
ins2(c, op, areg(regs[full]),
|
||||||
|
amem(D_BP, scr + full * 8));
|
||||||
|
}
|
||||||
|
/* dest &a[i] → BX (mirror #121 / #270-1b resolve) */
|
||||||
|
cgexpr(c, n->lhs->rhs, locals); /* idx → AX */
|
||||||
|
if (esz > 1) {
|
||||||
|
ins2(c, A_MOVQ, aimm(esz), areg(D_CX));
|
||||||
|
ins2(c, A_IMULQ, areg(D_CX), areg(D_AX));
|
||||||
|
}
|
||||||
|
ins1(c, A_PUSHQ, areg(D_AX));
|
||||||
|
if (base->kind == N_IDENT) {
|
||||||
|
int off = localfind(locals, base->str);
|
||||||
|
int isglobal = (off == 0)
|
||||||
|
&& let_islet(base->str);
|
||||||
|
if (isglobal && is_arr)
|
||||||
|
ins2(c, A_LEAQ,
|
||||||
|
masym(c, base->str),
|
||||||
|
areg(D_BX));
|
||||||
|
else if (isglobal)
|
||||||
|
ins2(c, A_MOVQ,
|
||||||
|
masym(c, base->str),
|
||||||
|
areg(D_BX));
|
||||||
|
else if (is_arr)
|
||||||
|
ins2(c, A_LEAQ,
|
||||||
|
amem(D_BP, off), areg(D_BX));
|
||||||
|
else
|
||||||
|
ins2(c, A_MOVQ,
|
||||||
|
amem(D_BP, off), areg(D_BX));
|
||||||
|
} else if (cg_dotbase_addr(c, base, D_BX, locals)) {
|
||||||
|
/* N_DOT array-field base resolved inline. */
|
||||||
|
} else {
|
||||||
|
cgexpr(c, base, locals);
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX), areg(D_BX));
|
||||||
|
}
|
||||||
|
ins1(c, A_POPQ, areg(D_AX));
|
||||||
|
ins2(c, A_ADDQ, areg(D_AX), areg(D_BX));
|
||||||
|
/* word-copy scratch → dest (tail-aware, mirror
|
||||||
|
* #270-1b copy). */
|
||||||
|
int k = 0;
|
||||||
|
for (; k + 8 <= esz; k += 8) {
|
||||||
|
ins2(c, A_MOVQ, amem(D_BP, scr + k),
|
||||||
|
areg(D_AX));
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX),
|
||||||
|
amem(D_BX, k));
|
||||||
|
}
|
||||||
|
if (k + 4 <= esz) {
|
||||||
|
ins2(c, A_MOVL, amem(D_BP, scr + k),
|
||||||
|
areg(D_AX));
|
||||||
|
ins2(c, A_MOVL, areg(D_AX),
|
||||||
|
amem(D_BX, k));
|
||||||
|
k += 4;
|
||||||
|
}
|
||||||
|
if (k + 2 <= esz) {
|
||||||
|
ins2(c, A_MOVW, amem(D_BP, scr + k),
|
||||||
|
areg(D_AX));
|
||||||
|
ins2(c, A_MOVW, areg(D_AX),
|
||||||
|
amem(D_BX, k));
|
||||||
|
k += 2;
|
||||||
|
}
|
||||||
|
if (k + 1 <= esz) {
|
||||||
|
ins2(c, A_MOVB, amem(D_BP, scr + k),
|
||||||
|
areg(D_AX));
|
||||||
|
ins2(c, A_MOVB, areg(D_AX),
|
||||||
|
amem(D_BX, k));
|
||||||
|
k += 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
/* #121 (write-face of leg-b): a tuple-LITERAL rhs into an
|
/* #121 (write-face of leg-b): a tuple-LITERAL rhs into an
|
||||||
* indexed element `a[i] = (3,4)`. A literal has no source
|
* indexed element `a[i] = (3,4)`. A literal has no source
|
||||||
* ADDRESS, so the ident/dot/deref copy arm below can't reach
|
* ADDRESS, so the ident/dot/deref copy arm below can't reach
|
||||||
|
|||||||
@@ -9123,6 +9123,137 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
|||||||
c.sretdestoff = 0;
|
c.sretdestoff = 0;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
// C2c / #31-G: an IN-CAP aggregate-returning CALL into
|
||||||
|
// an indexed element `a[i] = mk()`. The #234 arm above
|
||||||
|
// only fires for an OVER-cap (sret) return; the #270-1b
|
||||||
|
// arm below copies from a source ADDRESS (which a call
|
||||||
|
// result has none). An in-cap (<=24B) struct/array/tuple
|
||||||
|
// return leaves AX/DX/CX per the #4 cgreturn ABI but fell
|
||||||
|
// to the 1-word scalar store (AX only) — dropping DX/CX
|
||||||
|
// (the documented-but-unfixed #31-G). Materialise the
|
||||||
|
// return into a frame scratch (the AX/DX/CX receive shape,
|
||||||
|
// cstage cgen.c:3434), THEN compute &a[i] and word-copy
|
||||||
|
// scratch -> dest. Scratch-first (not a dest spill across
|
||||||
|
// the call) keeps the call at the frame's natural
|
||||||
|
// alignment. esz>8 non-str/non-slice IS struct/array/tuple
|
||||||
|
// here (tagged returned above). In-cap only
|
||||||
|
// (callsretsize==0). Mirror of cstage cgen.c C2c arm.
|
||||||
|
if (n.rhs.kind == syntax.nkind.N_CALL && esz > 8
|
||||||
|
&& !isstrtype(c, elemtn) && !isslicetype(c, elemtn)
|
||||||
|
&& callsretsize(c, n.rhs) == 0) {
|
||||||
|
let scrc2: i32 = tagscradd(c, esz);
|
||||||
|
cgexpr(c, n.rhs); // call -> AX/DX/CX
|
||||||
|
// AX/DX/CX -> scratch (mirror cstage cgen.c:3434).
|
||||||
|
let full: i32 = esz / 8;
|
||||||
|
let tail: i32 = esz % 8;
|
||||||
|
let wi: i32 = 0;
|
||||||
|
for (wi < full) {
|
||||||
|
let rn: str = "AX";
|
||||||
|
if (wi == 1) { rn = "DX"; }
|
||||||
|
else { if (wi == 2) { rn = "CX"; }; };
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitline(rn);
|
||||||
|
emitline(", ");
|
||||||
|
emitoff((scrc2 + wi * 8): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
wi += 1;
|
||||||
|
};
|
||||||
|
if (tail > 0) {
|
||||||
|
let top: str = "MOVB";
|
||||||
|
if (tail == 4) { top = "MOVL"; }
|
||||||
|
else { if (tail == 2) { top = "MOVW"; }; };
|
||||||
|
let treg: str = "AX";
|
||||||
|
if (full == 1) { treg = "DX"; }
|
||||||
|
else { if (full == 2) { treg = "CX"; }; };
|
||||||
|
emitline("\t");
|
||||||
|
emitline(top);
|
||||||
|
emitline("\t");
|
||||||
|
emitline(treg);
|
||||||
|
emitline(", ");
|
||||||
|
emitoff((scrc2 + full * 8): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
};
|
||||||
|
// dest &a[i] -> BX (#121 / #270-1b base resolve)
|
||||||
|
cgexpr(c, idx);
|
||||||
|
if (esz > 1) {
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(esz: i64);
|
||||||
|
emitline(", CX\n");
|
||||||
|
emitline("\tIMULQ\tCX, AX\n");
|
||||||
|
};
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
if (isglobalarr) {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, globalname);
|
||||||
|
emitline("(SB), BX\n");
|
||||||
|
} else { if (isglobalptr) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitsymname(c, globalname);
|
||||||
|
emitline("(SB), BX\n");
|
||||||
|
} else { if (baselocal != nil) {
|
||||||
|
let tn2: *syntax.node = baselocal.tnode;
|
||||||
|
let isarr2: bool = false;
|
||||||
|
if (tn2 != nil) { if (tn2.kind == syntax.nkind.N_TARRAY) { isarr2 = true; }; };
|
||||||
|
if (basealias) {
|
||||||
|
let bu60: *syntax.tinfo = tichase(base.type_: *syntax.tinfo);
|
||||||
|
if (bu60 != nil) { isarr2 = bu60.kind == syntax.tykind.TY_ARRAY; };
|
||||||
|
};
|
||||||
|
if (isarr2) {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitoff(baselocal.off: i64);
|
||||||
|
emitline("(BP), BX\n");
|
||||||
|
} else {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(baselocal.off: i64);
|
||||||
|
emitline("(BP), BX\n");
|
||||||
|
};
|
||||||
|
} else { if (dotbaseaddr(c, base, "BX")) {
|
||||||
|
} else {
|
||||||
|
cgexpr(c, base);
|
||||||
|
emitline("\tMOVQ\tAX, BX\n");
|
||||||
|
};};};};
|
||||||
|
emitline("\tPOPQ\tAX\n");
|
||||||
|
emitline("\tADDQ\tAX, BX\n");
|
||||||
|
// word-copy scratch -> dest (tail-aware, #270-1b copy)
|
||||||
|
let kc: i32 = 0;
|
||||||
|
for (kc + 8 <= esz) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((scrc2 + kc): i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tMOVQ\tAX, ");
|
||||||
|
emitoff(kc: i64);
|
||||||
|
emitline("(BX)\n");
|
||||||
|
kc += 8;
|
||||||
|
};
|
||||||
|
if (kc + 4 <= esz) {
|
||||||
|
emitline("\tMOVL\t");
|
||||||
|
emitoff((scrc2 + kc): i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tMOVL\tAX, ");
|
||||||
|
emitoff(kc: i64);
|
||||||
|
emitline("(BX)\n");
|
||||||
|
kc += 4;
|
||||||
|
};
|
||||||
|
if (kc + 2 <= esz) {
|
||||||
|
emitline("\tMOVW\t");
|
||||||
|
emitoff((scrc2 + kc): i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tMOVW\tAX, ");
|
||||||
|
emitoff(kc: i64);
|
||||||
|
emitline("(BX)\n");
|
||||||
|
kc += 2;
|
||||||
|
};
|
||||||
|
if (kc + 1 <= esz) {
|
||||||
|
emitline("\tMOVB\t");
|
||||||
|
emitoff((scrc2 + kc): i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tMOVB\tAX, ");
|
||||||
|
emitoff(kc: i64);
|
||||||
|
emitline("(BX)\n");
|
||||||
|
kc += 1;
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
};
|
||||||
// #121 (write-face of leg-b): a tuple-LITERAL rhs into
|
// #121 (write-face of leg-b): a tuple-LITERAL rhs into
|
||||||
// an indexed element `a[i] = (3,4)`. A literal has no
|
// an indexed element `a[i] = (3,4)`. A literal has no
|
||||||
// source ADDRESS, so the ident/dot/deref copy arm below
|
// source ADDRESS, so the ident/dot/deref copy arm below
|
||||||
|
|||||||
112
test/lang/idx_aggret_recv_test.ww
Normal file
112
test/lang/idx_aggret_recv_test.ww
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
// idx_aggret_recv_test — in-cap aggregate-returning CALL received into an
|
||||||
|
// INDEXED element `a[i] = mk()` (C2c / #31-G). Pre-fix the N_ASSIGN-into-
|
||||||
|
// N_INDEX path had no arm for an in-cap (<=24B, AX/DX/CX-return) struct /
|
||||||
|
// array / tuple call rhs: the #270-1b element-store arm gates its source to
|
||||||
|
// N_IDENT / N_DOT / STAR (a call result has no source address) and the #234
|
||||||
|
// arm only fires for an OVER-cap (sret) return, so the call fell to the
|
||||||
|
// 1-word scalar store — only AX (member 0) was written, DX/CX dropped, AND
|
||||||
|
// the index-scale clobbered CX. Both stages emitted byte-IDENTICAL wrong asm
|
||||||
|
// (gate-blind, the #263 both-wrong form). Each @test asserts EVERY member (a
|
||||||
|
// dropped word fails); covers 2- and 3-eightbyte structs/tuples, an array-of-
|
||||||
|
// array element, const + runtime index, and local + GLOBAL + N_DOT-field +
|
||||||
|
// slice base. T2 keeps the cs==ww net.
|
||||||
|
|
||||||
|
package idx_aggret_recv_test;
|
||||||
|
|
||||||
|
type t2 = struct { a: i64, b: i64 };
|
||||||
|
type t3 = struct { a: i64, b: i64, c: i64 };
|
||||||
|
type holder = struct { arr: [3]t3 };
|
||||||
|
|
||||||
|
let g3: [2]t3 = [t3{a=0i64,b=0i64,c=0i64}, t3{a=0i64,b=0i64,c=0i64}];
|
||||||
|
|
||||||
|
fn mk2(x: i64) t2 = { return t2{a=x, b=x+10i64}; };
|
||||||
|
fn mk3(x: i64) t3 = { return t3{a=x, b=x+1i64, c=x+2i64}; };
|
||||||
|
fn mktup2() (i64, i64) = { return (3i64, 4i64); };
|
||||||
|
fn mktup3() (i64, i64, i64) = { return (5i64, 6i64, 7i64); };
|
||||||
|
fn mkarr() [3]i64 = { return [8i64, 9i64, 10i64]; };
|
||||||
|
fn mkarr32() [3]i32 = { return [11i32, 22i32, 33i32]; };
|
||||||
|
fn idx1() i64 = { return 1i64; };
|
||||||
|
|
||||||
|
@test fn struct_2eb() void = {
|
||||||
|
let a: [2]t2;
|
||||||
|
a[1] = mk2(5i64);
|
||||||
|
assert(a[1].a == 5i64);
|
||||||
|
assert(a[1].b == 15i64);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn struct_3eb() void = {
|
||||||
|
let a: [2]t3;
|
||||||
|
a[1] = mk3(5i64);
|
||||||
|
assert(a[1].a == 5i64);
|
||||||
|
assert(a[1].b == 6i64);
|
||||||
|
assert(a[1].c == 7i64);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn runtime_index() void = {
|
||||||
|
let a: [2]t3;
|
||||||
|
a[idx1()] = mk3(7i64);
|
||||||
|
assert(a[1].a == 7i64);
|
||||||
|
assert(a[1].b == 8i64);
|
||||||
|
assert(a[1].c == 9i64);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn global_dest() void = {
|
||||||
|
g3[1] = mk3(9i64);
|
||||||
|
assert(g3[1].a == 9i64);
|
||||||
|
assert(g3[1].b == 10i64);
|
||||||
|
assert(g3[1].c == 11i64);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn dot_base() void = {
|
||||||
|
let h: holder;
|
||||||
|
h.arr[2] = mk3(3i64);
|
||||||
|
assert(h.arr[2].a == 3i64);
|
||||||
|
assert(h.arr[2].b == 4i64);
|
||||||
|
assert(h.arr[2].c == 5i64);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn slice_base() void = {
|
||||||
|
let buf: [2]t3;
|
||||||
|
let sl: []t3;
|
||||||
|
sl.ptr = buf.ptr: *t3;
|
||||||
|
sl.len = 2;
|
||||||
|
sl.cap = 2;
|
||||||
|
sl[1] = mk3(4i64);
|
||||||
|
assert(buf[1].a == 4i64);
|
||||||
|
assert(buf[1].b == 5i64);
|
||||||
|
assert(buf[1].c == 6i64);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn tuple_2eb() void = {
|
||||||
|
let a: [2](i64, i64);
|
||||||
|
a[1] = mktup2();
|
||||||
|
assert(a[1].0 == 3i64);
|
||||||
|
assert(a[1].1 == 4i64);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn tuple_3eb() void = {
|
||||||
|
let a: [2](i64, i64, i64);
|
||||||
|
a[1] = mktup3();
|
||||||
|
assert(a[1].0 == 5i64);
|
||||||
|
assert(a[1].1 == 6i64);
|
||||||
|
assert(a[1].2 == 7i64);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn array_elem() void = {
|
||||||
|
let a: [2][3]i64;
|
||||||
|
a[1] = mkarr();
|
||||||
|
assert(a[1][0] == 8i64);
|
||||||
|
assert(a[1][1] == 9i64);
|
||||||
|
assert(a[1][2] == 10i64);
|
||||||
|
};
|
||||||
|
|
||||||
|
// [3]i32 element = 12B: esz%8 != 0, so the receive-store and the word-copy
|
||||||
|
// both hit the sub-8 TAIL arm (MOVL of the DX low-half), which the
|
||||||
|
// 8-multiple rows above never exercise.
|
||||||
|
@test fn array_elem_tail() void = {
|
||||||
|
let a: [2][3]i32;
|
||||||
|
a[1] = mkarr32();
|
||||||
|
assert(a[1][0] == 11i32);
|
||||||
|
assert(a[1][1] == 22i32);
|
||||||
|
assert(a[1][2] == 33i32);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user