From f597ce67f670451c30f77ac4a9265f6221434870 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 12 May 2026 04:25:28 +0900 Subject: [PATCH] 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. --- selfhost/cmd/w6c/main.combined.ww | 212 +++++++++++++++++++++++++++ selfhost/cmd/wcc/cgen.ww | 145 ++++++++++++++++++ selfhost/cmd/wcc/cgendecl.ww | 1 + selfhost/cmd/wcc/cgenexpr.ww | 66 +++++++++ selfhost/cmd/wwdump/main.combined.ww | 212 +++++++++++++++++++++++++++ test/wcc/994_w6c_ww.c | 6 + 6 files changed, 642 insertions(+) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index a8cb0762..76a0c799 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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) diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index fe2387c6..90956118 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -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) diff --git a/selfhost/cmd/wcc/cgendecl.ww b/selfhost/cmd/wcc/cgendecl.ww index 5a4b3f13..deff3490 100644 --- a/selfhost/cmd/wcc/cgendecl.ww +++ b/selfhost/cmd/wcc/cgendecl.ww @@ -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); diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 110e47ed..b715752c 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -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; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 75e2a352..2e7c9e5a 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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) diff --git a/test/wcc/994_w6c_ww.c b/test/wcc/994_w6c_ww.c index 43a12562..af962807 100644 --- a/test/wcc/994_w6c_ww.c +++ b/test/wcc/994_w6c_ww.c @@ -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 }, };