w6c+w6c_ww: box [N]tagged array-literal elements via the tagged-store path (fix #12)

A [N]tagged-union array-literal element fell through the is_agg
multi-word-copy path (STRUCT/ARRAY/TUPLE/str/slice only) to the scalar
1-word store: the raw value landed in word 0 (the tag slot) with no tag
written and no payload boxed, so a later match found no variant. Both
stages under-copied identically, so the copy-depth bug was byte-id-blind
— a stride-only fix would still store 1 word and pass the gate green on
both-wrong.

Route each tagged element through cg_widen_tagged_store / the N_LET "BP"
tagged-store wrapper — the same choke-point let-init, vararg gather and
struct-field stores already use — so boxing, tag-remap and zero-pad-to-
slot come for free. esz now comes from the stamped slot size (rule-13);
the wwstage narrow override only covered widths 1/2/4, leaving a 16/24B
tagged element on the wrong 8-byte sentinel stride. rule-7 loud-stops
the unwired `[N]tagged=[x...]` repeat-fill (the widen call consumes the
node and trashes AX).

test/wcc/685: table-driven runtime readback (106/42/13) + a build-fail
row for the repeat-fill loud-stop, both stages.
This commit is contained in:
2026-06-03 20:32:22 +09:00
parent 3f04ea15ef
commit 63142770de
6 changed files with 475 additions and 3 deletions

View File

@@ -29634,6 +29634,17 @@ fn cglet(c: *cgen, n: *node) void = {
let isslicel: bool = esubti != nil
&& esubti.kind == tykind.TY_SLICE;
if (isslicel) { esz = esubti.size: i32; };
// #12: a tagged-union element. NOT folded into isagg —
// isagg's body word-copies/fatals and never boxes the
// tag+payload; route through the cgwidentaggedstore
// choke-point the N_LET tagged path (cgenstmt.ww:1627)
// uses. esz must come from the stamped slot size (#8-class
// trap, rule-13): the narrow override below only rescues
// 1/2/4, so a tagged 16/24B element keeps the wrong 8
// sentinel stride without this.
let istaggedel: bool = esubti != nil
&& esubti.kind == tykind.TY_TAGGED;
if (istaggedel) { esz = esubti.size: i32; };
// #8: a named-narrow element (`[N]tk`, tk = enum i32) is
// neither a builtin prim (primsize=0 above, so esz stayed
// the 8 sentinel) nor an aggregate, so the scalar store kept
@@ -29721,6 +29732,8 @@ fn cglet(c: *cgen, n: *node) void = {
os.write(2, m1c.ptr, m1c.len: u64);
os.exit(1);
}; };
} else { if (istaggedel) {
cgwidentaggedstore(c, esubti, e, "BP", off + idx * esz, esz);
} else {
cgexpr(c, e);
if (isstrel || isslicel) {
@@ -29746,7 +29759,7 @@ fn cglet(c: *cgen, n: *node) void = {
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
}; };
};
}; };
idx += 1;
e = e.next;
};
@@ -29756,6 +29769,14 @@ fn cglet(c: *cgen, n: *node) void = {
os.write(2, m1cr.ptr, m1cr.len: u64);
os.exit(1);
};
// #12: `...` re-stores from AX, but cgwidentaggedstore consumed
// the node and trashed AX — the repeat-fill would write garbage.
// No consumer needs `[N]tagged=[x,...]`.
if (repeat && istaggedel) {
let m12r: str = "#12: `...` repeat of a tagged-union array-literal element not wired (rule-7)\n";
os.write(2, m12r.ptr, m12r.len: u64);
os.exit(1);
};
// AX (and BX for str) still holds the last stored value;
// fill remaining slots up to the declared length with it.
if (repeat) {