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:
@@ -413,7 +413,33 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
let bnm: str = n.str;
|
||||
if (bnm.len > 0) {
|
||||
checkmoduleshadow(c, bnm, "binding");
|
||||
scopedefine(c.cur, bnm, skind.SK_VAR, nil, n);
|
||||
// C4 (task #7): bind the ELEMENT type so field
|
||||
// reads off a by-value aggregate binding
|
||||
// (`for (let t .. threads) { t.pc }`) resolve —
|
||||
// pre-C4 the binding's decl was the N_FORRANGE
|
||||
// node itself, whose .lhs is the SCRUTINEE expr,
|
||||
// so exprtype's decl.lhs read handed the dot a
|
||||
// non-type node and asserttyped bailed (cstage
|
||||
// types it: check.c N_FORRANGE scope_define(...,
|
||||
// elem, ...)). Synthetic N_LET binder whose .lhs
|
||||
// is the element tnode — the stamptuplebinds
|
||||
// `b.lhs = et` idiom. A str scrutinee keeps the
|
||||
// old decl: cgen synthesises the u8 elem there
|
||||
// and no dot applies to a u8 binding.
|
||||
let et: *node = nil;
|
||||
let it: *node = exprtype(c, n.lhs, nil);
|
||||
if (it != nil) {
|
||||
if (it.kind == nkind.N_TSLICE) { et = it.lhs; };
|
||||
if (it.kind == nkind.N_TARRAY) { et = it.lhs; };
|
||||
};
|
||||
if (et != nil) {
|
||||
let bn: *node = newnode(nkind.N_LET, n.file, n.line, n.col);
|
||||
bn.str = bnm;
|
||||
bn.lhs = et;
|
||||
scopedefine(c.cur, bnm, skind.SK_VAR, nil, bn);
|
||||
} else {
|
||||
scopedefine(c.cur, bnm, skind.SK_VAR, nil, n);
|
||||
};
|
||||
};
|
||||
};
|
||||
if (n.body != nil) { resolvewalk(c, n.body); };
|
||||
|
||||
Reference in New Issue
Block a user