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:
@@ -240,8 +240,24 @@ fn cgtrytaggedshift(c: *cgen, n: *syntax.node) bool = {
|
||||
let sv: *syntax.tinfo = successvariant(ou);
|
||||
sv = tichase(sv);
|
||||
if (sv == nil) { return false; };
|
||||
if (sv.kind != syntax.tykind.TY_TAGGED) { return false; };
|
||||
if (sv.nullable != 0) { return false; };
|
||||
// #12: a general-aggregate (struct/array) success variant rides the
|
||||
// SAME in-cap {AX=w0,DX=w1,CX=w2} payload shuffle as the nested-TAGGED
|
||||
// box — the union return packs the payload as raw GP words past the
|
||||
// outer tag. Pre-#12 it matched no arm and fell to the bare MOVQ DX,AX
|
||||
// below, materialising only w0 (w1/w2 dropped) — a SILENT both-stage
|
||||
// word-drop. A float-bearing aggregate rides X0/X1 instead (the SSE
|
||||
// return-class), which this GP cursor cannot reach, so LOUD-STOP it
|
||||
// (mirror #11/#165). Mirrors cstage N_TRYPROP/N_TRYUNW.
|
||||
let agg: bool = false;
|
||||
if (sv.kind == syntax.tykind.TY_TAGGED && sv.nullable == 0) { agg = true; };
|
||||
if (sv.kind == syntax.tykind.TY_STRUCT) { agg = true; };
|
||||
if (sv.kind == syntax.tykind.TY_ARRAY) { agg = true; };
|
||||
if (!agg) { return false; };
|
||||
if (tinfoaggfloat(sv)) {
|
||||
let m12f: str = "#12/#165: float-bearing aggregate success variant unwrap (S|e)! rides SSE X0/X1; GP cursor unwired\n";
|
||||
os.write(2, m12f.ptr, m12f.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
emitline("\tMOVQ\tDX, AX\n");
|
||||
if (sv.size: i32 > 8) { emitline("\tMOVQ\tCX, DX\n"); };
|
||||
if (sv.size: i32 > 16) { emitline("\tMOVQ\tR8, CX\n"); };
|
||||
@@ -9106,7 +9122,16 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
// alignment. esz>8 non-str/non-slice IS struct/array/tuple
|
||||
// here (tagged returned above). In-cap only
|
||||
// (callsretsize==0). Mirror of cstage cgen.c C2c arm.
|
||||
if (n.rhs.kind == syntax.nkind.N_CALL && esz > 8
|
||||
// #12: an unwrap `mk()!` / `r?` whose success variant
|
||||
// is an in-cap struct/array rides the SAME {AX,DX,CX}
|
||||
// payload shape as the call return (the producer shift
|
||||
// at cgtrytaggedshift materialises it); admit it
|
||||
// alongside N_CALL so the materialise + copy just works.
|
||||
// callsretsize==0 holds (non-call → 0); the float/over-
|
||||
// cap loud-stops live at the producer.
|
||||
if ((n.rhs.kind == syntax.nkind.N_CALL
|
||||
|| n.rhs.kind == syntax.nkind.N_TRYUNW
|
||||
|| n.rhs.kind == syntax.nkind.N_TRYPROP) && esz > 8
|
||||
&& !isstrtype(c, elemtn) && !isslicetype(c, elemtn)
|
||||
&& callsretsize(c, n.rhs) == 0) {
|
||||
let scrc2: i32 = tagscradd(c, esz);
|
||||
@@ -10014,9 +10039,17 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
// pure-float return eightbyte rides X0/X1 which
|
||||
// the GP AX/DX/CX cursor cannot read). Mirrors
|
||||
// cstage cgen.c.
|
||||
// #12: an unwrap `arr[i].f = mk()!` rides the same
|
||||
// {AX,DX,CX} shape (producer shift) — admit it
|
||||
// alongside N_CALL; the #11b non-call arm below
|
||||
// excludes the unwrap kinds so this arm is the SOLE
|
||||
// handler. callsretsize==0 for a non-call, so an
|
||||
// over-cap unwrap stays loud at the producer.
|
||||
if (n.op == syntax.tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == syntax.nkind.N_CALL) {
|
||||
&& (n.rhs.kind == syntax.nkind.N_CALL
|
||||
|| n.rhs.kind == syntax.nkind.N_TRYUNW
|
||||
|| n.rhs.kind == syntax.nkind.N_TRYPROP)) {
|
||||
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);
|
||||
@@ -10179,9 +10212,16 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
// through aggargsrcaddr (src -> SI) + aggcopy — the ONE
|
||||
// copy emitter the non-indexed bases use (DRY, rule 8).
|
||||
// tsz natural (fi.fsz, type table). Mirrors cstage cgen.c.
|
||||
// #12: exclude the unwrap kinds — the #11 arm above is
|
||||
// their sole handler (they ride the {AX,DX,CX} register
|
||||
// cursor, NOT a source address, so aggargsrcaddr can't
|
||||
// reach them). Pre-#12 an N_TRYUNW matched != N_CALL and
|
||||
// loud-stopped here; now it materialises in the #11 arm.
|
||||
if (n.op == syntax.tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind != syntax.nkind.N_CALL) {
|
||||
&& n.rhs.kind != syntax.nkind.N_CALL
|
||||
&& n.rhs.kind != syntax.nkind.N_TRYUNW
|
||||
&& n.rhs.kind != syntax.nkind.N_TRYPROP) {
|
||||
let fk11b: *syntax.tinfo = tichase(fi.tnode.type_: *syntax.tinfo);
|
||||
let isagg11b: bool = false;
|
||||
if (fk11b != nil) {
|
||||
@@ -10458,10 +10498,15 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
// register RECV reads AX/DX/CX at 8-byte
|
||||
// granularity — size via structabisize (cstage
|
||||
// SSoT lu->size, check.c:760; cgen.c:7720
|
||||
// sz=lu->size at the receive twin).
|
||||
// sz=lu->size at the receive twin). #12: an
|
||||
// unwrap `p.f = mk()!` rides the same {AX,DX,CX}
|
||||
// shape (producer shift) — admit it alongside
|
||||
// N_CALL; float/over-cap loud-stop at the producer.
|
||||
if (n.op == syntax.tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == syntax.nkind.N_CALL
|
||||
&& (n.rhs.kind == syntax.nkind.N_CALL
|
||||
|| n.rhs.kind == syntax.nkind.N_TRYUNW
|
||||
|| n.rhs.kind == syntax.nkind.N_TRYPROP)
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == syntax.nkind.N_TNAME
|
||||
&& aliasprimsize(c, fi.tnode.str) == 0) {
|
||||
@@ -10702,10 +10747,15 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
// directly at (lc.off+fi.foff)(BP).
|
||||
// N_STRUCTLIT: field-walk; each inner
|
||||
// field stored at +fi.foff+inner_foff(BP).
|
||||
// BP-rel direct, no addr scratch needed.
|
||||
// BP-rel direct, no addr scratch needed. #12: an
|
||||
// unwrap `b.f = mk()!` rides the same {AX,DX,CX}
|
||||
// shape (producer shift) — admit it alongside
|
||||
// N_CALL; float/over-cap loud-stop at the producer.
|
||||
if (n.op == syntax.tkind.TK_ASSIGN
|
||||
&& n.rhs != nil
|
||||
&& n.rhs.kind == syntax.nkind.N_CALL
|
||||
&& (n.rhs.kind == syntax.nkind.N_CALL
|
||||
|| n.rhs.kind == syntax.nkind.N_TRYUNW
|
||||
|| n.rhs.kind == syntax.nkind.N_TRYPROP)
|
||||
&& fi.tnode != nil
|
||||
&& fi.tnode.kind == syntax.nkind.N_TNAME
|
||||
&& aliasprimsize(c, fi.tnode.str) == 0) {
|
||||
|
||||
@@ -3013,6 +3013,42 @@ fn aggargfloatstop(n: *syntax.node) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// tinfoaggfloat — does an aggregate tinfo carry ANY float leaf
|
||||
// (recursively through struct fields / array element / tuple
|
||||
// positionals)? The #12 producer (cgtrytaggedshift) loud-stops a
|
||||
// float-bearing aggregate success variant: the union return places a
|
||||
// float eightbyte in the SSE class (X0/X1) which the GP {AX,DX,CX}
|
||||
// payload shuffle cannot reach (mirror #11/#165). Conservative — ANY
|
||||
// float, not a per-eightbyte SSE classify like structfloatclass — a
|
||||
// loud-stop only needs to refuse, not transport. Mirrors cstage
|
||||
// agg_has_float (cmd/w6c/cgen.c).
|
||||
fn tinfoaggfloat(t: *syntax.tinfo) bool = {
|
||||
if (t == nil) { return false; };
|
||||
let u: *syntax.tinfo = tichase(t);
|
||||
if (u == nil) { return false; };
|
||||
if (syntax.typeisfloat(u)) { return true; };
|
||||
if (u.kind == syntax.tykind.TY_STRUCT) {
|
||||
let fi: *syntax.tfield = u.fields;
|
||||
for (fi != nil) {
|
||||
if (tinfoaggfloat(fi.type_)) { return true; };
|
||||
fi = fi.tnext;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
if (u.kind == syntax.tykind.TY_ARRAY) {
|
||||
return tinfoaggfloat(u.sub);
|
||||
};
|
||||
if (u.kind == syntax.tykind.TY_TUPLE) {
|
||||
let pe: *syntax.ttupleelem = u.tupleelems;
|
||||
for (pe != nil) {
|
||||
if (tinfoaggfloat(pe.type_)) { return true; };
|
||||
pe = pe.tnext;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// structfloatclass — SysV per-eightbyte classification for the #165
|
||||
// float-bearing-struct param case (param twin of #171's struct return;
|
||||
// classifies per-eightbyte, not #163's per-element). Returns 0 when the
|
||||
|
||||
Reference in New Issue
Block a user