Files
ViperC/App/lib/core/Gen/Class/ClassGenDef.vp
T

587 lines
37 KiB
Plaintext

import lib.core.Gen.GenTypes as GenTypes
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdio
import viperlib
import lib.core.VLogger as VLogger
import lib.core.Gen.Translator as HT
import lib.core.CompilerFactory as CompilerFactory
from . import ClassGenBase
# ClassGenDef - ClassGenMixin split (stage D): translate_class_def entry point
novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
"""Entry point for translate_class_def."""
# translate_class_def - translate a class definition (cluster D)
def translate_class_def(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate a class definition; returns 0."""
if node is None or trans is None: return 0
cd: ast.ClassDef* = ast.ClassDef*(node)
if cd is None or cd.name is None: return 0
pool: memhub.MemBuddy* = trans.Pool
class_name: str = cd.name
if trans.ModuleSha1 is None:
# fail-fast: ModuleSha1 missing while translating a class (namespace isolation depends on it)
VLogger.error("ModuleSha1 missing while translating a class", "CLASS")
self.Gen.TypeGenErr.fatal_error(cd, "ModuleSha1 missing while translating a class", "TypeError")
# Namespace isolation: mark as visible in the current file. Must precede
# the existing check, or Phase B's early return would never mark it.
self.Gen.StructGen.add_visible_struct(pool, class_name)
# Record that this module declares the class (key "{SHA1}.{name}", independent
# of struct registration) so bare self-references resolve to the right module.
self.Gen.StructGen.mark_module_class(pool, trans.ModuleSha1, class_name)
# Enum class: handled by HandlesEnum. Fail-fast on dispatch ambiguity: REnum
# takes priority over CEnum/CUnion, whose semantics are mutually exclusive.
if self.Gen.ClassGenBase._is_enum_class(cd, trans) == 1:
if self.Gen.ClassGenBase._is_renum_class(cd, trans) == 1:
self.Gen.TypeGenErr.fatal_error(cd, f"class '{class_name}' is classified as both CEnum and REnum (dispatch ambiguity), " f"the REnum variant table cannot be registered", "TypeError")
return self.Gen.ClassGenEnum._translate_enum_def(trans, cd)
# Union class: handled by the union path
if self.Gen.ClassGenBase._is_union_class(cd, trans) == 1: return self.Gen.ClassGenEnum._translate_union_def(trans, cd)
# REnum class: handled by REnum ({i32 tag, max_payload} layout)
if self.Gen.ClassGenBase._is_renum_class(cd, trans) == 1: return self.Gen.ClassGenEnum._translate_renum_def(trans, cd)
# Generic class: stored as a template, no IR emitted until instantiation.
# Bind cd.type_params to a local to drive type inference.
tp_val: list[str]* = cd.type_params
if self.Gen.ClassGenGeneric._is_generic_class(tp_val) == 1:
self.Gen.ClassGenGeneric._register_generic_template(pool, cd, trans.ModuleSha1)
return 0
# Exception class (try-except): register encoding and inheritance, no struct
# (aligned with TPC HandlesClassDef.py L509-511).
if self.Gen.ClassGenBase._is_exception_class_def(cd) == 1: return self.Gen.ClassGenBase._register_exception_class_def(trans, cd)
# Nested class/struct: emit as a separate struct `{Outer}_{Inner}`, before all
# early returns so both declare_only paths recurse and REnum/union don't repeat.
self._translate_nested_classdefs(trans, cd)
# Check if already registered (SHA1 distinguishes same-named classes across modules)
existing: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(class_name, trans.ModuleSha1)
# Incomplete empty-class registration: existing FieldCount==0 but the body has
# fields, so neutralize the stale entry and rebuild via the full path below.
needs_rereg: int = 0
# Forward-declaration placeholder hit: do not early-return, fall into the
# in-place fill branch below so Ptr(placeholder.Ty) refs stay valid.
fill_fwd: int = 0
if existing is not None:
if existing.IsForwardDecl != 0: fill_fwd = 1
elif existing.FieldCount == 0 and trans._declare_only == 0:
if self.Gen.ClassGenBase._class_body_has_field(cd) != 0:
if existing.Name is not None: existing.Name[0] = '\0'
existing.Ty = None
existing.FieldCount = 0
needs_rereg = 1
if existing is not None and needs_rereg == 0 and fill_fwd == 0:
# Struct already registered (Phase A-pre or Phase B retranslation), but methods still need translating here
if existing.Ty is not None:
# Phase 1b (declare_only=0): existing.Ty's Name points into Phase 1a pool
# memory, so deep-copy the Name to stdlib to keep it alive cross-file.
if trans._declare_only == 0:
# Deep-copy existing.Ty to stdlib: the 1a pool Ty dangles across the
# 1b reset, hanging when the dangling field chain is walked.
ty_deep: mlirlite.LLVMType* = mlirlite.DeepCopyType(CompilerFactory.factory.PermPool, existing.Ty)
if ty_deep is not None: existing.Ty = ty_deep
old_name: str = mlirlite.get_struct_name(existing.Ty)
if old_name is not None:
on_len: sizet = string.strlen(old_name)
on_copy: str = CompilerFactory.factory.PermPool.alloc(on_len + 1)
if on_copy is not None:
string.strcpy(on_copy, old_name)
# In-place overwrite keeps existing.Ty's pointer identity (Phase A
# registered it); *= a boxed REnum variant is a load+store materialization.
*existing.Ty = mlirlite.LLVMType.Struct(mlirlite.get_struct_fields(existing.Ty), mlirlite.get_struct_fcount(existing.Ty), on_copy)
# Phase B: register the existing named struct into the current module
mlirlite.module_add_named_type(trans.Module, pool, existing.Ty)
# B5: the existing path also needs packed/aligned set, since Phase B's
# re-add_named_type resets IsPacked=0 and prints `{...}` (idt_entry root cause).
if self.Gen.ClassGenBase._class_has_tattr(cd.decorator_list, "packed") != 0:
mlirlite.module_set_named_type_packed(trans.Module, pool, existing.Ty, 1)
# StructTable may be rebuilt per phase, so IsPacked must be re-set or
# cross-module stub injection reads 0 and emits a non-packed stub.
existing.IsPacked = 1
# Complete field types like the fresh-definition path: value-type fields
# need full definitions here or this class becomes unsized for GEP.
self.Gen.StructGen._ensure_field_types_in_module(pool, trans.Module, existing.Ty)
align_ex: int = self.Gen.ClassGenBase._class_aligned_value(cd.decorator_list)
if align_ex > 0 and existing.Align == 0: existing.Align = align_ex
# Phase A-pre (declare_only=1): class registered, skip method bodies.
# Phase B (declare_only=0): translate method bodies in full.
if trans._declare_only == 1: self.Gen.ClassGenVTable._translate_oop_methods(trans, cd, existing.Ty, class_name, 1)
else: self.Gen.ClassGenVTable._translate_oop_methods(trans, cd, existing.Ty, class_name)
# SHA1 is already set during register_struct, no need to re-set
return 0
# Trigger generic specialization of Subscripts in base (e.g. GSListNode[Value])
# before _detect_vtable_status and field inheritance, or the lookup returns None.
self.Gen.ClassGenBase._trigger_base_specialization(trans, cd)
# VTable detection: decide whether a vtable is enabled
has_vtable: int = self.Gen.ClassGenBase._detect_vtable_status(cd, trans)
parent_name: str = self.Gen.ClassGenBase._get_parent_class(cd, trans)
# Multiple inheritance: collect real parent names (skip marker bases) for flatten/vtable/MRO
parents_buf: sizet* = pool.alloc(8 * GenTypes.PARENT_MAX)
parents_count: int = 0
if parents_buf is not None: parents_count = self.Gen.ClassGenBase._collect_parents(pool, cd, parents_buf, GenTypes.PARENT_MAX, trans)
is_novtable_deco: int = 0
if cd.decorator_list is not None:
if self.Gen.ClassGenBase._has_decorator(cd.decorator_list, "NoVTable") == 1: is_novtable_deco = 1
# PhaseA pre-registration deferral: when the parent is unregistered, defer the
# current class to a later pass. Phase B (declare_only==0) never defers.
if trans._declare_only == 1 and parent_name is not None:
parent_check: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(parent_name, trans.ModuleSha1)
if parent_check is None: parent_check = self.Gen.StructGen.find_struct(parent_name)
if parent_check is None: return 0
# 1. Walk the class body and collect field info
# Use CSizeT arrays to store pointer values (8 bytes on 64-bit systems)
field_names_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
field_types_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
field_defaults_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
field_annot_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
# Field volatile flag buffer (1 = annotation union contains t.CVolatile), parallel
# to field_annot_buf with values 0/1.
field_volatile_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
# Field pointee-volatile flag buffer (1 = rawptr[t.CVolatile | T], i.e. volatile T*),
# parallel to field_volatile_buf with values 0/1.
field_pointee_volatile_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
# Field const-object flag buffer (1 = annotation has t.CConst and the type is not
# a pointer), parallel to field_volatile_buf with values 0/1.
field_const_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
# Field byte-order buffer (0 = native, 1 = t.BigEndian, 2 = t.LittleEndian), parallel
# to field_volatile_buf; from the `X | t.BigEndian` annotation union.
field_byteorder_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
if field_names_buf is None or field_types_buf is None or field_defaults_buf is None: return 0
if field_annot_buf is None or field_volatile_buf is None: return 0
if field_pointee_volatile_buf is None or field_const_buf is None: return 0
if field_byteorder_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)
string.memset(field_defaults_buf, 0, 8 * GenTypes.FIELD_MAX)
string.memset(field_annot_buf, 0, 8 * GenTypes.FIELD_MAX)
string.memset(field_volatile_buf, 0, 8 * GenTypes.FIELD_MAX)
string.memset(field_pointee_volatile_buf, 0, 8 * GenTypes.FIELD_MAX)
string.memset(field_const_buf, 0, 8 * GenTypes.FIELD_MAX)
string.memset(field_byteorder_buf, 0, 8 * GenTypes.FIELD_MAX)
field_count: int = 0
# 1.5 Inherit parent fields (field flattening): parent fields precede child
# fields, layout [vtable_ptr?, parent..., child...]; dedup by name (first wins).
if parents_count > 0 and parents_buf is not None:
for pi in range(int(parents_count)):
pname_addr: sizet = parents_buf[pi]
if pname_addr == 0: continue
pname: str = rawptr(pname_addr)
if pname is None: continue
# SHA1-aware parent lookup to avoid picking a same-named class in another module
parent_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(pname, trans.ModuleSha1)
if parent_entry is None:
# The parent may live in another module (different SHA1), and
# find_struct_by_module does not fall back, so query find_struct manually.
parent_entry = self.Gen.StructGen.find_struct(pname)
if parent_entry is None:
# Parent not yet registered: do not register a partial entry (perm_pool
# entries survive passes and the existing early-return never repairs it).
return 0
pfc: int = parent_entry.FieldCount
for pfi in range(int(pfc)):
pfe: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(parent_entry, pfi)
if pfe is None: continue
pfn: str = self.Gen.StructGen.get_field_name_ptr(pfe)
if pfn is None: continue
# Skip the parent's vtable pointer field
if string.strcmp(pfn, "__vtable__") == 0: continue
# Multi-inheritance dedup: skip existing field names (earlier parent wins)
if self.Gen.ClassGenBase._field_name_exists(field_names_buf, field_count, pfn) != 0: continue
if field_count < GenTypes.FIELD_MAX:
pft: mlirlite.LLVMType* = self.Gen.StructGen.get_field_type_ptr(pfe)
pfd: ast.AST* = self.Gen.StructGen.get_field_default_ptr(pfe)
pfa: str = self.Gen.StructGen.get_field_annot_class_name(pfe)
field_names_buf[field_count] = sizet(pfn)
field_types_buf[field_count] = sizet(pft)
if pfd is not None: field_defaults_buf[field_count] = sizet(pfd)
else: field_defaults_buf[field_count] = 0
if pfa is not None:
stored_val: sizet = sizet(pfa)
field_annot_buf[field_count] = stored_val
else: field_annot_buf[field_count] = 0
# Inherit the volatile flag (semantics unchanged after flattening)
field_volatile_buf[field_count] = sizet(pfe.IsVolatile)
# Inherit the pointee-volatile flag (unchanged after flattening)
field_pointee_volatile_buf[field_count] = sizet(pfe.PointeeVolatile)
# Inherit the const-object flag (unchanged after flattening)
field_const_buf[field_count] = sizet(pfe.IsConst)
# Inherit the byte-order flag (unchanged after flattening)
field_byteorder_buf[field_count] = sizet(pfe.ByteOrder)
field_count += 1
children: list[ast.AST*]* = cd.children
if children is None: return 0
cn: sizet = len(children)
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: continue
# AnnAssign(target=Name(id), annotation=type, value=...)
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
# Class-body CDefine constant (any position): register into the global CDefine
# table so bare names inside methods resolve; catches post-method AnnAssigns.
if self.Gen.AssignGen.is_cdefine_annotation(aa.annotation) != 0:
if aa.value is not None: self.Gen.AssignGen._register_cdefine_from_value(pool, tgt.id, aa.value, aa.annotation)
# CDefine field type is fixed i32, collected directly to skip generic annotation
# resolution (which does not know "CDefine") and avoid a Phase B NameError.
if field_count < GenTypes.FIELD_MAX:
cdef_ty_l: mlirlite.LLVMType* = mlirlite.Int32(pool)
if cdef_ty_l is not None:
field_names_buf[field_count] = sizet(tgt.id)
field_types_buf[field_count] = sizet(cdef_ty_l)
if aa.value is not None: field_defaults_buf[field_count] = sizet(aa.value)
else: field_defaults_buf[field_count] = 0
field_count += 1
continue
# Skip compile-time metadata fields (__provides__/__requires__/__require_must__):
# they only feed static with-context reachability checks, no runtime code.
if string.strcmp(tgt.id, "__provides__") == 0:
self.Gen.ClassGenBase._register_class_meta_list(pool, GenTypes.CLASS_META_PROVIDES, class_name, aa.value)
continue
if string.strcmp(tgt.id, "__requires__") == 0:
self.Gen.ClassGenBase._register_class_meta_list(pool, GenTypes.CLASS_META_REQUIRES, class_name, aa.value)
continue
if string.strcmp(tgt.id, "__require_must__") == 0:
self.Gen.ClassGenBase._register_class_meta_list(pool, GenTypes.CLASS_META_REQUIRE_MUST, class_name, aa.value)
continue
# Resolve the field type
# Value-reference markers are parameter-only: a struct field `x: &T` fails fast.
self.Gen.TypeGenResolve.reject_ref_marker_position(aa.annotation, trans._imported_modules, "a struct field")
field_ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, aa.annotation, trans._imported_modules, trans._from_imports, trans, 0)
# void is not a valid field type (only for `void*` and opaque typedefs); letting
# it through builds an illegal layout, so fail-fast at compile time here.
if field_ty is not None and self.Gen.StmtGenDecl._is_void_type(field_ty) != 0: self.Gen.TypeGenErr.fatal_error(aa, f"field '{tgt.id}' cannot be void type (void is only allowed for void* and typedef aliases)", "TypeError")
# Single-arg t.CArray[elem] with a list literal -> static array [N x elem] (like
# local AnnAssign); otherwise it stays Ptr(elem) and for-in iterates pointers wrongly.
if field_ty is not None: field_ty = self.Gen.AssignGen._infer_static_array_ty_from_literal(pool, aa.annotation, aa.value, field_ty)
if field_ty is None:
# BUG 5 fix: remove the silent i32 fallback. Phase A-pre defers to the next
# pass; Phase B fast-fails. Generic Subscripts fall back to Ptr(i8) instead.
is_generic_sub: int = 0
if aa.annotation is not None and aa.annotation.kind() == ast.ASTKind.Subscript:
# t.CArray[elem, N] is a fixed-size value array, not a generic heap object;
# falling back to Ptr(i8) would register a degraded layout Phase B reuses.
sub_g: ast.Subscript* = ast.Subscript*(aa.annotation)
is_carray_sub_g: int = 0
if sub_g is not None and sub_g.value is not None:
if sub_g.value.kind() == ast.ASTKind.Attribute:
svg_at: ast.Attribute* = ast.Attribute*(sub_g.value)
if svg_at.attr is not None and string.strcmp(svg_at.attr, "CArray") == 0: is_carray_sub_g = 1
elif sub_g.value.kind() == ast.ASTKind.Name:
svg_nm: ast.Name* = ast.Name*(sub_g.value)
if svg_nm.id is not None and (string.strcmp(svg_nm.id, "CArray") == 0 or string.strcmp(svg_nm.id, "rawarray") == 0 or string.strcmp(svg_nm.id, "array") == 0):
is_carray_sub_g = 1
if is_carray_sub_g == 0: is_generic_sub = 1
if is_generic_sub != 0:
# Generic not specialized: fall back to Ptr(i8); AnnotClassName restores it later
field_ty = mlirlite.Ptr(pool, mlirlite.Int8(pool))
else:
# Phase A-pre (declare_only=1): a dependency may not be registered yet, so
# silently return 0 and let the multi-pass convergence retry.
if trans._declare_only == 1: return 0
# Phase A (declare_only=0, multi-pass): a dependency module may translate
# after this one, so do not register the struct with incomplete fields.
return 0
# Old behavior fast-failed with NameError (removed: it misreported while
# dependencies were pending; the convergence check handles real errors).
# Store into the array (pointer cast to CSizeT)
if field_count < GenTypes.FIELD_MAX:
diag_name_val: sizet = sizet(tgt.id)
field_names_buf[field_count] = diag_name_val
field_types_buf[field_count] = sizet(field_ty)
# Store the default-value AST pointer (None = no default)
if aa.value is not None: field_defaults_buf[field_count] = sizet(aa.value)
else: field_defaults_buf[field_count] = 0
# Extract the class name from the raw annotation (used to find the struct when a
# union degrades to Ptr(i8)); pass pool so generic Subscripts return a mangled name.
if aa.annotation is not None:
annot_cn: str = self.Gen.TypeGenResolve.extract_class_name_from_annotation(aa.annotation, trans._imported_modules, pool, trans._from_imports)
if annot_cn is not None:
diag_cn_val: sizet = sizet(annot_cn)
field_annot_buf[field_count] = diag_cn_val
else: field_annot_buf[field_count] = 0
# t.CVolatile storage-class flag: loads/stores through this field are
# volatile (e.g. Spinlock.locked: t.CVolatile | t.CInt).
if self.Gen.TypeGenErr.has_decorator_marker(aa.annotation, "CVolatile", 0) != 0: field_volatile_buf[field_count] = 1
else: field_volatile_buf[field_count] = 0
# rawptr[t.CVolatile | T] (volatile pointee, i.e. C volatile T*): derefs
# through this field (self.ptr[i]/self.ptr.field) are volatile.
if self.Gen.TypeGenErr.has_pointee_volatile_marker(aa.annotation) != 0: field_pointee_volatile_buf[field_count] = 1
else: field_pointee_volatile_buf[field_count] = 0
# const object field (annotation has t.CConst, non-pointer type): assigning
# through the object fail-fasts, matching TPC's Gen.field_const_flags.
if self.Gen.TypeGenErr.is_const_object_marker(aa.annotation, field_ty) != 0: field_const_buf[field_count] = 1
else: field_const_buf[field_count] = 0
# Byte-order marker (t.BigEndian / t.LittleEndian) -> 1 / 2; annotation `|`
# means "and", so `X | t.BigEndian` is a BigEndian-encoded X field.
if self.Gen.TypeGenErr.has_decorator_marker(aa.annotation, "BigEndian", 0) != 0: field_byteorder_buf[field_count] = 1
elif self.Gen.TypeGenErr.has_decorator_marker(aa.annotation, "LittleEndian", 0) != 0: field_byteorder_buf[field_count] = 2
else: field_byteorder_buf[field_count] = 0
else:
field_annot_buf[field_count] = 0
field_volatile_buf[field_count] = 0
field_pointee_volatile_buf[field_count] = 0
field_const_buf[field_count] = 0
field_byteorder_buf[field_count] = 0
field_count += 1
if field_count == 0:
# During PhaseA pre-registration the parent may be unregistered (single alphabetic
# pass), so field_count==0; return silently and retry in a later pass.
if trans._declare_only == 1 and parent_name is not None: return 0
# Fieldless class (interface/marker): do not skip it, as it may hold only methods
# (e.g. ShapeM/Vec2Extra); an empty struct is legal in LLVM.
# 2. Create the LLVM StructType
# Build the ParamNode chain
first_node: mlirlite.ParamNode* = None
prev_node: mlirlite.ParamNode* = None
total_field_count: int = field_count
# With a vtable, add an i8* vtable pointer as the first field
if has_vtable == 1:
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
vtable_node: mlirlite.ParamNode* = pool.alloc(mlirlite.ParamNode.__sizeof__())
if vtable_node is None:
VLogger.error(f"failed to alloc vtable node for {class_name}", "CLASS")
return 0
string.memset(vtable_node, 0, mlirlite.ParamNode.__sizeof__())
vtable_node.Ty = i8_ptr_ty
first_node = vtable_node
prev_node = vtable_node
total_field_count = field_count + 1
for fi in range(int(field_count)):
fty_addr: sizet = field_types_buf[fi]
if fty_addr == 0: continue
fty: mlirlite.LLVMType* = (mlirlite.LLVMType*)(fty_addr)
# Create the ParamNode
pnode: mlirlite.ParamNode* = pool.alloc(mlirlite.ParamNode.__sizeof__())
if pnode is None: continue
string.memset(pnode, 0, mlirlite.ParamNode.__sizeof__())
pnode.Ty = fty
if first_node is None: first_node = pnode
if prev_node is not None: prev_node.Next = pnode
prev_node = pnode
# Build the named struct type name "sha1.ClassName" or "ClassName". In Phase 1b the
# Name must survive per-file reset_to(mark1), so deep-copy to stdlib; 1a uses pool.
type_name: str = class_name
if trans.ModuleSha1 is not None:
name_buf: str = pool.alloc(64)
if name_buf is not None:
viperlib.snprintf(name_buf, 64, "%s.%s", trans.ModuleSha1, class_name)
type_name = name_buf
if trans._declare_only == 0 and type_name is not None:
tn_len: sizet = string.strlen(type_name)
tn_copy: str = CompilerFactory.factory.PermPool.alloc(tn_len + 1)
if tn_copy is not None:
string.strcpy(tn_copy, type_name)
type_name = tn_copy
# Create the StructType (named struct)
struct_ty: mlirlite.LLVMType* = mlirlite.Struct(pool, first_node, total_field_count, type_name)
if struct_ty is None:
VLogger.error(f"failed to create StructType for {class_name}", "CLASS")
return 0
# Deep-copy the whole struct_ty tree to stdlib and replace the pointer so entry.Ty
# and callers share one surviving copy; required too under StdlibAlloc=1.
if trans._declare_only == 0 or self.Gen.StructGen.get_stdlib_alloc() != 0:
st_copy: mlirlite.LLVMType* = mlirlite.DeepCopyType(CompilerFactory.factory.PermPool, struct_ty)
if st_copy is not None:
struct_ty = st_copy
type_name = mlirlite.get_struct_name(struct_ty)
# Forward-declaration hit: in-place fill keeps existing.Ty's identity, writing the
# field body into the same object before module_add_named_type to avoid re-registration.
if fill_fwd != 0 and existing is not None and existing.Ty is not None:
_fwd_name: str = mlirlite.get_struct_name(existing.Ty)
_fwd_fields: mlirlite.ParamNode* = mlirlite.get_struct_fields(struct_ty)
_fwd_fc: int = mlirlite.get_struct_fcount(struct_ty)
*existing.Ty = mlirlite.LLVMType.Struct(_fwd_fields, _fwd_fc, _fwd_name)
struct_ty = existing.Ty
type_name = _fwd_name
existing.IsForwardDecl = 0
existing.FieldCount = 0
# Register the named struct into the module (emit the %"name" = type { ... } line)
mlirlite.module_add_named_type(trans.Module, pool, struct_ty)
# B5: @c.Attribute(t.attr.packed) emits <{...}> (LLVM packed struct, no padding) as
# needed by binary-format structs like idt/gdt/elf/fat32.
is_packed_cd: int = self.Gen.ClassGenBase._class_has_tattr(cd.decorator_list, "packed")
if is_packed_cd != 0: mlirlite.module_set_named_type_packed(trans.Module, pool, struct_ty, 1)
# Scan field types and declare cross-module struct refs in the current module, e.g.
# list[int].__pool__ referencing memhub.MemManager needs a %"sha1.MemManager" opaque.
for fi in range(int(field_count)):
fty_addr_op: sizet = field_types_buf[fi]
if fty_addr_op == 0: continue
fty_op: mlirlite.LLVMType* = (mlirlite.LLVMType*)(fty_addr_op)
if self.Gen.ExprGenBase.is_ptr_type(fty_op) != 0:
# Pointer field: the pointee only needs an opaque declaration (GEP does not index into it)
mlirlite.module_ensure_opaque_for_type(trans.Module, pool, fty_op)
else:
# Value-type field (struct body / struct array): copy the full definition, or
# this class becomes unsized and llc rejects field-indexed GEPs.
self.Gen.StructGen.ensure_struct_def_in_module(pool, trans.Module, fty_op)
self.Gen.StructGen._ensure_field_types_in_module(pool, trans.Module, fty_op)
# 3. Register into HandlesStruct
entry: GenTypes.StructEntry* = None
if fill_fwd != 0 and existing is not None:
# Forward-declaration placeholder: the field body is already in existing.Ty, so
# reuse existing (a new entry/Ty would orphan issued Ptr(placeholder.Ty) refs).
entry = existing
else: entry = self.Gen.StructGen.register_struct(pool, class_name, struct_ty, trans.ModuleSha1)
if entry is None:
VLogger.error(f"failed to register {class_name}", "CLASS")
return 0
# B5: record the packed flag into StructEntry for __sizeof__/layout queries
if is_packed_cd != 0: entry.IsPacked = 1
# B5: record the aligned value (@c.Attribute(t.attr.aligned(N)), e.g. fpu_context 16)
# for global/local alloca alignment.
align_cd: int = self.Gen.ClassGenBase._class_aligned_value(cd.decorator_list)
if align_cd > 0: entry.Align = align_cd
# Mark vtable status (use entry directly to avoid same-named cross-module lookup errors)
if has_vtable == 1: entry.HasVTable = 1
if is_novtable_deco == 1: entry.IsNoVTable = 1
if parent_name is not None:
# In 1b the parent name is a pool string that dangles after reset, and later files
# strcmp it via find_struct, so copy it to stdlib for cross-file survival.
if self.Gen.StructGen.get_stdlib_alloc() != 0:
pn_len: sizet = string.strlen(parent_name)
pn_buf: str = CompilerFactory.factory.PermPool.alloc(pn_len + 1)
if pn_buf is not None:
string.strcpy(pn_buf, parent_name)
entry.ParentName = pn_buf
else: entry.ParentName = parent_name
# Multiple inheritance: set the parent array for MRO/field/vtable queries (stdlib-deep-copied)
if parents_count > 0 and parents_buf is not None: self.Gen.StructGen.set_parent_names(pool, class_name, parents_buf, parents_count)
# Add field info. With a vtable, add the __vtable__ placeholder first (index 0),
# so user fields start at index 1.
if has_vtable == 1:
i8_ptr_ty2: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
self.Gen.StructGen.add_field(pool, entry, "__vtable__", i8_ptr_ty2, None, None)
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)
# Restore the default-value AST pointer
fdef_addr: sizet = field_defaults_buf[fi]
fdef: ast.AST* = None
if fdef_addr != 0: fdef = (ast.AST*)(fdef_addr)
# Restore the annotation class-name pointer
fannot_addr: sizet = field_annot_buf[fi]
fannot: str = None
if fannot_addr != 0: fannot = str(fannot_addr)
if fname is not None and fty2 is not None:
fidx_add: int = self.Gen.StructGen.add_field(pool, entry, fname, fty2, fdef, fannot)
# t.CVolatile field flag (field_volatile_buf[fi] corresponds 1:1 with add_field order)
if fidx_add >= 0 and field_volatile_buf[fi] != 0:
fe_vol: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(entry, fidx_add)
if fe_vol is not None: fe_vol.IsVolatile = 1
# rawptr[t.CVolatile | T] field flag (field_pointee_volatile_buf[fi] corresponds
# 1:1 with add_field): derefs through it load/store with volatile.
if fidx_add >= 0 and field_pointee_volatile_buf[fi] != 0:
fe_pv: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(entry, fidx_add)
if fe_pv is not None: fe_pv.PointeeVolatile = 1
# byte-order field flag (field_byteorder_buf[fi] corresponds 1:1 with add_field order)
if fidx_add >= 0 and field_byteorder_buf[fi] != 0:
fe_bo: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(entry, fidx_add)
if fe_bo is not None: fe_bo.ByteOrder = int(field_byteorder_buf[fi])
# const object field flag (field_const_buf[fi] corresponds 1:1 with add_field order)
if fidx_add >= 0 and field_const_buf[fi] != 0:
fe_c: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(entry, fidx_add)
if fe_c is not None: fe_c.IsConst = 1
# Phase 1a declaration mode: only register the struct and OOP flags, no method bodies
if trans._declare_only == 1:
self.Gen.ClassGenVTable._translate_oop_methods(trans, cd, struct_ty, class_name, 1)
return 0
# 4. OOP methods: scan FunctionDefs, mark OOP, translate methods, emit __before_init__
self.Gen.ClassGenVTable._translate_oop_methods(trans, cd, struct_ty, class_name)
return 0
# _translate_nested_classdefs - recursively translate nested classes/structs. Rewrite
# child names to flat `{Outer}_{Inner}` (PermPool) then recurse, restoring the def window.
def _translate_nested_classdefs(self, trans: HT.Translator*, cd: ast.ClassDef*) -> None:
"""Recursively translate nested ClassDefs under the flat name {Outer}_{Inner}."""
if trans is None or cd is None or cd.name is None: return
children: list[ast.AST*]* = cd.children
if children is None: return
cn: sizet = len(children)
ci: sizet = 0
while ci < cn:
child_ast: ast.AST* = children.get(ci)
ci += 1
if child_ast is None: continue
if child_ast.kind() != ast.ASTKind.ClassDef: continue
child: ast.ClassDef* = ast.ClassDef*(child_ast)
if child is None or child.name is None: continue
# Flat name `{Outer}_{Inner}` (prefer PermPool so it survives phases; fall back to
# the pass pool when missing, matching the old path).
flat_pool: memhub.MemBuddy* = CompilerFactory.factory.PermPool
if flat_pool is None: flat_pool = trans.Pool
if flat_pool is None: continue
outer_len: sizet = string.strlen(cd.name)
inner_len: sizet = string.strlen(child.name)
flat_len: sizet = outer_len + 1 + inner_len
flat_buf: str = flat_pool.alloc(flat_len + 1)
if flat_buf is None: continue
viperlib.snprintf(flat_buf, flat_len + 1, "%s_%s", cd.name, child.name)
# Rewrite the child name then recurse (everything registers/emits with the flat name)
old_name: str = child.name
child.name = flat_buf
saved_def_sha1: str = self.Gen.StructGen.CurDefModuleSha1
saved_def_cls: str = self.Gen.StructGen.CurDefClassName
self.translate_class_def(trans, child_ast)
self.Gen.StructGen.CurDefModuleSha1 = saved_def_sha1
self.Gen.StructGen.CurDefClassName = saved_def_cls
child.name = old_name