selfhost: cgen for enum (member fold + as pass-through)
w6c_ww now compiles enum end-to-end and emits byte-identical
asm to the C w6c on the new 994 corpus case (`type mode = enum u8
{ R, W, RW = R | W }; main() { return (mode.RW): i32 }`). Mechanism
mirrors the C side:
- collectenums walks every `type X = enum {...}` at file scope and
pre-resolves each member's u64 value (auto-increment from prior,
sibling-ref folding for `RDWR = READ | WRITE`).
- cgdot recognises `EnumName.MEMBER` before the local lookup and
emits MOVQ $value, AX directly.
- cgtypeassert short-circuits when either side is enum: cgexpr on
the LHS lands the value in AX with the right integer width; no
tag/unwrap.
main.combined.ww (wwdump/ + w6c/) regenerated by ww build.
This commit is contained in:
@@ -5943,7 +5943,57 @@ 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 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: i32 = e.kind;
|
||||
if (k == N_DOT) {
|
||||
if (e.lhs != nil) {
|
||||
if (e.lhs.kind == N_IDENT) {
|
||||
if (enumlookup(c, e.lhs.str) != nil) { return true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
if (k == N_IDENT) {
|
||||
let lc: *local = localfindnode(c, e.str);
|
||||
if (lc != nil) {
|
||||
if (lc.tnode != nil) {
|
||||
if (lc.tnode.kind == N_TNAME) {
|
||||
if (enumlookup(c, lc.tnode.str) != nil) { return true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (k == N_BIN) {
|
||||
if (isenumexpr(c, e.lhs)) { return true; };
|
||||
if (isenumexpr(c, e.rhs)) { return true; };
|
||||
};
|
||||
if (k == 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 == N_TENUM) { return true; };
|
||||
if (t.kind == 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.
|
||||
@@ -6257,6 +6307,22 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
fn cgdot(c: *cgen, n: *node) void = {
|
||||
let lhs: *node = n.lhs;
|
||||
let fld: str = n.str;
|
||||
// Enum member access: `EnumName.MEMBER` → inline the constant.
|
||||
// Pre-computed at collectenums time; no value-expr fold here.
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == N_IDENT) {
|
||||
let en: *enumtype = enumlookup(c, lhs.str);
|
||||
if (en != nil) {
|
||||
let v: u64;
|
||||
if (enummemberval(en, fld, &v)) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(v: i64);
|
||||
emitline(", AX\n");
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == N_IDENT) {
|
||||
let nm: str = lhs.str;
|
||||
@@ -8203,6 +8269,7 @@ export fn cgfile(c: *cgen, file: *node) void = {
|
||||
c.strlitseq = 0;
|
||||
collectaliases(c, file);
|
||||
collectstructs(c, file);
|
||||
collectenums(c, file);
|
||||
collectdefs(c, file);
|
||||
collectfnrets(c, file);
|
||||
fficollect(c, file);
|
||||
@@ -8303,6 +8370,132 @@ fn aliaslookup(c: *cgen, name: str) *node = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// ---- enum registry --------------------------------------------------
|
||||
//
|
||||
// Mirrors cmd/wcc/check.c's enum resolution at collect time: walk
|
||||
// every `type Foo = enum [storage] { ... }`, pre-compute each
|
||||
// member's u64 value (supporting auto-increment and sibling refs),
|
||||
// and stash them so cgdot can fold `Foo.MEMBER` → MOVQ $value, AX.
|
||||
|
||||
fn enumevalmember(prev: *enummember, e: *node, out: *u64) bool = {
|
||||
if (e == nil) { return false; };
|
||||
let k: i32 = e.kind;
|
||||
if (k == N_INTLIT) { *out = e.uval; return true; };
|
||||
if (k == N_RUNELIT) { *out = e.uval; return true; };
|
||||
if (k == N_TRUE) { *out = 1u64; return true; };
|
||||
if (k == N_FALSE) { *out = 0u64; return true; };
|
||||
if (k == N_IDENT) {
|
||||
let m: *enummember = prev;
|
||||
for (m != nil) {
|
||||
if (streq(m.mname, e.str)) {
|
||||
*out = m.mval;
|
||||
return true;
|
||||
};
|
||||
m = m.emnext;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
if (k == N_BIN) {
|
||||
let a: u64;
|
||||
let b: u64;
|
||||
if (!enumevalmember(prev, e.lhs, &a)) { return false; };
|
||||
if (!enumevalmember(prev, e.rhs, &b)) { return false; };
|
||||
let op: i32 = e.op;
|
||||
if (op == TK_PLUS) { *out = a + b; return true; };
|
||||
if (op == TK_MINUS) { *out = a - b; return true; };
|
||||
if (op == TK_STAR) { *out = a * b; return true; };
|
||||
if (op == TK_SLASH) {
|
||||
if (b == 0u64) { return false; };
|
||||
*out = a / b; return true;
|
||||
};
|
||||
if (op == TK_PERCENT) {
|
||||
if (b == 0u64) { return false; };
|
||||
*out = a % b; return true;
|
||||
};
|
||||
if (op == TK_AMP) { *out = a & b; return true; };
|
||||
if (op == TK_PIPE) { *out = a | b; return true; };
|
||||
if (op == TK_CARET) { *out = a ^ b; return true; };
|
||||
if (op == TK_LSHIFT) { *out = a << b; return true; };
|
||||
if (op == TK_RSHIFT) { *out = a >> b; return true; };
|
||||
return false;
|
||||
};
|
||||
if (k == N_UN) {
|
||||
let v: u64;
|
||||
if (!enumevalmember(prev, e.lhs, &v)) { return false; };
|
||||
let op: i32 = e.op;
|
||||
if (op == TK_MINUS) { *out = (-(v: i64)): u64; return true; };
|
||||
if (op == TK_TILDE) { *out = ~v; return true; };
|
||||
if (op == TK_PLUS) { *out = v; return true; };
|
||||
return false;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
fn collectenums(c: *cgen, file: *node) void = {
|
||||
c.enums = nil;
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
if (d.kind == N_TYPEDECL) {
|
||||
let body: *node = d.lhs;
|
||||
if (body != nil) {
|
||||
if (body.kind == N_TENUM) {
|
||||
let et: *enumtype = amalloc(c.a, 48u64): *enumtype;
|
||||
et.ename = d.str;
|
||||
et.storage = body.lhs;
|
||||
et.members = nil;
|
||||
let prev: u64 = (-1i64): u64;
|
||||
let mhead: *enummember = nil;
|
||||
let mtail: *enummember = nil;
|
||||
let m: *node = body.list;
|
||||
for (m != nil) {
|
||||
let val: u64;
|
||||
if (m.lhs == nil) {
|
||||
val = prev + 1u64;
|
||||
} else {
|
||||
if (!enumevalmember(mhead, m.lhs, &val)) {
|
||||
val = prev + 1u64;
|
||||
};
|
||||
};
|
||||
prev = val;
|
||||
let em: *enummember = amalloc(c.a, 32u64): *enummember;
|
||||
em.mname = m.str;
|
||||
em.mval = val;
|
||||
em.emnext = nil;
|
||||
if (mhead == nil) { mhead = em; mtail = em; }
|
||||
else { mtail.emnext = em; mtail = em; };
|
||||
m = m.next;
|
||||
};
|
||||
et.members = mhead;
|
||||
et.etnext = c.enums;
|
||||
c.enums = et;
|
||||
};
|
||||
};
|
||||
};
|
||||
d = d.next;
|
||||
};
|
||||
};
|
||||
|
||||
fn enumlookup(c: *cgen, name: str) *enumtype = {
|
||||
let e: *enumtype = c.enums;
|
||||
for (e != nil) {
|
||||
if (streq(e.ename, name)) { return e; };
|
||||
e = e.etnext;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
fn enummemberval(en: *enumtype, mname: str, out: *u64) bool = {
|
||||
let m: *enummember = en.members;
|
||||
for (m != nil) {
|
||||
if (streq(m.mname, mname)) {
|
||||
*out = m.mval;
|
||||
return true;
|
||||
};
|
||||
m = m.emnext;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// resolvetype — follow typedef alias chains to a "canonical" type
|
||||
// expr (str/slice/array/struct/...). Stops on cycles via depth limit.
|
||||
fn resolvetype(c: *cgen, t: *node) *node = {
|
||||
@@ -8367,6 +8560,24 @@ type ffi = struct {
|
||||
fnext: *ffi,
|
||||
};
|
||||
|
||||
// enummember — one (name, value) pair belonging to a registered enum.
|
||||
// Values are pre-computed at collect time (Hare allows sibling refs
|
||||
// like `RDWR = READ | WRITE`, so we walk the value expr against the
|
||||
// already-resolved siblings). Lookup is linear; enum cardinality is
|
||||
// usually small.
|
||||
type enummember = struct {
|
||||
mname: str,
|
||||
mval: u64,
|
||||
emnext: *enummember,
|
||||
};
|
||||
|
||||
type enumtype = struct {
|
||||
ename: str,
|
||||
storage: *node, // AST type expr for the storage type (i32 by default)
|
||||
members: *enummember,
|
||||
etnext: *enumtype,
|
||||
};
|
||||
|
||||
def LOOP_MAX: i32 = 16;
|
||||
def DEFER_MAX: i32 = 16;
|
||||
|
||||
@@ -8383,6 +8594,7 @@ type cgen = struct {
|
||||
fnrets: *fnret,
|
||||
aliases: *aliasent,
|
||||
structs: *structinfo,
|
||||
enums: *enumtype,
|
||||
mods: *modent, // non-exported decls → originating module
|
||||
fnname: str,
|
||||
fnret: *node, // declared return type of current fn (or nil)
|
||||
|
||||
@@ -80,6 +80,132 @@ fn aliaslookup(c: *cgen, name: str) *node = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// ---- enum registry --------------------------------------------------
|
||||
//
|
||||
// Mirrors cmd/wcc/check.c's enum resolution at collect time: walk
|
||||
// every `type Foo = enum [storage] { ... }`, pre-compute each
|
||||
// member's u64 value (supporting auto-increment and sibling refs),
|
||||
// and stash them so cgdot can fold `Foo.MEMBER` → MOVQ $value, AX.
|
||||
|
||||
fn enumevalmember(prev: *enummember, e: *node, out: *u64) bool = {
|
||||
if (e == nil) { return false; };
|
||||
let k: i32 = e.kind;
|
||||
if (k == N_INTLIT) { *out = e.uval; return true; };
|
||||
if (k == N_RUNELIT) { *out = e.uval; return true; };
|
||||
if (k == N_TRUE) { *out = 1u64; return true; };
|
||||
if (k == N_FALSE) { *out = 0u64; return true; };
|
||||
if (k == N_IDENT) {
|
||||
let m: *enummember = prev;
|
||||
for (m != nil) {
|
||||
if (streq(m.mname, e.str)) {
|
||||
*out = m.mval;
|
||||
return true;
|
||||
};
|
||||
m = m.emnext;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
if (k == N_BIN) {
|
||||
let a: u64;
|
||||
let b: u64;
|
||||
if (!enumevalmember(prev, e.lhs, &a)) { return false; };
|
||||
if (!enumevalmember(prev, e.rhs, &b)) { return false; };
|
||||
let op: i32 = e.op;
|
||||
if (op == TK_PLUS) { *out = a + b; return true; };
|
||||
if (op == TK_MINUS) { *out = a - b; return true; };
|
||||
if (op == TK_STAR) { *out = a * b; return true; };
|
||||
if (op == TK_SLASH) {
|
||||
if (b == 0u64) { return false; };
|
||||
*out = a / b; return true;
|
||||
};
|
||||
if (op == TK_PERCENT) {
|
||||
if (b == 0u64) { return false; };
|
||||
*out = a % b; return true;
|
||||
};
|
||||
if (op == TK_AMP) { *out = a & b; return true; };
|
||||
if (op == TK_PIPE) { *out = a | b; return true; };
|
||||
if (op == TK_CARET) { *out = a ^ b; return true; };
|
||||
if (op == TK_LSHIFT) { *out = a << b; return true; };
|
||||
if (op == TK_RSHIFT) { *out = a >> b; return true; };
|
||||
return false;
|
||||
};
|
||||
if (k == N_UN) {
|
||||
let v: u64;
|
||||
if (!enumevalmember(prev, e.lhs, &v)) { return false; };
|
||||
let op: i32 = e.op;
|
||||
if (op == TK_MINUS) { *out = (-(v: i64)): u64; return true; };
|
||||
if (op == TK_TILDE) { *out = ~v; return true; };
|
||||
if (op == TK_PLUS) { *out = v; return true; };
|
||||
return false;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
fn collectenums(c: *cgen, file: *node) void = {
|
||||
c.enums = nil;
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
if (d.kind == N_TYPEDECL) {
|
||||
let body: *node = d.lhs;
|
||||
if (body != nil) {
|
||||
if (body.kind == N_TENUM) {
|
||||
let et: *enumtype = amalloc(c.a, 48u64): *enumtype;
|
||||
et.ename = d.str;
|
||||
et.storage = body.lhs;
|
||||
et.members = nil;
|
||||
let prev: u64 = (-1i64): u64;
|
||||
let mhead: *enummember = nil;
|
||||
let mtail: *enummember = nil;
|
||||
let m: *node = body.list;
|
||||
for (m != nil) {
|
||||
let val: u64;
|
||||
if (m.lhs == nil) {
|
||||
val = prev + 1u64;
|
||||
} else {
|
||||
if (!enumevalmember(mhead, m.lhs, &val)) {
|
||||
val = prev + 1u64;
|
||||
};
|
||||
};
|
||||
prev = val;
|
||||
let em: *enummember = amalloc(c.a, 32u64): *enummember;
|
||||
em.mname = m.str;
|
||||
em.mval = val;
|
||||
em.emnext = nil;
|
||||
if (mhead == nil) { mhead = em; mtail = em; }
|
||||
else { mtail.emnext = em; mtail = em; };
|
||||
m = m.next;
|
||||
};
|
||||
et.members = mhead;
|
||||
et.etnext = c.enums;
|
||||
c.enums = et;
|
||||
};
|
||||
};
|
||||
};
|
||||
d = d.next;
|
||||
};
|
||||
};
|
||||
|
||||
fn enumlookup(c: *cgen, name: str) *enumtype = {
|
||||
let e: *enumtype = c.enums;
|
||||
for (e != nil) {
|
||||
if (streq(e.ename, name)) { return e; };
|
||||
e = e.etnext;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
fn enummemberval(en: *enumtype, mname: str, out: *u64) bool = {
|
||||
let m: *enummember = en.members;
|
||||
for (m != nil) {
|
||||
if (streq(m.mname, mname)) {
|
||||
*out = m.mval;
|
||||
return true;
|
||||
};
|
||||
m = m.emnext;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// resolvetype — follow typedef alias chains to a "canonical" type
|
||||
// expr (str/slice/array/struct/...). Stops on cycles via depth limit.
|
||||
fn resolvetype(c: *cgen, t: *node) *node = {
|
||||
@@ -144,6 +270,24 @@ type ffi = struct {
|
||||
fnext: *ffi,
|
||||
};
|
||||
|
||||
// enummember — one (name, value) pair belonging to a registered enum.
|
||||
// Values are pre-computed at collect time (Hare allows sibling refs
|
||||
// like `RDWR = READ | WRITE`, so we walk the value expr against the
|
||||
// already-resolved siblings). Lookup is linear; enum cardinality is
|
||||
// usually small.
|
||||
type enummember = struct {
|
||||
mname: str,
|
||||
mval: u64,
|
||||
emnext: *enummember,
|
||||
};
|
||||
|
||||
type enumtype = struct {
|
||||
ename: str,
|
||||
storage: *node, // AST type expr for the storage type (i32 by default)
|
||||
members: *enummember,
|
||||
etnext: *enumtype,
|
||||
};
|
||||
|
||||
def LOOP_MAX: i32 = 16;
|
||||
def DEFER_MAX: i32 = 16;
|
||||
|
||||
@@ -160,6 +304,7 @@ type cgen = struct {
|
||||
fnrets: *fnret,
|
||||
aliases: *aliasent,
|
||||
structs: *structinfo,
|
||||
enums: *enumtype,
|
||||
mods: *modent, // non-exported decls → originating module
|
||||
fnname: str,
|
||||
fnret: *node, // declared return type of current fn (or nil)
|
||||
|
||||
@@ -279,6 +279,7 @@ export fn cgfile(c: *cgen, file: *node) void = {
|
||||
c.strlitseq = 0;
|
||||
collectaliases(c, file);
|
||||
collectstructs(c, file);
|
||||
collectenums(c, file);
|
||||
collectdefs(c, file);
|
||||
collectfnrets(c, file);
|
||||
fficollect(c, file);
|
||||
|
||||
@@ -251,7 +251,57 @@ 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 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: i32 = e.kind;
|
||||
if (k == N_DOT) {
|
||||
if (e.lhs != nil) {
|
||||
if (e.lhs.kind == N_IDENT) {
|
||||
if (enumlookup(c, e.lhs.str) != nil) { return true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
if (k == N_IDENT) {
|
||||
let lc: *local = localfindnode(c, e.str);
|
||||
if (lc != nil) {
|
||||
if (lc.tnode != nil) {
|
||||
if (lc.tnode.kind == N_TNAME) {
|
||||
if (enumlookup(c, lc.tnode.str) != nil) { return true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (k == N_BIN) {
|
||||
if (isenumexpr(c, e.lhs)) { return true; };
|
||||
if (isenumexpr(c, e.rhs)) { return true; };
|
||||
};
|
||||
if (k == 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 == N_TENUM) { return true; };
|
||||
if (t.kind == 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.
|
||||
@@ -565,6 +615,22 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
fn cgdot(c: *cgen, n: *node) void = {
|
||||
let lhs: *node = n.lhs;
|
||||
let fld: str = n.str;
|
||||
// Enum member access: `EnumName.MEMBER` → inline the constant.
|
||||
// Pre-computed at collectenums time; no value-expr fold here.
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == N_IDENT) {
|
||||
let en: *enumtype = enumlookup(c, lhs.str);
|
||||
if (en != nil) {
|
||||
let v: u64;
|
||||
if (enummemberval(en, fld, &v)) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(v: i64);
|
||||
emitline(", AX\n");
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == N_IDENT) {
|
||||
let nm: str = lhs.str;
|
||||
|
||||
@@ -5943,7 +5943,57 @@ 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 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: i32 = e.kind;
|
||||
if (k == N_DOT) {
|
||||
if (e.lhs != nil) {
|
||||
if (e.lhs.kind == N_IDENT) {
|
||||
if (enumlookup(c, e.lhs.str) != nil) { return true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
if (k == N_IDENT) {
|
||||
let lc: *local = localfindnode(c, e.str);
|
||||
if (lc != nil) {
|
||||
if (lc.tnode != nil) {
|
||||
if (lc.tnode.kind == N_TNAME) {
|
||||
if (enumlookup(c, lc.tnode.str) != nil) { return true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (k == N_BIN) {
|
||||
if (isenumexpr(c, e.lhs)) { return true; };
|
||||
if (isenumexpr(c, e.rhs)) { return true; };
|
||||
};
|
||||
if (k == 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 == N_TENUM) { return true; };
|
||||
if (t.kind == 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.
|
||||
@@ -6257,6 +6307,22 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
fn cgdot(c: *cgen, n: *node) void = {
|
||||
let lhs: *node = n.lhs;
|
||||
let fld: str = n.str;
|
||||
// Enum member access: `EnumName.MEMBER` → inline the constant.
|
||||
// Pre-computed at collectenums time; no value-expr fold here.
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == N_IDENT) {
|
||||
let en: *enumtype = enumlookup(c, lhs.str);
|
||||
if (en != nil) {
|
||||
let v: u64;
|
||||
if (enummemberval(en, fld, &v)) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(v: i64);
|
||||
emitline(", AX\n");
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == N_IDENT) {
|
||||
let nm: str = lhs.str;
|
||||
@@ -8203,6 +8269,7 @@ export fn cgfile(c: *cgen, file: *node) void = {
|
||||
c.strlitseq = 0;
|
||||
collectaliases(c, file);
|
||||
collectstructs(c, file);
|
||||
collectenums(c, file);
|
||||
collectdefs(c, file);
|
||||
collectfnrets(c, file);
|
||||
fficollect(c, file);
|
||||
@@ -8303,6 +8370,132 @@ fn aliaslookup(c: *cgen, name: str) *node = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// ---- enum registry --------------------------------------------------
|
||||
//
|
||||
// Mirrors cmd/wcc/check.c's enum resolution at collect time: walk
|
||||
// every `type Foo = enum [storage] { ... }`, pre-compute each
|
||||
// member's u64 value (supporting auto-increment and sibling refs),
|
||||
// and stash them so cgdot can fold `Foo.MEMBER` → MOVQ $value, AX.
|
||||
|
||||
fn enumevalmember(prev: *enummember, e: *node, out: *u64) bool = {
|
||||
if (e == nil) { return false; };
|
||||
let k: i32 = e.kind;
|
||||
if (k == N_INTLIT) { *out = e.uval; return true; };
|
||||
if (k == N_RUNELIT) { *out = e.uval; return true; };
|
||||
if (k == N_TRUE) { *out = 1u64; return true; };
|
||||
if (k == N_FALSE) { *out = 0u64; return true; };
|
||||
if (k == N_IDENT) {
|
||||
let m: *enummember = prev;
|
||||
for (m != nil) {
|
||||
if (streq(m.mname, e.str)) {
|
||||
*out = m.mval;
|
||||
return true;
|
||||
};
|
||||
m = m.emnext;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
if (k == N_BIN) {
|
||||
let a: u64;
|
||||
let b: u64;
|
||||
if (!enumevalmember(prev, e.lhs, &a)) { return false; };
|
||||
if (!enumevalmember(prev, e.rhs, &b)) { return false; };
|
||||
let op: i32 = e.op;
|
||||
if (op == TK_PLUS) { *out = a + b; return true; };
|
||||
if (op == TK_MINUS) { *out = a - b; return true; };
|
||||
if (op == TK_STAR) { *out = a * b; return true; };
|
||||
if (op == TK_SLASH) {
|
||||
if (b == 0u64) { return false; };
|
||||
*out = a / b; return true;
|
||||
};
|
||||
if (op == TK_PERCENT) {
|
||||
if (b == 0u64) { return false; };
|
||||
*out = a % b; return true;
|
||||
};
|
||||
if (op == TK_AMP) { *out = a & b; return true; };
|
||||
if (op == TK_PIPE) { *out = a | b; return true; };
|
||||
if (op == TK_CARET) { *out = a ^ b; return true; };
|
||||
if (op == TK_LSHIFT) { *out = a << b; return true; };
|
||||
if (op == TK_RSHIFT) { *out = a >> b; return true; };
|
||||
return false;
|
||||
};
|
||||
if (k == N_UN) {
|
||||
let v: u64;
|
||||
if (!enumevalmember(prev, e.lhs, &v)) { return false; };
|
||||
let op: i32 = e.op;
|
||||
if (op == TK_MINUS) { *out = (-(v: i64)): u64; return true; };
|
||||
if (op == TK_TILDE) { *out = ~v; return true; };
|
||||
if (op == TK_PLUS) { *out = v; return true; };
|
||||
return false;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
fn collectenums(c: *cgen, file: *node) void = {
|
||||
c.enums = nil;
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
if (d.kind == N_TYPEDECL) {
|
||||
let body: *node = d.lhs;
|
||||
if (body != nil) {
|
||||
if (body.kind == N_TENUM) {
|
||||
let et: *enumtype = amalloc(c.a, 48u64): *enumtype;
|
||||
et.ename = d.str;
|
||||
et.storage = body.lhs;
|
||||
et.members = nil;
|
||||
let prev: u64 = (-1i64): u64;
|
||||
let mhead: *enummember = nil;
|
||||
let mtail: *enummember = nil;
|
||||
let m: *node = body.list;
|
||||
for (m != nil) {
|
||||
let val: u64;
|
||||
if (m.lhs == nil) {
|
||||
val = prev + 1u64;
|
||||
} else {
|
||||
if (!enumevalmember(mhead, m.lhs, &val)) {
|
||||
val = prev + 1u64;
|
||||
};
|
||||
};
|
||||
prev = val;
|
||||
let em: *enummember = amalloc(c.a, 32u64): *enummember;
|
||||
em.mname = m.str;
|
||||
em.mval = val;
|
||||
em.emnext = nil;
|
||||
if (mhead == nil) { mhead = em; mtail = em; }
|
||||
else { mtail.emnext = em; mtail = em; };
|
||||
m = m.next;
|
||||
};
|
||||
et.members = mhead;
|
||||
et.etnext = c.enums;
|
||||
c.enums = et;
|
||||
};
|
||||
};
|
||||
};
|
||||
d = d.next;
|
||||
};
|
||||
};
|
||||
|
||||
fn enumlookup(c: *cgen, name: str) *enumtype = {
|
||||
let e: *enumtype = c.enums;
|
||||
for (e != nil) {
|
||||
if (streq(e.ename, name)) { return e; };
|
||||
e = e.etnext;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
fn enummemberval(en: *enumtype, mname: str, out: *u64) bool = {
|
||||
let m: *enummember = en.members;
|
||||
for (m != nil) {
|
||||
if (streq(m.mname, mname)) {
|
||||
*out = m.mval;
|
||||
return true;
|
||||
};
|
||||
m = m.emnext;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// resolvetype — follow typedef alias chains to a "canonical" type
|
||||
// expr (str/slice/array/struct/...). Stops on cycles via depth limit.
|
||||
fn resolvetype(c: *cgen, t: *node) *node = {
|
||||
@@ -8367,6 +8560,24 @@ type ffi = struct {
|
||||
fnext: *ffi,
|
||||
};
|
||||
|
||||
// enummember — one (name, value) pair belonging to a registered enum.
|
||||
// Values are pre-computed at collect time (Hare allows sibling refs
|
||||
// like `RDWR = READ | WRITE`, so we walk the value expr against the
|
||||
// already-resolved siblings). Lookup is linear; enum cardinality is
|
||||
// usually small.
|
||||
type enummember = struct {
|
||||
mname: str,
|
||||
mval: u64,
|
||||
emnext: *enummember,
|
||||
};
|
||||
|
||||
type enumtype = struct {
|
||||
ename: str,
|
||||
storage: *node, // AST type expr for the storage type (i32 by default)
|
||||
members: *enummember,
|
||||
etnext: *enumtype,
|
||||
};
|
||||
|
||||
def LOOP_MAX: i32 = 16;
|
||||
def DEFER_MAX: i32 = 16;
|
||||
|
||||
@@ -8383,6 +8594,7 @@ type cgen = struct {
|
||||
fnrets: *fnret,
|
||||
aliases: *aliasent,
|
||||
structs: *structinfo,
|
||||
enums: *enumtype,
|
||||
mods: *modent, // non-exported decls → originating module
|
||||
fnname: str,
|
||||
fnret: *node, // declared return type of current fn (or nil)
|
||||
|
||||
@@ -143,6 +143,12 @@ main(void)
|
||||
" return n * fact(n - 1);\n"
|
||||
"};\n"
|
||||
"fn main() i32 = { return fact(5); };" },
|
||||
{ "enum",
|
||||
"type mode = enum u8 { R = 1, W = 2, RW = R | W };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let m: mode = mode.RW;\n"
|
||||
" return m as i32;\n"
|
||||
"};" },
|
||||
{ NULL, NULL },
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user