w6c: remap legacy-union error tags in wwstage ? propagation

The #173 remap loop in cgtryprop gated on the tparam's explicit `!`
flag only; a LEGACY union (no marks anywhere) classifies error
variants POSITIONALLY (index 0 is success, cstage cg_variant_is_error
cgen.c:905) — so wwstage emitted no remap and propagated the callee's
raw tag into a differently-ordered caller union, a silent wrong arm
(cstage exit 7, wwstage 9 on the banked r700 row). Add the
variantiserror twin and gate the loop on it; refresh the drifted
cstage cite.

Graduates the held e2e row into the corpus (pin 1727/1156/3454) and
adds legacy-mode rows to test/lang/tryprop_tag_remap_test.ww:
reversed order both directions, slot-0 remap, str payload integrity,
a two-hop chain whose pre-fix runtime pass was double-miss tag
cancellation (the lang byteid leg pins the emitted remap blocks),
and a same-order zero-emission control.
This commit is contained in:
2026-08-08 17:11:50 +09:00
parent 74f927be16
commit dc33af6217
4 changed files with 167 additions and 6 deletions

View File

@@ -200,6 +200,34 @@ fn successvariant(ou: *syntax.tinfo) *syntax.tinfo = {
return nil;
};
// variantiserror — is variant idx of tagged union u an error variant?
// Mirrors cstage cg_variant_is_error (cmd/w6c/cgen.c:905): explicit
// `!`-marked unions use the per-variant flag; a LEGACY union (no `!`
// marks anywhere) treats index 0 as success and every other variant as
// an error. The #173 remap loop must use THIS predicate, not the bare
// flag — the flag-only gate skipped legacy unions entirely, so `?`
// propagated the callee's raw tag into a reordered caller union
// (silent wrong arm).
fn variantiserror(ou: *syntax.tinfo, idx: i32) bool = {
let u: *syntax.tinfo = tichase(ou);
if (u == nil) { return false; };
if (u.kind != syntax.tykind.TY_TAGGED) { return false; };
let haserr: bool = false;
let p: *syntax.tparam = u.params;
for (p != nil) { if (p.iserror) { haserr = true; }; p = p.tnext; };
let i: i32 = 0;
p = u.params;
for (p != nil) {
if (i == idx) {
if (haserr) { return p.iserror; };
return idx != 0;
};
i += 1;
p = p.tnext;
};
return false;
};
// cgtrytupleshift — #241: if the `?`/`!` operand's success variant is a
// tuple, the unwrapped payload is an rvalue tuple that must fill the
// register cursor (shift past the tag), and the scalar/str MOVQ DX,AX tail
@@ -502,7 +530,7 @@ fn cgtryprop(c: *cgen, n: *syntax.node) void = {
// enclosing fn return union's variant order before propagating.
// When the `?` operand and the enclosing fn return differ in
// variant order, the raw operand tag names the WRONG variant in
// the return union. Mirrors cstage cmd/w6c/cgen.c:6161-6184.
// the return union. Mirrors cstage cmd/w6c/cgen.c:11296-11319.
// Payload words DX/CX/R8 ride the RET untouched; only AX (the
// tag) is rewritten. Same-order unions map every error variant to
// itself → zero instructions, byte-id with the pre-#173 emit.
@@ -519,7 +547,7 @@ fn cgtryprop(c: *cgen, n: *syntax.node) void = {
let p: *syntax.tparam = u.params;
let i: i32 = 0;
for (p != nil) {
if (p.iserror) {
if (variantiserror(u, i)) {
let j: i32 = flatvariantidxt(r, p.type_, false);
if (j < 0) { j = 0; };
if (j != i) {