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.
55 lines
2.4 KiB
Plaintext
55 lines
2.4 KiB
Plaintext
// #15: a union-return struct-lit fill stored every
|
|
// scalar field with MOVQ except the explicit {1→MOVB, 4→MOVL} arms, so a
|
|
// 2-byte (i16/u16) field fell through to an 8-byte MOVQ. For the LAST field at
|
|
// the frame edge this over-store ran past the slot into saved [BP]: a
|
|
// `(s14|e)` success variant places the s14 payload at -16(BP) after the 8B
|
|
// tag, so the tail field g (off 12) lands at -4(BP) and `MOVQ AX,-4(BP)` writes
|
|
// bytes -4..+3 — clobbering the low 4 bytes of saved BP. POPQ BP then restores
|
|
// a corrupted BP and the CALLER runs on a garbage frame. SILENT both-stage and
|
|
// byte-id-BLIND (both stages emit the same wrong MOVQ), so these VALUE asserts
|
|
// are the sole tooth. Fix: route the scalar store through fldstoreop /
|
|
// fieldstoreop (adds the missing MOVW for fsz==2). Reverting either stage to
|
|
// the MOVQ-default reverts the clobber and reddens.
|
|
//
|
|
// Two teeth: (1) a sentinel i64 live ACROSS the maker call detects the BP
|
|
// clobber directly — on the buggy build the corrupted POPQ BP moves the
|
|
// caller frame so the sentinel read mismatches. (2) assert all s14 members.
|
|
// union_maker (the bug) + plain_maker control (same struct, NON-union maker:
|
|
// g lands at -12(BP), in-frame dead space, no live neighbour — passes even on
|
|
// master). The control isolates the union-return path as the buggy one.
|
|
|
|
package union_subtail_bp_test;
|
|
|
|
type e = !i32;
|
|
type s14 = struct { a: i16, b: i16, c: i16, d: i16, e: i16, f: i16, g: i16 };
|
|
|
|
fn mk14() (s14 | e) = {
|
|
return s14 { a = 1i16, b = 2i16, c = 3i16, d = 4i16, e = 5i16, f = 6i16, g = 7i16 };
|
|
};
|
|
fn mk14p() s14 = {
|
|
return s14 { a = 1i16, b = 2i16, c = 3i16, d = 4i16, e = 5i16, f = 6i16, g = 7i16 };
|
|
};
|
|
|
|
@test fn union_maker() void = {
|
|
let sentinel: i64 = 0x5151515151515151i64; // lives across the mk14 call
|
|
let arr: [2]s14;
|
|
arr[1] = mk14()!; // maker over-stores g past its slot into saved BP
|
|
assert(arr[1].a == 1i16);
|
|
assert(arr[1].b == 2i16);
|
|
assert(arr[1].c == 3i16);
|
|
assert(arr[1].d == 4i16);
|
|
assert(arr[1].e == 5i16);
|
|
assert(arr[1].f == 6i16);
|
|
assert(arr[1].g == 7i16); // the over-stored tail field
|
|
assert(sentinel == 0x5151515151515151i64); // BP-clobber tooth
|
|
};
|
|
|
|
@test fn plain_maker() void = { // CONTROL: same struct, NON-union maker
|
|
let sentinel: i64 = 0x5252525252525252i64;
|
|
let arr: [2]s14;
|
|
arr[1] = mk14p();
|
|
assert(arr[1].a == 1i16);
|
|
assert(arr[1].g == 7i16);
|
|
assert(sentinel == 0x5252525252525252i64);
|
|
};
|