587 lines
37 KiB
Plaintext
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 |