w6c+wwstage: store struct-literal array-field init (#249 BUG A)

A struct literal initialising an array-typed field as a local
(`e{ encmap = [..] }`) silently dropped the initializer: cg_structlit_fill
(cstage) / cgstructlitfill (wwstage) had no TY_ARRAY field arm, so the
array field fell to the generic scalar tail — cgexpr the N_ARRLIT (→ AX≈0)
then store one sized word — losing every element. cstage returned 0;
wwstage emitted byte-identical wrong code. (The GLOBAL literal-init path
is unaffected: it goes through emit_struct_lit_bytes, already correct via
#129 A.3.)

Both stages now element-wise store the N_ARRLIT at base+field_off+i*esz,
reusing the proven N_LET array-init shape (cgen.c:8467 / cgenstmt.ww:1393)
for int and float elements plus its `...` repeat fill; esz routes through
the type table (rule 13). str/slice/struct/tagged ELEMENT arrays are the
N_LET path's documented multi-word gap (cgen.c:8462) — converted from the
silent drop to a LOUD rule-7 error in both stages, not left silent.
Symmetric both stages (rule 10), byte-identical .s.

The `...` repeat in a struct-literal array field is checker-unreachable
today (the field type-check rejects `[v...]` length inference — a
separate checker gap); the arm mirrors N_LET's repeat for symmetry.

Test 949_structlit_arrfield_run: +local literal-init reads (idx 0 / last
element), cstage run + cs==ww byte-id.
This commit is contained in:
2026-06-02 01:19:46 +09:00
parent 16b519465a
commit 0fb4bae337
5 changed files with 507 additions and 0 deletions

View File

@@ -2547,6 +2547,84 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit,
}
continue;
}
/* #249: array-typed field initialised from an N_ARRLIT. No prior
* arm matched, so without this the generic scalar tail below
* would cgexpr the N_ARRLIT (→ AX≈0) and store one sized word,
* silently DROPPING every element. Store element-wise at
* disp+foff+i*esz, reusing the N_LET array-init shape (cgen.c:
* 8467) for int/float elements and its `...` repeat. For non-BP
* modes cgexpr clobbers BX, so reload the base before each store
* (the X0/AX value reg survives the reload). str/slice/struct/
* tagged ELEMENT arrays are the N_LET path's documented multi-
* word gap (cgen.c:8462) — loud rule-7 error, not a silent drop. */
if (fu && fu->kind == TY_ARRAY
&& f->lhs && f->lhs->kind == N_ARRLIT) {
Type *esub = fu->sub;
Type *esubu = (esub && esub->kind == TY_NAMED)
? esub->under : esub;
int esz = esub ? (int)esub->size : 1;
int al_isf32 = 0;
int is_float_el = fld_isfloat(esub, &al_isf32);
if (type_isstr(esub) || type_isslice(esub)
|| (esubu && (esubu->kind == TY_STRUCT
|| esubu->kind == TY_TAGGED)))
fatal("cg_structlit_fill: array field '%s' has a "
"str/slice/struct/tagged element — multi-word "
"element store is out of #249 scope (N_LET "
"array-init gap, cgen.c:8462)",
f->str ? f->str : "?");
int eop = A_MOVQ;
if (esz == 1) eop = A_MOVB;
else if (esz == 2) eop = A_MOVW;
else if (esz == 4) eop = A_MOVL;
int fmov = al_isf32 ? A_MOVSS : A_MOVSD;
int idx = 0;
Node *last = NULL;
int repeat = 0;
for (Node *e = f->lhs->list; e; e = e->next) {
if (e->kind == N_FIELD && e->str
&& strcmp(e->str, "...") == 0) {
repeat = 1;
break;
}
cgexpr(c, e, *locals_p);
if (mode == DST_PTR_LOCAL)
ins2(c, A_MOVQ, amem(D_BP, srcoff),
areg(D_BX));
else if (mode == DST_GLOBAL)
ins2(c, A_LEAQ, masym(c, name),
areg(D_BX));
int eoff = disp + (int)foff + idx * esz;
if (is_float_el)
ins2(c, fmov, areg(D_X0),
amem(base_reg, eoff));
else
ins2(c, eop, areg(D_AX),
amem(base_reg, eoff));
last = e;
idx++;
}
if (repeat && last) {
while (idx < (int)fu->alen) {
if (mode == DST_PTR_LOCAL)
ins2(c, A_MOVQ,
amem(D_BP, srcoff),
areg(D_BX));
else if (mode == DST_GLOBAL)
ins2(c, A_LEAQ, masym(c, name),
areg(D_BX));
int eoff = disp + (int)foff + idx * esz;
if (is_float_el)
ins2(c, fmov, areg(D_X0),
amem(base_reg, eoff));
else
ins2(c, eop, areg(D_AX),
amem(base_reg, eoff));
idx++;
}
}
continue;
}
cgexpr(c, f->lhs, *locals_p);
/* For non-BP modes, cgexpr just clobbered BX; reload it
* before the store. */