w6c+w6c_ww: tagged-union struct-lit payload fills via the canonical fill (#23)

The widen choke-point's struct-payload arm carried its own inline
N_STRUCTLIT field loop -- a parallel fill that drifted from
cg_structlit_fill/cgstructlitfill: no tagged-field widen arm, so a
(void|T)-typed field's raw scalar landed in the field's TAG word
(silent truncation past the first tagged field, both stages,
byte-id, gate-blind; prober-9 PG5). Delete both loops and delegate
to the canonical fill at the payload base: one fill path, one widen
path, mutually recursive. Inherits the nested-struct/call/arrlit
field arms and closes a latent fsz==2 cs!=ww (old ww loop's
fieldstoreop MOVW vs cstage MOVQ). Test 938: 15-row table-driven
runtime readback (incl. ellipsis autofill, offset-0 tagged field,
(void|str) payload, 3-level widen-fill recursion torture), all 13
bug rows silent-fail at master 6699158; 2 rows skip the byte-id
check loudly (pre-existing match-on-tagged-FIELD readback cs!=ww,
master-confirmed, separate family).
This commit is contained in:
2026-06-05 02:07:49 +09:00
parent 66991585d6
commit 413aafa599
6 changed files with 629 additions and 202 deletions

View File

@@ -2606,52 +2606,17 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
amem(D_BP, write_off + 8 + k));
}
} else if (src->kind == N_STRUCTLIT) {
for (Node *f = src->list; f; f = f->next) {
u64 foff = 0;
int fsz = 8;
Type *ftype = NULL;
for (Tfield *fl = su->fields; fl; fl = fl->next) {
if (strcmp(fl->name, f->str) == 0) {
foff = fl->offset;
fsz = (int)(fl->type ? fl->type->size : 8);
ftype = fl->type;
break;
}
}
cgexpr(c, f->lhs, *locals_p);
int sl_isf32 = 0;
if (fld_isfloat(ftype, &sl_isf32)) {
int mov = sl_isf32 ? A_MOVSS : A_MOVSD;
ins2(c, mov, areg(D_X0),
amem(D_BP, write_off + 8 + (int)foff));
continue;
}
Type *fu = (ftype && ftype->kind == TY_NAMED)
? ftype->under : ftype;
/* str IS []u8 and a slice is the same 3-word
* {ptr,len,cap} header from cgexpr's AX/BX/CX
* (#1/Phase 3). The slice arm rides #38b's
* regex-shaped consumer (slice fields inside a
* union-payload struct literal); the prior
* str-only gate dropped .len/.cap via the
* scalar store below — the #24 gap's widener
* twin. */
if (fu && (fu->kind == TY_STR
|| fu->kind == TY_SLICE)) {
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, write_off + 8 + (int)foff + 0));
ins2(c, A_MOVQ, areg(D_BX),
amem(D_BP, write_off + 8 + (int)foff + 8));
ins2(c, A_MOVQ, areg(D_CX),
amem(D_BP, write_off + 8 + (int)foff + 16));
continue;
}
int op = A_MOVQ;
if (fsz == 1) op = A_MOVB;
else if (fsz == 4) op = A_MOVL;
ins2(c, op, areg(D_AX),
amem(D_BP, write_off + 8 + (int)foff));
}
/* #23: delegate to the single fill path. The inline
* field loop this replaces was a parallel fill that
* drifted: it lacked the tagged-field widen arm, so
* a (void|T)-typed field's raw scalar landed in the
* field's TAG word (silent truncation past the first
* tagged field, both stages). Delegation also
* inherits the nested-struct / call / array-lit
* field arms; float / str / slice / scalar fields
* emit byte-identically to the old loop. */
cg_structlit_fill(c, locals_p, su, src,
DST_BP, 0, NULL, write_off + 8);
}
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
amem(D_BP, write_off + 0));