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.
66 lines
3.1 KiB
Plaintext
66 lines
3.1 KiB
Plaintext
// #9: a local `[N]S` where S is a struct with
|
||
// a sub-8-tail field over-sized its stack FRAME (cs≠ww, gate-visible). The
|
||
// wwstage cgen frame reader (slotsize, cgenutil.ww TY_ARRAY arm) summed the
|
||
// element's slot-PADDED width (outer.slotsize 24) × N instead of the array's
|
||
// NATURAL size (outer.size 16) × N rounded to 8 — so `[2]outer` reserved $48
|
||
// where cstage reserves natural $32, and every later local stacked at a shifted
|
||
// BP offset. ROOT was the SAME dual-SSoT slotsize leak #75 fixed for the
|
||
// TY_STRUCT arm, never carried to its TY_ARRAY sibling; #9 applies the identical
|
||
// round8(ti.size) transform. ti.size == ti.slotsize for every array of prims /
|
||
// arrays / 8-multiple tagged elements, so the fix MOVES only this nested-sub-8-
|
||
// struct case (#48 [N]Alias and 1D/2D prim arrays are unchanged).
|
||
//
|
||
// The LOAD-BEARING tooth is the test-lang-byteid .s comparison: reverting the
|
||
// cgenutil.ww slotsize arm reddens this file (ww frame $48 vs cstage $32). The
|
||
// value asserts are a SECONDARY net — standalone ww is internally consistent at
|
||
// the inflated stride (it reserves AND addresses at the same over-sized frame),
|
||
// so a pure value run can pass while broken; they guard a future stride/offset
|
||
// regression, not the frame size. element_layout pins the natural per-element
|
||
// stride (16) and field offsets (a@0, p.x@1, p.y@2, z@8); cross_element pins
|
||
// that element 0 and element 1 don't alias after the fix shrinks the frame.
|
||
//
|
||
// Inline @test fns, not a row-table: the cases vary in the WRITE/READ place
|
||
// shape over a fixed struct layout, not in data over one operation, so a
|
||
// row-array `[](in,exp){}` can't express them (and that form is blocked by cgen
|
||
// #111). Mirrors dotbase_arr_test's #135 pins.
|
||
|
||
package arr_struct_subtail_frame_test;
|
||
|
||
type inner = struct { x: u8, y: u8 }; // natural 2, align 1
|
||
type outer = struct { a: u8, p: inner, z: i64 }; // natural 16, align 8 (sub-8 tail at p)
|
||
|
||
@test fn element_layout() void = {
|
||
let arr: [2]outer = [
|
||
outer { a = 1u8, p = inner { x = 2u8, y = 3u8 }, z = 100i64 },
|
||
outer { a = 4u8, p = inner { x = 5u8, y = 6u8 }, z = 200i64 }];
|
||
assert(arr[0].a: i32 == 1);
|
||
assert(arr[0].p.x: i32 == 2);
|
||
assert(arr[0].p.y: i32 == 3);
|
||
assert(arr[0].z == 100i64);
|
||
assert(arr[1].a: i32 == 4);
|
||
assert(arr[1].p.x: i32 == 5);
|
||
assert(arr[1].p.y: i32 == 6);
|
||
assert(arr[1].z == 200i64);
|
||
};
|
||
|
||
@test fn write_tail() void = {
|
||
let arr: [2]outer = [
|
||
outer { a = 1u8, p = inner { x = 2u8, y = 3u8 }, z = 100i64 },
|
||
outer { a = 4u8, p = inner { x = 5u8, y = 6u8 }, z = 200i64 }];
|
||
arr[1].z = 999i64;
|
||
arr[0].a = 7u8;
|
||
assert(arr[1].z == 999i64);
|
||
assert(arr[0].a: i32 == 7);
|
||
assert(arr[0].z == 100i64); // neighbour untouched
|
||
};
|
||
|
||
@test fn cross_element() void = { // element 0 and 1 are 16 apart, no alias
|
||
let arr: [2]outer = [
|
||
outer { a = 1u8, p = inner { x = 2u8, y = 3u8 }, z = 100i64 },
|
||
outer { a = 4u8, p = inner { x = 5u8, y = 6u8 }, z = 200i64 }];
|
||
arr[0].p.y = 77u8;
|
||
assert(arr[0].p.y: i32 == 77);
|
||
assert(arr[1].p.x: i32 == 5); // not clobbered by the elem-0 write
|
||
assert(arr[1].p.y: i32 == 6);
|
||
};
|