Files
ww/test/lang/aggregate_tail_test.ww
Hojun-Cho 83f5956df2 test: banner purge + WHY-only comment sweep (rule 8)
Every section banner dies (103 -> 0) across test/lang, the observer
suites, the C carriers, and the five comment-heavy corpus fixtures;
banner provenance (#N cites, carrier numbers, repair-cluster labels)
folded into headers or adjacent WHY comments. Narration deleted; row
provenance, ref cites, divergence pins, and layout contracts kept
(fwd-ref decl-order guards and bootstrap-gate corpus rationale
restored where the sweep over-cut). Comment-only proven: all 3742
wwbuild workdir .s byte-identical before/after; test-commit and
test-byteid (161 lang + 1399 data, 0 pinned-divergent) green.
2026-08-08 21:40:23 +09:00

83 lines
3.5 KiB
Plaintext

// In-cap aggregate-receive sub-8 tail materialise (#10).
// An in-cap (<=24B) aggregate-returning CALL received into an INDEXED dest is a
// two-step: (1) materialise the AX/DX/CX return regs into a frame scratch, (2)
// word-copy scratch -> dest. Step 1's sub-8 TAIL stored a single narrow MOV
// picked by (tail==4)?MOVL:(tail==2)?MOVW:MOVB, so for tail in {3,5,6,7} it fell
// to MOVB: one byte written while the copy reads the FULL tail, so the high tail
// bytes stayed uninitialised => dropped members. Two arms shared the gap — the
// C2c whole-element arr[i]=mk() (SILENT both stages, byte-id-BLIND: both emitted
// IDENTICAL wrong asm) and the #11 field-of-indexed arr[i].f=mk() (LOUD-STOPped).
// The fix stores the FULL register (MOVQ) into the ceil-8-padded scratch; the
// over-stored bytes die in the pad and the copy reads only the real size.
// A repro element must be align-<=2 UNPADDED: [7]i16 = 14B keeps a real tail-6;
// an i64-bearing struct pads to 16B (tail 0) and never trips it. PRIMITIVE-only
// asserts (deref_callarg lineage): a dropped word fails. The C2c value assert is
// the SOLE tooth there (byte-id blind); the #11 case re-arms a compile-error
// loud-stop on revert. Distinct nonzero per-element values so a stale read (the
// fully-dropped members read uninitialised, 0 on a fresh frame) mismatches.
package aggregate_tail_test;
type s = struct { f: [7]i16, g: i16 }; // f at off 0 (14B tail-6), g at off 14
fn mk7() [7]i16 = { return [10i16, 20i16, 30i16, 40i16, 50i16, 60i16, 70i16]; };
fn mk11() [11]u8 = { return [1u8,2u8,3u8,4u8,5u8,6u8,7u8,8u8,9u8,10u8,11u8]; };
fn mk13() [13]u8 = { return [1u8,2u8,3u8,4u8,5u8,6u8,7u8,8u8,9u8,10u8,11u8,12u8,13u8]; };
fn mk15() [15]u8 = { return [1u8,2u8,3u8,4u8,5u8,6u8,7u8,8u8,9u8,10u8,11u8,12u8,13u8,14u8,15u8]; };
@test fn c2c_whole_element() void = {
let arr: [3][7]i16;
arr[1] = mk7();
assert(arr[1][0] == 10i16);
assert(arr[1][1] == 20i16);
assert(arr[1][2] == 30i16);
assert(arr[1][3] == 40i16);
assert(arr[1][4] == 50i16); // eb1 byte 0-1
assert(arr[1][5] == 60i16); // eb1 byte 2-3 — dropped by the buggy MOVB tail
assert(arr[1][6] == 70i16); // eb1 byte 4-5 — dropped (fully-uninit tooth)
};
@test fn field_of_indexed() void = {
let arr: [3]s;
arr[1].g = 999i16; // neighbour set first
arr[1].f = mk7();
assert(arr[1].f[0] == 10i16);
assert(arr[1].f[4] == 50i16);
assert(arr[1].f[5] == 60i16);
assert(arr[1].f[6] == 70i16);
assert(arr[1].g == 999i16); // tail MOVQ writes the padded SCRATCH, not g
};
// The STRUCT-field-of-indexed variant ({t14,pad} struct field) is NOT pinned here:
// #10's materialise is byte-id clean for it, but the [N]box local frame size
// diverges cs!=ww (task #9, slotsize-vs-natural) — benign (both stages correct)
// yet byte-id-RED, so it stays out of the T2 corpus. Positive cstage coverage of
// that shape lives in test/wcc/data/idx_dot_aggret_subtail_run (converted from the
// retired loud-stop tripwire).
@test fn tail3() void = {
let a: [2][11]u8;
a[1] = mk11();
assert(a[1][0] == 1u8);
assert(a[1][7] == 8u8);
assert(a[1][8] == 9u8); // tail byte 0 (MOVB wrote only this)
assert(a[1][9] == 10u8); // tail byte 1 — dropped
assert(a[1][10] == 11u8); // tail byte 2 — dropped
};
@test fn tail5() void = {
let a: [2][13]u8;
a[1] = mk13();
assert(a[1][0] == 1u8);
assert(a[1][8] == 9u8);
assert(a[1][12] == 13u8); // tail byte 4 — dropped
};
@test fn tail7() void = {
let a: [2][15]u8;
a[1] = mk15();
assert(a[1][0] == 1u8);
assert(a[1][8] == 9u8);
assert(a[1][14] == 15u8); // tail byte 6 — dropped
};