import lib.core.Gen.GenTypes as GenTypes 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_builtin_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) # Marker wrapper (CConst[T] / CVolatile[T] / ...): resolve the wrapped type. if k == ast.ASTKind.Subscript: sub_em: ast.Subscript* = ast.Subscript*(ann_node) if sub_em.value is not None and sub_em.value.kind() == ast.ASTKind.Name: em_nm: ast.Name* = ast.Name*(sub_em.value) if em_nm.id is not None and (string.strcmp(em_nm.id, "CConst") == 0 or string.strcmp(em_nm.id, "CVolatile") == 0 \ or string.strcmp(em_nm.id, "BigEndian") == 0 or string.strcmp(em_nm.id, "LittleEndian") == 0 \ or string.strcmp(em_nm.id, "State") == 0 or string.strcmp(em_nm.id, "CStatic") == 0 \ or string.strcmp(em_nm.id, "CExport") == 0 or string.strcmp(em_nm.id, "CExtern") == 0 \ or string.strcmp(em_nm.id, "CRef") == 0 or string.strcmp(em_nm.id, "CTempRef") == 0): return self._resolve_enum_member_type(pool, sub_em.slice, imported_modules, from_imports) self.Gen.TypeGenErr.fatal_type_error(ann_node, "unsupported enum member type: only scalar types and their markers are allowed") return mlirlite.Int32(pool) # BinOp nodes are never enum member types (`T1 | T2` unions retired). if k == ast.ASTKind.BinOp: self.Gen.TypeGenErr.fatal_type_error(ann_node, "enum member type uses the retired `T1 | T2` union; write the concrete type directly") return mlirlite.Int32(pool) # 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 and self._enum_annotation_name_is_ptr(sub.value) != 0: return 1 if sub.slice is not None: return self._enum_annotation_name_is_ptr(sub.slice) return 0 return 0 def _enum_member_annotation_is_ptr(self, ann_node: ast.AST*) -> int: """Whether an enum member annotation contains a pointer type, 1=yes / 0=no""" 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(int(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(int(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(int(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(int(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(int(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(int(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: rawptr / 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(int(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(int(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(int(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(int(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(int(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 short names if string.strcmp(type_name, "sizet") == 0 or string.strcmp(type_name, "size_t") == 0: return 8 if string.strcmp(type_name, "int8") == 0 or string.strcmp(type_name, "uint8") == 0: return 1 if string.strcmp(type_name, "uchar") == 0: return 1 if string.strcmp(type_name, "int16") == 0 or string.strcmp(type_name, "uint16") == 0: return 2 if string.strcmp(type_name, "short") == 0 or string.strcmp(type_name, "ushort") == 0: return 2 if string.strcmp(type_name, "int32") == 0 or string.strcmp(type_name, "uint32") == 0: return 4 if string.strcmp(type_name, "int") == 0 or string.strcmp(type_name, "uint") == 0: return 4 if string.strcmp(type_name, "long") == 0 or string.strcmp(type_name, "ulong") == 0: return 4 if string.strcmp(type_name, "int64") == 0 or string.strcmp(type_name, "uint64") == 0: return 8 if string.strcmp(type_name, "longlong") == 0 or string.strcmp(type_name, "ulonglong") == 0: return 8 if string.strcmp(type_name, "ptrdifft") == 0: return 8 # 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(int(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(int(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, int(total_positions)): pos_max: sizet = 0 for vi in range(int(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(int(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 | rawptr)` (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, int(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(int(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(int(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(int(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(int(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(int(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(int(self.RenumClassCount)): vcount: int = int(self.RenumClassVariantCounts[i]) for j in range(int(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(int(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(int(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(int(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 int(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(int(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: # **变体名也必须一致**:同一 REnum 的不同变体可有**同名字段** # (LLVMType.Int.Bits 与 LLVMType.Float.Bits)。旧判据只比 (class, field), # 第二个同名字段被当成重复记录直接丢弃 ⇒ _lookup_renum_member_ty(class, # variant, idx) 查不到该记录 ⇒ match 绑定退化为按**物理槽类型**读取 # (max-payload 槽是 i8*/i64)⇒ 赋给 i32 时误报"隐式窄化禁止"(假阳性)。 _vn_existing: str = rawptr(self.RenumAliasVariant[i]) _vn_same: int = 0 if variant_name is None: _vn_same = 1 if _vn_existing is None else 0 elif _vn_existing is not None: _vn_same = 1 if string.strcmp(_vn_existing, variant_name) == 0 else 0 if _vn_same == 0: continue # 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(int(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 int(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(int(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(int(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