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:
172
cmd/w6c/cgen.c
172
cmd/w6c/cgen.c
@@ -5810,6 +5810,178 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
amem(D_BX, foff + 8));
|
||||
break;
|
||||
}
|
||||
/* #11: an in-cap aggregate-returning CALL into
|
||||
* an AGGREGATE field of an indexed element
|
||||
* `arr[i].f = mk()`. The scalar default below
|
||||
* stores only AX (eb0), dropping DX/CX — a SILENT
|
||||
* both-stage field-drop, the field-of-indexed twin
|
||||
* of C2c's whole-element arr[i]=mk() arm
|
||||
* (cgen.c :6764). Scratch-first materialise of the
|
||||
* AX/DX/CX return (not a PUSHQ spill — keeps the
|
||||
* CALL at the frame's 16B alignment and survives an
|
||||
* idx that itself contains a call), reuse the scalar
|
||||
* arm's &arr[i]->BX address computation verbatim,
|
||||
* then word-copy scratch to foff(BX). In-cap only
|
||||
* (cg_sret_retsize==0); over-cap sret-into-field
|
||||
* LOUD-STOPS (#11c/#234, task #8) and a float-
|
||||
* bearing aggregate LOUD-STOPS (#165/#171 — a pure-
|
||||
* float return eightbyte rides X0/X1 which the GP
|
||||
* AX/DX/CX cursor cannot read). Mirrors wwstage
|
||||
* cgenexpr.ww. */
|
||||
if (n->op == TK_ASSIGN && n->rhs
|
||||
&& n->rhs->kind == N_CALL) {
|
||||
if (cg_sret_retsize(ft) > 0)
|
||||
fatal("#11c/#234: over-cap "
|
||||
"(sret) aggregate field "
|
||||
"receive arr[i].f=mk() "
|
||||
"unwired (cs!=ww; task #8)");
|
||||
/* struct_float_class mirrors the
|
||||
* return-side SSE routing (#171a); a
|
||||
* field typed DIRECTLY as a tuple misses
|
||||
* it yet the bare-tuple return routes
|
||||
* floats to tuple_sse_seq — guard it too
|
||||
* so neither stage silently stores X0
|
||||
* garbage through the GP cursor. */
|
||||
int sclass11[2];
|
||||
int sse11 = struct_float_class(ft,
|
||||
sclass11) > 0;
|
||||
if (!sse11) {
|
||||
Type *tu11 =
|
||||
type_chase_named(ft);
|
||||
if (tu11 && tu11->kind
|
||||
== TY_TUPLE)
|
||||
for (Tparam *p11 =
|
||||
tu11->params; p11;
|
||||
p11 = p11->next) {
|
||||
int f32_11;
|
||||
if (fld_isfloat(
|
||||
p11->type,
|
||||
&f32_11))
|
||||
sse11 = 1;
|
||||
}
|
||||
}
|
||||
if (sse11)
|
||||
fatal("#11/#165: float-bearing "
|
||||
"aggregate field receive "
|
||||
"arr[i].f=mk() unwired (SSE "
|
||||
"return eightbyte; #171)");
|
||||
if (fsz > 8) {
|
||||
/* A 3/5/6/7-byte sub-8 tail cannot be
|
||||
* materialised by the single narrow MOV
|
||||
* below — it stores ONE byte while the
|
||||
* copy reads the full tail, dropping the
|
||||
* rest from uninitialised scratch (a
|
||||
* silent both-stage drop; empirically a
|
||||
* 14B 7xi16 field loses f/g). The whole-
|
||||
* element C2c sibling (:6764) shares this
|
||||
* single-tail gap; until a general
|
||||
* register->scratch tail (shift cascade)
|
||||
* lands across BOTH sites, LOUD-STOP
|
||||
* rather than silently drop — rule 7, the
|
||||
* sibling of the over-cap/float stops
|
||||
* above. Tails 0/1/2/4 are exact and flow
|
||||
* through. Mirrors wwstage cgenexpr.ww. */
|
||||
int tl11 = fsz % 8;
|
||||
if (tl11 == 3 || tl11 == 5
|
||||
|| tl11 == 6 || tl11 == 7)
|
||||
fatal("#11: aggregate field receive "
|
||||
"arr[i].f=mk() with a 3/5/6/7-byte "
|
||||
"sub-8 tail unwired (materialise "
|
||||
"single-MOV under-stores; "
|
||||
"C2c-shared)");
|
||||
int scr11 = cg_tagscr_slot(c,
|
||||
&locals, fsz);
|
||||
cgexpr(c, n->rhs, locals);
|
||||
int regs11[3] = { D_AX, D_DX,
|
||||
D_CX };
|
||||
int full11 = fsz / 8;
|
||||
int tail11 = fsz % 8;
|
||||
for (int i11 = 0;
|
||||
i11 < full11; i11++)
|
||||
ins2(c, A_MOVQ,
|
||||
areg(regs11[i11]),
|
||||
amem(D_BP, scr11
|
||||
+ i11 * 8));
|
||||
if (tail11 > 0) {
|
||||
int op11 = (tail11 == 4)
|
||||
? A_MOVL : (tail11
|
||||
== 2) ? A_MOVW
|
||||
: A_MOVB;
|
||||
ins2(c, op11,
|
||||
areg(regs11[full11]),
|
||||
amem(D_BP, scr11
|
||||
+ full11 * 8));
|
||||
}
|
||||
cgexpr(c, idx, locals);
|
||||
if (esz > 1) {
|
||||
ins2(c, A_MOVQ,
|
||||
aimm(esz),
|
||||
areg(D_CX));
|
||||
ins2(c, A_IMULQ,
|
||||
areg(D_CX),
|
||||
areg(D_AX));
|
||||
}
|
||||
if (is_arr)
|
||||
ins2(c, A_LEAQ,
|
||||
amem(D_BP, off),
|
||||
areg(D_BX));
|
||||
else
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, off),
|
||||
areg(D_BX));
|
||||
ins2(c, A_ADDQ,
|
||||
areg(D_AX), areg(D_BX));
|
||||
if (viaptr)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BX, 0),
|
||||
areg(D_BX));
|
||||
int k11 = 0;
|
||||
for (; k11 + 8 <= fsz;
|
||||
k11 += 8) {
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, scr11
|
||||
+ k11), areg(D_AX));
|
||||
ins2(c, A_MOVQ,
|
||||
areg(D_AX),
|
||||
amem(D_BX,
|
||||
foff + k11));
|
||||
}
|
||||
if (k11 + 4 <= fsz) {
|
||||
ins2(c, A_MOVL,
|
||||
amem(D_BP, scr11
|
||||
+ k11), areg(D_AX));
|
||||
ins2(c, A_MOVL,
|
||||
areg(D_AX),
|
||||
amem(D_BX,
|
||||
foff + k11));
|
||||
k11 += 4;
|
||||
}
|
||||
if (k11 + 2 <= fsz) {
|
||||
ins2(c, A_MOVW,
|
||||
amem(D_BP, scr11
|
||||
+ k11), areg(D_AX));
|
||||
ins2(c, A_MOVW,
|
||||
areg(D_AX),
|
||||
amem(D_BX,
|
||||
foff + k11));
|
||||
k11 += 2;
|
||||
}
|
||||
if (k11 + 1 <= fsz) {
|
||||
ins2(c, A_MOVB,
|
||||
amem(D_BP, scr11
|
||||
+ k11), areg(D_AX));
|
||||
ins2(c, A_MOVB,
|
||||
areg(D_AX),
|
||||
amem(D_BX,
|
||||
foff + k11));
|
||||
k11 += 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* fsz<=8 in-cap aggregate returns
|
||||
* wholly in AX; the scalar default's
|
||||
* single store is the correct receive. */
|
||||
}
|
||||
if (n->op == TK_ASSIGN) {
|
||||
cgexpr(c, n->rhs, locals);
|
||||
ins1(c, A_PUSHQ,
|
||||
|
||||
@@ -9984,6 +9984,185 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
// #11: an in-cap aggregate-returning CALL into
|
||||
// an AGGREGATE field of an indexed element
|
||||
// `arr[i].f = mk()`. The scalar default below
|
||||
// stores only AX (eb0), dropping DX/CX — a
|
||||
// SILENT both-stage field-drop, the field-of-
|
||||
// indexed twin of C2c's whole-element
|
||||
// arr[i]=mk() arm (cgenexpr.ww :9100). Scratch-
|
||||
// first materialise of the AX/DX/CX return (not
|
||||
// a PUSHQ spill — keeps the CALL at the frame's
|
||||
// 16B alignment and survives an idx that itself
|
||||
// contains a call), reuse the scalar arm's
|
||||
// &arr[i]->BX address computation verbatim, then
|
||||
// word-copy scratch to fi.foff(BX). tsz from the
|
||||
// type table (fieldsize, rule 13). In-cap only
|
||||
// (callsretsize==0); over-cap sret-into-field
|
||||
// LOUD-STOPS (#11c/#234, task #8) and a float-
|
||||
// bearing aggregate LOUD-STOPS (#165/#171 — a
|
||||
// pure-float return eightbyte rides X0/X1 which
|
||||
// the GP AX/DX/CX cursor cannot read). Mirrors
|
||||
// cstage cgen.c.
|
||||
if (n.op == syntax.tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == syntax.nkind.N_CALL) {
|
||||
if (callsretsize(c, n.rhs) > 0) {
|
||||
let m11o: str = "#11c/#234: over-cap (sret) aggregate field receive arr[i].f=mk() unwired (cs!=ww; task #8)\n";
|
||||
os.write(2, m11o.ptr, m11o.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
// structfloatclass mirrors the return-
|
||||
// side SSE routing (cgenstmt.ww #171a);
|
||||
// a field typed DIRECTLY as a tuple
|
||||
// misses it (non-N_TNAME) yet the bare-
|
||||
// tuple return routes floats to tupsse —
|
||||
// guard it too so neither stage silently
|
||||
// stores X0 garbage through the GP cursor.
|
||||
let sse11: bool = structfloatclass(c, fi.tnode) != 0;
|
||||
if (!sse11) {
|
||||
let rt11: *syntax.node = resolvetype(c, fi.tnode);
|
||||
if (rt11 != nil) { if (rt11.kind == syntax.nkind.N_TTUPLE) {
|
||||
let q11: *syntax.node = rt11.list;
|
||||
for (q11 != nil) {
|
||||
if (isfloattype(c, q11.lhs)) { sse11 = true; };
|
||||
q11 = q11.next;
|
||||
};
|
||||
};};
|
||||
};
|
||||
if (sse11) {
|
||||
let m11f: str = "#11/#165: float-bearing aggregate field receive arr[i].f=mk() unwired (SSE return eightbyte; #171)\n";
|
||||
os.write(2, m11f.ptr, m11f.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
// fi.fsz is the field's NATURAL size
|
||||
// (tf.type_.size, cgenutil.ww :2736) —
|
||||
// the byte-id twin of cstage's
|
||||
// fsz=ft->size. fieldsize() returns the
|
||||
// slot-PADDED size (16 for a 12B
|
||||
// 3×i32), which both diverges from
|
||||
// cstage AND a full-MOVQ tail on it
|
||||
// would smash the next field (g at +12).
|
||||
let tsz: i32 = fi.fsz;
|
||||
if (tsz > 8) {
|
||||
// A 3/5/6/7-byte sub-8 tail cannot
|
||||
// be materialised by the single narrow
|
||||
// MOV below — it stores ONE byte while
|
||||
// the copy reads the full tail, dropping
|
||||
// the rest from uninitialised scratch (a
|
||||
// silent both-stage drop; empirically a
|
||||
// 14B 7xi16 field loses f/g). The whole-
|
||||
// element C2c sibling (:9100) shares this
|
||||
// single-tail gap; until a general
|
||||
// register->scratch tail (shift cascade)
|
||||
// lands across BOTH sites, LOUD-STOP
|
||||
// rather than silently drop — rule 7, the
|
||||
// #11 sibling of the over-cap/float stops
|
||||
// above. Tails 0/1/2/4 are exact and flow
|
||||
// through. Mirrors cstage cgen.c.
|
||||
let tl11: i32 = tsz % 8;
|
||||
if (tl11 == 3 || tl11 == 5 || tl11 == 6 || tl11 == 7) {
|
||||
let m11t: str = "#11: aggregate field receive arr[i].f=mk() with a 3/5/6/7-byte sub-8 tail unwired (materialise single-MOV under-stores; C2c-shared)\n";
|
||||
os.write(2, m11t.ptr, m11t.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let scr11: i32 = tagscradd(c, tsz);
|
||||
cgexpr(c, n.rhs);
|
||||
// AX/DX/CX -> scratch (C2c materialise)
|
||||
let full11: i32 = tsz / 8;
|
||||
let tail11: i32 = tsz % 8;
|
||||
let wi11: i32 = 0;
|
||||
for (wi11 < full11) {
|
||||
let rn11: str = "AX";
|
||||
if (wi11 == 1) { rn11 = "DX"; }
|
||||
else { if (wi11 == 2) { rn11 = "CX"; }; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(rn11);
|
||||
emitline(", ");
|
||||
emitoff((scr11 + wi11 * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
wi11 += 1;
|
||||
};
|
||||
if (tail11 > 0) {
|
||||
let top11: str = "MOVB";
|
||||
if (tail11 == 4) { top11 = "MOVL"; }
|
||||
else { if (tail11 == 2) { top11 = "MOVW"; }; };
|
||||
let treg11: str = "AX";
|
||||
if (full11 == 1) { treg11 = "DX"; }
|
||||
else { if (full11 == 2) { treg11 = "CX"; }; };
|
||||
emitline("\t");
|
||||
emitline(top11);
|
||||
emitline("\t");
|
||||
emitline(treg11);
|
||||
emitline(", ");
|
||||
emitoff((scr11 + full11 * 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
// &arr[i] -> BX (verbatim scalar arm)
|
||||
cgexpr(c, idx);
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
if (baseisarray) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
if (viaptr) { emitline("\tMOVQ\t(BX), BX\n"); };
|
||||
// word-copy scratch -> fi.foff(BX),
|
||||
// tail-aware (C2c copy); foff on every
|
||||
// eightbyte, sub-8 tail stays sub-8.
|
||||
let kc11: i32 = 0;
|
||||
for (kc11 + 8 <= tsz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((scr11 + kc11): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg((fi.foff + kc11): i64, "BX");
|
||||
emitline("\n");
|
||||
kc11 += 8;
|
||||
};
|
||||
if (kc11 + 4 <= tsz) {
|
||||
emitline("\tMOVL\t");
|
||||
emitoff((scr11 + kc11): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg((fi.foff + kc11): i64, "BX");
|
||||
emitline("\n");
|
||||
kc11 += 4;
|
||||
};
|
||||
if (kc11 + 2 <= tsz) {
|
||||
emitline("\tMOVW\t");
|
||||
emitoff((scr11 + kc11): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVW\tAX, ");
|
||||
emitdispreg((fi.foff + kc11): i64, "BX");
|
||||
emitline("\n");
|
||||
kc11 += 2;
|
||||
};
|
||||
if (kc11 + 1 <= tsz) {
|
||||
emitline("\tMOVB\t");
|
||||
emitoff((scr11 + kc11): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg((fi.foff + kc11): i64, "BX");
|
||||
emitline("\n");
|
||||
kc11 += 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
// tsz<=8 in-cap aggregate returns wholly
|
||||
// in AX; the scalar default's MOVQ/MOVL AX
|
||||
// store is the correct 1-word receive.
|
||||
};
|
||||
// scalar plain `=`
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
|
||||
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