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;
|
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 = {
|
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.
|
||||||
@@ -6257,6 +6307,22 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
|||||||
fn cgdot(c: *cgen, n: *node) void = {
|
fn cgdot(c: *cgen, n: *node) void = {
|
||||||
let lhs: *node = n.lhs;
|
let lhs: *node = n.lhs;
|
||||||
let fld: str = n.str;
|
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 != nil) {
|
||||||
if (lhs.kind == N_IDENT) {
|
if (lhs.kind == N_IDENT) {
|
||||||
let nm: str = lhs.str;
|
let nm: str = lhs.str;
|
||||||
@@ -8203,6 +8269,7 @@ export fn cgfile(c: *cgen, file: *node) void = {
|
|||||||
c.strlitseq = 0;
|
c.strlitseq = 0;
|
||||||
collectaliases(c, file);
|
collectaliases(c, file);
|
||||||
collectstructs(c, file);
|
collectstructs(c, file);
|
||||||
|
collectenums(c, file);
|
||||||
collectdefs(c, file);
|
collectdefs(c, file);
|
||||||
collectfnrets(c, file);
|
collectfnrets(c, file);
|
||||||
fficollect(c, file);
|
fficollect(c, file);
|
||||||
@@ -8303,6 +8370,132 @@ fn aliaslookup(c: *cgen, name: str) *node = {
|
|||||||
return nil;
|
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
|
// resolvetype — follow typedef alias chains to a "canonical" type
|
||||||
// expr (str/slice/array/struct/...). Stops on cycles via depth limit.
|
// expr (str/slice/array/struct/...). Stops on cycles via depth limit.
|
||||||
fn resolvetype(c: *cgen, t: *node) *node = {
|
fn resolvetype(c: *cgen, t: *node) *node = {
|
||||||
@@ -8367,6 +8560,24 @@ type ffi = struct {
|
|||||||
fnext: *ffi,
|
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 LOOP_MAX: i32 = 16;
|
||||||
def DEFER_MAX: i32 = 16;
|
def DEFER_MAX: i32 = 16;
|
||||||
|
|
||||||
@@ -8383,6 +8594,7 @@ type cgen = struct {
|
|||||||
fnrets: *fnret,
|
fnrets: *fnret,
|
||||||
aliases: *aliasent,
|
aliases: *aliasent,
|
||||||
structs: *structinfo,
|
structs: *structinfo,
|
||||||
|
enums: *enumtype,
|
||||||
mods: *modent, // non-exported decls → originating module
|
mods: *modent, // non-exported decls → originating module
|
||||||
fnname: str,
|
fnname: str,
|
||||||
fnret: *node, // declared return type of current fn (or nil)
|
fnret: *node, // declared return type of current fn (or nil)
|
||||||
|
|||||||
@@ -80,6 +80,132 @@ fn aliaslookup(c: *cgen, name: str) *node = {
|
|||||||
return nil;
|
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
|
// resolvetype — follow typedef alias chains to a "canonical" type
|
||||||
// expr (str/slice/array/struct/...). Stops on cycles via depth limit.
|
// expr (str/slice/array/struct/...). Stops on cycles via depth limit.
|
||||||
fn resolvetype(c: *cgen, t: *node) *node = {
|
fn resolvetype(c: *cgen, t: *node) *node = {
|
||||||
@@ -144,6 +270,24 @@ type ffi = struct {
|
|||||||
fnext: *ffi,
|
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 LOOP_MAX: i32 = 16;
|
||||||
def DEFER_MAX: i32 = 16;
|
def DEFER_MAX: i32 = 16;
|
||||||
|
|
||||||
@@ -160,6 +304,7 @@ type cgen = struct {
|
|||||||
fnrets: *fnret,
|
fnrets: *fnret,
|
||||||
aliases: *aliasent,
|
aliases: *aliasent,
|
||||||
structs: *structinfo,
|
structs: *structinfo,
|
||||||
|
enums: *enumtype,
|
||||||
mods: *modent, // non-exported decls → originating module
|
mods: *modent, // non-exported decls → originating module
|
||||||
fnname: str,
|
fnname: str,
|
||||||
fnret: *node, // declared return type of current fn (or nil)
|
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;
|
c.strlitseq = 0;
|
||||||
collectaliases(c, file);
|
collectaliases(c, file);
|
||||||
collectstructs(c, file);
|
collectstructs(c, file);
|
||||||
|
collectenums(c, file);
|
||||||
collectdefs(c, file);
|
collectdefs(c, file);
|
||||||
collectfnrets(c, file);
|
collectfnrets(c, file);
|
||||||
fficollect(c, file);
|
fficollect(c, file);
|
||||||
|
|||||||
@@ -251,7 +251,57 @@ 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 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 = {
|
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.
|
||||||
@@ -565,6 +615,22 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
|||||||
fn cgdot(c: *cgen, n: *node) void = {
|
fn cgdot(c: *cgen, n: *node) void = {
|
||||||
let lhs: *node = n.lhs;
|
let lhs: *node = n.lhs;
|
||||||
let fld: str = n.str;
|
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 != nil) {
|
||||||
if (lhs.kind == N_IDENT) {
|
if (lhs.kind == N_IDENT) {
|
||||||
let nm: str = lhs.str;
|
let nm: str = lhs.str;
|
||||||
|
|||||||
@@ -5943,7 +5943,57 @@ 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 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 = {
|
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.
|
||||||
@@ -6257,6 +6307,22 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
|||||||
fn cgdot(c: *cgen, n: *node) void = {
|
fn cgdot(c: *cgen, n: *node) void = {
|
||||||
let lhs: *node = n.lhs;
|
let lhs: *node = n.lhs;
|
||||||
let fld: str = n.str;
|
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 != nil) {
|
||||||
if (lhs.kind == N_IDENT) {
|
if (lhs.kind == N_IDENT) {
|
||||||
let nm: str = lhs.str;
|
let nm: str = lhs.str;
|
||||||
@@ -8203,6 +8269,7 @@ export fn cgfile(c: *cgen, file: *node) void = {
|
|||||||
c.strlitseq = 0;
|
c.strlitseq = 0;
|
||||||
collectaliases(c, file);
|
collectaliases(c, file);
|
||||||
collectstructs(c, file);
|
collectstructs(c, file);
|
||||||
|
collectenums(c, file);
|
||||||
collectdefs(c, file);
|
collectdefs(c, file);
|
||||||
collectfnrets(c, file);
|
collectfnrets(c, file);
|
||||||
fficollect(c, file);
|
fficollect(c, file);
|
||||||
@@ -8303,6 +8370,132 @@ fn aliaslookup(c: *cgen, name: str) *node = {
|
|||||||
return nil;
|
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
|
// resolvetype — follow typedef alias chains to a "canonical" type
|
||||||
// expr (str/slice/array/struct/...). Stops on cycles via depth limit.
|
// expr (str/slice/array/struct/...). Stops on cycles via depth limit.
|
||||||
fn resolvetype(c: *cgen, t: *node) *node = {
|
fn resolvetype(c: *cgen, t: *node) *node = {
|
||||||
@@ -8367,6 +8560,24 @@ type ffi = struct {
|
|||||||
fnext: *ffi,
|
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 LOOP_MAX: i32 = 16;
|
||||||
def DEFER_MAX: i32 = 16;
|
def DEFER_MAX: i32 = 16;
|
||||||
|
|
||||||
@@ -8383,6 +8594,7 @@ type cgen = struct {
|
|||||||
fnrets: *fnret,
|
fnrets: *fnret,
|
||||||
aliases: *aliasent,
|
aliases: *aliasent,
|
||||||
structs: *structinfo,
|
structs: *structinfo,
|
||||||
|
enums: *enumtype,
|
||||||
mods: *modent, // non-exported decls → originating module
|
mods: *modent, // non-exported decls → originating module
|
||||||
fnname: str,
|
fnname: str,
|
||||||
fnret: *node, // declared return type of current fn (or nil)
|
fnret: *node, // declared return type of current fn (or nil)
|
||||||
|
|||||||
@@ -143,6 +143,12 @@ main(void)
|
|||||||
" return n * fact(n - 1);\n"
|
" return n * fact(n - 1);\n"
|
||||||
"};\n"
|
"};\n"
|
||||||
"fn main() i32 = { return fact(5); };" },
|
"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 },
|
{ NULL, NULL },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user