cgen: store all eightbytes when an in-cap aggregate call returns into a field of an indexed element (#11)
The arr[i].f=mk() assign arm had no aggregate-field sub-arm, so a by-value aggregate field receive fell to the scalar default (one MOVQ, dropping DX/CX) — silent on BOTH stages (byte-id blind). Add a dual-site symmetric in-cap N_CALL arm mirroring C2c (c83a340): scratch-first materialise AX/DX/CX, then word-copy to (fi.foff+k*8) within &arr[i], sizing from the natural field size fi.fsz (not slotsize). Rule-7 LOUD-STOP for the three cases the in-cap GP path cannot transport: over-cap sret (#11c/#234), a float-bearing field whose eightbyte classifies SSE (#11/#165), and a 3/5/6/7-byte sub-8 tail the single narrow tail MOV cannot express (the general cascade tail is the shared C2c/#11 follow-up, task #10). Value-asserting pins (poison-seeded, redden under each stage's independent revert) plus cfail pins for the three loud-stops.
Contained to the indexed base + in-cap call rhs; arr[i].f=src (#11b) and over-cap (#11c) are separate.
This commit is contained in:
43
test/lang/idx_dot_aggret_recv_runonly_test.ww
Normal file
43
test/lang/idx_dot_aggret_recv_runonly_test.ww
Normal file
@@ -0,0 +1,43 @@
|
||||
// idx_dot_aggret_recv_runonly_test — #11 sub-8-tail anti-clobber through a
|
||||
// LOCAL [N]<struct-with-sub-8-tail-field> array. The fix's MOVL (not MOVQ) tail
|
||||
// + foff-on-every-eightbyte are VALUE-verified here: the dropped eb1 would read
|
||||
// the poison 9, and a tail WIDENED to MOVQ would write bytes 8..15 and smash the
|
||||
// adjacent g at +12 — both caught by asserting f.c AND g. Exit-correct under
|
||||
// BOTH stages.
|
||||
//
|
||||
// _runonly (excluded from the test-lang-byteid T2 gate) because a local
|
||||
// [N]<sub-8-tail-struct> trips a SEPARATE PRE-EXISTING let-array slotsize cs!=ww
|
||||
// FRAME divergence — ww sizes the array element by slotsize (S slot-padded to
|
||||
// 24 -> [2]S=48), cstage by natural size (S=16 -> 32) — that PREDATES and is
|
||||
// INDEPENDENT of #11: `let arr:[2]S; return arr[1].g` already diverges $32 vs
|
||||
// $48 with no call involved. The #11 receive INSTRUCTIONS are byte-identical
|
||||
// (both stages emit the MOVL tail); only the container's frame SIZE differs.
|
||||
// The byte-id twin (idx_dot_aggret_recv_test) covers the same sub-8-tail shape
|
||||
// via global-backed bases, where the slotsize bug does not bite.
|
||||
|
||||
package idx_dot_aggret_recv_runonly_test;
|
||||
|
||||
type t12 = struct { a: i32, b: i32, c: i32 };
|
||||
type s12 = struct { f: t12, g: i32 };
|
||||
|
||||
fn mk12() t12 = { return t12{a=10i32, b=20i32, c=30i32}; };
|
||||
fn one() i64 = { return 1i64; };
|
||||
|
||||
@test fn subtail_local_const() void = {
|
||||
let a: [2]s12 = [s12{f=t12{a=9i32,b=9i32,c=9i32},g=9i32}, s12{f=t12{a=9i32,b=9i32,c=9i32},g=9i32}];
|
||||
a[1].f = mk12();
|
||||
assert(a[1].f.a == 10i32);
|
||||
assert(a[1].f.b == 20i32);
|
||||
assert(a[1].f.c == 30i32);
|
||||
assert(a[1].g == 9i32);
|
||||
assert(a[0].f.a == 9i32);
|
||||
};
|
||||
|
||||
@test fn subtail_local_runtime() void = {
|
||||
let a: [2]s12 = [s12{f=t12{a=9i32,b=9i32,c=9i32},g=9i32}, s12{f=t12{a=9i32,b=9i32,c=9i32},g=9i32}];
|
||||
a[one()].f = mk12();
|
||||
assert(a[1].f.a == 10i32);
|
||||
assert(a[1].f.b == 20i32);
|
||||
assert(a[1].f.c == 30i32);
|
||||
assert(a[1].g == 9i32);
|
||||
};
|
||||
83
test/lang/idx_dot_aggret_recv_test.ww
Normal file
83
test/lang/idx_dot_aggret_recv_test.ww
Normal file
@@ -0,0 +1,83 @@
|
||||
// idx_dot_aggret_recv_test — #11: an in-cap aggregate-returning CALL received
|
||||
// into an AGGREGATE field of an INDEXED element `arr[i].f = mk()`. Pre-fix the
|
||||
// N_DOT(N_INDEX) assign arm had no case for a struct/array/tuple field filled
|
||||
// from an in-cap (<=24B, AX/DX/CX-return) call rhs: it fell to the 1-word
|
||||
// scalar default — only AX (eb0) stored, DX/CX dropped. BOTH stages emitted
|
||||
// byte-IDENTICAL wrong asm (gate-blind, the #263 both-wrong form). The field-
|
||||
// of-indexed twin of C2c (idx_aggret_recv_test) — a DIFFERENT cgassign arm.
|
||||
// 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
|
||||
// then reads 9 and fails. Covers 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, 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), independent of #11. T2 keeps
|
||||
// the cs==ww net.
|
||||
|
||||
package idx_dot_aggret_recv_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 s16 = struct { f: t16, g: i64 };
|
||||
type s24 = struct { f: t24, g: i64 };
|
||||
type sfnf = struct { g: i64, f: t16 };
|
||||
type s12 = struct { f: t12, g: i32 };
|
||||
|
||||
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}];
|
||||
|
||||
fn mk16() t16 = { return t16{a=40i64, b=20i64}; };
|
||||
fn mk24() t24 = { return t24{a=100i64, b=20i64, c=3i64}; };
|
||||
fn mk12() t12 = { return t12{a=10i32, b=20i32, c=30i32}; };
|
||||
fn one() i64 = { return 1i64; };
|
||||
|
||||
// full=2, [N]S value-array local, const index, foff=0
|
||||
@test fn struct16_local_const() void = {
|
||||
let a: [2]s16 = [s16{f=t16{a=9i64,b=9i64},g=9i64}, s16{f=t16{a=9i64,b=9i64},g=9i64}];
|
||||
a[1].f = mk16();
|
||||
assert(a[1].f.a == 40i64);
|
||||
assert(a[1].f.b == 20i64);
|
||||
assert(a[1].g == 9i64);
|
||||
assert(a[0].f.a == 9i64);
|
||||
};
|
||||
|
||||
// full=3 (CX word), [N]S value-array local, runtime index, foff=0
|
||||
@test fn struct24_local_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}];
|
||||
a[one()].f = mk24();
|
||||
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, const index
|
||||
@test fn fnotfirst_local() void = {
|
||||
let a: [2]sfnf = [sfnf{g=9i64,f=t16{a=9i64,b=9i64}}, sfnf{g=9i64,f=t16{a=9i64,b=9i64}}];
|
||||
a[1].f = mk16();
|
||||
assert(a[1].f.a == 40i64);
|
||||
assert(a[1].f.b == 20i64);
|
||||
assert(a[1].g == 9i64);
|
||||
};
|
||||
|
||||
// sub-8-tail (12B, MOVL tail), *[N]S -> global, const index
|
||||
@test fn subtail_ptr_const() void = {
|
||||
let p: *[2]s12 = &g12p;
|
||||
p[1].f = mk12();
|
||||
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), []S -> global, runtime index
|
||||
@test fn subtail_slice_runtime() void = {
|
||||
let sl: []s12 = g12s[0:2];
|
||||
sl[one()].f = mk12();
|
||||
assert(g12s[1].f.a == 10i32);
|
||||
assert(g12s[1].f.b == 20i32);
|
||||
assert(g12s[1].f.c == 30i32);
|
||||
assert(g12s[1].g == 9i32);
|
||||
};
|
||||
14
test/wcc/data/idx_dot_aggret_float_loud/case.ww
Normal file
14
test/wcc/data/idx_dot_aggret_float_loud/case.ww
Normal file
@@ -0,0 +1,14 @@
|
||||
//ww:error "float-bearing aggregate field receive"
|
||||
// #11/#165 tripwire: an in-cap aggregate-returning CALL into a FLOAT-bearing
|
||||
// aggregate field of an indexed element. The return routes a float eightbyte to
|
||||
// X0/X1, which the GP AX/DX/CX materialise cursor cannot read -> both stages
|
||||
// LOUD-STOP (#171). SUCCESS = the float guard regressed to silent GP-garbage.
|
||||
package main;
|
||||
type ft = struct { x: f64, y: i64 };
|
||||
type sf = struct { f: ft, g: i64 };
|
||||
fn mk() ft = { return ft{x=1.5f64, y=7i64}; };
|
||||
export fn main() i32 = {
|
||||
let arr: [2]sf;
|
||||
arr[1].f = mk();
|
||||
return 0;
|
||||
};
|
||||
15
test/wcc/data/idx_dot_aggret_overcap_loud/case.ww
Normal file
15
test/wcc/data/idx_dot_aggret_overcap_loud/case.ww
Normal file
@@ -0,0 +1,15 @@
|
||||
//ww:error "over-cap (sret) aggregate field receive"
|
||||
// #11c/#234 tripwire: an OVER-cap (>24B, sret-returning) CALL into an aggregate
|
||||
// field of an indexed element. The result is written via a dest pointer, not the
|
||||
// AX/DX/CX cursor, so the in-cap materialise cannot handle it -> both stages
|
||||
// LOUD-STOP until the const-idx sret-into-field path is wired (task #8, rule 7).
|
||||
// SUCCESS = the over-cap stop regressed to a 1-word silent drop (cs!=ww).
|
||||
package main;
|
||||
type big = struct { a:i64, b:i64, c:i64, d:i64 };
|
||||
type s = struct { f: big, g: i64 };
|
||||
fn mk() big = { return big{a=1i64,b=2i64,c=3i64,d=4i64}; };
|
||||
export fn main() i32 = {
|
||||
let arr: [2]s;
|
||||
arr[1].f = mk();
|
||||
return 0;
|
||||
};
|
||||
16
test/wcc/data/idx_dot_aggret_subtail_loud/case.ww
Normal file
16
test/wcc/data/idx_dot_aggret_subtail_loud/case.ww
Normal file
@@ -0,0 +1,16 @@
|
||||
//ww:error "3/5/6/7-byte sub-8 tail unwired"
|
||||
// #11 tripwire: an in-cap aggregate-returning CALL into an aggregate field of
|
||||
// an indexed element where the field has a 3/5/6/7-byte sub-8 tail (here 14B
|
||||
// 7xi16, tail=6). The materialise's single narrow MOV stores only one tail byte
|
||||
// while the copy reads the full tail -> a SILENT both-stage member drop. Both
|
||||
// stages LOUD-STOP until a general register->scratch tail lands (rule 7,
|
||||
// C2c-shared). SUCCESS = the loud-stop regressed to a silent miscompile.
|
||||
package main;
|
||||
type t14 = struct { a:i16,b:i16,c:i16,d:i16,e:i16,f:i16,g:i16 };
|
||||
type s14 = struct { x: t14, pad: i16 };
|
||||
fn mk() t14 = { return t14{a=1i16,b=2i16,c=3i16,d=4i16,e=5i16,f=6i16,g=7i16}; };
|
||||
export fn main() i32 = {
|
||||
let arr: [2]s14;
|
||||
arr[1].x = mk();
|
||||
return 0;
|
||||
};
|
||||
Reference in New Issue
Block a user