cgen: widen the narrow-tail aggregate-register store to full sz<=24 at every routed site (#14)
Completes the #14 close-by-construction begun by the helper extraction (e7fefa3): every <=24B aggregate register-store now routes through cg_agg_reg_store/cgaggregstore and handles all tail sizes. B/C/D/G fix a silent both-stage drop of a 3/5/6/7-byte tail (byte-id-blind: both stages dropped identically, so the gate could not see it). A/F enable a previously loud-rejected shape (a cgen backend gap, not a type rejection; harec accepts and lowers it). G (global g=f() array) routes symmetrically, dest_padded=false. The #11 arr[i].f scratch loops fold into the helper (dest_padded=true, byte-id zero-change), completing the grep-audit. Pins value-assert each eightbyte (the class is byte-id-blind) and redden under each stage's independent revert; site H's 3/5/6/7 let-receive stays a loud fatal (#22).
This commit is contained in:
110
cmd/w6c/cgen.c
110
cmd/w6c/cgen.c
@@ -3507,17 +3507,13 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit,
|
||||
* fallthrough below silently dropped past the first
|
||||
* qword for any fsz > 8 (only AX got stored).
|
||||
*
|
||||
* Sized stores: MOVQ for full 8B chunks plus a sized tail
|
||||
* (MOVL/MOVW/MOVB) by `tail = fsz%8`. Mirrors #4's receive
|
||||
* shape at the N_LET / N_ASSIGN call-rhs sites; the
|
||||
* MOVW-for-tail==2 emission only fires on shapes that
|
||||
* didn't compile before, so no #13 byte-identity concern.
|
||||
* The choke-point stores MOVQ for full 8B chunks plus a
|
||||
* sized tail (MOVL/MOVW/MOVB) by `tail = fsz%8`.
|
||||
*
|
||||
* Guard `fsz <= 24 && fsz%8 ∈ {0,1,2,4}` matches #4's
|
||||
* cgreturn ABI: >24B falls through (sret deferred);
|
||||
* fsz%8 ∈ {3,5,6,7} would need shift-store and is also
|
||||
* unsupported by #4 — falls through to the existing
|
||||
* AX-only wrongness (consistent, tracked as follow-up).
|
||||
* Guard `fsz <= 24` (#14 widened from {0,1,2,4}): the
|
||||
* choke-point handles every in-cap tail incl. 3/5/6/7 (its
|
||||
* non-padded scratch detour, dest_padded=0 — a struct-lit
|
||||
* field is packed); >24B still falls through (sret deferred).
|
||||
*
|
||||
* INVARIANT: between cgexpr(N_CALL) and the AX/DX/CX
|
||||
* stores below, NO instruction may touch AX/DX/CX. The
|
||||
@@ -3525,9 +3521,7 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit,
|
||||
* added here will silently corrupt the return value. */
|
||||
if (fu && fu->kind == TY_STRUCT
|
||||
&& f->lhs && f->lhs->kind == N_CALL
|
||||
&& fsz <= 24
|
||||
&& (fsz % 8 == 0 || fsz % 8 == 1
|
||||
|| fsz % 8 == 2 || fsz % 8 == 4)) {
|
||||
&& fsz <= 24) {
|
||||
cgexpr(c, f->lhs, *locals_p);
|
||||
if (mode == DST_PTR_LOCAL)
|
||||
ins2(c, A_MOVQ, amem(D_BP, srcoff),
|
||||
@@ -5266,8 +5260,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* {AX,DX,CX} producer-shift materialise. cgplaceaddr clobbers
|
||||
* AX/CX, so it runs BEFORE cgexpr(rhs) and the address is saved
|
||||
* across the call. In-cap struct field only (size<=24, #12
|
||||
* scope); a float-bearing / over-cap success variant loud-stops
|
||||
* at the producer. Local/via-ptr single-dot stays on #12. */
|
||||
* scope, #14 widened from {0,1,2,4} — the choke-point handles
|
||||
* every tail incl. 3/5/6/7 via its non-padded scratch detour,
|
||||
* dest_padded=0 for a packed/chained field); a float-bearing /
|
||||
* over-cap success variant loud-stops at the producer.
|
||||
* Local/via-ptr single-dot stays on #12. */
|
||||
if (n->op == TK_ASSIGN && n->lhs && n->lhs->kind == N_DOT
|
||||
&& n->lhs->lhs
|
||||
&& n->rhs && (n->rhs->kind == N_TRYUNW
|
||||
@@ -5285,9 +5282,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
Type *fu = type_chase_named(n->lhs->type);
|
||||
if ((chained || globalbase) && fu
|
||||
&& fu->kind == TY_STRUCT
|
||||
&& (int)fu->size <= 24
|
||||
&& (fu->size % 8 == 0 || fu->size % 8 == 1
|
||||
|| fu->size % 8 == 2 || fu->size % 8 == 4)) {
|
||||
&& (int)fu->size <= 24) {
|
||||
int ssz = (int)fu->size;
|
||||
if (!cgplaceaddr(c, n->lhs, D_BX, locals))
|
||||
fatal("#16: global/chained aggregate "
|
||||
@@ -5514,18 +5509,17 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* / 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. */
|
||||
* producer. #14 widened size<=24 from {0,1,2,4}: the
|
||||
* choke-point handles every tail incl. 3/5/6/7 via its
|
||||
* non-padded scratch detour (dest_padded=0 — a packed
|
||||
* single-dot field). */
|
||||
if (n->op == TK_ASSIGN && str_fu
|
||||
&& str_fu->kind == TY_STRUCT
|
||||
&& (int)str_fu->size <= 24
|
||||
&& 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 == 1
|
||||
|| str_fu->size % 8 == 2
|
||||
|| str_fu->size % 8 == 4)) {
|
||||
&& !is_global))) {
|
||||
int ssz = (int)str_fu->size;
|
||||
cgexpr(c, n->rhs, locals);
|
||||
int base_reg, base_disp;
|
||||
@@ -6002,33 +5996,16 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* pad and the scratch->dest copy reads only fsz
|
||||
* bytes, so every in-cap tail (incl. 3/5/6/7) is
|
||||
* exact without an immediate-shift cascade (w6a
|
||||
* has no SHRQ $imm). Shares the C2c whole-element
|
||||
* materialise (:7008). #10; mirrors wwstage
|
||||
* cgenexpr.ww. */
|
||||
* has no SHRQ $imm). #10. #14: folded into the
|
||||
* choke-point (dest_padded=1 — the scratch IS a
|
||||
* ceil-8 slot so the tail eightbyte over-store
|
||||
* lands in its pad; byte-identical to the prior
|
||||
* inline form). Mirrors wwstage cgenexpr.ww. */
|
||||
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 == 1)
|
||||
? A_MOVB : (tail11
|
||||
== 2) ? A_MOVW
|
||||
: (tail11 == 4)
|
||||
? A_MOVL : A_MOVQ;
|
||||
ins2(c, op11,
|
||||
areg(regs11[full11]),
|
||||
amem(D_BP, scr11
|
||||
+ full11 * 8));
|
||||
}
|
||||
cg_agg_reg_store(c, &locals,
|
||||
D_BP, scr11, fsz, 1);
|
||||
cgexpr(c, idx, locals);
|
||||
if (esz > 1) {
|
||||
ins2(c, A_MOVQ,
|
||||
@@ -6736,12 +6713,14 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* - N_STRUCTLIT (added with #5): field-by-field
|
||||
* store; via_cx reloads BX before each store
|
||||
* so cgexpr can clobber AX/BX between fields.
|
||||
* #14 widened fsz<=24 from {0,1,2,4}: the choke-
|
||||
* point handles every tail incl. 3/5/6/7 via its
|
||||
* non-padded scratch detour (dest_padded=0 — a
|
||||
* packed chained field).
|
||||
*/
|
||||
if (fu && fu->kind == TY_STRUCT
|
||||
&& fsz <= 24
|
||||
&& n->rhs && n->rhs->kind == N_CALL
|
||||
&& (fsz % 8 == 0 || fsz % 8 == 1
|
||||
|| fsz % 8 == 2 || fsz % 8 == 4)) {
|
||||
&& n->rhs && n->rhs->kind == N_CALL) {
|
||||
cgexpr(c, n->rhs, locals);
|
||||
int base_reg, base_off;
|
||||
if (via_cx) {
|
||||
@@ -7943,11 +7922,13 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
break;
|
||||
}
|
||||
if (n->rhs && n->rhs->kind == N_CALL) {
|
||||
/* #14 widened sz<=24 from {0,1,2,4}: the
|
||||
* choke-point handles every in-cap tail incl.
|
||||
* 3/5/6/7 via its dest_padded=0 scratch detour
|
||||
* (always correct; kept symmetric with the
|
||||
* wwstage twin's local struct/array reassign). */
|
||||
if (off != 0 && au->kind != TY_TUPLE
|
||||
&& sz <= 24
|
||||
&& (sz % 8 == 0 || sz % 8 == 1
|
||||
|| sz % 8 == 2
|
||||
|| sz % 8 == 4)) {
|
||||
&& sz <= 24) {
|
||||
cgexpr(c, n->rhs, locals);
|
||||
cg_agg_reg_store(c, &locals, D_BP, off, sz, 0);
|
||||
break;
|
||||
@@ -7971,19 +7952,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* for this arm. */
|
||||
cgexpr(c, n->rhs, locals);
|
||||
ins2(c, A_LEAQ, masym(c, n->lhs->str), areg(D_DI));
|
||||
int regs[3] = { D_AX, D_DX, D_CX };
|
||||
int full = sz / 8;
|
||||
int tail = sz % 8;
|
||||
for (int i = 0; i < full; i++)
|
||||
ins2(c, A_MOVQ, areg(regs[i]),
|
||||
amem(D_DI, i * 8));
|
||||
if (tail > 0) {
|
||||
int op = (tail == 4) ? A_MOVL
|
||||
: (tail == 2) ? A_MOVW
|
||||
: A_MOVB;
|
||||
ins2(c, op, areg(regs[full]),
|
||||
amem(D_DI, full * 8));
|
||||
}
|
||||
/* #14: route through the choke-point so a
|
||||
* 3/5/6/7 tail is no longer dropped to a lone
|
||||
* MOVB (was silent both-stage). A global symbol
|
||||
* is non-padded => dest_padded=0 (its scratch
|
||||
* detour copies exactly `tail` bytes). */
|
||||
cg_agg_reg_store(c, &locals, D_DI, 0, sz, 0);
|
||||
break;
|
||||
}
|
||||
fatal("assign: aggregate call receive "
|
||||
|
||||
@@ -8471,19 +8471,17 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
if (fu != nil) { if (fu.kind == syntax.tykind.TY_STRUCT) {
|
||||
let ssz: i32 = fu.size: i32;
|
||||
if (ssz <= 24) {
|
||||
let tlm: i32 = ssz - (ssz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1 || tlm == 2 || tlm == 4) {
|
||||
if (!cgplaceaddr(c, lhs, "BX")) {
|
||||
let m16: str = "#16: global/chained aggregate unwrap field dest unresolved (cgplaceaddr)\n";
|
||||
os.write(2, m16.ptr, m16.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
cgaggregstore(c, "BX", 0, ssz, false);
|
||||
return;
|
||||
// #14: choke-point now stores every in-cap tail; 3/5/6/7 no longer dropped to a lone narrow MOV.
|
||||
if (!cgplaceaddr(c, lhs, "BX")) {
|
||||
let m16: str = "#16: global/chained aggregate unwrap field dest unresolved (cgplaceaddr)\n";
|
||||
os.write(2, m16.ptr, m16.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
cgaggregstore(c, "BX", 0, ssz, false);
|
||||
return;
|
||||
};
|
||||
}; };
|
||||
};
|
||||
@@ -10205,37 +10203,11 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
// materialise (:9100). #10; mirrors cstage cgen.c.
|
||||
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 = "MOVQ";
|
||||
if (tail11 == 1) { top11 = "MOVB"; }
|
||||
else { if (tail11 == 2) { top11 = "MOVW"; }
|
||||
else { if (tail11 == 4) { top11 = "MOVL"; }; }; };
|
||||
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");
|
||||
};
|
||||
// AX/DX/CX -> scratch: #14 folds this materialise into the choke-
|
||||
// point (dest_padded=true; the scratch IS a ceil-8 slot so the
|
||||
// tail eightbyte over-store lands in its pad — byte-identical to
|
||||
// the prior inline form).
|
||||
cgaggregstore(c, "BP", scr11, tsz, true);
|
||||
// &arr[i] -> BX (verbatim scalar arm)
|
||||
cgexpr(c, idx);
|
||||
if (esz > 1) {
|
||||
@@ -10620,16 +10592,13 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structabisize(ssi);
|
||||
if (ssz <= 24) {
|
||||
let tlm: i32 = ssz - (ssz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
cgaggregstore(c, "BX", fi.foff, ssz, false);
|
||||
return;
|
||||
};
|
||||
// #14: choke-point now stores every in-cap tail; 3/5/6/7 no longer dropped to a lone narrow MOV.
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
cgaggregstore(c, "BX", fi.foff, ssz, false);
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -10842,13 +10811,10 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structabisize(ssi);
|
||||
if (ssz <= 24) {
|
||||
let tlm: i32 = ssz - (ssz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
cgaggregstore(c, "BP", lc.off + fi.foff, ssz, false);
|
||||
return;
|
||||
};
|
||||
// #14: choke-point now stores every in-cap tail; 3/5/6/7 no longer dropped to a lone narrow MOV.
|
||||
cgexpr(c, n.rhs);
|
||||
cgaggregstore(c, "BP", lc.off + fi.foff, ssz, false);
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -11106,16 +11072,13 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
if (ssi != nil) {
|
||||
let ssz: i32 = structabisize(ssi);
|
||||
if (ssz <= 24) {
|
||||
let tlm: i32 = ssz - (ssz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
cgaggregstore(c, "BX", fi.foff, ssz, false);
|
||||
return;
|
||||
};
|
||||
// #14: choke-point now stores every in-cap tail; 3/5/6/7 no longer dropped to a lone narrow MOV.
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, bn);
|
||||
emitline("(SB), BX\n");
|
||||
cgaggregstore(c, "BX", fi.foff, ssz, false);
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -11778,24 +11741,21 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
// (cstage SSoT lu->size, check.c:760).
|
||||
let lsz: i32 = structabisize(lsi);
|
||||
if (lsz <= 24) {
|
||||
let tlm: i32 = lsz - (lsz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
if (viacx) {
|
||||
if (ptrroot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
// #14: choke-point now stores every in-cap tail; 3/5/6/7 no longer dropped to a lone narrow MOV.
|
||||
cgexpr(c, n.rhs);
|
||||
if (viacx) {
|
||||
if (ptrroot) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(rootoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, rootname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
if (viacx) { cgaggregstore(c, "BX", totaloff, lsz, false); } else { cgaggregstore(c, "BP", rootoff + totaloff, lsz, false); };
|
||||
return;
|
||||
};
|
||||
if (viacx) { cgaggregstore(c, "BX", totaloff, lsz, false); } else { cgaggregstore(c, "BP", rootoff + totaloff, lsz, false); };
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -12240,29 +12200,11 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), DI\n");
|
||||
let full: i32 = aggsz / 8;
|
||||
let tail: i32 = aggsz - full * 8;
|
||||
let i: i32 = 0;
|
||||
for (i < full) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitline(tupreg(i));
|
||||
emitline(", ");
|
||||
emitoff((i * 8): i64);
|
||||
emitline("(DI)\n");
|
||||
i += 1;
|
||||
};
|
||||
if (tail > 0) {
|
||||
let top: str = "MOVB";
|
||||
if (tail == 4) { top = "MOVL"; };
|
||||
if (tail == 2) { top = "MOVW"; };
|
||||
emitline("\t");
|
||||
emitline(top);
|
||||
emitline("\t");
|
||||
emitline(tupreg(full));
|
||||
emitline(", ");
|
||||
emitoff((full * 8): i64);
|
||||
emitline("(DI)\n");
|
||||
};
|
||||
// #14: route through the choke-point so a 3/5/6/7 tail is no
|
||||
// longer dropped to a lone MOVB (was silent both-stage). A global
|
||||
// symbol is non-padded => dest_padded=false (its scratch detour
|
||||
// copies exactly `tail` bytes).
|
||||
cgaggregstore(c, "DI", 0, aggsz, false);
|
||||
return;
|
||||
};
|
||||
};
|
||||
@@ -12534,13 +12476,10 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
};
|
||||
let lcsz: i32 = lcnsz;
|
||||
if (lcsz <= 24) {
|
||||
let tlm: i32 = lcsz - (lcsz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
cgaggregstore(c, "BP", off, lcsz, false);
|
||||
return;
|
||||
};
|
||||
// #14: choke-point now stores every in-cap tail; 3/5/6/7 no longer dropped to a lone narrow MOV.
|
||||
cgexpr(c, n.rhs);
|
||||
cgaggregstore(c, "BP", off, lcsz, false);
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -12571,13 +12510,10 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
|
||||
};
|
||||
};
|
||||
if (lcsz <= 24) {
|
||||
let tlm: i32 = lcsz - (lcsz / 8) * 8;
|
||||
if (tlm == 0 || tlm == 1
|
||||
|| tlm == 2 || tlm == 4) {
|
||||
cgexpr(c, n.rhs);
|
||||
cgaggregstore(c, "BP", off, lcsz, false);
|
||||
return;
|
||||
};
|
||||
// #14: choke-point now stores every in-cap tail; 3/5/6/7 no longer dropped to a lone narrow MOV.
|
||||
cgexpr(c, n.rhs);
|
||||
cgaggregstore(c, "BP", off, lcsz, false);
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -5325,19 +5325,16 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node,
|
||||
// silently dropped past the first qword for any
|
||||
// fsz > 8 (only AX got stored).
|
||||
//
|
||||
// Sized stores: MOVQ for full 8B chunks plus a
|
||||
// sized tail (MOVL/MOVW/MOVB) by `tail = fsz%8`.
|
||||
// Mirror of cstage cg_structlit_fill's #20 branch.
|
||||
// MOVW-for-tail==2 only fires on shapes that
|
||||
// didn't compile before, so no #13 byte-identity
|
||||
// concern.
|
||||
// The choke-point stores MOVQ for full 8B chunks
|
||||
// plus a sized tail (MOVL/MOVW/MOVB) by `tail =
|
||||
// fsz%8`. Mirror of cstage cg_structlit_fill's #20
|
||||
// branch.
|
||||
//
|
||||
// Guard `fsz <= 24 && fsz%8 ∈ {0,1,2,4}` matches
|
||||
// #4's cgreturn ABI: >24B falls through (sret
|
||||
// deferred); fsz%8 ∈ {3,5,6,7} would need shift-
|
||||
// store and is also unsupported by #4 — falls
|
||||
// through to the existing AX-only wrongness
|
||||
// (consistent, tracked as follow-up).
|
||||
// Guard `cfsz <= 24` (#14 widened from {0,1,2,4}):
|
||||
// the choke-point handles every in-cap tail incl.
|
||||
// 3/5/6/7 via its non-padded scratch detour
|
||||
// (dest_padded=false — a struct-lit field is
|
||||
// packed); >24B falls through (sret deferred).
|
||||
//
|
||||
// INVARIANT: between cgexpr(N_CALL) and the
|
||||
// AX/DX/CX stores below, NO instruction may touch
|
||||
@@ -5369,10 +5366,8 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node,
|
||||
// MOVQ+MOVL where cstage
|
||||
// writes MOVQ+MOVQ.
|
||||
let cfsz: i32 = structabisize(csi);
|
||||
let crem: i32 = cfsz - (cfsz / 8) * 8;
|
||||
if (cfsz <= 24) {
|
||||
if (crem == 0 || crem == 1
|
||||
|| crem == 2 || crem == 4) {
|
||||
// #14: choke-point now stores every in-cap tail; 3/5/6/7 no longer dropped to a lone narrow MOV.
|
||||
cgexpr(c, fieldnode.lhs);
|
||||
if (mode == 1) {
|
||||
emitline("\tMOVQ\t");
|
||||
@@ -5390,7 +5385,6 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *syntax.node,
|
||||
cgaggregstore(c, basereg, disp + fi.foff, cfsz, false);
|
||||
callwhole = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
138
test/lang/narrow_tail_widen_test.ww
Normal file
138
test/lang/narrow_tail_widen_test.ww
Normal file
@@ -0,0 +1,138 @@
|
||||
// narrow_tail_widen_test — #14 commit-2: the narrow-tail aggregate-register
|
||||
// materialise choke-point (cg_agg_reg_store / cgaggregstore), now reached by
|
||||
// the WIDENED gates. Before #14 these sites gated `tail%8 ∈ {0,1,2,4}` and a
|
||||
// 3/5/6/7-byte aggregate tail fell through to a lone narrow MOV (a SILENT
|
||||
// both-stage member drop — both cstage and wwstage emitted IDENTICALLY wrong
|
||||
// asm, so byte-id was GREEN and BLIND). Widening each gate to the full
|
||||
// `sz<=24` routes 3/5/6/7 through the choke-point's non-padded scratch detour
|
||||
// (dest_padded=false: MOVQ the tail eightbyte into a ceil-8 pad, then an
|
||||
// exact `tail`-byte aggcopy into the packed dest).
|
||||
//
|
||||
// Each @test POISON-SEEDS every dest eightbyte with a sentinel distinct from
|
||||
// every mk() value, performs the aggregate store, then value-asserts each
|
||||
// eightbyte INCLUDING the dropped 3/5/6/7 tail word. A drop leaves the poison
|
||||
// (mismatch). Where the dest is a PACKED field/element (C, D) a trailing
|
||||
// neighbour is sentinel-checked too: it pins dest_padded=FALSE (a wrong =true
|
||||
// would over-store the tail eightbyte and clobber the neighbour).
|
||||
//
|
||||
// Heterogeneous shapes per site + #111 (row-array @test blocked) => inline
|
||||
// @test fns, not a row table. A 14B struct/array (maxalign<=2) keeps a real
|
||||
// tail-6: an i64-bearing field pads to 16B (tail 0) and never trips it.
|
||||
|
||||
package narrow_tail_widen_test;
|
||||
|
||||
type t14 = struct { v: [7]i16 }; // 14B: full=1, tail=6
|
||||
type boxed = struct { f: t14, guard: i16 }; // f@0 (14B), guard@14
|
||||
type nested = struct { m: boxed }; // o.m.f chained dot
|
||||
type myerr = !i32; // site B unwrap error variant
|
||||
|
||||
fn mk14() t14 = {
|
||||
return t14 { v = [10i16, 20i16, 30i16, 40i16, 50i16, 60i16, 70i16] };
|
||||
};
|
||||
fn mk7arr() [7]i16 = {
|
||||
return [10i16, 20i16, 30i16, 40i16, 50i16, 60i16, 70i16];
|
||||
};
|
||||
fn mk14res() (t14 | myerr) = {
|
||||
return t14 { v = [10i16, 20i16, 30i16, 40i16, 50i16, 60i16, 70i16] };
|
||||
};
|
||||
|
||||
let gg: [7]i16 = [0i16, 0i16, 0i16, 0i16, 0i16, 0i16, 0i16]; // site G dest
|
||||
let gb: boxed = boxed { // site B dest
|
||||
f = t14 { v = [0i16, 0i16, 0i16, 0i16, 0i16, 0i16, 0i16] },
|
||||
guard = 0i16,
|
||||
};
|
||||
|
||||
fn poison7(p: *[7]i16) void = {
|
||||
let i: i32 = 0;
|
||||
for (i < 7) { p[i] = 0x7f7fi16; i += 1; };
|
||||
};
|
||||
|
||||
// Site F (cstage cgen.c N_ASSIGN local-ident array `g = call`; ww cgenexpr.ww
|
||||
// 12574). Whole local array reassign from an aggregate-returning call.
|
||||
@test fn f_local_array() void = {
|
||||
let g: [7]i16;
|
||||
poison7(&g);
|
||||
g = mk7arr();
|
||||
assert(g[0] == 10i16);
|
||||
assert(g[3] == 40i16);
|
||||
assert(g[4] == 50i16); // eb1 byte 0-1
|
||||
assert(g[5] == 60i16); // eb1 byte 2-3 — tail, dropped pre-#14
|
||||
assert(g[6] == 70i16); // eb1 byte 4-5 — tail, dropped pre-#14
|
||||
};
|
||||
|
||||
// Site F (struct twin; ww cgenexpr.ww 12537). Whole local struct reassign.
|
||||
@test fn f_local_struct() void = {
|
||||
let s: t14;
|
||||
poison7(&s.v);
|
||||
s = mk14();
|
||||
assert(s.v[0] == 10i16);
|
||||
assert(s.v[4] == 50i16);
|
||||
assert(s.v[5] == 60i16); // tail, dropped pre-#14
|
||||
assert(s.v[6] == 70i16); // tail, dropped pre-#14
|
||||
};
|
||||
|
||||
// Site G (cstage cgen.c N_ASSIGN global-ident array `g = call`, the inline
|
||||
// loop now routed; ww cgenexpr.ww 12231). Whole GLOBAL array reassign — G
|
||||
// had NO {0,1,2,4} gate, it dropped 3/5/6/7 via a lone MOVB.
|
||||
@test fn g_global_array() void = {
|
||||
poison7(&gg);
|
||||
gg = mk7arr();
|
||||
assert(gg[0] == 10i16);
|
||||
assert(gg[4] == 50i16);
|
||||
assert(gg[5] == 60i16); // tail, dropped pre-#14 (MOVB wrote only byte 0)
|
||||
assert(gg[6] == 70i16); // tail, dropped pre-#14
|
||||
};
|
||||
|
||||
// Site C (cstage cgen.c single-dot `b.f = call`; ww cgenexpr.ww 10845 local
|
||||
// twin). PACKED field => guard neighbour pins dest_padded=false.
|
||||
@test fn c_single_dot() void = {
|
||||
let b: boxed;
|
||||
poison7(&b.f.v);
|
||||
b.guard = 0x55aai16; // neighbour sentinel
|
||||
b.f = mk14();
|
||||
assert(b.f.v[0] == 10i16);
|
||||
assert(b.f.v[4] == 50i16);
|
||||
assert(b.f.v[5] == 60i16); // tail, dropped pre-#14
|
||||
assert(b.f.v[6] == 70i16); // tail, dropped pre-#14
|
||||
assert(b.guard == 0x55aai16); // intact: tail aggcopy is exactly 6 bytes
|
||||
};
|
||||
|
||||
// Site D (cstage cgen.c chained-dot terminal `o.m.f = call`; ww cgenexpr.ww
|
||||
// 11781). PACKED field of a nested struct => guard neighbour check.
|
||||
@test fn d_chained_dot() void = {
|
||||
let o: nested;
|
||||
poison7(&o.m.f.v);
|
||||
o.m.guard = 0x55aai16;
|
||||
o.m.f = mk14();
|
||||
assert(o.m.f.v[0] == 10i16);
|
||||
assert(o.m.f.v[4] == 50i16);
|
||||
assert(o.m.f.v[5] == 60i16); // tail, dropped pre-#14
|
||||
assert(o.m.f.v[6] == 70i16); // tail, dropped pre-#14
|
||||
assert(o.m.guard == 0x55aai16);
|
||||
};
|
||||
|
||||
// Site A (cstage cgen.c cg_structlit_fill nested-CALL field; ww cgenutil.ww
|
||||
// cgstructlitfill 5388). A struct-LITERAL whose field is an aggregate call.
|
||||
@test fn a_structlit_field() void = {
|
||||
let x: boxed = boxed { f = mk14(), guard = 999i16 };
|
||||
assert(x.f.v[0] == 10i16);
|
||||
assert(x.f.v[4] == 50i16);
|
||||
assert(x.f.v[5] == 60i16); // tail, dropped pre-#14
|
||||
assert(x.f.v[6] == 70i16); // tail, dropped pre-#14
|
||||
assert(x.guard == 999i16);
|
||||
};
|
||||
|
||||
// Site B (cstage cgen.c N_ASSIGN #16 global/chained `g.f = mk()!` unwrap; ww
|
||||
// cgenexpr.ww 8483). A GLOBAL struct's field unwrap-receive — pre-#16 the
|
||||
// generic single-word store DROPPED w1/w2; #16 routed it through the
|
||||
// {AX,DX,CX} materialise, #14 widens its tail to 3/5/6/7.
|
||||
@test fn b_global_unwrap() void = {
|
||||
poison7(&gb.f.v);
|
||||
gb.guard = 0x55aai16;
|
||||
gb.f = mk14res()!;
|
||||
assert(gb.f.v[0] == 10i16);
|
||||
assert(gb.f.v[4] == 50i16);
|
||||
assert(gb.f.v[5] == 60i16); // tail, dropped pre-#14
|
||||
assert(gb.f.v[6] == 70i16); // tail, dropped pre-#14
|
||||
assert(gb.guard == 0x55aai16);
|
||||
};
|
||||
Reference in New Issue
Block a user