wcc+w6c_ww: aggregate let/range element copies via cgplaceaddr (F5)
F5 (task #7): the N_LET aggregate-copy arm's source-addr enumeration
(cgen.c #265/#268) had TY_ARRAY-ident/N_DOT/N_INDEX bases but no
TY_SLICE base and no deref-spine shapes, so `let e: th = xs[0]` fell
out with havesrc=0 — cstage emitted NOTHING (slot uninitialised),
wwstage fell to its scalar default (8B truncation): gate-blind cs≠ww
(p6min13). Every remaining ADDRESSABLE rhs now resolves through
cgplaceaddr (the C1 resolver; enumerated arms dispatch first, their
asm untouched), and the arm closes by construction with a loud tail —
nothing below it can initialise a >8B struct/array slot, so any
unhandled rhs shape dies loud instead of silently. A pre-tail #38b
guard keeps the established `?`/`!`-on-sret loud-stop marker in
wwstage (mirror of cstage's pre-arm fatal; pre-fix that shape reached
the cgtryunw/cgtryprop gates which the tail now pre-empts in let
position).
Reviewer-C2 inheritance: `let c: capture = (*ts)[i].cap` (aggregate
leaf behind a deref spine) — wwstage's documented cgdot aggregate-leaf
loud is retired for let position (cglet routes the copy through the
resolver before cgexpr sees the leaf; the loud stays as the guard for
non-let expr positions), and cstage's silent no-copy on the same shape
is fixed by the same resolver fallback.
By-value RANGE payloads ride the same class: N_FORRANGE's single-bind
load truncated every aggregate element to one fldloadop word. Both
stages now word-copy the full element extent (MOVQ run + sized
MOVL/MOVW/MOVB tail, the #270-1b idiom) for esz > 8. wwstage esz is
re-keyed elemsizeof→elemsizeofc (the 8-sentinel hid struct elements
from the copy gate — the #8 named-narrow precedent), with a
stamped-slc.type_ fallback + element-tnode synthesis for non-ident
scrutinees (tinfo SSoT, #209/#211). The wwstage checker now binds the
ELEMENT type on single-bind ranges via a synthetic N_LET binder node
(mirror of cstage check.c N_FORRANGE scope_define(..., elem, ...));
pre-fix the binding's decl was the N_FORRANGE node itself, so any
field read off a by-value binding asserttyped-bailed. The checker
half folds in under rule 11 because the split is unsound in either
order: cgen-first is untestable (every field read off the binding
still bails), checker-first converts that loud bail into the 8B
SILENT truncation — only the pair closes the class.
FC0 graduates: regex.finish's by-value range over 24B charset elems
(non-ident scrutinee re.charsets) was the lib/regex byte-cmp's ONLY
hunk since fold-1 — cstage 8-of-24-byte copy + IMULQ $24 vs wwstage
1-byte MOVZBQ, runtime-masked by the no-op loop body. The byte-cmp is
now ZERO hunks (regex_test.combined.ww, w6c vs w6c_ww).
#36 disposition: NOT folded. p6min9/p6min10's remaining failure is the
struct-ident field rhs inside a struct LITERAL (cg_structlit_fill
under-copy) — a different choke-point from the let-copy source-addr
machinery; they still exit 4 here and stay blocked on #36 (read half
landed in C2).
Residual filed as task #43: an UNANNOTATED aggregate let
(`let e = xs[0]`) still skips the wwstage arm (aggn/letslotsize are
annotation-keyed; cstage keys the stamped n->type and now full-copies)
— cs≠ww on that shape remains, #38-family. A landmine comment in
test 805 marks the gap.
test 805: +6 rows — let-from-slice-elem 16B (p6min13 verbatim) /
24B/40B/12B(MOVQ+MOVL tail) matrix / deref-spine leaf / by-value range
([]struct both-fields sum, []capture 40B, []str 24B header) / range
edges (empty slice, by-VALUE binder-mutation pin, 12B elem MOVL tail)
/ reject row pinning the loud-tail text on both stages. All six fail
at the pristine parent 403625e (re-verified post-rebase; 121 prior
fixtures stay green there).
This commit is contained in:
@@ -10472,6 +10472,18 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
}
|
||||
}
|
||||
}
|
||||
/* C4 (F5, task #7): the remaining ADDRESSABLE rhs
|
||||
* shapes — a slice-base element (`= xs[0]`; the arms
|
||||
* above have TY_ARRAY/N_DOT/N_INDEX bases but no
|
||||
* TY_SLICE base) and deref-spine leaves
|
||||
* (`= (*ts)[i].cap`) — resolve through cgplaceaddr
|
||||
* (the C1 resolver; enumerated arms dispatch first so
|
||||
* their asm is untouched). Pre-C4 these fell out with
|
||||
* havesrc=0: cstage emitted NOTHING (slot
|
||||
* uninitialised), wwstage's scalar default truncated
|
||||
* to 8B — gate-blind cs≠ww. */
|
||||
if (!havesrc && cgplaceaddr(c, n->rhs, D_SI, *locals))
|
||||
havesrc = 1;
|
||||
if (havesrc) {
|
||||
int k = 0;
|
||||
for (; k + 8 <= sz; k += 8) {
|
||||
@@ -10503,6 +10515,11 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* C4: nothing below this arm can initialise a >8B
|
||||
* struct/array slot — every fall-through was a silent
|
||||
* miscompile (rule 7). */
|
||||
fatal("let: aggregate init from unhandled rhs shape "
|
||||
"(task #7/rule-7)");
|
||||
}
|
||||
if (n->rhs && sz == 8) {
|
||||
cgexpr(c, n->rhs, *locals);
|
||||
@@ -11513,11 +11530,47 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_BX));
|
||||
}
|
||||
ins2(c, A_ADDQ, areg(D_AX), areg(D_BX));
|
||||
/* load each binding from BX + foff into its slot */
|
||||
for (int b = 0; b < nbinds; b++) {
|
||||
int op = fldloadop(binds[b].ftype, binds[b].sz);
|
||||
ins2(c, op, amem(D_BX, binds[b].foff), areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, binds[b].off));
|
||||
/* load each binding from BX + foff into its slot. C4 (F5/FC0,
|
||||
* task #7): a by-value AGGREGATE element (struct / tuple /
|
||||
* str/slice header, esz > 8) copies its FULL extent — the
|
||||
* single fldloadop word truncated it to 8B, so every field
|
||||
* past word 0 (str/slice .len/.cap included) read stale slot
|
||||
* bytes (regex.finish's 24B charset binding, gate-blind
|
||||
* cs≠ww). Same word-run + sized-tail idiom as the N_LET
|
||||
* aggregate copy. */
|
||||
if (!destruct && esz > 8) {
|
||||
int k = 0;
|
||||
for (; k + 8 <= esz; k += 8) {
|
||||
ins2(c, A_MOVQ, amem(D_BX, k), areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, binds[0].off + k));
|
||||
}
|
||||
if (k + 4 <= esz) {
|
||||
ins2(c, A_MOVL, amem(D_BX, k), areg(D_AX));
|
||||
ins2(c, A_MOVL, areg(D_AX),
|
||||
amem(D_BP, binds[0].off + k));
|
||||
k += 4;
|
||||
}
|
||||
if (k + 2 <= esz) {
|
||||
ins2(c, A_MOVW, amem(D_BX, k), areg(D_AX));
|
||||
ins2(c, A_MOVW, areg(D_AX),
|
||||
amem(D_BP, binds[0].off + k));
|
||||
k += 2;
|
||||
}
|
||||
if (k + 1 <= esz) {
|
||||
ins2(c, A_MOVB, amem(D_BX, k), areg(D_AX));
|
||||
ins2(c, A_MOVB, areg(D_AX),
|
||||
amem(D_BP, binds[0].off + k));
|
||||
k += 1;
|
||||
}
|
||||
} else {
|
||||
for (int b = 0; b < nbinds; b++) {
|
||||
int op = fldloadop(binds[b].ftype, binds[b].sz);
|
||||
ins2(c, op, amem(D_BX, binds[b].foff),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, binds[b].off));
|
||||
}
|
||||
}
|
||||
cgstmt(c, n->body, locals, frame);
|
||||
label(c, rpost);
|
||||
|
||||
Reference in New Issue
Block a user