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:
@@ -22973,63 +22973,33 @@ fn cgtypetest(c: *cgen, n: *node) void = {
|
|||||||
return;
|
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 = {
|
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
|
// `e as T` — load tag, abort (exit 1) if tag != T's variant
|
||||||
// index, otherwise unwrap to T's ABI: scalar/ptr → AX, 16B
|
// index, otherwise unwrap to T's ABI: scalar/ptr → AX, 16B
|
||||||
// str → (AX, BX). Mirrors cgmatch's slot-based value load.
|
// str → (AX, BX). Mirrors cgmatch's slot-based value load.
|
||||||
// Slot resolution inlined; see cgtypetest comment.
|
// Slot resolution inlined; see cgtypetest comment.
|
||||||
// Family C (#35): identity-cast peel — see the cgtypetest twin.
|
// Family C (#35): identity-cast peel — see the cgtypetest twin.
|
||||||
let lhs: *node = taggedidcastpeel(c, n.lhs);
|
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
|
// #38b residual (rule 7): the spill below reads the cursor, which
|
||||||
// an sret-class call result never fills. Mirrors cstage cgen.c
|
// an sret-class call result never fills. Mirrors cstage cgen.c
|
||||||
// N_TYPEASSERT gate.
|
// N_TYPEASSERT gate.
|
||||||
|
|||||||
@@ -678,63 +678,33 @@ fn cgtypetest(c: *cgen, n: *node) void = {
|
|||||||
return;
|
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 = {
|
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
|
// `e as T` — load tag, abort (exit 1) if tag != T's variant
|
||||||
// index, otherwise unwrap to T's ABI: scalar/ptr → AX, 16B
|
// index, otherwise unwrap to T's ABI: scalar/ptr → AX, 16B
|
||||||
// str → (AX, BX). Mirrors cgmatch's slot-based value load.
|
// str → (AX, BX). Mirrors cgmatch's slot-based value load.
|
||||||
// Slot resolution inlined; see cgtypetest comment.
|
// Slot resolution inlined; see cgtypetest comment.
|
||||||
// Family C (#35): identity-cast peel — see the cgtypetest twin.
|
// Family C (#35): identity-cast peel — see the cgtypetest twin.
|
||||||
let lhs: *node = taggedidcastpeel(c, n.lhs);
|
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
|
// #38b residual (rule 7): the spill below reads the cursor, which
|
||||||
// an sret-class call result never fills. Mirrors cstage cgen.c
|
// an sret-class call result never fills. Mirrors cstage cgen.c
|
||||||
// N_TYPEASSERT gate.
|
// N_TYPEASSERT gate.
|
||||||
|
|||||||
@@ -22973,63 +22973,33 @@ fn cgtypetest(c: *cgen, n: *node) void = {
|
|||||||
return;
|
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 = {
|
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
|
// `e as T` — load tag, abort (exit 1) if tag != T's variant
|
||||||
// index, otherwise unwrap to T's ABI: scalar/ptr → AX, 16B
|
// index, otherwise unwrap to T's ABI: scalar/ptr → AX, 16B
|
||||||
// str → (AX, BX). Mirrors cgmatch's slot-based value load.
|
// str → (AX, BX). Mirrors cgmatch's slot-based value load.
|
||||||
// Slot resolution inlined; see cgtypetest comment.
|
// Slot resolution inlined; see cgtypetest comment.
|
||||||
// Family C (#35): identity-cast peel — see the cgtypetest twin.
|
// Family C (#35): identity-cast peel — see the cgtypetest twin.
|
||||||
let lhs: *node = taggedidcastpeel(c, n.lhs);
|
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
|
// #38b residual (rule 7): the spill below reads the cursor, which
|
||||||
// an sret-class call result never fills. Mirrors cstage cgen.c
|
// an sret-class call result never fills. Mirrors cstage cgen.c
|
||||||
// N_TYPEASSERT gate.
|
// N_TYPEASSERT gate.
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ import rt; // #3/B': check_empty_alloc_annotated calls alloc → rt_malloc.
|
|||||||
|
|
||||||
type point = struct { x: i32, y: i32 };
|
type point = struct { x: i32, y: i32 };
|
||||||
|
|
||||||
|
// #27b: enum bases for the `<enum> as <inttype>` value-reinterpret pins
|
||||||
|
// below (multiple enum bases × int widths).
|
||||||
|
type eflag = enum i32 { A = 1, B = 2, C = 4 };
|
||||||
|
type ebyte = enum u8 { LO = 1, HI = 200 };
|
||||||
|
type eplain = enum { Z = 0, ONE = 1, TWO = 2 };
|
||||||
|
|
||||||
// #87: a PLAIN (non-alias) module-level tagged-union global SEGV'd BOTH
|
// #87: a PLAIN (non-alias) module-level tagged-union global SEGV'd BOTH
|
||||||
// stages — no static DATA emitted (read as a frame-local at BP) + the
|
// stages — no static DATA emitted (read as a frame-local at BP) + the
|
||||||
// match scrutinee read saved BP as the tag. Fix: emit the 32B box that
|
// match scrutinee read saved BP as the tag. Fix: emit the 32B box that
|
||||||
@@ -176,3 +182,48 @@ let g86_tup: (u32, u32) = (1u32, 2u32);
|
|||||||
if (g86_tup.0 != l.0) { let _: i32 = 1 / 0; };
|
if (g86_tup.0 != l.0) { let _: i32 = 1 / 0; };
|
||||||
if (g86_tup.1 != l.1) { let _: i32 = 1 / 0; };
|
if (g86_tup.1 != l.1) { let _: i32 = 1 / 0; };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// #27b: `<enum> as <inttype>` must lower as a value reinterpret (the
|
||||||
|
// integer storage already occupies the register), NOT a tagged-union
|
||||||
|
// assertion. Pre-fix wwstage gated the passthrough on NODE SHAPE
|
||||||
|
// (isenumexpr), so a constant-folded enum MEMBER (`eflag.B`, an int-
|
||||||
|
// literal node carrying the enum type_) missed the arm and fell into the
|
||||||
|
// tag-assert + exit(1) path — every fnmatch `(fl as i32)` flag test
|
||||||
|
// aborted (8/8 → 0/8 on the wwstage build). Fix gates on the stamped
|
||||||
|
// operand/result TYPE (mirror cstage cmd/w6c/cgen.c N_TYPEASSERT). These
|
||||||
|
// rows pin the construct by construction: member-as (the folded shape),
|
||||||
|
// ident-as (the param shape), the fnmatch flag-test combination, int→enum
|
||||||
|
// (result-type gate), and multiple enum bases × int widths.
|
||||||
|
@test fn check_enum_as_member() void = {
|
||||||
|
if ((eflag.B as i32) != 2) { let _: i32 = 1 / 0; };
|
||||||
|
if ((eflag.C as i32) != 4) { let _: i32 = 1 / 0; };
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn check_enum_as_flagtest() void = {
|
||||||
|
let fl: eflag = eflag.A; // 1 & 2 == 0
|
||||||
|
if (((fl as i32) & (eflag.B as i32)) != 0) { let _: i32 = 1 / 0; };
|
||||||
|
let fl2: eflag = eflag.C; // 4 & 4 != 0
|
||||||
|
if (((fl2 as i32) & (eflag.C as i32)) == 0) { let _: i32 = 1 / 0; };
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn check_enum_as_widths() void = {
|
||||||
|
if ((ebyte.HI as u8) != 200) { let _: i32 = 1 / 0; };
|
||||||
|
if ((ebyte.HI as i32) != 200) { let _: i32 = 1 / 0; };
|
||||||
|
if ((eplain.TWO as int) != 2) { let _: i32 = 1 / 0; };
|
||||||
|
if ((eflag.B as i64) != 2i64) { let _: i32 = 1 / 0; };
|
||||||
|
};
|
||||||
|
|
||||||
|
@test fn check_int_as_enum() void = {
|
||||||
|
let v: i32 = 2i32;
|
||||||
|
let e: eflag = v as eflag; // result-type enum gate
|
||||||
|
if ((e as i32) != 2) { let _: i32 = 1 / 0; };
|
||||||
|
};
|
||||||
|
|
||||||
|
// CONTROL (#27b): a legit tagged-union `as` must KEEP the tag-assert +
|
||||||
|
// payload-unwrap lowering — byte-id UNMOVED by the enum-passthrough fix.
|
||||||
|
// This is the path enum-as must not hijack and must not be hijacked by.
|
||||||
|
@test fn check_tagged_as_control() void = {
|
||||||
|
let b: (i32 | str) = 7;
|
||||||
|
let v: i32 = b as i32;
|
||||||
|
if (v != 7) { let _: i32 = 1 / 0; };
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user