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:
2026-06-27 18:49:03 +09:00
parent b8d55a729e
commit a0e330b283
7 changed files with 522 additions and 0 deletions

View 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;
};

View 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;
};

View 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;
};