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:
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user