wcc/ww: store through a *tagged pointer widens, both stages (#17)

The N_UN/TK_STAR plain-deref assign arm fell to a single fldstoreop for
every pointee, so `*p = v` with p:*tagged wrote the rhs into the tag word
and never the payload -- identically in both stages, leaving the byte-id
gate green while the store corrupted the tag (#263-class, gate-blind).

Gate on TY_TAGGED and route through cg_widen_tagged_store into a scratch
slot, then word-copy to the destination -- the proven runtime-index arm.
Scalar pointees keep the single-store path unchanged.
This commit is contained in:
2026-06-13 23:54:12 +09:00
parent 728d86518e
commit 1074239859
6 changed files with 399 additions and 0 deletions

View File

@@ -7038,6 +7038,33 @@ cgexpr(Cg *c, Node *n, Local *locals)
Type *pu = type_chase_named(pt);
Type *vt = (pu && pu->kind == TY_PTR) ? pu->sub : NULL;
vt = type_chase_named(vt);
/* #17: tagged-union pointee. The scalar tail below stores
* only the first 8B (fldstoreop MOVQ) — rhs lands in the
* tag word and the payload is dropped, corrupting the
* union. Materialise the widened value (tag + payload
* words, nullable fold, tag remap) into a tag scratch via
* cg_widen_tagged_store, then word-copy scratch → *p.
* Mirrors the index-element tagged arm (cgen.c:6487-6531);
* the dest is just the pointer, so no base-addr dance. */
if (vt && vt->kind == TY_TAGGED) {
int ssz = (int)vt->size;
int scr = cg_tagscr_slot(c, &locals, ssz);
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
for (int k = 0; k < ssz; k += 8)
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, scr + k));
cg_widen_tagged_store(c, &locals, vt, n->rhs,
D_BP, scr, ssz);
cgexpr(c, n->lhs->lhs, locals); /* AX = pointer */
ins2(c, A_MOVQ, areg(D_AX), areg(D_BX));
for (int k = 0; k < ssz; k += 8) {
ins2(c, A_MOVQ, amem(D_BP, scr + k),
areg(D_AX));
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BX, k));
}
break;
}
/* `*p = v` for *f64 / *f32: cgexpr leaves the value in X0,
* not AX. Spill X0 to the stack, evaluate the pointer
* (clobbers AX/BX freely), then reload X0 and MOVSD/MOVSS