selfhost: alias-aware istaggedtype for nested-union match
`type error = !(invalid | overflow)` miscompiled — istaggedtype only matched N_TTAGGED directly, so an `e: error` param spilled as 8B scalar and the match's slot+8 read trailed into saved BP. Mirror isstrtype's alias+bang unwrap; add resolvetagged() for is/as/match sites that need the inner N_TTAGGED. Frame scan counts via slotsize so wwstage stays byte-identical to cstage. Unblocks lib/strconv.strerror.
This commit is contained in:
@@ -199,7 +199,7 @@ fn cgfnparams(c: *cgen, params: *node) void = {
|
||||
p = p.next;
|
||||
continue;
|
||||
};
|
||||
if (istaggedtype(p.lhs)) {
|
||||
if (istaggedtype(c, p.lhs)) {
|
||||
let slot: i32 = slotsize(c, p.lhs);
|
||||
let nw: i32 = slot / 8;
|
||||
if (idx + nw <= 6) {
|
||||
@@ -317,7 +317,7 @@ fn cgfn(c: *cgen, fn_: *node) void = {
|
||||
let frame: i32 = 0;
|
||||
for (scanp != nil) {
|
||||
if (scanp.kind == nkind.N_PARAM) {
|
||||
if (istaggedtype(scanp.lhs)) { frame += 24; }
|
||||
if (istaggedtype(c, scanp.lhs)) { frame += slotsize(c, scanp.lhs); }
|
||||
else { if (isslicetype(c, scanp.lhs)) { frame += 24; }
|
||||
else { if (isstrtype(c, scanp.lhs)) { frame += 16; }
|
||||
else { frame += 8; }; }; };
|
||||
|
||||
@@ -242,7 +242,7 @@ fn cgtypetest(c: *cgen, n: *node) void = {
|
||||
let lc: *local = localfindnode(c, lhs.str);
|
||||
if (lc != nil) {
|
||||
scrutoff = lc.off;
|
||||
scrutt = resolvetype(c, lc.tnode);
|
||||
scrutt = resolvetagged(c, lc.tnode);
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -330,7 +330,7 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
|
||||
let lc: *local = localfindnode(c, lhs.str);
|
||||
if (lc != nil) {
|
||||
scrutoff = lc.off;
|
||||
scrutt = resolvetype(c, lc.tnode);
|
||||
scrutt = resolvetagged(c, lc.tnode);
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -744,7 +744,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
let lc: *local = localfindnode(c, scrut.str);
|
||||
if (lc != nil) {
|
||||
scrutoff = lc.off;
|
||||
scrutt = resolvetype(c, lc.tnode);
|
||||
scrutt = resolvetagged(c, lc.tnode);
|
||||
};
|
||||
} else {
|
||||
// Non-ident scrutinee (call result, ?, etc.). Spill into a
|
||||
@@ -766,7 +766,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
if (callee.kind == nkind.N_DOT) { cnm = callee.str; };
|
||||
if (cnm.len > 0) {
|
||||
let rt: *node = fnretlookup(c, cnm);
|
||||
if (rt != nil) { scrutt = resolvetype(c, rt); };
|
||||
if (rt != nil) { scrutt = resolvetagged(c, rt); };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -142,7 +142,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
// For other variants, cgexpr leaves AX, shuffle DX←AX.
|
||||
// Nullable folded `(*T | void)`: just one word; AX is
|
||||
// already the pointer (or 0). No shuffle, no tag.
|
||||
if (istaggedtype(c.fnret)) {
|
||||
if (istaggedtype(c, c.fnret)) {
|
||||
// Forwarding a fallible call: `return f();` where f
|
||||
// also returns a tagged union. The result is already
|
||||
// in (AX=tag, DX=v0, CX=v1) — no shuffle, no tag.
|
||||
@@ -158,7 +158,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
if (callee.kind == nkind.N_DOT) { calleename = callee.str; };
|
||||
if (calleename.len > 0) {
|
||||
let rt: *node = fnretlookup(c, calleename);
|
||||
if (istaggedtype(rt)) { forwardtagged = true; };
|
||||
if (istaggedtype(c, rt)) { forwardtagged = true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -199,7 +199,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
// Bare `return;` from a tagged-union-returning fn is
|
||||
// the void variant: emit its tag. Payload is undefined
|
||||
// (void has size 0). Otherwise zero AX for determinism.
|
||||
if (istaggedtype(c.fnret)) {
|
||||
if (istaggedtype(c, c.fnret)) {
|
||||
if (isnullabletype(c.fnret)) {
|
||||
// null = void variant; AX = 0.
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
@@ -253,7 +253,7 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
// just spill all three.
|
||||
// - Otherwise rhs is a bare variant value: pack tag +
|
||||
// value(s).
|
||||
if (istaggedtype(tn)) {
|
||||
if (istaggedtype(c, tn)) {
|
||||
let nullable: bool = isnullabletype(tn);
|
||||
let rhsreturnstagged: bool = false;
|
||||
if (rhs.kind == nkind.N_CALL) {
|
||||
@@ -265,7 +265,7 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
if (callee.kind == nkind.N_DOT) { calleename = callee.str; };
|
||||
if (calleename.len > 0) {
|
||||
let rt: *node = fnretlookup(c, calleename);
|
||||
if (istaggedtype(rt)) { rhsreturnstagged = true; };
|
||||
if (istaggedtype(c, rt)) { rhsreturnstagged = true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -118,7 +118,7 @@ fn pushargsrev(c: *cgen, arg: *node) i32 = {
|
||||
let lc: *local = localfindnode(c, nm);
|
||||
if (lc != nil) {
|
||||
let off: i32 = lc.off;
|
||||
if (isslicetype(c, lc.tnode) || istaggedtype(lc.tnode)) {
|
||||
if (isslicetype(c, lc.tnode) || istaggedtype(c, lc.tnode)) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP), AX\n");
|
||||
@@ -1053,12 +1053,56 @@ fn isslicetype(c: *cgen, t: *node) bool = {
|
||||
return isslicetyperaw(r);
|
||||
};
|
||||
|
||||
fn istaggedtype(t: *node) bool = {
|
||||
fn istaggedtyperaw(t: *node) bool = {
|
||||
if (t == nil) { return false; };
|
||||
if (t.kind == nkind.N_TTAGGED) { return true; };
|
||||
return false;
|
||||
};
|
||||
|
||||
// resolvetagged — return the underlying N_TTAGGED node for `t`, or nil
|
||||
// if `t` doesn't ultimately denote a tagged union. Follows N_TNAME
|
||||
// aliases (via resolvetype) and unwraps one leading N_TBANG so
|
||||
// `type error = !(invalid | overflow);` resolves to its inner
|
||||
// `(invalid | overflow)` node. Use at sites that read variant lists
|
||||
// or detect nullable folding off a scrutinee — cgmatch, cgtypetest,
|
||||
// cgtypeassert — so aliased `!(A|B)` shapes still dispatch.
|
||||
export fn resolvetagged(c: *cgen, t: *node) *node = {
|
||||
let r: *node = resolvetype(c, t);
|
||||
if (r == nil) { return nil; };
|
||||
if (r.kind == nkind.N_TBANG) {
|
||||
let inner: *node = r.lhs;
|
||||
if (inner == nil) { return nil; };
|
||||
r = resolvetype(c, inner);
|
||||
if (r == nil) { return nil; };
|
||||
};
|
||||
if (r.kind == nkind.N_TTAGGED) { return r; };
|
||||
return nil;
|
||||
};
|
||||
|
||||
// istaggedtype — alias-aware. Mirrors isstrtype: follow N_TNAME to its
|
||||
// underlying decl, then unwrap a leading N_TBANG so `type error =
|
||||
// !(invalid | overflow);` is still recognised as tagged. Without the
|
||||
// bang unwrap the prologue treats the param as scalar (8B), spilling
|
||||
// only DI and losing the value-word SI; the match read of slot+8 then
|
||||
// trails into saved BP.
|
||||
fn istaggedtype(c: *cgen, t: *node) bool = {
|
||||
if (istaggedtyperaw(t)) { return true; };
|
||||
if (c == nil) { return false; };
|
||||
let r: *node = resolvetype(c, t);
|
||||
if (istaggedtyperaw(r)) { return true; };
|
||||
if (r != nil) {
|
||||
if (r.kind == nkind.N_TBANG) {
|
||||
let inner: *node = r.lhs;
|
||||
if (istaggedtyperaw(inner)) { return true; };
|
||||
if (inner != nil) {
|
||||
let r2: *node = resolvetype(c, inner);
|
||||
if (istaggedtyperaw(r2)) { return true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// isf32typeraw / isf64typeraw — bare TNAME check, no alias resolution.
|
||||
fn isf32typeraw(t: *node) bool = {
|
||||
if (t == nil) { return false; };
|
||||
|
||||
Reference in New Issue
Block a user