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:
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