Files
TransPyC/lib/core/Handles/HandlesImports.py
T
TermiNexus 3efe83f1ea fix(编译期名称解析): 修 StructGen 缺 _vlog 导入与 HandlesImports 的 node/Node 误用
两处均为 a(编译器缺陷),且都被 HandlesImports.py:1055 的
`except Exception -> WARN 忽略异常` 吞掉,表现为模块声明静默退化
(本次现场:VpiReader.vp 阶段 WARN + 结构体声明路径中断)。非源码违规、
非机制漏豁免。

1) lib/core/LLVMCG/StructGen.py
   - `_generate_structs` 的「成员表含重名条目、body 已是权威布局」提示调用
     `_vlog().warning(...)`,但本文件从未定义/导入 `_vlog` ⇒ NameError。
     该分支一旦命中(astaux.ExceptHandler 一类),`_generate_structs` 整体中断。
   - 补 `from lib.core.VLogger import get_logger as _vlog`,与同目录
     FuncGen.py / TypeConvert.py 同口径。

2) lib/core/Handles/HandlesImports.py:1317
   - `_EmitExternalGlobalDeclLlvm` 形参名为 `Node`,该 elif 分支误写成小写 `node`
     ⇒ 只要传入的是**非带注解**的跨模块全局量(`ast.Assign`)就恒抛 NameError,
     分支整体不可达:外部全局变量从未声明 external,消费侧表现为
     unknown global / 类型占位。
   - 判据改回 `isinstance(Node, ast.Assign)`。

静态核验:对 TransPyC 全部 .py 做 symtable 未定义名扫描,结果 0 条;
`python -m py_compile` 通过。
2026-09-30 14:55:40 +08:00

