Files
TransPyC/lib/core/LLVMCG/StructGen.py
T
TermiNexus 2e7bac1816 fix(HandlesImports/HandlesClassDef): 剥掉"祖先块前缀的陈旧半截副本",消除成员表重名条目
类别:a(编译器缺陷)

现场(用户构建日志):
  WARN[StructGen]: [结构体布局] 类 'ClassGenDef' 的成员表含**重名条目**:
  表 50 项 / 去重 42 项,现有 body 42 槽恰等于「去重名数 + vtable」

判据(磁盘实证 + 结构推导,非猜测):
  · ClassGenDef 自身 **0** 个字段(全是方法),父类 ClassGenBase 恰 **42** 个字段;
    temp / output 两份 stub 的 ClassGenDef body 都是 **42** 槽,且槽序 = ClassGenBase
    的字段声明序 ⇒ body 正确、**表多出 8 条**(50 = 42 + 8)。
  · ClassGenBase 自己的 Phase-1 stub 就是 42 槽(不是 8)⇒ 多出的 8 条并非"父类 stub 短",
    只能来自**内存里父类表尚未收集完就被展平**(写入了半截祖先块 Block[:k],k=8)——
    正是 PrependInheritedBlock 文档里写明的"已知残留"。
  · 重名条目不是布局槽位(见 FuncGen._get_member_offset 的重名段),却让
    _member_base 的 _Delta = 42 - 50 = -8 走进类型校准,并让回退路径的
    base + 表内下标 整体偏斜(同族现场:MatchCase / TypeGenResolve 的偏移算错)。

修复(判据全部为正向事实,且与插入方向无关):
  · 新增 NormalizeAncestralCopies(Table, Block, InsertAtHead) -> (新表, 是否已含完整块):
      ① 完整块在**头**、尾部挂前缀副本 ⇒ 剥尾(副本是被后来的整块前置推过去的);
      ② 完整块在**尾**、头部挂前缀副本 ⇒ 剥头(副本先写入、整块随后追加);
      ③ 完整块尚未出现 ⇒ 剥插入边(仅当这些名字已另有正本,见下)。
    剥掉的每一项在(表里已有的 / 随后插入的)完整块里同名同型 ⇒ **不丢布局信息**,
    与 PrependInheritedBlock 性质①"不删块内条目"不冲突(剥的是副本)。
    第一版曾"两端都剥",离线冒烟立刻暴露会把**正本**当副本剥掉([A,B,C,D,A,B] →
    [C,D,A,B],再前置整块反而多出 C,D 两份)⇒ 改为先定位完整块在哪一端、再剥另一端。
  · TrimStalePrefixCopy 判据:插入边连续游程与 Block[:j] 逐项同 name 同 IR 类型,
    1 ≤ j < len(Block),且这 j 个名字在表里**各另有一份**(计数 ≥ 2)——最后一条把
    "子类自有字段恰好与祖先块前缀同名同型"的误判排除。
  · 两个插入点(HandlesImports.PrependInheritedBlock 前置 / HandlesClassDef 展平追加)
    统一走该函数,判据对称;真正剥除时各留一条 WARN(含条数与前后表长)。
  · PrependInheritedBlock 原有两条幂等提前返回("表头等于块" / InheritedBlockAlreadyPresent)
    合并为 _HasFull 一条(语义等价),并更新其"已知残留"段落。
  · StructGen 的重名 WARN 追加**逐条明细**(名@表内下标):单看"表 50 / 去重 42"无法区分
    "祖先块前缀的陈旧副本"与"两条收集路径各登记一次"(两种成因修法完全不同)。

核验:py_compile + compileall 全 lib 通过;离线冒烟(调用真实函数,8 组用例 × 两个插入方向):
①/② 均 6→4 正确剥除,"无重复 / 带自有字段 / 类型不同 / 无正本 / 空表"均不改动。
未放宽任何 fail-fast。
2026-09-30 18:41:24 +08:00

