wcc/cgen: #52 error-first tagged-union success tag — successtag helper not hardcoded 0 (wwstage)
An error-first tagged union -- error variant at tag 0, success at tag 1+, e.g. (myerr | u16) -- was silently miscompiled by wwstage: the try/propagate codegen hardcoded success = tag 0, so the actual success value (tag 1) failed the CMPQ $0 and fell to the error path -> exit(1) instead of the value (44). cstage was correct (computes the success tag via cg_tagged_success_tag = first non-error variant). wwstage-only: a successtag/successvariant helper (mirroring cstage) replaces the hardcoded tag-0 / first-param assumption at all four try sites -- cgtryprop (?), cgtryunw (!), and the two latent shift sites cgtrytupleshift + cgtrytaggedshift (which bite an error-first union with an aggregate success payload). Success-first unions (the Hare idiom + what the selfhost uses) keep successtag=0 -> CMPQ $0 unchanged -> byte-id-neutral on 990-997. cstage untouched (w6c md5 unchanged). byte-id 990-997 8/8. test/wcc/825 table-driven (errfirst must/prop + tuple-success + success-first control). A separate nested-tagged-union construction divergence is filed (#10/#125).
This commit is contained in:
@@ -162,12 +162,54 @@ fn cgtagvariantidx(c: *cgen, tagged: *node, vt: *node) i32 = {
|
||||
return flatvariantidx(c, tagged, vt);
|
||||
};
|
||||
|
||||
// cgtrytupleshift — #241: if the `?`/`!` operand's success variant (tag 0)
|
||||
// is a tuple, the unwrapped payload is an rvalue tuple that must fill the
|
||||
// successtag — index of the success variant of a tagged union. Mirrors
|
||||
// cstage cg_tagged_success_tag (cmd/w6c/cgen.c:857): if any variant is an
|
||||
// error (`!T`), the success tag is the first NON-error variant's index;
|
||||
// else 0 (legacy/no-error). Drives the `?`/`!` success-tag CMPQ + the
|
||||
// payload-shift variant lookup (#52/#216 — replaces the hardcoded tag-0).
|
||||
fn successtag(ou: *tinfo) i64 = {
|
||||
let u: *tinfo = tichase(ou);
|
||||
if (u == nil) { return 0i64; };
|
||||
if (u.kind != tykind.TY_TAGGED) { return 0i64; };
|
||||
let haserr: bool = false;
|
||||
let p: *tparam = u.params;
|
||||
for (p != nil) { if (p.iserror) { haserr = true; }; p = p.tnext; };
|
||||
if (!haserr) { return 0i64; };
|
||||
let idx: i64 = 0i64;
|
||||
p = u.params;
|
||||
for (p != nil) {
|
||||
if (!p.iserror) { return idx; };
|
||||
idx += 1i64;
|
||||
p = p.tnext;
|
||||
};
|
||||
return 0i64;
|
||||
};
|
||||
|
||||
// successvariant — the success variant's type_ (the param at successtag).
|
||||
// Lets the #241 tuple/tagged payload-shift sites inspect the SUCCESS
|
||||
// variant's shape, not the (error-first) first param.
|
||||
fn successvariant(ou: *tinfo) *tinfo = {
|
||||
let u: *tinfo = tichase(ou);
|
||||
if (u == nil) { return nil; };
|
||||
if (u.kind != tykind.TY_TAGGED) { return nil; };
|
||||
let st: i64 = successtag(ou);
|
||||
let idx: i64 = 0i64;
|
||||
let p: *tparam = u.params;
|
||||
for (p != nil) {
|
||||
if (idx == st) { return p.type_; };
|
||||
idx += 1i64;
|
||||
p = p.tnext;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// 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
|
||||
// is skipped. Returns true when it emitted the shift. Reads the operand's
|
||||
// stamped tagged result tinfo (n.lhs.type_) — the success variant is the
|
||||
// first param, matching the `CMPQ $0` success-tag convention.
|
||||
// stamped tagged result tinfo (n.lhs.type_); the success variant is the
|
||||
// param at successtag (dynamic, #52 — not the hardcoded first param),
|
||||
// matching the dynamic CMPQ $successtag success-tag convention.
|
||||
fn cgtrytupleshift(c: *cgen, n: *node) bool = {
|
||||
if (n.lhs == nil) { return false; };
|
||||
let ou: *tinfo = n.lhs.type_: *tinfo;
|
||||
@@ -175,7 +217,7 @@ fn cgtrytupleshift(c: *cgen, n: *node) bool = {
|
||||
if (ou == nil) { return false; };
|
||||
if (ou.kind != tykind.TY_TAGGED) { return false; };
|
||||
if (ou.params == nil) { return false; };
|
||||
let sv: *tinfo = ou.params.type_;
|
||||
let sv: *tinfo = successvariant(ou);
|
||||
sv = tichase(sv);
|
||||
if (sv == nil) { return false; };
|
||||
if (sv.kind != tykind.TY_TUPLE) { return false; };
|
||||
@@ -184,7 +226,7 @@ fn cgtrytupleshift(c: *cgen, n: *node) bool = {
|
||||
};
|
||||
|
||||
// cgtrytaggedshift — Family C (#35, unwrap source): if the `?`/`!`
|
||||
// operand's success variant (tag 0) is itself a TAGGED union, the
|
||||
// operand's success variant (at successtag, #52) is itself a TAGGED union, the
|
||||
// unwrapped value is a NESTED box (ww keeps nested unions
|
||||
// un-flattened) riding the payload words intact — shift past the
|
||||
// outer tag so consumers see the standard AX=tag cursor. The scalar
|
||||
@@ -198,7 +240,7 @@ fn cgtrytaggedshift(c: *cgen, n: *node) bool = {
|
||||
if (ou == nil) { return false; };
|
||||
if (ou.kind != tykind.TY_TAGGED) { return false; };
|
||||
if (ou.params == nil) { return false; };
|
||||
let sv: *tinfo = ou.params.type_;
|
||||
let sv: *tinfo = successvariant(ou);
|
||||
sv = tichase(sv);
|
||||
if (sv == nil) { return false; };
|
||||
if (sv.kind != tykind.TY_TAGGED) { return false; };
|
||||
@@ -281,8 +323,8 @@ fn cgtryunwcursor(c: *cgen, n: *node, opname: str) void = {
|
||||
};
|
||||
|
||||
// cgtryprop — `e?` propagates the error variant up the stack.
|
||||
// Success tag = 0 (#216 tracks the legacy/flag-aware success-tag
|
||||
// divergence — out of scope here, success check stays `CMPQ $0`).
|
||||
// Success tag is dynamic via successtag (#52/#216): the first non-error
|
||||
// variant's index (cstage cg_tagged_success_tag parity), 0 when success-first.
|
||||
fn cgtryprop(c: *cgen, n: *node) void = {
|
||||
// #38b residuals (rule 7): the cursor read below cannot see an
|
||||
// sret-classified call result (AX = dest pointer), and the
|
||||
@@ -307,9 +349,16 @@ fn cgtryprop(c: *cgen, n: *node) void = {
|
||||
// cursor here (was a silent word0 unwrap); call sources keep
|
||||
// the plain cgexpr emission byte-for-byte.
|
||||
cgtryunwcursor(c, n, "?");
|
||||
// AX = tag. If non-zero, this is an error; pop frame and RET.
|
||||
// AX = tag. If not the success tag, this is an error; pop frame and
|
||||
// RET. Success tag is dynamic (successtag / cstage cg_tagged_success_tag
|
||||
// parity, #52): 0 for success-first, the first non-error index for an
|
||||
// error-first union.
|
||||
let cl: str = mklabel(c, "tryprop_ok");
|
||||
emitline("\tCMPQ\t$0, AX\n");
|
||||
let propu: *tinfo = nil;
|
||||
if (n.lhs != nil) { propu = n.lhs.type_: *tinfo; };
|
||||
emitline("\tCMPQ\t$");
|
||||
emitint(successtag(propu));
|
||||
emitline(", AX\n");
|
||||
emitline("\tJE\t");
|
||||
emitline(cl);
|
||||
emitline("\n");
|
||||
@@ -367,7 +416,7 @@ fn cgtryprop(c: *cgen, n: *node) void = {
|
||||
emitlabel(cl);
|
||||
// #241: a tuple success payload is an rvalue tuple — fill the cursor
|
||||
// (shift past the tag) so the destructure / let consumer reads every
|
||||
// element, not just word0. Success variant = tag 0 (first param).
|
||||
// element, not just word0. Success variant = successtag (#52).
|
||||
if (cgtrytupleshift(c, n)) { return; };
|
||||
if (cgtrytaggedshift(c, n)) { return; };
|
||||
// Success: unwrap value. Tag-only result was AX; the rest of
|
||||
@@ -422,8 +471,9 @@ fn cgtryprop(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
|
||||
// cgtryunw — `e!` aborts on the error variant via exit(1). Legacy
|
||||
// semantics (success tag = 0).
|
||||
// cgtryunw — `e!` aborts on the error variant via exit(1). Success tag
|
||||
// is dynamic via successtag (#52): the first non-error variant's index
|
||||
// (cstage cg_tagged_success_tag parity), 0 when success-first.
|
||||
fn cgtryunw(c: *cgen, n: *node) void = {
|
||||
// #38b residual (rule 7): see the cgtryprop twin.
|
||||
if (n.lhs != nil) {
|
||||
@@ -438,7 +488,14 @@ fn cgtryunw(c: *cgen, n: *node) void = {
|
||||
// Family C (#35/#46): see the cgtryprop twin.
|
||||
cgtryunwcursor(c, n, "!");
|
||||
let cl: str = mklabel(c, "tryunw_ok");
|
||||
emitline("\tCMPQ\t$0, AX\n");
|
||||
// Success tag is dynamic (successtag / cstage cg_tagged_success_tag
|
||||
// parity, #52): 0 for success-first, the first non-error index for an
|
||||
// error-first union.
|
||||
let unwu: *tinfo = nil;
|
||||
if (n.lhs != nil) { unwu = n.lhs.type_: *tinfo; };
|
||||
emitline("\tCMPQ\t$");
|
||||
emitint(successtag(unwu));
|
||||
emitline(", AX\n");
|
||||
emitline("\tJE\t");
|
||||
emitline(cl);
|
||||
emitline("\n");
|
||||
|
||||
Reference in New Issue
Block a user