与 includes 同步执行两批源改写:R12 把「i: int = 0 与 while i < N: … i += 1」形式的计数循环改写为「for i in range(N):」;R13 把 c.Set(*p, v) 迁移为 *p = v、c.Set(a, b) 迁移为 a = b(c.Set 是正在退化的路径)。 验证:bootstrap 三重重编 SUCCESS(无 vast_c 解析回退);全量 77/77 顺序回归通过(WebTest 首轮网络超时,单独重跑通过)。
1600 lines
101 KiB
Plaintext
1600 lines
101 KiB
Plaintext
import t, c
|
||
from stdint import *
|
||
import memhub
|
||
import string
|
||
import stdlib
|
||
import mlirlite
|
||
import stdio
|
||
import ast
|
||
import hashtable
|
||
import viperlib
|
||
import lib.core.CompilerFactory as CompilerFactory
|
||
import lib.core.VLogger as VLogger
|
||
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
|
||
import lib.core.Gen.GenTypes as GenTypes
|
||
import lib.core.Gen.Generator as Generator
|
||
import lib.core.Gen.Type.TypeSpec as TSpec
|
||
import lib.core.Gen.GenApi as GenApi
|
||
|
||
# StructGenMixin - struct registration / field lookup mixin.
|
||
# Registers and looks up structs (name/type indexes), finds fields, and matches cross-module ownership; @t.NoVTable uses static binding (MRO calls + this adjustment, no vtable cost).
|
||
|
||
|
||
# Constants (kept in sync with HandlesStruct).
|
||
define STRUCT_MAX = 512
|
||
# Name | type index slot count (power of two and > 2*STRUCT_MAX so load factor < 0.5 and probing terminates).
|
||
define STRUCT_IDX_CAP = 1024
|
||
# Field-slot upper limit (kept in sync with HandlesStruct): Generator flattens ~120 fields across 43 mixins, so a limit of 64 would drop 9th+ parent fields and break __init__ attribute assignment.
|
||
define FIELD_MAX = 256
|
||
define FIELD_NAME_MAX = 64
|
||
# Multiple-inheritance (MRO) support: max parents per class / max MRO chain length (Generator has 43 parents; PARENT_MAX=16 would drop the 17th+ parent fields and break __init__ writing self.DiagStmtCount).
|
||
define PARENT_MAX = 64
|
||
define MRO_MAX = 64
|
||
|
||
# Index hash constants (32-bit FNV-1a; all < 2^63 to avoid large decimal literal parsing risk).
|
||
define FNV32_OFFSET = 2166136261
|
||
define FNV32_PRIME = 16777619
|
||
define FNV32_MASK = 4294967295
|
||
|
||
|
||
# Index hash functions (name | Ty pointer value -> 64-bit hash).
|
||
# Used by StructTable's "name | Ty pointer value -> first registration index" index probing (see _struct_name_idx_insert / _struct_name_idx_find etc.).
|
||
def _struct_name_hash(name: str) -> uint64:
|
||
"""Hash a name with 32-bit FNV-1a over bytes up to NUL; empty name returns the offset basis."""
|
||
h: uint64 = uint64(FNV32_OFFSET)
|
||
if name is None: return h
|
||
i: int = 0
|
||
while name[i] != '\0':
|
||
h = (h ^ uint64(int(name[i]))) * uint64(FNV32_PRIME)
|
||
h = h & uint64(FNV32_MASK)
|
||
i += 1
|
||
return h
|
||
|
||
|
||
def _struct_ty_hash(tyval: uint64) -> uint64:
|
||
"""Hash a Ty pointer value with 32-bit FNV-1a over its 8 bytes."""
|
||
h: uint64 = uint64(FNV32_OFFSET)
|
||
v: uint64 = tyval
|
||
for j in range(8):
|
||
h = (h ^ (v & uint64(255))) * uint64(FNV32_PRIME)
|
||
h = h & uint64(FNV32_MASK)
|
||
v = v >> 8
|
||
return h
|
||
|
||
|
||
|
||
# StructGenMixin - struct registry / field lookup / visibility.
|
||
novirtual class StructGenMixin:
|
||
"""Mixin for struct registration and lookup."""
|
||
|
||
# --- Global registry (was _struct_table/_struct_count) ---
|
||
Gen: Generator.Generator*
|
||
StructTable: GenTypes.StructEntry* # STRUCT_MAX-slot array
|
||
StructCount: int
|
||
|
||
# --- OOM-reclaim allocation mode (was _g_stdlib_alloc) ---
|
||
StdlibAlloc: int
|
||
|
||
# --- Cross-module namespace isolation (was _visible_structs/_strict_visibility) ---
|
||
VisibleStructs: hashtable.HashTable*
|
||
StrictVisibility: int
|
||
|
||
# --- Per-module "declared class names" set (key "{module SHA1}.{class name}") ---
|
||
# Independent of StructTable registration state: recorded at the class-definition entry, so it can attribute a bare class name to its module even mid-definition; used by generic type-arg normalization (TypeGenResolve._norm_type_arg_name rule 5.5).
|
||
ModuleClasses: hashtable.HashTable*
|
||
|
||
# --- SHA1 of the module owning the class currently being defined (recorded by mark_module_class) ---
|
||
# Not reusing TypeGenBase.CurrentModuleSha1: A-pre/StubMerger paths only set Translator.ModuleSha1, and trans.ModuleSha1 is authoritative on every path, so it is the single source of truth for the "currently defining module".
|
||
CurDefModuleSha1: str
|
||
|
||
# --- Class name of the class currently being defined (recorded by mark_module_class; table-independent self-reference test) ---
|
||
# Needed because a self-referential field is parsed before its class is registered (and ModuleClasses may be unavailable), leaving a bare name that splits into two specialization names; the class name comes straight from the definition entry and never queries a table, so it is always decidable in that window.
|
||
CurDefClassName: str
|
||
|
||
# --- Top-level ClassDef name table for this file (rebuilt per file; registered by register_file_classes) ---
|
||
# A second "bare class name" window: the prologue translates module-level vars/forward declarations before translate_children, when the defining-class window is not yet established, so parameter/global bare class names would be registered as bare and split into two specialization names. This table records the positive fact "this name is declared in this file" before any annotation is parsed.
|
||
FileClassNames: hashtable.HashTable*
|
||
|
||
# Module SHA1 of this file (passed in by the Translator file entry with the pre-scan).
|
||
# Used only together with a FileClassNames hit, so even a stale value in the window cannot mis-attribute (a stale sha1 and "this file declares the class" cannot both hold).
|
||
FileModuleSha1: str
|
||
|
||
# Top-level FunctionDef name table for this file (see register_file_funcs).
|
||
# A stub-injected bare-name declaration (extern/state/export, no SHA1 prefix) that matches a local declaration would take the name first and make the local declaration silently dropped in forward_declare_functions; the injection side (VpiReader._inject_function_declare) skips these names.
|
||
FileFuncNames: hashtable.HashTable*
|
||
|
||
# --- Name | type index (fast lookup path; falls back to a linear scan when the index is untrusted) ---
|
||
StructNameIdx: sizet* # name -> first registration index (u64 slot: 0 = empty, else index+1)
|
||
StructTypeIdx: sizet* # Ty pointer value -> first registration index (same layout)
|
||
StructIdxCap: int # index slot count (power of two, > 2*STRUCT_MAX)
|
||
StructIdxBuilt: int # indexed entry count (-1 = untrusted; trusted only when == StructCount)
|
||
|
||
def InitStructGen(self) -> int:
|
||
"""Initialize struct-mixin fields; called inline from subclass constructors."""
|
||
if self is None: return 1
|
||
self.StructTable = None
|
||
self.StructCount = 0
|
||
self.StdlibAlloc = 0
|
||
self.VisibleStructs = None
|
||
self.StrictVisibility = 0
|
||
self.ModuleClasses = None
|
||
self.CurDefModuleSha1 = None
|
||
self.CurDefClassName = None
|
||
self.FileClassNames = None
|
||
self.FileModuleSha1 = None
|
||
self.FileFuncNames = None
|
||
self.StructNameIdx = None
|
||
self.StructTypeIdx = None
|
||
self.StructIdxCap = 0
|
||
self.StructIdxBuilt = 0
|
||
return 0
|
||
|
||
# P2-2 migrated methods (bodies from HandlesStruct.vp; globals become self fields, same-module calls become self methods; signatures identical to HandlesStruct).
|
||
|
||
def set_stdlib_alloc(self, flag: int) -> None:
|
||
"""Set the OOM-reclaim allocation mode: 1 = 1b phase (stdlib), 0 = registration phase (pool)."""
|
||
self.StdlibAlloc = flag
|
||
|
||
def get_stdlib_alloc(self) -> int:
|
||
"""Return the OOM-reclaim allocation mode (read via HandlesStruct delegation)."""
|
||
return self.StdlibAlloc
|
||
|
||
def reset_visible_structs(self, pool: memhub.MemBuddy*, strict: int) -> None:
|
||
"""Reset per-file visibility state and rebuild the per-file tables."""
|
||
self.StrictVisibility = strict
|
||
self.CurDefModuleSha1 = None
|
||
self.CurDefClassName = None
|
||
# The file-level class name table is rebuilt per file the same way (keys and table share a pool, so no dangling; re-registered by the Translator pre-scan, see register_file_classes).
|
||
# FileModuleSha1 points to trans.ModuleSha1 (same-pool string), so it must be cleared too to avoid reading reclaimed memory in the next file.
|
||
self.FileClassNames = None
|
||
self.FileModuleSha1 = None
|
||
self.FileFuncNames = None
|
||
self.ModuleClasses = hashtable.HashTable(pool)
|
||
if strict != 0:
|
||
# Strict mode: create a new HashTable recording visible structs.
|
||
self.VisibleStructs = hashtable.HashTable(pool)
|
||
else:
|
||
# Lenient mode: everything is visible, no HashTable needed.
|
||
self.VisibleStructs = None
|
||
|
||
def add_visible_struct(self, pool: memhub.MemBuddy*, name: str) -> None:
|
||
"""Record a visible struct name (class definition and from-import)."""
|
||
if name is None: return
|
||
# Lenient mode: everything is visible, no need to record.
|
||
if self.StrictVisibility == 0: return
|
||
if self.VisibleStructs is None: return
|
||
# Skip if already present (HashTable.__setitem__ would overwrite; check early to avoid a redundant allocation).
|
||
if self.VisibleStructs.__contains__(name) != 0: return
|
||
self.VisibleStructs.set_int(name, 1)
|
||
|
||
def is_struct_visible(self, name: str) -> int:
|
||
"""Return 1 when the struct name is visible, 0 otherwise."""
|
||
if self.StrictVisibility == 0: return 1
|
||
if name is None or self.VisibleStructs is None: return 0
|
||
return self.VisibleStructs.__contains__(name)
|
||
|
||
def _module_class_key(self, pool: memhub.MemBuddy*, sha1: str, name: str) -> str:
|
||
"""Build the "{sha1}.{class name}" lookup and registration key in the given pool."""
|
||
if pool is None or sha1 is None or name is None: return None
|
||
k_len: sizet = string.strlen(sha1) + 1 + string.strlen(name) + 1
|
||
k_buf: str = pool.alloc(k_len)
|
||
if k_buf is None: return None
|
||
k_buf[0] = '\0'
|
||
string.strcat(k_buf, sha1)
|
||
string.strcat(k_buf, ".")
|
||
string.strcat(k_buf, name)
|
||
return k_buf
|
||
|
||
def mark_module_class(self, pool: memhub.MemBuddy*, sha1: str, name: str) -> None:
|
||
"""Record "{sha1}.{class name}" as declared by the class's defining module."""
|
||
if sha1 is None or name is None: return
|
||
self.CurDefModuleSha1 = sha1
|
||
# Record the class name at the same time (table-independent self-reference test; must be recorded even if the pool/table is missing, since it never queries a table, see _norm_type_arg_name rule 5.5).
|
||
self.CurDefClassName = name
|
||
if pool is None or self.ModuleClasses is None:
|
||
# Not per-file initialized (this path did not call reset_visible_structs): do not register.
|
||
# Do not lazily build the table here: it must share a pool with the keys and be reset per file, else strcmp would read reclaimed keys (ASan access-violation).
|
||
return
|
||
k_buf: str = self._module_class_key(pool, sha1, name)
|
||
if k_buf is None: return
|
||
if self.ModuleClasses.__contains__(k_buf) != 0: return
|
||
self.ModuleClasses.set_int(k_buf, 1)
|
||
|
||
def is_module_class(self, pool: memhub.MemBuddy*, sha1: str, name: str) -> int:
|
||
"""Return 1 when the bare name is declared by the module sha1, regardless of registration."""
|
||
if pool is None or sha1 is None or name is None: return 0
|
||
if self.ModuleClasses is None: return 0
|
||
k_buf: str = self._module_class_key(pool, sha1, name)
|
||
if k_buf is None: return 0
|
||
return self.ModuleClasses.__contains__(k_buf)
|
||
|
||
def register_file_classes(self, pool: memhub.MemBuddy*, sha1: str, tree: ast.AST*) -> None:
|
||
"""Pre-register the top-level class names and module SHA1 of this file."""
|
||
if pool is None or sha1 is None or tree is None: return
|
||
self.FileModuleSha1 = sha1
|
||
ch: list[ast.AST*]* = tree.children
|
||
if ch is None: return
|
||
if self.FileClassNames is None: self.FileClassNames = hashtable.HashTable(pool)
|
||
if self.FileClassNames is None: return
|
||
cn: sizet = len(ch)
|
||
ci: sizet
|
||
for ci in range(cn):
|
||
child: ast.AST* = ch.get(ci)
|
||
if child is None: continue
|
||
if child.kind() != ast.ASTKind.ClassDef: continue
|
||
cd: ast.ClassDef* = ast.ClassDef*(child)
|
||
if cd is None or cd.name is None: continue
|
||
if self.FileClassNames.__contains__(cd.name) != 0: continue
|
||
# Keys must share a pool with the table (AST name strings zero-copy point into src_buf and may become invalid across phases): copy into the pool before registering to avoid strcmp on dangling memory during table resize/query.
|
||
nm_len: sizet = string.strlen(cd.name)
|
||
nm_buf: str = pool.alloc(nm_len + 1)
|
||
if nm_buf is None: continue
|
||
string.strcpy(nm_buf, cd.name)
|
||
self.FileClassNames.set_int(nm_buf, 1)
|
||
|
||
def is_file_class(self, pool: memhub.MemBuddy*, name: str) -> int:
|
||
"""Return 1 when the bare name is a top-level class declared in this file."""
|
||
if name is None or self.FileClassNames is None: return 0
|
||
return self.FileClassNames.__contains__(name)
|
||
|
||
def register_file_funcs(self, pool: memhub.MemBuddy*, tree: ast.AST*) -> None:
|
||
"""Pre-register the top-level function names of this file."""
|
||
if pool is None or tree is None: return
|
||
ch: list[ast.AST*]* = tree.children
|
||
if ch is None: return
|
||
if self.FileFuncNames is None: self.FileFuncNames = hashtable.HashTable(pool)
|
||
if self.FileFuncNames is None: return
|
||
cn: sizet = len(ch)
|
||
ci: sizet
|
||
for ci in range(cn):
|
||
child: ast.AST* = ch.get(ci)
|
||
if child is None: continue
|
||
if child.kind() != ast.ASTKind.FunctionDef: continue
|
||
fd: ast.FunctionDef* = ast.FunctionDef*(child)
|
||
if fd is None or fd.name is None: continue
|
||
if self.FileFuncNames.__contains__(fd.name) != 0: continue
|
||
# Keys must share a pool with the table (AST name strings zero-copy point into src_buf and may become invalid across phases): copy into the pool before registering to avoid strcmp on dangling memory during table resize/query.
|
||
nm_len: sizet = string.strlen(fd.name)
|
||
nm_buf: str = pool.alloc(nm_len + 1)
|
||
if nm_buf is None: continue
|
||
string.strcpy(nm_buf, fd.name)
|
||
self.FileFuncNames.set_int(nm_buf, 1)
|
||
|
||
def is_file_func(self, name: str) -> int:
|
||
"""Return 1 when the bare name is a top-level function declared in this file."""
|
||
if name is None or self.FileFuncNames is None: return 0
|
||
return self.FileFuncNames.__contains__(name)
|
||
|
||
def get_file_module_sha1(self) -> str:
|
||
"""Return the module SHA1 of the file being translated (None if unregistered)."""
|
||
return self.FileModuleSha1
|
||
|
||
def init_struct_table(self, pool: memhub.MemBuddy*) -> int:
|
||
"""Initialize the struct registry; returns 1 on success, using the perm pool in 1b."""
|
||
if self.StructTable is not None: return 1
|
||
|
||
entry_size: sizet = GenTypes.StructEntry.__sizeof__()
|
||
table_pool: memhub.MemBuddy* = pool
|
||
if self.StdlibAlloc != 0:
|
||
table_pool = CompilerFactory.factory.PermPool
|
||
if table_pool is None: table_pool = pool
|
||
self.StructTable = table_pool.alloc(entry_size * STRUCT_MAX)
|
||
if self.StructTable is None: return 0
|
||
string.memset(self.StructTable, 0, entry_size * STRUCT_MAX)
|
||
self.StructCount = 0
|
||
# Build the name | type index (allocated from the same pool as the table, so they live and die together).
|
||
# The index is an optional accelerator: on allocation failure keep None with StructIdxBuilt=-1 and lookups fall back to the original linear scan (identical semantics), so initialization does not fail.
|
||
idx_bytes: sizet = sizet(STRUCT_IDX_CAP) * 8
|
||
name_idx: sizet* = table_pool.alloc(idx_bytes)
|
||
type_idx: sizet* = table_pool.alloc(idx_bytes)
|
||
if name_idx is None or type_idx is None:
|
||
self.StructNameIdx = None
|
||
self.StructTypeIdx = None
|
||
self.StructIdxCap = 0
|
||
self.StructIdxBuilt = -1
|
||
else:
|
||
string.memset(name_idx, 0, idx_bytes)
|
||
string.memset(type_idx, 0, idx_bytes)
|
||
self.StructNameIdx = name_idx
|
||
self.StructTypeIdx = type_idx
|
||
self.StructIdxCap = STRUCT_IDX_CAP
|
||
self.StructIdxBuilt = 0
|
||
return 1
|
||
|
||
def _get_struct_entry(self, i: int) -> GenTypes.StructEntry*:
|
||
"""Return struct entry i, or None when out of range."""
|
||
if self.StructTable is None or i < 0 or i >= STRUCT_MAX: return None
|
||
entry_size: sizet = GenTypes.StructEntry.__sizeof__()
|
||
addr: uint64 = uint64(self.StructTable) + i * entry_size
|
||
return GenTypes.StructEntry*(addr)
|
||
|
||
# Name | type index (open addressing with linear probing; slot stores index+1, 0 = empty).
|
||
# Reduces find_struct/find_struct_by_module (name) and find_struct_by_type (type pointer) from O(n) to expected O(1); conflicts compare keys (strcmp for names, Ty pointer value for types). Trusted only when StructIdxBuilt == StructCount; otherwise fall back to a linear scan. Neutralized entries (Name[0]=0 / Ty=None) leave stale keys that simply miss on probe.
|
||
def _struct_entry_name(self, i: int) -> str:
|
||
"""Return entry i's Name for index probing, or None when out of range or empty."""
|
||
e: GenTypes.StructEntry* = self._get_struct_entry(i)
|
||
if e is None: return None
|
||
return e.Name
|
||
|
||
def _struct_entry_tyval(self, i: int) -> uint64:
|
||
"""Return entry i's Ty pointer value, or 0 when Ty is None."""
|
||
e: GenTypes.StructEntry* = self._get_struct_entry(i)
|
||
if e is None or e.Ty is None: return 0
|
||
return uint64(e.Ty)
|
||
|
||
def _struct_name_idx_insert(self, ei: int, name: str) -> int:
|
||
"""Insert (name, ei) into the name index; returns 0 on success, 1 on failure."""
|
||
base: sizet* = self.StructNameIdx
|
||
if base is None or self.StructIdxCap <= 0: return 1
|
||
h: uint64 = _struct_name_hash(name)
|
||
mask: sizet = sizet(self.StructIdxCap - 1)
|
||
idx: sizet = sizet(h) & mask
|
||
for i in range(self.StructIdxCap):
|
||
slot: sizet* = (sizet*)(uint64(base) + uint64(idx * 8))
|
||
if slot[0] == 0:
|
||
slot[0] = sizet(ei + 1)
|
||
return 0
|
||
idx = (idx + 1) & mask
|
||
return 1
|
||
|
||
def _struct_type_idx_insert(self, ei: int, tyval: uint64) -> int:
|
||
"""Insert (Ty pointer value, ei) into the type index; returns 0 on success, 1 on failure."""
|
||
base: sizet* = self.StructTypeIdx
|
||
if base is None or self.StructIdxCap <= 0 or tyval == 0: return 1
|
||
h: uint64 = _struct_ty_hash(tyval)
|
||
mask: sizet = sizet(self.StructIdxCap - 1)
|
||
idx: sizet = sizet(h) & mask
|
||
for i in range(self.StructIdxCap):
|
||
slot: sizet* = (sizet*)(uint64(base) + uint64(idx * 8))
|
||
if slot[0] == 0:
|
||
slot[0] = sizet(ei + 1)
|
||
return 0
|
||
idx = (idx + 1) & mask
|
||
return 1
|
||
|
||
def _struct_idx_add(self, ei: int, entry: GenTypes.StructEntry*) -> None:
|
||
"""Sync both indexes after adding an entry; mark the index untrusted on any write failure."""
|
||
if self.StructNameIdx is None or self.StructTypeIdx is None or self.StructIdxCap <= 0:
|
||
self.StructIdxBuilt = -1
|
||
return
|
||
failed: int = 0
|
||
if entry is not None and entry.Name is not None: failed = failed + self._struct_name_idx_insert(ei, entry.Name)
|
||
if entry is not None and entry.Ty is not None: failed = failed + self._struct_type_idx_insert(ei, uint64(entry.Ty))
|
||
if failed != 0: self.StructIdxBuilt = -1
|
||
else:
|
||
# ei = StructCount-1, so after syncing indexed entries == StructCount.
|
||
self.StructIdxBuilt = self.StructCount
|
||
|
||
def _struct_name_idx_find(self, name: str) -> int:
|
||
"""Return the first registration index for a name (>=0 hit), else -1 to fall back to a scan."""
|
||
base: sizet* = self.StructNameIdx
|
||
if base is None or self.StructIdxCap <= 0 or self.StructIdxBuilt != self.StructCount: return -1
|
||
# Empty-name ("") special case: a neutralized entry's Name is exactly "" (Name[0]=0), and the original scan would hit the first such entry, but index slots are hashed by the original name, so an empty name always falls back to the original scan for byte-consistent behavior.
|
||
if name is None or name[0] == '\0': return -1
|
||
h: uint64 = _struct_name_hash(name)
|
||
mask: sizet = sizet(self.StructIdxCap - 1)
|
||
idx: sizet = sizet(h) & mask
|
||
for i in range(self.StructIdxCap):
|
||
slot: sizet* = (sizet*)(uint64(base) + uint64(idx * 8))
|
||
sv: sizet = slot[0]
|
||
if sv == 0: return -1
|
||
se: int = int(sv) - 1
|
||
ek: str = self._struct_entry_name(se)
|
||
if ek is not None and string.strcmp(ek, name) == 0: return se
|
||
idx = (idx + 1) & mask
|
||
return -1
|
||
|
||
def _struct_type_idx_find(self, tyval: uint64) -> int:
|
||
"""Return the first registration index for a Ty value (>=0 hit), else -1 to fall back to a scan."""
|
||
base: sizet* = self.StructTypeIdx
|
||
if base is None or self.StructIdxCap <= 0 or tyval == 0: return -1
|
||
if self.StructIdxBuilt != self.StructCount: return -1
|
||
h: uint64 = _struct_ty_hash(tyval)
|
||
mask: sizet = sizet(self.StructIdxCap - 1)
|
||
idx: sizet = sizet(h) & mask
|
||
for i in range(self.StructIdxCap):
|
||
slot: sizet* = (sizet*)(uint64(base) + uint64(idx * 8))
|
||
sv: sizet = slot[0]
|
||
if sv == 0: return -1
|
||
se: int = int(sv) - 1
|
||
if self._struct_entry_tyval(se) == tyval: return se
|
||
idx = (idx + 1) & mask
|
||
return -1
|
||
|
||
def _get_field_entry(self, struct_entry: GenTypes.StructEntry*, i: int) -> GenTypes.FieldEntry*:
|
||
"""Return field entry i of a struct, or None when out of range."""
|
||
if struct_entry is None or i < 0 or i >= FIELD_MAX: return None
|
||
field_size: sizet = GenTypes.FieldEntry.__sizeof__()
|
||
addr: uint64 = uint64(struct_entry.Fields) + i * field_size
|
||
return GenTypes.FieldEntry*(addr)
|
||
|
||
def register_struct(self, pool: memhub.MemBuddy*, name: str, struct_ty: mlirlite.LLVMType*, sha1: str = None) -> GenTypes.StructEntry*:
|
||
"""Register a struct type by type pointer and return its StructEntry pointer."""
|
||
if self.init_struct_table(pool) == 0: return None
|
||
# De-duplicate by type pointer (the same type is not re-registered; supports cross-module same-named classes).
|
||
# Compare uint64 pointer values (is-comparison triggers a type-safety check that dereferences a dangling Ty). find_struct_by_type's semantics = "first entry with equal Ty pointer" (first registration wins), byte-identical to the original scan.
|
||
existing: GenTypes.StructEntry* = self.find_struct_by_type(struct_ty)
|
||
if existing is not None: return existing
|
||
if self.StructCount >= STRUCT_MAX:
|
||
VLogger.error(f"table full, cannot register {name}", "STRUCT")
|
||
return None
|
||
entry: GenTypes.StructEntry* = self._get_struct_entry(self.StructCount)
|
||
if entry is None: return None
|
||
# Allocate the field array (in 1b use the perm pool so Fields does not dangle after reset).
|
||
field_size: sizet = GenTypes.FieldEntry.__sizeof__()
|
||
if self.StdlibAlloc != 0:
|
||
perm_sg: memhub.MemBuddy* = CompilerFactory.factory.PermPool
|
||
if perm_sg is None: perm_sg = pool
|
||
entry.Fields = perm_sg.alloc(field_size * FIELD_MAX)
|
||
else: entry.Fields = pool.alloc(field_size * FIELD_MAX)
|
||
if entry.Fields is None: return None
|
||
string.memset(entry.Fields, 0, field_size * FIELD_MAX)
|
||
# Copy the class name (1b StdlibAlloc=1 uses the perm pool to survive reset; 1a uses the main pool, which also survives before mark1). Not copying would make find_struct_by_module's strcmp read a dangling Name.
|
||
name_pool: memhub.MemBuddy* = pool
|
||
if self.StdlibAlloc != 0:
|
||
name_pool = CompilerFactory.factory.PermPool
|
||
if name_pool is None: name_pool = pool
|
||
name_len: sizet = string.strlen(name)
|
||
name_buf: str = name_pool.alloc(name_len + 1)
|
||
if name_buf is not None:
|
||
string.strcpy(name_buf, name)
|
||
entry.Name = name_buf
|
||
entry.Ty = struct_ty
|
||
entry.FieldCount = 0
|
||
# Set SHA1 directly at registration (avoids set_struct_sha1 picking the wrong entry for cross-module same-named classes); SHA1 is also allocated per StdlibAlloc to survive 1a/1b and prevent a dangling strcmp.
|
||
if sha1 is not None:
|
||
sha1_len: sizet = string.strlen(sha1)
|
||
sha1_buf: str = name_pool.alloc(sha1_len + 1)
|
||
if sha1_buf is not None:
|
||
string.strcpy(sha1_buf, sha1)
|
||
entry.ModuleSha1 = sha1_buf
|
||
|
||
self.StructCount += 1
|
||
# Sync the name | type index (new entry index = StructCount-1).
|
||
self._struct_idx_add(self.StructCount - 1, entry)
|
||
return entry
|
||
|
||
# ensure_forward_class - forward-declare a class by "class name + defining-module SHA1".
|
||
# Aligns TPC semantics: an unregistered class becomes a named empty CStruct placeholder filled in later, so a pointer to an unregistered class returns Ptr(placeholder.Ty) instead of an anonymous Ptr(i8) that loses class identity; the definition side fills the same Ty in place. Idempotent (returns an existing (class_name, sha1) entry); returns None on invalid args or a full table.
|
||
def ensure_forward_class(self, pool: memhub.MemBuddy*, sha1: str, class_name: str) -> GenTypes.StructEntry*:
|
||
if pool is None or sha1 is None or class_name is None: return None
|
||
if sha1[0] == '\0' or class_name[0] == '\0': return None
|
||
existing: GenTypes.StructEntry* = self.find_struct_by_module(class_name, sha1)
|
||
if existing is not None: return existing
|
||
if self.init_struct_table(pool) == 0: return None
|
||
if self.StructCount >= STRUCT_MAX:
|
||
VLogger.error(f"table full, cannot forward-declare {class_name}", "STRUCT")
|
||
return None
|
||
# Named opaque struct "%{sha}.{Class}" (fields=None, fcount=0): the name matches ClassGenDef's "sha1.ClassName" verbatim so the definition side can locate and fill it in place.
|
||
# Name and Ty must use a persistent pool per StdlibAlloc (same as register_struct): the placeholder survives this file's reset, so Ty/Name in a transient pool would dangle.
|
||
fwd_pool: memhub.MemBuddy* = pool
|
||
if self.StdlibAlloc != 0:
|
||
fwd_pool = CompilerFactory.factory.PermPool
|
||
if fwd_pool is None: fwd_pool = pool
|
||
sh_len: sizet = string.strlen(sha1)
|
||
cn_len: sizet = string.strlen(class_name)
|
||
name_buf: str = fwd_pool.alloc(sh_len + 1 + cn_len + 1)
|
||
if name_buf is None: return None
|
||
string.strcpy(name_buf, sha1)
|
||
name_buf[sh_len] = '.'
|
||
string.strcpy(name_buf + sh_len + 1, class_name)
|
||
fwd_ty: mlirlite.LLVMType* = mlirlite.Struct(fwd_pool, None, 0, name_buf)
|
||
if fwd_ty is None: return None
|
||
entry: GenTypes.StructEntry* = self.register_struct(pool, class_name, fwd_ty, sha1)
|
||
if entry is None: return None
|
||
entry.IsForwardDecl = 1
|
||
self.add_visible_struct(pool, class_name)
|
||
return entry
|
||
|
||
def register_struct_alias(self, pool: memhub.MemBuddy*, name: str, sha1: str, src_entry: GenTypes.StructEntry*) -> GenTypes.StructEntry*:
|
||
"""Append a package re-export alias entry for an already-registered class."""
|
||
if name is None or sha1 is None or src_entry is None: return None
|
||
if self.init_struct_table(pool) == 0: return None
|
||
# Idempotent: skip if (name, sha1) is already registered (find_struct_by_module does not fall back to the bare name).
|
||
if sha1[0] != '\0':
|
||
existing: GenTypes.StructEntry* = self.find_struct_by_module(name, sha1)
|
||
if existing is not None: return existing
|
||
if self.StructCount >= STRUCT_MAX: return None
|
||
entry: GenTypes.StructEntry* = self._get_struct_entry(self.StructCount)
|
||
if entry is None: return None
|
||
# Copy the class name and SHA1 (alias registration also picks the allocator per StdlibAlloc to survive 1a/1b reset).
|
||
alias_pool: memhub.MemBuddy* = pool
|
||
if self.StdlibAlloc != 0:
|
||
alias_pool = CompilerFactory.factory.PermPool
|
||
if alias_pool is None: alias_pool = pool
|
||
name_len: sizet = string.strlen(name)
|
||
name_buf: str = alias_pool.alloc(name_len + 1)
|
||
if name_buf is None: return None
|
||
string.strcpy(name_buf, name)
|
||
entry.Name = name_buf
|
||
sha1_len: sizet = string.strlen(sha1)
|
||
sha1_buf: str = alias_pool.alloc(sha1_len + 1)
|
||
if sha1_buf is None: return None
|
||
string.strcpy(sha1_buf, sha1)
|
||
entry.ModuleSha1 = sha1_buf
|
||
# Reuse the source entry's type, fields and class attributes (same class, only the owning module differs).
|
||
entry.Ty = src_entry.Ty
|
||
entry.FieldCount = src_entry.FieldCount
|
||
entry.Fields = src_entry.Fields
|
||
entry.IsUnion = src_entry.IsUnion
|
||
entry.IsPacked = src_entry.IsPacked
|
||
entry.IsOOP = src_entry.IsOOP
|
||
entry.HasInit = src_entry.HasInit
|
||
entry.HasNew = src_entry.HasNew
|
||
entry.HasVTable = src_entry.HasVTable
|
||
entry.IsNoVTable = src_entry.IsNoVTable
|
||
entry.ParentName = src_entry.ParentName
|
||
entry.ParentNames = src_entry.ParentNames
|
||
entry.ParentCount = src_entry.ParentCount
|
||
entry.VTableMethodCount = src_entry.VTableMethodCount
|
||
entry.VTableMethods = src_entry.VTableMethods
|
||
entry.MethodsEmitted = 0
|
||
entry.SpecTriggerSha1 = None
|
||
self.StructCount += 1
|
||
# Sync the name | type index (the alias entry has the same name/Ty as the source, so the index keeps the first registration index).
|
||
self._struct_idx_add(self.StructCount - 1, entry)
|
||
return entry
|
||
|
||
def set_struct_sha1(self, pool: memhub.MemBuddy*, class_name: str, sha1: str) -> int:
|
||
"""Set the owning module SHA1 of a struct; returns 1 on success, 0 on failure."""
|
||
if class_name is None or sha1 is None: return 0
|
||
entry: GenTypes.StructEntry* = self.find_struct(class_name)
|
||
if entry is None: return 0
|
||
# Copy the SHA1 string (StdlibAlloc=1 uses the perm pool to survive 1a/1b and prevent a dangling strcmp).
|
||
sha1_len: sizet = string.strlen(sha1)
|
||
sha1_pool: memhub.MemBuddy* = pool
|
||
if self.StdlibAlloc != 0:
|
||
sha1_pool = CompilerFactory.factory.PermPool
|
||
if sha1_pool is None: sha1_pool = pool
|
||
sha1_buf: str = sha1_pool.alloc(sha1_len + 1)
|
||
if sha1_buf is None: return 0
|
||
string.strcpy(sha1_buf, sha1)
|
||
entry.ModuleSha1 = sha1_buf
|
||
return 1
|
||
|
||
def get_struct_sha1(self, class_name: str) -> str:
|
||
"""Return the owning module SHA1 of a struct, or None when unset."""
|
||
if class_name is None or self.StructTable is None: return None
|
||
entry: GenTypes.StructEntry* = self.find_struct(class_name)
|
||
if entry is None: return None
|
||
return entry.ModuleSha1
|
||
|
||
def add_field(self, pool: memhub.MemBuddy*, struct_entry: GenTypes.StructEntry*, field_name: str, field_ty: mlirlite.LLVMType*, default_val: ast.AST* = None, annot_class_name: str = None) -> int:
|
||
"""Add a field to a struct and return its index, or -1 on failure."""
|
||
if struct_entry is None or field_name is None or field_ty is None: return -1
|
||
if struct_entry.FieldCount >= FIELD_MAX:
|
||
VLogger.error(f"field table full for {struct_entry.Name}", "STRUCT")
|
||
return -1
|
||
idx: int = struct_entry.FieldCount
|
||
fe: GenTypes.FieldEntry* = self._get_field_entry(struct_entry, idx)
|
||
if fe is None: return -1
|
||
# Copy the field name (StdlibAlloc=1 uses the perm pool to survive reset); otherwise lookup_field's strcmp(fe.Name, ...) reads dangling out-of-bounds memory.
|
||
fname_pool: memhub.MemBuddy* = pool
|
||
if self.StdlibAlloc != 0:
|
||
fname_pool = CompilerFactory.factory.PermPool
|
||
if fname_pool is None: fname_pool = pool
|
||
name_len: sizet = string.strlen(field_name)
|
||
# Tagged call site: if the writer is the field-name buffer near these 32B, the report names this site directly.
|
||
name_buf: str = fname_pool.alloc_tagged(name_len + 1, memhub.DETSITE_STRUCTGENMIXIN_570)
|
||
if name_buf is not None:
|
||
string.strcpy(name_buf, field_name)
|
||
fe.Name = name_buf
|
||
fe.Index = idx
|
||
# In 1b (StdlibAlloc=1) the field type must be deep-copied to stdlib: if the field-level Ty still holds a pool pointer, cross-file reads after this file's reset_to(mark1) (field flattening, MI this-adjust, _apply_struct_defaults' build_gep_struct using fe.Ty) dereference dangling memory.
|
||
# Note: annotation class names are merged into Spec.SourceName and Spec is always PermPool-allocated, so it naturally survives reset.
|
||
if self.StdlibAlloc != 0:
|
||
dp_pool_sg: memhub.MemBuddy* = CompilerFactory.factory.PermPool
|
||
if dp_pool_sg is None: dp_pool_sg = pool
|
||
ty_copy: mlirlite.LLVMType* = mlirlite.DeepCopyType(dp_pool_sg, field_ty)
|
||
if ty_copy is not None: fe.Ty = ty_copy
|
||
else: fe.Ty = field_ty
|
||
# DefaultVal: cross-file struct construction (_translate_struct_ctor -> _apply_struct_defaults) reads field defaults while translating the caller file, after the original pool AST was reset, so constants are deep-copied to perm_pool (1a also requires DefaultVal to survive per-file reset).
|
||
fe.DefaultVal = self._deep_copy_default_ast(dp_pool_sg, default_val)
|
||
else:
|
||
fe.Ty = field_ty
|
||
fe.DefaultVal = default_val
|
||
# Instances are always PermPool-allocated (resident or a separate copy), so they naturally survive a 1b reset.
|
||
arena_f: TSpec.TypeSpecArena* = self.Gen.TypeGenBase.ensure_spec_arena()
|
||
if arena_f is not None:
|
||
fe.Spec = TSpec.SpecFromLLVMType(arena_f, field_ty)
|
||
# [S4 annotation-side Spec] Fill the field Spec's signedness and annotation source name (annot_class_name carries the real signedness and the annotation spelling; LLVM inference always yields TS_INT with no name).
|
||
# When type info is missing (Spec is None) build TS_UNKNOWN to carry the annotation name without asserting a concrete type.
|
||
if annot_class_name is not None:
|
||
if fe.Spec is None: fe.Spec = TSpec.NewTypeSpec(arena_f, TSpec.TS_UNKNOWN, 0, TSpec.TS_SIGN_UNKNOWN)
|
||
if fe.Spec is not None:
|
||
sign_f: int = self.Gen.TypeGenCType.spec_sign_of_annot(annot_class_name)
|
||
if sign_f != TSpec.TS_SIGN_UNKNOWN: fe.Spec = TSpec.SpecWithSign(arena_f, fe.Spec, sign_f)
|
||
fe.Spec = TSpec.SpecWithSource(fe.Spec, annot_class_name)
|
||
|
||
# Explicitly zero the const flag (not relying on the entry's zero-init): ClassGenDef sets it to 1 only for a confirmed const object, so explicit zeroing prevents a false "uninitialized read as 1".
|
||
fe.IsConst = 0
|
||
struct_entry.FieldCount = idx + 1
|
||
return idx
|
||
|
||
def _deep_copy_default_ast(self, pool: memhub.MemBuddy*, default_val: ast.AST*) -> ast.AST*:
|
||
"""Deep-copy a field default AST (constants and lists) into the perm pool."""
|
||
if default_val is None: return None
|
||
if default_val.kind() == ast.ASTKind.List: return self._deep_copy_list_ast(pool, default_val)
|
||
if default_val.kind() != ast.ASTKind.Constant: return None
|
||
src_c: ast.Constant* = ast.Constant*(default_val)
|
||
dst_c: ast.Constant* = ast.Constant*(pool.alloc_tagged(ast.Constant.__sizeof__(), memhub.DETSITE_STRUCTGENMIXIN_641))
|
||
if dst_c is None: return None
|
||
string.memset(dst_c, 0, ast.Constant.__sizeof__())
|
||
# Copy the vtable pointer (AST is @t.CVTable; kind() virtual calls depend on the first-field vptr); must match _deep_copy_list_ast (c.Deref read + c.DerefAs write).
|
||
# The old form silently failed under TPC: no load-vptr instruction and no store emitted, leaving vptr=0, so a later node.kind() reads `load i8*, i8** [0 + 0]` -> ASan READ at 0x0. Evidence: TransPyV\output\b5883dc9c114a604.ll:10382-10384.
|
||
vt_ptr_c: t.CPtr = t.CPtr(*((uint64*)(default_val)))
|
||
c.DerefAs(dst_c, vt_ptr_c)
|
||
# Copy position fields (for error location).
|
||
dst_c.lineno = src_c.lineno
|
||
dst_c.col_offset = src_c.col_offset
|
||
dst_c.end_lineno = src_c.end_lineno
|
||
dst_c.end_col_offset = src_c.end_col_offset
|
||
# Copy the constant value (int/float/str/bool distinguished by const_kind).
|
||
dst_c.const_kind = src_c.const_kind
|
||
dst_c.int_val = src_c.int_val
|
||
dst_c.float_val = src_c.float_val
|
||
if src_c.str_val is not None:
|
||
s_len: sizet = string.strlen(src_c.str_val)
|
||
s_copy: str = pool.alloc_tagged(s_len + 1, memhub.DETSITE_STRUCTGENMIXIN_659)
|
||
if s_copy is not None:
|
||
string.strcpy(s_copy, src_c.str_val)
|
||
dst_c.str_val = s_copy
|
||
return (ast.AST*)(dst_c)
|
||
|
||
def _deep_copy_list_ast(self, pool: memhub.MemBuddy*, default_val: ast.AST*) -> ast.AST*:
|
||
"""Deep-copy a list-literal default AST node into the perm pool."""
|
||
src_l: ast.List* = ast.List*(default_val)
|
||
dst_l: ast.List* = ast.List*(pool.alloc(ast.List.__sizeof__()))
|
||
if dst_l is None: return None
|
||
string.memset(dst_l, 0, ast.List.__sizeof__())
|
||
# Copy the vtable pointer (AST is @t.CVTable; kind() virtual calls depend on the first-field vptr).
|
||
vt_l: t.CPtr = t.CPtr(*((uint64*)(default_val)))
|
||
c.DerefAs(dst_l, vt_l)
|
||
# Copy position fields and ctx.
|
||
dst_l.lineno = src_l.lineno
|
||
dst_l.col_offset = src_l.col_offset
|
||
dst_l.end_lineno = src_l.end_lineno
|
||
dst_l.end_col_offset = src_l.end_col_offset
|
||
dst_l.ctx = src_l.ctx
|
||
# Deep-copy elts: manually build a list[AST*] container in stdlib.
|
||
src_elts_l: list[ast.AST*]* = src_l.elts
|
||
if src_elts_l is not None:
|
||
cnt_l: sizet = len(src_elts_l)
|
||
lst_copy_l: list[ast.AST*]* = (list[ast.AST*] | t.CPtr)(pool.alloc_tagged(list[ast.AST*].__sizeof__(), memhub.DETSITE_STRUCTGENMIXIN_693))
|
||
if lst_copy_l is not None:
|
||
string.memset(lst_copy_l, 0, list[ast.AST*].__sizeof__())
|
||
# The count must never be fixed from the source count up front; it must be settled by the actual written slot count (written_l below).
|
||
# Otherwise a null / non-copyable source element silently skipped would leave __count__ > actual fill and zero slots, so _init_global_array_from_list's elts.get(i) reads a bare 0 and crashes on an empty AST element (ASan access-violation 0x0). Only capacity is set here.
|
||
lst_copy_l.__capacity__ = cnt_l
|
||
# Single source of truth for element size: allocation (cnt_l * elem_sz_l), zeroing (same expression) and writing (slot base stepped by elem_sz_l) must agree; two separate literal 8s would let "allocate n*8 but write at another width" reappear.
|
||
elem_sz_l: sizet = 8
|
||
lst_copy_l.__elem_size__ = elem_sz_l
|
||
lst_copy_l.__pool__ = None
|
||
data_l: t.CPtr = pool.alloc_tagged(cnt_l * elem_sz_l, memhub.DETSITE_STRUCTGENMIXIN_698)
|
||
if data_l is not None:
|
||
string.memset(data_l, 0, cnt_l * elem_sz_l)
|
||
lst_copy_l.__data__ = data_l
|
||
written_l: sizet = 0
|
||
li_l: sizet
|
||
for li_l in range(cnt_l):
|
||
elem_src_l: ast.AST* = src_elts_l.get(li_l)
|
||
# Explicit null check (repo hard rule: null-check pointers with `is None`). An earlier revision compared `elem_src_l == 0` and noted it was equivalent to `is None`: the IR fact was right but the conclusion misleading.
|
||
# A null element in the source list means the producer is corrupted (count > actual fill) -> fail loudly, never backfill 0.
|
||
if elem_src_l is None:
|
||
self.Gen.TypeGenErr.fatal_error(default_val, f"字段默认值深拷贝:列表字面量第 {li_l} 个元素为空 AST 元素(指针为 0)。" f"上层某处产生了空 AST 元素(元素计数与实际填充不一致)," f"拒绝静默补 0 生成残缺的默认值;已终止编译。", "ValueError")
|
||
return None
|
||
elem_dst_l: ast.AST* = self._deep_copy_default_ast(pool, elem_src_l)
|
||
if elem_dst_l is None:
|
||
self.Gen.TypeGenErr.fatal_error(elem_src_l, f"字段默认值深拷贝:列表字面量第 {li_l} 个元素(kind={elem_src_l.kind()})" f"既非 Constant 也非 List,无法深拷贝到永久池。若在此静默跳过该槽," f"会形成【计数 > 实际填充数】的空槽(后续 elts.get 读到裸 0 崩溃);" f"若静默截短计数则会改变数组初始化长度。两者都是静默降级,故响亮失败。", "TypeError")
|
||
return None
|
||
# The slot element type must be a pointer: `ast.AST*`'s `[0]` is the object `ast.AST` (48 bytes), so `slot[0] = <pointer>` would silently compile as a 48-byte load + 48-byte store and overrun each cnt_l*elem_sz_l slot by 40 bytes, writing over this block's canary and the next free block's first word (root cause of three generations of [FREEFRAME] crashes).
|
||
# Per the repo merge rule (hashtable.vp:82-85) `X*` = Ptr(resolve(X)), so indexing yields a pointer element -> an 8-byte pointer store matching the allocation width.
|
||
slot_l: ast.AST** = (ast.AST**)(uint64(data_l) + li_l * elem_sz_l)
|
||
slot_l[0] = elem_dst_l
|
||
written_l += 1
|
||
# count == actual fill (== cnt_l on success; any mismatch already terminated above).
|
||
lst_copy_l.__count__ = written_l
|
||
dst_l.elts = lst_copy_l
|
||
return (ast.AST*)(dst_l)
|
||
|
||
def get_struct_count(self) -> int:
|
||
"""Return the number of registered structs."""
|
||
return self.StructCount
|
||
|
||
def _resolve_module_sha1(self, mod_name: str, from_imports: str = None) -> str:
|
||
"""Resolve a module name to its SHA1 by exact name, import alias, then suffix."""
|
||
if mod_name is None: return None
|
||
s_rm: str = StubMergerStore.LookupSha1ByModName(mod_name)
|
||
if s_rm is None and from_imports is not None:
|
||
real_rm: str = StubMergerStore.LookupModNameByAlias(from_imports, mod_name)
|
||
if real_rm is not None:
|
||
s_rm = StubMergerStore.LookupSha1ByModName(real_rm)
|
||
if s_rm is None: s_rm = StubMergerStore.LookupSha1ByModNameSuffix(real_rm)
|
||
if s_rm is None: s_rm = StubMergerStore.LookupSha1ByModNameSuffix(mod_name)
|
||
return s_rm
|
||
|
||
def find_struct_entry_with_ty(self, name: str, alt_name: str) -> GenTypes.StructEntry*:
|
||
"""Find the first struct entry with a valid Ty by name or alt name, else None."""
|
||
if self.StructTable is None: return None
|
||
for i in range(self.StructCount):
|
||
entry: GenTypes.StructEntry* = self._get_struct_entry(i)
|
||
if entry is None or entry.Name is None or entry.Ty is None: continue
|
||
if entry.Name[0] == '\0': continue
|
||
if string.strcmp(entry.Name, name) == 0: return entry
|
||
if alt_name is not None and string.strcmp(entry.Name, alt_name) == 0: return entry
|
||
return None
|
||
|
||
def find_struct(self, name: str) -> GenTypes.StructEntry*:
|
||
"""Find a struct by class name, supporting module-qualified names; else None."""
|
||
if name is None or self.StructTable is None: return None
|
||
# Pass 1: exact match.
|
||
# StructCount sanity guard (memory corruption can yield a huge value -> index loop false hang).
|
||
if self.StructCount < 0 or self.StructCount > 200000:
|
||
VLogger.error(f"StructCount 异常: {self.StructCount} (find_struct)", "STRUCT")
|
||
stdlib.exit(1)
|
||
# Name-index fast path: a hit gives the first registration index (semantics = the original exact-match scan's first hit).
|
||
fi: int = self._struct_name_idx_find(name)
|
||
if fi >= 0: return self._get_struct_entry(fi)
|
||
# When the name index is trusted (Built==StructCount, index present, non-empty query), an index miss means "definitely no same-named entry", since entries are named at registration and synced into the index and names are at most neutralized to "".
|
||
# So the O(n) exact scan can be skipped (hotspot: it accounts for all of find_struct's strcmp). When the index is untrusted / absent / the query name is empty, still fall back to the linear scan; if exact matching truly finds nothing, continue to the module-qualified / bare-name fallback.
|
||
name_idx_trusted: int = 0
|
||
if self.StructNameIdx is not None and self.StructIdxCap > 0 and self.StructIdxBuilt == self.StructCount: name_idx_trusted = 1
|
||
if name_idx_trusted == 0 or name[0] == '\0':
|
||
for i in range(self.StructCount):
|
||
entry: GenTypes.StructEntry* = self._get_struct_entry(i)
|
||
if entry is not None and entry.Name is not None:
|
||
if string.strcmp(entry.Name, name) == 0: return entry
|
||
# Module-qualified name (module.Class): when the module resolves, match by module ownership exactly, to stop the bare-name fallback from grabbing a cross-module same-named class; if the module misses but the bare name is unique, fall back.
|
||
# Take the dot outside generic brackets (numpy.ndarray[t.CDouble] -> numpy + ndarray[t.CDouble]), else strrchr lands inside the subscript and truncates the module name (same root cause as lookup_field_by_class).
|
||
dot_pos_q: str = self._last_dot_outside_brackets(name)
|
||
if dot_pos_q is not None and dot_pos_q[1] != '\0':
|
||
bare_f: str = dot_pos_q + 1
|
||
mod_len_f: sizet = string.strlen(name) - string.strlen(bare_f) - 1
|
||
if mod_len_f > 0:
|
||
mod_buf_f: str = stdlib.malloc(mod_len_f + 1)
|
||
if mod_buf_f is not None:
|
||
string.strncpy(mod_buf_f, name, mod_len_f)
|
||
mod_buf_f[mod_len_f] = '\0'
|
||
if string.strcmp(mod_buf_f, "t") != 0 and string.strcmp(mod_buf_f, "c") != 0:
|
||
# A 16-hex module segment is a SHA1 literal (e.g. "{9aab}.Scene3D", TypeGenResolve L1397-1402's sha1-prefix form): skip the store module-name lookup (store keys are module names, not sha1) and match name+sha1 exactly.
|
||
# Otherwise _resolve_module_sha1("9aab") returns None and a bare-name fallback returns the first-registered wrong cross-module Scene3D entry (VKernel gfx / App gfx both define Scene3D -> wrong ownership -> ret_sha1=0ae -> Phase A _deps_pending deadlock). [C10 convergence] The module segment is exactly 16 lowercase hex = SHA1 literal: go through GenApi.is_sha1_hex16 (single source of truth).
|
||
mod_hex_ok: int = GenApi.is_sha1_hex16(mod_buf_f)
|
||
if mod_hex_ok != 0:
|
||
me_h: GenTypes.StructEntry* = self.find_struct_by_module(bare_f, mod_buf_f)
|
||
stdlib.free(mod_buf_f)
|
||
return me_h
|
||
mod_sha1_f: str = self._resolve_module_sha1(mod_buf_f)
|
||
if mod_sha1_f is not None:
|
||
me_f: GenTypes.StructEntry* = self.find_struct_by_module(bare_f, mod_sha1_f)
|
||
if me_f is not None:
|
||
stdlib.free(mod_buf_f)
|
||
return me_f
|
||
# Module class not registered: count same bare-named classes, fall back when unique (no conflict).
|
||
same_cnt_f: int = 0
|
||
same_entry_f: GenTypes.StructEntry* = None
|
||
for i2 in range(self.StructCount):
|
||
e2: GenTypes.StructEntry* = self._get_struct_entry(i2)
|
||
if e2 is not None and e2.Name is not None:
|
||
if string.strcmp(e2.Name, bare_f) == 0:
|
||
same_cnt_f += 1
|
||
same_entry_f = e2
|
||
stdlib.free(mod_buf_f)
|
||
if same_cnt_f == 1: return same_entry_f
|
||
return None
|
||
stdlib.free(mod_buf_f)
|
||
# Pass 2: class-name fallback (handles "HT.Translator" -> "Translator", reached only when the module name cannot be resolved; the dot is also taken outside generic subscripts).
|
||
dot_pos: str = self._last_dot_outside_brackets(name)
|
||
if dot_pos is not None:
|
||
class_name: str = dot_pos + 1
|
||
if class_name is not None and class_name[0] != '\0':
|
||
# Same-name uniqueness check (aligns with the qualified path above): when a cross-module same-named class exists (e.g. VKernel gfx and App gfx both define Scene3D), do not return the first-registered wrong entry.
|
||
# Otherwise an unresolvable module segment is hijacked to the wrong module and the return type resolves against the wrong text.ll.
|
||
same_cnt_b: int = 0
|
||
same_entry_b: GenTypes.StructEntry* = None
|
||
for i in range(self.StructCount):
|
||
entry: GenTypes.StructEntry* = self._get_struct_entry(i)
|
||
if entry is not None and entry.Name is not None:
|
||
if string.strcmp(entry.Name, class_name) == 0:
|
||
same_cnt_b += 1
|
||
same_entry_b = entry
|
||
if same_cnt_b == 1: return same_entry_b
|
||
return None
|
||
|
||
def find_struct_by_module(self, name: str, sha1: str) -> GenTypes.StructEntry*:
|
||
"""Find a struct by class name plus SHA1; falls back to find_struct when sha1 is None."""
|
||
if name is None or self.StructTable is None: return None
|
||
# StructCount sanity guard (memory corruption can yield a huge value -> index loop false hang).
|
||
if self.StructCount < 0 or self.StructCount > 200000:
|
||
VLogger.error(f"StructCount 异常: {self.StructCount} (find_struct_by_module)", "STRUCT")
|
||
stdlib.exit(1)
|
||
if sha1 is not None:
|
||
# Name-index fast path: same-named entries all lie at or after the first same-name index, so scan name+sha1 from that index (equivalent to the original full scan's "first name with sha1 hit").
|
||
# A miss or untrusted index -> start=0 and use the original full scan (for correctness).
|
||
start: int = 0
|
||
fri: int = self._struct_name_idx_find(name)
|
||
if fri >= 0: start = fri
|
||
for i in range(start, self.StructCount):
|
||
entry: GenTypes.StructEntry* = self._get_struct_entry(i)
|
||
if entry is not None and entry.Name is not None:
|
||
# Fast path: strcmp equality implies equal first chars, so skip strcmp for differing first chars.
|
||
if entry.Name[0] == name[0] and string.strcmp(entry.Name, name) == 0:
|
||
if entry.ModuleSha1 is not None:
|
||
# Same: sha1 equality implies equal first chars; compare first chars before strcmp.
|
||
if entry.ModuleSha1[0] == sha1[0] and string.strcmp(entry.ModuleSha1, sha1) == 0: return entry
|
||
# SHA1 lookup failed: do not fall back to find_struct (it would return a cross-module same-named wrong entry); return None so the caller creates a new type, avoiding Phase B short-circuiting with a wrong type.
|
||
return None
|
||
# No SHA1: find the first by class name.
|
||
return self.find_struct(name)
|
||
|
||
def reset_module_methods_emitted(self, sha1: str) -> int:
|
||
"""Clear MethodsEmitted for classes registered by the given module sha1."""
|
||
if sha1 is None or self.StructTable is None: return 0
|
||
if self.StructCount < 0 or self.StructCount > 200000:
|
||
VLogger.error(f"StructCount 异常: {self.StructCount} (reset_module_methods_emitted)", "STRUCT")
|
||
return 0
|
||
reset_count: int = 0
|
||
for i in range(self.StructCount):
|
||
entry: GenTypes.StructEntry* = self._get_struct_entry(i)
|
||
if entry is not None:
|
||
# Normal classes match by ModuleSha1.
|
||
if entry.ModuleSha1 is not None:
|
||
if string.strcmp(entry.ModuleSha1, sha1) == 0:
|
||
entry.MethodsEmitted = 0
|
||
reset_count += 1
|
||
continue
|
||
# Generic-specialized classes match by the triggering file's sha1: their ModuleSha1 is the template sha1 (method names carry the template prefix) and cannot match the current obsolete file; without resetting, the specialized body keeps MethodsEmitted=1 and is not retranslated, leaving text.ll with only calls and no define.
|
||
if entry.SpecTriggerSha1 is not None:
|
||
if string.strcmp(entry.SpecTriggerSha1, sha1) == 0:
|
||
entry.MethodsEmitted = 0
|
||
reset_count += 1
|
||
return reset_count
|
||
|
||
def find_struct_by_type(self, struct_ty: mlirlite.LLVMType*) -> GenTypes.StructEntry*:
|
||
"""Find a struct by type pointer value comparison, or None."""
|
||
if struct_ty is None or self.StructTable is None: return None
|
||
# StructCount sanity guard (memory corruption can yield a huge value -> index loop false hang).
|
||
if self.StructCount < 0 or self.StructCount > 200000:
|
||
VLogger.error(f"StructCount 异常: {self.StructCount} (find_struct_by_type)", "STRUCT")
|
||
stdlib.exit(1)
|
||
st_val: uint64 = uint64(struct_ty)
|
||
# The Ty index is entirely disabled here (its structure is still maintained by _struct_idx_add but not used): an entry's Ty may be rewritten to a new pointer by deep copy (ClassGenDef), leaving the slot keyed by the registration-time Ty value out of sync.
|
||
# So an index hit may not be the original scan's "first entry with equal Ty"; restore the original linear exact scan (hit/miss behavior byte-identical to before the index).
|
||
for i in range(self.StructCount):
|
||
entry: GenTypes.StructEntry* = self._get_struct_entry(i)
|
||
if entry is not None and entry.Ty is not None:
|
||
if uint64(entry.Ty) == st_val: return entry
|
||
return None
|
||
|
||
def struct_type_is_packed(self, struct_ty: mlirlite.LLVMType*) -> int:
|
||
"""Return 1 when the named struct is packed, else 0."""
|
||
if struct_ty is None: return 0
|
||
sname: str = mlirlite.get_struct_name(struct_ty)
|
||
if sname is None: return 0
|
||
entry: GenTypes.StructEntry* = self.find_struct_by_type(struct_ty)
|
||
if entry is None: entry = self.find_struct(sname)
|
||
if entry is None: return 0
|
||
return entry.IsPacked
|
||
|
||
# verify_field_slot_consistency - field-table vs struct-slot consistency guard (fail-fast).
|
||
# A field table and the struct layout are produced by two paths from the same field list; a mismatch makes GEPs computed from FieldEntry.Index read a neighbouring field (e.g. ast.Name.id landing on the ctx slot, reading pointer 1 as an integer and crashing strlen). Only clearly different classes (pointer-ness / integer width / float width) are compared; unions and empty field tables (vpi signature-only classes) are exempt.
|
||
def verify_field_slot_consistency(self, struct_ty: mlirlite.LLVMType*, entry: GenTypes.StructEntry*) -> int:
|
||
"""Assert the field count matches the struct slots and each field type matches its slot."""
|
||
if struct_ty is None or entry is None: return 0
|
||
if entry.FieldCount <= 0: return 0
|
||
# Union exemption: a union's field table has N entries (one per member) but the LLVM layout has only 1 slot (all members at offset 0, sized to the max member), so they are never equal.
|
||
# Field access goes through a bitcast GEP and does not depend on the slot index, so this guard does not apply (false-positive evidence: EdgeCaseTest's `class Data(t.CUnion)`, 2 members -> 1 slot).
|
||
if entry.IsUnion != 0 or self.is_union_by_type(struct_ty) != 0: return 0
|
||
fcount: int = mlirlite.get_struct_fcount(struct_ty)
|
||
if fcount <= 0: return 0
|
||
if entry.FieldCount != fcount:
|
||
self.Gen.TypeGenErr.fatal_error(None, f"结构体 '{entry.Name}' 的字段表条数({entry.FieldCount})与 LLVM 槽位数({fcount})不一致:" f"字段条目与结构体布局由两条路径产生,错位会让字段访问按错误下标算 GEP" f"(实证:ast.Name.id 落到 ctx 槽 ⇒ 指针被读成整数 1 ⇒ strlen(0x1) 崩溃)。", "TypeError")
|
||
return 0
|
||
node: rawptr[mlirlite.ParamNode] = mlirlite.get_struct_fields(struct_ty)
|
||
i: int = 0
|
||
while i < entry.FieldCount and node is not None:
|
||
fe: GenTypes.FieldEntry* = self._get_field_entry(entry, i)
|
||
slot_ty: mlirlite.LLVMType* = mlirlite.paramnode_get_ty(node)
|
||
if fe is not None and fe.Name is not None and fe.Ty is not None and slot_ty is not None:
|
||
ptr_f: int = self.Gen.ExprGenBase.is_ptr_type(fe.Ty)
|
||
ptr_s: int = self.Gen.ExprGenBase.is_ptr_type(slot_ty)
|
||
bits_f: int = self.Gen.ExprGenBase.get_llvm_type_bits(fe.Ty)
|
||
bits_s: int = self.Gen.ExprGenBase.get_llvm_type_bits(slot_ty)
|
||
fbits_f: int = self.Gen.ExprGenBase.get_llvm_float_bits(fe.Ty)
|
||
fbits_s: int = self.Gen.ExprGenBase.get_llvm_float_bits(slot_ty)
|
||
if ptr_f != ptr_s or bits_f != bits_s or fbits_f != fbits_s:
|
||
self.Gen.TypeGenErr.fatal_error(None, f"结构体 '{entry.Name}' 的字段 '{fe.Name}'(条目下标 {i})与其槽位类型不同类:" f"条目 指针性={ptr_f}/整数位宽={bits_f}/浮点位宽={fbits_f}," f"槽位 指针性={ptr_s}/整数位宽={bits_s}/浮点位宽={fbits_s}。" f"字段条目与结构体布局错位(实证:id 落到 ctx 槽 ⇒ 指针被读成整数 1 ⇒ strlen(0x1) 崩溃)。", "TypeError")
|
||
return 0
|
||
node = mlirlite.paramnode_get_next(node)
|
||
i += 1
|
||
return 0
|
||
|
||
def lookup_field(self, struct_ty: mlirlite.LLVMType*, field_name: str) -> GenTypes.FieldEntry*:
|
||
"""Look up a field by struct type and field name using identity then name fallback."""
|
||
if struct_ty is None or field_name is None or self.StructTable is None: return None
|
||
|
||
# Two-pass lookup strategy (root fix for cross-module same-named class conflicts):
|
||
# Pass 1 compares type pointers by identity (highest priority; the type uniquely determines the entry, so a missing field returns None, it avoids the union == bug, and it is independent of iteration order). Pass 2 is a name fallback (used for unregistered types or unreliable identity); if a name matches but the field is missing, continue to check other same-named entries.
|
||
|
||
# Pass 1: identity comparison by uint64 pointer value (avoids the is-comparison path dereferencing a dangling Ty). find_struct_by_type's semantics = "first entry with equal Ty pointer" (first registration wins), consistent with the original identity scan.
|
||
# The type uniquely determines the entry: once hit, look up the field; if missing return None directly.
|
||
lk_entry: GenTypes.StructEntry* = self.find_struct_by_type(struct_ty)
|
||
if lk_entry is not None:
|
||
# Field-table vs struct-slot consistency guard (fail-fast): reject a mismatch (see verify_field_slot_consistency header; real crash: ast.Name.id -> slot 9).
|
||
self.verify_field_slot_consistency(struct_ty, lk_entry)
|
||
for fi in range(lk_entry.FieldCount):
|
||
fe: GenTypes.FieldEntry* = self._get_field_entry(lk_entry, fi)
|
||
if fe is not None and fe.Name is not None:
|
||
if string.strcmp(fe.Name, field_name) == 0: return fe
|
||
return None
|
||
|
||
# Pass 2: name fallback (identity matched nothing).
|
||
ty_name: str = self._extract_struct_name(struct_ty)
|
||
if ty_name is not None:
|
||
# Strengthen SHA1 namespace isolation: for a type name like "3d4afd9cdffeba6d.Thread", compare its 16-hex prefix exactly with the first 16 chars of entry.ModuleSha1 to forbid a strstr bare-name cross-module grab (sched.Thread overridden by includes/vthreading.vp's Thread -> field misalignment -> _thread_entry call 0).
|
||
# strncmp allocates no temporary buffer; the prefix test goes through the single source GenApi.sha1_prefix_len.
|
||
ty_has_sha1_lf: int = 0
|
||
if GenApi.sha1_prefix_len(ty_name) != 0: ty_has_sha1_lf = 1
|
||
for i in range(self.StructCount):
|
||
entry: GenTypes.StructEntry* = self._get_struct_entry(i)
|
||
if entry is not None and entry.Name is not None:
|
||
# Type name with a SHA1 prefix must match the first 16 chars of entry.ModuleSha1 exactly, else skip (cross-module same-named class namespace isolation).
|
||
if ty_has_sha1_lf != 0:
|
||
if entry.ModuleSha1 is None or string.strlen(entry.ModuleSha1) < 16 or string.strncmp(entry.ModuleSha1, ty_name, 16) != 0: continue
|
||
matched: int = 0
|
||
if string.strcmp(entry.Name, ty_name) == 0: matched = 1
|
||
elif string.strstr(ty_name, entry.Name) is not None: matched = 1
|
||
if matched != 0:
|
||
# Look up the field.
|
||
for fi in range(entry.FieldCount):
|
||
fe: GenTypes.FieldEntry* = self._get_field_entry(entry, fi)
|
||
if fe is not None and fe.Name is not None:
|
||
if string.strcmp(fe.Name, field_name) == 0: return fe
|
||
# Name matched but the field was not found; keep checking other same-named entries (cross-module same name).
|
||
continue
|
||
# No entry matched.
|
||
return None
|
||
|
||
def _last_dot_outside_brackets(self, s: str) -> str:
|
||
"""Return a pointer to the last '.' outside [...] brackets, or None."""
|
||
if s is None: return None
|
||
n: sizet = string.strlen(s)
|
||
depth: int = 0
|
||
i: sizet = n
|
||
while i > 0:
|
||
i -= 1
|
||
ch: char = s[i]
|
||
if ch == ']': depth += 1
|
||
elif ch == '[' and depth > 0: depth -= 1
|
||
elif ch == '.' and depth == 0: return s + i
|
||
return None
|
||
|
||
def lookup_field_by_class(self, class_name: str, field_name: str, sha1: str = None, node: ast.AST* = None, from_imports: str = None) -> GenTypes.FieldEntry*:
|
||
"""Look up a field by class name and field name, preferring an exact SHA1 match."""
|
||
if class_name is None or field_name is None: return None
|
||
# Prefer SHA1 matching (distinguishes cross-module same-named classes).
|
||
# Supports module-qualified names: class_name may be "HT.Translator" while entry.Name is "Translator"; try exact match first, then fall back to the strrchr-extracted class name.
|
||
if sha1 is not None:
|
||
sha1_matched: int = 0
|
||
# Compute the class-name fallback ("HT.Translator" -> "Translator"; "numpy.ndarray[t.CDouble]" -> "ndarray[t.CDouble]").
|
||
bare_name: str = class_name
|
||
dot_pos_lfbc: str = self._last_dot_outside_brackets(class_name)
|
||
if dot_pos_lfbc is not None:
|
||
bare_name = dot_pos_lfbc + 1
|
||
if bare_name is None or bare_name[0] == '\0': bare_name = class_name
|
||
for i in range(self.StructCount):
|
||
entry: GenTypes.StructEntry* = self._get_struct_entry(i)
|
||
if entry is not None and entry.Name is not None:
|
||
# Exact match or class-name fallback match.
|
||
name_match: int = 0
|
||
if string.strcmp(entry.Name, class_name) == 0: name_match = 1
|
||
elif bare_name is not class_name and string.strcmp(entry.Name, bare_name) == 0: name_match = 1
|
||
if name_match != 0:
|
||
if entry.ModuleSha1 is not None and string.strcmp(entry.ModuleSha1, sha1) == 0:
|
||
sha1_matched = 1
|
||
# SHA1 + class name matched; look up the field.
|
||
for fi in range(entry.FieldCount):
|
||
fe: GenTypes.FieldEntry* = self._get_field_entry(entry, fi)
|
||
if fe is not None and fe.Name is not None and string.strcmp(fe.Name, field_name) == 0: return fe
|
||
return None
|
||
|
||
# Module-qualified name (module.Class, module not t/c built-in): resolve by module ownership and forbid a bare-name fallback grabbing a cross-module same-named class (root cause: VKernel sched.Thread overridden by includes/vthreading.vp's Thread -> missing 'tid').
|
||
# Resolve the module name to a sha1 via the StubMergerStore mapping, then match name+sha1 exactly (vpi stub injection / definition-side bare-name entries). For generic names the dot must go through _last_dot_outside_brackets, or the module name truncates.
|
||
dot_pos_q: str = self._last_dot_outside_brackets(class_name)
|
||
if dot_pos_q is not None and dot_pos_q[1] != '\0':
|
||
bare_q: str = dot_pos_q + 1
|
||
mod_len_q: sizet = string.strlen(class_name) - string.strlen(bare_q) - 1
|
||
if mod_len_q > 0:
|
||
mod_buf_q: str = stdlib.malloc(mod_len_q + 1)
|
||
if mod_buf_q is not None:
|
||
string.strncpy(mod_buf_q, class_name, mod_len_q)
|
||
mod_buf_q[mod_len_q] = '\0'
|
||
if string.strcmp(mod_buf_q, "t") != 0 and string.strcmp(mod_buf_q, "c") != 0:
|
||
# A 16-hex module segment ("{16hex}.Class", the normalized generic type-arg form) is the module SHA1; a registered class proves that module compiled, so match name+sha1 exactly without _resolve_module_sha1 (store keys are module names, so querying the sha1 is always None and would fail-fast wrongly).
|
||
# [C10 convergence] The module segment is exactly 16 lowercase hex = SHA1 literal: go through GenApi.is_sha1_hex16; generic subscript dots are already skipped by _last_dot_outside_brackets, so only top-level a.b forms remain here.
|
||
mod_hex_ok_q: int = GenApi.is_sha1_hex16(mod_buf_q)
|
||
mod_sha1_q: str = None
|
||
if mod_hex_ok_q != 0:
|
||
# A SHA1 prefix is itself evidence of registration (same semantics as existing resolution): mod_sha1_q aliases mod_buf_q (the return paths below free it once).
|
||
mod_sha1_q = mod_buf_q
|
||
else: mod_sha1_q = self._resolve_module_sha1(mod_buf_q, from_imports)
|
||
if mod_sha1_q is not None:
|
||
mod_entry_q: GenTypes.StructEntry* = self.find_struct_by_module(bare_q, mod_sha1_q)
|
||
if mod_entry_q is not None:
|
||
# Dependencies-not-ready tolerance (aligns the bare-name path): an empty class (FieldCount==0, incomplete registration / source module not compiled) -> return None so the caller marks _deps_pending and retranslates next pass; not fatal here.
|
||
if mod_entry_q.FieldCount == 0:
|
||
stdlib.free(mod_buf_q)
|
||
return None
|
||
for fi_q in range(mod_entry_q.FieldCount):
|
||
fe_q: GenTypes.FieldEntry* = self._get_field_entry(mod_entry_q, fi_q)
|
||
if fe_q is not None and fe_q.Name is not None:
|
||
if string.strcmp(fe_q.Name, field_name) == 0:
|
||
stdlib.free(mod_buf_q)
|
||
return fe_q
|
||
# REnum variant field alias fallback (same as the bare-name path): when a module-qualified access resolves the class but not the field, look up the variant alias directly (REnum fields are _p1/_p2 slots).
|
||
# Previously this fallback existed only on the bare-name path, so the module-qualified path fataled first and never reached it.
|
||
rn_cls_q: str = mod_entry_q.Name
|
||
if rn_cls_q is None: rn_cls_q = bare_q
|
||
rn_is_renum_q: int = self.Gen.ClassGenEnum._is_renum_class_name(bare_q)
|
||
if rn_is_renum_q == 0 and rn_cls_q is not None: rn_is_renum_q = self.Gen.ClassGenEnum._is_renum_class_name(rn_cls_q)
|
||
rn_alias_q: int = -1
|
||
if rn_is_renum_q != 0:
|
||
rn_alias_q = self.Gen.ClassGenEnum._lookup_renum_field_alias(rn_cls_q, field_name)
|
||
if rn_alias_q < 1 and bare_q is not rn_cls_q: rn_alias_q = self.Gen.ClassGenEnum._lookup_renum_field_alias(bare_q, field_name)
|
||
if rn_alias_q >= 1:
|
||
rn_fe_q: GenTypes.FieldEntry* = self._get_field_entry(mod_entry_q, rn_alias_q)
|
||
if rn_fe_q is not None and rn_fe_q.Ty is not None:
|
||
stdlib.free(mod_buf_q)
|
||
return rn_fe_q
|
||
# Class hit but field not found: readable error (traceback format).
|
||
self.Gen.TypeGenErr.fatal_error(node, f"lookup_field_by_class: 类 '{class_name}' 已按模块归属解析(ModuleSha1={mod_sha1_q}, FieldCount={mod_entry_q.FieldCount})但字段 '{field_name}' 未找到", "AttributeError")
|
||
stdlib.free(mod_buf_q)
|
||
return None
|
||
# Module resolvable but class not registered: fail-fast (exposes vpi injection/registration defects; no bare-name cross-library grab of another module's same-named class).
|
||
self.Gen.TypeGenErr.fatal_error(node, f"lookup_field_by_class: 类 '{class_name}' 未找到(模块 '{mod_buf_q}' 可解析 sha1={mod_sha1_q},但无同名注册类;请检查 vpi stub 注入或编译顺序)", "AttributeError")
|
||
stdlib.free(mod_buf_q)
|
||
return None
|
||
# Module name not registered: fail-fast (no more bare-name cross-library fallback).
|
||
self.Gen.TypeGenErr.fatal_error(node, f"lookup_field_by_class: 类 '{class_name}' 的模块 '{mod_buf_q}' 无法解析(sha1 映射表未登记,请检查 import 与编译顺序)", "AttributeError")
|
||
stdlib.free(mod_buf_q)
|
||
return None
|
||
stdlib.free(mod_buf_q)
|
||
|
||
# Fallback: no SHA1 or SHA1 match failed; find the first by class name.
|
||
entry = self.find_struct(class_name)
|
||
if entry is None:
|
||
# Diagnostic: target class not registered (Python traceback style; terminate compilation immediately).
|
||
self.Gen.TypeGenErr.fatal_error(node, f"lookup_field_by_class: 类 '{class_name}' 未注册 (struct_count={self.StructCount})", "AttributeError")
|
||
return None
|
||
# Dependencies-not-ready tolerance: the caller expects a specific module (sha1 non-None) but the bare-name fallback hit an empty FieldCount==0 stub class (vpi injection without fields / defining module not compiled).
|
||
# Return None so the caller marks _deps_pending and retranslates (the real definition overrides the stub once the source module compiles). SECURITY_ATTRIBUTES FieldCount=0 root cause (subprocess.vp sa.nLength).
|
||
if entry.FieldCount == 0 and sha1 is not None: return None
|
||
# Look up the field first (return before diagnostics/error, else the error exits immediately and the lookup always fails).
|
||
for fi in range(entry.FieldCount):
|
||
fe: GenTypes.FieldEntry* = self._get_field_entry(entry, fi)
|
||
if fe is not None and fe.Name is not None:
|
||
if string.strcmp(fe.Name, field_name) == 0: return fe
|
||
# REnum variant field alias fallback: REnum classes (e.g. LLVMType) have variant inner fields (Ptr.Pointee etc.) that are _p1/_p2 slot aliases, not normal fields.
|
||
# Leaving it to the caller would never run because this function's fatal on a missing field calls sys.exit(1) first. Note: _lookup_renum_field_alias compares class_name by pointer value, so it must receive entry.Name (the pool pointer unified by register_struct + _update_renum_alias_class_ptr).
|
||
rn_cls_l: str = entry.Name
|
||
if rn_cls_l is None: rn_cls_l = class_name
|
||
rn_is_renum_l: int = self.Gen.ClassGenEnum._is_renum_class_name(class_name)
|
||
if rn_is_renum_l == 0 and rn_cls_l is not None:
|
||
# The class name may be module-qualified ("mod.LLVMType") while the REnum table stores bare names; double-check with the bare name too.
|
||
rn_is_renum_l = self.Gen.ClassGenEnum._is_renum_class_name(rn_cls_l)
|
||
rn_alias_l: int = -1
|
||
if rn_is_renum_l != 0:
|
||
rn_alias_l = self.Gen.ClassGenEnum._lookup_renum_field_alias(rn_cls_l, field_name)
|
||
if rn_alias_l < 1 and class_name is not rn_cls_l:
|
||
# Double-check: the alias table may still hold an old AST pointer (before _update_renum_alias_class_ptr / multi-module re-registration), so retry with the caller's class_name.
|
||
rn_alias_l = self.Gen.ClassGenEnum._lookup_renum_field_alias(class_name, field_name)
|
||
if rn_alias_l >= 1:
|
||
rn_fe_l: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(entry, rn_alias_l)
|
||
if rn_fe_l is not None and rn_fe_l.Ty is not None: return rn_fe_l
|
||
# Diagnostic: the struct was found but the field truly was not; concatenate the registered field list (comma-separated on one line) for a one-glance report.
|
||
fb_flds_lst: str = stdlib.malloc(1024)
|
||
if fb_flds_lst is not None:
|
||
fb_flds_lst[0] = '\0'
|
||
for fi in range(entry.FieldCount):
|
||
fe: GenTypes.FieldEntry* = self._get_field_entry(entry, fi)
|
||
if fe is not None and fe.Name is not None:
|
||
if string.strlen(fb_flds_lst) > 0 and string.strlen(fb_flds_lst) + 3 < 1024: string.strcat(fb_flds_lst, ", ")
|
||
if string.strlen(fb_flds_lst) + string.strlen(fe.Name) < 1024: string.strcat(fb_flds_lst, fe.Name)
|
||
err: str
|
||
if rn_is_renum_l != 0:
|
||
# Friendly error: for a REnum class state that variant field alias lookup failed, including entry.Name and the alias slot result, so it is clear whether the alias is unregistered (slot=-1) or the class_name pointer is not unified.
|
||
err = f"lookup_field_by_class: 类 '{class_name}' 是 REnum 类(FieldCount={entry.FieldCount}),变体字段 '{field_name}' 别名查找失败(entry.Name='{rn_cls_l}',别名槽位={rn_alias_l},请检查该变体字段是否已注册别名,或 class_name 指针是否统一)"
|
||
else:
|
||
# Readability: include entry.Name/ModuleSha1 (cross-module same-name location) + the registered field list + the expected sha1 (caller), to distinguish "cross-module same-named class mismatch" from "incomplete vpi stub injection" from "class definition and access site disagree".
|
||
sha1_txt: str = "?"
|
||
if entry.ModuleSha1 is not None: sha1_txt = entry.ModuleSha1
|
||
want_sha1_txt: str = "?"
|
||
if sha1 is not None: want_sha1_txt = sha1
|
||
fld_lst_txt: str = "(无)"
|
||
if fb_flds_lst is not None and string.strlen(fb_flds_lst) > 0: fld_lst_txt = fb_flds_lst
|
||
err = f"lookup_field_by_class: 类 '{class_name}' 已注册 (FieldCount={entry.FieldCount}, " f"entry.Name='{entry.Name}', ModuleSha1={sha1_txt}, 调用方期望 sha1={want_sha1_txt}) " f"但字段 '{field_name}' 未找到;注册字段: [{fld_lst_txt}]。可能原因: 跨模块同名类" f"被误匹配 / vpi stub 注入字段不全 / 类定义与访问处不一致"
|
||
# Output in Python traceback style (traceback frame + file:line + error type) and terminate compilation immediately (same semantics as VLogger.error, format aligned with fatal_error).
|
||
self.Gen.TypeGenErr.fatal_error(node, err, "AttributeError")
|
||
if fb_flds_lst is not None: stdlib.free(fb_flds_lst)
|
||
# Diagnostic: struct found but field not found.
|
||
return None
|
||
|
||
def get_struct_type(self, class_name: str) -> mlirlite.LLVMType*:
|
||
"""Return the LLVM type of a struct by class name."""
|
||
entry: GenTypes.StructEntry* = self.find_struct(class_name)
|
||
if entry is not None: return entry.Ty
|
||
return None
|
||
|
||
def get_field_by_index(self, class_name: str, idx: int) -> GenTypes.FieldEntry*:
|
||
"""Return a field entry by class name and field index."""
|
||
if class_name is None: return None
|
||
entry: GenTypes.StructEntry* = self.find_struct(class_name)
|
||
if entry is None: return None
|
||
if idx < 0 or idx >= entry.FieldCount: return None
|
||
return self._get_field_entry(entry, idx)
|
||
|
||
def struct_has_defaults(self, class_name: str) -> int:
|
||
"""Return 1 when the struct has any field with a default value."""
|
||
if class_name is None: return 0
|
||
entry: GenTypes.StructEntry* = self.find_struct(class_name)
|
||
if entry is None: return 0
|
||
for fi in range(entry.FieldCount):
|
||
fe: GenTypes.FieldEntry* = self._get_field_entry(entry, fi)
|
||
if fe is not None and fe.DefaultVal is not None: return 1
|
||
return 0
|
||
|
||
def get_struct_type_from_value(self, val: mlirlite.Value*) -> mlirlite.LLVMType*:
|
||
"""Infer the struct type from a value's type, unwrapping pointers recursively."""
|
||
if val is None or val.Ty is None: return None
|
||
ty: mlirlite.LLVMType* = val.Ty
|
||
while ty is not None:
|
||
# Same-class compilation bug (see TypeGenResolve L1463 comment): updating the while-condition variable (ty = pointee) directly in a case body makes the bootstrapped match write the assignment to the subject snapshot slot while the while condition reads the old slot -> infinite loop.
|
||
# Fix: bind the case to a local variable and update the loop variable outside the match.
|
||
nxt_ty: mlirlite.LLVMType* = None
|
||
match ty:
|
||
case mlirlite.LLVMType.Ptr(pointee):
|
||
nxt_ty = pointee
|
||
case mlirlite.LLVMType.Struct(fields, fcount, name):
|
||
return ty
|
||
case _:
|
||
return None
|
||
ty = nxt_ty
|
||
return None
|
||
|
||
def mark_as_union(self, name: str) -> int:
|
||
"""Mark a registered struct as a union; returns 1 on success, 0 on failure."""
|
||
if name is None: return 0
|
||
entry: GenTypes.StructEntry* = self.find_struct(name)
|
||
if entry is None: return 0
|
||
entry.IsUnion = 1
|
||
return 1
|
||
|
||
def is_union_by_name(self, name: str) -> int:
|
||
"""Return 1 when the named struct is a union, else 0."""
|
||
if name is None: return 0
|
||
entry: GenTypes.StructEntry* = self.find_struct(name)
|
||
if entry is None: return 0
|
||
return entry.IsUnion
|
||
|
||
def is_union_by_type(self, struct_ty: mlirlite.LLVMType*) -> int:
|
||
"""Return 1 when the type-pointer struct is a union, falling back to name matching."""
|
||
if struct_ty is None or self.StructTable is None: return 0
|
||
ty_name: str = self._extract_struct_name(struct_ty)
|
||
st_iu: uint64 = uint64(struct_ty)
|
||
for i in range(self.StructCount):
|
||
entry: GenTypes.StructEntry* = self._get_struct_entry(i)
|
||
if entry is not None and entry.Ty is not None:
|
||
matched: int = 0
|
||
if uint64(entry.Ty) == st_iu: matched = 1
|
||
elif ty_name is not None and entry.Name is not None:
|
||
if string.strcmp(entry.Name, ty_name) == 0: matched = 1
|
||
elif string.strstr(ty_name, entry.Name) is not None: matched = 1
|
||
if matched != 0: return entry.IsUnion
|
||
return 0
|
||
|
||
def mark_as_oop(self, name: str) -> int:
|
||
"""Mark a registered struct as OOP; returns 1 on success, 0 on failure."""
|
||
if name is None: return 0
|
||
entry: GenTypes.StructEntry* = self.find_struct(name)
|
||
if entry is None: return 0
|
||
entry.IsOOP = 1
|
||
return 1
|
||
|
||
def is_oop_by_name(self, name: str) -> int:
|
||
"""Return 1 when the named struct is OOP, else 0."""
|
||
if name is None: return 0
|
||
entry: GenTypes.StructEntry* = self.find_struct(name)
|
||
if entry is None: return 0
|
||
return entry.IsOOP
|
||
|
||
def mark_has_init(self, name: str) -> int:
|
||
"""Mark a struct as having an __init__ method; returns 1 on success, 0 on failure."""
|
||
if name is None: return 0
|
||
entry: GenTypes.StructEntry* = self.find_struct(name)
|
||
if entry is None: return 0
|
||
entry.HasInit = 1
|
||
return 1
|
||
|
||
def has_init_by_name(self, name: str) -> int:
|
||
"""Return 1 when the named struct has an __init__ method, else 0."""
|
||
if name is None: return 0
|
||
entry: GenTypes.StructEntry* = self.find_struct(name)
|
||
if entry is None: return 0
|
||
return entry.HasInit
|
||
|
||
def mark_has_new(self, name: str) -> int:
|
||
"""Mark a struct as having a __new__ method; returns 1 on success, 0 on failure."""
|
||
if name is None: return 0
|
||
entry: GenTypes.StructEntry* = self.find_struct(name)
|
||
if entry is None: return 0
|
||
entry.HasNew = 1
|
||
return 1
|
||
|
||
def has_new_by_name(self, name: str) -> int:
|
||
"""Return 1 when the named struct has a __new__ method, else 0."""
|
||
if name is None: return 0
|
||
entry: GenTypes.StructEntry* = self.find_struct(name)
|
||
if entry is None: return 0
|
||
return entry.HasNew
|
||
|
||
def get_class_name_by_type(self, pool: memhub.MemBuddy*, struct_ty: mlirlite.LLVMType*) -> str:
|
||
"""Return the class name for a type pointer, or None."""
|
||
if struct_ty is None or self.StructTable is None: return None
|
||
# find_struct_by_type's semantics = "first entry with equal Ty pointer" (first registration wins); return its Name. The Ty index is disabled and the linear scan restored, so it is byte-identical.
|
||
fast_cn: GenTypes.StructEntry* = self.find_struct_by_type(struct_ty)
|
||
if fast_cn is not None: return fast_cn.Name
|
||
# Fallback: when the type pointer comparison fails, iterate all registered structs matching by name; extract struct_ty's Name field (may be "SHA1.ClassName").
|
||
ty_name: str = self._extract_struct_name(struct_ty)
|
||
if ty_name is not None:
|
||
for j in range(self.StructCount):
|
||
entry2: GenTypes.StructEntry* = self._get_struct_entry(j)
|
||
if entry2 is not None and entry2.Name is not None:
|
||
# Build the "SHA1.ClassName" form for comparison.
|
||
entry2_sha1: str = entry2.ModuleSha1
|
||
if entry2_sha1 is not None:
|
||
full_nm: str = pool.alloc(128)
|
||
if full_nm is not None:
|
||
viperlib.snprintf(full_nm, 128, "%s.%s", entry2_sha1, entry2.Name)
|
||
if string.strcmp(full_nm, ty_name) == 0: return entry2.Name
|
||
# Also compare the class name directly (struct_ty's Name may lack a SHA1 prefix).
|
||
if string.strcmp(entry2.Name, ty_name) == 0: return entry2.Name
|
||
# Final fallback: suffix match ("SHA1.ClassName" tail) -- safer than a strstr substring match, which would wrongly hit the base name "GSList" inside the specialization "SHA1.GSList[Param]".
|
||
# That mixup mis-resolves chained method call class names (e.g. GSList.append lookup failing, then a sibling mixin falling back to LinkedNode.append, shifting this by sizeof(LinkedNode) and writing over the container's first 48 bytes -- the LLvmLiteTest Test 3 crash root cause).
|
||
if entry2.Name is not None and string.strlen(entry2.Name) > 0:
|
||
_tlen_cn: sizet = string.strlen(ty_name)
|
||
_nlen_cn: sizet = string.strlen(entry2.Name)
|
||
if _tlen_cn > _nlen_cn and ty_name[_tlen_cn - _nlen_cn - 1] == '.':
|
||
_tail_ok_cn: int = 1
|
||
_ti_cn: sizet
|
||
for _ti_cn in range(_nlen_cn):
|
||
if ty_name[_tlen_cn - _nlen_cn + _ti_cn] != entry2.Name[_ti_cn]:
|
||
_tail_ok_cn = 0
|
||
break
|
||
if _tail_ok_cn == 1: return entry2.Name
|
||
return None
|
||
|
||
def _extract_struct_name(self, ty: mlirlite.LLVMType*) -> str:
|
||
"""Extract the Name of a Struct LLVMType variant, or None for non-Struct types."""
|
||
if ty is None: return None
|
||
match ty:
|
||
case mlirlite.LLVMType.Struct(fields, fcount, name):
|
||
return name
|
||
case _:
|
||
return None
|
||
|
||
def mark_has_vtable(self, name: str) -> int:
|
||
"""Mark a struct as having a vtable; returns 1 on success, 0 on failure."""
|
||
if name is None: return 0
|
||
entry: GenTypes.StructEntry* = self.find_struct(name)
|
||
if entry is None: return 0
|
||
entry.HasVTable = 1
|
||
return 1
|
||
|
||
def has_vtable_by_name(self, name: str) -> int:
|
||
"""Return 1 when the named struct has a vtable, else 0."""
|
||
if name is None: return 0
|
||
entry: GenTypes.StructEntry* = self.find_struct(name)
|
||
if entry is None: return 0
|
||
return entry.HasVTable
|
||
|
||
def mark_novtable(self, name: str) -> int:
|
||
"""Mark a struct as NoVTable; returns 1 on success, 0 on failure."""
|
||
if name is None: return 0
|
||
entry: GenTypes.StructEntry* = self.find_struct(name)
|
||
if entry is None: return 0
|
||
entry.IsNoVTable = 1
|
||
return 1
|
||
|
||
def is_novtable_by_name(self, name: str) -> int:
|
||
"""Return 1 when the named struct is NoVTable, else 0."""
|
||
if name is None: return 0
|
||
entry: GenTypes.StructEntry* = self.find_struct(name)
|
||
if entry is None: return 0
|
||
return entry.IsNoVTable
|
||
|
||
def set_parent_name(self, name: str, parent_name: str) -> int:
|
||
"""Set a struct's single parent class name; returns 1 on success, 0 on failure."""
|
||
if name is None: return 0
|
||
entry: GenTypes.StructEntry* = self.find_struct(name)
|
||
if entry is None: return 0
|
||
entry.ParentName = parent_name
|
||
return 1
|
||
|
||
def set_parent_names(self, pool: memhub.MemBuddy*, name: str, names_ptr: sizet*, count: int) -> int:
|
||
"""Set a struct's parent-name array, deep-copied into the perm pool."""
|
||
if name is None: return 0
|
||
entry: GenTypes.StructEntry* = self.find_struct(name)
|
||
if entry is None: return 0
|
||
if names_ptr is None or count <= 0:
|
||
entry.ParentNames = None
|
||
entry.ParentCount = 0
|
||
return 1
|
||
if count > PARENT_MAX: count = PARENT_MAX
|
||
pn_pool: memhub.MemBuddy* = pool
|
||
if self.StdlibAlloc != 0:
|
||
pn_pool = CompilerFactory.factory.PermPool
|
||
if pn_pool is None: pn_pool = pool
|
||
arr: sizet* = pn_pool.alloc(8 * count)
|
||
if arr is None: return 0
|
||
for i in range(count):
|
||
addr: sizet = names_ptr[i]
|
||
arr[i] = 0
|
||
if addr != 0:
|
||
src: str = rawptr(addr)
|
||
if src is not None:
|
||
slen: sizet = string.strlen(src)
|
||
dup: str = pn_pool.alloc(slen + 1)
|
||
if dup is not None:
|
||
string.strcpy(dup, src)
|
||
arr[i] = sizet(dup)
|
||
entry.ParentNames = arr
|
||
entry.ParentCount = count
|
||
return 1
|
||
|
||
def get_parent_count(self, name: str) -> int:
|
||
"""Return the parent count (0 none, 1 single inheritance, >1 multiple)."""
|
||
if name is None: return 0
|
||
entry: GenTypes.StructEntry* = self.find_struct(name)
|
||
if entry is None: return 0
|
||
return entry.ParentCount
|
||
|
||
def get_parent_name_at(self, name: str, idx: int) -> str:
|
||
"""Return the idx-th parent class name, or None when out of range."""
|
||
if name is None or idx < 0: return None
|
||
entry: GenTypes.StructEntry* = self.find_struct(name)
|
||
if entry is None or entry.ParentNames is None: return None
|
||
if idx >= entry.ParentCount: return None
|
||
parr: sizet* = (sizet*)(entry.ParentNames)
|
||
addr: sizet = parr[idx]
|
||
if addr == 0: return None
|
||
return rawptr(addr)
|
||
|
||
def get_mro_chain(self, pool: memhub.MemBuddy*, name: str) -> sizet*:
|
||
"""Return the MRO-linearized class-name array (depth-first, declaration order, deduped)."""
|
||
if pool is None or name is None: return None
|
||
arr: sizet* = pool.alloc(8 * (MRO_MAX + 1))
|
||
if arr is None: return None
|
||
stack: sizet* = pool.alloc(8 * MRO_MAX)
|
||
if stack is None: return None
|
||
visited: sizet* = pool.alloc(8 * MRO_MAX)
|
||
if visited is None: return None
|
||
stack_count: int = 0
|
||
out_count: int = 0
|
||
visit_count: int = 0
|
||
# Push self initially.
|
||
stack[0] = sizet(name)
|
||
stack_count = 1
|
||
while stack_count > 0:
|
||
stack_count -= 1
|
||
cur_addr: sizet = stack[stack_count]
|
||
cur: str = rawptr(cur_addr)
|
||
if cur is None: continue
|
||
# Visited check (linear search).
|
||
dup: int = 0
|
||
for vi in range(visit_count):
|
||
va: sizet = visited[vi]
|
||
if va != 0:
|
||
vn: str = rawptr(va)
|
||
if vn is not None and string.strcmp(vn, cur) == 0:
|
||
dup = 1
|
||
break
|
||
if dup != 0: continue
|
||
# Add to the result and visited sets.
|
||
if out_count < MRO_MAX:
|
||
arr[out_count] = sizet(cur)
|
||
out_count += 1
|
||
if visit_count < MRO_MAX:
|
||
visited[visit_count] = sizet(cur)
|
||
visit_count += 1
|
||
# Push parents in reverse (compute the reverse index with a forward range so they pop in declaration order).
|
||
entry: GenTypes.StructEntry* = self.find_struct(cur)
|
||
if entry is not None and entry.ParentCount > 0 and entry.ParentNames is not None:
|
||
pc: int = entry.ParentCount
|
||
parr: sizet* = (sizet*)(entry.ParentNames)
|
||
for pi2 in range(pc):
|
||
pi: int = pc - 1 - pi2
|
||
if stack_count < MRO_MAX:
|
||
stack[stack_count] = parr[pi]
|
||
stack_count += 1
|
||
# Terminate with a trailing 0.
|
||
arr[out_count] = 0
|
||
return arr
|
||
|
||
def get_parent_name(self, name: str) -> str:
|
||
"""Return a struct's single parent class name, or None."""
|
||
if name is None: return None
|
||
entry: GenTypes.StructEntry* = self.find_struct(name)
|
||
if entry is None: return None
|
||
return entry.ParentName
|
||
|
||
def set_vtable_methods(self, name: str, methods: str, count: int) -> int:
|
||
"""Set the vtable method-name array; returns 1 on success, 0 on failure."""
|
||
if name is None: return 0
|
||
entry: GenTypes.StructEntry* = self.find_struct(name)
|
||
if entry is None: return 0
|
||
entry.VTableMethods = methods
|
||
entry.VTableMethodCount = count
|
||
return 1
|
||
|
||
def get_vtable_method_index(self, name: str, method_name: str) -> int:
|
||
"""Return the method's vtable index, or -1 when not found."""
|
||
if name is None or method_name is None: return -1
|
||
entry: GenTypes.StructEntry* = self.find_struct(name)
|
||
if entry is None or entry.VTableMethods is None: return -1
|
||
# VTableMethods is declared char* but actually stores a sizet array; it must be cast to sizet* to read the 8-byte pointer values correctly.
|
||
methods_arr: sizet* = (sizet*)(entry.VTableMethods)
|
||
for i in range(entry.VTableMethodCount):
|
||
mname_addr: sizet = methods_arr[i]
|
||
if mname_addr == 0: continue
|
||
mname: str = rawptr(mname_addr)
|
||
if mname is not None and string.strcmp(mname, method_name) == 0: return i
|
||
return -1
|
||
|
||
def get_vtable_method_count(self, name: str) -> int:
|
||
"""Return the vtable method count."""
|
||
if name is None: return 0
|
||
entry: GenTypes.StructEntry* = self.find_struct(name)
|
||
if entry is None: return 0
|
||
return entry.VTableMethodCount
|
||
|
||
def get_vtable_method_name(self, name: str, idx: int) -> str:
|
||
"""Return the idx-th vtable method name, or None when out of range."""
|
||
if name is None or idx < 0: return None
|
||
entry: GenTypes.StructEntry* = self.find_struct(name)
|
||
if entry is None or entry.VTableMethods is None: return None
|
||
if idx >= entry.VTableMethodCount: return None
|
||
methods_arr: sizet* = (sizet*)(entry.VTableMethods)
|
||
mname_addr: sizet = methods_arr[idx]
|
||
if mname_addr == 0: return None
|
||
return rawptr(mname_addr)
|
||
|
||
def get_field_count(self, name: str) -> int:
|
||
"""Return a struct's field count, or -1 when not found."""
|
||
if name is None: return -1
|
||
entry: GenTypes.StructEntry* = self.find_struct(name)
|
||
if entry is None: return -1
|
||
return entry.FieldCount
|
||
|
||
def ensure_struct_def_in_module(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, ty: mlirlite.LLVMType*):
|
||
"""Ensure a cross-module struct definition is available in the current module."""
|
||
if ty is None or mod is None or pool is None: return
|
||
# Pointer type: ensure the pointee has at least an opaque declaration.
|
||
match ty:
|
||
case mlirlite.LLVMType.Ptr(pointee):
|
||
mlirlite.module_ensure_opaque_for_type(mod, pool, ty)
|
||
# If the pointee is a Struct, recursively copy the full definition (GEP field access needs a sized type; opaque only makes llc error).
|
||
if pointee is not None: self.ensure_struct_def_in_module(pool, mod, pointee)
|
||
return
|
||
case _:
|
||
pass
|
||
# Get the struct name (None for non-Struct types).
|
||
sname: str = mlirlite.get_struct_name(ty)
|
||
if sname is None: return
|
||
# Check whether this is a cross-module reference (name contains '.').
|
||
if string.strchr(sname, 46) is None:
|
||
return # not a cross-module reference; nothing to do
|
||
# Extract the class name (the part after the first '.').
|
||
# Must use strchr not strrchr: a generic specialization like "list[ast.AST*]" contains a '.' inside (ast.AST), and strrchr would wrongly parse the class name as "AST*]".
|
||
dot_pos: str = string.strchr(sname, '.')
|
||
if dot_pos is None: return
|
||
class_name: str = dot_pos + 1
|
||
if class_name is None or class_name[0] == '\0': return
|
||
# Extract the SHA1 (between sname and dot_pos, using strlen to avoid pointer subtraction).
|
||
total_len: sizet = string.strlen(sname)
|
||
class_name_len: sizet = string.strlen(class_name)
|
||
sha1_len: sizet = total_len - class_name_len - 1
|
||
sha1_buf: str = pool.alloc_tagged(32, memhub.DETSITE_STRUCTGENMIXIN_1804)
|
||
if sha1_buf is None: return
|
||
# 32-byte buffer: strncpy does not truncate, so a prefix over 31 chars would overrun, plus the manual NUL would overrun again.
|
||
sha1_cp: sizet = sha1_len
|
||
if sha1_cp > 31: sha1_cp = 31
|
||
string.strncpy(sha1_buf, sname, sha1_cp)
|
||
sha1_buf[sha1_cp] = '\0'
|
||
# Look up the struct definition by class name + SHA1.
|
||
entry: GenTypes.StructEntry* = self.find_struct_by_module(class_name, sha1_buf)
|
||
if entry is None:
|
||
# Fall back to lookup by class name (may find a same-named class in another module, but better than nothing).
|
||
entry = self.find_struct(class_name)
|
||
if entry is None: return
|
||
if entry.Ty is None: return
|
||
# Add the full definition to the current module (replaces an existing opaque declaration).
|
||
mlirlite.module_add_named_type(mod, pool, entry.Ty)
|
||
# B5: a cross-module injected packed class must have its packed flag synced (<{...}>), else the referencing module GEPs with a non-packed layout and misaligns with the defining side's packed layout (gdt/elf/fat32 binary formats misread; root cause of idt_entry/gdt_entry degrading to {…} in merged IR).
|
||
if entry.IsPacked != 0: mlirlite.module_set_named_type_packed(mod, pool, entry.Ty, 1)
|
||
# Recursively scan field types to ensure value-type sub-structs (e.g. FILETIME inside WIN32_FIND_DATAA) also have full definitions; pointer-type fields only need an opaque declaration (handled by module_ensure_opaque_for_type).
|
||
self._ensure_field_types_in_module(pool, mod, entry.Ty)
|
||
|
||
def _ensure_field_types_in_module(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, ty: mlirlite.LLVMType*):
|
||
"""Recursively scan a type tree to ensure value-type sub-structs are fully defined."""
|
||
if ty is None or mod is None or pool is None: return
|
||
# Nested match to walk the type tree.
|
||
match ty:
|
||
case mlirlite.LLVMType.Struct(fields, fcount, sname):
|
||
# Walk the field list.
|
||
if fields is not None:
|
||
fld: rawptr[mlirlite.ParamNode] = fields
|
||
cnt_ef: int = 0
|
||
while fld is not None:
|
||
# Step guard: a cyclic field list (memory corruption) would loop forever (G3 LinkedNode.prepend hang candidate); over the limit, report and abort immediately.
|
||
cnt_ef += 1
|
||
if cnt_ef > 100000:
|
||
VLogger.error("字段链表疑似成环(_ensure_field_types_in_module),强制终止", "STRUCT")
|
||
stdlib.exit(1)
|
||
if fld.Ty is not None:
|
||
# Value-type sub-structs (e.g. FILETIME) need a full definition.
|
||
match fld.Ty:
|
||
case mlirlite.LLVMType.Struct(f2, fc2, n2):
|
||
self.ensure_struct_def_in_module(pool, mod, fld.Ty)
|
||
case _:
|
||
pass
|
||
self._ensure_field_types_in_module(pool, mod, fld.Ty)
|
||
fld = fld.Next
|
||
case mlirlite.LLVMType.Ptr(pointee):
|
||
# Pointer type: the pointee only needs an opaque declaration, not a full definition, but if the pointee is a Struct ensure the opaque declaration exists.
|
||
if pointee is not None: mlirlite.module_ensure_opaque_for_type(mod, pool, ty)
|
||
case mlirlite.LLVMType.Array(elem_ty, acount):
|
||
# Array type: if the element is a value-type struct it needs a full definition (Array elements only recurse into fields without emitting definitions -> llc "use of undefined type named '...'").
|
||
if elem_ty is not None:
|
||
match elem_ty:
|
||
case mlirlite.LLVMType.Struct(_, _, _):
|
||
self.ensure_struct_def_in_module(pool, mod, elem_ty)
|
||
case _:
|
||
pass
|
||
self._ensure_field_types_in_module(pool, mod, elem_ty)
|
||
case _:
|
||
pass
|
||
|
||
def get_field_name_ptr(self, fe: GenTypes.FieldEntry*) -> str:
|
||
"""Return a FieldEntry's field name, or None when unset."""
|
||
if fe is None: return None
|
||
return fe.Name
|
||
|
||
def get_field_type_ptr(self, fe: GenTypes.FieldEntry*) -> mlirlite.LLVMType*:
|
||
"""Return a FieldEntry's field type, or None when unset."""
|
||
if fe is None: return None
|
||
return fe.Ty
|
||
|
||
def get_field_default_ptr(self, fe: GenTypes.FieldEntry*) -> ast.AST*:
|
||
"""Return a FieldEntry's default-value AST node, or None when unset."""
|
||
if fe is None: return None
|
||
return fe.DefaultVal
|
||
|
||
def get_field_annot_class_name(self, fe: GenTypes.FieldEntry*) -> str:
|
||
"""Return a FieldEntry's annotation class name (Spec.SourceName), or None."""
|
||
if fe is None: return None
|
||
return TSpec.SpecSourceName(fe.Spec)
|
||
|
||
def field_is_volatile(self, fe: GenTypes.FieldEntry*) -> int:
|
||
"""Return 1 when the field is volatile, driving LLVM volatile load/store qualifiers."""
|
||
if fe is None: return 0
|
||
return fe.IsVolatile
|
||
|
||
def field_pointee_is_volatile(self, fe: GenTypes.FieldEntry*) -> int:
|
||
"""Return 1 when the field's pointee is volatile, driving volatile load/store qualifiers."""
|
||
if fe is None: return 0
|
||
return fe.PointeeVolatile
|
||
|
||
def field_is_const(self, fe: GenTypes.FieldEntry*) -> int:
|
||
"""Return 1 when the field is a const object, making field assignment fail fast."""
|
||
if fe is None: return 0
|
||
return fe.IsConst
|