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:
2026-05-12 04:25:28 +09:00
parent 9227a07f91
commit f597ce67f6
6 changed files with 642 additions and 0 deletions

View File

@@ -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)