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:
@@ -9123,6 +9123,137 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
c.sretdestoff = 0;
|
||||
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
|
||||
// an indexed element `a[i] = (3,4)`. A literal has no
|
||||
// source ADDRESS, so the ident/dot/deref copy arm below
|
||||
|
||||
Reference in New Issue
Block a user