4032 lines
251 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 TYPE_CHECKING
if TYPE_CHECKING:
from lib.core.translator import Translator
from lib.core.Translator.LlvmGenerator import LlvmGeneratorMixin
import vast as ast
import inspect
import os
import re
import hashlib
import llvmlite.ir as ir
from lib.core.Handles.HandlesBase import BaseHandle, CTypeInfo, FuncMeta
from lib.core.VLogger import get_logger as _vlog
from lib.constants.config import mode as _config_mode
from lib.includes import t
from lib.includes.t import CTypeRegistry
from lib.core.SymbolUtils import ParseListAnnotation, ExtractTypeNameFromBinOp, AnnotationContainsName, FindStructNameInAnnotationWithContext
from lib.core.SymbolUtils import EnumClassIntWidth, ExpandSoftKeywords
from lib.core.SymbolUtils import ClassHasFuncLevelCVTable, IsVtableSlotMethod
# 注解里的存储类 / 限定符标记:这些名字**不是类名**(联合注解里与指针类混写,
# 如 `t.CExtern | rawptr[CompilerFactory]`),提取"指针指向的类"时必须滤掉。
_MARKER_ANNOT_NAMES: frozenset[str] = frozenset({
'CExtern', 'CExport', 'CStatic', 'CInline', 'CConst', 'CVolatile', 'CRegister',
'CAuto', 'CDefine', 'CTypedef', 'CPostdefinition', 'CPtr', 'CArrayPtr', 'CVoid',
'State', 'TLS',
})
# 标记基类名(不是真实父类):与 HandlesClassDef._EmitClassLlvm 的 base 跳过表同口径。
_MARKER_BASE_NAMES: frozenset[str] = frozenset({
'Object', 'CVTable', 'NoVTable', 'CStruct', 'CUnion', 'CEnum', 'REnum',
'Exception', 'Enum',
})
# LLVM 基础类型表(模块级常量):`_parse_llvm_type_impl` 原先把这份字典**每次调用现建**
# (profile 2026-09-29:本函数 1,689,809 次调用 ⇒ 字典字面量 1.69M 次、
# `IntType.__new__` 9.09M 次)。这些基础类型在 llvmlite 里无状态且 IntType 按宽度
# 缓存同一实例,跨调用/跨 Gen 共享安全 ⇒ 提升为模块常量,表意逐字不变。
_LLVM_PRIMITIVE_TYPE_MAP: dict[str, ir.Type] = {
'void': ir.VoidType(),
'i1': ir.IntType(1),
'i8': ir.IntType(8),
'i16': ir.IntType(16),
'i32': ir.IntType(32),
'i64': ir.IntType(64),
'float': ir.FloatType(),
'double': ir.DoubleType(),
}
def _HasRealParentClass(Node: ast.ClassDef) -> bool:
"""类是否声明了**真实父类**(排除 Object/CVTable/NoVTable/CEnum/... 标记基类)。"""
for base in getattr(Node, 'bases', None) or []:
base_name: str | None = None
if hasattr(base, 'id'):
base_name = base.id
elif hasattr(base, 'attr'):
base_name = base.attr
elif isinstance(base, ast.Subscript):
if hasattr(base.value, 'id'):
base_name = base.value.id
elif hasattr(base.value, 'attr'):
base_name = base.value.attr
if base_name and base_name not in _MARKER_BASE_NAMES:
return True
return False
def _VtableByClassLevelForImport(Node: ast.ClassDef, Gen: object) -> bool:
"""跨模块导入类的 VtableByClassLevel(忽略方法级 CVTable 标记的 vtable 状态)。
与 HandlesClassDef._resolve_class_flags 的口径一致:类级 @t.CVTable 或
"有真实父类且父类不是 NoVTable" ⇒ True。供 SymbolUtils.IsVtableSlotMethod
判定方法是否占虚表槽(唯一的判据源),使导入侧的虚表槽序与定义模块逐槽相同。
"""
if getattr(Node, 'decorator_list', None):
for decorator in Node.decorator_list:
if isinstance(decorator, ast.Attribute) and decorator.attr == 'CVTable':
return True
if isinstance(decorator, ast.Name) and decorator.id == 'CVTable':
return True
if not _HasRealParentClass(Node):
return False
_novtable_set: set = getattr(Gen, '_cross_module_novtable', None) or set()
for base in getattr(Node, 'bases', None) or []:
base_name: str | None = None
if hasattr(base, 'id'):
base_name = base.id
elif hasattr(base, 'attr'):
base_name = base.attr
if base_name and base_name not in _MARKER_BASE_NAMES:
if base_name in _novtable_set:
return False
return True
def _ParseFailureSite(Exc: BaseException) -> str:
"""解析异常 → `行[:列]` 文本;定位信息完全取不到时返回 `'?'`。
CPython/vast 的 SyntaxError 若按 (msg, (file, lineno, offset, text)) 构造则带
`.lineno`/`.offset`;而 TPC 自研 vast 的 Parser/ExprParser 只把行号写进报文
(`f"... at line {tok.Line}"`,见 lib/vast/Parser.py:826、ExprParser.py:640),
字段为空 ⇒ 必须正则兜底,否则诊断退化成"无行号",无法直接跳到失败行。
"""
Line: int = int(getattr(Exc, 'lineno', 0) or 0)
Col: int = int(getattr(Exc, 'offset', 0) or 0)
if not Line:
Matched: re.Match | None = re.search(r'at line (\d+)', str(Exc))
if Matched:
Line = int(Matched.group(1))
if not Line:
return '?'
return f"{Line}:{Col}" if Col else str(Line)
def _ReportDeclarationParseFailure(FilePath: str, Exc: BaseException, Consequence: str) -> None:
"""声明文件"读 → parse"失败的统一诊断口径(strict fail-fast / 非严格告警)。
诊断必带四要素:**文件路径**、**失败行[:列]**、**异常类型与文本**、**后果说明**
(后果由调用方按点位给出,如"该模块声明将全部缺失,后续可能表现为
`Undefined function`")。原实现(`_LoadModuleDeclarationsFromFile:364-367`)只在
strict 模式调 `Trans.LogWarning`——而 `Trans.Warnings` 全仓只被 append、无人消费
(BaseTranslator.py:55/137),**连 strict 模式都是无声的**;非严格模式更是直接
`return`。于是 `includes/ast/base.vp:168` 的 `enum ASTFlag:` 让整个 base.vp 的
声明(含顶层 `_init_ast`)一个都不注册,下游只报 `Undefined function: '_init_ast'`,
浪费了一轮排查。
fail-fast 依据:仓库有严格模式开关 `lib/constants/config.py:59 mode = "strict"`,
且既有多处同口径 `if _config_mode == "strict": raise`(本文件
`_RegisterSubModuleSha1:547`、`_build_stub_index:2443`、`_TryLoadStructFromStub:2539`
等)——本函数与它们一致,未放宽任何既有 fail-fast。
`FileNotFoundError` 例外:属"isfile 与 open 之间文件被删/被 `--clean` 清掉"的
竞态(先例见本文件 `_build_stub_index:2439-2441`),不是"源码解析失败",
故只告警、不触发 fail-fast。
"""
Site: str = _ParseFailureSite(Exc)
Message: str = (
f"声明解析失败 {FilePath}:{Site} — {type(Exc).__name__}: {Exc};{Consequence}"
)
if _config_mode == "strict" and not isinstance(Exc, FileNotFoundError):
raise ImportError(Message) from Exc
_vlog().warning(Message, "Exception")
def PrependInheritedBlock(Gen: object, ClassName: str, Block: list) -> bool:
"""把祖先字段块 `Block` 前置到 `Gen.class_members[ClassName]`(幂等,且**不删块内条目**)。
为什么单独成函数:这段判据是"继承段与 body 继承前缀一一对应"的唯一保障,必须可
独立验证(见本仓"按'表头是否已等于块'判幂等"的三条性质)。
判据与性质:
① **不删块内条目**。块里每一项都对应 body 的一个继承槽;只要块内某项被删,
被删项之后的继承字段的表内下标就会整体前移 k,而查询侧回退路径用
`base + 表内下标`(FuncGen._get_member_offset)⇒ 落到类型不同的槽。
旧实现按"名字已在整表内"过滤整块,正是这样删掉了**与子类自有字段重名**的
继承项(现场:TypeGenCType 重复声明 5 个父类已有字段后,继承字段
LastBuiltinsTypedefResult 下标 32→27,写进 i32 槽 → "指针隐式转整数已禁止")。
② **幂等**。本函数会被多个加载路径重复调用(_LoadModuleDeclarationsFromFile /
_LoadDeclarationsFromStubLlvm / _TryLoadClassMembersFromPyi 懒加载);判据是
"表头前 len(Block) 项的名字序列是否等于 Block 的名字序列"——相等即整块已前置,
直接返回 False(不再前置),避免 2026-08-10 那类重复字段
(BasicBlock: [Next, Next, Name, ...] → 与 struct elements 数量不匹配 → 偏移错乱)。
③ **同名并存**。子类自有字段与继承项同名时,两者都保留(定义端
HandlesClassDef 的 body 同样是"继承前缀 + 自有段",两侧槽数一致);
源码层面的这种重名已由 HandlesClassDef 的"字段名遮蔽 fail-fast"拦下,
此处只是不让它把布局判据带偏。
已知残留:若此前只前置过**部分**祖先块(父类表当时较短),本次会整块再前置一次,
该前缀出现重复条目。调用点上方刚对父类做过 force 重载(块内容稳定),故该情形仅在
"父类表被中途改写"时出现,且重复条目会在成员槽失败报文的"名序"里直接可见。
返回 True 表示本次真的前置了。
"""
_Members: list = getattr(Gen, 'class_members', {}).get(ClassName) or []
_BlockNames: list = [nm for nm, _ in Block]
if not _BlockNames:
return False
_HeadNames: list = [nm for nm, _ in _Members[:len(_BlockNames)]]
if _HeadNames == _BlockNames:
return False
getattr(Gen, 'class_members')[ClassName] = list(Block) + list(_Members)
return True
class ImportHandle(BaseHandle):
_struct_Load_cache = {}
_pyi_cache = {}
_pyi_cache_dir = None
_project_root_cache = None
# 目录 os.walk 结果缓存(import 失败兜底查找用):目录 -> [(root, files)]。
# 编译进程内 SearchDirsForSubmodule(includes/LibraryPaths/工程目录)内容不变,
# 避免每次 import 解析失败都全量递归扫描目录树(PROF:import 热路径高频调用)。
_walk_cache: dict[str, list[tuple[str, list[str]]]] = {}
# 类级 stub 索引:class_name -> list of (full_name, struct_body, file_sha1, from_output)
# 一次扫描所有 .stub.ll 文件构建索引,避免 _TryLoadStructFromStub 每次调用都全量扫描
# (原 609 次调用 × ~100 stub 文件 × ~50 行 = 5.5M 正则匹配 → 一次扫描 ~5000 匹配)
_stub_index: dict[str, list[tuple[str, str, str, bool]]] = {}
# 已解析进索引的 stub 文件绝对路径集合(增量重建用)。
# 单次编译进程内 stub 只增不减(output 逐步填充),记录已扫描文件后,
# 签名变化只需增量解析新增文件,避免每编译完一个文件就全量重扫(原 847 次全量重建)。
_stub_index_scanned: set[str] = set()
# 类级缓存:跨 worker 任务共享(同一进程内,worker 单线程无竞争)。
# 键均为 (source_sha1, class_name),不同文件同名类(不同 sha1)不冲突。
# classify:_EmitExternalClassDeclLlvm 开头的 bases/decorator_list 分类遍历结果
# (与 Gen 无关的纯函数计算,同一 (sha1, class) 跨任务重复调用时跳过遍历)。
# decl:pyi 树中的 ClassDef 节点引用(pyi 树索引,pyi 树由 _pyi_cache 类级缓存
# 保证进程内唯一),避免 _TryLoadClassMembersFromPyi 跨任务反复线性扫描 body。
_extern_class_classify_cache: dict[tuple[str, str], tuple] = {}
_extern_class_decl_cache: dict[tuple[str, str], ast.ClassDef] = {}
# 类级 stub 函数签名缓存(_LookupStubFuncType):跨 worker 任务共享。
# 与 _stub_index/_stub_index_scanned 同模式:签名变化只增量解析新增 stub
# 文件(编译进程内 stub 只增不改),避免每次任务全量重扫 + 每文件
# os.path.isfile(Windows stat ~0.5ms/次,原 5840 次 ≈ 6.5s)。
_stub_func_cache: dict[str, tuple[str, str, bool, str]] = {}
_stub_func_cache_signature: tuple[int, int] | None = None
_stub_func_scanned: set[str] = set()
# stub 文件集合自上次建索引以来是否**可能**变化(写 *.stub.ll 的一方显式置脏)。
# 见 MarkStubSetDirty / _build_stub_index 的零 syscall 快路径:原实现每次
# _TryLoadStructFromStub 索引 miss 都要 2× os.path.isdir + 2× os.listdir 重新
# 拼文件集合(profile:_build_stub_index 被调 238k 次;nt._path_isdir 491s +
# nt.listdir 183s ≈ 总时长 30%),而"集合是否变化"完全可以由写入方免费告知。
_stub_set_dirty: bool = True
# 目录 → (listdir 时的目录 mtime_ns, 该次统计出的 *.stub.ll 数量)。
# 键为目录绝对路径:temp/ 与 output/ 分别计缓存。见 _CountStubFilesInDir。
_stub_dir_count_cache: dict[str, tuple[int, int]] = {}
@classmethod
def MarkStubSetDirty(cls) -> None:
"""标记「stub 文件集合可能已变化」——**所有写 *.stub.ll 的一方都必须调用**。
调用点多在 Phase2Translator:每个文件翻译入口(跨文件新增一律可见)、
写 temp/<sha1>.stub.ll 之后、注册 include 写 output/<sha1>.stub.ll 之后。
"""
ImportHandle._stub_set_dirty = True
def __init__(self, translator: "Translator") -> None:
super().__init__(translator)
# 实例级去重集合:每个 translator 独立跟踪已加载的 .pyi 和已发射的模块声明。
# 若作为类变量共享,main.py 编译时加载的 __nodes.py 会阻止 __parser.py 编译时
# 重新加载,导致函数未注册到 __parser.py 的 Gen.functions(NamedExpr bug 根因)。
self._loaded_pyi_paths: set[str] = set()
self._emitted_module_decls: set[tuple] = set()
# `_parse_llvm_type` 的结果 memo(键 = (类型串, source_sha1, create_structs))。
# 只缓存**与 Gen 状态无关**的结果,见 `_parse_llvm_type` 的判据;因此跨文件
# 复用是安全的(i8* 之类纯字面类型与哪个 Gen 无关),不随 `_reset_file_cache` 清空。
self._llvm_type_memo: dict[tuple, ir.Type] = {}
def _reset_file_cache(self) -> None:
self._struct_Load_cache.clear()
self._loaded_pyi_paths.clear()
self._emitted_module_decls.clear()
# stub 索引在文件间保留(同一 worker 进程内),但标记需检查是否过期
# 不清除 _stub_index / _stub_index_scanned,通过增量扫描校验新鲜度
def _find_project_root(self) -> str:
if self._project_root_cache is not None:
return self._project_root_cache
# 优先使用 translator 上显式设置的 _temp_dir(由 Phase2Translator._translate_file 传入),
# 这样可以正确处理 include 文件——其源文件目录不在项目根目录下,
# 从源文件目录向上搜索 temp/output 会失败并回退到错误的路径。
temp_dir_attr = getattr(self.Trans, '_temp_dir', None)
if temp_dir_attr and os.path.isdir(temp_dir_attr):
project_root = os.path.dirname(os.path.abspath(temp_dir_attr))
if os.path.isdir(os.path.join(project_root, 'temp')) and os.path.isdir(os.path.join(project_root, 'output')):
self._project_root_cache = project_root
return project_root
current_file = getattr(self.Trans, 'CurrentFile', '') or ''
if current_file:
current_dir = os.path.dirname(os.path.abspath(current_file))
search_dir = current_dir
for _ in range(10):
if os.path.isdir(os.path.join(search_dir, 'temp')) and os.path.isdir(os.path.join(search_dir, 'output')):
self._project_root_cache = search_dir
return search_dir
parent = os.path.dirname(search_dir)
if parent == search_dir:
break
search_dir = parent
# 回退到当前工作目录(编译运行时的项目根目录)
cwd = os.getcwd()
if os.path.isdir(os.path.join(cwd, 'temp')) and os.path.isdir(os.path.join(cwd, 'output')):
self._project_root_cache = cwd
return cwd
if current_file:
self._project_root_cache = current_dir
return current_dir
self._project_root_cache = cwd
return cwd
def _EmitImportDeclarationsLlvm(self, Node: ast.Import, Gen: LlvmGeneratorMixin) -> None:
if not getattr(self.Trans, '_ImportedModules', None):
self.Trans._ImportedModules = set()
for alias in Node.names:
name = alias.name
if name in ('c', 't'):
if alias.asname:
self.Trans.SymbolTable.import_aliases[alias.asname] = name
AliasInfo = CTypeInfo()
AliasInfo.IsModuleAlias = True
AliasInfo.ResolvedModule = name
self.Trans.SymbolTable.insert(alias.asname, AliasInfo)
continue
self.Trans._ImportedModules.add(name)
if alias.asname:
self.Trans.SymbolTable.import_aliases[alias.asname] = name
# 同时在符号表中注册模块别名,以便 CTypeInfo.FromNode 能解析
AliasInfo = CTypeInfo()
AliasInfo.IsModuleAlias = True
AliasInfo.ResolvedModule = name
self.Trans.SymbolTable.insert(alias.asname, AliasInfo)
current_module = self._get_current_module_name()
self._EmitModuleDeclarationsLlvm(name, Gen, register_module_name=current_module)
def _RegisterFromImportAliases(self, Node: ast.ImportFrom, Gen: LlvmGeneratorMixin, module: str) -> None:
"""Register 'from module import y as z' aliases after module declarations are Loaded"""
if not Node.names:
return
for alias in Node.names:
name = alias.name
asname = alias.asname
# For 'from X import Y' (no asname), register CDefine constants
# into _define_constants so they can be found by _HandleNameLlvm
if not asname or asname == name:
define_constants = vars(Gen).setdefault('_define_constants', {})
# Check if this name is a CDefine constant in SymbolTable
sym_info = self.Trans.SymbolTable.lookup(name)
if sym_info and sym_info.IsDefine and sym_info.DefineValue is not None:
if name not in define_constants:
define_constants[name] = sym_info.DefineValue
# Also check with module prefix
for prefix_key in [f"{module}.{name}", name]:
sym_info2 = self.Trans.SymbolTable.lookup(prefix_key)
if sym_info2 and sym_info2.IsDefine and sym_info2.DefineValue is not None:
if name not in define_constants:
define_constants[name] = sym_info2.DefineValue
break
# 治本修复:如果符号表中没有,回退查询 _all_define_constants
# _all_define_constants 由 Phase2Translator 从所有 .pyi 文件预提取,
# 包含跨模块导入的 CDefine 常量(如 FOREGROUND_GREEN、STD_OUTPUT_HANDLE)
if name not in define_constants:
all_dc: dict = getattr(Gen, '_all_define_constants', None) or getattr(self.Trans, '_all_define_constants', None) or {}
if name in all_dc:
define_constants[name] = all_dc[name]
continue
if not hasattr(self.Trans, '_t_c_imported_names'):
self.Trans._t_c_imported_names = {}
self.Trans._t_c_imported_names[asname] = (module, name)
if name in Gen.functions:
Gen.functions[asname] = Gen.functions[name]
if name in Gen.structs:
Gen.structs[asname] = Gen.structs[name]
info = self.Trans.SymbolTable.lookup(name)
if info:
self.Trans.SymbolTable.insert(asname, info)
if name in Gen.variables:
Gen.variables[asname] = Gen.variables[name]
if name in Gen.global_vars:
Gen.global_vars[asname] = Gen.global_vars[name]
for func_name in list(Gen.functions.keys()):
if func_name.startswith(f'{name}.'):
method_suffix = func_name[len(name):]
Gen.functions[f'{asname}{method_suffix}'] = Gen.functions[func_name]
for key in list(self.Trans.SymbolTable.keys()):
if key.startswith(f'{name}.'):
suffix = key[len(name):]
self.Trans.SymbolTable.insert(f'{asname}{suffix}', self.Trans.SymbolTable[key])
for meta_dict in (Gen.class_members, Gen.class_member_defaults,
Gen.class_member_signeds, Gen.class_member_bitfields,
Gen.class_member_byteorders, Gen.class_member_bitoffsets,
Gen.class_methods):
if name in meta_dict:
meta_dict[asname] = meta_dict[name]
if name in Gen.class_vtable:
Gen.class_vtable.add(asname)
if name in Gen.class_packed:
Gen.class_packed.add(asname)
struct_sha1_map = getattr(Gen, '_struct_sha1_map', {})
if name in struct_sha1_map:
struct_sha1_map[asname] = struct_sha1_map[name]
def _EmitImportFromDeclarationsLlvm(self, Node: ast.ImportFrom, Gen: LlvmGeneratorMixin) -> None:
module = Node.module if Node.module else ''
if module in ('c', 't'):
if not getattr(self.Trans, '_ImportedModules', None):
self.Trans._ImportedModules = set()
self.Trans._ImportedModules.add(module)
if not hasattr(self.Trans, '_t_c_imported_names'):
self.Trans._t_c_imported_names = {}
# 注册 t/c 类型别名到 _t_type_symbols(CType 子类)
t_type_syms = self.Trans.SymbolTable._t_type_symbols
for alias in Node.names:
name = alias.name
asname = alias.asname or name
self.Trans._t_c_imported_names[asname] = (module, name)
# 仅注册 CType 子类(CEnum/CUnion/CStruct/REnum 等)
if module == 't':
cls = getattr(t, name, None)
if isinstance(cls, type) and issubclass(cls, t.CType):
t_type_syms[asname] = cls
return
if not getattr(self.Trans, '_ImportedModules', None):
self.Trans._ImportedModules = set()
current_module = self._get_current_module_name()
if Node.level and Node.level > 0:
current_file = getattr(self.Trans, 'CurrentFile', '') or ''
current_dir = os.path.dirname(os.path.abspath(current_file))
parent_dir = current_dir
for _ in range(Node.level - 1):
parent_dir = os.path.dirname(parent_dir)
if module:
SearchExtensions = ['.vpi', '.vp', '.pyi', '.py']
sub_path_base = os.path.join(parent_dir, module)
for ext in SearchExtensions:
candidate = sub_path_base + ext
if os.path.isfile(candidate):
self.Trans._ImportedModules.add(module)
self._LoadModuleDeclarationsFromFile(candidate, Gen, module_name=module, register_module_name=current_module)
self._RegisterFromImportAliases(Node, Gen, module)
return
ModuleSha1Map = getattr(Gen, 'ModuleSha1Map', {})
# 尝试完整模块名和短模块名
full_mod_name = f"{current_module}.{module}" if current_module else module
target_sha1 = ModuleSha1Map.get(full_mod_name) or ModuleSha1Map.get(module)
if target_sha1:
temp_dir = getattr(Gen, '_temp_dir', None)
if temp_dir:
sha1_pyi = os.path.join(temp_dir, f"{target_sha1}.vpi")
if os.path.isfile(sha1_pyi):
self.Trans._ImportedModules.add(module)
self._LoadModuleDeclarationsFromFile(sha1_pyi, Gen, module_name=module, register_module_name=current_module)
self._RegisterFromImportAliases(Node, Gen, module)
return
else:
mod_dir = parent_dir
SearchExtensions = ['.vpi', '.vp', '.pyi', '.py']
pkg_name = ''
if not module:
# When 'from . import name' is used, Load the package's __init__
# to make package-level names (functions, classes, constants) available
pkg_name = self._LoadPackageInitForRelativeImport(mod_dir, Gen, current_module)
# Also try to Load each alias as a submodule
for alias in Node.names:
found_sub = False
for ext in SearchExtensions:
sub_path = os.path.join(mod_dir, alias.name + ext)
if os.path.isfile(sub_path):
self.Trans._ImportedModules.add(alias.name)
self._LoadModuleDeclarationsFromFile(sub_path, Gen, module_name=alias.name, register_module_name=current_module)
if alias.asname:
self.Trans.SymbolTable.import_aliases[alias.asname] = alias.name
found_sub = True
break
if not found_sub:
# SHA1 map fallback for submodule
ModuleSha1Map = getattr(Gen, 'ModuleSha1Map', {})
sub_ModulePath = f"{pkg_name}.{alias.name}" if pkg_name else alias.name
target_sha1 = ModuleSha1Map.get(sub_ModulePath) or ModuleSha1Map.get(alias.name)
if target_sha1:
temp_dir = getattr(Gen, '_temp_dir', None)
if temp_dir:
sha1_pyi = os.path.join(temp_dir, f"{target_sha1}.vpi")
if os.path.isfile(sha1_pyi):
self.Trans._ImportedModules.add(sub_ModulePath)
self._LoadModuleDeclarationsFromFile(sha1_pyi, Gen, module_name=alias.name, register_module_name=current_module)
if alias.asname:
self.Trans.SymbolTable.import_aliases[alias.asname] = alias.name
self._RegisterFromImportAliases(Node, Gen, module or pkg_name)
return
self.Trans._ImportedModules.add(module)
self._EmitModuleDeclarationsLlvm(module, Gen, register_module_name=current_module)
self._RegisterFromImportAliases(Node, Gen, module)
def _RegisterCrossModuleDocstrings(self, pyi_path: str, Gen: LlvmGeneratorMixin, module_prefix: str | None) -> None:
"""从 {sha1}.doc.json 加载跨模块 docstring 注册到 Gen._doc_strings。
- 长名 {module_prefix}.{symbol} 总是注册
- 短名 {symbol} 只在不存在时注册(避免覆盖本模块的)
- slice_level != 0(优化开启)时注册为 None
- 跨模块 docstring 在 _get_doc_ptr 访问时创建本地常量副本
"""
if not module_prefix:
return
basename: str = os.path.basename(pyi_path)
# 若传入 .vp/.py 文件,则从 _source_module_sig_files 查找对应的 {sha1}.vpi
if basename.endswith('.vp') or basename.endswith('.py'):
source_sig_files = getattr(self.Trans, '_source_module_sig_files', None)
if source_sig_files:
sig_path = source_sig_files.get(module_prefix)
if sig_path and os.path.isfile(sig_path):
pyi_path = sig_path
basename = os.path.basename(pyi_path)
else:
return
else:
return
if not (basename.endswith('.vpi') or basename.endswith('.pyi')):
return
sha1: str = basename[:-4] # 去掉 .vpi 或 .pyi 后缀(均为 4 字符)
shared_doc_meta = getattr(self.Trans, '_shared_doc_meta', None)
if not shared_doc_meta:
return
docs: dict[str, str] | None = shared_doc_meta.get(sha1)
if not docs:
return
trans = getattr(Gen, '_Trans', None)
slice_level: int = getattr(trans, 'SliceLevel', 3)
optimized: bool = slice_level != 0
for symbol, doc in docs.items():
long_key: str = f"{module_prefix}.{symbol}"
if optimized:
Gen._doc_strings[long_key] = None
if symbol not in Gen._doc_strings:
Gen._doc_strings[symbol] = None
else:
Gen._doc_strings[long_key] = doc
if symbol not in Gen._doc_strings:
Gen._doc_strings[symbol] = doc
def _LoadModuleDeclarationsFromFile(self, pyi_path: str, Gen: LlvmGeneratorMixin, module_name: str | None = None, register_module_name: str | None = None, actual_module_name: str | None = None, reexport_package: str | None = None) -> None:
if not pyi_path or not os.path.isfile(pyi_path):
return
# 去重:同一 .pyi 文件只加载一次(菱形依赖下避免重复 read+parse)
abs_pyi = os.path.abspath(pyi_path)
if abs_pyi in self._loaded_pyi_paths:
return
self._loaded_pyi_paths.add(abs_pyi)
# 注册跨模块 __doc__ docstring 到 Gen._doc_strings
self._RegisterCrossModuleDocstrings(pyi_path, Gen, register_module_name or module_name)
try:
with open(pyi_path, 'r', encoding='utf-8') as f:
ModuleCode = f.read()
# Viper 软关键字层(与 TPC 其余前端入口同一层):`.vp` 源码可含
# `enum X:` / `fn` 等语法糖,直接 vast.parse 会在糖行 SyntaxError →
# 被下面的 except 吞掉并 return → **该模块的全部声明(函数/类/全局)
# 一个都不注册**,调用方随后报 "Undefined function"
# (includes/ast/base.vp 的 `enum ASTFlag:` → 六处 `from .base import …`
# 全部拿不到 `_init_ast`,即本次现场)。`.vpi`/`.pyi` 无糖,展开是空操作。
ModuleTree = ast.parse(ExpandSoftKeywords(ModuleCode), pyi_path)
except Exception as _e:
# 统一诊断口径(strict fail-fast / 非严格告警),必带文件+行号+异常+后果。
# 原实现"吞异常 + return"(strict 也只写进无人消费的 Trans.Warnings),
# 是本次 `Undefined function: '_init_ast'` 被伪装成下游症状的直接原因。
_ReportDeclarationParseFailure(
pyi_path, _e,
"该模块的全部声明(函数/类/全局/枚举)不会注册,"
"后续引用方可能表现为 Undefined function / Undefined struct")
return
# 计算源模块名(从 .pyi 文件路径推导)。
# 治本修复:module_name 参数可能来自 alias.name(如 `from stmts import Module`
# 中的 'Module' 是类名而非模块名),直接传给 _EmitExternalClassDeclLlvm 会导致
# ModuleSha1Map 查找失败 → _self_struct_sha1=None → 使用错误的同名 struct。
# 使用文件名推导的模块名(如 'stmts')作为 source_module_name 才是正确的。
# 对于 sha1 命名的 .pyi 文件(如 e64295c03b564a53.pyi),文件名是 sha1 而非
# 模块名,需回退到 actual_module_name 或 module_name。
file_module_name = os.path.splitext(os.path.basename(pyi_path))[0]
if len(file_module_name) == 16 and all(_c in '0123456789abcdef' for _c in file_module_name):
file_module_name = actual_module_name or module_name or file_module_name
for node in ModuleTree.body:
try:
if isinstance(node, ast.FunctionDef):
if node.name in ('va_start', 'va_arg', 'va_end'):
continue
effective_register = register_module_name or module_name
reexport_names = []
if module_name and module_name != file_module_name and module_name != effective_register:
reexport_names.append(module_name)
# When Loading a submodule for 'from .xxx import yyy' inside a package's __init__.py,
# re-export functions under the package name so 'pkg.func()' resolves correctly
if reexport_package and reexport_package != file_module_name and reexport_package not in reexport_names:
reexport_names.append(reexport_package)
self._EmitExternalFuncDeclLlvm(node, Gen, source_module_name=file_module_name, register_module_name=effective_register, reexport_module_names=reexport_names if reexport_names else None)
elif isinstance(node, ast.AnnAssign):
self._EmitExternalGlobalDeclLlvm(node, Gen, module_name=module_name, ModulePath=pyi_path)
elif isinstance(node, ast.Assign):
self._EmitExternalGlobalDeclLlvm(node, Gen, module_name=module_name, ModulePath=pyi_path)
elif isinstance(node, ast.ClassDef):
if hasattr(node, 'type_params') and node.type_params:
if not hasattr(self.Trans.ClassHandler, '_generic_class_templates'):
self.Trans.ClassHandler._generic_class_templates = {}
type_params = [tp.name for tp in node.type_params]
# 保留 prescan 阶段已注册的 sha1;若无则从 _ModuleSha1Map 查找,
# 避免覆盖带 sha1 的注册导致特化时 SHA1 前缀丢失
existing = self.Trans.ClassHandler._generic_class_templates.get(node.name)
template_sha1 = existing.get('sha1') if existing else None
if template_sha1 is None:
msm = getattr(self.Trans, '_ModuleSha1Map', None)
if msm:
for lookup_name in (file_module_name, module_name, actual_module_name):
if lookup_name and lookup_name in msm:
template_sha1 = msm[lookup_name]
break
self.Trans.ClassHandler._generic_class_templates[node.name] = {
'node': node,
'type_params': type_params,
'sha1': template_sha1,
}
# 治本修复:使用 file_module_name(源模块名)而非 module_name(可能是别名/类名)。
# 这样 _EmitExternalClassDeclLlvm 内的 ModuleSha1Map 查找才能命中,
# _EmitExternalFuncDeclLlvm 才能拿到正确的 source_module_name → 正确的 struct sha1。
# 注意:actual_module_name 保持原值(可能为 None),不回退到 module_name(可能是别名)
self._EmitExternalClassDeclLlvm(node, Gen, module_name=file_module_name, actual_module_name=actual_module_name)
elif isinstance(node, ast.ImportFrom):
if node.level > 0:
current_dir = os.path.dirname(os.path.abspath(pyi_path))
parent_dir = current_dir
for _ in range(node.level - 1):
parent_dir = os.path.dirname(parent_dir)
if node.module:
SearchExtensions = ['.vpi', '.vp', '.pyi', '.py']
sub_path_base = os.path.join(parent_dir, node.module)
found_sub = False
# 治本修复:用包目录名(如 'json')而非 register_module_name(当前编译模块名,如 '_dict')。
# 当 _dict.py 导入 json 包,递归处理 json/__init__.py 中的 `from .__parser import parse` 时,
# register_module_name='_dict' 会导致 actual_module='_dict.__parser',
# 使 _mangle_func_name 查找失败回退到 self.module_sha1,生成错误的 mangled name。
# 包名推导优先级:
# 1. module_name(如果它在 ModuleSha1Map 中,说明是已注册的包名,如 'json')
# — 处理 sha1 命名的 .pyi 文件(parent_dir 是 temp 目录时)
# 2. os.path.basename(parent_dir)(原始路径推导,如 'json')
if module_name and module_name in Gen.ModuleSha1Map:
pkg_basename = module_name
else:
pkg_basename = os.path.basename(parent_dir)
for ext in SearchExtensions:
candidate = sub_path_base + ext
if os.path.isfile(candidate):
actual_module = f"{pkg_basename}.{node.module}" if pkg_basename else node.module
self._RegisterSubModuleSha1(candidate, actual_module, node.module, Gen)
if not getattr(self.Trans, '_ImportedModules', None):
self.Trans._ImportedModules = set()
self.Trans._ImportedModules.add(actual_module)
for alias in node.names:
if alias.name == '*':
self._LoadModuleDeclarationsFromFile(candidate, Gen, module_name=module_name, register_module_name=register_module_name, reexport_package=reexport_package or module_name)
else:
self._LoadModuleDeclarationsFromFile(candidate, Gen, module_name=alias.name, register_module_name=register_module_name, actual_module_name=actual_module, reexport_package=reexport_package or module_name)
found_sub = True
break
# SHA1 map 回退
if not found_sub:
actual_module = f"{pkg_basename}.{node.module}" if pkg_basename else node.module
ModuleSha1Map = getattr(Gen, 'ModuleSha1Map', {})
target_sha1 = ModuleSha1Map.get(actual_module) or ModuleSha1Map.get(node.module)
if target_sha1:
temp_dir = getattr(Gen, '_temp_dir', None)
if temp_dir:
sha1_pyi = os.path.join(temp_dir, f"{target_sha1}.vpi")
if os.path.isfile(sha1_pyi):
if not getattr(self.Trans, '_ImportedModules', None):
self.Trans._ImportedModules = set()
self.Trans._ImportedModules.add(actual_module)
for alias in node.names:
if alias.name == '*':
self._LoadModuleDeclarationsFromFile(sha1_pyi, Gen, module_name=module_name, register_module_name=register_module_name, reexport_package=reexport_package or module_name)
else:
self._LoadModuleDeclarationsFromFile(sha1_pyi, Gen, module_name=alias.name, register_module_name=register_module_name, actual_module_name=actual_module, reexport_package=reexport_package or module_name)
else:
mod_dir = parent_dir
SearchExtensions = ['.vpi', '.vp', '.pyi', '.py']
# Load __init__.py to make package-level names available
self._LoadPackageInitForRelativeImport(mod_dir, Gen, register_module_name)
# 治本修复:用包目录名(如 'json')而非 register_module_name(当前编译模块名,如 '_dict')。
# 同 if node.module 分支的修复逻辑。
if module_name and module_name in Gen.ModuleSha1Map:
pkg_basename = module_name
else:
pkg_basename = os.path.basename(mod_dir)
for alias in node.names:
found_alias = False
for ext in SearchExtensions:
sub_path = os.path.join(mod_dir, alias.name + ext)
if os.path.isfile(sub_path):
sub_ModulePath = f"{pkg_basename}.{alias.name}" if pkg_basename else alias.name
if not getattr(self.Trans, '_ImportedModules', None):
self.Trans._ImportedModules = set()
self.Trans._ImportedModules.add(sub_ModulePath)
if alias.name == '*':
self._LoadModuleDeclarationsFromFile(sub_path, Gen, module_name=module_name, register_module_name=register_module_name, reexport_package=reexport_package or module_name)
else:
self._LoadModuleDeclarationsFromFile(sub_path, Gen, module_name=alias.name, register_module_name=register_module_name, reexport_package=reexport_package or module_name)
found_alias = True
break
# SHA1 map 回退
if not found_alias:
sub_ModulePath = f"{pkg_basename}.{alias.name}" if pkg_basename else alias.name
ModuleSha1Map = getattr(Gen, 'ModuleSha1Map', {})
target_sha1 = ModuleSha1Map.get(sub_ModulePath) or ModuleSha1Map.get(alias.name)
if target_sha1:
temp_dir = getattr(Gen, '_temp_dir', None)
if temp_dir:
sha1_pyi = os.path.join(temp_dir, f"{target_sha1}.vpi")
if os.path.isfile(sha1_pyi):
if not getattr(self.Trans, '_ImportedModules', None):
self.Trans._ImportedModules = set()
self.Trans._ImportedModules.add(sub_ModulePath)
if alias.name == '*':
self._LoadModuleDeclarationsFromFile(sha1_pyi, Gen, module_name=module_name, register_module_name=register_module_name, reexport_package=reexport_package or module_name)
else:
self._LoadModuleDeclarationsFromFile(sha1_pyi, Gen, module_name=alias.name, register_module_name=register_module_name, reexport_package=reexport_package or module_name)
except Exception as _e:
from lib.core.LLVMCG.FuncGen import FailFastError as _FFE
if isinstance(_e, _FFE):
# fail-fast 一律上抛(本仓既定口径:FailFastError 不被吞,见
# HandlesBase.py 的说明)。原实现把它与其它异常一起吞成一行
# "忽略节点 … 异常" WARN 并 continue ⇒ 该节点(如某个 ClassDef 的
# 全部方法声明)被整条静默丢弃,创建中途失败的**半注册名字**留到
# 下一轮才以"函数声明名字悬空占用"暴露,真因与现场彻底脱节
# (现场:50741ad4b4b0934d.LLVMTypeCore.__init__)。
raise
_vlog().warning(
f"HandlesImports[_LoadModuleDeclarationsFromFile]: 忽略节点 "
f"{type(node).__name__} {getattr(node, 'name', '?')} 异常: {_e}",
exc_info=_e
)
continue
def _get_current_module_name(self) -> str | None:
current_file = getattr(self.Trans, 'CurrentFile', '') or ''
if current_file:
return os.path.splitext(os.path.basename(current_file))[0]
return None
def _RegisterSubModuleSha1(self, candidate_path: str, actual_module: str, short_module: str, Gen: LlvmGeneratorMixin) -> None:
"""Register a submodule's SHA1 in ModuleSha1Map so cross-module class references work"""
try:
with open(candidate_path, 'r', encoding='utf-8') as f:
sub_content = f.read()
sub_sha1 = hashlib.sha1(sub_content.encode('utf-8')).hexdigest()[:16]
if hasattr(Gen, 'ModuleSha1Map'):
Gen.ModuleSha1Map[actual_module] = sub_sha1
if short_module and short_module not in Gen.ModuleSha1Map:
Gen.ModuleSha1Map[short_module] = sub_sha1
except Exception as _e:
if _config_mode == "strict":
raise
_vlog().warning(f"处理导入失败: {_e}", "Exception")
def _LoadPackageInitForRelativeImport(self, mod_dir: str, Gen: LlvmGeneratorMixin, register_module_name: str | None) -> str:
"""Load __init__.py from the package directory for 'from . import name' resolution.
This makes package-level names (functions, classes, constants) available.
Returns the package name."""
pkg_name = os.path.basename(mod_dir)
init_Loaded = False
# 跳过当前正在编译的文件,避免重复加载导致 class_members 重复
current_sha1 = getattr(Gen, 'module_sha1', None)
ModuleSha1Map = getattr(Gen, 'ModuleSha1Map', {})
pkg_sha1 = ModuleSha1Map.get(pkg_name)
if pkg_sha1 and pkg_sha1 == current_sha1:
return pkg_name
# Try SHA1 map first (correct mangled names from stub)
if pkg_sha1:
temp_dir = getattr(Gen, '_temp_dir', None)
if temp_dir:
sha1_pyi = os.path.join(temp_dir, f"{pkg_sha1}.vpi")
if os.path.isfile(sha1_pyi):
self._LoadModuleDeclarationsFromFile(sha1_pyi, Gen, module_name=pkg_name, register_module_name=register_module_name)
init_Loaded = True
# Fallback: try Loading __init__.py directly from the package directory
if not init_Loaded:
for ext in ['.vpi', '.vp', '.pyi', '.py']:
init_path = os.path.join(mod_dir, '__init__' + ext)
if os.path.isfile(init_path):
self._LoadModuleDeclarationsFromFile(init_path, Gen, module_name=pkg_name, register_module_name=register_module_name)
init_Loaded = True
break
return pkg_name
def _EmitModuleDeclarationsLlvm(self, module_name: str, Gen: LlvmGeneratorMixin, register_module_name: str | None = None) -> None:
# 去重:同一模块声明只发射一次(避免多个 import 同一模块时重复加载)
decl_key = (module_name, register_module_name)
if decl_key in self._emitted_module_decls:
return
self._emitted_module_decls.add(decl_key)
if module_name in ('pyzlib',):
source_sig_files = getattr(self.Trans, '_source_module_sig_files', None)
ModulePathBase = module_name.replace('.', os.sep)
PackageInitPathBase = module_name.replace('.', os.sep) + os.sep + '__init__'
ProjectRoot = os.path.dirname(os.path.abspath(getattr(self.Trans, 'CurrentFile', '') or '')) or os.getcwd()
HandlesFile = os.path.abspath(inspect.getfile(self.__class__))
TransPyCRoot = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(HandlesFile))))
FullModulePath = None
# includes 只认项目(project.vpj includes → LibraryPaths)目录,禁止回退仓库硬编码
# TransPyCRoot/includes:仓库副本缺失时回退会造成声明解析时序错乱(memhub 未注册→i32)。
SearchDirs = list(self.Trans.LibraryPaths)
for LibPath in SearchDirs:
SearchPath = os.path.join(ProjectRoot, LibPath) if not os.path.isabs(LibPath) else LibPath
# 优先尝试 .vpi,回退 .pyi
for stub_ext in ('.vpi', '.pyi'):
CandidatePath = os.path.join(SearchPath, ModulePathBase + stub_ext)
if os.path.isfile(CandidatePath):
FullModulePath = CandidatePath
break
CandidatePath = os.path.join(SearchPath, PackageInitPathBase + stub_ext)
if os.path.isfile(CandidatePath):
FullModulePath = CandidatePath
break
if FullModulePath:
break
ModuleParts = ModulePathBase.split(os.sep)
if len(ModuleParts) > 1:
LibPathBasename = os.path.basename(LibPath.rstrip('/\\'))
if LibPathBasename == ModuleParts[0]:
RemainingPath = os.sep.join(ModuleParts[1:])
for stub_ext in ('.vpi', '.pyi'):
CandidatePath = os.path.join(SearchPath, RemainingPath + stub_ext)
if os.path.isfile(CandidatePath):
FullModulePath = CandidatePath
break
if FullModulePath:
break
if not FullModulePath:
ModulePathPyBase = module_name.replace('.', os.sep)
PackageInitPathPyBase = module_name.replace('.', os.sep) + os.sep + '__init__'
SearchDirsForPy = list(self.Trans.LibraryPaths)
for LibPath in SearchDirsForPy:
SearchPath = os.path.join(ProjectRoot, LibPath) if not os.path.isabs(LibPath) else LibPath
# 优先尝试 .vp,回退 .py
for src_ext in ('.vp', '.py'):
CandidatePath = os.path.join(SearchPath, ModulePathPyBase + src_ext)
if os.path.isfile(CandidatePath):
FullModulePath = CandidatePath
break
CandidatePath = os.path.join(SearchPath, PackageInitPathPyBase + src_ext)
if os.path.isfile(CandidatePath):
FullModulePath = CandidatePath
break
if FullModulePath:
break
if not FullModulePath or not os.path.isfile(FullModulePath):
source_sig_files = getattr(self.Trans, '_source_module_sig_files', None)
if source_sig_files:
sig_path = source_sig_files.get(module_name)
if sig_path and os.path.isfile(sig_path):
FullModulePath = sig_path
else:
for key, val in source_sig_files.items():
if key.endswith('.' + module_name) or key == module_name:
FullModulePath = val
break
if not FullModulePath or not os.path.isfile(FullModulePath):
if module_name in ('pyzlib', 'zdeflate', 'zinflate', 'zchecksum', 'zdef', 'zhuff'):
pass
if not FullModulePath:
CurrentFile = getattr(self.Trans, 'CurrentFile', None) or ''
if CurrentFile:
CurrentFileDir = os.path.dirname(os.path.abspath(CurrentFile))
SearchFromDirs = [CurrentFileDir, os.getcwd()]
else:
SearchFromDirs = [os.getcwd()]
SearchDirsForSubmodule = SearchFromDirs + list(self.Trans.LibraryPaths)
for SearchPath in SearchDirsForSubmodule:
if not os.path.isdir(SearchPath):
continue
# 多级模块名(含 '.')与 basename 文件列表(files 无路径)
# 永不匹配 `module_name + ext in files`,walk 纯属浪费,跳过。
if '.' in module_name:
continue
# 目录 walk 结果缓存(编译进程内目录内容不变):import 热路径
# 高频解析失败时避免重复递归扫描 includes/工程目录(PROF 热点)
walk_entries: list[tuple[str, list[str]]] = ImportHandle._walk_cache.get(SearchPath)
if walk_entries is None:
walk_entries = [(r, fs) for r, dirs, fs in os.walk(SearchPath) if '__pycache__' not in r]
ImportHandle._walk_cache[SearchPath] = walk_entries
for root, files in walk_entries:
for src_ext in ('.vp', '.py', '.vpi', '.pyi'):
if module_name + src_ext in files:
FullModulePath = os.path.join(root, module_name + src_ext)
break
if FullModulePath:
break
if FullModulePath:
break
# fail-fast:模块在所有来源都"完全未知"时立即报错,暴露 import 路径错误
# (如目录重组后未更新的旧路径)。之前静默容忍导致引用方跨模块 mangled
# 前缀 fallback 当前模块 sha1,链接期才暴露 undefined symbol。
# 注意:文件系统找不到 ≠ 模块不存在——TPV 自举工程 includes 为合并形态,
# 源码模块(如 lib.Stub.StubMerger 包)不提供裸 .vp 文件,声明由
# ModuleSha1Map/_source_module_sig_files 定位 sha1 → temp/*.stub.ll 加载,
# 属正常路径;只有模块在编译上下文完全未注册(无 sha1)才算真正不存在。
if not FullModulePath or not os.path.isfile(FullModulePath):
_known_sha1: str | None = None
_sig_files = getattr(self.Trans, '_source_module_sig_files', None)
if _sig_files:
for _key, _val in _sig_files.items():
if _key == module_name or _key.endswith('.' + module_name):
_known_sha1 = os.path.basename(_val).replace('.vpi', '').replace('.pyi', '')
break
if not _known_sha1:
_sha_map: dict[str, str] = getattr(Gen, 'ModuleSha1Map', None) or {}
_known_sha1 = _sha_map.get(module_name)
if not _known_sha1:
raise ImportError(
f"无法解析 import 模块 '{module_name}': "
f"在 includes/LibraryPaths/工程目录均未找到对应 .vp/.py/.vpi/.pyi 文件,"
f"且编译上下文无该模块的 sha1 注册(模块完全未知)")
# 注册跨模块 __doc__ docstring(module_name 是被导入模块名)
if FullModulePath:
self._RegisterCrossModuleDocstrings(FullModulePath, Gen, module_name)
self._LoadDeclarationsFromStubLlvm(module_name, Gen)
if module_name == 'pyzlib':
pass
try:
with open(FullModulePath, 'r', encoding='utf-8') as f:
ModuleCode = f.read()
# 同上:`.vp` 源码须先过 Viper 软关键字层再 vast.parse(否则含
# `enum X:` / `fn` 的模块声明整体丢失,落到 except 的 stub 兜底)。
ModuleTree = ast.parse(ExpandSoftKeywords(ModuleCode), FullModulePath)
if module_name == 'pyzlib':
func_names = [n.name for n in ModuleTree.body if isinstance(n, ast.FunctionDef)]
has_functions_or_classes = False
for node in ModuleTree.body:
if module_name == 'pyzlib':
node_type = type(node).__name__
name = getattr(node, 'name', getattr(node, 'attr', '?'))
try:
if isinstance(node, ast.FunctionDef):
if node.name in ('va_start', 'va_arg', 'va_end'):
continue
effective_register = register_module_name or module_name
self._EmitExternalFuncDeclLlvm(node, Gen, source_module_name=module_name, register_module_name=effective_register)
has_functions_or_classes = True
elif isinstance(node, ast.AnnAssign):
self._EmitExternalGlobalDeclLlvm(node, Gen, module_name=module_name, ModulePath=FullModulePath)
has_functions_or_classes = True
elif isinstance(node, ast.Assign):
self._EmitExternalGlobalDeclLlvm(node, Gen, module_name=module_name, ModulePath=FullModulePath)
has_functions_or_classes = True
elif isinstance(node, ast.ClassDef):
if hasattr(node, 'type_params') and node.type_params:
if not hasattr(self.Trans.ClassHandler, '_generic_class_templates'):
self.Trans.ClassHandler._generic_class_templates = {}
type_params = [tp.name for tp in node.type_params]
# 保留 prescan 阶段已注册的 sha1;若无则从 _ModuleSha1Map 查找,
# 避免覆盖带 sha1 的注册导致特化时 SHA1 前缀丢失
existing = self.Trans.ClassHandler._generic_class_templates.get(node.name)
template_sha1 = existing.get('sha1') if existing else None
if template_sha1 is None:
msm = getattr(self.Trans, '_ModuleSha1Map', None)
if msm and module_name in msm:
template_sha1 = msm[module_name]
self.Trans.ClassHandler._generic_class_templates[node.name] = {
'node': node,
'type_params': type_params,
'sha1': template_sha1,
}
self._EmitExternalClassDeclLlvm(node, Gen, module_name=module_name)
has_functions_or_classes = True
elif isinstance(node, ast.ImportFrom):
if node.level > 0:
current_dir = os.path.dirname(os.path.abspath(FullModulePath))
parent_dir = current_dir
for _ in range(node.level - 1):
parent_dir = os.path.dirname(parent_dir)
if node.module:
SearchExtensions = ['.vpi', '.vp', '.pyi', '.py']
sub_path_base = os.path.join(parent_dir, node.module)
found_sub = False
for ext in SearchExtensions:
candidate = sub_path_base + ext
if os.path.isfile(candidate):
actual_module = f"{module_name}.{node.module}" if module_name else node.module
self._RegisterSubModuleSha1(candidate, actual_module, node.module, Gen)
if not getattr(self.Trans, '_ImportedModules', None):
self.Trans._ImportedModules = set()
self.Trans._ImportedModules.add(actual_module)
for alias in node.names:
if alias.name == '*':
self._LoadModuleDeclarationsFromFile(candidate, Gen, module_name=module_name, register_module_name=register_module_name, reexport_package=module_name)
else:
self._LoadModuleDeclarationsFromFile(candidate, Gen, module_name=alias.name, register_module_name=register_module_name, actual_module_name=actual_module, reexport_package=module_name)
has_functions_or_classes = True
found_sub = True
break
# SHA1 map 回退:当文件查找失败时,从 SHA1 映射中查找子模块 stub
if not found_sub:
actual_module = f"{module_name}.{node.module}" if module_name else node.module
ModuleSha1Map = getattr(Gen, 'ModuleSha1Map', {})
# 尝试完整模块名和短模块名
target_sha1 = ModuleSha1Map.get(actual_module) or ModuleSha1Map.get(node.module)
if target_sha1:
temp_dir = getattr(Gen, '_temp_dir', None)
if temp_dir:
sha1_pyi = os.path.join(temp_dir, f"{target_sha1}.vpi")
if os.path.isfile(sha1_pyi):
if not getattr(self.Trans, '_ImportedModules', None):
self.Trans._ImportedModules = set()
self.Trans._ImportedModules.add(actual_module)
for alias in node.names:
if alias.name == '*':
self._LoadModuleDeclarationsFromFile(sha1_pyi, Gen, module_name=module_name, register_module_name=register_module_name, reexport_package=module_name)
else:
self._LoadModuleDeclarationsFromFile(sha1_pyi, Gen, module_name=alias.name, register_module_name=register_module_name, actual_module_name=actual_module, reexport_package=module_name)
has_functions_or_classes = True
else:
mod_dir = parent_dir
SearchExtensions = ['.vpi', '.vp', '.pyi', '.py']
# Load __init__.py to make package-level names available
self._LoadPackageInitForRelativeImport(mod_dir, Gen, register_module_name or module_name)
for alias in node.names:
found_alias = False
for ext in SearchExtensions:
sub_path = os.path.join(mod_dir, alias.name + ext)
if os.path.isfile(sub_path):
sub_ModulePath = f"{module_name}.{alias.name}" if module_name else alias.name
if not getattr(self.Trans, '_ImportedModules', None):
self.Trans._ImportedModules = set()
self.Trans._ImportedModules.add(sub_ModulePath)
if alias.name == '*':
self._LoadModuleDeclarationsFromFile(sub_path, Gen, module_name=module_name, register_module_name=register_module_name, reexport_package=module_name)
else:
self._LoadModuleDeclarationsFromFile(sub_path, Gen, module_name=alias.name, register_module_name=register_module_name, reexport_package=module_name)
has_functions_or_classes = True
found_alias = True
break
# SHA1 map 回退
if not found_alias:
sub_ModulePath = f"{module_name}.{alias.name}" if module_name else alias.name
ModuleSha1Map = getattr(Gen, 'ModuleSha1Map', {})
target_sha1 = ModuleSha1Map.get(sub_ModulePath) or ModuleSha1Map.get(alias.name)
if target_sha1:
temp_dir = getattr(Gen, '_temp_dir', None)
if temp_dir:
sha1_pyi = os.path.join(temp_dir, f"{target_sha1}.vpi")
if os.path.isfile(sha1_pyi):
if not getattr(self.Trans, '_ImportedModules', None):
self.Trans._ImportedModules = set()
self.Trans._ImportedModules.add(sub_ModulePath)
if alias.name == '*':
self._LoadModuleDeclarationsFromFile(sha1_pyi, Gen, module_name=module_name, register_module_name=register_module_name, reexport_package=module_name)
else:
self._LoadModuleDeclarationsFromFile(sha1_pyi, Gen, module_name=alias.name, register_module_name=register_module_name, reexport_package=module_name)
has_functions_or_classes = True
except RecursionError:
# 家族口径(HandlesBase.py:1060-1068):RecursionError 不是"可忽略异常",
# 而是类型解析/声明发射链**成环**的真缺陷信号。此前它被本处
# `except Exception` 吞成一行 `WARN: 忽略异常 maximum recursion depth exceeded`
# + 声明静默退化(现场与 :2985 深探针记录一致:`match.MatchOr.patterns`、
# `stmts.Interactive.body` 等"首字段即指针/容器"的类会触发
# `_TryLoadClassMembersFromPyi ↔ _EmitExternalClassDeclLlvm ↔ _TryLoadStructFromStub`
# 环)。此处 fail-fast 上抛(保留原始栈),暴露上游根因而非掩盖。
raise
except Exception as e:
# NoneType 异常是数据缺失的正常兜底,静默不 WARN(干扰太多)
if not (isinstance(e, AttributeError) and "'NoneType'" in str(e)):
if module_name == 'pyzlib':
node_type = type(node).__name__
name = getattr(node, 'name', getattr(node, 'attr', '?'))
_vlog().warning(f"HandlesImports: 忽略异常 {e}", exc_info=e)
continue
if not has_functions_or_classes and module_name:
self._LoadDeclarationsFromStubLlvm(module_name, Gen)
elif 'includes' in FullModulePath and module_name:
self._LoadDeclarationsFromStubLlvm(module_name, Gen)
except Exception as e:
# 同族口径:读/parse 失败必须显式(文件+行号+异常+后果),不允许静默降级。
# 非严格模式仍保留既有 stub 兜底(StubGen 产物声明仍可用);strict 模式
# 由 _ReportDeclarationParseFailure 直接 fail-fast,与 :412 主点一致。
#
# **限定条件**:只有"确实有源码文件却读/解析失败"才算声明解析失败。本函数
# 的 `FullModulePath` 可为 None——TPV 自举工程 includes 为合并形态,源码模块
# (如 lib.Stub.StubMerger 包)不提供裸 .vp 文件,声明由上方
# `_LoadDeclarationsFromStubLlvm` 从 temp/*.stub.ll 加载,属**正常路径**
# (见本函数 :750 附近的长注释);此时 `open(None)` 的 TypeError 与"文件恰好
# 不在"都不应触发 fail-fast。
if FullModulePath and os.path.isfile(FullModulePath):
_ReportDeclarationParseFailure(
FullModulePath, e,
"该模块的源码声明全部未注册,"
"仅能依赖 stub 兜底(非严格模式);声明可能与源码不一致")
if module_name:
self._LoadDeclarationsFromStubLlvm(module_name, Gen)
def _AnnotationStructName(self, annotation: ast.AST) -> str | None:
"""从注解里提取「指针指向的类」短名(递归,0 判定返回 None)
覆盖形态:`X` / `mod.X` / `rawptr[X]` / `t.CPtr[X]` / 联合 `A | B`(两翼都试)。
存储类与限定符标记(`t.CExtern` / `t.CExport` / `t.CPtr` 等)不算类名,
由 `_MARKER_ANNOT_NAMES` 过滤;返回的是**短名**,调用方再解析成
`Gen.structs` 真实键。
"""
if annotation is None:
return None
if isinstance(annotation, ast.BinOp) and isinstance(annotation.op, ast.BitOr):
_left: str | None = self._AnnotationStructName(annotation.left)
if _left:
return _left
return self._AnnotationStructName(annotation.right)
if isinstance(annotation, ast.Subscript):
_base: ast.AST = annotation.value
_bn: str = ''
if isinstance(_base, ast.Name):
_bn = _base.id
elif isinstance(_base, ast.Attribute):
_bn = _base.attr
if _bn in ('rawptr', 'CPtr', 'CArrayPtr'):
_sl: ast.AST = annotation.slice
if hasattr(ast, 'Index') and isinstance(_sl, ast.Index):
_sl = _sl.value
return self._AnnotationStructName(_sl)
return None
if isinstance(annotation, ast.Name):
if annotation.id in _MARKER_ANNOT_NAMES:
return None
return annotation.id
if isinstance(annotation, ast.Attribute):
# `mod.Class`:mod 为 t/c 时是内建标记/内建类型名,不作为类的键
if isinstance(annotation.value, ast.Name) and annotation.value.id in ('t', 'c'):
return None
if annotation.attr in _MARKER_ANNOT_NAMES:
return None
return annotation.attr
return None
def _ResolveAnnotationStructKey(self, Gen: LlvmGeneratorMixin, name: str) -> str | None:
"""把注解里的类短名解析为 `Gen.structs` 的真实键(0 命中返回 None)
三条路径与类字段侧的 `rawptr[裸结构]` 登记保持一致:裸名直查 /
`class_sha1_map` 加前缀 / 全量短名模糊扫描(跨模块类在
`class_sha1_map` 尚未登记该短名时靠这条兜住)。
"""
if not name:
return None
if name in Gen.structs:
return name
_sha: str | None = (getattr(Gen, 'class_sha1_map', {}) or {}).get(name)
if _sha and f'{_sha}.{name}' in Gen.structs:
return f'{_sha}.{name}'
for _k in Gen.structs:
_short: str = _k.split('.')[-1] if '.' in _k else _k
if _short == name:
return _k
return None
def _EmitExternalGlobalDeclLlvm(self, Node: ast.AnnAssign | ast.Assign, Gen: LlvmGeneratorMixin, module_name: str | None = None, ModulePath: str | None = None) -> None:
"""Emit external global variable declaration from imported module"""
if isinstance(Node, ast.AnnAssign) and isinstance(Node.target, ast.Name):
VarName = Node.target.id
# 不再跳过 _ 开头的全局变量,跨模块引用需要声明它们
var_type = ir.IntType(32)
IsPtr = False
is_string_val = False
TypeInfo = None
# 【跨模块模块级变量的**类登记**】`CTypeInfo.FromNode` 对
# `X | t.CPtr` / `rawptr[X]` 注解只给出指针占位(rawptr[X] 折叠成 i8*),
# 且本函数**只算类型、不登记变量类** → 消费侧 `Mod.Var.Field` 链在
# `_get_var_class` 里解析为空:StructGenMixin.vp:139 "属性访问未能解析出
# 目标类型/成员"、CallGenLeafKit.vp:589 / ClassGenVTable.vp:99 误报
# Unknown module(factory.PermPool.alloc)。这里对齐 HandlesAnnAssign
# 变量侧补偿:从注解找回真实结构体键并登记;仅当缺失或指针占位时覆盖。
# 注意:必须放在下面的「已存在则 return」**之前**,否则全局量先由别的
# 路径声明(如 stub 文本 i8*)时这里永远不执行。
_eg_tn: str | None = self._AnnotationStructName(Node.annotation)
if _eg_tn:
# 只在能落到 Gen.structs 真实键时登记(避免把 sizet/CPtr 等标量名当类)
_eg_tk: str | None = self._ResolveAnnotationStructKey(Gen, _eg_tn)
if _eg_tk:
_eg_cur: str | None = Gen.var_struct_class.get(VarName)
if (not _eg_cur or _eg_cur == 'rawptr' or _eg_cur.endswith('.rawptr')
or _eg_cur.startswith('rawptr[')):
Gen.var_struct_class[VarName] = _eg_tk
if getattr(Gen, 'global_struct_class', None) is not None:
Gen.global_struct_class[VarName] = _eg_tk
if VarName in Gen.module.globals:
existing = Gen.module.globals[VarName]
if isinstance(existing, ir.GlobalVariable):
return
if Node.annotation:
parse_result = ParseListAnnotation(Node.annotation)
if parse_result and not parse_result.is_pointer:
# list[type, count] 数组声明
elem_type_node = parse_result.elem_type_node
count_node = parse_result.count_node
ElemTypeInfo = CTypeInfo.FromNode(elem_type_node, self.Trans.SymbolTable)
if ElemTypeInfo:
elem_type = Gen._ctype_to_llvm(ElemTypeInfo)
if isinstance(elem_type, ir.VoidType):
elem_type = ir.IntType(8)
if isinstance(count_node, ast.Constant) and isinstance(count_node.value, int):
var_type = ir.ArrayType(elem_type, count_node.value)
elif isinstance(count_node, ast.Constant) and count_node.value is None:
var_type = ir.ArrayType(elem_type, 0)
else:
var_type = Gen._ctype_to_llvm(ElemTypeInfo)
IsPtr = False
else:
TypeInfo = CTypeInfo.FromNode(Node.annotation, self.Trans.SymbolTable)
if TypeInfo:
var_type = Gen._ctype_to_llvm(TypeInfo)
IsPtr = TypeInfo.IsPtr
if isinstance(var_type, ir.IdentifiedStructType) and (var_type.elements is None or len(var_type.elements) == 0):
var_type = ir.IntType(32)
if Node.value and ModulePath and IsPtr and isinstance(Node.value, ast.Constant) and isinstance(Node.value.value, str):
str_val = Node.value.value + '\x00'
str_bytes = str_val.encode('utf-8')
arr_type = ir.ArrayType(ir.IntType(8), len(str_bytes))
str_gv_name = VarName + '_str'
if str_gv_name not in Gen.module.globals:
str_gv = ir.GlobalVariable(Gen.module, arr_type, name=str_gv_name)
str_gv.initializer = ir.Constant(arr_type, bytearray(str_bytes))
str_gv.linkage = 'internal'
ptr_type = ir.PointerType(ir.IntType(8))
if VarName not in Gen.module.globals:
ptr_gv = ir.GlobalVariable(Gen.module, ptr_type, name=VarName)
str_gv = Gen.module.globals[str_gv_name]
ptr_gv.initializer = ir.Constant.gep(str_gv, [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), 0)])
ptr_gv.linkage = 'internal'
Gen.variables[VarName] = None
return
# 检查是否是 CDefine 常量或 CTypedef 类型别名
IsCDefine = False
IsCTypedef = False
if Node.annotation:
if isinstance(Node.annotation, ast.Attribute):
if getattr(Node.annotation.value, 'id', None) == 't' and Node.annotation.attr == 'CDefine':
IsCDefine = True
elif getattr(Node.annotation.value, 'id', None) == 't' and Node.annotation.attr == 'CTypedef':
IsCTypedef = True
elif isinstance(Node.annotation, ast.Name):
if Node.annotation.id == 'CDefine':
IsCDefine = True
elif Node.annotation.id == 'CTypedef':
IsCTypedef = True
if IsCTypedef:
# 检查符号是否已存在且已正确解析(OriginalType 已设置),避免覆盖
existing_sym = self.Trans.SymbolTable.lookup(VarName) if hasattr(self.Trans.SymbolTable, 'lookup') else None
if existing_sym and isinstance(existing_sym, CTypeInfo) and existing_sym.IsTypedef and existing_sym.OriginalType:
return
if Node.value:
ValueTypeInfo = None
if isinstance(Node.value, ast.BinOp) and isinstance(Node.value.op, ast.BitOr):
ValueTypeInfo = self.Trans.TypeMergeHandler.MergeTypes(Node.value)
else:
ValueTypeInfo = self.Trans.TypeMergeHandler.GetCTypeInfo(Node.value)
if isinstance(ValueTypeInfo, CTypeInfo) and (ValueTypeInfo.BaseType or ValueTypeInfo.PtrCount > 0):
# GetCTypeInfo 对 t.CInt 等 Attribute 返回的 BaseType 可能是 CType 基类实例
# (而非具体子类如 CInt),导致 _resolve_typedef_int_width 无法解析位宽。
# 此时从 Node.value 提取类型名,设置 OriginalType 为字符串,
# 让 _resolve_typedef_int_width 通过 CTypeRegistry.GetClassByName 解析。
if ValueTypeInfo.BaseType and type(ValueTypeInfo.BaseType) is t.CType:
if isinstance(Node.value, ast.Attribute) and getattr(Node.value.value, 'id', None) == 't':
type_name: str = Node.value.attr
resolved_cls = CTypeRegistry.GetClassByName(type_name)
if resolved_cls is not None:
ValueTypeInfo.OriginalType = type_name
ValueTypeInfo.IsTypedef = True
ValueTypeInfo.Name = VarName
# 设置 OriginalType,确保 _resolve_name 能解析(MergeTypes 不为 BinOp 结果设置 OriginalType)
if not ValueTypeInfo.OriginalType:
OriginalCopy: CTypeInfo = ValueTypeInfo.Copy()
OriginalCopy.IsTypedef = False
OriginalCopy.Name = ''
ValueTypeInfo.OriginalType = OriginalCopy
self.Trans.SymbolTable.insert(VarName, ValueTypeInfo)
if module_name:
FullName = f"{module_name}.{VarName}"
self.Trans.SymbolTable.insert(FullName, ValueTypeInfo)
return
TTypeInfo = self.Trans.TypeMergeHandler.GetCTypeInfo(Node.annotation)
if TTypeInfo:
TTypeInfo.IsTypedef = True
TTypeInfo.Name = VarName
self.Trans.SymbolTable.insert(VarName, TTypeInfo)
if module_name:
FullName = f"{module_name}.{VarName}"
self.Trans.SymbolTable.insert(FullName, TTypeInfo)
return
if IsCDefine:
# CDefine 宏不生成 LLVM 全局变量
# 治本修复(方案C):裸名只注册到本模块 _define_constants,
# 不再注册到跨模块共享的 SymbolTable,避免同名 CDefine 污染其他模块的全局变量
# (如 Phase1.SHA1_LEN=17 覆盖 VpiReader.SHA1_LEN=16 导致双点链接错误)。
# 跨模块导入通过 _RegisterFromImportAliases 查带前缀全名后注册到本模块 _define_constants。
DefineValue = None
if Node.value:
DefineValue = self._ExtractConstValue(Node.value)
# 仅本模块 CDefine 注册到 _define_constants(供 _HandleNameLlvm 步骤1 查找)
if module_name is None and DefineValue is not None:
define_constants = vars(Gen).setdefault('_define_constants', {})
if VarName not in define_constants:
define_constants[VarName] = DefineValue
# 如果提供了模块名,注册带模块前缀的全名到 SymbolTable
# (供 _RegisterFromImportAliases 的 from X import Y 显式导入查找)
if module_name:
FullName = f"{module_name}.{VarName}"
existing_full = self.Trans.SymbolTable.lookup(FullName)
if not (existing_full and existing_full.IsTypedef and existing_full.OriginalType):
self._RegisterCDefineSymbol(FullName, DefineValue, Node.lineno, ModulePath or 'unknown')
return # 不再继续生成 LLVM 全局变量
# t.State 标记的全局变量:只声明不定义(external linkage),避免 DSO/non-DSO 重定义
if TypeInfo and TypeInfo.IsState:
if VarName not in Gen.module.globals:
gv = ir.GlobalVariable(Gen.module, var_type, name=VarName)
gv.linkage = 'external'
Gen._export_funcs.add(VarName)
return
gv = ir.GlobalVariable(Gen.module, var_type, name=VarName)
# 外部全局变量声明:只声明不定义(external linkage),避免多重定义
gv.linkage = 'external'
elif isinstance(Node, ast.Assign) and Node.targets and isinstance(Node.targets[0], ast.Name):
VarName = Node.targets[0].id
if VarName.startswith('_'):
return
if VarName in Gen.module.globals:
return
gv = ir.GlobalVariable(Gen.module, ir.IntType(32), name=VarName)
gv.linkage = 'external'
def _ExtractValue(self, value_node: ast.AST, target_type: ir.Type, var_name: str | None = None) -> ir.Constant | None:
"""Extract constant value from AST node for global variable initializer"""
# 处理负号
if isinstance(value_node, ast.UnaryOp) and isinstance(value_node.op, ast.USub):
if isinstance(value_node.operand, ast.Constant) and isinstance(value_node.operand.value, int):
neg_val = -value_node.operand.value
if isinstance(target_type, ir.IntType):
return ir.Constant(target_type, neg_val)
return ir.Constant(ir.IntType(32), neg_val)
# 处理正号
elif isinstance(value_node, ast.UnaryOp) and isinstance(value_node.op, ast.UAdd):
return self._ExtractValue(value_node.operand, target_type)
if isinstance(value_node, ast.Constant):
if isinstance(value_node.value, bool):
if isinstance(target_type, ir.IntType):
return ir.Constant(target_type, 1 if value_node.value else 0)
return ir.Constant(ir.IntType(32), 1 if value_node.value else 0)
elif isinstance(value_node.value, int):
if isinstance(target_type, ir.IntType):
return ir.Constant(target_type, value_node.value)
return ir.Constant(ir.IntType(32), value_node.value)
elif isinstance(value_node.value, str):
str_val = value_node.value + '\x00'
str_bytes = str_val.encode('utf-8')
arr_type = ir.ArrayType(ir.IntType(8), len(str_bytes))
Gen = self.Trans.LlvmGen
gv_name = var_name + '_str' if var_name else f"str_global_{id(value_node)}"
if gv_name in Gen.module.globals:
gv = Gen.module.globals[gv_name]
else:
gv = ir.GlobalVariable(Gen.module, arr_type, name=gv_name)
gv.initializer = ir.Constant(arr_type, bytearray(str_bytes))
gv.linkage = 'internal'
ptr_type = ir.PointerType(ir.IntType(8))
return ir.Constant(ptr_type, gv.reference)
elif isinstance(value_node.value, bool):
return ir.Constant(ir.IntType(32), 1 if value_node.value else 0)
elif value_node.value is None:
if isinstance(target_type, ir.BaseStructType):
return ir.Constant(target_type, None)
if isinstance(target_type, ir.PointerType):
return ir.Constant(target_type, None)
return ir.Constant(target_type, 0)
elif isinstance(value_node, ast.Name):
if value_node.id == 'None':
if isinstance(target_type, ir.BaseStructType):
return ir.Constant(target_type, None)
if isinstance(target_type, ir.PointerType):
return ir.Constant(target_type, None)
return ir.Constant(target_type, 0)
return None
def _ExtractConstValue(self, value_node: ast.AST) -> object:
"""Extract constant value from AST node for SymbolTable registration
支持常量、一元运算符、二元运算符和常量引用
"""
if isinstance(value_node, ast.Constant):
return value_node.value
elif isinstance(value_node, ast.BinOp):
left = self._ExtractConstValue(value_node.left)
right = self._ExtractConstValue(value_node.right)
if left is None or right is None:
return None
if isinstance(value_node.op, ast.Add):
return left + right
elif isinstance(value_node.op, ast.Sub):
return left - right
elif isinstance(value_node.op, ast.Mult):
return left * right
elif isinstance(value_node.op, ast.Div):
return left // right if isinstance(left, int) and isinstance(right, int) else left / right
elif isinstance(value_node.op, ast.FloorDiv):
return left // right
elif isinstance(value_node.op, ast.Mod):
return left % right
elif isinstance(value_node.op, ast.Pow):
return left ** right
elif isinstance(value_node.op, ast.LShift):
return left << right
elif isinstance(value_node.op, ast.RShift):
return left >> right
elif isinstance(value_node.op, ast.BitOr):
return left | right
elif isinstance(value_node.op, ast.BitXor):
return left ^ right
elif isinstance(value_node.op, ast.BitAnd):
return left & right
elif isinstance(value_node, ast.UnaryOp):
operand = self._ExtractConstValue(value_node.operand)
if operand is None:
return None
if isinstance(value_node.op, ast.USub):
return -operand
elif isinstance(value_node.op, ast.UAdd):
return +operand
elif isinstance(value_node.op, ast.Invert):
return ~operand
elif isinstance(value_node, ast.Name):
if value_node.id in getattr(self.Trans, 'SymbolTable', {}):
info = self.Trans.SymbolTable[value_node.id]
if info.IsDefine and info.DefineValue is not None:
return info.DefineValue
elif isinstance(value_node, ast.Call):
if isinstance(value_node.func, ast.Attribute):
if getattr(value_node.func.value, 'id', None) == 't':
if value_node.args:
return self._ExtractConstValue(value_node.args[0])
elif isinstance(value_node.func, ast.Name):
if value_node.args:
return self._ExtractConstValue(value_node.args[0])
return None
def _ExtractTypedefOriginalType(self, value_node: ast.AST) -> str | None:
if isinstance(value_node, ast.Attribute):
if getattr(value_node.value, 'id', None) == 't':
llvm_str = CTypeRegistry.NameToLLVM(value_node.attr)
if llvm_str:
return llvm_str
elif isinstance(value_node, ast.Name):
llvm_str = CTypeRegistry.NameToLLVM(value_node.id)
if llvm_str:
return llvm_str
elif isinstance(value_node, ast.BinOp) and isinstance(value_node.op, ast.BitOr):
left = self._ExtractTypedefOriginalType(value_node.left)
right = self._ExtractTypedefOriginalType(value_node.right)
parts = []
if left:
parts.append(left)
if right:
parts.append(right)
if parts:
return '|'.join(parts)
elif isinstance(value_node, ast.Call):
if isinstance(value_node.func, ast.Attribute):
if getattr(value_node.func.value, 'id', None) == 't':
llvm_str = CTypeRegistry.NameToLLVM(value_node.func.attr)
if llvm_str:
return llvm_str
elif isinstance(value_node.func, ast.Name):
llvm_str = CTypeRegistry.NameToLLVM(value_node.func.id)
if llvm_str:
return llvm_str
return None
def _RegisterCDefineSymbol(self, FullName: str, DefineValue: object, lineno: int, FilePath: str) -> None:
"""Register CDefine constant to SymbolTable"""
# 防御:新值为 None 但已存在非 None 值时保留旧值。
# 翻译期间声明发射调用 _ExtractConstValue(不支持 ast.Attribute 形式如 t.CSizeT().Size),
# 会返回 None 并覆盖共享符号表中已由 ConstEvaluator 正确求值的值(如 8)。
# 共享符号表在 _build_shared_symbol_data 阶段已正确求值,此处不应覆盖。
if DefineValue is None:
existing: Any = self.Trans.SymbolTable.lookup(FullName)
if existing and existing.IsDefine and existing.DefineValue is not None:
return
info = CTypeInfo()
info.IsDefine = True
info.DefineValue = DefineValue
info.Lineno = lineno
info.file = FilePath
# 直接添加到 SymbolTable 字典
self.Trans.SymbolTable.insert(FullName, info)
def _check_annotation_for_state(self, annotation: ast.AST) -> bool:
return (AnnotationContainsName(annotation, 'CExport') or
AnnotationContainsName(annotation, 'CExtern') or
AnnotationContainsName(annotation, 'State'))
def _check_decorators_for_state(self, Node: ast.FunctionDef) -> bool:
"""检查装饰器列表中是否包含 @t.CExport / @t.CExtern / @t.State(t.CExport 既能作返回值注解也能作装饰器)"""
if not Node.decorator_list:
return False
for decorator in Node.decorator_list:
if (AnnotationContainsName(decorator, 'CExport') or
AnnotationContainsName(decorator, 'CExtern') or
AnnotationContainsName(decorator, 'State')):
return True
return False
def _EmitExternalFuncDeclLlvm(self, Node: ast.FunctionDef, Gen: LlvmGeneratorMixin, is_class_method: bool = False, source_module_name: str | None = None, register_module_name: str | None = None, reexport_module_names: list[str] | None = None) -> None:
FuncName = Node.name
if (Node.returns and self._check_annotation_for_state(Node.returns)) or self._check_decorators_for_state(Node):
Gen._export_funcs.add(FuncName) # t.State 标记的外部 C 函数必须保持原始名称,以便链接器解析
if source_module_name and source_module_name not in ('c', 't'):
_src_sha1 = Gen.ModuleSha1Map.get(source_module_name)
if not _src_sha1:
_init_name = f"{source_module_name}.__init__"
if _init_name in Gen.ModuleSha1Map:
_src_sha1 = Gen.ModuleSha1Map[_init_name]
if _src_sha1:
Gen._export_extern_funcs[FuncName] = _src_sha1
if FuncName in Gen.functions:
return
if Node.decorator_list:
for decorator in Node.decorator_list:
if isinstance(decorator, ast.Call) and isinstance(decorator.func, ast.Attribute):
# fail-fast:`@c.CReturn` 已移除,扫到即报错。
if decorator.func.attr == 'CReturn':
raise SyntaxError(
f"函数 '{FuncName}':`@c.CReturn` 已移除:请改用返回注解 "
f"`-> tuple[ty1, ty2, ...]`(多返回值打包为 struct)"
)
IsPtr = False
ReturnTypeInfo = None
if Node.returns:
try:
ReturnTypeInfo = self.ResolveAnnotationType(Node.returns)
# 仅指针层数 <=1 才算裸 str/bytes(i8*)。
# `str | t.CPtr` 合并后 IsStr=True 但 PtrCount=2(i8**,如 argv 数组),不能压平。
if ReturnTypeInfo and ReturnTypeInfo.IsStr and ReturnTypeInfo.PtrCount <= 1:
ReturnTypeInfo = CTypeInfo()
ReturnTypeInfo.BaseType = t.CChar()
ReturnTypeInfo.PtrCount = 1
if ReturnTypeInfo is None:
ReturnTypeInfo = CTypeInfo()
ReturnTypeInfo.BaseType = t.CInt()
IsPtr = ReturnTypeInfo.IsPtr
except RecursionError:
# 家族口径(HandlesBase.py:1060-1068):RecursionError 是类型解析链成环的
# 真缺陷信号,不允许退化成"默认 CInt"(那会让被指类丢失、下游全部错型)。
raise
except Exception as e: # 回退:返回类型解析失败时使用默认 CInt
# NoneType 异常(如对 None 调 endswith)是数据缺失的正常兜底场景,
# 静默不输出 WARN(干扰太多);其他异常保留提示。
if not (isinstance(e, AttributeError) and "'NoneType'" in str(e)):
_vlog().warning(f"HandlesImports: 忽略异常 {e}", exc_info=e)
ReturnTypeInfo = CTypeInfo()
ReturnTypeInfo.BaseType = t.CInt()
else:
ReturnTypeInfo = CTypeInfo()
ReturnTypeInfo.BaseType = t.CInt()
ReturnType = Gen._ctype_to_llvm(ReturnTypeInfo)
if (isinstance(ReturnType, ir.VoidType) or (isinstance(ReturnType, ir.PointerType) and isinstance(ReturnType.pointee, ir.VoidType))) and Node.returns:
RetAnnName = None
if isinstance(Node.returns, ast.Name):
RetAnnName = Node.returns.id
elif isinstance(Node.returns, ast.Attribute):
RetAnnName = Node.returns.attr
elif isinstance(Node.returns, ast.BinOp) and isinstance(Node.returns.op, ast.BitOr):
left = Node.returns.left
if isinstance(left, ast.Name):
RetAnnName = left.id
elif isinstance(left, ast.Attribute):
RetAnnName = left.attr
if RetAnnName and RetAnnName in Gen.structs:
ReturnType = ir.PointerType(Gen.structs[RetAnnName])
elif IsPtr or RetAnnName:
ReturnType = ir.PointerType(ir.IntType(8))
# __new__ 方法应返回类指针类型(堆分配替换),与 _GetFunctionSignatureLlvm/_EmitFunctionLlvm 保持一致
# .pyi 生成器对无返回类型注解的 __new__ 默认使用 t.CInt,这里修正为 ClassName*
if '.__new__' in FuncName:
_np = FuncName.split('.')
if len(_np[0]) == 16 and all(_c in '0123456789abcdef' for _c in _np[0]):
_new_class_name = _np[1]
else:
_new_class_name = _np[0]
# 治本修复:优先用 source sha1 查找正确的 struct,避免同名类冲突时返回错误类型
_new_struct = None
_new_src_sha1: str | None = None
if source_module_name and hasattr(Gen, 'ModuleSha1Map'):
_new_src_sha1 = Gen.ModuleSha1Map.get(source_module_name)
if not _new_src_sha1:
_init_name = f"{source_module_name}.__init__"
if _init_name in Gen.ModuleSha1Map:
_new_src_sha1 = Gen.ModuleSha1Map[_init_name]
if _new_src_sha1 and _new_class_name:
_new_struct = Gen.structs.get(f"{_new_src_sha1}.{_new_class_name}")
if _new_struct is None:
_new_struct = Gen.structs.get(_new_class_name)
if _new_struct is None:
_full_name = FuncName.split('.__new__')[0]
_new_struct = Gen.structs.get(_full_name)
if _new_struct:
if isinstance(_new_struct, ir.PointerType):
ReturnType = _new_struct
else:
ReturnType = ir.PointerType(_new_struct)
ParamTypes = []
is_method = is_class_method or '.__' in FuncName
class_name_for_method = FuncName.split('.')[0] if '.' in FuncName else None
class_is_cpython = class_name_for_method and self.Trans.SymbolTable.is_struct(class_name_for_method)
# 治本修复:通过 source_module_name 获取源模块 sha1,查找正确的 struct。
# 当存在同名类冲突(如 stmts.py:Module 和 __module.py:Module)时,
# Gen.structs[short_name] 可能指向错误模块的 struct(set_body 已锁定错误布局)。
# 优先查找以 sha1 为前缀的完整 key,避免使用错误的 struct 作为 self 参数类型。
_self_struct_sha1: str | None = None
if source_module_name and hasattr(Gen, 'ModuleSha1Map'):
_self_struct_sha1 = Gen.ModuleSha1Map.get(source_module_name)
if not _self_struct_sha1:
_init_name = f"{source_module_name}.__init__"
if _init_name in Gen.ModuleSha1Map:
_self_struct_sha1 = Gen.ModuleSha1Map[_init_name]
for i, Arg in enumerate(Node.args.args):
if i == 0 and is_method:
# self parameter of a method should always be a pointer to the struct
_self_struct = None
if _self_struct_sha1 and class_name_for_method:
_full_key = f"{_self_struct_sha1}.{class_name_for_method}"
_self_struct = Gen.structs.get(_full_key)
if _self_struct is None:
_self_struct = Gen.structs.get(class_name_for_method or FuncName.split('.')[0], ir.IntType(8))
ParamType = ir.PointerType(_self_struct)
elif Arg.annotation:
try:
ParamTypeInfo = self.ResolveAnnotationType(Arg.annotation)
if ParamTypeInfo is None:
ParamTypeInfo = CTypeInfo()
ParamTypeInfo.BaseType = t.CInt()
ParamIsPtr = ParamTypeInfo.IsPtr
if i == 0 and is_method and Arg.arg == 'self' and class_is_cpython:
ParamIsPtr = True
ParamTypeInfo.PtrCount = max(ParamTypeInfo.PtrCount, 1)
# 仅当指针层数 <=1 时才压平为 i8*(裸 str/bytes)。
# `str | t.CPtr` 合并后 IsStr=True 但 PtrCount=2(i8**,如 argv 数组),
# 不能压平,否则形参类型错误导致 _adjust_args 多 load 一层崩溃。
if (ParamTypeInfo.IsStr and ParamTypeInfo.PtrCount <= 1) or (isinstance(Arg.annotation, ast.Name) and Arg.annotation.id in ('str', 'bytes')):
ParamTypeInfo = CTypeInfo()
ParamTypeInfo.BaseType = t.CChar()
ParamTypeInfo.PtrCount = 1
ParamIsPtr = True
# C 语言中数组参数退化为指针:list[type, N] → type*
if ParamTypeInfo.ArrayDims:
ParamTypeInfo.ArrayDims = []
ParamTypeInfo.PtrCount = max(ParamTypeInfo.PtrCount, 1)
ParamType = Gen._ctype_to_llvm(ParamTypeInfo)
except RecursionError:
# 家族口径(HandlesBase.py:1060-1068):RecursionError 是类型解析链成环的
# 真缺陷信号,不允许退化成"默认 i32"(形参错型会让 _adjust_args 行为错乱)。
raise
except Exception as e: # 回退:参数类型解析失败时使用默认 i32
# NoneType 异常是数据缺失的正常兜底,静默不 WARN(干扰太多)
if not (isinstance(e, AttributeError) and "'NoneType'" in str(e)):
_vlog().warning(f"HandlesImports: 忽略异常 {e}", exc_info=e)
ParamType = ir.IntType(32)
if isinstance(ParamType, ir.VoidType) or (isinstance(ParamType, ir.PointerType) and isinstance(ParamType.pointee, ir.VoidType)):
AnnName = None
if isinstance(Arg.annotation, ast.Name):
AnnName = Arg.annotation.id
elif isinstance(Arg.annotation, ast.Attribute):
AnnName = Arg.annotation.attr
elif isinstance(Arg.annotation, ast.BinOp) and isinstance(Arg.annotation.op, ast.BitOr):
AnnName = ExtractTypeNameFromBinOp(Arg.annotation)
if AnnName and AnnName in Gen.structs:
ParamType = ir.PointerType(Gen.structs[AnnName])
else:
ParamType = ir.PointerType(ir.IntType(8))
else:
ParamType = ir.IntType(32)
ParamTypes.append(ParamType)
IsVariadic = Node.args.vararg is not None
FuncType = ir.FunctionType(ReturnType, ParamTypes, var_arg=IsVariadic)
_is_export = False
if isinstance(ReturnTypeInfo, CTypeInfo) and ReturnTypeInfo.IsState:
_is_export = True
if isinstance(ReturnTypeInfo, CTypeInfo) and ReturnTypeInfo.Storage and isinstance(ReturnTypeInfo.Storage, t.CExport) and not ReturnTypeInfo.IsState:
Gen._export_funcs.add(FuncName)
_is_export = True
if isinstance(ReturnTypeInfo, CTypeInfo) and ReturnTypeInfo.Storage and isinstance(ReturnTypeInfo.Storage, t.CExtern):
Gen._export_funcs.add(FuncName)
_is_export = True
if not _is_export:
if Node.returns and self._check_annotation_for_state(Node.returns):
_is_export = True
elif self._check_decorators_for_state(Node):
_is_export = True
MangledName = Gen._mangle_func_name(FuncName, module_name=source_module_name)
func = Gen._get_or_declare_function(MangledName, FuncType)
Gen.functions[MangledName] = func
Gen.functions[FuncName] = func
func_meta = FuncMeta.NONE
if Node.decorator_list:
for d in Node.decorator_list:
if isinstance(d, ast.Name):
if d.id == 'staticmethod':
func_meta |= FuncMeta.STATIC_METHOD
elif d.id == 'property':
func_meta |= FuncMeta.PROPERTY_GETTER
elif d.id == 'classmethod':
func_meta |= FuncMeta.CLASS_METHOD
elif isinstance(d, ast.Attribute):
if isinstance(d.value, ast.Name):
# 支持 @property.setter 和 @propname.setter 两种形式
if d.value.id == 'property' or d.attr in ('setter', 'getter', 'deleter'):
if d.attr == 'setter':
func_meta |= FuncMeta.PROPERTY_SETTER
elif d.attr == 'getter':
func_meta |= FuncMeta.PROPERTY_GETTER
elif d.attr == 'deleter':
func_meta |= FuncMeta.PROPERTY_DELETER
if not self.Trans.SymbolTable.has(FuncName):
FuncInfo = CTypeInfo()
FuncInfo.Name = FuncName
FuncInfo.IsFunction = True
FuncInfo.MetaList = func_meta
self.Trans.SymbolTable.insert(FuncName, FuncInfo)
else:
existing = self.Trans.SymbolTable[FuncName]
if existing.MetaList == FuncMeta.NONE and func_meta != FuncMeta.NONE:
existing.MetaList = func_meta
# 同时注册带模块前缀的符号,以便 module.func_name 查找能命中
if source_module_name and source_module_name not in ('c', 't'):
FullSymKey = f"{source_module_name}.{FuncName}"
if not self.Trans.SymbolTable.has(FullSymKey):
FullFuncInfo = CTypeInfo()
FullFuncInfo.Name = FullSymKey
FullFuncInfo.IsFunction = True
FullFuncInfo.MetaList = func_meta
self.Trans.SymbolTable.insert(FullSymKey, FullFuncInfo)
# property setter/deleter: 在原始 PropKey(不带后缀)下注册 MetaList
if FuncMeta.PROPERTY_SETTER in func_meta or FuncMeta.PROPERTY_DELETER in func_meta:
BasePropKey = FuncName.replace('$set', '').replace('$del', '')
base_existing = self.Trans.SymbolTable.lookup(BasePropKey)
if base_existing:
if func_meta != FuncMeta.NONE:
base_existing.MetaList = base_existing.MetaList | func_meta
else:
PropInfo = CTypeInfo()
PropInfo.Name = BasePropKey
PropInfo.IsFunction = True
PropInfo.MetaList = func_meta
self.Trans.SymbolTable.insert(BasePropKey, PropInfo)
if register_module_name and register_module_name != source_module_name:
ReexportName = Gen._mangle_func_name(FuncName, module_name=register_module_name)
Gen.functions[ReexportName] = func
if reexport_module_names:
for reexport_mod in reexport_module_names:
if reexport_mod and reexport_mod != source_module_name:
ReexportName2 = Gen._mangle_func_name(FuncName, module_name=reexport_mod)
if ReexportName2 not in Gen.functions:
Gen.functions[ReexportName2] = func
def _ClassifyExternalClass(self, Node: ast.ClassDef) -> tuple:
"""遍历 Node.bases/decorator_list 做类别判断(纯函数、与 Gen 无关)。
结果由 _EmitExternalClassDeclLlvm 按 (source_sha1, class_name) 类级缓存,
同一 worker 进程内多个任务对同一跨模块类重复调用时跳过本遍历。
返回 (IsGenericClass, IsCenum, IsRenum, IsCpythonObject, IsCVTable,
IsNoVTable, IsPacked)。
PEP 695 泛型类(如 GSListNode[T])特殊:跨模块继承展平需要从泛型基类
获取字段(如 Next),否则子类(如 Value(GSListNode[Value]))的
class_members 会缺少继承字段;但泛型类的方法声明必须跳过(方法参数
类型 T 会被解析为 opaque struct,LLVM 不允许 opaque struct 作为值参数)。
"""
IsGenericClass: bool = hasattr(Node, 'type_params') and bool(Node.type_params)
IsCenum = False
IsRenum = False
IsCpythonObject = False
IsCVTable = False
IsNoVTable = False
if Node.bases:
for base in Node.bases:
if getattr(base, 'attr', None):
if base.attr == 'REnum':
IsRenum = True
break
elif base.attr == 'CEnum' or base.attr == 'Enum':
IsCenum = True
break
elif getattr(base, 'id', None):
if base.id == 'REnum':
IsRenum = True
break
elif base.id == 'CEnum' or base.id == 'Enum':
IsCenum = True
break
if not IsCenum and not IsRenum and getattr(Node, 'decorator_list', None):
for decorator in Node.decorator_list:
if isinstance(decorator, ast.Attribute):
if getattr(decorator.value, 'id', None) == 't':
if decorator.attr == 'Object':
IsCpythonObject = True
elif decorator.attr == 'CVTable':
IsCVTable = True
elif decorator.attr == 'NoVTable':
IsNoVTable = True
elif decorator.attr == 'REnum':
IsRenum = True
elif decorator.attr == 'CEnum' or decorator.attr == 'Enum':
IsCenum = True
elif isinstance(decorator, ast.Call):
if isinstance(decorator.func, ast.Attribute):
if getattr(decorator.func.value, 'id', None) == 't':
if decorator.func.attr == 'Object':
IsCpythonObject = True
elif decorator.func.attr == 'CVTable':
IsCVTable = True
elif decorator.func.attr == 'NoVTable':
IsNoVTable = True
elif isinstance(decorator.func, ast.Name):
if decorator.func.id == 'Object':
IsCpythonObject = True
elif decorator.func.id == 'CVTable':
IsCVTable = True
elif decorator.func.id == 'NoVTable':
IsNoVTable = True
IsPacked = False
if getattr(Node, 'decorator_list', None):
for decorator in Node.decorator_list:
if isinstance(decorator, ast.Call):
if isinstance(decorator.func, ast.Attribute):
if getattr(decorator.func.value, 'id', None) == 'c' and decorator.func.attr == 'Attribute':
for arg in decorator.args:
if isinstance(arg, ast.Attribute):
if isinstance(arg.value, ast.Attribute):
if getattr(arg.value.value, 'id', None) == 't' and arg.value.attr == 'attr' and arg.attr == 'packed':
IsPacked = True
# 方法级 virtual(`virtual def m(...)` ⇒ 方法装饰器 @t.CVTable)触发类级 vtable:
# 与 HandlesClassDef._resolve_class_flags / TPV _detect_vtable_status 的
# `_has_any_func_cvtable` 分支对齐(跨模块这里只能判"类级 NoVTable /
# 无真实父类"两种可判定形态;父类 vtable 状态由 .pyi 按需注册路径解析)。
if not IsCVTable and ClassHasFuncLevelCVTable(Node):
if IsNoVTable or not _HasRealParentClass(Node):
IsCVTable = True
return (IsGenericClass, IsCenum, IsRenum, IsCpythonObject,
IsCVTable, IsNoVTable, IsPacked)
def _EmitExternalClassDeclLlvm(self, Node: ast.ClassDef, Gen: LlvmGeneratorMixin, module_name: str | None = None, actual_module_name: str | None = None, source_sha1: str | None = None) -> None:
ClassName = Node.name
# Compute source_sha1 up front (moved from mid-function) so it can serve as
# the key of the class-level classification cache. Prefer the explicit
# source_sha1 passed by _TryLoadClassMembersFromPyi (module_name lookup may
# fail when the current module does not import the defining module directly).
if not source_sha1:
if actual_module_name and hasattr(Gen, 'ModuleSha1Map') and actual_module_name in Gen.ModuleSha1Map:
source_sha1 = Gen.ModuleSha1Map[actual_module_name]
elif module_name and hasattr(Gen, 'ModuleSha1Map') and module_name in Gen.ModuleSha1Map:
source_sha1 = Gen.ModuleSha1Map[module_name]
# Class-level classification cache: the bases/decorator_list walk below is pure
# Gen-independent computation. Repeated calls on the same (source_sha1, class_name)
# across worker tasks (same process) skip the walk. When source_sha1 is None
# (lookup failed) neither read nor write the cache, to avoid mismatching
# same-named classes from different modules under a shared None key.
_classify_key = (source_sha1, ClassName) if source_sha1 else None
_cached_classify = self._extern_class_classify_cache.get(_classify_key) if _classify_key else None
if _cached_classify is None:
_cached_classify = self._ClassifyExternalClass(Node)
if _classify_key is not None:
self._extern_class_classify_cache[_classify_key] = _cached_classify
(IsGenericClass, IsCenum, IsRenum, IsCpythonObject,
IsCVTable, IsNoVTable, IsPacked) = _cached_classify
if ClassName.startswith('__') and not IsCpythonObject and not IsCenum and not IsRenum:
return
if IsPacked:
Gen.class_packed.add(ClassName)
if IsCpythonObject:
CInfo = CTypeInfo()
CInfo.Name = ClassName
CInfo.IsCpythonObject = True
CInfo.IsStruct = True
if module_name:
FullName = f"{module_name}.{ClassName}"
self.Trans.SymbolTable.insert(FullName, CInfo)
self.Trans.SymbolTable.insert(ClassName, CInfo)
if IsCenum:
EnumTypeNode = CTypeInfo()
EnumTypeNode.Name = ClassName
EnumTypeNode.BaseType = t.CEnum(ClassName)
EnumTypeNode.IsEnum = True
# [跨模块枚举宽度] 读侧重推导:.vpi/.pyi 保留成员注解(`Name: T`),
# 故在本处按"最宽成员类型"重算(与定义端 HandlesClassDef 同一规则源
# EnumClassIntWidth),使跨模块使用同一枚举时宽度一致。本函数会以短名
# 覆盖共享符号表(_shared_sym.json pickle)中的同名条目,若不在此登记
# 宽度,覆盖后即退化为默认 i32(= 上一轮遗留缺口)。旧产物缺注解时
# 自然回落 32(Default),不崩。
EnumWidth: int = EnumClassIntWidth(Node.body, self.Trans.SymbolTable)
EnumTypeNode.set('enum_width', EnumWidth)
self.Trans.SymbolTable.insert(ClassName, EnumTypeNode)
next_enum_value: int = 0
for item in Node.body:
VarName = None
ItemValue = None
if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
VarName = item.target.id
ItemValue = item.value
elif isinstance(item, ast.Assign):
for target in item.targets:
if isinstance(target, ast.Name):
VarName = target.id
break
ItemValue = item.value
if VarName is None:
continue
# 提取枚举值:优先用显式赋值,否则自动编号
if ItemValue is not None and isinstance(ItemValue, ast.Constant) and isinstance(ItemValue.value, int):
value = ItemValue.value
next_enum_value = ItemValue.value + 1
else:
value = next_enum_value
next_enum_value += 1
MemberNode = CTypeInfo()
MemberNode.Name = VarName
MemberNode.BaseType = t.CEnum(ClassName)
MemberNode.value = value
MemberNode.EnumName = ClassName
MemberNode.Lineno = item.lineno
MemberNode.IsEnumMember = True
MemberNode.set('enum_width', EnumWidth)
self.Trans.SymbolTable.insert(VarName, MemberNode)
self.Trans.SymbolTable.insert(f"{ClassName}.{VarName}", MemberNode)
self.Trans.SymbolTable.insert(f"{ClassName}_{VarName}", MemberNode)
return
if IsRenum:
# REnum 跨模块导入:注册 REnum 类型 + 变体成员到 SymbolTable
# 对齐同模块 _EmitREnumLlvm 的注册逻辑
RenumTypeNode = CTypeInfo()
RenumTypeNode.Name = ClassName
RenumTypeNode.IsRenum = True
RenumTypeNode.IsEnum = True # 兼容 IsEnum 检查
# REnum 在内存布局上是 struct,必须设置 IsStruct=True 才能让
# HandlesTypeMerge 的 CPtr 吸收逻辑生效(REnumType | t.CPtr = REnumType*)
RenumTypeNode.IsStruct = True
if module_name:
FullName = f"{module_name}.{ClassName}"
self.Trans.SymbolTable.insert(FullName, RenumTypeNode)
self.Trans.SymbolTable.insert(ClassName, RenumTypeNode)
# 从 stub.ll 加载 REnum 结构体布局到 Gen.structs
self._TryLoadStructFromStub(ClassName, Gen)
# 记录 class_sha1_map(跨模块名修饰)
source_sha1 = None
if actual_module_name and hasattr(Gen, 'ModuleSha1Map') and actual_module_name in Gen.ModuleSha1Map:
source_sha1 = Gen.ModuleSha1Map[actual_module_name]
elif module_name and hasattr(Gen, 'ModuleSha1Map') and module_name in Gen.ModuleSha1Map:
source_sha1 = Gen.ModuleSha1Map[module_name]
if source_sha1:
if not hasattr(Gen, 'class_sha1_map'):
Gen.class_sha1_map = {}
Gen.class_sha1_map[ClassName] = source_sha1
# 遍历嵌套 ClassDef 注册变体成员
# REnum body 是嵌套 ClassDef(变体定义),不是 AnnAssign/Assign
next_tag: int = 0
variant_names: list[str] = []
for item in Node.body:
if isinstance(item, ast.ClassDef):
VariantName = item.name
MemberNode = CTypeInfo()
MemberNode.Name = VariantName
MemberNode.value = next_tag
MemberNode.EnumName = ClassName
MemberNode.Lineno = item.lineno
MemberNode.IsEnumMember = True
self.Trans.SymbolTable.insert(VariantName, MemberNode)
self.Trans.SymbolTable.insert(f"{ClassName}.{VariantName}", MemberNode)
self.Trans.SymbolTable.insert(f"{ClassName}_{VariantName}", MemberNode)
variant_names.append(VariantName)
next_tag += 1
# 加载嵌套变体结构体(如 LLVMType_Int)到 Gen.structs,
# 并注册 class_members 以支持 _HandleREnumConstructLlvm 的 payload 存储和
# _HandleRenumMatchLlvm 的 binding 提取。
NestedStructName: str = f"{ClassName}_{VariantName}"
self._TryLoadStructFromStub(NestedStructName, Gen)
if NestedStructName not in Gen.class_members:
Gen.class_members[NestedStructName] = []
if NestedStructName not in Gen.class_member_signeds:
Gen.class_member_signeds[NestedStructName] = {}
if Gen.class_members[NestedStructName]:
# 已有成员(_TryLoadStructFromStub 可能已加载),跳过
pass
else:
Gen.class_members[NestedStructName].append(('__tag', ir.IntType(32)))
Gen.class_member_signeds[NestedStructName]['__tag'] = None
for nested_item in item.body:
# 只把 AnnAssign 当载荷声明;变体体里的 pass / docstring 等
# 非声明语句合法跳过(如 `class Void: pass`)。
if not isinstance(nested_item, ast.AnnAssign):
continue
_member_txt: str = ast.unparse(nested_item)
try:
parsed = self.CollectAnnAssignMember(nested_item, Gen)
except Exception as _pe:
# 历史上此处异常被上层吞掉 ⇒ 变体只剩 __tag ⇒ 变体构造静默
# 丢载荷、match 模式捕获绑不上(现场 LLVMType_Ptr / pointee_cs),
# 且循环中断使后续变体(Func/Array/...)整批缺失。改为响亮失败。
from lib.core.LLVMCG.FuncGen import FailFastError as _FFE
raise _FFE(
f"REnum 变体 '{ClassName}.{VariantName}' 的载荷成员 "
f"'{_member_txt}' 解析失败:{type(_pe).__name__}: {_pe} "
f"—— 拒绝留下只有 __tag 的 class_members"
f"(会导致变体构造丢载荷、match 捕获绑定缺失、后续变体整批缺失)"
) from _pe
if not parsed:
from lib.core.LLVMCG.FuncGen import FailFastError as _FFE
raise _FFE(
f"REnum 变体 '{ClassName}.{VariantName}' 的载荷成员 "
f"'{_member_txt}' 未解析出类型(CollectAnnAssignMember 返回 None)"
f"—— 同上,拒绝静默降级为无载荷"
)
MName, MType, MTypeInfo = parsed
Gen.class_members[NestedStructName].append((MName, MType))
Gen.class_member_signeds[NestedStructName][MName] = None
# 当 stub 中没有嵌套变体结构体定义时(_TryLoadStructFromStub 失败),
# 使用主 REnum 结构体的 body (max_variant_struct) 创建嵌套变体结构体,
# 确保跨模块布局一致。否则 _generate_structs() Step 4 会从 class_members
# 设置最小布局 body,导致 GEP 偏移量与定义模块不匹配(offset 4 vs offset 8)。
NestedStructExisting = Gen.structs.get(NestedStructName)
NestedHasBody = (isinstance(NestedStructExisting, ir.IdentifiedStructType)
and NestedStructExisting.elements is not None
and len(NestedStructExisting.elements) > 0)
if not NestedHasBody:
MainRenumStruct = Gen.structs.get(ClassName)
if (isinstance(MainRenumStruct, ir.IdentifiedStructType)
and MainRenumStruct.elements is not None
and len(MainRenumStruct.elements) > 0):
NestedStruct = Gen._get_or_create_struct(NestedStructName, source_sha1=source_sha1)
if isinstance(NestedStruct, ir.IdentifiedStructType) and NestedStruct.is_opaque:
NestedStruct.set_body(*MainRenumStruct.elements)
elif isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
# 显式值变体(无 payload)
VariantName = item.target.id
value = next_tag
if item.value and isinstance(item.value, ast.Constant) and isinstance(item.value.value, int):
value = item.value.value
MemberNode = CTypeInfo()
MemberNode.Name = VariantName
MemberNode.value = value
MemberNode.EnumName = ClassName
MemberNode.Lineno = item.lineno
MemberNode.IsEnumMember = True
self.Trans.SymbolTable.insert(VariantName, MemberNode)
self.Trans.SymbolTable.insert(f"{ClassName}.{VariantName}", MemberNode)
self.Trans.SymbolTable.insert(f"{ClassName}_{VariantName}", MemberNode)
variant_names.append(VariantName)
next_tag = value + 1
RenumTypeNode.RenumVariants = variant_names
return
# 【声明发射重入标记】本函数主体会重入自身:成员收集(CollectAnnAssignMember
# → ResolveAnnotationType → 泛型特化 list[T]/GSListNode[T] → _EmitClassLlvm
# → StructGen._generate_structs → _TryLoadStructFromStub(X)
# → _TryLoadClassMembersFromPyi(X) → 本函数(X))。首字段即指针/容器的类必现
# (llvmlite.Param 的 Attrs、match.MatchOr 的 patterns、stmts.Interactive 的
# body…),且每轮仅 4 帧:本函数 → _TryLoadStructFromStub → 成员加载 → 本函数。
# 两条加载路径的缓存键(带/不带 source_sha1 前缀)互不相同,失败时键还会被
# 清除(_TryLoadStructFromStub 的"仍 opaque ⇒ 删键重试"),缓存守卫挡不住
# ⇒ 无限递归(RecursionError 被上层 except 吞掉,表现为类型解析静默退化)。
# 登记 (ClassName, source_sha1);由 `_TryLoadStructFromStub` 里的"顺带加载成员"
# 那一步短路(该边是递归环的唯一闭合边:结构体 body 加载会连带成员加载 → 又回到
# 本函数);本函数自身**不**短路——重入的成员收集是"把表填起来"的唯一途径,
# 消费者(重入编译的其他模块方法体按名查字段)必须能拿到它。
# 放在 CEnum/REnum 早退(上方 return)之后:那两个分支不加载成员/结构体。
_emit_inprog: set | None = getattr(Gen, '_extern_decl_emit_in_progress', None)
if _emit_inprog is None:
_emit_inprog = set()
Gen._extern_decl_emit_in_progress = _emit_inprog
_emit_key: tuple = (ClassName, source_sha1)
_emit_inprog.add(_emit_key)
# 先初始化 class_methods/class_members 等容器,确保 _TryLoadStructFromStub
# 和 _get_or_create_struct 中的 IsEnum 守卫(clean_name not in class_methods)
# 能正确识别 AST 节点类名(避免 ASTKind 枚举名冲突时把节点类当枚举返回 i32)
if ClassName not in Gen.class_members:
Gen.class_members[ClassName] = []
if ClassName not in Gen.class_member_defaults:
Gen.class_member_defaults[ClassName] = {}
if ClassName not in Gen.class_member_signeds:
Gen.class_member_signeds[ClassName] = {}
if ClassName not in Gen.class_member_bitfields:
Gen.class_member_bitfields[ClassName] = {}
if ClassName not in Gen.class_member_byteorders:
Gen.class_member_byteorders[ClassName] = {}
if ClassName not in Gen.class_member_bitoffsets:
Gen.class_member_bitoffsets[ClassName] = {}
if ClassName not in Gen.class_methods:
Gen.class_methods[ClassName] = []
# source_sha1 已在函数开头统一计算(作为分类缓存键),此处直接使用。
# 注意:REnum 分支(上方)会按 actual_module_name 优先重新推导并已 return。
# 提前注册 struct:使用导入的 source_sha1 创建 opaque struct,
# 避免后续 _TryLoadStructFromStub 触发嵌套编译时,StructGen 用本地 sha1
# 创建缺少基类字段(如 GSListNode.Next)的本地类型定义。
Gen._get_or_create_struct(ClassName, source_sha1=source_sha1, packed=IsPacked)
# Track which module defines this class (for cross-module name mangling)
if source_sha1:
if not hasattr(Gen, 'class_sha1_map'):
Gen.class_sha1_map = {}
Gen.class_sha1_map[ClassName] = source_sha1
# --- 预扫描预注册:修复编译时序问题 ---
# _TryLoadStructFromStub → _parse_llvm_type → _specialize_generic_class
# 可能触发 list[str].__new__ 编译(其中调用 pool.alloc(48)),
# 此时 MemManager 尚未注册到虚表,导致直接调用返回 NULL → 段错误。
# 修复:在 _TryLoadStructFromStub 之前预扫描方法列表并提前注册虚表。
_has_methods_prescan = False
# PEP 695 泛型类的方法声明必须跳过:方法参数类型 T(如 list[T].append(item: T))
# 会被解析为 opaque struct,LLVM 报 "invalid type for function argument"。
# 字段(AnnAssign)仍正常加载,确保继承字段(如 GSListNode.Next)可被展平。
if not IsGenericClass:
# 虚表槽筛选:方法级 `novirtual def`(⇒ @t.NoVTable)不进槽、方法级
# `virtual def`(⇒ @t.CVTable)进槽——判据为 SymbolUtils.IsVtableSlotMethod
# (唯一规则源,与定义端 HandlesClassDef / TPV _should_method_be_virtual 同口径),
# 保证跨模块虚表槽序与定义模块逐槽一致。
_VtByClassLevel: bool = _VtableByClassLevelForImport(Node, Gen)
for _item in Node.body:
if isinstance(_item, ast.FunctionDef):
_has_methods_prescan = True
_MethodName = _item.name
# 构造函数(__new__/__init__/__before_init__)不放入 vtable,
# 避免子类与基类 vtable 大小不一致导致虚分派索引偏移
# (如 Module 有 __new__/__init__ 使 vtable=7,AST 无它们 vtable=5,
# 调用方按 AST 布局取 append@idx4 实际取到 Module.dump@idx4)。
if _MethodName in ('__new__', '__init__', '__before_init__'):
continue
_FuncFullName = f"{ClassName}.__init__" if _MethodName == "__init__" else f"{ClassName}.__call__" if _MethodName == "__call__" else f"{ClassName}.{_MethodName}"
_IsSlot: bool = (not IsCVTable) or IsVtableSlotMethod(_item, _VtByClassLevel)
if _IsSlot and _FuncFullName not in Gen.class_methods[ClassName]:
Gen.class_methods[ClassName].append(_FuncFullName)
if _has_methods_prescan and IsCVTable:
Gen._cross_module_vtable_classes.add(ClassName)
Gen.class_vtable.add(ClassName)
# 首次加载就传入 source_sha1,避免从其他模块的 output stub 中加载到错误的结构体定义
# (如 HandlesExprCall.py 的 output stub 中 43f27dda2e5b5923.Value 只有 5 字段,
# 而正确的 aaf8ba449d1e64c1.Value 有 6 字段)
self._TryLoadStructFromStub(ClassName, Gen, source_sha1=source_sha1)
# 如果 _get_or_create_struct 因 sha1 冲突创建了新的 opaque struct,
# 需用 source_sha1 重新加载正确的 stub body。
if source_sha1:
self._TryLoadStructFromStub(ClassName, Gen, source_sha1=source_sha1)
has_methods = False
# 解析 __provides__/__requires__/__require_must__ 编译期元数据(跨模块加载)
for item in Node.body:
if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
field_name: str = item.target.id
if field_name in ('__provides__', '__requires__', '__require_must__') and item.value:
values: list[str] = []
if isinstance(item.value, ast.List):
for elt in item.value.elts:
if isinstance(elt, ast.Constant) and isinstance(elt.value, str):
values.append(elt.value)
if field_name == '__provides__':
Gen.class_provides[ClassName] = values
elif field_name == '__requires__':
Gen.class_requires[ClassName] = values
elif field_name == '__require_must__':
Gen.class_require_must[ClassName] = values
# 字段收集中保护(与 _EmitClassLlvm 的 _collecting_members_set 同源):
# 本节的成员解析会重入结构体发射 —— CollectAnnAssignMember →
# ResolveAnnotationType → HandlesTypeMerge._HandleSubscript → CollectAll →
# _ResolveGenericSlice → _specialize_generic_class → _EmitClassLlvm →
# _generate_structs(),而此刻 class_members[ClassName] 只含"已扫到的
# 自有字段"(继承字段展平尚未执行,见下方 _all_inherited 段),
# 且 llvmlite 的 set_body 只允许调用一次 → 残缺布局被永久固化:
# BasicBlock.Lines: rawptr[GSList[Line]] → body 只到 Name 即止
# Function.Params: rawptr[GSList[Param]] → body 只到 RetTy 即止
# Line/Param 等则整类丢失继承字段 Next(GEP 索引整体前移一位)。
# 置入保护后重入的 _generate_structs 会跳过本类(StructGen.py:461),
# 待本节收集+展平完成后由下方显式 _generate_structs() 一次性 set_body。
Gen._collecting_members_set.add(ClassName)
_existing_member_names = {m[0] for m in Gen.class_members[ClassName]}
for item in Node.body:
# 跳过编译期元数据字段(__provides__/__requires__/__require_must__)
if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
if item.target.id in ('__provides__', '__requires__', '__require_must__'):
continue
parsed = self.CollectAnnAssignMember(item, Gen)
if parsed:
VarName, MemberType, TypeInfo = parsed
if TypeInfo is None:
if VarName not in _existing_member_names:
Gen.class_members[ClassName].append((VarName, MemberType))
_existing_member_names.add(VarName)
Gen.class_member_signeds[ClassName][VarName] = None
Gen.class_member_bitfields[ClassName][VarName] = 0
else:
if TypeInfo.IsBitField:
Gen.class_member_bitfields[ClassName][VarName] = TypeInfo.BitWidth
else:
if VarName not in _existing_member_names:
Gen.class_members[ClassName].append((VarName, MemberType))
_existing_member_names.add(VarName)
Gen.class_member_bitfields[ClassName][VarName] = 0
# 与 HandlesClassDef 对齐:联合类型字段(如 `list[str] | t.CPtr`)
# 记录泛型特化名到 class_member_element_class。否则跨模块 import
# 的类(如 TypeGenBase.CTypedefNames)缺此记录,_HandleLenLlvm
# 无法识别字段为 list → len() 误编译为 strlen → stdint 崩溃。
if isinstance(MemberType, ir.PointerType) and isinstance(MemberType.pointee, ir.IntType) and MemberType.pointee.width == 8:
if isinstance(item.annotation, ast.BinOp) and isinstance(item.annotation.op, ast.BitOr):
element_class: str | None = FindStructNameInAnnotationWithContext(
item.annotation, self.Trans, Gen)
if not element_class and TypeInfo.IsStruct and TypeInfo.Name and TypeInfo.Name in Gen.structs:
element_class = TypeInfo.Name
if element_class:
if ClassName not in Gen.class_member_element_class:
Gen.class_member_element_class[ClassName] = {}
Gen.class_member_element_class[ClassName][VarName] = element_class
elif (isinstance(MemberType, ir.PointerType)
and isinstance(MemberType.pointee, ir.IdentifiedStructType)
and isinstance(item.annotation, ast.BinOp)
and isinstance(item.annotation.op, ast.BitOr)):
spec_in_anno: str | None = FindStructNameInAnnotationWithContext(
item.annotation, self.Trans, Gen)
if spec_in_anno and '[' in spec_in_anno:
if ClassName not in Gen.class_member_element_class:
Gen.class_member_element_class[ClassName] = {}
Gen.class_member_element_class[ClassName][VarName] = spec_in_anno
# Subscript 容器字段(list[X] / rawptr[list[X]],如 includes/ast
# 的 Call.args / Import.names: rawptr[list[rawptr[AST]]]):非 BinOp
# 联合,上面两分支不命中 → 容器身份丢失 → 字段方法调用(get/len…)
# 分派失败报 "Undefined method 'get' in class 'rawptr'"(Converter.vp)。
# 用与特化生成端同源的 _resolve_type_arg_str 构造 element_class,
# 键名与 Gen.structs 中 list 特化名保持一致。
if isinstance(item.annotation, ast.Subscript):
_ann_sub_s: ast.Subscript = item.annotation
_subv_s: ast.AST = _ann_sub_s.value
_subname_s: str = getattr(_subv_s, 'id', None) or getattr(_subv_s, 'attr', '')
_elem_slice_s: ast.AST | None = None
if _subname_s == 'list':
_elem_slice_s = _ann_sub_s.slice
elif _subname_s == 'rawptr':
_slice_s: ast.AST = _ann_sub_s.slice
if isinstance(_slice_s, ast.Subscript):
# rawptr[泛型容器](rawptr[list[X]] / rawptr[GSList[Param]]):
# 容器身份 = 泛型特化名。list 继续走 _elem_slice_s 统一登记;
# 其余泛型容器直接登记 element_class(阻塞 B 根因链一环:
# 原实现只认内层 list,rawptr[GSList[Param]] 在此空转并跳过
# 裸结构分支 → element_class 缺失 → 字段/局部变量方法分派
# 丢失容器身份,FakeDuck 错绑 LinkedNode.append 写穿)。
_inner_sub_s: ast.Subscript = _slice_s
_iname_s: str = getattr(_inner_sub_s.value, 'id', None) or getattr(_inner_sub_s.value, 'attr', '')
if _iname_s == 'list':
_elem_slice_s = _inner_sub_s.slice
else:
_ec_gs: str | None = None
if _iname_s:
try:
_ec_gs = f'{_iname_s}[{self.Trans.ExprCallHandle._resolve_type_arg_str(_inner_sub_s.slice)}]'
except Exception:
_ec_gs = None
if _ec_gs:
if ClassName not in Gen.class_member_element_class:
Gen.class_member_element_class[ClassName] = {}
Gen.class_member_element_class[ClassName][VarName] = _ec_gs
else:
# rawptr[裸结构](如 rawptr[Token]):跨模块 stub 会把自引用/类内
# 指针字段退化为 i8*,链式成员访问(a.b.c)需 element_class 恢复
# struct 身份走 char*→struct cast。HEAD 态 `Token | t.CPtr` 联合
# 由上方 BinOp 分支登记;rawptr 重构后此分支补齐(阻塞 A 根因)。
# 与 HandlesClassDef 的 rawptr 分支对称。
_slc_name_s: str | None = None
if isinstance(_slice_s, ast.Name):
_slc_name_s = _slice_s.id
elif isinstance(_slice_s, ast.Attribute):
_slc_name_s = _slice_s.attr
_ec_target_s: str | None = None
if _slc_name_s:
if _slc_name_s in Gen.structs:
_ec_target_s = _slc_name_s
else:
_st_sha1_s: str | None = (getattr(Gen, 'class_sha1_map', {}) or {}).get(_slc_name_s)
if _st_sha1_s and f'{_st_sha1_s}.{_slc_name_s}' in Gen.structs:
_ec_target_s = f'{_st_sha1_s}.{_slc_name_s}'
else:
# 短名模糊扫描(对齐 HandlesAnnAssign 变量侧
# 补偿 L597-602):跨模块 `mod.Class` 形态的
# rawptr[Class] 字段在 class_sha1_map 尚无该短名
# 时上面两路全 miss → element_class 静默缺失 →
# 字段方法调用(factory.PermPool.alloc)链尾类名
# 丢失 → TPC 调用分派返回 i32 0 哨兵 → 误报
# "Unknown module: ...PermPool"。
for _ec_k_s in Gen.structs:
_ec_short_s: str = _ec_k_s.split('.')[-1] if '.' in _ec_k_s else _ec_k_s
if _ec_short_s == _slc_name_s:
_ec_target_s = _ec_k_s
break
if _ec_target_s:
if ClassName not in Gen.class_member_element_class:
Gen.class_member_element_class[ClassName] = {}
Gen.class_member_element_class[ClassName][VarName] = _ec_target_s
if _elem_slice_s is not None:
_ec_s: str | None = None
try:
_ec_s = f'list[{self.Trans.ExprCallHandle._resolve_type_arg_str(_elem_slice_s)}]'
except Exception:
_ec_s = None
if _ec_s:
if ClassName not in Gen.class_member_element_class:
Gen.class_member_element_class[ClassName] = {}
Gen.class_member_element_class[ClassName][VarName] = _ec_s
if TypeInfo.ByteOrder:
Gen.class_member_byteorders[ClassName][VarName] = TypeInfo.ByteOrder
else:
Gen.class_member_byteorders[ClassName][VarName] = ""
if item.value:
const = self._BuildScalarConstant(item.value, MemberType, Gen)
if const:
Gen.class_member_defaults[ClassName][VarName] = const
elif isinstance(item, ast.FunctionDef) and not IsGenericClass:
# PEP 695 泛型类跳过方法声明:T 参数会变成 opaque struct 触发
# "invalid type for function argument"。字段已由上面的 AnnAssign
# 分支加载,继承字段(如 GSListNode.Next)由后面的展平逻辑补充。
has_methods = True
MethodName = item.name
FuncFullName = f"{ClassName}.__init__" if MethodName == "__init__" else f"{ClassName}.__call__" if MethodName == "__call__" else f"{ClassName}.{MethodName}"
# 构造函数(__new__/__init__/__before_init__)不放入 vtable,
# 避免子类与基类 vtable 大小不一致导致虚分派索引偏移。
# register_method 内部也会过滤,此处仅跳过 class_methods.append。
# 方法级修饰符(virtual def/novirtual def)⇒ 是否占槽按
# SymbolUtils.IsVtableSlotMethod(唯一规则源)判定,与定义模块逐槽一致。
_IsSlot2: bool = (not IsCVTable) or IsVtableSlotMethod(
item, _VtableByClassLevelForImport(Node, Gen))
if MethodName not in ('__new__', '__init__', '__before_init__'):
if _IsSlot2 and FuncFullName not in Gen.class_methods[ClassName]:
Gen.class_methods[ClassName].append(FuncFullName)
Gen.register_method(ClassName, FuncFullName, _IsSlot2)
is_item_static = any(isinstance(d, ast.Name) and d.id == 'staticmethod' for d in item.decorator_list)
is_item_property = any(isinstance(d, ast.Name) and d.id == 'property' for d in item.decorator_list)
is_item_classmethod = any(isinstance(d, ast.Name) and d.id == 'classmethod' for d in item.decorator_list)
is_item_prop_setter = any(isinstance(d, ast.Attribute) and d.attr == 'setter' and isinstance(d.value, ast.Name) and d.value.id == 'property' for d in item.decorator_list)
is_item_prop_getter = any(isinstance(d, ast.Attribute) and d.attr == 'getter' and isinstance(d.value, ast.Name) and d.value.id == 'property' for d in item.decorator_list)
is_item_prop_deleter = any(isinstance(d, ast.Attribute) and d.attr == 'deleter' and isinstance(d.value, ast.Name) and d.value.id == 'property' for d in item.decorator_list)
# setter/deleter 使用不同的函数名后缀
DeclFuncName = FuncFullName
if is_item_prop_setter:
DeclFuncName = FuncFullName + '$set'
elif is_item_prop_deleter:
DeclFuncName = FuncFullName + '$del'
if DeclFuncName not in Gen.functions:
FuncDeclNode = ast.FunctionDef(
name=DeclFuncName,
args=item.args,
body=item.body,
decorator_list=item.decorator_list,
returns=item.returns
)
try:
self._EmitExternalFuncDeclLlvm(FuncDeclNode, Gen, is_class_method=not is_item_static and not is_item_classmethod, source_module_name=module_name)
except Exception as e:
raise
func_meta = FuncMeta.NONE
if is_item_static:
func_meta |= FuncMeta.STATIC_METHOD
if is_item_property:
func_meta |= FuncMeta.PROPERTY_GETTER
if is_item_classmethod:
func_meta |= FuncMeta.CLASS_METHOD
if is_item_prop_setter:
func_meta |= FuncMeta.PROPERTY_SETTER
if is_item_prop_getter:
func_meta |= FuncMeta.PROPERTY_GETTER
if is_item_prop_deleter:
func_meta |= FuncMeta.PROPERTY_DELETER
# setter/deleter 的 SymKey 使用带后缀的函数名
SymKey = DeclFuncName
if not self.Trans.SymbolTable.has(SymKey):
FuncInfo = CTypeInfo()
FuncInfo.Name = SymKey
FuncInfo.IsFunction = True
FuncInfo.MetaList = func_meta
self.Trans.SymbolTable.insert(SymKey, FuncInfo)
else:
existing = self.Trans.SymbolTable[SymKey]
if existing.MetaList == FuncMeta.NONE and func_meta != FuncMeta.NONE:
existing.MetaList = func_meta
# setter/deleter: 同时在原始 PropKey(不带后缀)下注册 MetaList
if is_item_prop_setter or is_item_prop_deleter:
base_existing = self.Trans.SymbolTable.lookup(FuncFullName)
if base_existing:
if func_meta != FuncMeta.NONE:
base_existing.MetaList = base_existing.MetaList | func_meta
else:
PropInfo = CTypeInfo()
PropInfo.Name = FuncFullName
PropInfo.IsFunction = True
PropInfo.MetaList = func_meta
self.Trans.SymbolTable.insert(FuncFullName, PropInfo)
# --- 跨模块继承字段展平 ---
# _EmitExternalClassDeclLlvm 只添加了类自身定义的字段(来自 .pyi),
# 没有展平父类(如 GSListNode[T])的继承字段,导致 class_members 缺少
# 继承字段,_get_member_offset 返回错误偏移。
# 修复:先尝试从已加载的父类获取真实字段名;若父类未加载,
# 则用 struct body 元素数推断继承字段数并补充占位条目 __inherited_N。
_ParentClass: str | None = None
_ParentClasses: list[str] = []
if Node.bases:
for _base in Node.bases:
_base_name: str | None = None
if hasattr(_base, 'id'):
_base_name = _base.id
elif hasattr(_base, 'attr'):
_base_name = _base.attr
elif isinstance(_base, ast.Subscript):
_spec_name: str | None = self.Trans.ExprCallHandle._ResolveGenericSlice(_base)
if _spec_name:
_base_name = _spec_name
else:
# _ResolveGenericSlice 失败(泛型基类未在当前模块注册为模板,
# 常见于跨模块导入:如 HandlesExprCall.py 导入 Value(GSListNode[Value]),
# 但 GSListNode 定义在 includes/linkedlist.py,尚未编译)。
# 直接从 AST 提取泛型基类名(如 GSListNode[Value] -> GSListNode),
# 后续继承字段展平逻辑会从 .pyi 加载该基类的字段。
if isinstance(_base.value, ast.Name):
_base_short_name: str = _base.value.id
elif isinstance(_base.value, ast.Attribute):
_base_short_name: str = _base.value.attr
else:
continue
# 尝试手动构造特化名并检查是否已有特化 class_members。
# 这解决了 _ResolveGenericSlice 失败但特化已通过其他路径
# (如 _parse_llvm_type)触发的情况。特化 class_members 中
# 的 T 已被正确替换为具体类型(如 BasicBlock),避免使用
# 未特化的 GSListNode 导致 T* 占位类型残留。
_slice_name: str | None = None
if isinstance(_base.slice, ast.Name):
_slice_name = _base.slice.id
elif isinstance(_base.slice, ast.Attribute):
_slice_name = _base.slice.attr
elif isinstance(_base.slice, ast.Subscript):
# 嵌套泛型(如 GSList[GSListNode[T]]),暂不处理
pass
_manual_spec_name: str | None = None
if _slice_name is not None:
_manual_spec_name = f"{_base_short_name}[{_slice_name}]"
if _manual_spec_name is not None and _manual_spec_name in Gen.class_members:
# 特化 class_members 已存在,直接使用(T 已被替换)
_base_name = _manual_spec_name
else:
_base_name = _base_short_name
else:
continue
if _base_name == ClassName:
continue
if _base_name in ('CVTable', 'Object', 'NoVTable', 'CStruct', 'CUnion', 'CEnum', 'REnum', 'Exception', 'Enum'):
continue
# 多继承:收集所有真实父类(声明顺序),不再 break。
# 总是记录父类名,即使父类 class_members 未加载。
# _get_member_offset 会沿 class_parent 链回退查找继承字段。
_ParentClasses.append(_base_name)
if _ParentClass is None:
_ParentClass = _base_name
if not hasattr(Gen, 'class_parent'):
Gen.class_parent = {}
Gen.class_parent[ClassName] = _ParentClass
if _ParentClasses:
if not hasattr(Gen, 'class_parents'):
Gen.class_parents = {}
Gen.class_parents[ClassName] = _ParentClasses
# 治本修复:在继承展开前,尝试从 stub/.pyi 加载父类的 class_members。
# 确保子类 class_members 能正确展平父类真实字段名(而非 __inherited_N 占位符),
# 避免 struct body 从不完整 class_members 生成导致 GEP 越界。
# 同时处理父类 class_members 全为占位符的情况(清除后重新加载)。
_any_parent_loaded: bool = False
_all_inherited: list[tuple] = []
# 展平完整性判定(供 `_members_flattened` 登记):仅当"每个声明父类都已在
# class_members 中就绪且其自身已展平",或"占位符路径已按 struct 槽位补齐
# 继承前缀"时才为 True。未完成(典型:声明期 struct 尚 opaque / 父类所在
# 模块 stub 未就绪)时不登记,后续查询会重跑本节把继承前缀补齐
# —— 使展平结果与"父类/struct 就绪的先后顺序"无关。
_flat_complete: bool = not _ParentClasses
_placeholder_applied: bool = False
# 按继承源去重的跨父类共享 seen 集合(必须在 for 循环外,跨所有父类
# 共享——否则每个父类新建 seen,菱形祖先(如 ClassGenBase)被每个
# 继承它的 ClassGen* 父类各展平一次 → 重复(Generator 曾 707 字段)
_seen_flat_cls: set[str] = set()
for _ParentClass in _ParentClasses:
if not _ParentClass:
continue
_parent_members_check: list = Gen.class_members.get(_ParentClass, [])
_parent_all_placeholders: bool = bool(_parent_members_check) and all(
n.startswith('__inherited_') for n, _ in _parent_members_check
)
# 2026-08-18 关键修复:无条件 force 重载父类成员(此前仅当成员为空/
# 全占位时才加载)。并行 worker 中父类可能已存在"不完整"的成员表
# (此前某次展平时祖先未加载完成即冻结部分字段,guard 又阻止重载),
# 导致 class_members[Generator] 83~106 vs 114 不确定 → this_adjust
# 错位 → 继承方法 self 偏移错误 → 运行时越界崩溃。force=True 会先
# 清空再重建(缺啥补啥、顺序与定义模块一致),且 _EmitExternalClassDeclLlvm
# 对其祖先递归同样强制重载,最终所有 worker 展平结果确定性一致。
_old_parent_members: list = _parent_members_check
if _parent_all_placeholders:
Gen.class_members[_ParentClass] = []
_sha1_map: dict[str, str] = getattr(Gen, 'ModuleSha1Map', {}) or {}
_parent_found: bool = False
# 已加载过的父类直接用其定义模块 sha1 强制重载(O(1)),
# 避免对每个父类线性扫描全部 ModuleSha1Map。
_parent_sha1: str | None = getattr(Gen, 'class_sha1_map', {}).get(_ParentClass)
if _parent_sha1:
try:
_parent_found = self._TryLoadClassMembersFromPyi(_ParentClass, f"{_parent_sha1}.stub.ll", Gen, force=True)
except Exception:
_parent_found = False
else:
for _mod_name, _mod_sha1 in _sha1_map.items():
_stub_name: str = f"{_mod_sha1}.stub.ll"
try:
_parent_found = self._TryLoadClassMembersFromPyi(_ParentClass, _stub_name, Gen, force=True)
except Exception:
_parent_found = False
if _parent_found:
break
_parent_members_check = Gen.class_members.get(_ParentClass, [])
if not _parent_found and _old_parent_members:
# 加载失败(父类不在任何 pyi 中,如泛型特化基类),回滚旧条目(有总比没有好)
Gen.class_members[_ParentClass] = _old_parent_members
if _ParentClass in Gen.class_members:
# 按继承源去重(与 HandlesClassDef._FlattenInherited 一致,2026-08-18):
# 菱形继承(Generator 同时继承 ClassGenMixin/ClassGenEnum/.../ClassGenBase,
# 而它们都继承 ClassGenBase)若直接拼接会重复展平 → 与 DeclarationGenerator
# stub struct 布局不一致 → 并行 worker 里 this_adjust 算不出偏移 → 继承
# 方法 self 不调整 → 运行时错位崩溃(register_reexport 写 0x3f)。
# 递归每类只处理一次,每类只贡献非祖先字段。
def _FlattenExt(cls_name: str) -> None:
if cls_name in _seen_flat_cls:
return
_seen_flat_cls.add(cls_name)
for _gp in Gen.class_parents.get(cls_name, []) or []:
_FlattenExt(_gp)
_anc_flat: set[str] = set()
_stk: list[str] = [cls_name]
_vis: set[str] = set()
while _stk:
_c = _stk.pop()
for _gp2 in Gen.class_parents.get(_c, []) or []:
if _gp2 in _vis:
continue
_vis.add(_gp2)
_stk.append(_gp2)
for _mn2, _mt2 in Gen.class_members.get(_gp2, []) or []:
_anc_flat.add(_mn2)
for _mn3, _mt3 in Gen.class_members.get(cls_name, []) or []:
if _mn3 == '__vtable__':
continue
if _mn3 in _anc_flat:
continue
_all_inherited.append((_mn3, _mt3))
_pd3: dict = dict(Gen.class_member_defaults.get(cls_name, {}))
if _mn3 in _pd3:
Gen.class_member_defaults[ClassName][_mn3] = _pd3[_mn3]
_FlattenExt(_ParentClass)
if _all_inherited:
# 防重复注册(2026-08-10):_EmitExternalClassDeclLlvm 可能被多个模块
# 加载路径重复调用(_LoadModuleDeclarationsFromFile / _LoadDeclarationsFromStubLlvm
# / _TryLoadClassMembersFromPyi 懒加载),此前调用已前置的父类字段会被再次
# 前置 → class_members 出现重复字段(如 BasicBlock: [Next, Next, Name, ...])
# → 与 struct elements 数量不匹配 → 属性偏移错乱。
# 【判据改为"前置段是否已等于祖先块"(2026-09-30)】旧判据是
# `nm2 not in {整表所有名字}`——它会把**与子类自有字段重名的继承项**删掉,
# 于是继承段与 body 的继承前缀失去一一对应:被删项**之后**的继承字段表内
# 下标整体前移 k(k = 被删项数),而查询侧回退路径用 `base + 表内下标`
# (FuncGen._get_member_offset)⇒ 落到**类型完全不同的槽**。现场:
# TypeGenCType 重复声明 5 个父类已有字段后,继承字段 LastBuiltinsTypedefResult
# 下标 32→27,写到 i32 槽 → "指针隐式转整数已禁止"。
# 新判据(幂等且不删块内条目):只问"表头前 len(块) 项的名字序列是否等于
# 祖先块的名字序列"——相等 ⇒ 整块已前置(本次是重复调用)⇒ 不再前置;
# 否则整块前置。**任何情况下都不删块内条目**,因此"同名自有字段 + 同名继承项"
# 会并存(body 里本来就是两个槽,定义端 HandlesClassDef 同样两者都在)。
PrependInheritedBlock(Gen, ClassName, _all_inherited)
# 父类字段已处理(可能此前已前置),跳过占位符路径避免叠加
_any_parent_loaded = True
if not _any_parent_loaded and _ParentClasses:
# 父类未加载到 class_members(常见于泛型特化基类如 GSListNode[Value],
# 其特化不在 .pyi 中)。用 struct body 元素数推断继承字段数。
_st = Gen.structs.get(ClassName)
# 【必须先取快照】`elements` 不是稳定值:`StructGen._generate_structs` 判定
# "struct body 与 class_members 不一致"时会**主动置 `elements = None`**
# (StructGen.py:579/:590,为强制重新派生的既有机制)。本分支在下方
# `_ResolveInheritedFieldNames`(加载父类成员 → _TryLoadClassMembersFromPyi
# → _EmitExternalClassDeclLlvm → _generate_structs)里会重入,重入期间本类
# 的 `elements` 可能被置回 None ⇒ 若在配对时(:2526)才读 `_st.elements`,
# 就是对 None 下标:裸 `TypeError: 'NoneType' object is not subscriptable`
# (2026-09-29 现场:astaux.vp 编译期 `_EmitExternalClassDeclLlvm` 崩,
# 外层 :647 `except Exception` 只 WARN "忽略节点 ClassDef" 就 continue ⇒
# 该类声明被整条静默丢弃)。
# 快照与 `_num_elements` 同源(都是 guard 通过的这一份 body),二者必须
# 一致:槽位数与槽类型取自同一时刻的布局,不能一个用旧、一个用新。
if isinstance(_st, ir.IdentifiedStructType) and _st.elements is not None:
_elements_ph: list = list(_st.elements)
_num_elements = len(_elements_ph)
# 如果类有 vtable ptr(struct 首元素),需减去 1
# 否则 vtable ptr 会被误当成 __inherited_0 占位条目,
# 导致子类继承时多出一个字段(如 Name(AST) 从 9 字段变成 10)
_has_vtable_ptr = IsCVTable or (has_methods and not IsNoVTable)
if _has_vtable_ptr and _num_elements > 0:
_num_elements -= 1
# struct 槽位起点:有 vtable 指针时首元素是 vtable,继承字段从元素 1 起
# (上面已把 vtable 从 _num_elements 扣掉,配对槽位必须同步右移一位,
# 否则会把 vtable 指针的类型当成第一个继承字段的类型)。
_slot0_ph: int = 1 if _has_vtable_ptr else 0
_num_members = len(Gen.class_members[ClassName])
if _num_elements > _num_members:
_num_inherited = _num_elements - _num_members
# 继承字段的**真实名字**:按声明顺序沿继承链取父类声明中的字段名
# (父类可能是泛型特化 `GSListNode[ParamNode]` —— 特化体不在任何
# .vpi 中,回退用模板名 `GSListNode` 加载其成员),与 struct 槽位
# 按序配对。这样展平结果直接是 `('Next', <槽位类型>)`,属性访问
# 命中 class_members 真实条目,**无需任何查找期回退**。
# 名字取不到时(父类声明也不可得)才退回 __inherited_N 占位名,
# 仅保证偏移正确。
_inh_names: list[str] = self._ResolveInheritedFieldNames(Gen, _ParentClasses, _num_inherited)
_inherited_ph: list[tuple] = []
for _i_ph in range(_num_inherited):
_nm_ph: str = _inh_names[_i_ph] if _i_ph < len(_inh_names) else f'__inherited_{_i_ph}'
_inherited_ph.append((_nm_ph, _elements_ph[_slot0_ph + _i_ph]))
# 占位符路径同样整体前置(继承字段在 struct 头部),与主路径保持一致
Gen.class_members[ClassName] = _inherited_ph + Gen.class_members[ClassName]
_placeholder_applied = True
if _ParentClasses:
# 完整展平的判据:占位符路径已按 struct 槽位补齐继承前缀,或
# 每个声明父类都已在 class_members 中就绪且其自身已展平。
_flat_complete = _placeholder_applied or all(
(_pc in Gen.class_members and _pc in Gen._members_flattened)
for _pc in _ParentClasses if _pc
)
# 成员收集 + 继承展平完成 → 解除收集中保护,并用完整 class_members 设置 body。
# 必须在此显式触发一次 _generate_structs():保护期内(上方 add)所有重入的
# _generate_structs 都跳过了本类,若此处不再触发,本类 struct 将保持 opaque
# (opaque 结构体上的 GEP 是非法 IR,llvm-as 会直接报错),而残缺布局正是
# 本保护要消除的目标。
Gen._collecting_members_set.discard(ClassName)
Gen._generate_structs()
if _flat_complete:
# 登记"该表已按父类展平",供 _TryLoadClassMembersFromPyi 的 guard 与
# _member_base 的查询前补齐使用。未登记 ⇒ 后续任一查询都可重跑本节补齐。
Gen._members_flattened.add(ClassName)
# ============================================================
# 跨模块 vtable 槽布局与定义模块一致:父类方法在前(子类覆盖放父类槽位),
# 子类新增方法在后。定义模块(HandlesClassDef)已按此合并,跨模块若只
# 注册自身方法(_has_methods_prescan),vtable 槽位会错位 →
# 虚分派调用错误函数(如 MemBuddy.alloc 被解析到槽 13=_fl_find_and_remove
# → 返回 None → "Generator alloc failed")。
# ============================================================
if _ParentClasses:
_all_parent_methods: list[str] = []
_parent_method_names_seen: set[str] = set()
for _pcls in _ParentClasses:
for _pm in list(Gen.class_methods.get(_pcls, [])):
_pm_name: str = _pm.split('.')[-1] if '.' in _pm else _pm
if _pm_name in ('__new__', '__init__', '__before_init__'):
continue
if _pm_name in _parent_method_names_seen:
continue
_parent_method_names_seen.add(_pm_name)
_all_parent_methods.append(_pm)
if _all_parent_methods:
_child_method_map: dict[str, str] = {}
for _m in Gen.class_methods.get(ClassName, []):
_mn: str = _m.split('.')[-1] if '.' in _m else _m
_child_method_map[_mn] = _m
# 从 .vpi 的 FunctionDef 补充子类方法映射(覆盖方法可能尚未注册)。
# 方法级 `novirtual def`(⇒ @t.NoVTable)不占槽,**不得**用它覆盖父类槽位
# ——与 TPV _collect_vtable_methods 的 child_nvt 分支同口径(子类标记
# NoVTable 时槽位仍指向实际定义类的实现)。
_VtByClassLevelC: bool = _VtableByClassLevelForImport(Node, Gen)
for _item_m in getattr(Node, 'body', []):
if isinstance(_item_m, ast.FunctionDef):
_mn2: str = _item_m.name
if _mn2 in ('__new__', '__init__', '__before_init__'):
continue
if not IsVtableSlotMethod(_item_m, _VtByClassLevelC):
continue
_child_method_map[_mn2] = f"{ClassName}.{_mn2}"
_new_methods: list[str] = []
_covered_names: set[str] = set()
for _pm2 in _all_parent_methods:
_mname2: str = _pm2.split('.')[-1] if '.' in _pm2 else _pm2
if _mname2 in _child_method_map:
_new_methods.append(_child_method_map[_mname2])
else:
_new_methods.append(_pm2)
_covered_names.add(_mname2)
for _m3 in list(Gen.class_methods.get(ClassName, [])):
_mn3: str = _m3.split('.')[-1] if '.' in _m3 else _m3
if _mn3 not in _covered_names:
_new_methods.append(_m3)
Gen.class_methods[ClassName] = _new_methods
if has_methods:
if IsCVTable:
Gen._cross_module_vtable_classes.add(ClassName)
Gen.class_vtable.add(ClassName)
if IsNoVTable:
Gen._cross_module_novtable.add(ClassName)
# __before_init__ 对所有跨模块结构体声明(定义侧无条件生成,导入侧也应无条件声明)
NewFuncName = f'{ClassName}.__before_init__'
if not Gen._has_function(NewFuncName):
# 复用前面已查找的 source_sha1(优先 actual_module_name 再 module_name),
# 通过 class_sha1_map 获取,避免此处只查 module_name 导致子模块(如 w32.win32file)查找失败
source_sha1 = getattr(Gen, 'class_sha1_map', {}).get(ClassName)
MangledName = Gen._mangle_name(NewFuncName) if not source_sha1 else f"{source_sha1}.{NewFuncName}"
StructType = Gen.structs.get(ClassName)
if StructType is not None:
StructPtrType = ir.PointerType(StructType)
NewFuncType = ir.FunctionType(ir.VoidType(), [StructPtrType])
# 走唯一幂等入口:查的是 NewFuncName 而建的是 MangledName,二者不同名
# ⇒ 此处若裸建,MangledName 已被 stub / 调用点占住时必抛
# llvmlite DuplicatedNameError。
NewFunc = Gen._CreateOrReuseFuncDecl(MangledName, NewFuncType)
Gen.functions[NewFuncName] = NewFunc
bitfields = Gen.class_member_bitfields.get(ClassName, {})
if any(v > 0 for v in bitfields.values()):
current_bit_offset = 0
for name, _ in Gen.class_members.get(ClassName, []):
bw = bitfields.get(name, 0)
Gen.class_member_bitoffsets[ClassName][name] = current_bit_offset
current_bit_offset += bw
# 声明发射结束:解除重入标记(与上方 add 配对)。
_emit_inprog.discard(_emit_key)
def _build_stub_index(self) -> None:
"""扫描所有 .stub.ll 文件构建 class_name → 条目列表索引
一次扫描替代 _TryLoadStructFromStub 的 609 次全量扫描。
增量策略:记录已解析文件集合 _stub_index_scanned,单次编译进程内
stub 文件只增不减(output 逐步填充),只增量解析新增文件;
无新增文件时仅做一次目录集合比较即返回(省去原签名失效后的全量重扫)。
零 syscall 快路径:本函数由 _TryLoadStructFromStub 的**每次索引 miss**
调用(profile:238k 次),而"stub 集合是否变化"由写入方显式置脏
(见 MarkStubSetDirty)⇒ 未置脏时直接返回,不再做
2× isdir + 2× listdir + 全量 endswith/join(这三项在 profile 里合计
约占总时长 30%:nt._path_isdir 491s、nt.listdir 183s、
np.join/splitroot/endswith ≈ 180s)。
"""
if not ImportHandle._stub_set_dirty:
return
ProjectRoot = self._find_project_root()
temp_dir = os.path.join(ProjectRoot, 'temp')
output_dir = os.path.join(ProjectRoot, 'output')
if not os.path.isdir(temp_dir) and not os.path.isdir(output_dir):
return
# 收集当前 stub 文件集合(绝对路径,仅文件名不读内容)
current_files: set[str] = set()
for scan_dir in (output_dir, temp_dir):
if not os.path.isdir(scan_dir):
continue
for filename in os.listdir(scan_dir):
if filename.endswith('.stub.ll'):
current_files.add(os.path.join(scan_dir, filename))
# 无新增文件 → 索引最新,清脏(下次 miss 走零 syscall 快路径)
if current_files <= self._stub_index_scanned:
ImportHandle._stub_set_dirty = False
return
# 只增量解析新增文件(首次调用时全量)
for stub_path in sorted(current_files - self._stub_index_scanned):
filename = os.path.basename(stub_path)
# 预检查文件存在性:os.listdir 与 open 之间文件可能被 --clean 或
# 并发写入删除(独立 profile 脚本曾触发 FileNotFoundError)
if not os.path.isfile(stub_path):
continue
is_output = stub_path.startswith(os.path.join(output_dir, ''))
file_sha1 = filename.replace('.stub.ll', '')
try:
with open(stub_path, 'r', encoding='utf-8') as f:
content = f.read()
for line in content.splitlines():
stripped = line.strip()
match = re.match(r'%"?((?:[a-f0-9]+\.)?([\w\[\]]+))"?\s*=\s*type\s*(.*)', stripped)
if match:
full_name = match.group(1)
short_name = match.group(2)
struct_body = match.group(3).strip()
# 索引键:short_name 和 full_name 都映射到同一条目
self._stub_index.setdefault(short_name, []).append((full_name, struct_body, file_sha1, is_output))
if full_name != short_name:
self._stub_index.setdefault(full_name, []).append((full_name, struct_body, file_sha1, is_output))
except FileNotFoundError:
# 竞态:isfile 检查后文件被删除,跳过即可(不应触发 strict raise)
continue
except Exception as _e:
if _config_mode == "strict":
raise
_vlog().warning(f"构建 stub 索引失败: {_e}", "Exception")
self._stub_index_scanned.add(stub_path)
# 增量解析完成 → 索引与目录一致,清脏(下次 miss 走零 syscall 快路径)
ImportHandle._stub_set_dirty = False
@staticmethod
def _ExternDeclEmitting(Gen: LlvmGeneratorMixin, class_name: str, source_sha1: str | None) -> bool:
"""该类(同名同源)的跨模块声明发射是否**正在进行中**(递归重入判定)。
标记由 `_EmitExternalClassDeclLlvm` 主体开头登记、末尾解除(键
`(ClassName, source_sha1)`)。定义模块 sha1 未知(None)时按类名保守判定:
None 与 '<sha1>' 两种形态可能指同一个类,宁可本次漏加载(由最外层收尾)
也不递归——`_EmitExternalClassDeclLlvm` → `_TryLoadStructFromStub(X)`
→ `_TryLoadClassMembersFromPyi(X)` → `_EmitExternalClassDeclLlvm(X)`
每轮仅 4 帧,且两条加载路径的缓存键(带/不带 sha1 前缀)互不相同、
失败时键还会被清除("仍 opaque ⇒ 删键重试")⇒ 缓存守卫挡不住,
会一直递归到 RecursionError(被上层 except 吞掉 ⇒ 类型解析静默退化)。
"""
_inprog: set | None = getattr(Gen, '_extern_decl_emit_in_progress', None)
if not _inprog:
return False
if (class_name, source_sha1) in _inprog:
return True
if source_sha1 is None:
return any(_k[0] == class_name for _k in _inprog)
return False
def _TryLoadStructFromStub(self, class_name: str, Gen: LlvmGeneratorMixin, source_sha1: str | None = None) -> None:
# 缓存键包含 Gen 实例标识,确保每个模块的 Gen 都能独立加载 struct 定义,
# 同时仍能防止同一 Gen 内的递归重入。原先使用纯 class_name 作为键会导致
# 跨模块 struct 类型对象无法分别 set_body(每个模块有独立的 self.structs)。
# 治本修复:当 source_sha1 指定时(同名类冲突情况),使用 sha1 前缀全名作为
# 缓存键,确保不同模块的同名类(如 stmts.py:Module 和 __module.py:Module)
# 不会互相阻止 stub 加载。
cache_key = (id(Gen), f"{source_sha1}.{class_name}" if source_sha1 else class_name)
if cache_key in self._struct_Load_cache:
return
# 先设置缓存防止递归重入,但如果加载失败则清除缓存允许后续重试
self._struct_Load_cache[cache_key] = True
# 使用 stub 索引替代全量扫描(609 次 × 100 文件 → 1 次扫描 + O(1) 查询)。
# 命中索引直接返回,不触发 _build_stub_index(避免每次调用做目录扫描);
# 仅在 miss 时才增量扫描(无新增文件时仅一次集合比较)。
entries = self._stub_index.get(class_name, [])
if not entries:
self._build_stub_index()
entries = self._stub_index.get(class_name, [])
if not entries:
del self._struct_Load_cache[cache_key]
return
# 选择最佳条目:
# 优先级 0: 当 source_sha1 指定时,优先选择 type_sha1 匹配的条目(治本修复:
# 同名类冲突时确保选择正确模块的 struct 定义)
# 优先级 1: 类型定义模块自身的 stub(file_sha1 == full_name 中的 sha1 前缀)
# 定义模块的 stub 包含完整的字段展平(含跨模块继承字段)
# 优先级 2: output(Phase2 生成)优于 temp(Phase1 生成)
# 优先级 3: 非 opaque(具体定义)优于 opaque
def _entry_priority(e: tuple[str, str, str, bool]) -> tuple[int, int, int, int]:
full_name, struct_body, file_sha1, from_output = e
type_sha1 = file_sha1
if '.' in full_name:
parts = full_name.split('.', 1)
if len(parts[0]) == 16 and all(c in '0123456789abcdef' for c in parts[0]):
type_sha1 = parts[0]
sha1_match = 0 if (source_sha1 and type_sha1 == source_sha1) else 1
is_defining = 0 if type_sha1 == file_sha1 else 1
is_temp = 0 if from_output else 1
is_opaque = 0 if (struct_body and struct_body != 'opaque') else 1
return (sha1_match, is_defining, is_temp, is_opaque)
best_entry = min(entries, key=_entry_priority)
full_name, struct_body, file_sha1, from_output = best_entry
# Bug 1 修复:从 full_name 提取 sha1 前缀(类型定义模块的 sha1),
# 而非使用 file_sha1(stub 文件所在模块的 sha1,可能只是导入方而非定义方)。
# 例如 output/35ffd4b9fbd082d1.stub.ll (main.py) 中包含
# %"5cde8c6813a2b6f3.Function" = type {...},
# 此时 file_sha1='35ffd4b9fbd082d1' (main.py) 但类型实际定义在
# __function.py (sha1='5cde8c6813a2b6f3'),必须用后者作为 source_sha1。
actual_sha1 = file_sha1
if '.' in full_name:
parts = full_name.split('.', 1)
if len(parts[0]) == 16 and all(c in '0123456789abcdef' for c in parts[0]):
actual_sha1 = parts[0]
is_packed = struct_body is not None and struct_body.startswith('<{') and struct_body.endswith('}>')
if struct_body and struct_body != 'opaque':
elem_types = []
if is_packed:
inner = struct_body[2:-1].strip()
else:
inner = struct_body.strip('{}').strip()
if inner:
for elem in inner.split(','):
elem = elem.strip()
if elem:
et = self._parse_llvm_type(elem, Gen, source_sha1=actual_sha1)
elem_types.append(et if et else ir.IntType(32))
try:
st = Gen._get_or_create_struct(class_name, source_sha1=actual_sha1, packed=is_packed)
if isinstance(st, ir.IdentifiedStructType) and st.is_opaque:
st.set_body(*elem_types)
if is_packed:
Gen.class_packed.add(class_name)
# 同步设置短名 struct 的 body:当存在 sha1 冲突时,_get_or_create_struct
# 返回 full_key struct,但代码可能通过短名引用旧 struct(由 setup_from_symbol_table
# 早期创建的 opaque 占位符,使用本地 sha1 前缀)。需要同步设置短名 struct
# 的 body,确保 GEP 指令能正确访问字段(修复跨模块类型定义冲突导致的
# 字段索引错位问题)。
short_st = Gen.structs.get(class_name)
if short_st is not None and short_st is not st and isinstance(short_st, ir.IdentifiedStructType) and short_st.is_opaque:
try:
short_st.set_body(*elem_types)
except Exception:
pass # set_body 可能因已设置而失败,忽略
except Exception as _e:
if _config_mode == "strict":
raise
_vlog().warning(f"处理导入失败: {_e}", "Exception")
stub_filename = actual_sha1 + '.stub.ll'
# 【递归环闭合边 · 重入短路】该类的声明发射尚未结束(`_EmitExternalClassDeclLlvm`
# 正在跑:见其主体开头的 `_extern_decl_emit_in_progress` 登记)⇒ 本调用是环内一环:
# 发射(X) → _TryLoadStructFromStub(X) →(此处)成员加载(X) → 发射(X) …
# 每轮仅 4 帧,且两条加载路径的缓存键(带/不带 source_sha1 前缀)互不相同、
# 失败时键还会被清除("仍 opaque ⇒ 删键重试")⇒ 缓存守卫挡不住 ⇒ 无限递归。
# 短路对象**只限"顺带加载成员"这一步**:上面的 struct body 解析照常执行
# (发射主体依赖它提前 set_body —— 否则 llvmlite `__len__` 的
# `assert self.elements is not None` 直接炸,见 _EmitExternalFuncDeclLlvm
# 的 `if _new_struct:`);成员由外层发射收尾统一收集。
if not ImportHandle._ExternDeclEmitting(Gen, class_name, source_sha1):
self._TryLoadClassMembersFromPyi(class_name, stub_filename, Gen)
# 根本修复:加载后 struct 仍为 opaque 时(如选中 'type opaque' 占位条目、
# 或 set_body 未生效),不保留成功缓存。保留会导致同 Gen 内后续调用永久
# 跳过补载,opaque 状态被固化,vtable 分派 GEP 直接崩溃。
# 失败状态可重试,待 stub 索引刷新 / 定义文件就绪后再加载。
_final_st = Gen.structs.get(class_name)
if isinstance(_final_st, ir.IdentifiedStructType) and _final_st.is_opaque:
try:
del self._struct_Load_cache[cache_key]
except KeyError:
pass
def EnsureClassMembersFlattened(self, class_name: str, Gen: LlvmGeneratorMixin) -> None:
"""查询前补齐:确保 `class_members[class_name]` 已按父类展平(幂等、顺序无关)。
背景:`_EmitExternalClassDeclLlvm` 的展平只在"父类成员表 + 声明期 struct
body 都已就绪"时才能算出继承前缀;若当时 struct 仍 opaque(定义模块 stub
尚未生成)或父类未就绪,表就停在"自有字段",而长度随之与 struct 元素数
不再自洽(delta = 缺失槽数)。此后 `_TryLoadClassMembersFromPyi` 的原
guard(`len>0` 即短路)会把这份残缺表永久冻结 ⇒ 该类**自有字段**的 GEP
下标缺了继承前缀而整体前移(`ExceptHandler.type` 返回 1 而非 8)。
本方法是该冻结的解除点(与 `FuncGen._member_base` 的查询侧调用配套):
只有"未登记已展平 且 该类有父类 且 struct 与成员表长度不自洽(含 struct
尚未登记)"才重跑展平;对已展平类、无父类类(含 REnum 变体等非继承键)
是纯内存判断直接返回,真正重跑的类另按依赖签名去重(见下)。
以 `(struct 元素数, 成员表长度)` 作为"依赖状态签名":同一签名下补齐无效
时不再重复尝试(避免依赖长期不就绪时在热路径反复重建);依赖一旦变化
(struct 被 `_generate_structs` 修正、或表被别处改动)签名变化即重试。
"""
if Gen is None:
return
_flat: set | None = getattr(Gen, '_members_flattened', None)
if _flat is None or class_name in _flat:
return
_inprog: set | None = getattr(Gen, '_members_flatten_in_progress', None)
if _inprog is None or class_name in _inprog:
return
if class_name in (getattr(Gen, '_collecting_members_set', None) or ()):
# 该类的字段正在收集中(重入路径)⇒ 不在此处重跑展平,
# 交由收集完成后的 `_EmitExternalClassDeclLlvm` 正常收尾。
return
_parents: list = (getattr(Gen, 'class_parents', {}) or {}).get(class_name) or []
if not _parents:
# 无父类 ⇒ 展平平凡成立。表非空时登记(后续查询直接短路);表为空时
# 不登记、也不在此补齐(那是"表未加载",由 _get_member_offset 既有的
# 懒加载路径负责,不在本专项范围内)。
if Gen.class_members.get(class_name):
_flat.add(class_name)
return
_tbl: list = Gen.class_members.get(class_name) or []
_st = Gen.structs.get(class_name)
_num_elements: int = -1
if isinstance(_st, ir.IdentifiedStructType) and _st.elements is not None:
_num_elements = len(_st.elements)
if (_num_elements - len(_tbl)) in (0, 1):
# struct 布局与成员表长度自洽(无 vtable 槽 / 恰一个 vtable 槽)
# ⇒ 已展平:登记,后续查询直接短路。
_flat.add(class_name)
return
_sig: tuple[int, int] = (_num_elements, len(_tbl))
_sigs: dict = getattr(Gen, '_members_flatten_repair_sig', None)
if _sigs is None:
_sigs = {}
Gen._members_flatten_repair_sig = _sigs
if _sigs.get(class_name) == _sig:
# 同一依赖状态下补齐无效(父类/struct 仍未就绪)⇒ 不重复尝试。
return
_sigs[class_name] = _sig
if not hasattr(self, 'Trans') or self.Trans is None:
return
_sha1: str | None = (getattr(Gen, 'class_sha1_map', {}) or {}).get(class_name)
_cands: list[str] = [f"{_sha1}.stub.ll"] if _sha1 else []
if not _cands:
_sha1_map: dict = getattr(Gen, 'ModuleSha1Map', {}) or {}
_cands = [f"{_s}.stub.ll" for _s in _sha1_map.values()]
_inprog.add(class_name)
try:
for _stub in _cands:
try:
if self._TryLoadClassMembersFromPyi(class_name, _stub, Gen):
break
except Exception:
continue
finally:
_inprog.discard(class_name)
def _ResolveInheritedFieldNames(self, Gen: LlvmGeneratorMixin,
ParentClasses: list[str], Limit: int) -> list[str]:
"""沿继承链取父类声明中的**真实字段名**(按声明顺序,跨父类去重)。
用途:`_EmitExternalClassDeclLlvm` 的继承展平在"父类 class_members 未就绪"
时只能按 struct 槽位数补 `__inherited_N` 占位名 —— 偏移对、**名字丢**,
于是继承字段的属性访问找不到字段(`[fast-fail] 属性 'Next' 在类 'ParamNode'
中找不到`)。本方法把名字补回来:按声明顺序遍历父类,逐个取其声明里的字段名
(AnnAssign 目标名,见 `_EmitExternalClassDeclLlvm` 的成员收集),与 struct
头部槽位按序配对。名字来自**声明**,槽位类型仍取 struct 自身元素,二者一致。
泛型基类回退:父类名可能是特化体 `X[Arg]`(特化体不在任何 .vpi 中 ⇒ 直接
加载必然落空),此时回退用**模板名** `X` 加载其成员 —— 模板里声明了字段名
(如 `GSListNode[T]: Next: T*` 的 `Next`),这正是编译期唯一可信的名字来源。
与 [`basegen._get_or_create_struct` 的 `_struct_sha1_map` 拼名规则]、
[DeclarationGenerator._get_inherited_members 取模板成员] 同一条"类名 → 模板/模块"
规则源。
返回的列表长度可能小于 Limit(父类声明不可得 / 类内字段名重复)——调用方对
缺失位置继续用 `__inherited_N` 占位,保证偏移语义不变。
"""
Names: list[str] = []
Seen: set[str] = set()
for _Pc in ParentClasses:
if not _Pc:
continue
_cands: list[str] = [_Pc]
_tpl: str = _Pc.split('[', 1)[0]
if _tpl and _tpl != _Pc:
_cands.append(_tpl)
_members: list = []
for _key in _cands:
_m_cur: list = Gen.class_members.get(_key) or []
if _m_cur and not any(n.startswith('__inherited_') for n, _ in _m_cur):
_members = _m_cur
break
_sha: str | None = getattr(Gen, 'class_sha1_map', {}).get(_key)
_loaded: bool = False
if _sha:
_loaded = self._TryLoadClassMembersFromPyi(_key, f"{_sha}.stub.ll", Gen)
else:
for _mod_name, _mod_sha1 in (getattr(Gen, 'ModuleSha1Map', {}) or {}).items():
if self._TryLoadClassMembersFromPyi(_key, f"{_mod_sha1}.stub.ll", Gen):
_loaded = True
break
_m_new: list = Gen.class_members.get(_key) or []
if _loaded and _m_new and not any(n.startswith('__inherited_') for n, _ in _m_new):
_members = _m_new
break
for _nm, _ in _members:
if _nm.startswith('__inherited_') or _nm == '__vtable__':
continue
if _nm not in Seen:
Seen.add(_nm)
Names.append(_nm)
if len(Names) >= Limit:
break
return Names
def _TryLoadClassMembersFromPyi(self, class_name: str, stub_filename: str, Gen: LlvmGeneratorMixin,
force: bool = False) -> bool:
"""从 .vpi/.pyi 声明文件加载类成员。返回 True 表示在 pyi 中找到该类并完成处理。
force=True 时(跨模块展平父类的强制重载):先清空该类的成员数据再重建,
保证并行 worker 间展平结果确定性一致(缺啥补啥、字段顺序与定义模块一致),
避免此前"部分成员被冻结、guard 阻止重载"导致的 class_members[Generator]
83~106 vs 114 不确定变体(this_adjust 错位崩溃)。
"""
ProjectRoot = self._find_project_root()
temp_dir = os.path.join(ProjectRoot, 'temp')
# guard(2026-xx 收紧):只在"表非空 **且已登记为已展平**"时短路。原实现
# 只看 `len>0`,于是"停在自有字段"的残缺表(展平未完成)一旦生成就再也不能
# 补齐(顺序敏感)——这正是自有字段下标缺继承前缀的成因。未展平的表允许
# 重跑 `_EmitExternalClassDeclLlvm`(其自有字段收集按名去重、继承前缀按名
# 去重,重跑幂等),从而与父类/struct 的就绪顺序无关。
# 【重入短路 · 与成员表长度解耦】该类**正在**被 `_EmitExternalClassDeclLlvm`
# 收集字段(`_collecting_members_set` 置位于本文件 :2054、解除于 :2481)时,
# 本次调用必是递归重入,直接返回、交由最外层收尾。
# 为什么判据不能带 `len(class_members)>0`:类内**第一个**字段的注解解析就会
# 重入本方法 —— CollectAnnAssignMember → ResolveAnnotationType → 泛型特化
# (list[T] 等)→ _EmitClassLlvm → StructGen._generate_structs
# → _TryLoadStructFromStub(X) → 本方法(X)。此刻 class_members[X] 仍为空
# (第一个字段尚未收集完),旧判据(要求 len>0)放行 ⇒ 无限递归(每轮 4 帧):
# _TryLoadClassMembersFromPyi(X) → _EmitExternalClassDeclLlvm(X)
# → _TryLoadStructFromStub(X) → _TryLoadClassMembersFromPyi(X) …
# 现场(2026-09-24 深度探针):match.MatchOr 的 patterns、stmts.Interactive 的
# body 等"首字段即指针/容器"的类;RecursionError 被上层 except 吞掉后表现为
# `WARN: … 忽略异常 maximum recursion depth exceeded` + 类型解析静默退化
# (失败的 typedef 报文、注解赋值 RHS 求值为空等次生现象)。
_collecting: set | None = getattr(Gen, '_collecting_members_set', None)
if ((not force) and (
(_collecting is not None and class_name in _collecting)
or (class_name in Gen.class_members
and len(Gen.class_members[class_name]) > 0
and class_name in Gen._members_flattened))):
# guard 命中:class_members 已存在(可能从别处预置、不完整)或该类正在
# 收集中;但 class_parents 可能未设置 → 多继承展平(_FlattenExt)的递归/anc_names
# 失效 → 菱形去重失败 → class_members 不完整(Generator 89/83 vs 114)
# → this_adjust 错位崩溃。此处补解析 .vpi 设置 class_parents(不重载
# class_members,避免覆盖可能更完整的已有数据)。
_path0: str = os.path.join(temp_dir, stub_filename.replace('.stub.ll', '.vpi'))
try:
_tree0: ast.AST | None = getattr(self, '_pyi_cache', {}).get(_path0)
if _tree0 is None and os.path.isfile(_path0):
with open(_path0, 'r', encoding='utf-8') as _fh0:
_tree0 = ast.parse(_fh0.read(), _path0)
getattr(self, '_pyi_cache', {})[_path0] = _tree0
if _tree0 is not None:
for _n0 in _tree0.body:
if isinstance(_n0, ast.ClassDef) and _n0.name == class_name:
_b0: list[str] = []
for _bb in _n0.bases:
_bn0: str | None = getattr(_bb, 'id', None) or getattr(_bb, 'attr', None)
if _bn0 and _bn0 not in ('Object', 'CVTable', 'NoVTable', 'CStruct', 'CUnion', 'CEnum', 'REnum', 'Exception', 'Enum'):
_b0.append(_bn0)
if _b0:
if not hasattr(Gen, 'class_parents'):
Gen.class_parents = {}
Gen.class_parents[class_name] = _b0
break
except Exception as _e0:
# 同族口径:此处原为 `except Exception: pass`(静默)。class_parents
# 未回填 ⇒ 多继承/菱形展平不完整 ⇒ class_members 长度不确定 ⇒
# this_adjust 错位。改为显式诊断(strict fail-fast / 非严格告警)。
_ReportDeclarationParseFailure(
_path0, _e0,
f"类 '{class_name}' 的 class_parents 未能回填,"
"多继承展平可能不完整(字段下标/this_adjust 可能错位)")
return False
pyi_basename = stub_filename.replace('.stub.ll', '.vpi')
pyi_path = os.path.join(temp_dir, pyi_basename)
# 已探测过的路径(_pyi_cache 含键,值为树或 None=不存在)不再重复
# os.path.isfile(Windows stat ~0.5ms/次;本函数 28543 次调用 × 探测
# ≈ 12.8s,占 _path_isfile 总耗时 63%)。
if pyi_path not in self._pyi_cache and not os.path.isfile(pyi_path):
# 回退读取旧缓存 .pyi
pyi_basename = stub_filename.replace('.stub.ll', '.pyi')
pyi_path = os.path.join(temp_dir, pyi_basename)
if self._pyi_cache_dir != temp_dir:
self._pyi_cache.clear()
self._pyi_cache_dir = temp_dir
if pyi_path not in self._pyi_cache:
if not os.path.isfile(pyi_path):
self._pyi_cache[pyi_path] = None
return False
try:
with open(pyi_path, 'r', encoding='utf-8') as f:
self._pyi_cache[pyi_path] = ast.parse(f.read(), pyi_path)
except Exception as e:
# 同族口径:原告警不带文件名("忽略异常"),改为显式诊断
# (strict fail-fast / 非严格告警)。.vpi 是编译器无糖产物,
# 解析失败属内部不变量被破坏,非严格模式仍缓存 None 并返回 False。
_ReportDeclarationParseFailure(
pyi_path, e,
f"类 '{class_name}' 的成员/父类信息不会加载,"
"class_members 可能残缺(字段下标/this_adjust 可能错位)")
self._pyi_cache[pyi_path] = None
return False
pyi_tree = self._pyi_cache[pyi_path]
if pyi_tree is None:
return False
source_sha1 = stub_filename.replace('.stub.ll', '')
module_name = None
if hasattr(Gen, 'ModuleSha1Map'):
for mod_name, mod_sha1 in Gen.ModuleSha1Map.items():
if mod_sha1 == source_sha1 and '.' not in mod_name:
module_name = mod_name
break
if module_name is None:
for mod_name, mod_sha1 in Gen.ModuleSha1Map.items():
if mod_sha1 == source_sha1:
module_name = mod_name
break
if module_name is None and hasattr(Gen, 'module_sha1') and Gen.module_sha1 == source_sha1:
module_name = '__self__'
# module_name 反查失败时(当前模块未直接导入定义模块),
# 仍需注册类成员和 vtable 信息。传 source_sha1 让 _EmitExternalClassDeclLlvm
# 正确设置 class_sha1_map 和注册 vtable。
# 类级 pyi 树节点索引:同一 (source_sha1, class_name) 的 ClassDef 节点在进程内
# 唯一(_pyi_cache 以 pyi_path 为键只 parse 一次),避免跨 worker 任务反复
# 线性扫描 pyi_tree.body 查找同一定义类。
found = False
node = self._extern_class_decl_cache.get((source_sha1, class_name))
if node is not None:
found = True
else:
for node in pyi_tree.body:
if isinstance(node, ast.ClassDef) and node.name == class_name:
self._extern_class_decl_cache[(source_sha1, class_name)] = node
found = True
break
if not found:
return False
if force:
# 强制重载:清空按类缓存的成员数据,重建出与定义模块一致的完整布局。
# 不清空则 dedup 只在末尾追加缺失字段 → 字段顺序与 struct elements 错位。
Gen.class_members[class_name] = []
# 同步撤销"已展平"登记:重建若因依赖未就绪而不完整,须允许后续查询再补齐。
Gen._members_flattened.discard(class_name)
for _gd_attr in ('class_member_defaults', 'class_member_signeds', 'class_member_bitfields',
'class_member_byteorders', 'class_member_bitoffsets'):
_gd: dict | None = getattr(Gen, _gd_attr, None)
if _gd is not None:
_gd.pop(class_name, None)
if hasattr(Gen, 'class_parents'):
Gen.class_parents.pop(class_name, None)
if hasattr(Gen, 'class_parent'):
Gen.class_parent.pop(class_name, None)
# 去重登记(force=True 时不登记,见下);在 try 之外先绑定,
# 便于发射失败时统一撤销登记。
_sigmap: dict | None = None
_sig_key: tuple | None = None
_sig: tuple | None = None
if not force:
# 【稳定态去重 · 依赖签名】guard(上方)只在"表非空 **且已登记已展平**"
# 时短路;而"父类是泛型特化/始终不可得"的类**永远不会**被登记为已展平
# ⇒ guard 永不命中 ⇒ 每次重入都重跑整套 `_EmitExternalClassDeclLlvm`
# (其内部又触发 _generate_structs → _TryLoadStructFromStub → 本方法,
# 形成每轮 4 帧的环)。profile(2026-09-29 main_prof_72472)实测:
# _TryLoadClassMembersFromPyi 142,888 次 → 6,683 次落到 _EmitExternalClassDeclLlvm
# (cum 103.1s)→ _generate_structs 8,059 次(cum 128.7s)
# → _TryLoadStructFromStub 88,970 次 → _parse_llvm_type 909,382 次
# 即"环上重复重跑"而非"首次发射"是主开销。
# 依赖签名(与 EnsureClassMembersFlattened 的"(struct 元素数, 成员表长度)
# 签名"同一思路):签名不变 ⇒ 上一次发射已经把该状态下能填的都填完了,
# 重跑是纯 no-op,直接短路。**只在签名变化时才允许重跑** ⇒ 与"父类/struct
# 就绪顺序无关"这条设计目标不冲突(依赖一变,签名就变,立刻放行)。
# 保守量:全局"已登记类数 / 已展平类数"——声明加载只要有任何进展就会变,
# 因此不会把"别的类补齐后本类该重跑"的情形漏掉。
# 键含 source_sha1:同名类可能分属不同模块,发射是 per-(sha1, 类名) 的。
_sigmap: dict | None = getattr(Gen, '_class_members_reload_sig', None)
if _sigmap is None:
_sigmap = {}
Gen._class_members_reload_sig = _sigmap
_sig_key: tuple = (source_sha1, class_name)
_sig: tuple = self._ClassMembersReloadSignature(Gen, class_name)
if _sigmap.get(_sig_key) == _sig:
return True
# 先登记、再发射:**登记必须在发射之前**,否则环上第一个内层重入
# (发射进行中,状态已变)仍会看到"无签名"而再发一次,去重被推迟一层;
# 先登记后内层重入即可在同状态下直接短路(环深 2 层即收敛)。
_sigmap[_sig_key] = _sig
try:
self._EmitExternalClassDeclLlvm(node, Gen, module_name=module_name, source_sha1=source_sha1)
except BaseException:
# 发射失败(fail-fast 或将被上层 except 吞掉):撤销本次签名登记,
# 保证"失败可重试"这一既有语义不被去重挡死——否则失败态会被签名
# 永久冻结(正是本仓反复踩过的"残缺表冻结"类缺陷)。
if _sigmap is not None and _sigmap.get(_sig_key) == _sig:
del _sigmap[_sig_key]
raise
return True
def _ClassMembersReloadSignature(self, Gen: LlvmGeneratorMixin, class_name: str) -> tuple:
"""`_TryLoadClassMembersFromPyi` 重跑的依赖签名(纯读,不产生副作用)。
收录"重跑能否产生新结果"的全部输入:
- 全局进度:**逐类**(`class_members` 键, 该表长度) 的完整元组(不是"类数"——
只看键数会在"某已有类的成员表继续增长"(父类成员后到)时看不出变化,
而父类成员正是继承前缀的来源);`_members_flattened` 长度;
`class_parents` / `class_parent` 的条目数(父链登记本身也是输入)
- 本类自身:成员表长度、是否已登记已展平、struct body 元素数(None ⇒ -1)
- 各父类:成员表长度 + 是否已展平(父类从 `class_parents` / `class_parent` 取;
两者都缺时用空元组 —— 此时本类依赖尚未建立,签名仍稳定,不影响收敛)
"""
_parents: list = list((getattr(Gen, 'class_parents', {}) or {}).get(class_name) or [])
if not _parents:
_p1: str | None = (getattr(Gen, 'class_parent', {}) or {}).get(class_name)
if _p1:
_parents = [_p1]
_flat: set = getattr(Gen, '_members_flattened', None) or set()
_st: object = Gen.structs.get(class_name)
_st_len: int = -1
if isinstance(_st, ir.IdentifiedStructType) and _st.elements is not None:
_st_len = len(_st.elements)
_parent_sig: tuple = tuple(
(_p, len(Gen.class_members.get(_p, []) or []), _p in _flat)
for _p in _parents
)
_cm_sig: tuple = tuple((_k, len(_v)) for _k, _v in Gen.class_members.items())
return (
_cm_sig,
len(_flat),
len(getattr(Gen, 'class_parents', {}) or {}),
len(getattr(Gen, 'class_parent', {}) or {}),
len(Gen.class_members.get(class_name, []) or []),
class_name in _flat,
_st_len,
_parent_sig,
)
def _BuildScalarConstant(self, value_node: ast.AST, target_type: ir.Type, Gen: LlvmGeneratorMixin) -> ir.Constant | None:
"""Build a scalar constant value from AST node, delegated to unified method."""
return self.Trans._BuildScalarConstantUnified(value_node, target_type, Gen)
@staticmethod
def _CountStubFilesInDir(DirPath: str) -> int:
"""目录下 `*.stub.ll` 的数量(同一「目录 + 目录 mtime」只 listdir 一次)。
为什么可缓存:stub 目录**只增不改**,且由主进程在批次之间写入 ⇒ 只有目录
条目增删才会改变计数,而 Windows/NTFS 在条目增/删/改名时会更新目录 mtime。
命中缓存即复用上次计数,省掉 `os.listdir` + 全量 `endswith('.stub.ll')`
判定 —— 这两个统计只为在 `_LookupStubFuncType` 里构造"是否新增 stub"的
签名,而该函数位于每次跨模块函数/方法查找的热路径上(20 个调用点),
工程越大(stub 越多)这份与编译工作量无关的开销越高。
取不到 stat(目录不存在 / 权限)时**不写缓存**,退化为每次实算:
计数仍是权威判据,缓存只决定"要不要重新 listdir",不改变任何判定结果。
"""
if not os.path.isdir(DirPath):
return 0
try:
_Mtime: int = os.stat(DirPath).st_mtime_ns
except OSError:
return sum(1 for _f in os.listdir(DirPath) if _f.endswith('.stub.ll'))
_Hit: tuple[int, int] | None = ImportHandle._stub_dir_count_cache.get(DirPath)
if _Hit is not None and _Hit[0] == _Mtime:
return _Hit[1]
_Count: int = sum(1 for _f in os.listdir(DirPath) if _f.endswith('.stub.ll'))
ImportHandle._stub_dir_count_cache[DirPath] = (_Mtime, _Count)
return _Count
def _LookupStubFuncType(self, func_name: str, Gen: LlvmGeneratorMixin) -> ir.FunctionType | None:
"""从 stub.ll 文件中查找指定函数的 LLVM 类型
扫描 output/ 和 temp/ 两个目录的 .stub.ll 文件。
关键修复:output/(Phase 2 完整结果)必须覆盖 temp/(Phase 1 不完整结果),
否则继承包装方法(在 Phase 2 的 _generate_inherited_method_wrappers 中生成)
将无法被解析,导致跨模块调用使用默认 i32 返回类型 → 64 位指针截断。
"""
ProjectRoot = self._find_project_root()
temp_dir = os.path.join(ProjectRoot, 'temp')
output_dir = os.path.join(ProjectRoot, 'output')
if not os.path.isdir(temp_dir) and not os.path.isdir(output_dir):
return None
# 文件数签名:检测 stub 新增(增量编译时 output 逐步填充完整签名)。
# 类级缓存(ImportHandle._stub_func_cache)跨 worker 任务共享;签名变化
# 只增量解析新增 stub 文件(编译进程内 stub 只增不改,旧文件不重扫,
# 省略每文件 os.path.isfile ——Windows stat ~0.5ms/次,原 5840 次 ≈ 6.5s)。
# 覆盖语义不变:先扫 temp/ 后扫 output/,output 同名声明覆盖 temp。
# 计数本身也不重复 listdir:走 _CountStubFilesInDir 的「目录 mtime 命中即复用」
# 缓存(原实现每次调用都 listdir + 全量 endswith 判定,纯浪费)。
output_count = self._CountStubFilesInDir(output_dir)
temp_count = self._CountStubFilesInDir(temp_dir)
signature = (output_count, temp_count)
if ImportHandle._stub_func_cache_signature != signature:
ImportHandle._stub_func_cache_signature = signature
for scan_dir in [temp_dir, output_dir]:
if not os.path.isdir(scan_dir):
continue
for filename in os.listdir(scan_dir):
if not filename.endswith('.stub.ll'):
continue
stub_path = os.path.join(scan_dir, filename)
if stub_path in ImportHandle._stub_func_scanned:
continue
ImportHandle._stub_func_scanned.add(stub_path)
source_sha1 = filename.replace('.stub.ll', '')
try:
with open(stub_path, 'r', encoding='utf-8') as f:
content = f.read()
for line in content.splitlines():
stripped = line.strip()
if not stripped.startswith('declare '):
continue
name_match = stripped.split('@', 1)
if len(name_match) < 2:
continue
stub_func_name = name_match[1].split('(', 1)[0].strip().strip('"')
match = re.match(r'declare\s+(.+?)\s+@("?[\w.]+"?)\s*\((.*)\)$', stripped)
if match:
stub_ret = match.group(1).strip()
stub_params = match.group(3).strip()
ImportHandle._stub_func_cache[stub_func_name] = (stub_ret, stub_params, '...' in stub_params, source_sha1)
except FileNotFoundError:
# listdir 与 open 之间文件被并发删除:跳过(等效旧 isfile 预检查)
continue
except Exception as _e:
if _config_mode == "strict":
raise
_vlog().warning(f"处理导入失败: {_e}", "Exception")
if func_name not in self._stub_func_cache:
return None
ret_str, params_str, is_variadic, source_sha1 = self._stub_func_cache[func_name]
ret_type = self._parse_llvm_type(ret_str, Gen, source_sha1=source_sha1, create_structs=False)
if ret_type is None:
ret_type = ir.IntType(32)
param_types = []
if params_str and params_str != 'void':
depth = 0
current = ''
for ch in params_str:
if ch == ',' and depth == 0:
p = self._strip_llvm_param_name(current.strip())
if p and p != '...':
pt = self._parse_llvm_type(p, Gen, source_sha1=source_sha1, create_structs=False)
if pt is not None:
param_types.append(pt)
else:
param_types.append(ir.PointerType(ir.IntType(8)))
current = ''
else:
if ch == '{':
depth += 1
elif ch == '}':
depth -= 1
current += ch
p = self._strip_llvm_param_name(current.strip())
if p and p != '...':
pt = self._parse_llvm_type(p, Gen, source_sha1=source_sha1, create_structs=False)
if pt is not None:
param_types.append(pt)
else:
param_types.append(ir.PointerType(ir.IntType(8)))
return ir.FunctionType(ret_type, param_types, var_arg=is_variadic)
def _strip_llvm_param_name(self, param_str: str) -> str:
"""剥离 LLVM IR 参数字符串中的参数名,仅保留类型部分"""
param_str = param_str.strip()
# 支持 %name 和 %"quoted.name" 两种参数名形式
result = re.sub(r'\s+%("[^"]*"|[\w.]+)\s*$', '', param_str)
return result.strip()
def _parse_simple_llvm_type(self, type_str: str) -> ir.Type | None:
"""解析简单的 LLVM 类型字符串(不创建结构体,避免副作用)"""
type_str = type_str.strip()
type_map = {
'void': ir.VoidType(),
'i1': ir.IntType(1),
'i8': ir.IntType(8),
'i16': ir.IntType(16),
'i32': ir.IntType(32),
'i64': ir.IntType(64),
'float': ir.FloatType(),
'double': ir.DoubleType(),
}
if type_str in type_map:
return type_map[type_str]
if type_str.endswith('*'):
pointee = type_str[:-1].strip()
pointee_type = self._parse_simple_llvm_type(pointee)
if pointee_type:
return ir.PointerType(pointee_type)
return ir.PointerType(ir.IntType(8))
if type_str.startswith('[') and ']' in type_str:
arr_match = re.match(r'\[(\d+)\s*x\s+(.+)\]', type_str)
if arr_match:
size = int(arr_match.group(1))
elem_type = self._parse_simple_llvm_type(arr_match.group(2).strip())
if elem_type:
return ir.ArrayType(elem_type, size)
if type_str.startswith('%'):
return ir.PointerType(ir.IntType(8))
return None
def _LoadDeclarationsFromStubLlvm(self, module_name: str, Gen: LlvmGeneratorMixin) -> None:
"""从 stub.ll 文件加载函数声明和结构体定义到 Gen"""
ProjectRoot = self._find_project_root()
temp_dir = os.path.join(ProjectRoot, 'temp')
if not os.path.isdir(temp_dir):
return
all_stub_files = [f for f in os.listdir(temp_dir) if f.endswith('.stub.ll')]
source_sig_files = getattr(self.Trans, '_source_module_sig_files', {})
target_sha1 = None
if module_name:
for key, val in source_sig_files.items():
if key == module_name or key.endswith('.' + module_name):
target_sha1 = os.path.basename(val).replace('.vpi', '').replace('.pyi', '')
break
if not target_sha1:
ModuleSha1Map = getattr(Gen, 'ModuleSha1Map', {})
target_sha1 = ModuleSha1Map.get(module_name)
if target_sha1:
target_stub = f"{target_sha1}.stub.ll"
if target_stub in all_stub_files:
stub_files = [target_stub]
else:
stub_files = []
else:
stub_files = []
needed_sha1s = set()
for filename in stub_files:
stub_path = os.path.join(temp_dir, filename)
try:
with open(stub_path, 'r', encoding='utf-8') as f:
content = f.read()
for line in content.splitlines():
stripped = line.strip()
if stripped.startswith('%') and '=' in stripped and 'type' in stripped:
for m in re.finditer(r'%\"?([a-f0-9]{16})\.', stripped):
needed_sha1s.add(m.group(1))
elif stripped.startswith('declare '):
for m in re.finditer(r'%\"?([a-f0-9]{16})\.', stripped):
needed_sha1s.add(m.group(1))
except Exception as _e:
if _config_mode == "strict":
raise
_vlog().warning(f"处理导入失败: {_e}", "Exception")
preLoad_files = []
for sha1 in needed_sha1s:
dep_stub = f"{sha1}.stub.ll"
if dep_stub in all_stub_files and dep_stub not in stub_files:
preLoad_files.append(dep_stub)
Load_order = preLoad_files + stub_files
for filename in Load_order:
stub_path = os.path.join(temp_dir, filename)
source_sha1 = filename.replace('.stub.ll', '')
is_preLoad = filename in preLoad_files
try:
with open(stub_path, 'r', encoding='utf-8') as f:
content = f.read()
for line in content.splitlines():
stripped = line.strip()
if stripped.startswith('%') and '=' in stripped and 'type' in stripped:
struct_def_match = re.match(r'%"?((?:[a-f0-9]+\.)?([\w\[\]]+))"?\s*=\s*type\s*(.*)', stripped)
if struct_def_match:
raw_name = struct_def_match.group(1)
# 治本修复:从 raw_name 提取类型定义的实际 sha1 前缀,
# 而非使用 stub 文件的 sha1。同一 stub 文件可能包含多个模块的
# struct 定义(前向引用),必须用类型名中的 sha1 作为 source_sha1,
# 否则同名类冲突检测会失效(如 __module.py stub 中的
# e64295c03b564a53.Module 前向引用会被错误地用 6e3f70566907c6cf 作为 sha1)。
actual_type_sha1 = source_sha1
if '.' in raw_name:
_rn_parts = raw_name.split('.', 1)
if len(_rn_parts[0]) == 16 and all(_c in '0123456789abcdef' for _c in _rn_parts[0]):
actual_type_sha1 = _rn_parts[0]
clean_name = _rn_parts[1]
else:
clean_name = raw_name
struct_body = struct_def_match.group(3).strip()
if struct_body and struct_body != 'opaque':
elem_types = []
is_packed = struct_body is not None and struct_body.startswith('<{') and struct_body.endswith('}>')
if is_packed:
inner = struct_body[2:-1].strip()
else:
inner = struct_body.strip('{}')
if inner:
for elem in inner.split(','):
elem = elem.strip()
if elem:
elem_type = self._parse_llvm_type(elem, Gen, source_sha1=actual_type_sha1)
if elem_type is not None:
elem_types.append(elem_type)
else:
elem_types.append(ir.IntType(32))
st = Gen._get_or_create_struct(clean_name, source_sha1=actual_type_sha1, packed=is_packed)
if isinstance(st, ir.IdentifiedStructType) and st.elements is None:
try:
st.set_body(*elem_types)
except Exception as _e:
if _config_mode == "strict":
raise
_vlog().warning(f"处理导入失败: {_e}", "Exception")
continue
# 全局变量声明:@name = external global TYPE(stub.ll 实际写入格式,
# 跨模块全局变量为裸名,如 @_sha1_store_arr;原正则只匹配
# @sha1.name 前缀格式,实际从未命中 → TPC 编译 TPV 时跨模块
# 全局变量(如 StubMerger._sha1_store_count)从未嵌入
# Gen.module.globals → _lookup_module_global 恒 None → store/load
# 全部丢失 → memset(null) 崩溃。此处兼容裸名 + sha1 前缀两种格式,
# 名称与定义一致(temp/*.text.ll 均为裸名)。
_gv_match = re.match(r'@("?)(?:([a-f0-9]{16})\.)?([\w.\[\]]+)\1\s*=\s*external\s+global\s+(\S.*)', stripped)
if _gv_match:
_gv_name: str = _gv_match.group(3)
_gv_sha1: str | None = _gv_match.group(2)
_gv_type_str: str = _gv_match.group(4).strip()
# 去掉尾部修饰(zeroinitializer / , align N),只保留类型
_gv_type_str = re.sub(r'\s+zeroinitializer.*$', '', _gv_type_str)
_gv_type_str = re.sub(r'\s*,\s*align\s+\d+.*$', '', _gv_type_str)
if _gv_name not in Gen.module.globals:
_gv_ty: ir.Type | None = self._parse_llvm_type(_gv_type_str, Gen, source_sha1=source_sha1)
if _gv_ty is not None:
_gv = ir.GlobalVariable(Gen.module, _gv_ty, name=_gv_name)
_gv.linkage = 'external'
continue
if not stripped.startswith('declare '):
continue
if is_preLoad:
continue
func_name_match = stripped.split('@', 1)
if len(func_name_match) < 2:
continue
func_name = func_name_match[1].split('(', 1)[0].strip().strip('"')
if not module_name:
continue
# bare 名(无 16 位 hex SHA1 前缀)表示 CExport,调用点需用纯名
_Prefix: str = func_name.split('.', 1)[0]
if not (len(_Prefix) == 16 and all(_c in '0123456789abcdef' for _c in _Prefix)):
Gen._export_funcs.add(func_name)
try:
func_type = self._parse_llvm_declare(stripped, Gen)
if func_type is not None:
# __new__ 方法应返回类指针类型(堆分配替换),与 _GetFunctionSignatureLlvm/_EmitFunctionLlvm 保持一致
# .stub.ll 由 .pyi 生成,.pyi 生成器对无返回类型注解的 __new__ 默认使用 t.CInt,这里修正为 ClassName*
if '.__new__' in func_name:
_np = func_name.split('.')
if len(_np[0]) == 16 and all(_c in '0123456789abcdef' for _c in _np[0]):
_new_class = _np[1]
else:
_new_class = _np[0]
_new_struct = Gen.structs.get(_new_class)
if _new_struct is None:
_full_name = func_name.split('.__new__')[0]
_new_struct = Gen.structs.get(_full_name)
if _new_struct:
_ret_type = _new_struct if isinstance(_new_struct, ir.PointerType) else ir.PointerType(_new_struct)
func_type = ir.FunctionType(_ret_type, func_type.args, var_arg=func_type.var_arg)
if func_name in Gen.functions:
existing_func = Gen.functions[func_name]
existing_ftype = getattr(existing_func, 'ftype', None) or existing_func.type.pointee
if str(existing_ftype) != str(func_type):
# 类型不一致:清理 module.globals + module.scope._useset 后重建,
# 避免 DuplicatedNameError。必须同时清 existing_func.name(真名)
# 与 func_name(字典键):别名场景下两者不同(如
# Gen.functions['ExprGenAttr.find_global_in_module'] 指向名为
# 'dddc595175f00c66.ExprGenAttr.find_global_in_module' 的对象),
# 只删真名会留下字典键仍占住 module.scope(llvmlite 按名字去重)
# → 重建仍抛 DuplicatedNameError(报文只有名字,不含冲突双方)。
module_scope = getattr(Gen.module, 'scope', None)
_useset = getattr(module_scope, '_useset', None)
for _stale_name in {existing_func.name, func_name}:
_stale = Gen.module.globals.get(_stale_name)
if isinstance(_stale, ir.Function):
del Gen.module.globals[_stale_name]
if _useset is not None:
_useset.discard(_stale_name)
func = Gen._CreateOrReuseFuncDecl(func_name, func_type)
Gen.functions[func_name] = func
if '.' in func_name:
short_name = func_name.split('.', 1)[1]
Gen.functions[short_name] = func
else:
func = Gen._get_or_declare_function(func_name, func_type)
Gen.functions[func_name] = func
if '.' in func_name:
short_name = func_name.split('.', 1)[1]
Gen.functions[short_name] = func
except Exception as _e:
from lib.core.LLVMCG.FuncGen import FailFastError as _FFE
if isinstance(_e, _FFE):
# 声明创建/签名的 fail-fast 必须上抛(见 _LoadModuleDeclarationsFromFile
# 同款说明):吞掉它会让该声明静默缺失、并把半注册名字留给下一轮。
raise
if _config_mode == "strict":
raise
_vlog().warning(f"处理导入失败: {_e}", "Exception")
except Exception as _e:
from lib.core.LLVMCG.FuncGen import FailFastError as _FFE
if isinstance(_e, _FFE):
raise
if _config_mode == "strict":
raise
_vlog().warning(f"处理导入失败: {_e}", "Exception")
def _TryLoadFuncDeclsFromOutputStub(self, struct_name: str, Gen: LlvmGeneratorMixin) -> bool:
"""按需从 output 目录的 stub.ll 加载泛型特化方法声明。
当 temp stub 不包含泛型特化(如 list[str].__iter__)时,
从 output stub(由 _split_ll 从完整 IR 生成)中按需加载。
struct_name: 结构体名,如 '9163064cf3eb88f4.list[str]' 或 'list[str]'
返回 True 如果找到了并加载了新函数声明。
"""
ProjectRoot = self._find_project_root()
output_dir = os.path.join(ProjectRoot, 'output')
if not os.path.isdir(output_dir):
return False
# 提取短名称和 SHA1 前缀
sha1_prefix: str | None = None
if '.' in struct_name:
parts: list[str] = struct_name.split('.', 1)
if len(parts[0]) == 16 and all(c in '0123456789abcdef' for c in parts[0]):
sha1_prefix = parts[0]
short_name: str = parts[1]
else:
short_name = struct_name
else:
short_name = struct_name
# 确定要搜索的 stub 文件列表
stub_files_to_search: list[str] = []
if sha1_prefix:
target_stub = f'{sha1_prefix}.stub.ll'
target_path = os.path.join(output_dir, target_stub)
if os.path.isfile(target_path):
stub_files_to_search.append(target_stub)
else:
# 没有SHA1前缀,搜索所有 output stub
for fname in os.listdir(output_dir):
if fname.endswith('.stub.ll'):
stub_files_to_search.append(fname)
loaded_any: bool = False
for fname in stub_files_to_search:
stub_path = os.path.join(output_dir, fname)
try:
with open(stub_path, 'r', encoding='utf-8') as f:
content = f.read()
for line in content.splitlines():
stripped = line.strip()
if not stripped.startswith('declare '):
continue
# 检查是否是目标结构体的方法
if f'.{short_name}.' not in stripped:
continue
try:
func_type = self._parse_llvm_declare(stripped, Gen)
if func_type is None:
continue
# 提取函数名:最后一个 @ 到第一个 ( 之间的内容
paren_pos: int = stripped.index('(')
at_pos: int = stripped.rindex('@', 0, paren_pos)
func_name: str = stripped[at_pos + 1:paren_pos].strip().strip('"')
if func_name not in Gen.functions:
# module.globals 已有同名声明 → 复用(llvmlite NameScope
# 按名字去重,重复 ir.Function 必抛 DuplicatedNameError;
# 此前该异常被下方 except 静默吞掉 → 声明未登记 → 后续
# 调用点找不到签名)
g_ex_stub = Gen.module.globals.get(func_name)
if g_ex_stub is not None:
func = g_ex_stub
else:
func = Gen._CreateOrReuseFuncDecl(func_name, func_type)
Gen.functions[func_name] = func
if '.' in func_name:
short_fn = func_name.split('.', 1)[1]
Gen.functions[short_fn] = func
loaded_any = True
except Exception as _e:
from lib.core.LLVMCG.FuncGen import FailFastError as _FFE
if isinstance(_e, _FFE):
# 见 _LoadModuleDeclarationsFromFile 同款说明:fail-fast 必须上抛。
# 原实现是裸 `except Exception: pass` —— 创建中途失败(半注册名字)
# 会在这里被彻底吞掉,后续同名创建只能报"名字悬空占用"。
raise
_vlog().warning(
f"按需加载 output stub 声明失败: '{stripped}' → {_e}", "Exception")
except Exception as _e:
from lib.core.LLVMCG.FuncGen import FailFastError as _FFE
if isinstance(_e, _FFE):
raise
if _config_mode == "strict":
raise
_vlog().warning(f"按需加载 output stub 失败: {_e}", "Exception")
return loaded_any
def _parse_llvm_declare(self, declare_line: str, Gen: LlvmGeneratorMixin) -> ir.FunctionType | None:
"""解析 LLVM declare 语句"""
match = re.match(r'declare\s+(.+?)\s+@("?[^()]+"?)\s*\((.*)\)$', declare_line)
if not match:
return None
ret_type_str = match.group(1).strip()
params_str = match.group(3).strip()
ret_type = self._parse_llvm_type(ret_type_str, Gen)
if ret_type is None:
return None
param_types = []
if params_str and params_str != 'void':
depth = 0
current = ''
for ch in params_str:
if ch == ',' and depth == 0:
p = self._strip_llvm_param_name(current.strip())
if p and p != '...':
pt = self._parse_llvm_type(p, Gen)
if pt is not None:
param_types.append(pt)
current = ''
else:
if ch == '{':
depth += 1
elif ch == '}':
depth -= 1
current += ch
p = self._strip_llvm_param_name(current.strip())
if p and p != '...':
pt = self._parse_llvm_type(p, Gen)
if pt is not None:
param_types.append(pt)
is_variadic = '...' in params_str
return ir.FunctionType(ret_type, param_types, var_arg=is_variadic)
@staticmethod
def _IsGenIndependentLlvmType(T: ir.Type) -> bool:
"""该 llvm 类型是否**不含具名结构体**(⇒ 结果与 `Gen.structs` 状态无关,可缓存)。
具名结构体(`IdentifiedStructType`)由 `Gen._get_or_create_struct` 创建、
会被 `_generate_structs` 清除重建(instance 与注册表状态绑定),且
`create_structs=False` 分支返回的 None 表示"此刻未加载"(状态依赖)
⇒ 这两类结果一律不进 memo。
"""
if isinstance(T, ir.IdentifiedStructType):
return False
if isinstance(T, ir.PointerType):
return ImportHandle._IsGenIndependentLlvmType(T.pointee)
if isinstance(T, ir.ArrayType):
return ImportHandle._IsGenIndependentLlvmType(T.element)
if isinstance(T, ir.VectorType):
return ImportHandle._IsGenIndependentLlvmType(T.element)
if isinstance(T, ir.LiteralStructType):
return all(ImportHandle._IsGenIndependentLlvmType(_e) for _e in T.elements)
if isinstance(T, ir.FunctionType):
return (ImportHandle._IsGenIndependentLlvmType(T.return_type)
and all(ImportHandle._IsGenIndependentLlvmType(_a) for _a in T.args))
return True
def _parse_llvm_type(self, type_str: str, Gen: LlvmGeneratorMixin, source_sha1: str | None = None, create_structs: bool = True) -> ir.Type | None:
"""`_parse_llvm_type_impl` 的 memo 包装(只缓存与 Gen 状态无关的结果)。
profile(2026-09-29 main_prof_72472):本函数被调 1,689,809 次
(cum 46.7s,其中 909,382 次来自 `_TryLoadStructFromStub`、57,525 次来自
`_LoadDeclarationsFromStubLlvm`、683,127 次是自递归),并且每次 `X*` 都要
新建 `ir.PointerType` —— 全程序 `_TypedPointerType.__init__` 4,596,463 次
(≈12–15s)主要来自这里。而 `i8*`/`i32`/`[N x i8]` 这些**纯字面类型串**的
解析结果与任何 Gen 状态无关,重复解析纯属浪费 ⇒ 加 memo。
"""
_key_str: str = type_str.strip()
_key: tuple = (_key_str, source_sha1, create_structs)
_memo: dict = self._llvm_type_memo
try:
return _memo[_key]
except KeyError:
pass
_res: ir.Type | None = self._parse_llvm_type_impl(
_key_str, Gen, source_sha1=source_sha1, create_structs=create_structs)
if _res is not None and ImportHandle._IsGenIndependentLlvmType(_res):
_memo[_key] = _res
return _res
def _parse_llvm_type_impl(self, type_str: str, Gen: LlvmGeneratorMixin, source_sha1: str | None = None, create_structs: bool = True) -> ir.Type | None:
"""解析 LLVM 类型字符串"""
type_str = type_str.strip()
# 模块级常量(原为每调用重建的字典字面量,见 _LLVM_PRIMITIVE_TYPE_MAP 注释)
type_map: dict[str, ir.Type] = _LLVM_PRIMITIVE_TYPE_MAP
if type_str in type_map:
return type_map[type_str]
if type_str.startswith('{') and type_str.endswith('}'):
inner = type_str[1:-1].strip()
field_types = []
depth = 0
current = ''
for ch in inner:
if ch == ',' and depth == 0:
field_types.append(current.strip())
current = ''
else:
if ch == '{':
depth += 1
elif ch == '}':
depth -= 1
current += ch
if current.strip():
field_types.append(current.strip())
llvm_fields = []
for ft in field_types:
parsed = self._parse_llvm_type(ft, Gen, source_sha1=source_sha1, create_structs=create_structs)
if parsed is not None:
llvm_fields.append(parsed)
else:
return None
if llvm_fields:
return ir.LiteralStructType(llvm_fields)
return None
if type_str.endswith('*'):
pointee = type_str[:-1].strip()
pointee_type = self._parse_llvm_type(pointee, Gen, source_sha1=source_sha1, create_structs=create_structs)
if pointee_type is not None:
return ir.PointerType(pointee_type)
# 被指类型是**具名聚合**(`%...`)却在"允许建结构体"模式下解析不出来:
# 具名类型必然可由 _get_or_create_struct 兜出,取不到即内部不变量已被破坏
# (成员表/布局两链不同源、名字带多余前缀等)。静默退化成 i8* 会把
# 「结构体指针」悄悄变成通用指针,外层再套一层即成 i8**(现场:
# Generator.ExprGenConst 声明 %"...ExprGenConst"* 却落到 i8**)。
# create_structs=False 时未加载不属异常 ⇒ 只在该模式下响亮失败。
if create_structs and pointee.startswith('%'):
from lib.core.LLVMCG.FuncGen import FailFastError as _FFE
raise _FFE(
f"具名类型指针的被指类型解析失败:'{type_str}' 的被指部分 '{pointee}'"
f" 在 create_structs=True 下仍取不到结构体(source_sha1={source_sha1})"
f"—— 拒绝静默退化为 i8*(会使结构体指针变通用指针,再套一层即 i8**)")
return ir.PointerType(ir.IntType(8))
if type_str.startswith('[') and ']' in type_str:
arr_match = re.match(r'\[(\d+)\s*x\s+(.+)\]', type_str)
if arr_match:
size = int(arr_match.group(1))
elem_type = self._parse_llvm_type(arr_match.group(2).strip(), Gen, source_sha1=source_sha1, create_structs=create_structs)
if elem_type is not None:
return ir.ArrayType(elem_type, size)
typedef_names = {'typedef', 'CType', 'CVolatile', 'CEnum', 'CUnion', 'CStruct', 'enum', 'REnum', 'renum'}
# 类型名内允许 '*':泛型特化名可带指针层实参(如
# %"<模sha1>.list[<sha1>.AST*]"*)。此前字符集缺 '*' 会把名字截断在
# 内层 '*' 处(丢掉尾随 ']'),随后 gc_base/gc_args 重解析出
# list[<sha1>.AST](少一层指针)并 declare_only 特化出无方法体的
# 假类型 → 调用点引用未定义的 <模sha1>.list[<sha1>.AST].__len__
# (链接期 undefined symbol 根因)。
struct_match = re.match(r'%"?((?:[a-f0-9]+\.)?[\w.\[\]*]+)"?', type_str)
if struct_match:
raw_name = struct_match.group(1)
actual_source_sha1 = source_sha1
if '.' in raw_name:
parts = raw_name.split('.', 1)
if len(parts[0]) == 16 and all(c in '0123456789abcdef' for c in parts[0]):
actual_source_sha1 = parts[0]
struct_name = parts[1]
else:
struct_name = raw_name
if struct_name not in typedef_names:
# 泛型类特化(如 list[str]):触发特化创建结构体和方法声明
# 使用 declare_only=True 避免在引用模块中生成方法体(定义由定义模块提供)
if '[' in struct_name and actual_source_sha1:
if struct_name in Gen.structs:
return Gen.structs[struct_name]
gc_base: str = struct_name.split('[')[0]
gc_args_raw: str = struct_name[len(gc_base):]
gc_type_args: list[str] = []
for gc_arg_part in gc_args_raw.split(']['):
gc_arg_part = gc_arg_part.strip('[]')
if gc_arg_part:
gc_type_args.append(gc_arg_part)
if (gc_type_args
and hasattr(self.Trans, 'ClassHandler')
and hasattr(self.Trans.ClassHandler, '_generic_class_templates')
and gc_base in self.Trans.ClassHandler._generic_class_templates):
gc_type_names: list[str] = []
for gc_ta in gc_type_args:
gc_tn: str = gc_ta
if gc_ta == 'int':
gc_tn = 'CInt'
elif gc_ta == 'double':
gc_tn = 'CDouble'
elif gc_ta == 'float':
gc_tn = 'CFloat'
elif gc_ta == 'char':
gc_tn = 'CChar'
elif gc_ta == 'bool':
gc_tn = 'CBool'
gc_type_names.append(gc_tn)
saved_module_sha1: str | None = getattr(Gen, 'module_sha1', None)
saved_trans_sha1: str | None = getattr(self.Trans, '_module_sha1', None)
Gen.module_sha1 = actual_source_sha1
self.Trans._module_sha1 = actual_source_sha1
try:
gc_spec: str | None = self.Trans.ClassHandler._specialize_generic_class(
gc_base, gc_type_args, Gen, type_names=gc_type_names,
declare_only=True)
except Exception:
gc_spec = None
Gen.module_sha1 = saved_module_sha1
self.Trans._module_sha1 = saved_trans_sha1
if gc_spec and gc_spec in Gen.structs:
return Gen.structs[gc_spec]
if create_structs:
return Gen._get_or_create_struct(struct_name, source_sha1=actual_source_sha1)
else:
existing = Gen.structs.get(struct_name)
if existing is not None:
return existing
# 尝试用 source_sha1 前缀的全名查找
if actual_source_sha1:
full_key = f"{actual_source_sha1}.{struct_name}"
existing_full = Gen.structs.get(full_key)
if existing_full is not None:
return existing_full
return None
return ir.IntType(32)
return None
def _strip_param_name(self, param_str: str) -> str:
"""从 LLVM 参数字符串中剥离参数名,只保留类型部分
例如: '%"sha1.struct_name"* %param_name' -> '%"sha1.struct_name"*'
'i8* %path' -> 'i8*'
'i32' -> 'i32'
"""
param_str = param_str.strip()
stripped = re.sub(r'\s+%[\w.]+\s*$', '', param_str)
return stripped.strip()