Files
ww/test/lang/idx_dot_src_aggregate_test.ww
Hojun-Cho 3719ff1c64 cgen: copy all eightbytes when a non-call aggregate assigns into a field of an indexed element (#11b)
The arr[i].f=src legacy assign block enumerated scalar field-type arms then fell to a 1-word scalar default, so a non-call aggregate source (ident/dot/index) cgexpr'd only its first word into AX and stored one eightbyte — silent on BOTH stages (byte-id blind). The non-indexed bases (local/deref/chained/global) reach the general assign resolver's canonical aggargsrcaddr+aggcopy; the indexed arm short-circuited before it. Route the indexed base through the block's own proven &arr[i] spine into the same aggargsrcaddr+aggcopy emitters (DRY — no third copy), dual-site symmetric. Unlike #11's in-cap arm, the source is a memory address so aggcopy is a pure memcpy: float bits and the sub-8 tail transport verbatim, no loud-stop needed. Did not fall through to the general resolver because its cgplaceaddr N_INDEX arm rejects a *[N]S (TY_PTR) base (latent resolver gap, filed separately).

Contained to the indexed base + non-call aggregate-field rhs; value-asserting pins redden under each stage's independent revert.
2026-06-27 19:40:04 +09:00

137 lines
5.5 KiB
Plaintext

// idx_dot_src_aggregate_test — #11b: a NON-call AGGREGATE source assigned into
// an AGGREGATE field of an INDEXED element `arr[i].f = src` (src an ident or a
// field `.g`, NOT a function call). Pre-fix the N_DOT(N_INDEX) assign arm had no
// aggregate-source case: it fell to the 1-word scalar default — cgexpr loaded
// only the source's FIRST word into AX and stored ONE word, dropping the rest.
// BOTH stages emitted byte-IDENTICAL wrong asm (gate-blind, the #263 both-wrong
// form) — the non-call twin of #11 (idx_dot_aggret_recv_test, the in-cap CALL
// source). Unlike #11's GP AX/DX/CX cursor, the source here is a MEMORY address,
// so the shared mem-to-mem aggcopy transports EVERY byte — a sub-8 tail (MOVL/
// MOVW/MOVB) and float bits copy verbatim, so the fix needs NO tail/float/over-
// cap loud-stop (those #11 stops were register-cursor artefacts). Each @test
// POISON-seeds the field + its neighbour with sentinel 9 (distinct from 0 AND
// every expected value) and asserts EVERY member: a dropped word reads 9 and
// fails. Covers IDENT and DOT sources, full=2/full=3 (CX word), the sub-8-tail
// MOVL row (anti-clobber: the 12B field's tail MUST stay MOVL — a widened MOVQ
// would smash the adjacent g at +12), foff!=0, a float-bearing field, and bases
// [N]S / *[N]S / []S with const + runtime index. The sub-8-tail rows use a
// GLOBAL-backed base: a LOCAL [N]<sub-8-tail-struct> array trips a SEPARATE
// PRE-EXISTING let-array slotsize cs!=ww FRAME divergence (see the _runonly
// twin; task #9), independent of #11b. T2 keeps the cs==ww net.
package idx_dot_src_aggregate_test;
type t16 = struct { a: i64, b: i64 };
type t24 = struct { a: i64, b: i64, c: i64 };
type t12 = struct { a: i32, b: i32, c: i32 };
type ft = struct { x: f64, y: i64 };
type s16 = struct { f: t16, g: i64 };
type s24 = struct { f: t24, g: i64 };
type sfnf = struct { g: i64, f: t16 };
type sf = struct { f: ft, g: i64 };
type s12 = struct { f: t12, g: i32 };
type w16 = struct { pad: i64, inner: t16 };
type w12 = struct { pad: i64, inner: t12 };
let g12p: [2]s12 = [s12{f=t12{a=9i32,b=9i32,c=9i32},g=9i32}, s12{f=t12{a=9i32,b=9i32,c=9i32},g=9i32}];
let g12s: [2]s12 = [s12{f=t12{a=9i32,b=9i32,c=9i32},g=9i32}, s12{f=t12{a=9i32,b=9i32,c=9i32},g=9i32}];
let g12d: [2]s12 = [s12{f=t12{a=9i32,b=9i32,c=9i32},g=9i32}, s12{f=t12{a=9i32,b=9i32,c=9i32},g=9i32}];
// full=2, [N]S value-array local, IDENT src, const index, foff=0
@test fn struct16_local_ident_const() void = {
let a: [2]s16 = [s16{f=t16{a=9i64,b=9i64},g=9i64}, s16{f=t16{a=9i64,b=9i64},g=9i64}];
let src: t16 = t16{a=40i64, b=20i64};
a[1].f = src;
assert(a[1].f.a == 40i64);
assert(a[1].f.b == 20i64);
assert(a[1].g == 9i64);
assert(a[0].f.a == 9i64);
};
// full=2, [N]S local, DOT src (`w.inner`), const index, foff=0
@test fn struct16_local_dot_const() void = {
let a: [2]s16 = [s16{f=t16{a=9i64,b=9i64},g=9i64}, s16{f=t16{a=9i64,b=9i64},g=9i64}];
let w: w16 = w16{pad=7i64, inner=t16{a=40i64, b=20i64}};
a[1].f = w.inner;
assert(a[1].f.a == 40i64);
assert(a[1].f.b == 20i64);
assert(a[1].g == 9i64);
};
// full=3 (CX word), [N]S local, IDENT src, runtime index, foff=0
@test fn struct24_local_ident_runtime() void = {
let a: [2]s24 = [s24{f=t24{a=9i64,b=9i64,c=9i64},g=9i64}, s24{f=t24{a=9i64,b=9i64,c=9i64},g=9i64}];
let src: t24 = t24{a=100i64, b=20i64, c=3i64};
a[one()].f = src;
assert(a[1].f.a == 100i64);
assert(a[1].f.b == 20i64);
assert(a[1].f.c == 3i64);
assert(a[1].g == 9i64);
};
// foff != 0 (f at offset 8), [N]S local, IDENT src, const index
@test fn fnotfirst_local_ident() void = {
let a: [2]sfnf = [sfnf{g=9i64,f=t16{a=9i64,b=9i64}}, sfnf{g=9i64,f=t16{a=9i64,b=9i64}}];
let src: t16 = t16{a=40i64, b=20i64};
a[1].f = src;
assert(a[1].f.a == 40i64);
assert(a[1].f.b == 20i64);
assert(a[1].g == 9i64);
};
// float-bearing field (mem-to-mem copies float bits verbatim — no loud-stop),
// [N]S local, IDENT src, const index
@test fn float_local_ident() void = {
let a: [2]sf = [sf{f=ft{x=9.0f64,y=9i64},g=9i64}, sf{f=ft{x=9.0f64,y=9i64},g=9i64}];
let src: ft = ft{x=1.5f64, y=7i64};
a[1].f = src;
assert(a[1].f.x == 1.5f64);
assert(a[1].f.y == 7i64);
assert(a[1].g == 9i64);
};
// sub-8-tail (12B, MOVL tail), IDENT src, *[N]S -> global, const index
@test fn subtail_ptr_ident_const() void = {
let p: *[2]s12 = &g12p;
let src: t12 = t12{a=10i32, b=20i32, c=30i32};
p[1].f = src;
assert(g12p[1].f.a == 10i32);
assert(g12p[1].f.b == 20i32);
assert(g12p[1].f.c == 30i32);
assert(g12p[1].g == 9i32);
};
// sub-8-tail (12B, MOVL tail), IDENT src, []S -> global, runtime index
@test fn subtail_slice_ident_runtime() void = {
let sl: []s12 = g12s[0:2];
let src: t12 = t12{a=10i32, b=20i32, c=30i32};
sl[one()].f = src;
assert(g12s[1].f.a == 10i32);
assert(g12s[1].f.b == 20i32);
assert(g12s[1].f.c == 30i32);
assert(g12s[1].g == 9i32);
};
// INDEX src (`srcarr[0]`), [N]S local, const index, foff=0
@test fn struct16_local_index_src() void = {
let a: [2]s16 = [s16{f=t16{a=9i64,b=9i64},g=9i64}, s16{f=t16{a=9i64,b=9i64},g=9i64}];
let srcarr: [2]t16 = [t16{a=40i64, b=20i64}, t16{a=99i64, b=99i64}];
a[1].f = srcarr[0];
assert(a[1].f.a == 40i64);
assert(a[1].f.b == 20i64);
assert(a[1].g == 9i64);
};
// sub-8-tail (12B, MOVL tail), DOT src (`w.inner`), *[N]S -> global, const index
@test fn subtail_ptr_dot_const() void = {
let p: *[2]s12 = &g12d;
let w: w12 = w12{pad=7i64, inner=t12{a=10i32, b=20i32, c=30i32}};
p[1].f = w.inner;
assert(g12d[1].f.a == 10i32);
assert(g12d[1].f.b == 20i32);
assert(g12d[1].f.c == 30i32);
assert(g12d[1].g == 9i32);
};
fn one() i64 = { return 1i64; };