wwstage: remap error tag on try-propagate across reordered unions (#173)
wwstage cgtryprop returned the operand union's RAW tag when propagating a `c(s)?` error, while cstage (cmd/w6c/cgen.c:6161-6184) remaps it to the enclosing return union's variant ordering. When the operand and return unions differ in variant order, wwstage propagated the WRONG error variant at runtime — gate-blind: byte-id (990-997) and cstage==wwstage asm both pass because the bootstrap only ever tries same-order unions, while the differing-order case is silently wrong. Port cstage's remap loop into cgtryprop (iserror-only): for each error variant whose return-union index differs, emit the CMPQ/JNE/MOVQ/JMP that rewrites the tag in AX; the error payload words (DX/CX/R8) are untouched and ride the RET. Mirror cstage's emission exactly — lazy tryprop_ret allocation on the first remap, j==i skip, j<0 fallback, no dead label when empty, identical label strings and operand order — so same-order emits zero extra instructions (byte-id preserved) and differing-order is now byte-identical cstage==wwstage. Scope: error-variant remap only. wwstage's hardcoded success-tag=0 and iserror-only error detection (vs cstage's cg_tagged_success_tag + cg_variant_is_error legacy fallback) diverge for non-idx-0-success or unmarked unions — also gate-blind, also latent — filed separately as #216. test/wcc/925_tryprop_tag_remap_run: 5 rows (differing-order for both error variants, success unwrap, same-order byte-id witness, and a multi-word !str payload row asserting the payload bytes survive the remap), each with a cstage==wwstage .s byte-id check.
This commit is contained in:
@@ -18377,9 +18377,8 @@ fn cgtagvariantidx(c: *cgen, tagged: *node, vt: *node) i32 = {
|
||||
};
|
||||
|
||||
// cgtryprop — `e?` propagates the error variant up the stack.
|
||||
// Legacy semantics only (success tag = 0). No tag remap; the
|
||||
// selfhost code that uses ? today has the same variant order in
|
||||
// operand and enclosing fn.
|
||||
// Success tag = 0 (#216 tracks the legacy/flag-aware success-tag
|
||||
// divergence — out of scope here, success check stays `CMPQ $0`).
|
||||
fn cgtryprop(c: *cgen, n: *node) void = {
|
||||
cgexpr(c, n.lhs);
|
||||
// AX = tag. If non-zero, this is an error; pop frame and RET.
|
||||
@@ -18388,6 +18387,56 @@ fn cgtryprop(c: *cgen, n: *node) void = {
|
||||
emitline("\tJE\t");
|
||||
emitline(cl);
|
||||
emitline("\n");
|
||||
// #173: remap the operand union's error-variant tag to the
|
||||
// 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.
|
||||
// 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.
|
||||
let u: *tinfo = nil;
|
||||
if (n.lhs != nil) { u = n.lhs.type_: *tinfo; };
|
||||
for (u != nil && u.kind == tykind.TY_NAMED) { u = u.under; };
|
||||
let r: *tinfo = nil;
|
||||
if (c.fnret != nil) { r = c.fnret.type_: *tinfo; };
|
||||
for (r != nil && r.kind == tykind.TY_NAMED) { r = r.under; };
|
||||
if (u != nil && r != nil && r.kind == tykind.TY_TAGGED && u.params != nil) {
|
||||
let propret: str;
|
||||
propret.ptr = nil; propret.len = 0;
|
||||
let haveret: bool = false;
|
||||
let p: *tparam = u.params;
|
||||
let i: i32 = 0;
|
||||
for (p != nil) {
|
||||
if (p.iserror) {
|
||||
let j: i32 = flatvariantidxt(r, p.type_);
|
||||
if (j < 0) { j = 0; };
|
||||
if (j != i) {
|
||||
let skip: str = mklabel(c, "tryprop_skip");
|
||||
emitline("\tCMPQ\t$");
|
||||
emitint(i: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tJNE\t");
|
||||
emitline(skip);
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(j: i64);
|
||||
emitline(", AX\n");
|
||||
if (!haveret) {
|
||||
propret = mklabel(c, "tryprop_ret");
|
||||
haveret = true;
|
||||
};
|
||||
emitline("\tJMP\t");
|
||||
emitline(propret);
|
||||
emitline("\n");
|
||||
emitlabel(skip);
|
||||
};
|
||||
};
|
||||
p = p.tnext;
|
||||
i += 1;
|
||||
};
|
||||
if (haveret) { emitlabel(propret); };
|
||||
};
|
||||
emitline("\tMOVQ\tBP, SP\n\tPOPQ\tBP\n\tRET\n");
|
||||
emitlabel(cl);
|
||||
// Success: unwrap value. Tag-only result was AX; the rest of
|
||||
|
||||
@@ -156,9 +156,8 @@ fn cgtagvariantidx(c: *cgen, tagged: *node, vt: *node) i32 = {
|
||||
};
|
||||
|
||||
// cgtryprop — `e?` propagates the error variant up the stack.
|
||||
// Legacy semantics only (success tag = 0). No tag remap; the
|
||||
// selfhost code that uses ? today has the same variant order in
|
||||
// operand and enclosing fn.
|
||||
// Success tag = 0 (#216 tracks the legacy/flag-aware success-tag
|
||||
// divergence — out of scope here, success check stays `CMPQ $0`).
|
||||
fn cgtryprop(c: *cgen, n: *node) void = {
|
||||
cgexpr(c, n.lhs);
|
||||
// AX = tag. If non-zero, this is an error; pop frame and RET.
|
||||
@@ -167,6 +166,56 @@ fn cgtryprop(c: *cgen, n: *node) void = {
|
||||
emitline("\tJE\t");
|
||||
emitline(cl);
|
||||
emitline("\n");
|
||||
// #173: remap the operand union's error-variant tag to the
|
||||
// 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.
|
||||
// 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.
|
||||
let u: *tinfo = nil;
|
||||
if (n.lhs != nil) { u = n.lhs.type_: *tinfo; };
|
||||
for (u != nil && u.kind == tykind.TY_NAMED) { u = u.under; };
|
||||
let r: *tinfo = nil;
|
||||
if (c.fnret != nil) { r = c.fnret.type_: *tinfo; };
|
||||
for (r != nil && r.kind == tykind.TY_NAMED) { r = r.under; };
|
||||
if (u != nil && r != nil && r.kind == tykind.TY_TAGGED && u.params != nil) {
|
||||
let propret: str;
|
||||
propret.ptr = nil; propret.len = 0;
|
||||
let haveret: bool = false;
|
||||
let p: *tparam = u.params;
|
||||
let i: i32 = 0;
|
||||
for (p != nil) {
|
||||
if (p.iserror) {
|
||||
let j: i32 = flatvariantidxt(r, p.type_);
|
||||
if (j < 0) { j = 0; };
|
||||
if (j != i) {
|
||||
let skip: str = mklabel(c, "tryprop_skip");
|
||||
emitline("\tCMPQ\t$");
|
||||
emitint(i: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tJNE\t");
|
||||
emitline(skip);
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(j: i64);
|
||||
emitline(", AX\n");
|
||||
if (!haveret) {
|
||||
propret = mklabel(c, "tryprop_ret");
|
||||
haveret = true;
|
||||
};
|
||||
emitline("\tJMP\t");
|
||||
emitline(propret);
|
||||
emitline("\n");
|
||||
emitlabel(skip);
|
||||
};
|
||||
};
|
||||
p = p.tnext;
|
||||
i += 1;
|
||||
};
|
||||
if (haveret) { emitlabel(propret); };
|
||||
};
|
||||
emitline("\tMOVQ\tBP, SP\n\tPOPQ\tBP\n\tRET\n");
|
||||
emitlabel(cl);
|
||||
// Success: unwrap value. Tag-only result was AX; the rest of
|
||||
|
||||
@@ -18377,9 +18377,8 @@ fn cgtagvariantidx(c: *cgen, tagged: *node, vt: *node) i32 = {
|
||||
};
|
||||
|
||||
// cgtryprop — `e?` propagates the error variant up the stack.
|
||||
// Legacy semantics only (success tag = 0). No tag remap; the
|
||||
// selfhost code that uses ? today has the same variant order in
|
||||
// operand and enclosing fn.
|
||||
// Success tag = 0 (#216 tracks the legacy/flag-aware success-tag
|
||||
// divergence — out of scope here, success check stays `CMPQ $0`).
|
||||
fn cgtryprop(c: *cgen, n: *node) void = {
|
||||
cgexpr(c, n.lhs);
|
||||
// AX = tag. If non-zero, this is an error; pop frame and RET.
|
||||
@@ -18388,6 +18387,56 @@ fn cgtryprop(c: *cgen, n: *node) void = {
|
||||
emitline("\tJE\t");
|
||||
emitline(cl);
|
||||
emitline("\n");
|
||||
// #173: remap the operand union's error-variant tag to the
|
||||
// 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.
|
||||
// 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.
|
||||
let u: *tinfo = nil;
|
||||
if (n.lhs != nil) { u = n.lhs.type_: *tinfo; };
|
||||
for (u != nil && u.kind == tykind.TY_NAMED) { u = u.under; };
|
||||
let r: *tinfo = nil;
|
||||
if (c.fnret != nil) { r = c.fnret.type_: *tinfo; };
|
||||
for (r != nil && r.kind == tykind.TY_NAMED) { r = r.under; };
|
||||
if (u != nil && r != nil && r.kind == tykind.TY_TAGGED && u.params != nil) {
|
||||
let propret: str;
|
||||
propret.ptr = nil; propret.len = 0;
|
||||
let haveret: bool = false;
|
||||
let p: *tparam = u.params;
|
||||
let i: i32 = 0;
|
||||
for (p != nil) {
|
||||
if (p.iserror) {
|
||||
let j: i32 = flatvariantidxt(r, p.type_);
|
||||
if (j < 0) { j = 0; };
|
||||
if (j != i) {
|
||||
let skip: str = mklabel(c, "tryprop_skip");
|
||||
emitline("\tCMPQ\t$");
|
||||
emitint(i: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tJNE\t");
|
||||
emitline(skip);
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(j: i64);
|
||||
emitline(", AX\n");
|
||||
if (!haveret) {
|
||||
propret = mklabel(c, "tryprop_ret");
|
||||
haveret = true;
|
||||
};
|
||||
emitline("\tJMP\t");
|
||||
emitline(propret);
|
||||
emitline("\n");
|
||||
emitlabel(skip);
|
||||
};
|
||||
};
|
||||
p = p.tnext;
|
||||
i += 1;
|
||||
};
|
||||
if (haveret) { emitlabel(propret); };
|
||||
};
|
||||
emitline("\tMOVQ\tBP, SP\n\tPOPQ\tBP\n\tRET\n");
|
||||
emitlabel(cl);
|
||||
// Success: unwrap value. Tag-only result was AX; the rest of
|
||||
|
||||
Reference in New Issue
Block a user