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:
2026-05-13 04:23:31 +09:00
parent e16634baec
commit 6e7c9e0df4
9 changed files with 893 additions and 59 deletions

View File

@@ -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; };