cgen: #49 aggregate-ASSIGN word0-only family — one mem-to-mem funnel (cg_aggcopy), both stages
Whole-aggregate reassignment `b = a` fell to the N_ASSIGN scalar tail
and copied ONE MOVQ — word 0 of any struct/array/tuple — in BOTH
stages, byte-identical, gate-blind (ken f49_min asm proof; latent
because lib style is let-init, whose #265/#268 copy is full-width).
Same class at three more positions: struct-lit FIELD init from an
ident source (`outer{.., r = r}`, the #38 non-tagged half), the deref
place `*p = s` (#31-A), and the module-let global `g = a` / `g = pt{..}`.
Fix: extract the C1.25 assign-resolver word-copy tail verbatim into
cg_aggcopy/aggcopy — the ONE place-resolved (SI)->(BX) aggregate copy
— and wire it at the N_ASSIGN ident-aggregate arm (local + global),
the deref-place divert into the existing resolver aggregate arm, and
the structlit-fill aggregate-field arm, all fed by aggarg_srcaddr
(the closed #265/#268 dispatch). The new arms key on the FULL alias
chase (type_chase_named / chased stamped tinfo, the #22 precedent) in
BOTH stages — the region's single-peel `lu`/`fu` would miss
`type b = a; type a = struct` on cstage while the wwstage twin fired
(ken R1, gA3b: master cs ran the word0 corruption, exit 2; now 0).
Non-addressable aggregate rhs (tuple-lit, unhandled call shapes) dies
LOUD (rule 7) instead of silently truncating: #31-E `*p = (3,4)` and
#31-G's deref flavor `*p = mk()` are now loud both stages (the INDEX
flavor `a[i] = mk()` stays in the legacy INDEX arm — receive
machinery, not this funnel; still filed under #31). #31-B rides: the
cstage-only <=24B gate before cg_structlit_fill_bp is lifted (the
wwstage twin never gated — a >24B literal reassign was
cs-zero/ww-filled, rule-10 break). Global structlit reassign rides
the existing DST_GLOBAL fill machinery.
Unsplit (rule 11): the assign arm, fill arm and deref divert all
route through the one new funnel (cg_aggcopy + aggarg_srcaddr) in
both stages; splitting by site or by stage would ship a transient
cs!=ww (gate-red) or a funnel with no consumer.
941 t2_reject_chain_arg: the row's tuple-LITERAL field fill now louds
at the #49 fill arm before reaching the pinned ARG-site reject; the
fill switches to an ident source (newly working via the fill arm) so
the original arg-site pin still fires.
test/wcc/812_agg_assign_width.c: 17 runtime-readback rows (the only
oracle for a gate-blind class) + per-row asm byte-id; every row fails
at 7545bf7 (ken matrix f49_min/f49b/f49c/fA_16b/f38b + gA3b/gA6 +
impl-A probes; reviewer-A re-probed 5 rows + the gA2 12B shape at a
master git-archive scratch). Alias rows use FIELD-WISE init: the
struct-LIT spelling louds earlier at the pre-existing task-#7
aggregate-let bound on wwstage (the #5 alias-arc's hole, not this
funnel's). Reviewer-A amendment (test-only, K5 self-certify): add
the ken-gA2 odd-size row (12B {u32,u32,u32}, maxalign 4 — pins the
MOVL tail; master both stages exit 3) and gA4's neighbor guards on
the deref row, completing ken's validated matrix in the committed
suite.
This commit is contained in:
248
cmd/w6c/cgen.c
248
cmd/w6c/cgen.c
@@ -2073,6 +2073,37 @@ aggarg_srcaddr(Cg *c, Node *src, int dst, Local *locals)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* cg_aggcopy — the ONE place-resolved mem-to-mem aggregate copy:
|
||||
* sz bytes (SI) → (BX) via AX, a MOVQ run plus a 4/2/1 sized tail.
|
||||
* Extracted verbatim from the C1.25 assign-resolver tail so every
|
||||
* aggregate copy position (resolver field store, #49 ident reassign,
|
||||
* #49 structlit fill-field) funnels through one loop — close-by-
|
||||
* construction, no per-site width logic to skew. */
|
||||
static void
|
||||
cg_aggcopy(Cg *c, int sz)
|
||||
{
|
||||
int k = 0;
|
||||
for (; k + 8 <= sz; k += 8) {
|
||||
ins2(c, A_MOVQ, amem(D_SI, k), areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BX, k));
|
||||
}
|
||||
if (k + 4 <= sz) {
|
||||
ins2(c, A_MOVL, amem(D_SI, k), areg(D_AX));
|
||||
ins2(c, A_MOVL, areg(D_AX), amem(D_BX, k));
|
||||
k += 4;
|
||||
}
|
||||
if (k + 2 <= sz) {
|
||||
ins2(c, A_MOVW, amem(D_SI, k), areg(D_AX));
|
||||
ins2(c, A_MOVW, areg(D_AX), amem(D_BX, k));
|
||||
k += 2;
|
||||
}
|
||||
if (k + 1 <= sz) {
|
||||
ins2(c, A_MOVB, amem(D_SI, k), areg(D_AX));
|
||||
ins2(c, A_MOVB, areg(D_AX), amem(D_BX, k));
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* cgplaceaddr — compute the ADDRESS of an arbitrary place (lvalue)
|
||||
* expression into dst_reg; returns 1 when the shape is wired, 0
|
||||
* otherwise (the caller loud-stops — rule 7, never a silent drop).
|
||||
@@ -3111,6 +3142,43 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit,
|
||||
}
|
||||
continue;
|
||||
}
|
||||
/* #49 (f38b/x5f-h): an aggregate field from an ADDRESSABLE
|
||||
* source expr — `outer{.., r = r}` — fell to the scalar
|
||||
* tail below and stored word0 only. Funnel: source address
|
||||
* via aggarg_srcaddr (SI), field address via LEAQ (BX —
|
||||
* loaded AFTER the source walk, which clobbers BX/AX), then
|
||||
* cg_aggcopy. Non-addressable aggregate sources (tuple-lit,
|
||||
* >24B/odd-tail call) die loud — pre-#49 they were the same
|
||||
* silent word0 (rule 7). FULL alias chase (#22 precedent),
|
||||
* not the region's single-peel `fu` — the wwstage twin
|
||||
* full-chases the stamped tinfo; a single peel here would
|
||||
* miss `type b = a; type a = struct` and silently diverge. */
|
||||
Type *fagg = type_chase_named(ft);
|
||||
if (fagg && (fagg->kind == TY_STRUCT || fagg->kind == TY_ARRAY
|
||||
|| fagg->kind == TY_TUPLE)) {
|
||||
if (!aggarg_srcaddr(c, f->lhs, D_SI, *locals_p))
|
||||
fatal("structlit fill: aggregate field '%s' "
|
||||
"from a non-addressable source unwired "
|
||||
"(task #49/rule-7)",
|
||||
f->str ? f->str : "?");
|
||||
if (mode == DST_BP)
|
||||
ins2(c, A_LEAQ, amem(D_BP, disp + (int)foff),
|
||||
areg(D_BX));
|
||||
else {
|
||||
if (mode == DST_PTR_LOCAL)
|
||||
ins2(c, A_MOVQ, amem(D_BP, srcoff),
|
||||
areg(D_BX));
|
||||
else
|
||||
ins2(c, A_LEAQ, masym(c, name),
|
||||
areg(D_BX));
|
||||
if (disp + (int)foff != 0)
|
||||
ins2(c, A_ADDQ,
|
||||
aimm(disp + (int)foff),
|
||||
areg(D_BX));
|
||||
}
|
||||
cg_aggcopy(c, (int)fagg->size);
|
||||
continue;
|
||||
}
|
||||
cgexpr(c, f->lhs, *locals_p);
|
||||
/* For non-BP modes, cgexpr just clobbered BX; reload it
|
||||
* before the store. */
|
||||
@@ -6073,14 +6141,26 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
"tagged GLOBAL lvalue unwired");
|
||||
}
|
||||
}
|
||||
/* #49 (#31-A fold): an aggregate pointee diverts the whole
|
||||
* deref-assign to the resolver aggregate arm below — the
|
||||
* scalar tail here stored ONE word of `*p = s` (#31-A);
|
||||
* tuple-lit (#31-E) and call (#31-G) rhs now die loud there
|
||||
* instead of silently truncating. str/slice pointees keep
|
||||
* their 3-word arm here (byte-id-pinned). */
|
||||
int deref_agg = 0;
|
||||
if (n->lhs && n->lhs->kind == N_UN && n->lhs->op == TK_STAR
|
||||
&& n->op == TK_ASSIGN) {
|
||||
Type *du = type_chase_named(n->lhs->type);
|
||||
if (du && (du->kind == TY_STRUCT
|
||||
|| du->kind == TY_ARRAY
|
||||
|| du->kind == TY_TUPLE))
|
||||
deref_agg = 1;
|
||||
}
|
||||
/* Deref-target assignment `*p = v;`. The size of the store is
|
||||
* determined by the type *p points at; the pointer expression
|
||||
* is evaluated after the value so we don't need to spill BX.
|
||||
* Retained gap: an aggregate >8B rhs (ident, tuple-lit, call)
|
||||
* truncates to one word here — task #31 A/E/G; struct-lit
|
||||
* diverts at the place_slit gate, array-lit dies loud (#32). */
|
||||
* is evaluated after the value so we don't need to spill BX. */
|
||||
if (n->lhs && n->lhs->kind == N_UN && n->lhs->op == TK_STAR
|
||||
&& n->op == TK_ASSIGN && !place_slit) {
|
||||
&& n->op == TK_ASSIGN && !place_slit && !deref_agg) {
|
||||
Type *pt = n->lhs->lhs ? n->lhs->lhs->type : NULL;
|
||||
Type *pu = (pt && pt->kind == TY_NAMED) ? pt->under : pt;
|
||||
Type *vt = (pu && pu->kind == TY_PTR) ? pu->sub : NULL;
|
||||
@@ -6259,37 +6339,61 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Struct local reassignment: `s = expr;` where s is
|
||||
* a TY_STRUCT local of size <=24B. Two rhs shapes,
|
||||
* mirroring cglet's N_STRUCTLIT and the call-result
|
||||
* branch above:
|
||||
* - N_STRUCTLIT: walk fields, store at off+foff
|
||||
* directly (same shape as the let-init branch).
|
||||
* - N_CALL: cgexpr → AX/DX/CX, sized stores per the
|
||||
* same ASYMMETRY rules documented at the N_LET
|
||||
* receive site (MOVQ for full 8B chunks plus
|
||||
* MOVL/MOVW/MOVB tail). The struct-IDENT word-copy
|
||||
* rhs shape (s = p) is left unwired; #5 is scoped to
|
||||
* the receive side of #4's cgreturn (calls + literals).
|
||||
* Sizes >24B and non-{0,1,2,4}-byte tails fall through
|
||||
* to the existing scalar path. */
|
||||
if (lu && (lu->kind == TY_STRUCT || lu->kind == TY_ARRAY)
|
||||
&& (int)lu->size <= 24) {
|
||||
/* #49: aggregate (struct/array/tuple) IDENT
|
||||
* reassignment — `s = expr;`. Literal and call rhs
|
||||
* keep their dedicated receive arms; every OTHER rhs
|
||||
* is an addressable source and funnels through the
|
||||
* ONE mem-to-mem copy (aggarg_srcaddr → SI, dst
|
||||
* address → BX, cg_aggcopy — the let-init copy's
|
||||
* assign-position twin). Pre-#49 any shape that
|
||||
* missed an arm fell to the scalar tail below and
|
||||
* word0-copied: `b = a` lost every byte past 8 (ken
|
||||
* f49_min; latent because lib style is let-init).
|
||||
* The block never falls through to the scalar tail
|
||||
* (rule 7). Keyed on the FULL alias chase (the #22
|
||||
* type_chase_named precedent), NOT the region's
|
||||
* single-peel `lu` — `type b = a; type a = struct`
|
||||
* left a TY_NAMED after one peel, missing the arm
|
||||
* (the wwstage twin full-chases the stamped tinfo;
|
||||
* a single peel here would silently diverge). */
|
||||
Type *au = type_chase_named(lt);
|
||||
if (au && (au->kind == TY_STRUCT || au->kind == TY_ARRAY
|
||||
|| au->kind == TY_TUPLE)) {
|
||||
int off = localfind(locals, n->lhs->str);
|
||||
if (off != 0) {
|
||||
int sz = (int)lu->size;
|
||||
if (n->rhs && n->rhs->kind == N_STRUCTLIT) {
|
||||
int sz = (int)au->size;
|
||||
int isglob = off == 0 && let_islet(n->lhs->str);
|
||||
if (off == 0 && !isglob)
|
||||
fatal("unsupported assign target: "
|
||||
"unresolved identifier '%s'",
|
||||
n->lhs->str);
|
||||
if (n->rhs && n->rhs->kind == N_STRUCTLIT) {
|
||||
if (off != 0) {
|
||||
/* Delegate to the shared BP-relative
|
||||
* structlit fill helper. Handles
|
||||
* TK_ELLIPSIS autofill, tagged fields,
|
||||
* float/scalar stores, AND nested
|
||||
* struct-typed structlit values via
|
||||
* recursion (#17 silent-zero fix). */
|
||||
cg_structlit_fill_bp(c, &locals, lu,
|
||||
* recursion (#17 silent-zero fix).
|
||||
* #31-B: the pre-#49 ≤24B gate is
|
||||
* lifted — the fill walks fields at
|
||||
* any size; the wwstage twin never
|
||||
* gated, so a >24B literal reassign
|
||||
* was cs-zero/ww-filled (rule-10). */
|
||||
cg_structlit_fill_bp(c, &locals, au,
|
||||
n->rhs, off);
|
||||
break;
|
||||
}
|
||||
if (n->rhs && n->rhs->kind == N_CALL
|
||||
/* Global structlit reassign rides the
|
||||
* DST_GLOBAL fill (the N_DOT global arms'
|
||||
* machinery); pre-#49 it fell to the
|
||||
* scalar tail and zeroed word0 only. */
|
||||
cg_structlit_fill(c, &locals, au, n->rhs,
|
||||
DST_GLOBAL, 0, n->lhs->str, 0);
|
||||
break;
|
||||
}
|
||||
if (n->rhs && n->rhs->kind == N_CALL) {
|
||||
if (off != 0 && au->kind != TY_TUPLE
|
||||
&& sz <= 24
|
||||
&& (sz % 8 == 0 || sz % 8 == 1
|
||||
|| sz % 8 == 2
|
||||
|| sz % 8 == 4)) {
|
||||
@@ -6309,10 +6413,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (lu->kind == TY_ARRAY
|
||||
&& let_islet(n->lhs->str)
|
||||
&& n->rhs && n->rhs->kind == N_CALL
|
||||
&& n->op == TK_ASSIGN) {
|
||||
if (isglob && au->kind == TY_ARRAY
|
||||
&& sz <= 24) {
|
||||
/* #272: `g = f();` where g is a GLOBAL
|
||||
* aggregate ≤24B. The callee leaves the result
|
||||
* in AX/DX/CX (#272 reg-return); the scalar IDENT
|
||||
@@ -6323,28 +6425,45 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* global arm above and the #220 sret-to-symbol path.
|
||||
* #276: this arm is TY_ARRAY-only — a ≤24B STRUCT
|
||||
* global receive can be float-class (X0/X1, not
|
||||
* AX/DX/CX) so it stays at its pre-existing symmetric
|
||||
* fall-through; closing it needs struct_float_class
|
||||
* here. No consumer. Arrays are never float-class, so
|
||||
* AX/DX/CX is always correct for this arm. */
|
||||
int sz = (int)lu->size;
|
||||
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));
|
||||
* AX/DX/CX) so it has no receive here; pre-#49
|
||||
* it fell through symmetric-silent, now it dies
|
||||
* loud below. No consumer. Arrays are never
|
||||
* float-class, so AX/DX/CX is always correct
|
||||
* 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));
|
||||
}
|
||||
break;
|
||||
}
|
||||
fatal("assign: aggregate call receive "
|
||||
"shape unwired (task #49/#276/"
|
||||
"rule-7)");
|
||||
}
|
||||
if (aggarg_srcaddr(c, n->rhs, D_SI, locals)) {
|
||||
if (off != 0)
|
||||
ins2(c, A_LEAQ, amem(D_BP, off),
|
||||
areg(D_BX));
|
||||
else
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, n->lhs->str),
|
||||
areg(D_BX));
|
||||
cg_aggcopy(c, sz);
|
||||
break;
|
||||
}
|
||||
fatal("assign: aggregate rhs shape unwired "
|
||||
"(task #49/rule-7)");
|
||||
}
|
||||
}
|
||||
/* F6 (cgplaceaddr, commit C1): an N_DOT lvalue none of the
|
||||
@@ -6449,34 +6568,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (!placed)
|
||||
fatal("unsupported assign target "
|
||||
"shape");
|
||||
int k = 0;
|
||||
for (; k + 8 <= fsz; k += 8) {
|
||||
ins2(c, A_MOVQ, amem(D_SI, k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BX, k));
|
||||
}
|
||||
if (k + 4 <= fsz) {
|
||||
ins2(c, A_MOVL, amem(D_SI, k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVL, areg(D_AX),
|
||||
amem(D_BX, k));
|
||||
k += 4;
|
||||
}
|
||||
if (k + 2 <= fsz) {
|
||||
ins2(c, A_MOVW, amem(D_SI, k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVW, areg(D_AX),
|
||||
amem(D_BX, k));
|
||||
k += 2;
|
||||
}
|
||||
if (k + 1 <= fsz) {
|
||||
ins2(c, A_MOVB, amem(D_SI, k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVB, areg(D_AX),
|
||||
amem(D_BX, k));
|
||||
k += 1;
|
||||
}
|
||||
cg_aggcopy(c, fsz);
|
||||
break;
|
||||
}
|
||||
if (fu && (fu->kind == TY_STR
|
||||
|
||||
Reference in New Issue
Block a user