1202 lines
71 KiB
Plaintext
1202 lines
71 KiB
Plaintext
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 |