wcc/cgen: enum as int is a value conversion, not a tagged assertion (ww stage)

The value-passthrough arm gated on node shape (isenumexpr); a
constant-folded enum member reaches cgen as an int-literal node,
missed the arm, and fell into the tagged-union assertion path
(CMPQ $0 + JNE -> exit 1) — silent wrong code for every `enum as
int`, fnmatch's flag tests included. Gate on the stamped operand
type (tichase == TY_ENUM) at the cgtypeassert choke point, mirroring
cstage cgen.c N_TYPEASSERT; dead node-shape helpers deleted. Tagged
`as` assertion path byte-id-unmoved (control rows). Six @test pins
in attest_pass.ww, dual-stage. (#27b-team, cat-A)
This commit is contained in:
2026-06-11 02:37:32 +09:00
parent 19e9535ef4
commit 00b9933ee0
4 changed files with 111 additions and 150 deletions

View File

@@ -22973,63 +22973,33 @@ fn cgtypetest(c: *cgen, n: *node) void = {
return;
};
// isenumexpr — does this expression's static type resolve to an enum?
// Recognises enum-member access (`Foo.MEMBER`), enum-typed local
// idents, and nkind.N_BIN whose either operand is enum (so `R | W` flows
// through the cast pass-through too).
fn isenumexpr(c: *cgen, e: *node) bool = {
if (e == nil) { return false; };
let k: nkind = e.kind;
if (k == nkind.N_DOT) {
if (e.lhs != nil) {
if (e.lhs.kind == nkind.N_IDENT) {
if (enumlookup(c, e.lhs.str) != nil) { return true; };
};
};
};
if (k == nkind.N_IDENT) {
let lc: *local = localfindnode(c, e.str);
if (lc != nil) {
if (lc.tnode != nil) {
if (lc.tnode.kind == nkind.N_TNAME) {
if (enumlookup(c, lc.tnode.str) != nil) { return true; };
};
};
};
};
if (k == nkind.N_BIN) {
if (isenumexpr(c, e.lhs)) { return true; };
if (isenumexpr(c, e.rhs)) { return true; };
};
if (k == nkind.N_UN) {
if (isenumexpr(c, e.lhs)) { return true; };
};
return false;
};
fn isenumtype(c: *cgen, t: *node) bool = {
if (t == nil) { return false; };
if (t.kind == nkind.N_TENUM) { return true; };
if (t.kind == nkind.N_TNAME) {
if (enumlookup(c, t.str) != nil) { return true; };
};
return false;
};
fn cgtypeassert(c: *cgen, n: *node) void = {
// Enum ↔ integer: reinterpret-only. The LHS value already
// occupies AX (or AX:BX for str variants, irrelevant here);
// no tag/unwrap. Matches cmd/w6c/cgen.c's same short-circuit.
if (isenumexpr(c, n.lhs) || isenumtype(c, n.rhs)) {
cgexpr(c, n.lhs);
return;
};
// `e as T` — load tag, abort (exit 1) if tag != T's variant
// index, otherwise unwrap to T's ABI: scalar/ptr → AX, 16B
// str → (AX, BX). Mirrors cgmatch's slot-based value load.
// Slot resolution inlined; see cgtypetest comment.
// Family C (#35): identity-cast peel — see the cgtypetest twin.
let lhs: *node = taggedidcastpeel(c, n.lhs);
// Enum ↔ integer: reinterpret-only — the value already occupies AX
// (or AX:BX for str variants, irrelevant here); no tag/unwrap. Gate
// on the stamped operand / result TYPE, not node shape: a constant-
// folded enum member (`flag.NOESCAPE`) reaches cgen as an int-literal
// node carrying the enum type_, which a node-shape probe missed →
// spurious tagged-assertion + exit(1) (#27b). Mirrors cstage
// cmd/w6c/cgen.c N_TYPEASSERT (type_chase_named on operand + result).
{
let su: *tinfo = nil;
if (lhs != nil) { su = tichase(lhs.type_: *tinfo); };
let vu: *tinfo = tichase(n.type_: *tinfo);
let lenum: bool = false;
let renum: bool = false;
if (su != nil) { if (su.kind == tykind.TY_ENUM) { lenum = true; }; };
if (vu != nil) { if (vu.kind == tykind.TY_ENUM) { renum = true; }; };
if (lenum || renum) {
cgexpr(c, lhs);
return;
};
};
// #38b residual (rule 7): the spill below reads the cursor, which
// an sret-class call result never fills. Mirrors cstage cgen.c
// N_TYPEASSERT gate.