Files
ViperC/App/lib/core/Gen/Class/ClassGenEnum.vp
T
TermiNexus 3b9deb08d8 fix(narrow): ClassGenEnum RenumClassVariantCounts 元素读取改用 int(...)
n_first_v(1029) 与 vcount(1010/1055) 承接 sizet* 元素,原先 64->32 隐式窄化;改用显式 int(...) 保持原截断语义
2026-10-11 10:00:05 +08:00

1182 lines
69 KiB
Plaintext

import lib.core.Gen.GenTypes as GenTypes
import t, c
from stdint import *
import ast
import mlirlite
import memhub
import string
import viperlib
import lib.core.VLogger as VLogger
import lib.core.Gen.Translator as HT
import lib.core.CompilerFactory as CompilerFactory
from . import ClassGenBase
# Local constant copies (originally defined in HandlesEnum/HandlesClassDef; lost when the file was split).
# Undefined names fold to 0 in constant context, silently skipping enum member collection and CEnum registration.
define ENUM_MEMBER_MAX_LOCAL = 128
define RENUM_CLASS_MAX = 64
define RENUM_VARIANT_PER_CLASS = 32
define RENUM_ALIAS_MAX = 512
# ClassGenEnum - split from ClassGenMixin (stage D): enum/union/renum definitions and variant tables.
# ============================================================
novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
"""enum/union/renum definitions and variant tables"""
def _resolve_enum_member_type(self, pool: memhub.MemBuddy*, ann_node: ast.AST*, imported_modules: str, from_imports: str) -> mlirlite.LLVMType*:
"""Resolve an enum member's type annotation to an LLVM integer type"""
if ann_node is None: return mlirlite.Int32(pool)
k: int = ann_node.kind()
# Attribute node: t.State / int / int8 etc.
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(ann_node)
if at.attr is None:
# [S4 fail-fast] Attribute missing the member name (corrupt AST); no silent i32 fallback
self.Gen.TypeGenErr.fatal_error(ann_node, "enum member type annotation Attribute is missing the member name", "TypeError")
return mlirlite.Int32(pool) # unreachable (fatal_error already exited)
# t.State -> default i32
if string.strcmp(at.attr, "State") == 0: return mlirlite.Int32(pool)
# [enum bool rule] `t.CBool` and bare `bool` both materialize as 8 bits in enum member annotations,
# else the same member would split into i1 / i8; the global `t.CBool` stays i1 (only enums differ).
if string.strcmp(at.attr, "CBool") == 0: return mlirlite.Int8(pool)
# Other t.CXxx types: unified (CTypedef dynamic table first, fall back to map_t_type)
ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_typedef_or_map(pool, at.attr, imported_modules, from_imports)
if ty is not None: return ty
# [S4 fail-fast] annotated but unresolvable -> no silent i32 fallback
self.Gen.TypeGenErr.fatal_error(ann_node, f"failed to resolve enum member type annotation: t.{at.attr}", "NameError")
return mlirlite.Int32(pool) # unreachable (fatal_error already exited)
# Name node: from-import form (State / CInt)
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(ann_node)
if nm.id is None:
# [S4 fail-fast] Name missing the identifier (corrupt AST); no silent i32 fallback
self.Gen.TypeGenErr.fatal_error(ann_node, "enum member type annotation Name is missing the identifier", "TypeError")
return mlirlite.Int32(pool) # unreachable (fatal_error already exited)
if string.strcmp(nm.id, "State") == 0: return mlirlite.Int32(pool)
# Builtin bare integer types (same rule as resolve_annotation_type's Name branch): enum members are
# integer constants, so `int` / `char` / `bool` pass through; other bare names fail fast on a typedef miss.
if string.strcmp(nm.id, "int") == 0: return mlirlite.Int32(pool)
if string.strcmp(nm.id, "char") == 0: return mlirlite.Int8(pool)
if string.strcmp(nm.id, "bool") == 0:
# [enum bool rule] enum storage must be byte-addressable, and i1 cannot be an enum width,
# so an enum member's bool is 8 bits. This does not change the global bool = i1.
return mlirlite.Int8(pool)
ty2: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_typedef_or_map(pool, nm.id, imported_modules, from_imports)
if ty2 is not None: return ty2
# [S4 fail-fast] annotated but unresolvable -> no silent i32 fallback
self.Gen.TypeGenErr.fatal_error(ann_node, f"failed to resolve enum member type annotation: {nm.id}", "NameError")
return mlirlite.Int32(pool) # unreachable (fatal_error already exited)
# BinOp node: t.State | int8 (recurse to find the concrete type)
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(ann_node)
if bop.op == ast.OpKind.BitOr:
left_ty: mlirlite.LLVMType* = self._resolve_enum_member_type(pool, bop.left, imported_modules, from_imports)
# Skip t.State (the i32 default); prefer a concrete type
left_bits: int = self.Gen.TypeGenErr.get_llvm_type_bits(left_ty)
if left_bits != 0 and not self._is_state_annotation(bop.left): return left_ty
right_ty: mlirlite.LLVMType* = self._resolve_enum_member_type(pool, bop.right, imported_modules, from_imports)
if right_ty is not None and not self._is_state_annotation(bop.right): return right_ty
# Both sides are State or undecidable; return the wider one
right_bits: int = self.Gen.TypeGenErr.get_llvm_type_bits(right_ty)
if left_bits >= right_bits: return left_ty
return right_ty
# fail-fast: BinOp but not BitOr (unknown union op); no silent i32 fallback
self.Gen.TypeGenErr.fatal_error(ann_node, f"failed to resolve enum member type annotation: {self.Gen.TypeGenResolve.annotation_to_str(ann_node)}", "TypeError")
return mlirlite.Int32(pool) # unreachable (fatal_error already exited)
# fail-fast: unknown annotation node kind; no silent i32 fallback
self.Gen.TypeGenErr.fatal_error(ann_node, f"failed to resolve enum member type annotation: {self.Gen.TypeGenResolve.annotation_to_str(ann_node)}", "TypeError")
return mlirlite.Int32(pool) # unreachable (fatal_error already exited)
def _is_state_annotation(self, node: ast.AST*) -> int:
"""Check whether an annotation node is t.State, 1=yes / 0=no"""
if node is None: return 0
k: int = node.kind()
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(node)
if at.attr is not None and string.strcmp(at.attr, "State") == 0: return 1
return 0
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm.id is not None and string.strcmp(nm.id, "State") == 0: return 1
return 0
return 0
def _enum_annotation_name_is_ptr(self, node: ast.AST*) -> int:
"""Whether the annotation's top name (Name/Attribute/Subscript) is a pointer form, 1=yes / 0=no"""
if node is None: return 0
k: int = node.kind()
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(node)
if at.attr is not None:
if string.strcmp(at.attr, "CPtr") == 0: return 1
if string.strcmp(at.attr, "CRef") == 0: return 1
if string.strcmp(at.attr, "CTempRef") == 0: return 1
if string.strcmp(at.attr, "CAutoPtr") == 0: return 1
if string.strcmp(at.attr, "rawptr") == 0: return 1
if string.strcmp(at.attr, "str") == 0: return 1
if string.strcmp(at.attr, "bytes") == 0: return 1
return 0
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm.id is not None:
if string.strcmp(nm.id, "CPtr") == 0: return 1
if string.strcmp(nm.id, "CRef") == 0: return 1
if string.strcmp(nm.id, "CTempRef") == 0: return 1
if string.strcmp(nm.id, "CAutoPtr") == 0: return 1
if string.strcmp(nm.id, "rawptr") == 0: return 1
if string.strcmp(nm.id, "str") == 0: return 1
if string.strcmp(nm.id, "bytes") == 0: return 1
return 0
if k == ast.ASTKind.Subscript:
sub: ast.Subscript* = ast.Subscript*(node)
if sub.value is not None: return self._enum_annotation_name_is_ptr(sub.value)
return 0
return 0
def _enum_member_annotation_is_ptr(self, ann_node: ast.AST*) -> int:
"""Whether an enum member annotation (checking each union side) contains a pointer type, 1=yes / 0=no"""
if ann_node is None: return 0
if ann_node.kind() == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(ann_node)
if bop.op != ast.OpKind.BitOr: return 0
if self._enum_member_annotation_is_ptr(bop.left) == 1: return 1
return self._enum_member_annotation_is_ptr(bop.right)
return self._enum_annotation_name_is_ptr(ann_node)
def _extract_int_value(self, node: ast.AST*) -> int64:
"""Extract an integer value from an AST node, 0 on failure"""
if node is None: return 0
k: int = node.kind()
if k == ast.ASTKind.Constant:
cn: ast.Constant* = ast.Constant*(node)
if cn.const_kind == ast.CONST_INT: return cn.int_val
return 0
if k == ast.ASTKind.UnaryOp:
uo: ast.UnaryOp* = ast.UnaryOp*(node)
if uo.op == ast.OpKind.USub:
inner: int64 = self._extract_int_value(uo.operand)
return -inner
return 0
return 0
# "Visible name vs registered name" fallback check for enum members (same rule source as TPC).
# Any unrecognized member form makes the visible-name count exceed the registered count -> fail-fast.
def _enum_elts_visible_names(self, elts: list[ast.AST*]*) -> int:
"""Count visible names in an element table (shared by Tuple/List)."""
if elts is None: return 0
n: int = 0
for i in range(len(elts)): n += self._enum_target_visible_names(elts.get(i))
return n
def _enum_target_visible_names(self, node: ast.AST*) -> int:
"""Count visible names at a member-name position (Name / Tuple / List items, Starred recursion)."""
if node is None: return 0
k: int = node.kind()
if k == ast.ASTKind.Name: return 1
if k == ast.ASTKind.Tuple:
tup: ast.Tuple* = ast.Tuple*(node)
return self._enum_elts_visible_names(tup.elts)
if k == ast.ASTKind.List:
lst: ast.List* = ast.List*(node)
return self._enum_elts_visible_names(lst.elts)
if k == ast.ASTKind.Starred:
st: ast.Starred* = ast.Starred*(node)
return self._enum_target_visible_names(st.value)
return 0
def _enum_body_visible_names(self, children: list[ast.AST*]*) -> int:
"""Total source-visible member names in an enum class body (including forms the registrar rejects)."""
if children is None: return 0
n: int = 0
for ci in range(len(children)):
stmt: ast.AST* = children.get(ci)
if stmt is None: continue
sk: int = stmt.kind()
if sk == ast.ASTKind.Assign:
ag: ast.Assign* = ast.Assign*(stmt)
if ag.targets is not None:
for ti in range(len(ag.targets)): n += self._enum_target_visible_names(ag.targets.get(ti))
elif sk == ast.ASTKind.AnnAssign:
aa: ast.AnnAssign* = ast.AnnAssign*(stmt)
n += self._enum_target_visible_names(aa.target)
elif sk == ast.ASTKind.Expr:
ex: ast.Expr* = ast.Expr*(stmt)
n += self._enum_target_visible_names(ex.value)
return n
def _enum_first_unregistered_node(self, children: list[ast.AST*]*) -> ast.AST*:
"""First statement with visible member names the registrar rejects, for fail-fast line location; None if none."""
if children is None: return None
for ci in range(len(children)):
stmt: ast.AST* = children.get(ci)
if stmt is None: continue
sk: int = stmt.kind()
if sk == ast.ASTKind.Expr:
ex: ast.Expr* = ast.Expr*(stmt)
if self._enum_target_visible_names(ex.value) > 0: return stmt
elif sk == ast.ASTKind.AnnAssign:
aa: ast.AnnAssign* = ast.AnnAssign*(stmt)
if aa.target is not None and aa.target.kind() != ast.ASTKind.Name and self._enum_target_visible_names(aa.target) > 0: return stmt
elif sk == ast.ASTKind.Assign:
ag: ast.Assign* = ast.Assign*(stmt)
if ag.targets is not None:
for ti in range(len(ag.targets)):
tgt: ast.AST* = ag.targets.get(ti)
if tgt is not None and tgt.kind() != ast.ASTKind.Name and self._enum_target_visible_names(tgt) > 0: return stmt
return None
def _translate_enum_def(self, trans: HT.Translator*, cd: ast.ClassDef*) -> int:
"""Translate an enum class definition, return 0"""
if cd is None or cd.name is None or trans is None: return 0
pool: memhub.MemBuddy* = trans.Pool
class_name: str = cd.name
# Do not skip by global same-name: cross-module enums may share a name (e.g. FRESULT), and
# skipping would drop the later module's members. register_enum/add_enum_member are idempotent.
children: list[ast.AST*]* = cd.children
if children is None:
VLogger.warning(f"{class_name} has no body", "ENUM")
return 0
cn: sizet = len(children)
# Temporary arrays holding member info
member_names_buf: sizet* = pool.alloc(8 * ENUM_MEMBER_MAX_LOCAL)
member_types_buf: sizet* = pool.alloc(8 * ENUM_MEMBER_MAX_LOCAL)
member_values_buf: int64* = pool.alloc(8 * ENUM_MEMBER_MAX_LOCAL)
if member_names_buf is None or member_types_buf is None or member_values_buf is None: return 0
string.memset(member_names_buf, 0, 8 * ENUM_MEMBER_MAX_LOCAL)
string.memset(member_types_buf, 0, 8 * ENUM_MEMBER_MAX_LOCAL)
string.memset(member_values_buf, 0, 8 * ENUM_MEMBER_MAX_LOCAL)
member_count: int = 0
next_val: int64 = 0
for ci in range(cn):
stmt: ast.AST* = children.get(ci)
if stmt is None: continue
sk: int = stmt.kind()
if sk == ast.ASTKind.AnnAssign:
aa: ast.AnnAssign* = ast.AnnAssign*(stmt)
if aa is None or aa.target is None: continue
# Get the member name
if aa.target.kind() != ast.ASTKind.Name: continue
tgt: ast.Name* = ast.Name*(aa.target)
if tgt.id is None: continue
# [enum spec fail-fast] Members may not be pointer types: t.CPtr / CRef /
# CTempRef / CAutoPtr / rawptr[...] / str / bytes and unions carrying a pointer. Checked before type resolution.
if self._enum_member_annotation_is_ptr(aa.annotation) == 1:
self.Gen.TypeGenErr.fatal_error(aa, f"enum member '{tgt.id}' cannot be a pointer type (enum values only support integer member types)", "TypeError")
return 0 # unreachable (fatal_error already exited)
# Resolve the member type
member_ty: mlirlite.LLVMType* = self._resolve_enum_member_type(pool, aa.annotation, trans._imported_modules, trans._from_imports)
# [enum spec fail-fast] Member types must be integers: float/void/struct/array have bit
# width 0 and cannot drive "enum width = widest member"; no silent downgrade.
member_bits: int = self.Gen.TypeGenErr.get_llvm_type_bits(member_ty)
if member_bits <= 0:
self.Gen.TypeGenErr.fatal_error(aa, f"the type of enum member '{tgt.id}' must be an integer type (pointers/floats/void/structs/arrays are not allowed), cannot infer enum width", "TypeError")
return 0 # unreachable (fatal_error already exited)
# Resolve the member value
has_value: int = 0
cur_val: int64 = 0
if aa.value is not None:
cur_val = self._extract_int_value(aa.value)
has_value = 1
else: cur_val = next_val
# Next automatic value
next_val = cur_val + 1
# Store
if member_count < ENUM_MEMBER_MAX_LOCAL:
member_names_buf[member_count] = sizet(tgt.id)
member_types_buf[member_count] = sizet(member_ty)
member_values_buf[member_count] = cur_val
member_count += 1
elif sk == ast.ASTKind.Assign:
# Handle Assign-form enum members (DEBUG = 0, INFO = 1, ...). Each Name target is a
# member (A = B = 1 registers both), matching TPC's per-target rule.
ag: ast.Assign* = ast.Assign*(stmt)
if ag is None or ag.targets is None: continue
tgts: list[ast.AST*]* = ag.targets
tn: sizet = len(tgts)
if tn == 0: continue
# Assign has no annotation; default to i32
member_ty_a: mlirlite.LLVMType* = mlirlite.Int32(pool)
# Resolve the member value (all targets in the statement share it; next_val advances once)
cur_val_a: int64 = 0
if ag.value is not None: cur_val_a = self._extract_int_value(ag.value)
else: cur_val_a = next_val
next_val = cur_val_a + 1
# Store per target (non-Name targets are caught by the registration-side fallback check)
for ti in range(tn):
t0: ast.AST* = tgts.get(ti)
if t0 is None or t0.kind() != ast.ASTKind.Name: continue
tgt_a: ast.Name* = ast.Name*(t0)
if tgt_a.id is None: continue
if member_count < ENUM_MEMBER_MAX_LOCAL:
member_names_buf[member_count] = sizet(tgt_a.id)
member_types_buf[member_count] = sizet(member_ty_a)
member_values_buf[member_count] = cur_val_a
member_count += 1
# [fallback check fail-fast] "Visible member names" vs "actually registered members". Any form the
# registrar does not recognize makes visible > registered, so fail-fast instead of silently dropping.
visible_count: int = self._enum_body_visible_names(children)
if visible_count != member_count:
diff_count: int = visible_count - member_count
bad: ast.AST* = self._enum_first_unregistered_node(children)
bad_desc: str = "(cannot locate the specific statement, check the enum body)"
if bad is not None: bad_desc = bad.type_name()
self.Gen.TypeGenErr.fatal_error(bad if bad is not None else cd, f"enum '{class_name}' member registration count mismatch: {visible_count} member names visible in source, " f"but only {member_count} registered ({diff_count} difference would be silently dropped). " f"first unrecognized member form: a statement of shape {bad_desc} -- e.g. comma group with value `A, B = 1`, " f"comma group with annotation `A, B: T`, single name with trailing comma `A,`. " f"expected forms: one `Name` / `Name: T` / `Name = value` per line, " f"or multiple simple identifiers separated by commas (`A, B, C`, trailing comma allowed). " f"fix: rewrite that line as one member per line, or as a plain identifier comma group (remove `= value` / `: T`). " f"(this check compares the number of visible names vs registered names at the registrar, so any unrecognized form hits it, " f"no more members are silently dropped)", "TypeError")
return 0
if member_count == 0:
VLogger.warning(f"{class_name} has no members", "ENUM")
return 0
# Determine the base type: the widest member type in bits
base_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
max_bits: int = 0
for mi in range(member_count):
ty_addr: sizet = member_types_buf[mi]
if ty_addr == 0: continue
mty: mlirlite.LLVMType* = (mlirlite.LLVMType*)(ty_addr)
bits: int = self.Gen.TypeGenErr.get_llvm_type_bits(mty)
if bits > max_bits:
max_bits = bits
base_ty = mty
# Register the enum
entry: GenTypes.EnumEntry* = self.Gen.StmtGenDecl.register_enum(pool, class_name, base_ty)
if entry is None:
VLogger.error("failed to register {class_name}", "ENUM")
return 0
# Add members
for mi in range(member_count):
fname_addr: sizet = member_names_buf[mi]
if fname_addr == 0: continue
mname: str = rawptr(fname_addr)
fty_addr2: sizet = member_types_buf[mi]
if fty_addr2 == 0: continue
mty2: mlirlite.LLVMType* = (mlirlite.LLVMType*)(fty_addr2)
mval: int64 = member_values_buf[mi]
if mname is not None and mty2 is not None: self.Gen.StmtGenDecl.add_enum_member(pool, entry, mname, mval, mty2)
VLogger.info(f"registered {class_name} with {member_count} members, base_bits={max_bits}", "ENUM")
return 0
def _get_type_size(self, ty: mlirlite.LLVMType*) -> int:
"""Compute the byte size of an LLVM type including alignment padding"""
if ty is None: return 0
match ty:
case mlirlite.LLVMType.Int(bits):
return (bits + 7) // 8
case mlirlite.LLVMType.Float(bits):
return bits // 8
case mlirlite.LLVMType.Ptr(pointee):
return 8
case mlirlite.LLVMType.Array(elem_ty, count):
return self._get_type_size(elem_ty) * count
case mlirlite.LLVMType.Struct(fields, fcount, name):
# Compute the struct size with field-alignment padding: each field's alignment is its
# natural alignment and the total is padded to the largest field alignment. packed structs have no padding.
is_packed: int = self.Gen.StructGen.struct_type_is_packed(ty)
total: int = 0
max_align: int = 1
cur: mlirlite.ParamNode* = fields
i: int = 0
while cur is not None and i < fcount:
if cur.Ty is not None:
fty: mlirlite.LLVMType* = (mlirlite.LLVMType*)(cur.Ty)
fsize: int = self._get_type_size(fty)
falign: int = self._get_type_align(fty)
# Align the current offset to the field boundary (packed inserts no padding)
if is_packed == 0 and falign > 0:
rem: int = total % falign
if rem != 0: total += falign - rem
total += fsize
if is_packed == 0 and falign > max_align: max_align = falign
cur = cur.Next
i += 1
# packed has no tail alignment; non-packed totals are padded to the max field alignment
if is_packed != 0: return total
rem2: int = total % max_align
if rem2 != 0: total += max_align - rem2
return total
case _:
return 8
def _get_type_align(self, ty: mlirlite.LLVMType*) -> int:
"""Return the natural alignment of a type in bytes"""
if ty is None: return 1
match ty:
case mlirlite.LLVMType.Int(bits):
if bits <= 8: return 1
elif bits <= 16: return 2
elif bits <= 32: return 4
else: return 8
case mlirlite.LLVMType.Float(bits):
if bits <= 32: return 4
else: return 8
case mlirlite.LLVMType.Ptr(pointee):
return 8
case mlirlite.LLVMType.Array(elem_ty, count):
return self._get_type_align(elem_ty)
case mlirlite.LLVMType.Struct(fields, fcount, name):
# A packed struct is byte-aligned (alignment 1, no padding)
if self.Gen.StructGen.struct_type_is_packed(ty) != 0: return 1
# A struct's alignment is the largest field alignment
max_align: int = 1
cur: mlirlite.ParamNode* = fields
i: int = 0
while cur is not None and i < fcount:
if cur.Ty is not None:
fty: mlirlite.LLVMType* = (mlirlite.LLVMType*)(cur.Ty)
fa: int = self._get_type_align(fty)
if fa > max_align: max_align = fa
cur = cur.Next
i += 1
return max_align
case _:
return 8
def _translate_union_def(self, trans: HT.Translator*, cd: ast.ClassDef*) -> int:
"""Translate a union definition, return 0"""
if cd is None or cd.name is None or trans is None: return 0
pool: memhub.MemBuddy* = trans.Pool
class_name: str = cd.name
# Namespace isolation: mark visible in the current file (before the existing check)
self.Gen.StructGen.add_visible_struct(pool, class_name)
# Check whether already registered (SHA1 distinguishes cross-module same-name classes)
existing: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(class_name, trans.ModuleSha1)
if existing is not None:
# Phase B: register the existing named struct into the current module
if existing.Ty is not None: mlirlite.module_add_named_type(trans.Module, pool, existing.Ty)
# SHA1 was already set by register_struct; no need to set it again
return 0
children: list[ast.AST*]* = cd.children
if children is None:
VLogger.warning("{class_name} has no body", "UNION")
return 0
cn: sizet = len(children)
# Temporary arrays holding field info
field_names_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
field_types_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
if field_names_buf is None or field_types_buf is None: return 0
string.memset(field_names_buf, 0, 8 * GenTypes.FIELD_MAX)
string.memset(field_types_buf, 0, 8 * GenTypes.FIELD_MAX)
field_count: int = 0
max_size: int = 0
for ci in range(cn):
stmt: ast.AST* = children.get(ci)
if stmt is None: continue
sk: int = stmt.kind()
if sk == ast.ASTKind.AnnAssign:
aa: ast.AnnAssign* = ast.AnnAssign*(stmt)
if aa is None or aa.target is None: continue
# Get the field name
if aa.target.kind() != ast.ASTKind.Name: continue
tgt: ast.Name* = ast.Name*(aa.target)
if tgt.id is None: continue
# Resolve the field type
field_ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, aa.annotation, trans._imported_modules, trans._from_imports, trans, 0)
if field_ty is None:
# Phase A-pre (declare_only=1): a dependency class may not be registered yet;
# return 1 so multi-pass convergence kicks in (renum returns 1 = skip this class)
if trans._declare_only == 1: return 1
# Phase B (declare_only=0): a real type resolution error; fail-fast
type_name: str = self.Gen.TypeGenResolve.annotation_to_str(aa.annotation)
self.Gen.TypeGenErr.fatal_error(aa, f"'{type_name}' is not defined", "NameError")
field_ty = mlirlite.Int32(pool)
# Compute the field size and update max_size
fsize: int = self._get_type_size(field_ty)
if fsize > max_size: max_size = fsize
# Store into the array
if field_count < GenTypes.FIELD_MAX:
field_names_buf[field_count] = sizet(tgt.id)
field_types_buf[field_count] = sizet(field_ty)
field_count += 1
if field_count == 0:
VLogger.warning(f"{class_name} has no fields", "UNION")
return 0
if max_size < 1: max_size = 1
# Create the union's LLVM type: Struct([Array(Int8, max_size)]), i.e. { [max_size x i8] }
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
array_ty: mlirlite.LLVMType* = mlirlite.Array(pool, i8_ty, max_size)
# Build the ParamNode list (single field: byte array)
pnode: mlirlite.ParamNode* = pool.alloc(mlirlite.ParamNode.__sizeof__())
if pnode is None: return 0
string.memset(pnode, 0, mlirlite.ParamNode.__sizeof__())
pnode.Ty = array_ty
# Build the named struct type name: "sha1.ClassName" or "ClassName"
union_type_name: str = class_name
if trans.ModuleSha1 is not None:
union_name_buf: str = pool.alloc(64)
if union_name_buf is not None:
viperlib.snprintf(union_name_buf, 64, "%s.%s", trans.ModuleSha1, class_name)
union_type_name = union_name_buf
union_ty: mlirlite.LLVMType* = mlirlite.Struct(pool, pnode, 1, union_type_name)
if union_ty is None:
VLogger.error(f"failed to create type for {class_name}", "UNION")
return 0
# Deep-copy the newly registered union struct_ty to stdlib (survives cross-file reset).
# Needed even under StdlibAlloc=1 (after Phase2 mark2): else reset_to(mark2) leaves entry.Ty dangling.
if trans._declare_only == 0 or self.Gen.StructGen.get_stdlib_alloc() != 0:
union_copy: mlirlite.LLVMType* = mlirlite.DeepCopyType(CompilerFactory.factory.PermPool, union_ty)
if union_copy is not None: union_ty = union_copy
# Register the named struct into the module
mlirlite.module_add_named_type(trans.Module, pool, union_ty)
# Register into HandlesStruct (SHA1 is set directly at registration to avoid picking the wrong entry)
entry: GenTypes.StructEntry* = self.Gen.StructGen.register_struct(pool, class_name, union_ty, trans.ModuleSha1)
if entry is None:
VLogger.error(f"failed to register {class_name}", "UNION")
return 0
# Mark visible in the current file (namespace isolation)
self.Gen.StructGen.add_visible_struct(pool, class_name)
# Mark as a union (using entry directly to avoid cross-module same-name lookup errors)
entry.IsUnion = 1
# Add field info (all fields have Index=0 because they share offset 0)
for fi in range(field_count):
fname_addr: sizet = field_names_buf[fi]
if fname_addr == 0: continue
fname: str = rawptr(fname_addr)
fty_addr2: sizet = field_types_buf[fi]
if fty_addr2 == 0: continue
fty2: mlirlite.LLVMType* = (mlirlite.LLVMType*)(fty_addr2)
if fname is not None and fty2 is not None: self.Gen.StructGen.add_field(pool, entry, fname, fty2, None, None)
VLogger.info(f"registered {class_name} with {field_count} fields, max_size={max_size}", "UNION")
return 0
def _get_annotation_field_size(self, pool: memhub.MemBuddy*, annotation: ast.AST*) -> int:
"""Compute the annotation type's byte size for REnum layout"""
if annotation is None: return 8
k: int = annotation.kind()
# Union type A | B -> pointer size 8
if k == ast.ASTKind.BinOp: return 8
# Name(id) or Attribute(attr) -> resolve the type name
type_name: str = None
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(annotation)
if nm is not None: type_name = nm.id
elif k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(annotation)
if at is not None: type_name = at.attr
if type_name is None: return 8
# [stage 3] Unified sizing: library typedefs (BYTE/DWORD etc.) resolve through normal library
# analysis; the hardcoded size table below is only a fallback for t/c core primitives.
dyn_sz: int = self.Gen.TypeGenResolve.get_type_size_bytes(pool, type_name, None, None)
if dyn_sz > 0: return dyn_sz
# Builtin types
if string.strcmp(type_name, "int") == 0: return 4
if string.strcmp(type_name, "CInt") == 0: return 4
if string.strcmp(type_name, "CInt8T") == 0: return 1
if string.strcmp(type_name, "CInt16T") == 0: return 2
if string.strcmp(type_name, "CInt32T") == 0: return 4
if string.strcmp(type_name, "CInt64T") == 0: return 8
if string.strcmp(type_name, "CChar") == 0: return 1
if string.strcmp(type_name, "CDouble") == 0: return 8
if string.strcmp(type_name, "CFloat") == 0: return 4
if string.strcmp(type_name, "CPtr") == 0: return 8
if string.strcmp(type_name, "CSizeT") == 0: return 8
if string.strcmp(type_name, "str") == 0: return 8
if string.strcmp(type_name, "bytes") == 0: return 8
if string.strcmp(type_name, "bool") == 0: return 1
# Registered struct type
struct_ty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type(type_name)
if struct_ty is not None:
sz: int = self._get_type_size(struct_ty)
if sz > 0: return sz
# Unregistered type -> default pointer size 8 (may be self- or forward-referenced)
return 8
def _translate_renum_def(self, trans: HT.Translator*, cd: ast.ClassDef*) -> int:
"""Translate an REnum class definition, return 0"""
if cd is None or cd.name is None or trans is None: return 0
pool: memhub.MemBuddy* = trans.Pool
class_name: str = cd.name
# Namespace isolation: mark visible in the current file
self.Gen.StructGen.add_visible_struct(pool, class_name)
# Check whether already registered (SHA1 distinguishes cross-module same-name classes)
existing: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(class_name, trans.ModuleSha1)
if existing is not None:
# existing only guarantees the struct entry, not the REnum semantics (variant table): the entry
# lives in StructTable across phases, but the variant table is a per-Generator field reset each time.
if self._is_renum_class_name(class_name) != 0:
if existing.Ty is not None: mlirlite.module_add_named_type(trans.Module, pool, existing.Ty)
return 0
# Variant table missing: neutralize the old entry so find_struct_by_module no longer matches,
# then fall through to full registration to fix both the layout and the variant table.
if existing.Name is not None: existing.Name[0] = '\0'
existing.Ty = None
existing.FieldCount = 0
children: list[ast.AST*]* = cd.children
if children is None:
# [D32 fail-fast] Previously only warned and returned: the variant table was silently unregistered,
# so _is_renum_class_name stayed 0 and variant construction lost its tag. No silent skip here.
self.Gen.TypeGenErr.fatal_error(cd, f"REnum class '{class_name}' has no class body (children is None), cannot register the variant table", "TypeError")
return 0
cn: sizet = len(children)
# At most 32 variants per class, 16 fields per variant; variant_field_sizes[i][j] = variant i's field j size.
variant_field_sizes: sizet* = pool.alloc(8 * 64 * 16)
if variant_field_sizes is None: return 0
string.memset(variant_field_sizes, 0, 8 * 64 * 16)
variant_count: int = 0
max_field_count: int = 0 # max field count across all variants
for ci in range(cn):
stmt: ast.AST* = children.get(ci)
if stmt is None: continue
sk: int = stmt.kind()
if sk == ast.ASTKind.ClassDef:
# Nested variant class
variant_cd: ast.ClassDef* = ast.ClassDef*(stmt)
if variant_cd is None or variant_cd.children is None:
variant_count += 1
continue
variant_children: list[ast.AST*]* = variant_cd.children
vcn: sizet = len(variant_children)
field_idx: int = 0
for vi in range(vcn):
vstmt: ast.AST* = variant_children.get(vi)
if vstmt is None: continue
vk: int = vstmt.kind()
if vk != ast.ASTKind.AnnAssign: continue
vaa: ast.AnnAssign* = ast.AnnAssign*(vstmt)
if vaa is None or vaa.target is None or vaa.annotation is None: continue
if vaa.target.kind() != ast.ASTKind.Name: continue
# Extract the field name
vnm: ast.Name* = ast.Name*(vaa.target)
# Compute the field size
fsize: int = self._get_annotation_field_size(pool, vaa.annotation)
if variant_count < 32 and field_idx < 16:
# variant_field_sizes[variant_count][field_idx] = fsize
offset: sizet = variant_count * 16 + field_idx
variant_field_sizes[offset] = sizet(fsize)
# Register the variant field-name alias directly, resolving its actual type for match
# binding. Self-referencing annotations fail to resolve here and fall back to the slot type.
if vnm is not None and vnm.id is not None:
mem_ty_l: mlirlite.LLVMType* = None
if trans is not None: mem_ty_l = self.Gen.TypeGenResolve.resolve_annotation_type(pool, vaa.annotation, trans._imported_modules, trans._from_imports, trans, 0)
self._register_renum_field_alias(pool, class_name, vnm.id, field_idx + 1, mem_ty_l, variant_cd.name)
field_idx += 1
if field_idx > max_field_count: max_field_count = field_idx
variant_count += 1
elif sk == ast.ASTKind.Assign:
# Assign-form payload-less variants (e.g. `Empty = 1`): same rule as TPC, still a variant (occupies
# a slot) with no fields. Tags are assigned by _register_renum_variants (explicit values first).
ag_v: ast.Assign* = ast.Assign*(stmt)
if ag_v is None or ag_v.targets is None: continue
tgts_v: list[ast.AST*]* = ag_v.targets
if len(tgts_v) == 0: continue
t0_v: ast.AST* = tgts_v.get(0)
if t0_v is None or t0_v.kind() != ast.ASTKind.Name: continue
tgt_v: ast.Name* = ast.Name*(t0_v)
if tgt_v.id is None: continue
variant_count += 1
if variant_count == 0:
# [D32 fail-fast] Same as above: variant_count 0 previously returned silently, skipping registration.
# For `class X(t.REnum)` the count must be > 0; 0 means the AST variant classes were lost.
self.Gen.TypeGenErr.fatal_error(cd, f"REnum class '{class_name}' has 0 variants (nested variant classes lost), cannot register the variant table", "TypeError")
return 0
# Compute the max field size for each position.
# Position 0 = __tag (i32, 4 bytes); positions 1+ = variant fields.
total_positions: int = max_field_count + 1 # +1 for tag
if total_positions < 2:
total_positions = 2 # at least {i32, i32}
position_max_sizes: sizet* = pool.alloc(8 * total_positions)
if position_max_sizes is None: return 0
string.memset(position_max_sizes, 0, 8 * total_positions)
# Position 0 = tag = 4 bytes
position_max_sizes[0] = sizet(4)
# Positions 1+: take the max field size across all variants at each position
for pos in range(1, total_positions):
pos_max: sizet = 0
for vi in range(variant_count):
if vi >= 64:
break # variant_field_sizes 64-class slot cap; guards against out-of-bounds reads
field_pos: int = pos - 1 # variant fields start at position 0
if field_pos >= 16:
continue # beyond the 16-field-per-variant cap: not written (memset 0), skip
offset2: sizet = vi * 16 + field_pos
fsize_val: sizet = variant_field_sizes[offset2]
if fsize_val > pos_max: pos_max = fsize_val
position_max_sizes[pos] = sizet(pos_max)
# Build the ParamNode list
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
first_node: mlirlite.ParamNode* = None
tail_node: mlirlite.ParamNode* = None
for pos in range(total_positions):
psize: int = int(position_max_sizes[pos])
field_ty: mlirlite.LLVMType* = i32_ty
if psize > 4: field_ty = i64_ty
pnode: mlirlite.ParamNode* = mlirlite.new_param_node(pool, field_ty)
if pnode is None: continue
tail_node = mlirlite.param_list_append(first_node, tail_node, pnode)
if first_node is None: first_node = tail_node
if first_node is None:
VLogger.error(f"failed to build field list for {class_name}", "RENUM")
return 0
# Build the named struct type name: "sha1.ClassName" or "ClassName"
renum_type_name: str = class_name
if trans.ModuleSha1 is not None:
renum_name_buf: str = pool.alloc(64)
if renum_name_buf is not None:
viperlib.snprintf(renum_name_buf, 64, "%s.%s", trans.ModuleSha1, class_name)
renum_type_name = renum_name_buf
renum_ty: mlirlite.LLVMType* = mlirlite.Struct(pool, first_node, total_positions, renum_type_name)
if renum_ty is None:
VLogger.error(f"failed to create type for {class_name}", "RENUM")
return 0
# Deep-copy the newly registered REnum struct_ty to stdlib (survives cross-file reset and keeps the
# same pointer as entry.Ty so find_struct_by_type still matches); required under StdlibAlloc=1 too.
if trans._declare_only == 0 or self.Gen.StructGen.get_stdlib_alloc() != 0:
renum_copy: mlirlite.LLVMType* = mlirlite.DeepCopyType(CompilerFactory.factory.PermPool, renum_ty)
if renum_copy is not None: renum_ty = renum_copy
# Register the named struct into the module
mlirlite.module_add_named_type(trans.Module, pool, renum_ty)
# Register into HandlesStruct.
# A forward-declared placeholder for THIS class may have been created *during the variant walk*
# above: a variant field like `Pointee: LLVMType*` is `BinOp(LLVMType | t.CPtr)` (parser's
# _make_ptr_type expands the C-style suffix into a union, NOT into `rawptr[...]`), so resolving it
# resolves the class name and calls ensure_forward_class. register_struct de-duplicates by Ty
# pointer, so it would add a SECOND same-named entry and get_struct_type/find_struct (first match
# wins) would keep returning the opaque placeholder -> __sizeof__() == 0 at the first use
# ("registered but size 0, field chain unfilled"). Fill the placeholder in place instead, which is
# exactly what ensure_forward_class documents ("the definition side fills the same Ty in place").
entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(class_name, trans.ModuleSha1)
if entry is not None and entry.IsForwardDecl != 0:
entry.Ty = renum_ty
entry.IsForwardDecl = 0
else:
entry = self.Gen.StructGen.register_struct(pool, class_name, renum_ty, trans.ModuleSha1)
if entry is None:
VLogger.error(f"failed to register {class_name}", "RENUM")
return 0
# Add the __tag field (position 0, i32)
self.Gen.StructGen.add_field(pool, entry, "__tag", i32_ty, None, None)
# Add payload fields (positions 1+, i32 or i64)
for pos in range(1, total_positions):
psize2: int = int(position_max_sizes[pos])
payload_ty: mlirlite.LLVMType* = i32_ty
if psize2 > 4: payload_ty = i64_ty
field_name_buf: str = pool.alloc(16)
if field_name_buf is not None:
viperlib.snprintf(field_name_buf, 16, "_p%d", pos)
self.Gen.StructGen.add_field(pool, entry, field_name_buf, payload_ty, None, None)
# Register variant names into the global variant table (translate_call detects REnum construction)
self._register_renum_variants(pool, class_name, cd, variant_count)
# Variant field-name aliases were registered during the variant walk, which runs before register_struct;
# self-referencing annotations left a 0 type slot, so refill it now (see _refill_renum_field_types).
self._refill_renum_field_types(trans, pool, class_name, cd)
VLogger.info(f"registered {class_name} with {variant_count} variants, {total_positions} positions", "RENUM")
return 0
def _refill_renum_field_types(self, trans: HT.Translator*, pool: memhub.MemBuddy*, class_name: str, cd: ast.ClassDef*):
"""After REnum registration, re-resolve variant field types and backfill only aliases whose type slot is still 0."""
if trans is None or pool is None or class_name is None or cd is None: return
children: list[ast.AST*]* = cd.children
if children is None: return
cn: sizet = len(children)
for ci in range(cn):
stmt: ast.AST* = children.get(ci)
if stmt is None or stmt.kind() != ast.ASTKind.ClassDef: continue
variant_cd: ast.ClassDef* = ast.ClassDef*(stmt)
if variant_cd is None or variant_cd.name is None or variant_cd.children is None: continue
vchildren: list[ast.AST*]* = variant_cd.children
vcn: sizet = len(vchildren)
field_idx: int = 0
for vi in range(vcn):
vstmt: ast.AST* = vchildren.get(vi)
if vstmt is None: continue
if vstmt.kind() != ast.ASTKind.AnnAssign: continue
vaa: ast.AnnAssign* = ast.AnnAssign*(vstmt)
if vaa is None or vaa.target is None or vaa.annotation is None:
# Same rule as the first pass: an invalid AnnAssign does not occupy a payload slot
continue
if vaa.target.kind() != ast.ASTKind.Name: continue
vnm: ast.Name* = ast.Name*(vaa.target)
if vnm.id is None:
# Same rule as the first pass: a valid AnnAssign still occupies a payload slot
field_idx += 1
continue
mem_ty_l: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, vaa.annotation, trans._imported_modules, trans._from_imports, trans, 0)
# Resolved -> trigger the alias dedupe path (backfill when the type slot is 0; skip a non-zero slot)
if mem_ty_l is not None: self._register_renum_field_alias(pool, class_name, vnm.id, field_idx + 1, mem_ty_l, variant_cd.name)
field_idx += 1
def _register_renum_variants(self, pool: memhub.MemBuddy*, class_name: str, cd: ast.ClassDef*, variant_count: int):
"""Register an REnum class's variant names and tag values into the global table"""
if pool is None or class_name is None or cd is None: return
if variant_count == 0: return
children: list[ast.AST*]* = cd.children
if children is None: return
# Lazily initialize the tables on first registration. They must be stdlib-allocated: if pool-allocated
# they would be reclaimed by reset_to(mark1) and later matches would read freed memory and crash.
if self.RenumClassNames is None:
self.RenumClassNames = CompilerFactory.factory.PermPool.alloc(8 * RENUM_CLASS_MAX)
self.RenumClassVariantCounts = CompilerFactory.factory.PermPool.alloc(8 * RENUM_CLASS_MAX)
self.RenumVariantNames = CompilerFactory.factory.PermPool.alloc(8 * RENUM_CLASS_MAX * RENUM_VARIANT_PER_CLASS)
self.RenumVariantTags = CompilerFactory.factory.PermPool.alloc(8 * RENUM_CLASS_MAX * RENUM_VARIANT_PER_CLASS)
if (self.RenumClassNames is None or self.RenumClassVariantCounts is None or self.RenumVariantNames is None or self.RenumVariantTags is None):
# A None table means the perm-pool request failed; returning here left the table empty while the
# caller still reported "registered", so every REnum case in match dispatch was silently lost.
self.RenumClassNames = None
self.Gen.TypeGenErr.fatal_error(cd, f"failed to allocate REnum variant table (class='{class_name}'): PermPool returned NULL, variant table unavailable", "InternalCompilerError")
return
string.memset(self.RenumClassNames, 0, 8 * RENUM_CLASS_MAX)
string.memset(self.RenumClassVariantCounts, 0, 8 * RENUM_CLASS_MAX)
string.memset(self.RenumVariantNames, 0, 8 * RENUM_CLASS_MAX * RENUM_VARIANT_PER_CLASS)
string.memset(self.RenumVariantTags, 0, 8 * RENUM_CLASS_MAX * RENUM_VARIANT_PER_CLASS)
self.RenumClassCount = 0
# [D30] Register the pointers into the compile-time factory singleton (same lifetime as struct
# entries); InitClassGen restores them for new Generators instead of zeroing them.
CompilerFactory.factory.RenumClassNames = self.RenumClassNames
CompilerFactory.factory.RenumClassVariantCounts = self.RenumClassVariantCounts
CompilerFactory.factory.RenumVariantNames = self.RenumVariantNames
CompilerFactory.factory.RenumVariantTags = self.RenumVariantTags
CompilerFactory.factory.RenumClassCount = self.RenumClassCount
#rcv_chk: uint64 = uint64(self.RenumClassVariantCounts)
# VLogger.error(f"[RENUM-FAIL] RCV={rcv_chk} abnormal (perm_pool address expected after lazy init)", "RENUM")
# Check whether already registered (avoid duplicate registration)
for i in range(self.RenumClassCount):
existing: str = str(self.RenumClassNames[i])
if existing is not None and string.strcmp(existing, class_name) == 0: return
# Table full
if self.RenumClassCount >= RENUM_CLASS_MAX:
# Silently dropping the class made its later `match` lose every case; a
# real limit breach must be reported instead.
self.Gen.TypeGenErr.fatal_error(cd, f"REnum class count exceeds the limit {RENUM_CLASS_MAX}: cannot register class='{class_name}'", "InternalCompilerError")
return
# Register the class name (strcpy into perm_pool: after each file's 1a reset the AST raw pointer
# dangles, and strcmp in _lookup_renum_variant/_is_renum_class_name would read poisoned memory).
idx: int = self.RenumClassCount
cn_len_rv: sizet = string.strlen(class_name)
cn_buf_rv: str = CompilerFactory.factory.PermPool.alloc(cn_len_rv + 1)
if cn_buf_rv is None:
self.Gen.TypeGenErr.fatal_error(cd, f"failed to allocate REnum class name copy (class='{class_name}'): PermPool returned NULL", "InternalCompilerError")
return
string.strcpy(cn_buf_rv, class_name)
self.RenumClassNames[idx] = sizet(cn_buf_rv)
self.RenumClassVariantCounts[idx] = variant_count
# Walk children and register variant names + tags. Two forms: `class Some: ...` (payload variant,
# tags numbered from max(explicit)+1) and `Empty = 1` (payload-less, tag = explicit integer value).
cn: sizet = len(children)
# First pass: find the upper bound of Assign variants' explicit integer values (stays -1 if none)
max_explicit: int = -1
for ci in range(cn):
stmt_p: ast.AST* = children.get(ci)
if stmt_p is None or stmt_p.kind() != ast.ASTKind.Assign: continue
ag_p: ast.Assign* = ast.Assign*(stmt_p)
if ag_p is None or ag_p.targets is None or ag_p.value is None: continue
tgts_p: list[ast.AST*]* = ag_p.targets
if len(tgts_p) == 0: continue
t0_p: ast.AST* = tgts_p.get(0)
if t0_p is None or t0_p.kind() != ast.ASTKind.Name: continue
if ag_p.value.kind() != ast.ASTKind.Constant: continue
ev_v: int = int(self._extract_int_value(ag_p.value))
if ev_v > max_explicit: max_explicit = ev_v
# Second pass: write the table in declaration order. slot is the contiguous table index (0..count-1),
# while the cell stores the tag; they are no longer equal, so a single counter cannot serve both.
slot: int = 0
next_class_tag: int = max_explicit + 1
variant_index: int = 0
for ci in range(cn):
if slot >= variant_count: break
if slot >= RENUM_VARIANT_PER_CLASS:
# Fixed 32 slots per class: exceeding it would overflow into the next class's slots; report fail-fast
VLogger.error(f"REnum variant count exceeds the limit {RENUM_VARIANT_PER_CLASS}: {class_name}", "RENUM")
break
stmt: ast.AST* = children.get(ci)
if stmt is None: continue
sk_rv: int = stmt.kind()
vname_ptr: sizet = 0
tag_val: int = 0
if sk_rv == ast.ASTKind.ClassDef:
vcd: ast.ClassDef* = ast.ClassDef*(stmt)
if vcd is None or vcd.name is None: continue
vname_ptr = sizet(vcd.name)
tag_val = next_class_tag
next_class_tag += 1
elif sk_rv == ast.ASTKind.Assign:
# Assign-form payload-less variant (same filter as _translate_renum_def's first pass)
ag_rv: ast.Assign* = ast.Assign*(stmt)
if ag_rv is None or ag_rv.targets is None: continue
tgts_rv: list[ast.AST*]* = ag_rv.targets
if len(tgts_rv) == 0: continue
t0_rv: ast.AST* = tgts_rv.get(0)
if t0_rv is None or t0_rv.kind() != ast.ASTKind.Name: continue
tgt_rv: ast.Name* = ast.Name*(t0_rv)
if tgt_rv.id is None: continue
vname_ptr = sizet(tgt_rv.id)
if ag_rv.value is not None and ag_rv.value.kind() == ast.ASTKind.Constant: tag_val = int(self._extract_int_value(ag_rv.value))
else:
# No explicit value: use a separate counter (distinct from the class-variant counter, as
# TPC's variant_index) so it cannot collide with next_class_tag.
tag_val = variant_index
variant_index += 1
else: continue
name_offset: sizet = idx * RENUM_VARIANT_PER_CLASS + slot
# The variant name is also strcpy'd into perm_pool (the AST name dangles after each file's 1a reset)
vname_src: str = str(vname_ptr)
vn_len_rv: sizet = string.strlen(vname_src)
vn_buf_rv: str = CompilerFactory.factory.PermPool.alloc(vn_len_rv + 1)
if vn_buf_rv is None:
# Breaking here registered a partial variant table: the class would
# keep some variants and silently lose every later one.
self.Gen.TypeGenErr.fatal_error(cd, f"failed to allocate REnum variant name copy (class='{class_name}'): PermPool returned NULL", "InternalCompilerError")
break
string.strcpy(vn_buf_rv, vname_src)
self.RenumVariantNames[name_offset] = sizet(vn_buf_rv)
self.RenumVariantTags[name_offset] = tag_val
slot += 1
self.RenumClassCount = idx + 1
# [D30] Write the count back to the factory singleton (new Generators restore from it; otherwise they
# would see a stale count and miss subsequently registered variants).
CompilerFactory.factory.RenumClassCount = self.RenumClassCount
def _lookup_renum_variant(self, variant_name: str) -> str:
"""Find the REnum class name owning a variant name; None if absent"""
if variant_name is None or self.RenumClassNames is None: return None
for i in range(self.RenumClassCount):
vcount: int = int(self.RenumClassVariantCounts[i])
for j in range(vcount):
name_offset: sizet = i * RENUM_VARIANT_PER_CLASS + j
vname: str = str(self.RenumVariantNames[name_offset])
if vname is not None and string.strcmp(vname, variant_name) == 0:
cls_nm: str = str(self.RenumClassNames[i])
return cls_nm
return None
def RenumTableDump(self) -> str:
"""Diagnostic variant-table summary to tell 'class never registered' from 'class present but variant missing'."""
buf: str = CompilerFactory.factory.PermPool.alloc(256)
if buf is None: return "-"
buf[0] = '\0'
cls_first: str = "-"
v_first: str = "-"
n_first_v: int = 0
if self.RenumClassNames is not None and self.RenumClassCount > 0:
cls_first = str(self.RenumClassNames[0])
if self.RenumVariantNames is not None and self.RenumClassVariantCounts is not None:
n_first_v = int(self.RenumClassVariantCounts[0])
if n_first_v > 0: v_first = str(self.RenumVariantNames[0])
viperlib.snprintf(buf, 256, "count=%d first=%s vcount=%d vfirst=%s", self.RenumClassCount, cls_first, n_first_v, v_first)
return buf
def _is_renum_by_entry(self, class_name: str) -> int:
"""Variant-table-free REnum test: a struct definition carrying a `__tag` field is an REnum, so match dispatch keeps variant bindings even if the table is missing."""
if class_name is None: return 0
st: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type(class_name)
if st is None: return 0
if self.Gen.StructGen.lookup_field(st, "__tag") is None: return 0
return 1
def _is_renum_class_name(self, class_name: str) -> int:
"""Check whether a name is a registered REnum class, 1=yes / 0=no"""
if class_name is None or self.RenumClassNames is None: return 0
for i in range(self.RenumClassCount):
existing: str = str(self.RenumClassNames[i])
if existing is not None and string.strcmp(existing, class_name) == 0: return 1
return 0
def _lookup_renum_variant_in_class(self, class_name: str, variant_name: str) -> int:
"""Find a variant in a given REnum class, return its tag value or -1"""
if class_name is None or variant_name is None or self.RenumClassNames is None: return -1
for i in range(self.RenumClassCount):
existing: str = str(self.RenumClassNames[i])
if existing is None or string.strcmp(existing, class_name) != 0: continue
vcount: int = int(self.RenumClassVariantCounts[i])
for j in range(vcount):
name_offset: sizet = i * RENUM_VARIANT_PER_CLASS + j
vname: str = str(self.RenumVariantNames[name_offset])
if vname is not None and string.strcmp(vname, variant_name) == 0: return self.RenumVariantTags[name_offset]
return -1
return -1
def _register_renum_field_alias(self, pool: memhub.MemBuddy*, class_name: str, field_name: str, payload_idx: int, field_ty: mlirlite.LLVMType* = None, variant_name: str = None):
"""Register an REnum variant field name alias in the global table; optional field type and variant name aid match binding."""
if pool is None or class_name is None or field_name is None: return
if payload_idx < 1: return
# Lazily initialize the tables (perm_pool: the tables themselves survive each file's 1a reset)
if self.RenumAliasClass is None:
alias_pool_rfa: memhub.MemBuddy* = CompilerFactory.factory.PermPool
self.RenumAliasClass = alias_pool_rfa.alloc(8 * RENUM_ALIAS_MAX)
self.RenumAliasField = alias_pool_rfa.alloc(8 * RENUM_ALIAS_MAX)
self.RenumAliasIndex = alias_pool_rfa.alloc(8 * RENUM_ALIAS_MAX)
self.RenumAliasTy = alias_pool_rfa.alloc(8 * RENUM_ALIAS_MAX)
self.RenumAliasVariant = alias_pool_rfa.alloc(8 * RENUM_ALIAS_MAX)
if (self.RenumAliasClass is None or self.RenumAliasField is None or self.RenumAliasIndex is None or self.RenumAliasTy is None or self.RenumAliasVariant is None):
# Same as the variant table: a silent bail left the alias table empty,
# so match bindings later failed with an unrelated NameError.
self.RenumAliasClass = None
self.Gen.TypeGenErr.fatal_error(None, f"failed to allocate REnum field alias table (class='{class_name}'): PermPool returned NULL", "InternalCompilerError")
return
string.memset(self.RenumAliasClass, 0, 8 * RENUM_ALIAS_MAX)
string.memset(self.RenumAliasField, 0, 8 * RENUM_ALIAS_MAX)
string.memset(self.RenumAliasIndex, 0, 8 * RENUM_ALIAS_MAX)
string.memset(self.RenumAliasTy, 0, 8 * RENUM_ALIAS_MAX)
string.memset(self.RenumAliasVariant, 0, 8 * RENUM_ALIAS_MAX)
self.RenumAliasCount = 0
# [D30] Same as above: register the alias table pointers into the factory singleton to survive Generators
CompilerFactory.factory.RenumAliasClass = self.RenumAliasClass
CompilerFactory.factory.RenumAliasField = self.RenumAliasField
CompilerFactory.factory.RenumAliasIndex = self.RenumAliasIndex
CompilerFactory.factory.RenumAliasTy = self.RenumAliasTy
CompilerFactory.factory.RenumAliasVariant = self.RenumAliasVariant
CompilerFactory.factory.RenumAliasCount = self.RenumAliasCount
if self.RenumAliasCount >= RENUM_ALIAS_MAX:
# Dropping the record silently made the variant field bind to a wrong-typed
# slot (or fail with NameError) instead of reporting the limit breach.
self.Gen.TypeGenErr.fatal_error(None, f"REnum field alias count exceeds the limit {RENUM_ALIAS_MAX}: cannot register {class_name}.{field_name}", "InternalCompilerError")
return
# Dedupe check (pointer-free): the class slot holds a perm_pool string copy compared with strcmp,
# so it no longer relies on pointer equality (registration uses AST pointers, lookup uses pool copies).
for i in range(self.RenumAliasCount):
cls_existing_ptr: str = rawptr(self.RenumAliasClass[i])
if cls_existing_ptr is not None and string.strcmp(cls_existing_ptr, class_name) == 0:
fld_existing_ptr: str = rawptr(self.RenumAliasField[i])
if fld_existing_ptr is not None and string.strcmp(fld_existing_ptr, field_name) == 0:
# Existing record: if the first pass left the type slot 0 (self-referencing field, see
# _refill_renum_field_types), backfill the real type now; a non-zero slot is left as is.
if self.RenumAliasTy[i] == 0 and field_ty is not None:
ty_copy_rf2: mlirlite.LLVMType* = mlirlite.DeepCopyType(CompilerFactory.factory.PermPool, field_ty)
if ty_copy_rf2 is not None: self.RenumAliasTy[i] = sizet(ty_copy_rf2)
return
idx: int = self.RenumAliasCount
# class_name is also strcpy'd into perm_pool (pointer-free: any class_name string from the lookup
# side matches via strcmp, so no _update_renum_alias_class_ptr is needed).
cls_len_rfa: sizet = string.strlen(class_name)
cls_buf_rfa: str = CompilerFactory.factory.PermPool.alloc(cls_len_rfa + 1)
if cls_buf_rfa is None: return
string.strcpy(cls_buf_rfa, class_name)
self.RenumAliasClass[idx] = sizet(cls_buf_rfa)
# field_name stored as a raw AST pointer dangles after each file's 1a reset; since
# _lookup_renum_field_alias compares with strcmp, copying into perm_pool does not affect lookup.
fn_len_rfa: sizet = string.strlen(field_name)
fn_buf_rfa: str = CompilerFactory.factory.PermPool.alloc(fn_len_rfa + 1)
if fn_buf_rfa is None: return
string.strcpy(fn_buf_rfa, field_name)
self.RenumAliasField[idx] = sizet(fn_buf_rfa)
self.RenumAliasIndex[idx] = sizet(payload_idx)
# Deep-copy the member type into perm_pool (pool types dangle after each file's 1a reset; on an
# unresolved type such as a self reference, store 0 and fall back to the slot type when binding).
self.RenumAliasTy[idx] = 0
if field_ty is not None:
ty_copy_rfa: mlirlite.LLVMType* = mlirlite.DeepCopyType(CompilerFactory.factory.PermPool, field_ty)
if ty_copy_rfa is not None: self.RenumAliasTy[idx] = sizet(ty_copy_rfa)
# The variant name is also strcpy'd into perm_pool (variant_cd.name dangles after the 1a reset)
self.RenumAliasVariant[idx] = 0
if variant_name is not None:
vn_len_rfa: sizet = string.strlen(variant_name)
vn_buf_rfa: str = CompilerFactory.factory.PermPool.alloc(vn_len_rfa + 1)
if vn_buf_rfa is not None:
string.strcpy(vn_buf_rfa, variant_name)
self.RenumAliasVariant[idx] = sizet(vn_buf_rfa)
self.RenumAliasCount = idx + 1
# [D30] Write the count back to the factory singleton (new Generators restore from it)
CompilerFactory.factory.RenumAliasCount = self.RenumAliasCount
def _lookup_renum_field_alias(self, class_name: str, field_name: str) -> int:
"""Look up an REnum variant field name's payload index, return it or -1 (class/field compared by content)."""
if class_name is None or field_name is None or self.RenumAliasClass is None: return -1
for i in range(self.RenumAliasCount):
cls_existing_ptr: str = rawptr(self.RenumAliasClass[i])
if cls_existing_ptr is not None and string.strcmp(cls_existing_ptr, class_name) == 0:
# field_name is compared by strcmp content (pointers differ, content is the same)
fld_existing_ptr: str = rawptr(self.RenumAliasField[i])
if fld_existing_ptr is not None and string.strcmp(fld_existing_ptr, field_name) == 0: return self.RenumAliasIndex[i]
return -1
def _lookup_renum_field_alias_ty(self, class_name: str, field_name: str) -> mlirlite.LLVMType*:
"""Look up an REnum variant member's actual LLVM type, or None when the type slot is 0 (binding falls back to the slot type)."""
if class_name is None or field_name is None or self.RenumAliasTy is None: return None
for i in range(self.RenumAliasCount):
cls_existing_ptr: str = rawptr(self.RenumAliasClass[i])
if cls_existing_ptr is not None and string.strcmp(cls_existing_ptr, class_name) == 0:
fld_existing_ptr: str = rawptr(self.RenumAliasField[i])
if fld_existing_ptr is not None and string.strcmp(fld_existing_ptr, field_name) == 0:
if self.RenumAliasTy[i] == 0: return None
return (mlirlite.LLVMType*)(self.RenumAliasTy[i])
return None
def _lookup_renum_member_ty(self, class_name: str, variant_name: str, payload_idx: int) -> mlirlite.LLVMType*:
"""Look up a variant member's actual LLVM type by (class, variant name, payload index) for match binding; None if unresolved."""
if class_name is None or variant_name is None or self.RenumAliasVariant is None: return None
if payload_idx < 1: return None
for i in range(self.RenumAliasCount):
cls_existing_ptr: str = rawptr(self.RenumAliasClass[i])
if cls_existing_ptr is None or string.strcmp(cls_existing_ptr, class_name) != 0: continue
if self.RenumAliasVariant[i] == 0: continue
vnt_ptr: str = rawptr(self.RenumAliasVariant[i])
if vnt_ptr is None or string.strcmp(vnt_ptr, variant_name) != 0: continue
if self.RenumAliasIndex[i] != payload_idx: continue
if self.RenumAliasTy[i] == 0: return None
return (mlirlite.LLVMType*)(self.RenumAliasTy[i])
return None