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:
105
cmd/w6c/cgen.c
105
cmd/w6c/cgen.c
@@ -557,6 +557,36 @@ struct_arg_size(Type *t)
|
|||||||
return (int)t->size;
|
return (int)t->size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* agg_has_float — does an aggregate Type carry ANY float leaf
|
||||||
|
* (recursively through struct fields / array element / tuple
|
||||||
|
* positionals)? The #12 producer (N_TRYPROP/N_TRYUNW success shuffle)
|
||||||
|
* 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 struct_float_class
|
||||||
|
* — a loud-stop only needs to refuse, not transport. Mirrors wwstage
|
||||||
|
* tinfo_agg_float (cgenutil.ww). */
|
||||||
|
static int
|
||||||
|
agg_has_float(Type *t)
|
||||||
|
{
|
||||||
|
t = type_chase_named(t);
|
||||||
|
if (t == NULL) return 0;
|
||||||
|
if (cg_isfloat(t)) return 1;
|
||||||
|
if (t->kind == TY_STRUCT) {
|
||||||
|
for (Tfield *f = t->fields; f; f = f->next)
|
||||||
|
if (agg_has_float(f->type)) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (t->kind == TY_ARRAY)
|
||||||
|
return agg_has_float(t->sub);
|
||||||
|
if (t->kind == TY_TUPLE) {
|
||||||
|
for (Tparam *p = t->params; p; p = p->next)
|
||||||
|
if (agg_has_float(p->type)) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* struct_float_class — SysV per-eightbyte classification for the #165
|
/* struct_float_class — SysV per-eightbyte classification for the #165
|
||||||
* float-bearing-struct param case (the param twin of #171's struct
|
* float-bearing-struct param case (the param twin of #171's struct
|
||||||
* return, classifying per-eightbyte rather than #163's per-element).
|
* return, classifying per-eightbyte rather than #163's per-element).
|
||||||
@@ -5385,10 +5415,21 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
* store; for via_ptr/is_global the dst base addr
|
* store; for via_ptr/is_global the dst base addr
|
||||||
* is reloaded into BX before each store so cgexpr
|
* is reloaded into BX before each store so cgexpr
|
||||||
* can clobber AX/BX between fields. */
|
* can clobber AX/BX between fields. */
|
||||||
|
/* #12: an unwrap `b.f = mk()!` (direct) / `p.f =
|
||||||
|
* mk()!` (via-ptr) rides the same {AX,DX,CX} shape
|
||||||
|
* (producer shift) — admit it alongside N_CALL. Gated
|
||||||
|
* !is_global: the wwstage single-dot arms cover a direct
|
||||||
|
* / via-ptr base only; a global `g.f` unwrap stays on
|
||||||
|
* its pre-existing path in BOTH stages (out of scope,
|
||||||
|
* not regressed). Float/over-cap loud-stop at the
|
||||||
|
* producer. */
|
||||||
if (n->op == TK_ASSIGN && str_fu
|
if (n->op == TK_ASSIGN && str_fu
|
||||||
&& str_fu->kind == TY_STRUCT
|
&& str_fu->kind == TY_STRUCT
|
||||||
&& (int)str_fu->size <= 24
|
&& (int)str_fu->size <= 24
|
||||||
&& n->rhs && n->rhs->kind == N_CALL
|
&& n->rhs && (n->rhs->kind == N_CALL
|
||||||
|
|| ((n->rhs->kind == N_TRYUNW
|
||||||
|
|| n->rhs->kind == N_TRYPROP)
|
||||||
|
&& !is_global))
|
||||||
&& (str_fu->size % 8 == 0
|
&& (str_fu->size % 8 == 0
|
||||||
|| str_fu->size % 8 == 1
|
|| str_fu->size % 8 == 1
|
||||||
|| str_fu->size % 8 == 2
|
|| str_fu->size % 8 == 2
|
||||||
@@ -5828,8 +5869,17 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
* float return eightbyte rides X0/X1 which the GP
|
* float return eightbyte rides X0/X1 which the GP
|
||||||
* AX/DX/CX cursor cannot read). Mirrors wwstage
|
* AX/DX/CX cursor cannot read). Mirrors wwstage
|
||||||
* cgenexpr.ww. */
|
* cgenexpr.ww. */
|
||||||
|
/* #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. cg_sret_retsize keys off ft (the
|
||||||
|
* field = success variant type), so an over-cap
|
||||||
|
* unwrap stays loud here too. */
|
||||||
if (n->op == TK_ASSIGN && n->rhs
|
if (n->op == TK_ASSIGN && n->rhs
|
||||||
&& n->rhs->kind == N_CALL) {
|
&& (n->rhs->kind == N_CALL
|
||||||
|
|| n->rhs->kind == N_TRYUNW
|
||||||
|
|| n->rhs->kind == N_TRYPROP)) {
|
||||||
if (cg_sret_retsize(ft) > 0)
|
if (cg_sret_retsize(ft) > 0)
|
||||||
fatal("#11c/#234: over-cap "
|
fatal("#11c/#234: over-cap "
|
||||||
"(sret) aggregate field "
|
"(sret) aggregate field "
|
||||||
@@ -5991,8 +6041,14 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
* cg_aggcopy — the ONE copy emitter the non-
|
* cg_aggcopy — the ONE copy emitter the non-
|
||||||
* indexed bases use (DRY). fsz natural
|
* indexed bases use (DRY). fsz natural
|
||||||
* (ft->size). Mirrors wwstage cgenexpr.ww. */
|
* (ft->size). Mirrors wwstage cgenexpr.ww. */
|
||||||
|
/* #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). */
|
||||||
if (n->op == TK_ASSIGN && n->rhs
|
if (n->op == TK_ASSIGN && n->rhs
|
||||||
&& n->rhs->kind != N_CALL && fu
|
&& n->rhs->kind != N_CALL
|
||||||
|
&& n->rhs->kind != N_TRYUNW
|
||||||
|
&& n->rhs->kind != N_TRYPROP && fu
|
||||||
&& (fu->kind == TY_STRUCT || fu->kind == TY_ARRAY
|
&& (fu->kind == TY_STRUCT || fu->kind == TY_ARRAY
|
||||||
|| fu->kind == TY_TUPLE) && fsz > 8) {
|
|| fu->kind == TY_TUPLE) && fsz > 8) {
|
||||||
cgexpr(c, idx, locals);
|
cgexpr(c, idx, locals);
|
||||||
@@ -6986,12 +7042,20 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
* copy scratch → dest. Scratch-first (not a dest spill across
|
* copy scratch → dest. Scratch-first (not a dest spill across
|
||||||
* the call) keeps the call at the frame's natural alignment.
|
* the call) keeps the call at the frame's natural alignment.
|
||||||
* In-cap only (cg_sret_retsize==0). */
|
* In-cap only (cg_sret_retsize==0). */
|
||||||
|
/* #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 materialises
|
||||||
|
* it); admit it alongside N_CALL. cg_sret_retsize(esub)==0
|
||||||
|
* holds for an in-cap variant; the float/over-cap loud-
|
||||||
|
* stops live at the producer. */
|
||||||
if ((is_arr || is_sl || is_ptr) && n->op == TK_ASSIGN
|
if ((is_arr || is_sl || is_ptr) && n->op == TK_ASSIGN
|
||||||
&& esubu && (esubu->kind == TY_STRUCT
|
&& esubu && (esubu->kind == TY_STRUCT
|
||||||
|| esubu->kind == TY_ARRAY
|
|| esubu->kind == TY_ARRAY
|
||||||
|| esubu->kind == TY_TUPLE)
|
|| esubu->kind == TY_TUPLE)
|
||||||
&& esz > 8
|
&& esz > 8
|
||||||
&& n->rhs->kind == N_CALL
|
&& (n->rhs->kind == N_CALL
|
||||||
|
|| n->rhs->kind == N_TRYUNW
|
||||||
|
|| n->rhs->kind == N_TRYPROP)
|
||||||
&& cg_sret_retsize(esub) == 0) {
|
&& cg_sret_retsize(esub) == 0) {
|
||||||
int scr = cg_tagscr_slot(c, &locals, esz);
|
int scr = cg_tagscr_slot(c, &locals, esz);
|
||||||
cgexpr(c, n->rhs, locals); /* call → AX/DX/CX */
|
cgexpr(c, n->rhs, locals); /* call → AX/DX/CX */
|
||||||
@@ -11113,8 +11177,24 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
* standard AX=tag cursor. The scalar MOVQ DX,AX
|
* standard AX=tag cursor. The scalar MOVQ DX,AX
|
||||||
* below carried only the inner tag and dropped the
|
* below carried only the inner tag and dropped the
|
||||||
* payload (ken unw16). Nullable folds to one word
|
* payload (ken unw16). Nullable folds to one word
|
||||||
* and stays on the scalar move. */
|
* and stays on the scalar move.
|
||||||
if (stu && stu->kind == TY_TAGGED && !stu->nullable) {
|
*
|
||||||
|
* #12: a general-aggregate (struct/array) success
|
||||||
|
* variant rides the SAME in-cap {AX,DX,CX} payload
|
||||||
|
* shuffle — 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 (the SSE return-class) which this GP cursor
|
||||||
|
* cannot reach, so LOUD-STOP it (mirror #11/#165). */
|
||||||
|
if (stu && ((stu->kind == TY_TAGGED && !stu->nullable)
|
||||||
|
|| stu->kind == TY_STRUCT || stu->kind == TY_ARRAY)) {
|
||||||
|
if (agg_has_float(stu))
|
||||||
|
fatal("#12/#165: float-bearing "
|
||||||
|
"aggregate success variant unwrap "
|
||||||
|
"(S|e)! rides SSE X0/X1; GP cursor "
|
||||||
|
"unwired");
|
||||||
ins2(c, A_MOVQ, areg(D_DX), areg(D_AX));
|
ins2(c, A_MOVQ, areg(D_DX), areg(D_AX));
|
||||||
if (stu->size > 8)
|
if (stu->size > 8)
|
||||||
ins2(c, A_MOVQ, areg(D_CX),
|
ins2(c, A_MOVQ, areg(D_CX),
|
||||||
@@ -11225,8 +11305,17 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
}
|
}
|
||||||
/* Family C (#35): TAGGED success = nested box on the
|
/* Family C (#35): TAGGED success = nested box on the
|
||||||
* payload words — shift past the outer tag (see the
|
* payload words — shift past the outer tag (see the
|
||||||
* N_TRYPROP twin). */
|
* N_TRYPROP twin). #12: a struct/array success variant
|
||||||
if (stu && stu->kind == TY_TAGGED && !stu->nullable) {
|
* rides the same in-cap {AX,DX,CX} shuffle; a float-
|
||||||
|
* bearing aggregate LOUD-STOPS (X0/X1, GP cursor can't
|
||||||
|
* reach it). Full WHY at the N_TRYPROP twin. */
|
||||||
|
if (stu && ((stu->kind == TY_TAGGED && !stu->nullable)
|
||||||
|
|| stu->kind == TY_STRUCT || stu->kind == TY_ARRAY)) {
|
||||||
|
if (agg_has_float(stu))
|
||||||
|
fatal("#12/#165: float-bearing "
|
||||||
|
"aggregate success variant unwrap "
|
||||||
|
"(S|e)! rides SSE X0/X1; GP cursor "
|
||||||
|
"unwired");
|
||||||
ins2(c, A_MOVQ, areg(D_DX), areg(D_AX));
|
ins2(c, A_MOVQ, areg(D_DX), areg(D_AX));
|
||||||
if (stu->size > 8)
|
if (stu->size > 8)
|
||||||
ins2(c, A_MOVQ, areg(D_CX),
|
ins2(c, A_MOVQ, areg(D_CX),
|
||||||
|
|||||||
@@ -240,8 +240,24 @@ fn cgtrytaggedshift(c: *cgen, n: *syntax.node) bool = {
|
|||||||
let sv: *syntax.tinfo = successvariant(ou);
|
let sv: *syntax.tinfo = successvariant(ou);
|
||||||
sv = tichase(sv);
|
sv = tichase(sv);
|
||||||
if (sv == nil) { return false; };
|
if (sv == nil) { return false; };
|
||||||
if (sv.kind != syntax.tykind.TY_TAGGED) { return false; };
|
// #12: a general-aggregate (struct/array) success variant rides the
|
||||||
if (sv.nullable != 0) { return false; };
|
// 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");
|
emitline("\tMOVQ\tDX, AX\n");
|
||||||
if (sv.size: i32 > 8) { emitline("\tMOVQ\tCX, DX\n"); };
|
if (sv.size: i32 > 8) { emitline("\tMOVQ\tCX, DX\n"); };
|
||||||
if (sv.size: i32 > 16) { emitline("\tMOVQ\tR8, CX\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
|
// alignment. esz>8 non-str/non-slice IS struct/array/tuple
|
||||||
// here (tagged returned above). In-cap only
|
// here (tagged returned above). In-cap only
|
||||||
// (callsretsize==0). Mirror of cstage cgen.c C2c arm.
|
// (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)
|
&& !isstrtype(c, elemtn) && !isslicetype(c, elemtn)
|
||||||
&& callsretsize(c, n.rhs) == 0) {
|
&& callsretsize(c, n.rhs) == 0) {
|
||||||
let scrc2: i32 = tagscradd(c, esz);
|
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
|
// pure-float return eightbyte rides X0/X1 which
|
||||||
// the GP AX/DX/CX cursor cannot read). Mirrors
|
// the GP AX/DX/CX cursor cannot read). Mirrors
|
||||||
// cstage cgen.c.
|
// 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
|
if (n.op == syntax.tkind.TK_ASSIGN
|
||||||
&& n.rhs != nil
|
&& 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) {
|
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";
|
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.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
|
// through aggargsrcaddr (src -> SI) + aggcopy — the ONE
|
||||||
// copy emitter the non-indexed bases use (DRY, rule 8).
|
// copy emitter the non-indexed bases use (DRY, rule 8).
|
||||||
// tsz natural (fi.fsz, type table). Mirrors cstage cgen.c.
|
// 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
|
if (n.op == syntax.tkind.TK_ASSIGN
|
||||||
&& n.rhs != nil
|
&& 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 fk11b: *syntax.tinfo = tichase(fi.tnode.type_: *syntax.tinfo);
|
||||||
let isagg11b: bool = false;
|
let isagg11b: bool = false;
|
||||||
if (fk11b != nil) {
|
if (fk11b != nil) {
|
||||||
@@ -10458,10 +10498,15 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
|||||||
// register RECV reads AX/DX/CX at 8-byte
|
// register RECV reads AX/DX/CX at 8-byte
|
||||||
// granularity — size via structabisize (cstage
|
// granularity — size via structabisize (cstage
|
||||||
// SSoT lu->size, check.c:760; cgen.c:7720
|
// 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
|
if (n.op == syntax.tkind.TK_ASSIGN
|
||||||
&& n.rhs != nil
|
&& 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 != nil
|
||||||
&& fi.tnode.kind == syntax.nkind.N_TNAME
|
&& fi.tnode.kind == syntax.nkind.N_TNAME
|
||||||
&& aliasprimsize(c, fi.tnode.str) == 0) {
|
&& 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).
|
// directly at (lc.off+fi.foff)(BP).
|
||||||
// N_STRUCTLIT: field-walk; each inner
|
// N_STRUCTLIT: field-walk; each inner
|
||||||
// field stored at +fi.foff+inner_foff(BP).
|
// 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
|
if (n.op == syntax.tkind.TK_ASSIGN
|
||||||
&& n.rhs != nil
|
&& 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 != nil
|
||||||
&& fi.tnode.kind == syntax.nkind.N_TNAME
|
&& fi.tnode.kind == syntax.nkind.N_TNAME
|
||||||
&& aliasprimsize(c, fi.tnode.str) == 0) {
|
&& aliasprimsize(c, fi.tnode.str) == 0) {
|
||||||
|
|||||||
@@ -3013,6 +3013,42 @@ fn aggargfloatstop(n: *syntax.node) bool = {
|
|||||||
return false;
|
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
|
// structfloatclass — SysV per-eightbyte classification for the #165
|
||||||
// float-bearing-struct param case (param twin of #171's struct return;
|
// float-bearing-struct param case (param twin of #171's struct return;
|
||||||
// classifies per-eightbyte, not #163's per-element). Returns 0 when the
|
// classifies per-eightbyte, not #163's per-element). Returns 0 when the
|
||||||
|
|||||||
77
test/lang/struct_unwrap_test.ww
Normal file
77
test/lang/struct_unwrap_test.ww
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// struct_unwrap_test — #12: an aggregate (struct) success-variant unwrap
|
||||||
|
// `(S|e)!` / `r?` dropped payload words (SILENT, both stages, byte-id-BLIND).
|
||||||
|
// A struct success variant rides the in-cap {AX=w0,DX=w1,CX=w2} GP ABI — a
|
||||||
|
// DIFFERENT shuffle from #6's str/slice {ptr,len,cap} header. Two layers:
|
||||||
|
// L1 PRODUCER (cgtrytaggedshift / cgen.c N_TRYPROP+N_TRYUNW): a struct/array
|
||||||
|
// success variant matched no arm and fell to the bare `MOVQ DX,AX`,
|
||||||
|
// materialising only w0 — w1/w2 (in CX/R8) dropped.
|
||||||
|
// L2 CONSUMER (the aggregate-STORE arms): gated rhs.kind==N_CALL and stored
|
||||||
|
// one word for an N_TRYUNW/N_TRYPROP rhs — admitting the unwrap kinds
|
||||||
|
// reuses the #10-correct scratch materialise.
|
||||||
|
// Both layers are silent both-stage, so the byte-id gate is BLIND to the drop —
|
||||||
|
// these VALUE asserts are the sole tooth. Reverting either stage's L2 relax (or
|
||||||
|
// the shared L1 producer) reverts the dropped word to the poison sentinel 9 and
|
||||||
|
// reddens. POISON-SEED every dest member to 9 so a dropped w1/w2 reads 9 != the
|
||||||
|
// expected nonzero value (a fresh-frame 0 would also mismatch, but 9 is robust).
|
||||||
|
//
|
||||||
|
// Coverage = the three SILENT store shapes (arr-elem C2c, single-dot field
|
||||||
|
// direct + via-ptr, indexed-field #11) for a clean 2-eightbyte s2 (w1 tooth)
|
||||||
|
// plus a 3-eightbyte s24 (w2 tooth — exercises the producer's R8->CX shift).
|
||||||
|
// The sub-8-tail-THROUGH-unwrap case (#10's tail) is NOT pinnable: an ARRAY
|
||||||
|
// success variant is checker-rejected (`([7]i16|e)`, task #5/#60) and a sub-8-
|
||||||
|
// tail STRUCT success variant trips a SEPARATE pre-existing bug — the union-
|
||||||
|
// return struct-lit fill overruns saved BP when the variant ends at the frame
|
||||||
|
// edge (g's MOVQ writes past the 14B struct into [BP]); filed for the lead.
|
||||||
|
|
||||||
|
package struct_unwrap_test;
|
||||||
|
|
||||||
|
type e = !i32;
|
||||||
|
type s2 = struct { a: i64, b: i64 }; // 2 eightbytes: w0=a, w1=b
|
||||||
|
type s24 = struct { a: i64, b: i64, c: i64 }; // 3 eightbytes: w0,w1,w2
|
||||||
|
type box = struct { f: s2, g: i64 };
|
||||||
|
|
||||||
|
fn mk2() (s2 | e) = { return s2 { a = 111i64, b = 222i64 }; };
|
||||||
|
fn mk24() (s24 | e) = { return s24 { a = 111i64, b = 222i64, c = 333i64 }; };
|
||||||
|
|
||||||
|
@test fn arr_elem() void = { // L2 site: C2c whole-element arr[i]=mk()!
|
||||||
|
let arr: [2]s2;
|
||||||
|
arr[1].a = 9i64; arr[1].b = 9i64; // poison: w1 sentinel 9 != 222
|
||||||
|
arr[1] = mk2()!;
|
||||||
|
assert(arr[1].a == 111i64);
|
||||||
|
assert(arr[1].b == 222i64); // w1 — the dropped-word tooth
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn arr_elem_w2() void = { // C2c, 3 eightbytes — w2 (R8->CX) tooth
|
||||||
|
let arr: [2]s24;
|
||||||
|
arr[1].a = 9i64; arr[1].b = 9i64; arr[1].c = 9i64;
|
||||||
|
arr[1] = mk24()!;
|
||||||
|
assert(arr[1].a == 111i64);
|
||||||
|
assert(arr[1].b == 222i64);
|
||||||
|
assert(arr[1].c == 333i64); // w2 tooth (>16B producer shift)
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn field_direct() void = { // L2 site: single-dot direct b.f=mk()!
|
||||||
|
let b: box = box { f = s2{a=9i64,b=9i64}, g = 7i64 };
|
||||||
|
b.f = mk2()!;
|
||||||
|
assert(b.f.a == 111i64);
|
||||||
|
assert(b.f.b == 222i64); // w1 tooth
|
||||||
|
assert(b.g == 7i64); // neighbour field intact
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn field_via_ptr() void = { // L2 site: single-dot via *struct p.f=mk()!
|
||||||
|
let b: box = box { f = s2{a=9i64,b=9i64}, g = 7i64 };
|
||||||
|
let p: *box = &b;
|
||||||
|
p.f = mk2()!;
|
||||||
|
assert(b.f.a == 111i64);
|
||||||
|
assert(b.f.b == 222i64); // w1 tooth
|
||||||
|
assert(b.g == 7i64);
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn field_of_indexed() void = { // L2 site: #11 indexed-field arr[i].f=mk()!
|
||||||
|
let arr: [2]box;
|
||||||
|
arr[1].g = 5i64; arr[1].f.a = 9i64; arr[1].f.b = 9i64;
|
||||||
|
arr[1].f = mk2()!;
|
||||||
|
assert(arr[1].f.a == 111i64);
|
||||||
|
assert(arr[1].f.b == 222i64); // w1 tooth
|
||||||
|
assert(arr[1].g == 5i64); // neighbour field intact
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user