Files
TransPyC/lib/core/LLVMCG/StructGen.py
T
TermiNexus 07ea8e2fd9 fix(tpc): 布局校验/修复排除 REnum 镜像布局 + set_body 失败带回滚与上下文
a 编译器缺陷(承接 b8ed33a2 / 7fb0e498):REnum 的 struct body 是 **max-variant
镜像布局** —— class_members 里的名字是*变体*而非字段,变体嵌套结构体也必须逐字
镜像主结构体。此前两处按"声明类型 vs 槽类型"的校验/修复会把这类布局当成错位:
  - Step 4 前置校验可能据此清 _elements 重建;
  - _SyncStructBodyTypes 可能据此改写布局(跨模块 GEP 偏移随即与定义端失配)。
新增 _IsRenumLayoutClass(主 REnum 自身 / `<REnum>_<Variant>`,判据走 SymbolTable 的
IsRenum)并在两处排除。

另外:就地修复的 `_elements = None` + `set_body` 一旦被 llvmlite 拒绝
(`<name> is already defined`,现场 fd4cd0ec583f42b9.Generator),原来会抛出这句
毫无上下文的裸 RuntimeError。现改为:捕获后**直接写回原 _elements 回滚**,再抛
FailFastError 带上类名 / struct / 原异常 / 元素数前后值,既不留中间状态也不再含糊。
2026-09-29 11:34:17 +08:00

880 lines
51 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
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
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 _SyncStructBodyTypes(self, ClassName: str | None) -> None:
"""把 struct body 中与 `class_members` 声明**不兼容**的槽改回声明类型(就地修复)。
背景:llvmlite 的 `set_body` 只能设一次。若某个早期 pass 在 `class_members`
尚未被 .vpi / stub 修正、类型已降级(如 `i8**` 顶替 `%X*`)时就把 body 冻住,
错位会**永久留存** —— 后续重载只修 `class_members`,不动 body
(现场:Generator 第 3/11 槽;一次修好一槽、下一槽仍错,正是时序随机)。
Step 4 的前置校验只在**当次** `_generate_structs` 生效,漏"修正晚于冻结"的时序,
故在**使用点**(成员偏移查询,即任何 GEP 之前)再校一次并就地修复:
- 只替换**存储时必被拒绝**的槽(判据与存储侧同一套 `_MemberSlotAccepts`),
其余元素(含通用指针 i8* 槽)原样保留 —— 不做整表重建,避免丢 stub 侧信息;
- `_elements` 置空后 `set_body` 重设:命名类型对象不变,故已发出的
`getelementptr %"name"*` 不受影响。
LLVM 具名类型只有一份定义,重设 body 后打印即为修正后的布局,故在模块落盘前
修复是安全的;越早(首次字段访问)修复越干净。
"""
if not ClassName:
return
# REnum 布局(主 REnum 及其变体嵌套结构体)是 max-variant 镜像布局,声明成员
# 不是字段 —— 一律不参与本修复,避免改写其布局导致跨模块偏移失配。
if self._IsRenumLayoutClass(ClassName):
return
_st: Any = self.structs.get(ClassName)
if not isinstance(_st, ir.IdentifiedStructType) or _st.elements is None:
return
_members: list = self.class_members.get(ClassName) or []
if not _members:
return
_base: int = self._member_base(ClassName)
_elems: list = list(_st.elements)
_changed: bool = False
for _i, _item in enumerate(_members):
_j: int = _base + _i
if _j >= len(_elems):
break
_decl: Any = _item[1] if isinstance(_item, tuple) and len(_item) >= 2 else None
if _decl is not None and not self._MemberSlotAccepts(_decl, _elems[_j]):
_elems[_j] = _decl
_changed = True
if not _changed:
return
_backup: list = list(_st.elements)
_st._elements = None
try:
_st.set_body(*_elems)
except Exception as _e:
# 回滚到原布局(直接写回私有字段,不再经 set_body),再响亮失败:把 llvmlite
# 那句含糊的 "<name> is already defined" 变成带类名/元素数/原异常的错误,
# 且保证不留下被改坏的中间状态。
_st._elements = _backup
from lib.core.LLVMCG.FuncGen import FailFastError
raise FailFastError(
f"struct body 就地修复失败(类 '{ClassName}',struct='{_st}'):"
f"{type(_e).__name__}: {_e};元素数={len(_backup)} → 目标元素数={len(_elems)},"
f"已回滚原布局"
) from _e
def _generate_structs(self) -> 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 self.SymbolTable:
Entry = self.SymbolTable.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:
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:
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 self.SymbolTable:
Entry = self.SymbolTable.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 和 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 使用了未特化的基类,需清除重新生成。
_has_t_placeholder_pre: bool = False
for _elem_pre in existing_st.elements:
if isinstance(_elem_pre, ir.PointerType) and isinstance(_elem_pre.pointee, ir.IdentifiedStructType):
_pname_pre: str = _elem_pre.pointee.name
_short_pre: str = _pname_pre.rsplit('.', 1)[-1] if '.' in _pname_pre else _pname_pre
if _short_pre == 'T':
_has_t_placeholder_pre = True
break
# 逐元素类型一致性(治本):仅比"个数 / 首元素是 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
# struct body 不完整,清除 _elements 和 stub 缓存以便重新加载
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:
member_types.append(ir.PointerType(ir.IntType(8)))
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 重新设置。
if struct_type.elements is not None and member_types:
_has_t_placeholder_post: bool = False
for _elem_post in struct_type.elements:
if isinstance(_elem_post, ir.PointerType) and isinstance(_elem_post.pointee, ir.IdentifiedStructType):
_pname_post: str = _elem_post.pointee.name
_short_post: str = _pname_post.rsplit('.', 1)[-1] if '.' in _pname_post else _pname_post
if _short_post == 'T':
_has_t_placeholder_post = True
break
if _has_t_placeholder_post:
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)
def _create_Vtable_globals(self) -> None:
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 全局变量。
has_local_method: bool = False
prefix: str = f"{ClassName}."
for func_name, func_obj in self.functions.items():
if func_name.startswith(prefix):
# 跳过跨模块的 declare(无函数体),只考虑本模块定义的函数
if not getattr(func_obj, 'is_declaration', False):
has_local_method = True
break
if not has_local_method:
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)