962 lines
59 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
from typing import Any
import re
import vast as ast
import llvmlite.ir as ir
from llvmlite.ir.values import _Undefined
from lib.includes import t as _t
from lib.core.VLogger import get_logger as _vlog
# vtable 槽类型 `i8*` 的共享实例:llvmlite 的类型是不可变值语义(`_TypedPointerType`
# 的 `__eq__` 按结构比较、打印结果只取决于结构),同一实例可在任意多少量复用。
# 原来在 `_generate_structs` 的逐类 `member_types` 构建里**每类每次现建**
# (profile:`_TypedPointerType.__init__` 3,750,259 次中有一份来自这里)。
_I8_PTR: ir.Type = ir.PointerType(ir.IntType(8))
class StructGenMixin:
def _get_align(self, llvm_type: ir.Type) -> int:
"""类型自然对齐(字节)。
与 `_get_struct_size` 同一约定:**不允许静默近似**。对齐猜错会让字段字节偏移
与 LLVM 实际布局不一致(本函数是 `_get_struct_size` 的字段偏移累加与
`_compute_field_byte_offset` 的输入),故拿不到对齐时一律响亮失败:
- opaque/未解析的 IdentifiedStructType → 曾按 ptr_size 猜;
- 未识别类型 → 曾按 4 猜。
"""
if isinstance(llvm_type, ir.IntType):
return max(1, llvm_type.width // 8)
if isinstance(llvm_type, ir.FloatType):
return 4
if isinstance(llvm_type, ir.DoubleType):
return 8
if isinstance(llvm_type, ir.PointerType):
return self.ptr_size
if isinstance(llvm_type, ir.ArrayType):
return self._get_align(llvm_type.element)
if isinstance(llvm_type, ir.VectorType):
# 与 LLVM 一致:向量对齐 = 元素对齐(同 _get_struct_size 的向量口径)
return self._get_align(llvm_type.element)
if isinstance(llvm_type, ir.LiteralStructType):
return max((self._get_align(f) for f in llvm_type.elements), default=1)
if isinstance(llvm_type, ir.IdentifiedStructType):
resolved = self._resolve_opaque_struct(llvm_type)
if resolved is not llvm_type:
return self._get_align(resolved)
if llvm_type.elements is None or len(llvm_type.elements) == 0:
raise ValueError(
f"[FATAL][__sizeof__] 结构体 '{getattr(llvm_type, 'name', llvm_type)}' "
f"布局未就绪(opaque / 未解析到定义),拒绝按指针对齐兜底 —— "
f"对齐猜错会让字段字节偏移与 LLVM 实际布局不一致(读写落到错误字段)。"
f"请先确保该结构体已注册/可从 stub 加载后再取对齐。"
)
return max((self._get_align(f) for f in llvm_type.elements), default=1)
if isinstance(llvm_type, ir.VoidType):
return 0
raise ValueError(
f"[FATAL][__sizeof__] 不支持的类型 {llvm_type}({type(llvm_type).__name__})"
f"无法计算对齐,拒绝以常数 4 兜底 —— 对齐猜错会让字段字节偏移与 LLVM 实际布局不一致。")
def _resolve_opaque_struct(self, llvm_type: ir.Type) -> ir.Type:
"""尝试从 Gen.structs 中查找已解析的同名 struct,解决跨模块 struct 类型对象不一致的问题"""
if not isinstance(llvm_type, ir.IdentifiedStructType):
return llvm_type
if llvm_type.elements is not None and len(llvm_type.elements) > 0:
return llvm_type
# opaque struct,尝试通过名称在 Gen.structs 中查找已解析的版本
st_name: str
try:
st_name = llvm_type.name
except Exception:
return llvm_type
if not st_name:
return llvm_type
# 提取短名称
short_name: str = st_name
if '.' in st_name:
short_name = st_name.split('.', 1)[1]
# 尝试短名称查找
if short_name in self.structs:
resolved: ir.IdentifiedStructType = self.structs[short_name]
if isinstance(resolved, ir.IdentifiedStructType) and resolved.elements is not None and len(resolved.elements) > 0:
return resolved
# 尝试全名查找
if st_name in self.structs:
resolved: ir.IdentifiedStructType = self.structs[st_name]
if isinstance(resolved, ir.IdentifiedStructType) and resolved.elements is not None and len(resolved.elements) > 0:
return resolved
# 尝试通过导入处理器从 stub 文件加载 struct 定义
import_handler: Any = self._import_handler_ref
if import_handler is not None:
try:
import_handler._TryLoadStructFromStub(short_name, self)
except Exception:
pass
# 加载后再次查找
if short_name in self.structs:
resolved = self.structs[short_name]
if isinstance(resolved, ir.IdentifiedStructType) and resolved.elements is not None and len(resolved.elements) > 0:
return resolved
if st_name in self.structs:
resolved = self.structs[st_name]
if isinstance(resolved, ir.IdentifiedStructType) and resolved.elements is not None and len(resolved.elements) > 0:
return resolved
return llvm_type
def _get_struct_size(self, llvm_type: ir.Type) -> int:
"""类型字节尺寸(含字段对齐/填充)。
**不允许静默近似**:本函数的返回值会被 `X.__sizeof__()` 直接采用
(HandlesExprCall._HandleSizeofLlvm),而 memhub 的 alloc 又直接吃该值。
此前两处静默兜底会**少算尺寸**,进而按过小尺寸分配 → 越界写坏邻居块的
8 字节块头(块头 `(order<<1)|1` 被写成偶数 ⇒ reset_to 视为"已释放"跳过、
free 被 _is_valid_ptr 判无效而静默 no-op)⇒ 该块永久丢失 ⇒ 1 GiB 主池漏满:
- opaque/未解析的 IdentifiedStructType → 曾一律返回 ptr_size;
- 未识别类型(如 SIMD VectorType)→ 曾一律返回 4。
故尺寸拿不到时一律响亮失败(项目约定:类型信息缺失立即终止,不兜底)。
"""
if isinstance(llvm_type, ir.IntType):
return max(1, llvm_type.width // 8)
if isinstance(llvm_type, ir.FloatType):
return 4
if isinstance(llvm_type, ir.DoubleType):
return 8
if isinstance(llvm_type, ir.PointerType):
return self.ptr_size
if isinstance(llvm_type, ir.ArrayType):
return self._get_struct_size(llvm_type.element) * llvm_type.count
if isinstance(llvm_type, ir.VectorType):
# 与 LLVM 一致:元素尺寸 × 元素数,再向上取到 2 的幂(<3 x i32> = 16 字节)
elem_size: int = self._get_struct_size(llvm_type.element)
lanes: int = llvm_type.count
padded: int = 1
while padded < lanes:
padded = padded << 1
return elem_size * padded
if isinstance(llvm_type, ir.LiteralStructType):
total: int = 0
max_align: int = 1
# llvmlite 的 LiteralStructType 只有 `.elements`(**没有** `.fields`):
# 旧代码写 `.fields` 必然 AttributeError,尺寸/对齐链在此静默断掉。
for f in llvm_type.elements:
fa: int = self._get_align(f)
fs: int = self._get_struct_size(f)
max_align = max(max_align, fa)
if fa > 1:
total = (total + fa - 1) // fa * fa
total += fs
if max_align > 1:
total = (total + max_align - 1) // max_align * max_align
return total
if isinstance(llvm_type, ir.IdentifiedStructType):
# 尝试解析 opaque struct 为已注册的版本
resolved = self._resolve_opaque_struct(llvm_type)
if resolved is not llvm_type:
return self._get_struct_size(resolved)
if llvm_type.elements is None or len(llvm_type.elements) == 0:
raise ValueError(
f"[FATAL][__sizeof__] 结构体 '{getattr(llvm_type, 'name', llvm_type)}' "
f"布局未就绪(opaque / 未解析到定义),拒绝按指针大小兜底 —— "
f"近似会少算尺寸,调用方(memhub alloc)按过小尺寸分配并越界写坏邻居块头。"
f"请先确保该结构体已注册/可从 stub 加载后再取尺寸。"
)
total2: int = 0
max_align2: int = 1
for f in llvm_type.elements:
fa2: int = self._get_align(f)
fs2: int = self._get_struct_size(f)
max_align2 = max(max_align2, fa2)
if fa2 > 1:
total2 = (total2 + fa2 - 1) // fa2 * fa2
total2 += fs2
if max_align2 > 1:
total2 = (total2 + max_align2 - 1) // max_align2 * max_align2
return total2
raise ValueError(
f"[FATAL][__sizeof__] 不支持的类型 {llvm_type}({type(llvm_type).__name__})"
f"无法计算尺寸,拒绝以常数兜底 —— 近似会少算尺寸并越界写坏内存池块头。"
)
@staticmethod
def _strip_unnecessary_quotes(ir_str: str) -> str:
def _unquote(m: re.Match) -> str:
prefix: str = m.group(1)
name: str = m.group(2)
if '.' in name:
return m.group(0)
if re.match(r'^[a-zA-Z_$][a-zA-Z0-9_$]*$', name):
return prefix + name
return m.group(0)
return re.sub(r'([@%])"([^"]+)"', _unquote, ir_str)
def finalize(self) -> str:
self._set_Vtable_initializers()
self._fix_global_var_init()
# 函数级 DWARF(--dwarf / --asan):把 Module 落成 .ll 文本之前补模块级
# !llvm.dbg.cu 与 !llvm.module.flags(Debug Info Version=3);未启用时
# _GetDebugInfo() 返回 None,不做任何事(零影响)。
_DbgInfo = self._GetDebugInfo()
if _DbgInfo is not None:
_DbgInfo.Finalize()
# Ctrl+C 调用栈(--traceback):同样是"落成 .ll 文本之前"的模块级收尾——
# 当本模块就是 includes/traceback.vp 时,注入 @llvm.global_ctors 构造器调用
# 其 Install()(见 BaseGen._RegisterTracebackInstallViaCtor)。未启用/非该模块
# 时 traceback_ctor=False,不做任何事(零影响)。
if self.traceback_ctor:
self._RegisterTracebackInstallViaCtor()
ir_text: str
try:
ir_text = str(self.module)
except TypeError as e:
for func in self.module.functions:
for block in func.blocks:
for instr in block.instructions:
try:
str(instr)
except TypeError as te:
pass
raise
ir_text = self._strip_unnecessary_quotes(ir_text)
struct_defs: list[str] = []
struct_names: set[str] = set()
for ClassName, struct_type in self.structs.items():
if isinstance(struct_type, ir.IdentifiedStructType):
sname: str = struct_type.name
sname_ir: str
if sname[0].isdigit() or '.' in sname or not sname.replace('_', '').replace('.', '').isalnum():
sname_ir = f'%"{sname}"'
else:
sname_ir = f'%{sname}'
if struct_type.elements is None or len(struct_type.elements) == 0:
if ClassName not in self.typedef_int_widths:
struct_names.add(sname)
struct_defs.append(f'{sname_ir} = type opaque')
continue
elems: str = ', '.join(str(e) for e in struct_type.elements)
if struct_type.packed:
struct_defs.append(f'{sname_ir} = type <{{ {elems} }}>')
else:
struct_defs.append(f'{sname_ir} = type {{ {elems} }}')
struct_names.add(sname)
# 协程帧结构体兜底(M1):帧注册进 Gen.structs 时上面已输出;
# 未注册(被 _generate_structs 清空)时这里补齐定义,避免
# getelementptr base element must be sized。
coro_frames: dict = getattr(self, '_coro_frame_types', None) or {}
for _cf_name, _cf_ty in coro_frames.items():
if _cf_name in struct_names:
continue
if isinstance(_cf_ty, ir.IdentifiedStructType) and _cf_ty.elements:
_cf_sname: str = _cf_ty.name
_cf_ir: str = f'%"{_cf_sname}"' if (_cf_sname[0].isdigit() or "." in _cf_sname) else f'%{_cf_sname}'
struct_defs.append(f'{_cf_ir} = type {{ {", ".join(str(e) for e in _cf_ty.elements)} }}')
struct_names.add(_cf_sname)
if struct_defs:
lines: list[str] = ir_text.split('\n')
# 预计算全部 "= type" 定义行前缀(一次性),用 str.startswith(tuple)
# 在 C 层批量比较,替代原 O(lines × structs × patterns) 的 Python 嵌套
# 循环(PROF: finalize tottime 84.8s + startswith 4114 万次 46.9s)。
name_prefixes: list[str] = []
for name in struct_names:
if name[0].isdigit() or '.' in name or not name.replace('_', '').replace('.', '').isalnum():
name_prefixes.append(f'%"{name}" = type')
name_prefixes.append(f'%"{name}"=type')
name_prefixes.append(f'%{name} = type')
name_prefixes.append(f'%{name}=type')
else:
name_prefixes.append(f'%{name} = type')
name_prefixes.append(f'%{name}= type')
name_prefixes.append(f'%{name}=type')
prefixes_tuple: tuple[str, ...] = tuple(name_prefixes)
filtered: list[str] = []
for line in lines:
if not line.strip().startswith(prefixes_tuple):
filtered.append(line)
ir_text = '\n'.join(filtered)
header_end: int = ir_text.find('\ndefine')
if header_end == -1:
header_end = ir_text.find('\ndeclare')
if header_end == -1:
header_end = ir_text.find('\n@')
if header_end == -1:
header_end = len(ir_text)
insert_pos: int = ir_text.rfind('\n', 0, header_end)
if insert_pos == -1:
insert_pos = header_end
struct_block: str = '\n'.join(struct_defs) + '\n'
_ir_head = ir_text[:insert_pos + 1]
_ir_tail = ir_text[insert_pos + 1:]
ir_text = _ir_head + struct_block + _ir_tail
# 仅含特殊字符(数字开头或含点)的 struct 名需要加引号:
# 合并为单个正则一次扫描全文本,替代逐 name 各自 re.sub(每个 name
# 一次 O(text) 扫描)。lookahead 边界排除保证各备选互斥,语义等价。
special_names: list[str] = [n for n in struct_names if n[0].isdigit() or '.' in n]
if special_names:
ir_text = re.sub(
r'%(' + '|'.join(re.escape(n) for n in special_names) + r')(?=[^a-zA-Z0-9_$\.]|$)',
lambda m: r'%"' + m.group(1) + r'"',
ir_text
)
return ir_text
def _MemberSlotAccepts(self, Declared: Any, Slot: Any) -> bool:
"""声明类型能否**隐式写进**该 struct 槽(判据与存储侧同一套)。
用于判定已冻结的 struct body 是否与 `class_members` 自洽。判定放宽到与
存储侧一致,避免误清:通用指针槽(i8*/void*)照旧放行(历史 stub 普遍
如此),继承上转/数组退让亦放行;只有**存储时必被拒绝**的组合——层数
不同、指针与非指针混位——才判为不一致,触发重建。
"""
if Declared is None or Slot is None:
return True
# 同一对象直接放行:llvmlite 的 Type.__eq__ 走类型键比较(profile:
# 本函数被 _generate_structs 调 54.2M 次,其中 types.py __eq__ 合计 ~30s)
# ⇒ 先做身份判定短路,等价且避免绝大多数 __eq__/str() 开销。
if Declared is Slot:
return True
if Declared == Slot or str(Declared) == str(Slot):
return True
_d_ptr: bool = isinstance(Declared, ir.PointerType)
_s_ptr: bool = isinstance(Slot, ir.PointerType)
if _d_ptr != _s_ptr:
return False
if not _d_ptr:
# 数值/浮点宽度差异不在本次收紧范围:保持既有行为(不清 body)。
return True
try:
Allowed, _Reason = self._ClassifyPtrImplicitConvert(Declared, Slot)
return bool(Allowed)
except Exception:
# 判据不可用(缺上下文)时保守放行,避免误清已正确的布局。
return True
def _IsRenumLayoutClass(self, ClassName: str | None) -> bool:
"""该类是否属 **REnum 布局**(主 REnum 自身,或其变体嵌套结构体 `<REnum>_<Variant>`)。
REnum 的 struct body 是 **max-variant 镜像布局**:`class_members` 里那些名字是
*变体*(不是字段),变体嵌套结构体也必须逐字镜像主结构体。因此**一律不得**用
class_members 的"声明类型"去改写或校验它们的布局 —— 否则跨模块 GEP 偏移与
定义端失配(变体载荷槽本就允许与 max-variant 槽类型不同,取值处靠 bitcast)。
"""
if not ClassName:
return False
_tbl: Any = getattr(self, 'SymbolTable', None)
if _tbl is None:
return False
try:
_e: Any = _tbl.lookup(ClassName)
except Exception:
return False
if _e is not None and getattr(_e, 'IsRenum', False):
return True
if '_' in ClassName:
_prefix: str = ClassName.rsplit('_', 1)[0]
try:
_pe: Any = _tbl.lookup(_prefix)
except Exception:
_pe = None
if _pe is not None and getattr(_pe, 'IsRenum', False):
return True
return False
def _StructBodyHasPlaceholderT(self, St: Any) -> bool:
"""该 struct body 是否含未特化的 `T*` 占位字段(结果按 body 对象记忆)。
`_generate_structs` 里有两处对"全部类的全部元素"做这一扫描(Step 4 的完整性与
set_body 前的复核),每次调用都全量重扫 —— profile(2026-09-29 main_prof_72472):
这两处是本函数 62.5M 次 `isinstance`(6.20s)与 4.98M 次 `rsplit`(1.12s)的主源。
判定精确性:扫描是 `st.elements` 的**纯函数**,而 `elements` 在 llvmlite 里是
**整体替换**(`set_body` 赋新 tuple;作废时置 None;从不原地改元素)⇒ 用
`elements` 的**对象身份**当版本号即可精确判定"body 未变"。缓存条目**强引用**
struct 实例与 elements 对象,二者存活 ⇒ `id()` 不会被回收复用 ⇒ 不会误判命中。
(代价:被替换掉的旧 body tuple 会被缓存多留一份引用;量级 = 结构体数量级,可忽略。)
"""
_cache: dict | None = getattr(self, '_struct_t_placeholder_cache', None)
if _cache is None:
_cache = {}
self._struct_t_placeholder_cache = _cache
_key: int = id(St)
_elems: Any = getattr(St, 'elements', None)
_hit: Any = _cache.get(_key)
if _hit is not None and _hit[0] is St and _hit[1] is _elems:
return _hit[2]
_found: bool = False
if _elems:
for _elem in _elems:
if isinstance(_elem, ir.PointerType) and isinstance(_elem.pointee, ir.IdentifiedStructType):
_pname: str = _elem.pointee.name
_short: str = _pname.rsplit('.', 1)[-1] if '.' in _pname else _pname
if _short == 'T':
_found = True
break
_cache[_key] = (St, _elems, _found)
return _found
def _generate_structs(self) -> None:
# 热路径局部化:profile 中本函数被调 35,240 次(cum 1893s),内部
# SymbolTable.lookup 15.9M 次(16.7s)+ 真值判断触发的 __len__ 15.9M 次
# (6.8s)⇒ 把 SymbolTable / lookup 提到局部,避免逐类重复属性查找。
# 真值语义与 `if self.SymbolTable:` 逐字一致(SymbolTable 定义了 __len__,
# 空表为假);表在单次调用内只增不减,故提升到函数级等价。
_symtab: Any = self.SymbolTable
_has_symtab: bool = bool(_symtab)
_sym_lookup: Any = _symtab.lookup if _symtab is not None else None
all_classes: set[str] = set(self.class_methods.keys()) | set(self.class_members.keys())
preserved_structs: dict[str, ir.IdentifiedStructType] = {}
for name, st in self.structs.items():
if name not in all_classes:
preserved_structs[name] = st
elif isinstance(st, ir.IdentifiedStructType):
preserved_structs[name] = st
self.structs.clear()
for name, st in preserved_structs.items():
self.structs[name] = st
# Step 1: 先为所有类创建空的 struct,确保后面可以正确引用
all_classes = set(self.class_methods.keys()) | set(self.class_members.keys())
for ClassName in all_classes:
is_packed: bool = ClassName in self.class_packed
if _has_symtab and _sym_lookup is not None:
Entry = _sym_lookup(ClassName)
if Entry and Entry.IsExceptionClass:
continue
# 注意:类名可能与 CEnum 成员名冲突(如 ASTKind.Module 枚举成员与 Module 类同名)
# 如果 ClassName 在 class_methods 中,表明是 _EmitClassLlvm 显式处理的常规类,
# 不应被 IsEnum/IsTypedef 检查跳过
if Entry and Entry.IsEnum and ClassName not in self.class_methods:
if not Entry.IsRenum:
continue
if Entry and Entry.IsTypedef and ClassName not in self.class_methods:
if Entry.BaseType and not isinstance(Entry.BaseType, (type(None),)):
if not isinstance(Entry.BaseType, (_t._CTypedef,)):
continue
if ClassName in self.structs:
if is_packed:
self.structs[ClassName].packed = True
continue
# 修复跨模块类型定义冲突:如果类是从其他模块导入的(已在 class_sha1_map 注册),
# 必须使用导入的 source_sha1 作为前缀,而非本地模块的 sha1。
# 否则会创建缺少基类字段(如 GSListNode.Next)的本地类型定义,
# 导致字段索引错位 → 访问违规崩溃。
_imported_sha1: str = self.class_sha1_map.get(ClassName, '')
if not _imported_sha1:
# 兜底查全局类→模块映射:class_sha1_map 可能因共享 class_methods
# 跨文件泄漏而缺失条目(LlvmGenerator 共享赋值),此时若处于
# 泛型特化窗口(module_sha1 被切为模板定义模块如 _list 的 sha1),
# _mangle_name 会用错误前缀注册其它模块的类
# (7cadfe07e983fd90.Translator 跨模块类型错位根因)。
# _struct_sha1_map 由 Phase2 从 vpi 扫描构建,含全部类的正确模块映射。
_imported_sha1 = self._struct_sha1_map.get(ClassName, '')
if _imported_sha1:
mangled_name: str = f"{_imported_sha1}.{ClassName}"
else:
mangled_name: str = self._mangle_name(ClassName)
struct_type: ir.IdentifiedStructType = ir.IdentifiedStructType(self.module, mangled_name, packed=is_packed)
self.structs[ClassName] = struct_type
# Step 2: 解析跨模块的成员类型
for ClassName, annotations in self.class_member_annotations.items():
if ClassName not in self.class_members:
continue
for i, (member_name, member_type) in enumerate(self.class_members[ClassName]):
if member_name in annotations:
annotation: Any = annotations[member_name]
ResolvedType: ir.Type | None = None
if isinstance(annotation, ast.Attribute):
ClassTypeName: str = annotation.attr
if ClassTypeName in self.structs:
ResolvedType = self.structs[ClassTypeName]
elif isinstance(annotation, ast.BinOp) and isinstance(annotation.op, ast.BitOr):
left: Any = annotation.left
if isinstance(left, ast.Attribute):
ClassTypeName: str = left.attr
if ClassTypeName in self.structs:
ResolvedType = self.structs[ClassTypeName]
if ResolvedType:
self.class_members[ClassName][i] = (member_name, ResolvedType)
struct_name_map: dict[str, ir.IdentifiedStructType] = {}
for struct_name, struct in self.structs.items():
try:
struct_name_map[struct.name] = struct
except AssertionError:
struct_name_map[struct_name] = struct
orig: Any = getattr(struct, '_original_name', None)
if orig:
struct_name_map[orig] = struct
# Step 3: 更新成员类型,确保使用正确的 mangled struct 引用
for ClassName in all_classes:
if ClassName not in self.class_members:
continue
for i, (member_name, member_type) in enumerate(self.class_members[ClassName]):
if isinstance(member_type, ir.PointerType):
pointee: ir.Type = member_type.pointee
if isinstance(pointee, ir.IdentifiedStructType):
pname: str | None
try:
pname = pointee.name
except AssertionError:
pname = None
struct: ir.IdentifiedStructType | None = struct_name_map.get(pname) if pname else None
if struct is None:
orig: Any = getattr(pointee, '_original_name', None)
struct = struct_name_map.get(orig) if orig else None
if struct is not None:
# 【不重建已是同一实例的指针】`ir.PointerType(同一 struct 实例)`
# 与现有成员类型**结构等价**(llvmlite 的 `_TypedPointerType.__eq__`
# 按 pointee 比较;打印结果只取决于结构)⇒ 覆盖写是纯 no-op,却要
# 现建一个指针对象。profile(2026-09-29 main_prof_72472):本函数内
# `_TypedPointerType.__init__` 3,750,259 次(2.60s)+ `_patched_new`
# 3,750,260 次(2.03s)+ `_patched_init`/`_IsBadPointee`(约 4.4s)
# 主要来自这里;而本分支每次调用都会遍历全部类的全部成员
# ⇒ 绝大多数是"pointee 本来就是它"的重复重建。
if pointee is not struct:
self.class_members[ClassName][i] = (member_name, ir.PointerType(struct))
else:
raw_name: str = pname or ''
if raw_name.startswith('struct.'):
raw_name = raw_name[7:]
new_struct: ir.IdentifiedStructType = self._get_or_create_struct(raw_name)
self.class_members[ClassName][i] = (member_name, ir.PointerType(new_struct))
elif isinstance(member_type, ir.IdentifiedStructType):
mname: str | None
try:
mname = member_type.name
except AssertionError:
mname = None
struct: ir.IdentifiedStructType | None = struct_name_map.get(mname) if mname else None
if struct is None:
orig: Any = getattr(member_type, '_original_name', None)
struct = struct_name_map.get(orig) if orig else None
if struct is not None:
# 同上:已是同一实例 ⇒ 覆盖写是纯 no-op(llvmlite 类型按结构相等)。
if member_type is not struct:
self.class_members[ClassName][i] = (member_name, struct)
else:
raw_name: str = mname or ''
if raw_name.startswith('struct.'):
raw_name = raw_name[7:]
new_struct: ir.IdentifiedStructType = self._get_or_create_struct(raw_name)
self.class_members[ClassName][i] = (member_name, new_struct)
# Step 4: 创建最终的 structs 并设置正确的成员类型
for ClassName in all_classes:
if _has_symtab and _sym_lookup is not None:
Entry = _sym_lookup(ClassName)
if Entry and Entry.IsExceptionClass:
continue
# 注意:类名可能与 CEnum 成员名冲突(如 ASTKind.Constant 枚举成员与 Constant 类同名)
# 如果 ClassName 在 class_methods 中,表明是 _EmitClassLlvm 显式处理的常规类,
# 不应被 IsEnum 检查跳过(与 Step 1 的守卫保持一致)
if Entry and Entry.IsEnum and ClassName not in self.class_methods:
if not Entry.IsRenum:
continue
existing_st: ir.IdentifiedStructType | None = self.structs.get(ClassName)
if isinstance(existing_st, ir.IdentifiedStructType) and existing_st.elements is not None and len(existing_st.elements) > 0:
# 治本修复:检查 struct body 是否不完整(元素数少于 class_members + vtable 预期)。
# 常见于跨模块继承:子类 struct 在父类 class_members 加载前从 stub 生成,
# stub 中的定义可能不完整或类型不正确(如 GSList* 代替 AST*、i8* 代替 i32)。
# 此处检测到不完整后清除 elements(公开属性,见下方 Step 4 说明)和
# stub 缓存,让后续代码重新从 stub 加载。
_has_vtable_precheck: bool = ClassName in self.class_vtable or ClassName in self._cross_module_vtable_classes
if not _has_vtable_precheck:
_p_pre: str | None = self.class_parent.get(ClassName)
while _p_pre:
if _p_pre in self.class_vtable or _p_pre in self._cross_module_vtable_classes:
_has_vtable_precheck = True
break
_p_pre = self.class_parent.get(_p_pre)
_expected_count_pre: int = len(self.class_members.get(ClassName, [])) + (1 if _has_vtable_precheck else 0)
# has_vtable 时,首字段必须是 i8*(vtable 指针)。
# 若首字段非指针(如 i8 占位符),struct body 不完整,需清除重新加载。
_vtable_first_ok: bool = True
if _has_vtable_precheck and len(existing_st.elements) > 0:
_vtable_first_ok = isinstance(existing_st.elements[0], ir.PointerType)
# 检测 T* 占位类型(未特化的泛型类型参数)。
# 当 struct 包含 T* 字段时(如 BasicBlock 继承 GSListNode[BasicBlock] 但 stub 未特化),
# 说明 stub 使用了未特化的基类,需清除重新生成。
# 结果按 body 对象记忆(见 _StructBodyHasPlaceholderT:本扫描是
# "全类 × 全元素"的纯函数,重复扫描曾占本函数 isinstance 的主源)。
_has_t_placeholder_pre: bool = self._StructBodyHasPlaceholderT(existing_st)
# 逐元素类型一致性(治本):仅比"个数 / 首元素是 vtable 指针 / T 占位"
# 会漏掉**类型错位**的已冻结 body —— 现场 Generator:47 个元素个数正确、
# 元素名字也全对,但第 4 槽是 i8** 而 class_members 声明
# %"d827f3045bd28467.TypeGenCDef"*,个数命中即被当作"完整"直接 continue;
# 而 llvmlite 的 set_body 只能设一次 ⇒ 错布局被永久冻结(后续 .vpi 重载
# 只修 class_members,改不了 body)⇒ 赋值时报"层数不同"。
# 判据与存储侧同一套(_MemberSlotAccepts → _ClassifyPtrImplicitConvert):
# 声明类型**能隐式写进该槽**才算一致 —— 通用指针槽(i8*)照旧放行,不误清。
_slot_types_ok: bool = True
_members_pre: list = self.class_members.get(ClassName, [])
# REnum 布局(主 REnum 及变体嵌套结构体)是 max-variant 镜像布局,
# class_members 里是*变体*不是字段 ⇒ 不参与逐元素类型校验(见
# _IsRenumLayoutClass)。
if _members_pre and not self._IsRenumLayoutClass(ClassName):
_base_pre: int = 1 if _has_vtable_precheck else 0
_elems_pre: list = list(existing_st.elements)
for _i_pre, _item_pre in enumerate(_members_pre):
_j_pre: int = _base_pre + _i_pre
if _j_pre >= len(_elems_pre):
break
_decl_pre: Any = _item_pre[1] if isinstance(_item_pre, tuple) and len(_item_pre) >= 2 else None
if not self._MemberSlotAccepts(_decl_pre, _elems_pre[_j_pre]):
_slot_types_ok = False
break
if (len(existing_st.elements) >= _expected_count_pre and _vtable_first_ok
and not _has_t_placeholder_pre and _slot_types_ok):
if ClassName in self.class_packed and not existing_st.packed:
existing_st.packed = True
continue
# 成员表**重名**时长度判据不可信:重名条目不是布局槽位(body 由定义模块的
# stub 决定),却会让 `_expected_count_pre` 多算一项 ⇒ 误判"body 不完整"
# 而用带重复项的表重建 body(本模块布局比其它模块多一槽 ⇒ 跨模块 ABI 撕裂)。
# 现场:astaux.ExceptHandler 表 10 项含两个 'children',而定义模块 stub 的
# body 恰为 10 槽 = vtable + 9 个不同字段名。判据:表有重名**且** body 槽数
# 恰等于「去重名数 + vtable」⇒ body 已是权威布局,只报事实、不重建。
_uniq_names_pre: set[str] = {n for n, _ in _members_pre}
if (len(_uniq_names_pre) < len(_members_pre)
and len(existing_st.elements) == len(_uniq_names_pre) + (1 if _has_vtable_precheck else 0)
and _vtable_first_ok and not _has_t_placeholder_pre):
_DupRep: set = getattr(self, '_StructDupMembersReported', None)
if _DupRep is None:
_DupRep = set()
self._StructDupMembersReported = _DupRep
if ClassName not in _DupRep:
_DupRep.add(ClassName)
# 重名条目**逐条列出**(名 + 表内下标):单看"表 50 / 去重 42"
# 无法判断是哪 8 条、落在哪一端,而"祖先块前缀的陈旧副本"
# (见 HandlesImports.DropStaleAncestralCopies)与"两条收集路径
# 各登记一次"两种成因的**修法完全不同** ⇒ 报文必须自带判据。
_SeenIdx: dict = {}
_DupDetail: list = []
_idxS: int
for _idxS, (_nmS, _) in enumerate(_members_pre):
if _nmS in _SeenIdx:
_DupDetail.append(f"{_nmS}@{_idxS}")
else:
_SeenIdx[_nmS] = _idxS
_vlog().warning(
f"[结构体布局] 类 '{ClassName}' 的成员表含**重名条目**:表 "
f"{len(_members_pre)} 项 / 去重 {len(_uniq_names_pre)} 项,"
f"现有 body {len(existing_st.elements)} 槽恰等于「去重名数 + vtable」"
f"⇒ 认定 body 为权威布局(来自定义模块 stub),**不按重复表重建**"
f"(重建会多出一槽、与其它模块布局不一致)。"
f"重名条目(名@表内下标)={_DupDetail[:24]!r}"
f"(共 {len(_DupDetail)} 条)。请核对成员表为何重复登记同一字段名。",
"StructGen")
if ClassName in self.class_packed and not existing_st.packed:
existing_st.packed = True
continue
# struct body 不完整,清除 elements 和 stub 缓存以便重新加载。
# 注意:本仓 llvmlite 版本的字段名是**公开属性 `elements`**
# (types.py: `self.elements = None`;`is_opaque` = `elements is None`;
# `set_body` 只在 `elements is not None` 时拒绝)。此前写
# `_elements = None` 在本版本**不存在该属性** ⇒ 清空是空操作 ⇒
# 后续 `if struct_type.elements is None` 恒假 ⇒ set_body 永不执行 ⇒
# 错误布局(现场 Generator elements[11]=i8**)被永久冻结,而
# class_members 已被权威重建为正确类型 ⇒ 赋值时报"层数不同"。
existing_st.elements = None
# 同时清除 sha1 全名 key 的 elements:
# self.structs 中可能同时存在 'Value'(短名) 和 'f9e36e2cd6fa659f.Value'(sha1全名) 两个 key,
# _TryLoadStructFromStub 调用 _get_or_create_struct 时会返回 sha1全名 key 的 struct。
# 若只清除短名 key 的 elements,sha1全名 key 的 struct 仍保持错误 body(is_opaque=False),
# 导致 _TryLoadStructFromStub 跳过 set_body,struct 保持不完整字段数。
_imported_sha1_pre: str = self.class_sha1_map.get(ClassName, '')
if _imported_sha1_pre:
_full_key_pre: str = f"{_imported_sha1_pre}.{ClassName}"
_full_st_pre: ir.IdentifiedStructType | None = self.structs.get(_full_key_pre)
if _full_st_pre is not None and isinstance(_full_st_pre, ir.IdentifiedStructType):
_full_st_pre.elements = None
_import_handler_pre: Any = self._import_handler_ref
if _import_handler_pre is not None:
_cache_key_pre: tuple = (id(self), ClassName)
if _cache_key_pre in _import_handler_pre._struct_Load_cache:
del _import_handler_pre._struct_Load_cache[_cache_key_pre]
# 同时清除 sha1全名 key 的 stub 缓存
if _imported_sha1_pre:
_cache_key_full_pre: tuple = (id(self), f"{_imported_sha1_pre}.{ClassName}")
if _cache_key_full_pre in _import_handler_pre._struct_Load_cache:
del _import_handler_pre._struct_Load_cache[_cache_key_full_pre]
has_vtable: bool = ClassName in self.class_vtable or ClassName in self._cross_module_vtable_classes
if not has_vtable:
p: str | None = self.class_parent.get(ClassName)
while p:
if p in self.class_vtable or p in self._cross_module_vtable_classes:
has_vtable = True
self.class_vtable.add(ClassName)
break
p = self.class_parent.get(p)
has_bitfield: bool = ClassName in self.class_member_bitfields and any(
self.class_member_bitfields[ClassName].get(name, 0) > 0
for name, _ in self.class_members.get(ClassName, [])
)
all_members: list[tuple[str, ir.Type]] = self.class_members.get(ClassName, [])
# 跳过正在发射中且成员尚未填充的类(递归特化时避免过早设置空 body)
if not all_members and not has_vtable and ClassName in self._classes_in_progress:
continue
# 字段收集期间触发的嵌套 _generate_structs:class_members 可能不完整,
# set_body 只能调用一次,跳过等字段收集完成后再设置 body
if ClassName in self._collecting_members_set:
continue
all_bitfield: bool = all(
self.class_member_bitfields.get(ClassName, {}).get(name, 0) > 0
for name, _ in all_members
) if all_members else False
mangled_name: str = self._mangle_name(ClassName)
member_types: list[ir.Type]
if has_bitfield and all_bitfield:
total_bits: int = sum(self.class_member_bitfields.get(ClassName, {}).get(name, 0)
for name, _ in all_members)
storage_type: ir.IntType
if total_bits <= 8:
storage_type = ir.IntType(8)
elif total_bits <= 16:
storage_type = ir.IntType(16)
elif total_bits <= 32:
storage_type = ir.IntType(32)
else:
storage_type = ir.IntType(64)
member_types = [storage_type]
bitfield_offsets: dict[str, int] = {}
current_bit_offset: int = 0
for name, _ in all_members:
bit_width: int = self.class_member_bitfields.get(ClassName, {}).get(name, 0)
bitfield_offsets[name] = current_bit_offset
current_bit_offset += bit_width
self.class_member_bitoffsets[ClassName] = bitfield_offsets
else:
member_types = []
if has_vtable:
# 复用共享 i8* 实例(见模块顶部 _I8_PTR 说明)
member_types.append(_I8_PTR)
if ClassName in self.class_members:
for _, member_type in self.class_members[ClassName]:
if member_type is None:
member_type = ir.IntType(32)
member_types.append(member_type)
else:
if has_vtable:
# has_vtable 但 class_members 为空(跨模块导入类):
# 保持 member_types 为空([i8*] 占位会导致 stub 加载跳过,
# 因为 set_body 后 is_opaque=False)。让 struct 保持 opaque,
# 由 _TryLoadStructFromStub 设置完整 body。
member_types = []
else:
# 无 vtable 且 class_members 为空:常见于 register_method 早期
# 调用 _generate_structs(此时 class_members[ClassName]=[] 但尚未
# 填充实际字段)。保持 opaque,避免 set_body(*[i8]) 永久损坏 struct
# (llvmlite 的 set_body 只能调用一次)。后续 _generate_structs 调用
# 会从 class_members 或 stub 设置正确 body。
member_types = []
# 获取之前创建的 struct 并设置成员
struct_type: ir.IdentifiedStructType = self.structs[ClassName]
if ClassName in self.class_packed:
struct_type.packed = True
# 只在未设置 body 时设置,避免重复定义错误
if struct_type.elements is None:
# 跨模块 struct 的 class_members 可能不完整(当前模块只解析了部分字段),
# 优先从 stub.ll 加载完整定义。set_body 只能对 opaque struct 调用一次,
# 若用不完整 class_members 设置 body,后续无法修正,会导致 GEP 越界。
# 但本地定义的类(_classes_in_progress)不从 stub 加载,因为 stub 可能
# 来自同名导入类,其 vtable 状态与本地定义不同(@t.NoVTable 冲突)
import_handler: Any = self._import_handler_ref
if import_handler is not None and ClassName not in self._classes_in_progress:
try:
# 传入 source_sha1 确保从正确的 stub 文件加载完整定义
_src_sha1: str = self.class_sha1_map.get(ClassName, '')
import_handler._TryLoadStructFromStub(ClassName, self, source_sha1=_src_sha1 or None)
except Exception:
pass
struct_type = self.structs.get(ClassName, struct_type)
# 检测 stub body 中的 T* 占位类型(未特化的泛型类型参数)。
# 若 stub 使用了未特化的基类(如 GSListNode 而非 GSListNode[BasicBlock]),
# 清除 elements 让 set_body 用正确的 class_members 重新设置
# (字段名必须是公开属性 `elements`,见上方 Step 4 的说明)。
# 复用 _StructBodyHasPlaceholderT 的按 body 记忆结果(与上方 Step 4 同一判据)。
if member_types and struct_type.elements is not None:
if self._StructBodyHasPlaceholderT(struct_type):
struct_type.elements = None
if struct_type.elements is None:
# 防御性检查:member_types 为空时不调用 set_body。
# set_body(*[]) 会设置 elements=() 使 is_opaque=False,
# 后续无法再次 set_body(llvmlite 限制),导致跨模块类型永久损坏。
# 根因:register_method 在 class_members[ClassName]=[] 时立即调用
# _generate_structs,此时 member_types 为空。保持 opaque 状态,
# 让后续 _TryLoadStructFromStub 能正确设置 body。
if member_types:
struct_type.set_body(*member_types)
# ---- 冻结"成员名 → 槽位下标"映射(布局真源,挂在 struct 实例上)----
# struct body 一旦 set_body 即**永久冻结**,而 class_members 是**可变**
# 表(继承展平会在父类就绪后前置前缀、跨模块可按需重载/重跑),
# 两者序位会漂移。而字段访问用的是 `_get_member_offset` 的
# `base + 成员表下标`,漂移即"整类字段偏移平移"——现场:
# Converter 模块读 `Alias.name` 取到槽 9(= asname),`import t`
# 无 as 子句 ⇒ asname=NULL ⇒ vpi 生成报"import 别名 name 为空"。
# 这里把"建 body 那一刻"的真实槽位表记到 struct 实例上
# (用实例属性而非全局表:同一类名可能存在不止一个 struct 实例,
# 偏移必须与被 GEP 的那一份同源)。仅当 member_types 恰为
# `[vtable?] + 全部成员` 时记录(位域类折叠成单一 storage 槽,
# 其字段下标另由 class_member_bitoffsets 表达,不在此记录)。
_ExpectN: int = (1 if has_vtable else 0) + len(all_members)
if len(member_types) == _ExpectN:
_SlotMap: dict[str, int] = {}
_SlotIdx: int = 1 if has_vtable else 0
for _mnS, _ in all_members:
if _mnS not in _SlotMap:
_SlotMap[_mnS] = _SlotIdx
_SlotIdx += 1
struct_type._tpc_member_slots = _SlotMap
struct_type._tpc_member_base = 1 if has_vtable else 0
# 有效性锚点:本表只对"建表这一刻的 body"成立。body 之后可能被
# 清空重设(本文件上方预检的 `existing_st.elements = None`、
# HandlesImports._TryLoadStructFromStub 的 set_body),而挂在
# 实例上的属性不会随之更新 ⇒ 陈旧表会把字段指到别的槽(现场:
# AnnAssign 自有 4 字段时冻结 {'value': 3},body 重设为 12 槽后
# 'value' 仍读槽 3 = lineno(i32))。记下建表时的 **body 对象**
# (set_body 每次赋新 tuple),查询侧用 `is` 判同一性,O(1) 且无猜测。
struct_type._tpc_member_body = struct_type.elements
def _create_Vtable_globals(self) -> None:
# 「本模块有定义(非 declare)」的函数名所蕴含的全部**点分前缀**集合。
# 一次 O(函数数) 预计算替代原实现"每个候选类都遍历全部函数"的
# O(类数 × 函数数)(profile:tottime 75.7s / 1631 次调用 ≈ 46ms per call,
# 因为它对每个跨模块类都要把 self.functions 全扫一遍)。
# 判据等价:原 `func_name.startswith(f"{ClassName}.") 且非 declare` ⟺
# `f"{ClassName}."` 出现在某个非 declare 函数名的点分前缀集合里。
_local_prefixes: set[str] = set()
for _fn, _fo in self.functions.items():
if getattr(_fo, 'is_declaration', False):
continue
_i: int = _fn.find('.')
while _i != -1:
_local_prefixes.add(_fn[:_i + 1])
_i = _fn.find('.', _i + 1)
for ClassName, methods in self.class_methods.items():
if ClassName in self.Vtables:
continue
if not methods:
continue
if ClassName not in self.class_vtable and ClassName not in self._cross_module_vtable_classes:
continue
# 跳过正在发射中的类:它们的方法列表可能还不完整(如继承方法已添加但自有方法未添加),
# 此时创建 vtable 会导致大小不匹配。这些类的 vtable 会在其 _EmitClassLlvm 完成后
# 由 _create_vtable_for_class 显式创建,或在模块级 _create_Vtable_globals 调用中创建。
if ClassName in self._classes_in_progress:
continue
# 跨模块虚表过滤:_precollect_vtable_state 会将所有 includes 的有方法类同时加入
# _shared_class_vtable 和 _shared_cross_module_vtable,导致 class_vtable 与
# _cross_module_vtable_classes 内容相同,无法用以区分本模块类与跨模块类。
# _generate_structs 会为所有 class_methods 的类创建空 struct type,导致 self.structs
# 也包含所有类。过滤条件是 self.functions:只考虑本模块定义的函数(define,有函数体),
# 跳过跨模块的 declare(无函数体)。跨模块类的 declare 通过 HandlesImports 加入
# self.functions(短名如 "MemManager.alloc"),但其 is_declaration=True。
# 若只存在 declare 而无 define,说明本模块未定义该类,虚表应由定义模块创建。
# vtable linkage='internal',跨模块调用通过对象的 vtable 指针(首字段)完成分派,
# 不依赖本模块的 vtable 全局变量。
# 只考虑本模块定义的函数(define,有函数体);跨模块 declare 不算
# ⇒ _local_prefixes 已按 is_declaration 过滤,此处只查前缀集合。
if f"{ClassName}." not in _local_prefixes:
continue
VtableType: ir.ArrayType = ir.ArrayType(ir.PointerType(ir.IntType(8)), len(methods))
Vtable: ir.GlobalVariable = ir.GlobalVariable(self.module, VtableType, name=self._mangle_name(f"{ClassName}_Vtable"))
Vtable.initializer = ir.Constant(VtableType, [ir.Constant(ir.PointerType(ir.IntType(8)), None)] * len(methods))
# 保持默认 linkage(空字符串,带 initializer 自动打印为外部强定义
# global,llvmlite descr 规则:initializer 非空时 linkage 归一为空)。
# 2026-08-10:跨模块 isinstance 需引用 sha1.Class_Vtable 符号,
# vtable 名含模块 sha1 前缀全局唯一,外部可见不会冲突。
self.Vtables[ClassName] = Vtable
def _create_vtable_for_class(self, ClassName: str) -> None:
"""为单个类创建 vtable(用于 _EmitClassLlvm 中当前类的方法已全部注册后)。"""
methods: list = self.class_methods.get(ClassName, [])
if not methods:
return
if ClassName not in self.class_vtable and ClassName not in self._cross_module_vtable_classes:
return
_vtable_name: str = self._mangle_name(f"{ClassName}_Vtable")
# 如果虚表已在导入阶段创建(方法列表不完整,缺少继承方法),删除旧虚表
if ClassName in self.Vtables:
_old_vtable = self.Vtables[ClassName]
_old_name = _old_vtable.name
if _old_name in self.module.globals:
del self.module.globals[_old_name]
# llvmlite 的 NameScope._useset 追踪所有已使用的名字,
# del module.globals[name] 不会从 _useset 移除,
# 导致再次创建同名 GlobalVariable 时 scope.register() 报 DuplicatedNameError
self.module.scope._useset.discard(_old_name)
del self.Vtables[ClassName]
# 防御性:如果全局变量已存在但不在 Vtables 字典中,也删除
if _vtable_name in self.module.globals:
del self.module.globals[_vtable_name]
self.module.scope._useset.discard(_vtable_name)
VtableType: ir.ArrayType = ir.ArrayType(ir.PointerType(ir.IntType(8)), len(methods))
Vtable: ir.GlobalVariable = ir.GlobalVariable(self.module, VtableType, name=_vtable_name)
Vtable.initializer = ir.Constant(VtableType, [ir.Constant(ir.PointerType(ir.IntType(8)), None)] * len(methods))
# 默认 linkage(外部强定义),跨模块 isinstance 引用(与 _create_Vtable_globals 一致)
self.Vtables[ClassName] = Vtable
def _fix_global_var_init(self) -> None:
for gv_name, gv in list(self.module.globals.items()):
if not isinstance(gv, ir.GlobalVariable):
continue
if gv.linkage in ('external', 'extern_weak'):
continue
needs_fix: bool = False
if gv.initializer is None:
needs_fix = True
elif hasattr(gv.initializer, 'constant') and isinstance(gv.initializer.constant, _Undefined):
needs_fix = True
elif isinstance(gv.initializer, ir.Constant):
if isinstance(gv.initializer.constant, list):
if any(hasattr(c, 'constant') and isinstance(c.constant, _Undefined) for c in gv.initializer.constant):
needs_fix = True
if not needs_fix:
continue
gv.initializer = ir.Constant(gv.value_type, None)
if gv_name.startswith('str_const_') or gv_name.endswith('_Vtable') or gv_name.endswith('_str'):
gv.linkage = 'internal'
def _set_Vtable_initializers(self) -> None:
for ClassName, methods in self.class_methods.items():
if ClassName not in self.Vtables:
continue
if not methods:
continue
struct_type: ir.IdentifiedStructType | None = self.structs.get(ClassName)
if struct_type is None:
continue
Vtable: ir.GlobalVariable = self.Vtables[ClassName]
Vtable_entries: list[ir.Constant] = []
for MethodName in methods:
short_name: str = MethodName.split('.')[-1] if '.' in MethodName else MethodName
# 多继承:vtable 槽直接用完整方法名(实际定义类.方法,如 B.get_b)
func: ir.Function | None = None
if MethodName in self.functions:
func = self.functions[MethodName]
if func is None:
full_MethodName: str = f"{ClassName}.{short_name}"
if full_MethodName in self.functions:
func = self.functions[full_MethodName]
if func is None:
# 沿 MRO 祖先链查找定义类(先声明父类优先)
for anc in self._get_mro_chain(ClassName)[1:]:
parent_full: str = f"{anc}.{short_name}"
if parent_full in self.functions:
func = self.functions[parent_full]
break
if func is None:
Vtable_entries.append(ir.Constant(ir.PointerType(ir.IntType(8)), None))
continue
Vtable_entries.append(func.bitcast(ir.PointerType(ir.IntType(8))))
Vtable.initializer = ir.Constant(Vtable.type.pointee, Vtable_entries)