w6c+w6c_ww: struct-lit store into indexed/deref/field place fills via resolver (#20)

A struct-LITERAL rhs aimed at an N_INDEX element (a[i] = pt{...},
(*ts)[i].caps[k] = capture{...}), an N_UN deref place (*p = pt{...}),
or an indexed-base FIELD place (a[i].f = pt{...}, reviewer-20 sibling)
fell to a scalar store tail in BOTH stages: cgexpr on a struct
literal emits nothing (AX=0) and one MOVQ zeroed the place's first
word — every field silently dropped, a str-leading element's
content.ptr nulled (downstream SEGFAULT). Byte-identically wrong, so
every byte-id gate was blind; runtime pins added.

Fix: divert struct-lit-rhs INDEX/UN-STAR/DOT-over-INDEX places past
the legacy arms and widen the F6 assign-resolver gate
(N_DOT -> N_DOT|N_INDEX|N_UN); the existing C1.25 aggregate arm
materialises the literal into a fresh per-use @placescr slot and
word-copies to the cgplaceaddr-resolved address. No new path;
@placescr alloc site stays single per stage. Rider (task #32): an
array-LITERAL rhs at assignment — unwired for EVERY place kind, same
silent zero-word tail — now dies loud at one choke-point until the
fill lands; build-fail rows pin it.

Gates regex fold-5a (run_thread groupstart capture store,
regex.ha:643-651). Residual adjacent gaps (deref ident-rhs truncation,
>24B ident reassign cs!=ww, struct compound acceptance, value-global
DATAW, tuple-lit deref truncation, CALL-rhs RAX-only store) probed
pre-existing and filed as tasks #31 A-G / #32.
This commit is contained in:
2026-06-04 23:46:11 +09:00
parent 0ce98cc5dc
commit 06b0fea98b
6 changed files with 832 additions and 26 deletions

View File

@@ -4267,6 +4267,41 @@ cgexpr(Cg *c, Node *n, Local *locals)
cgexpr(c, n->rhs, locals);
break;
}
/* #20 (task): struct-lit rhs into an INDEXED struct element —
* `a[i] = pt{...}`, `(*ts)[i].caps[k] = capture{...}` — a
* DEREF place (`*p = pt{...}`) or an indexed-base FIELD
* place (`a[i].f = pt{...}`, same class) skips the legacy
* arms and routes to the resolver aggregate arm below (the
* single @placescr funnel). The legacy arms' rhs handling
* (#270-1b ident/dot/deref gate; deref scalar store; the
* a[i].f fldstoreop tail) let the lit fall to a scalar
* tail: cgexpr(N_STRUCTLIT) emits nothing (AX=0) and one
* MOVQ zeroed the place's first word — every field
* silently dropped, a leading str header trashed. */
int place_slit = 0;
if (n->lhs
&& (n->lhs->kind == N_INDEX
|| (n->lhs->kind == N_UN && n->lhs->op == TK_STAR)
|| (n->lhs->kind == N_DOT && n->lhs->lhs
&& n->lhs->lhs->kind == N_INDEX))
&& n->op == TK_ASSIGN
&& n->rhs && n->rhs->kind == N_STRUCTLIT) {
Type *iet = type_chase_named(n->lhs->type);
if (iet && iet->kind == TY_STRUCT)
place_slit = 1;
}
/* Task #32: an array-LITERAL rhs at assignment is unwired
* for EVERY place kind (ident reassign, index, deref, dot)
* — only decl-init fills. Pre-#32 the same scalar tail
* zeroed one word silently; die loud until the fill lands.
* Slice-typed places are already loud in the checker. */
if (n->op == TK_ASSIGN && n->lhs
&& n->rhs && n->rhs->kind == N_ARRLIT) {
Type *alt = type_chase_named(n->lhs->type);
if (alt && alt->kind == TY_ARRAY)
fatal("array-literal store at assignment "
"unwired (task #32)");
}
/* p.x = v or p.x += v where p.x is a struct field
* (direct or via *struct). For compound ops we read-modify-
* write the field; for plain `=` we just write. The base
@@ -4645,7 +4680,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
* fall through and silently drop the store. Placed before the
* `!= N_IDENT` branch so both shapes share one path. */
if (n->lhs && n->lhs->kind == N_DOT && n->lhs->lhs
&& n->lhs->lhs->kind == N_INDEX) {
&& n->lhs->lhs->kind == N_INDEX && !place_slit) {
Node *idxbase = n->lhs->lhs->lhs;
Node *idx = n->lhs->lhs->rhs;
if (idxbase && idxbase->kind == N_IDENT && idx) {
@@ -5482,7 +5517,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
* ptr local) or a more complex expression like s.ptr where
* s: *[]u8. We compute the base address, scale the index by
* elem size, and store with the right size. */
if (n->lhs->kind == N_INDEX && n->lhs->lhs) {
if (n->lhs->kind == N_INDEX && n->lhs->lhs && !place_slit) {
Node *base = n->lhs->lhs;
Type *bt = base->type;
Type *u = (bt && bt->kind == TY_NAMED) ? bt->under : bt;
@@ -5585,8 +5620,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
* address, then word-copy esz bytes: the WRITE-twin of the
* #268 let-init copy loop. Source shapes mirror that loop
* (ident local/global, N_DOT field via cg_dotchain_addr,
* `*p` deref); a by-value call result is the deferred #271,
* so N_CALL/literal sources fall through unchanged. */
* `*p` deref); struct-lit sources divert at the place_slit
* gate above (#20), array-lit dies loud (task #32), and a
* by-value call result still falls to the scalar tail —
* RAX-only store, task #31-G. */
if ((is_arr || is_sl || is_ptr) && n->op == TK_ASSIGN
&& esubu && (esubu->kind == TY_STRUCT
|| esubu->kind == TY_ARRAY
@@ -5943,9 +5980,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
}
/* 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. */
* 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). */
if (n->lhs && n->lhs->kind == N_UN && n->lhs->op == TK_STAR
&& n->op == TK_ASSIGN) {
&& n->op == TK_ASSIGN && !place_slit) {
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;
@@ -6218,8 +6258,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
* routes through cgplaceaddr; the load/store emission stays
* here. Any N_DOT shape the resolver can't address dies
* LOUD below: the pre-C1 dispatch tail silently emitted
* NOTHING (rhs unevaluated) for every such shape. */
if (n->lhs && n->lhs->kind == N_DOT) {
* NOTHING (rhs unevaluated) for every such shape.
* #20 (task): N_INDEX and N_UN(STAR) lvalues enroll too —
* only the struct-lit-rhs diversion above reaches here
* (every other indexed/deref shape broke out of its legacy
* arm), and the C1.25 aggregate branch fills via
* @placescr. */
if (n->lhs && (n->lhs->kind == N_DOT
|| n->lhs->kind == N_INDEX
|| (n->lhs->kind == N_UN && n->lhs->op == TK_STAR))) {
Type *ft = n->lhs->type;
Type *fu = type_chase_named(ft);
int fsz = (int)(ft ? ft->size : 8);