cgen: materialize and store all eightbytes of an aggregate unwrap success (#12)
A struct/array success variant in an (S|e)! / r? unwrap dropped eightbytes on BOTH stages (byte-id blind). Two layers: (L1) the unwrap success shuffle (cgtrytaggedshift) matched no arm for a struct/array success and fell to a bare MOVQ DX,AX, materializing only w0 — widen the existing nested-TAGGED shift's gate to admit TY_STRUCT/TY_ARRAY (the in-cap union packs the payload as raw GP words past the tag, so that shift is exact); (L2) the aggregate store arms gated on rhs.kind==N_CALL and stored one word for an unwrap rhs — relax to also admit N_TRYUNW/N_TRYPROP at the three silent store shapes (arr[i]=, single-dot field, indexed-field), reusing the materialise scratch path (now #10-correct). Rule-7 LOUD-STOP for a float-bearing success variant (an SSE eightbyte cannot ride the GP {AX,DX,CX} shift, #165). The four already-loud unwrap consumers (let-receive #7, call-arg #271, assign-existing #49, resolver-field #24) stay loud; global/chained single-dot field (#16) and the sub-8-tail-through-unwrap union-maker frame clobber (#15) are separate follow-ups. Value-asserting pin, reddens under each stage's independent revert.
This commit is contained in:
77
test/lang/struct_unwrap_test.ww
Normal file
77
test/lang/struct_unwrap_test.ww
Normal file
@@ -0,0 +1,77 @@
|
||||
// struct_unwrap_test — #12: an aggregate (struct) success-variant unwrap
|
||||
// `(S|e)!` / `r?` dropped payload words (SILENT, both stages, byte-id-BLIND).
|
||||
// A struct success variant rides the in-cap {AX=w0,DX=w1,CX=w2} GP ABI — a
|
||||
// DIFFERENT shuffle from #6's str/slice {ptr,len,cap} header. Two layers:
|
||||
// L1 PRODUCER (cgtrytaggedshift / cgen.c N_TRYPROP+N_TRYUNW): a struct/array
|
||||
// success variant matched no arm and fell to the bare `MOVQ DX,AX`,
|
||||
// materialising only w0 — w1/w2 (in CX/R8) dropped.
|
||||
// L2 CONSUMER (the aggregate-STORE arms): gated rhs.kind==N_CALL and stored
|
||||
// one word for an N_TRYUNW/N_TRYPROP rhs — admitting the unwrap kinds
|
||||
// reuses the #10-correct scratch materialise.
|
||||
// Both layers are silent both-stage, so the byte-id gate is BLIND to the drop —
|
||||
// these VALUE asserts are the sole tooth. Reverting either stage's L2 relax (or
|
||||
// the shared L1 producer) reverts the dropped word to the poison sentinel 9 and
|
||||
// reddens. POISON-SEED every dest member to 9 so a dropped w1/w2 reads 9 != the
|
||||
// expected nonzero value (a fresh-frame 0 would also mismatch, but 9 is robust).
|
||||
//
|
||||
// Coverage = the three SILENT store shapes (arr-elem C2c, single-dot field
|
||||
// direct + via-ptr, indexed-field #11) for a clean 2-eightbyte s2 (w1 tooth)
|
||||
// plus a 3-eightbyte s24 (w2 tooth — exercises the producer's R8->CX shift).
|
||||
// The sub-8-tail-THROUGH-unwrap case (#10's tail) is NOT pinnable: an ARRAY
|
||||
// success variant is checker-rejected (`([7]i16|e)`, task #5/#60) and a sub-8-
|
||||
// tail STRUCT success variant trips a SEPARATE pre-existing bug — the union-
|
||||
// return struct-lit fill overruns saved BP when the variant ends at the frame
|
||||
// edge (g's MOVQ writes past the 14B struct into [BP]); filed for the lead.
|
||||
|
||||
package struct_unwrap_test;
|
||||
|
||||
type e = !i32;
|
||||
type s2 = struct { a: i64, b: i64 }; // 2 eightbytes: w0=a, w1=b
|
||||
type s24 = struct { a: i64, b: i64, c: i64 }; // 3 eightbytes: w0,w1,w2
|
||||
type box = struct { f: s2, g: i64 };
|
||||
|
||||
fn mk2() (s2 | e) = { return s2 { a = 111i64, b = 222i64 }; };
|
||||
fn mk24() (s24 | e) = { return s24 { a = 111i64, b = 222i64, c = 333i64 }; };
|
||||
|
||||
@test fn arr_elem() void = { // L2 site: C2c whole-element arr[i]=mk()!
|
||||
let arr: [2]s2;
|
||||
arr[1].a = 9i64; arr[1].b = 9i64; // poison: w1 sentinel 9 != 222
|
||||
arr[1] = mk2()!;
|
||||
assert(arr[1].a == 111i64);
|
||||
assert(arr[1].b == 222i64); // w1 — the dropped-word tooth
|
||||
};
|
||||
|
||||
@test fn arr_elem_w2() void = { // C2c, 3 eightbytes — w2 (R8->CX) tooth
|
||||
let arr: [2]s24;
|
||||
arr[1].a = 9i64; arr[1].b = 9i64; arr[1].c = 9i64;
|
||||
arr[1] = mk24()!;
|
||||
assert(arr[1].a == 111i64);
|
||||
assert(arr[1].b == 222i64);
|
||||
assert(arr[1].c == 333i64); // w2 tooth (>16B producer shift)
|
||||
};
|
||||
|
||||
@test fn field_direct() void = { // L2 site: single-dot direct b.f=mk()!
|
||||
let b: box = box { f = s2{a=9i64,b=9i64}, g = 7i64 };
|
||||
b.f = mk2()!;
|
||||
assert(b.f.a == 111i64);
|
||||
assert(b.f.b == 222i64); // w1 tooth
|
||||
assert(b.g == 7i64); // neighbour field intact
|
||||
};
|
||||
|
||||
@test fn field_via_ptr() void = { // L2 site: single-dot via *struct p.f=mk()!
|
||||
let b: box = box { f = s2{a=9i64,b=9i64}, g = 7i64 };
|
||||
let p: *box = &b;
|
||||
p.f = mk2()!;
|
||||
assert(b.f.a == 111i64);
|
||||
assert(b.f.b == 222i64); // w1 tooth
|
||||
assert(b.g == 7i64);
|
||||
};
|
||||
|
||||
@test fn field_of_indexed() void = { // L2 site: #11 indexed-field arr[i].f=mk()!
|
||||
let arr: [2]box;
|
||||
arr[1].g = 5i64; arr[1].f.a = 9i64; arr[1].f.b = 9i64;
|
||||
arr[1].f = mk2()!;
|
||||
assert(arr[1].f.a == 111i64);
|
||||
assert(arr[1].f.b == 222i64); // w1 tooth
|
||||
assert(arr[1].g == 5i64); // neighbour field intact
|
||||
};
|
||||
Reference in New Issue
Block a user