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

@@ -8433,6 +8433,42 @@ fn cgassign(c: *cgen, n: *node) void = {
if (n.op == tkind.TK_ASSIGN && !placeslit
&& !derefagg) {
let inner: *node = lhs.lhs;
// #17: tagged-union pointee. The single-store
// tail below writes rhs into the tag word only,
// dropping the payload and corrupting the union.
// Materialise the widened value (tag + payload
// words, nullable fold, tag remap) into the shared
// @tagscr scratch via cgwidentaggedstore, then
// word-copy scratch -> *p. Mirror of the runtime-
// index tagged element arm (cgenexpr.ww:8768) and
// the cstage twin (cmd/w6c/cgen.c #17 deref arm).
let du: *tinfo = tichase(lhs.type_: *tinfo);
if (du != nil && du.kind == tykind.TY_TAGGED) {
let ssz: i32 = du.size: i32;
let scr: i32 = tagscradd(c, ssz);
emitline("\tXORQ\tAX, AX\n");
let zk: i32 = 0;
for (zk < ssz) {
emitline("\tMOVQ\tAX, ");
emitoff((scr + zk): i64);
emitline("(BP)\n");
zk += 8;
};
cgwidentaggedstore(c, du, n.rhs, "BP", scr, ssz);
cgexpr(c, inner);
emitline("\tMOVQ\tAX, BX\n");
let ck: i32 = 0;
for (ck < ssz) {
emitline("\tMOVQ\t");
emitoff((scr + ck): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff(ck: i64);
emitline("(BX)\n");
ck += 8;
};
return;
};
let elemstr: bool = false;
let elemfloat: bool = false;
let elemf32: bool = false;