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:
@@ -1,13 +1,13 @@
|
||||
package wwfixture;
|
||||
|
||||
def protocolversion: i32 = 1;
|
||||
def corpuscount: i32 = 1726;
|
||||
def corpuscount: i32 = 1727;
|
||||
def errorcount: i32 = 343;
|
||||
def compilecount: i32 = 19;
|
||||
def runcount: i32 = 209;
|
||||
def runexitcount: i32 = 1155;
|
||||
def nativecount: i32 = 3452;
|
||||
def corpushash: str = "870ba4f5140ef331e9effb7cf21fcebe69b5f29d1177cf158e6a1b6db71e9d28";
|
||||
def runexitcount: i32 = 1156;
|
||||
def nativecount: i32 = 3454;
|
||||
def corpushash: str = "40ad5fd79515a743f2adb53e4a26396c6f13859df956fa079299a4b459594177";
|
||||
|
||||
type directive = enum i32 {
|
||||
ERROR = 0,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -80,3 +80,115 @@ fn f_mw(which: i32) (i32 | emsg | e1) = {
|
||||
case e1 => { assert(false); };
|
||||
};
|
||||
};
|
||||
|
||||
// Legacy (unmarked) unions: no variant carries `!`, so index 0 is the
|
||||
// success and every other variant is an error POSITIONALLY (cstage
|
||||
// cg_variant_is_error legacy arm). The remap must fire from that
|
||||
// classification, not the `!` flag — the flag-only gate skipped legacy
|
||||
// unions entirely and `?` propagated the callee's raw tag into a
|
||||
// reordered caller union (the r700_try_rev_errs silent miscompile).
|
||||
|
||||
fn lg(which: i32) (i32 | str | bool) = {
|
||||
if (which == 1) { return "z"; };
|
||||
if (which == 2) { return false; };
|
||||
return 100;
|
||||
};
|
||||
|
||||
// caller error order (bool, str) reverses lg's (str, bool): 1->2, 2->1.
|
||||
fn lf_rev(which: i32) (i64 | bool | str) = {
|
||||
let v: i32 = lg(which)?;
|
||||
return v: i64 + 1;
|
||||
};
|
||||
|
||||
@test fn legacy_rev_str() void = {
|
||||
match (lf_rev(1)) {
|
||||
case let v: i64 => { assert(false); };
|
||||
case bool => { assert(false); };
|
||||
case let s: str => { assert(s.len == 1); assert(s[0] == 122u8); };
|
||||
};
|
||||
};
|
||||
|
||||
@test fn legacy_rev_bool() void = {
|
||||
match (lf_rev(2)) {
|
||||
case let v: i64 => { assert(false); };
|
||||
case let b: bool => { assert(!b); };
|
||||
case str => { assert(false); };
|
||||
};
|
||||
};
|
||||
|
||||
@test fn legacy_rev_success() void = {
|
||||
match (lf_rev(0)) {
|
||||
case let v: i64 => { assert(v == 101i64); };
|
||||
case bool => { assert(false); };
|
||||
case str => { assert(false); };
|
||||
};
|
||||
};
|
||||
|
||||
// the propagated error lands at slot 0 of the enclosing union.
|
||||
fn lg0(which: i32) (i64 | str | bool) = {
|
||||
if (which == 1) { return false; };
|
||||
if (which == 2) { return "q"; };
|
||||
return 5i64;
|
||||
};
|
||||
fn lf_slot0(which: i32) (bool | i64 | str) = {
|
||||
let v: i64 = lg0(which)?;
|
||||
return v + 1;
|
||||
};
|
||||
|
||||
@test fn legacy_slot0() void = {
|
||||
match (lf_slot0(1)) {
|
||||
case let b: bool => { assert(!b); };
|
||||
case i64 => { assert(false); };
|
||||
case str => { assert(false); };
|
||||
};
|
||||
match (lf_slot0(0)) {
|
||||
case bool => { assert(false); };
|
||||
case let v: i64 => { assert(v == 6i64); };
|
||||
case str => { assert(false); };
|
||||
};
|
||||
};
|
||||
|
||||
// two chained remaps: bool rides 2->1->2 across differently-ordered
|
||||
// unions. Runtime alone can pass by double-miss cancellation (2->2->2);
|
||||
// the lang byte-id leg pins the emitted remap blocks in BOTH hops.
|
||||
fn n2(w: i32) (i64 | bool | str) = {
|
||||
let v: i32 = lg(w)?;
|
||||
return v: i64;
|
||||
};
|
||||
fn n3(w: i32) (u8 | str | bool) = {
|
||||
let v: i64 = n2(w)?;
|
||||
return v: u8;
|
||||
};
|
||||
|
||||
@test fn legacy_nested_two_hop() void = {
|
||||
match (n3(2)) {
|
||||
case u8 => { assert(false); };
|
||||
case str => { assert(false); };
|
||||
case let b: bool => { assert(!b); };
|
||||
};
|
||||
match (n3(0)) {
|
||||
case let v: u8 => { assert(v == 100u8); };
|
||||
case str => { assert(false); };
|
||||
case bool => { assert(false); };
|
||||
};
|
||||
};
|
||||
|
||||
// same error order end to end: the remap loop maps every variant to
|
||||
// itself and must emit NOTHING (byte-id with the pre-remap window).
|
||||
fn sctl(w: i32) (i64 | str | bool) = {
|
||||
let v: i32 = lg(w)?;
|
||||
return v: i64;
|
||||
};
|
||||
|
||||
@test fn legacy_same_order() void = {
|
||||
match (sctl(2)) {
|
||||
case i64 => { assert(false); };
|
||||
case str => { assert(false); };
|
||||
case let b: bool => { assert(!b); };
|
||||
};
|
||||
match (sctl(0)) {
|
||||
case let v: i64 => { assert(v == 100i64); };
|
||||
case str => { assert(false); };
|
||||
case bool => { assert(false); };
|
||||
};
|
||||
};
|
||||
|
||||
21
test/wcc/data/r700_try_rev_errs/case.ww
Normal file
21
test/wcc/data/r700_try_rev_errs/case.ww
Normal file
@@ -0,0 +1,21 @@
|
||||
//ww:run-exit 7
|
||||
// Migrated from 700_e2e row 69.
|
||||
package main;
|
||||
fn inner(n: i32) (i32 | str | bool) = {
|
||||
if (n == 0) { return "z"; };
|
||||
if (n < 0) { return false; };
|
||||
return n;
|
||||
};
|
||||
fn outer(n: i32) (i64 | bool | str) = {
|
||||
let v: i32 = inner(n)?;
|
||||
return v: i64 + 1000;
|
||||
};
|
||||
fn main() i32 = {
|
||||
let r: (i64 | bool | str) = outer(-1);
|
||||
match (r) {
|
||||
case let n: i64 => return n: i32;
|
||||
case let b: bool => { if (!b) { return 7; }; return 8; };
|
||||
case let s: str => return 9;
|
||||
};
|
||||
return 0;
|
||||
};
|
||||
Reference in New Issue
Block a user