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

@@ -27248,6 +27248,42 @@ fn cgcall(c: *cgen, n: *node) void = {
fn cgassign(c: *cgen, n: *node) void = {
let lhs: *node = n.lhs;
// #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.
let placeslit: bool = false;
let placedotidx: bool = false;
if (lhs != nil) {
if (lhs.kind == nkind.N_DOT && lhs.lhs != nil) {
if (lhs.lhs.kind == nkind.N_INDEX) {
placedotidx = true;
};
};
if ((lhs.kind == nkind.N_INDEX
|| (lhs.kind == nkind.N_UN && lhs.op == tkind.TK_STAR)
|| placedotidx)
&& n.op == tkind.TK_ASSIGN && n.rhs != nil) {
if (n.rhs.kind == nkind.N_STRUCTLIT) {
let iet: *tinfo = lhs.type_: *tinfo;
for (iet != nil && iet.kind == tykind.TY_NAMED) {
iet = iet.under;
};
if (iet != nil) {
if (iet.kind == tykind.TY_STRUCT) {
placeslit = true;
};
};
};
};
};
// Discard lvalue `_ = expr;` — evaluate rhs for side effects,
// write nothing. Detected by lhs being an nkind.N_IDENT with empty str
// (planted by parseprimary on the tkind.TK_UNDER token).
@@ -27261,6 +27297,26 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
// 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 == tkind.TK_ASSIGN && lhs != nil && n.rhs != nil) {
if (n.rhs.kind == nkind.N_ARRLIT) {
let alt: *tinfo = lhs.type_: *tinfo;
for (alt != nil && alt.kind == tykind.TY_NAMED) {
alt = alt.under;
};
if (alt != nil) {
if (alt.kind == tykind.TY_ARRAY) {
let mal: str = "array-literal store at assignment unwired (task #32)\n";
os.write(2, mal.ptr, mal.len: u64);
os.exit(1);
};
};
};
};
// Tagged-union local reassignment: `r = expr;` where r has a
// tagged-union type. Delegate to cgwidentaggedstore (same path
// as cglet's tagged-init). Covers nullable fold, tagged source,
@@ -27341,10 +27397,13 @@ fn cgassign(c: *cgen, n: *node) void = {
// and BX if str), push, eval pointer, pop value, store.
// We default to MOVQ (8B) since most fixtures use it; for
// `*bool` / `*u8` / `*i32` we narrow via the local's tnode.
// 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 placeslit gate, array-lit dies loud (#32).
if (lhs != nil) {
if (lhs.kind == nkind.N_UN) {
if (lhs.op == tkind.TK_STAR) {
if (n.op == tkind.TK_ASSIGN) {
if (n.op == tkind.TK_ASSIGN && !placeslit) {
let inner: *node = lhs.lhs;
let elemstr: bool = false;
let elemfloat: bool = false;
@@ -27552,7 +27611,7 @@ fn cgassign(c: *cgen, n: *node) void = {
// Array/slice/ptr index store: `arr[i] = v;`. Element size
// from base.tnode picks MOVB vs MOVQ.
if (lhs != nil) {
if (lhs.kind == nkind.N_INDEX) {
if (lhs.kind == nkind.N_INDEX && !placeslit) {
if (n.op == tkind.TK_ASSIGN) {
let base: *node = lhs.lhs;
let idx: *node = lhs.rhs;
@@ -27771,8 +27830,10 @@ fn cgassign(c: *cgen, n: *node) void = {
// address, then word-copy esz bytes: the WRITE-twin of
// the #268 let-init copy loop. Source shapes mirror that
// loop (ident, N_DOT field via dotchainaddr, `*p`
// deref); a by-value call result is the deferred #271, so
// N_CALL/literal sources fall through unchanged. esz>8
// deref); struct-lit sources divert at the placeslit
// 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. esz>8
// non-str/non-slice IS a struct/array/tuple here (the
// tagged element already returned above; floats are ≤8).
let aggsrc: bool = (n.rhs.kind == nkind.N_IDENT)
@@ -28196,7 +28257,7 @@ fn cgassign(c: *cgen, n: *node) void = {
// field write).
if (lhs != nil) {
if (lhs.kind == nkind.N_DOT && lhs.lhs != nil
&& lhs.lhs.kind == nkind.N_INDEX) {
&& lhs.lhs.kind == nkind.N_INDEX && !placeslit) {
let idxbase: *node = lhs.lhs.lhs;
let idx: *node = lhs.lhs.rhs;
let fld2: str = lhs.str;
@@ -30569,8 +30630,13 @@ fn cgassign(c: *cgen, n: *node) void = {
// N_DOT shape the resolver can't address dies LOUD below: the
// pre-C1 fall-off-the-function tail silently emitted NOTHING
// (rhs unevaluated). Mirror of the cstage cgen.c N_ASSIGN arm.
// #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 returned from its legacy arm), and the
// C1.25 aggregate branch fills via @placescr.
if (lhs != nil) {
if (lhs.kind == nkind.N_DOT) {
if (lhs.kind == nkind.N_DOT || lhs.kind == nkind.N_INDEX
|| (lhs.kind == nkind.N_UN && lhs.op == tkind.TK_STAR)) {
let ft: *tinfo = lhs.type_: *tinfo;
let fu: *tinfo = ft;
for (fu != nil && fu.kind == tykind.TY_NAMED) {

View File

@@ -6814,6 +6814,42 @@ fn cgcall(c: *cgen, n: *node) void = {
fn cgassign(c: *cgen, n: *node) void = {
let lhs: *node = n.lhs;
// #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.
let placeslit: bool = false;
let placedotidx: bool = false;
if (lhs != nil) {
if (lhs.kind == nkind.N_DOT && lhs.lhs != nil) {
if (lhs.lhs.kind == nkind.N_INDEX) {
placedotidx = true;
};
};
if ((lhs.kind == nkind.N_INDEX
|| (lhs.kind == nkind.N_UN && lhs.op == tkind.TK_STAR)
|| placedotidx)
&& n.op == tkind.TK_ASSIGN && n.rhs != nil) {
if (n.rhs.kind == nkind.N_STRUCTLIT) {
let iet: *tinfo = lhs.type_: *tinfo;
for (iet != nil && iet.kind == tykind.TY_NAMED) {
iet = iet.under;
};
if (iet != nil) {
if (iet.kind == tykind.TY_STRUCT) {
placeslit = true;
};
};
};
};
};
// Discard lvalue `_ = expr;` — evaluate rhs for side effects,
// write nothing. Detected by lhs being an nkind.N_IDENT with empty str
// (planted by parseprimary on the tkind.TK_UNDER token).
@@ -6827,6 +6863,26 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
// 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 == tkind.TK_ASSIGN && lhs != nil && n.rhs != nil) {
if (n.rhs.kind == nkind.N_ARRLIT) {
let alt: *tinfo = lhs.type_: *tinfo;
for (alt != nil && alt.kind == tykind.TY_NAMED) {
alt = alt.under;
};
if (alt != nil) {
if (alt.kind == tykind.TY_ARRAY) {
let mal: str = "array-literal store at assignment unwired (task #32)\n";
os.write(2, mal.ptr, mal.len: u64);
os.exit(1);
};
};
};
};
// Tagged-union local reassignment: `r = expr;` where r has a
// tagged-union type. Delegate to cgwidentaggedstore (same path
// as cglet's tagged-init). Covers nullable fold, tagged source,
@@ -6907,10 +6963,13 @@ fn cgassign(c: *cgen, n: *node) void = {
// and BX if str), push, eval pointer, pop value, store.
// We default to MOVQ (8B) since most fixtures use it; for
// `*bool` / `*u8` / `*i32` we narrow via the local's tnode.
// 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 placeslit gate, array-lit dies loud (#32).
if (lhs != nil) {
if (lhs.kind == nkind.N_UN) {
if (lhs.op == tkind.TK_STAR) {
if (n.op == tkind.TK_ASSIGN) {
if (n.op == tkind.TK_ASSIGN && !placeslit) {
let inner: *node = lhs.lhs;
let elemstr: bool = false;
let elemfloat: bool = false;
@@ -7118,7 +7177,7 @@ fn cgassign(c: *cgen, n: *node) void = {
// Array/slice/ptr index store: `arr[i] = v;`. Element size
// from base.tnode picks MOVB vs MOVQ.
if (lhs != nil) {
if (lhs.kind == nkind.N_INDEX) {
if (lhs.kind == nkind.N_INDEX && !placeslit) {
if (n.op == tkind.TK_ASSIGN) {
let base: *node = lhs.lhs;
let idx: *node = lhs.rhs;
@@ -7337,8 +7396,10 @@ fn cgassign(c: *cgen, n: *node) void = {
// address, then word-copy esz bytes: the WRITE-twin of
// the #268 let-init copy loop. Source shapes mirror that
// loop (ident, N_DOT field via dotchainaddr, `*p`
// deref); a by-value call result is the deferred #271, so
// N_CALL/literal sources fall through unchanged. esz>8
// deref); struct-lit sources divert at the placeslit
// 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. esz>8
// non-str/non-slice IS a struct/array/tuple here (the
// tagged element already returned above; floats are ≤8).
let aggsrc: bool = (n.rhs.kind == nkind.N_IDENT)
@@ -7762,7 +7823,7 @@ fn cgassign(c: *cgen, n: *node) void = {
// field write).
if (lhs != nil) {
if (lhs.kind == nkind.N_DOT && lhs.lhs != nil
&& lhs.lhs.kind == nkind.N_INDEX) {
&& lhs.lhs.kind == nkind.N_INDEX && !placeslit) {
let idxbase: *node = lhs.lhs.lhs;
let idx: *node = lhs.lhs.rhs;
let fld2: str = lhs.str;
@@ -10135,8 +10196,13 @@ fn cgassign(c: *cgen, n: *node) void = {
// N_DOT shape the resolver can't address dies LOUD below: the
// pre-C1 fall-off-the-function tail silently emitted NOTHING
// (rhs unevaluated). Mirror of the cstage cgen.c N_ASSIGN arm.
// #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 returned from its legacy arm), and the
// C1.25 aggregate branch fills via @placescr.
if (lhs != nil) {
if (lhs.kind == nkind.N_DOT) {
if (lhs.kind == nkind.N_DOT || lhs.kind == nkind.N_INDEX
|| (lhs.kind == nkind.N_UN && lhs.op == tkind.TK_STAR)) {
let ft: *tinfo = lhs.type_: *tinfo;
let fu: *tinfo = ft;
for (fu != nil && fu.kind == tykind.TY_NAMED) {

View File

@@ -27248,6 +27248,42 @@ fn cgcall(c: *cgen, n: *node) void = {
fn cgassign(c: *cgen, n: *node) void = {
let lhs: *node = n.lhs;
// #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.
let placeslit: bool = false;
let placedotidx: bool = false;
if (lhs != nil) {
if (lhs.kind == nkind.N_DOT && lhs.lhs != nil) {
if (lhs.lhs.kind == nkind.N_INDEX) {
placedotidx = true;
};
};
if ((lhs.kind == nkind.N_INDEX
|| (lhs.kind == nkind.N_UN && lhs.op == tkind.TK_STAR)
|| placedotidx)
&& n.op == tkind.TK_ASSIGN && n.rhs != nil) {
if (n.rhs.kind == nkind.N_STRUCTLIT) {
let iet: *tinfo = lhs.type_: *tinfo;
for (iet != nil && iet.kind == tykind.TY_NAMED) {
iet = iet.under;
};
if (iet != nil) {
if (iet.kind == tykind.TY_STRUCT) {
placeslit = true;
};
};
};
};
};
// Discard lvalue `_ = expr;` — evaluate rhs for side effects,
// write nothing. Detected by lhs being an nkind.N_IDENT with empty str
// (planted by parseprimary on the tkind.TK_UNDER token).
@@ -27261,6 +27297,26 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
// 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 == tkind.TK_ASSIGN && lhs != nil && n.rhs != nil) {
if (n.rhs.kind == nkind.N_ARRLIT) {
let alt: *tinfo = lhs.type_: *tinfo;
for (alt != nil && alt.kind == tykind.TY_NAMED) {
alt = alt.under;
};
if (alt != nil) {
if (alt.kind == tykind.TY_ARRAY) {
let mal: str = "array-literal store at assignment unwired (task #32)\n";
os.write(2, mal.ptr, mal.len: u64);
os.exit(1);
};
};
};
};
// Tagged-union local reassignment: `r = expr;` where r has a
// tagged-union type. Delegate to cgwidentaggedstore (same path
// as cglet's tagged-init). Covers nullable fold, tagged source,
@@ -27341,10 +27397,13 @@ fn cgassign(c: *cgen, n: *node) void = {
// and BX if str), push, eval pointer, pop value, store.
// We default to MOVQ (8B) since most fixtures use it; for
// `*bool` / `*u8` / `*i32` we narrow via the local's tnode.
// 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 placeslit gate, array-lit dies loud (#32).
if (lhs != nil) {
if (lhs.kind == nkind.N_UN) {
if (lhs.op == tkind.TK_STAR) {
if (n.op == tkind.TK_ASSIGN) {
if (n.op == tkind.TK_ASSIGN && !placeslit) {
let inner: *node = lhs.lhs;
let elemstr: bool = false;
let elemfloat: bool = false;
@@ -27552,7 +27611,7 @@ fn cgassign(c: *cgen, n: *node) void = {
// Array/slice/ptr index store: `arr[i] = v;`. Element size
// from base.tnode picks MOVB vs MOVQ.
if (lhs != nil) {
if (lhs.kind == nkind.N_INDEX) {
if (lhs.kind == nkind.N_INDEX && !placeslit) {
if (n.op == tkind.TK_ASSIGN) {
let base: *node = lhs.lhs;
let idx: *node = lhs.rhs;
@@ -27771,8 +27830,10 @@ fn cgassign(c: *cgen, n: *node) void = {
// address, then word-copy esz bytes: the WRITE-twin of
// the #268 let-init copy loop. Source shapes mirror that
// loop (ident, N_DOT field via dotchainaddr, `*p`
// deref); a by-value call result is the deferred #271, so
// N_CALL/literal sources fall through unchanged. esz>8
// deref); struct-lit sources divert at the placeslit
// 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. esz>8
// non-str/non-slice IS a struct/array/tuple here (the
// tagged element already returned above; floats are ≤8).
let aggsrc: bool = (n.rhs.kind == nkind.N_IDENT)
@@ -28196,7 +28257,7 @@ fn cgassign(c: *cgen, n: *node) void = {
// field write).
if (lhs != nil) {
if (lhs.kind == nkind.N_DOT && lhs.lhs != nil
&& lhs.lhs.kind == nkind.N_INDEX) {
&& lhs.lhs.kind == nkind.N_INDEX && !placeslit) {
let idxbase: *node = lhs.lhs.lhs;
let idx: *node = lhs.lhs.rhs;
let fld2: str = lhs.str;
@@ -30569,8 +30630,13 @@ fn cgassign(c: *cgen, n: *node) void = {
// N_DOT shape the resolver can't address dies LOUD below: the
// pre-C1 fall-off-the-function tail silently emitted NOTHING
// (rhs unevaluated). Mirror of the cstage cgen.c N_ASSIGN arm.
// #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 returned from its legacy arm), and the
// C1.25 aggregate branch fills via @placescr.
if (lhs != nil) {
if (lhs.kind == nkind.N_DOT) {
if (lhs.kind == nkind.N_DOT || lhs.kind == nkind.N_INDEX
|| (lhs.kind == nkind.N_UN && lhs.op == tkind.TK_STAR)) {
let ft: *tinfo = lhs.type_: *tinfo;
let fu: *tinfo = ft;
for (fu != nil && fu.kind == tykind.TY_NAMED) {