fix(LlvmGenerator/Phase2Translator): 本模块声明的类钉死本模块 SHA1 前缀,消除同名类前缀漂移
类别:a(编译器缺陷)
现象(上一版 ab906f2c 之后的下一步):
includes/ast/parser.vp:1774 `kw: rawptr[AST] = KwNode(ps.pool, None, val)` 报
TypeError: Type of #1 arg mismatch:
%"fef783da4d24330d.Keyword"* != %"8312ce86a2fd82c0.Keyword"*
(llvmlite 该报文是「形参 != 实参」且下标 1-based ⇒ 实际就是**第 0 个形参 self**)
根因(磁盘实证,非推断):TransPyV/output/8312ce86a2fd82c0.stub.ll 内自相矛盾
- `%"fef783da4d24330d.Keyword" = type {…}` 与 `%"8312ce86a2fd82c0.Keyword" = type {…}`
两份同名类的 struct 同时存在;
- `declare %"fef783da4d24330d.Keyword"* @"8312ce86a2fd82c0.Keyword.__new__"(%"8312ce86a2fd82c0.Keyword"* %self, …)`
- `declare void @"8312ce86a2fd82c0.Keyword.__before_init__(%"fef783da4d24330d.Keyword"* %".1")`
其中 8312ce86a2fd82c0 = includes/ast/astaux.vp、fef783da4d24330d = includes/ast/tokens.vp
(TransPyV/temp/_sha1_map.txt),两者各自定义了一个 `class Keyword`。
机制:`_build_struct_sha1_map` 建的是「**类名** → 定义模块 SHA1」,但去重判据写成
`if sha1_key in struct_sha1_map` —— 用模块 SHA1 去查一张按类名建的表,判据恒假 ⇒
实际是「后扫到的覆盖先扫到的」,同名类归属随 os.listdir 顺序漂移。定义端
`StructGen._generate_structs` 在 `class_sha1_map` 为空时用此表取前缀,于是 astaux
**自己**的 Keyword 被命名成 `fef783da4d24330d.Keyword`(另一个模块的 sha1);而按本
模块前缀另建的 `8312ce86a2fd82c0.Keyword` 被 `__new__`/`__init__` 当作 self,
`HandlesClassDef._EmitNewFunctionLlvm` 生成的 `__before_init__` 却取
`Gen.structs['Keyword']`(错名前缀那份)⇒ 声明与调用点分叉。
修复(两点,均为正向事实判据):
1) LlvmGenerator(Gen 构造处,**先于** setup_from_symbol_table 内部的 _generate_structs):
把本文件顶层 ClassDef 名单钉死为本模块 SHA1,写进 Gen.class_sha1_map(其优先级高于
_struct_sha1_map)。"本文件声明的类"就在本次要翻译的 AST 里;class_sha1_map 是 Gen
实例级、每模块重建 ⇒ 非本模块声明的类不受影响。
2) Phase2Translator._build_struct_sha1_map:去重判据改为**类名**(先扫到的为准),并另按
模块 SHA1 去重"同一模块文件被两处目录重复解析"——归属不再随 listdir 顺序漂移。
未放宽任何 fail-fast;未改动 __new__/__before_init__ 的调用点。
This commit is contained in:
1 parent
ab906f2cde
commit
52ea21f082
2 files changed
+35
-2
No files matched your search
@@ -1152,6 +1152,7 @@ class Phase2Translator:
|
||||
pyi_dirs: list[str] = [self.temp_dir]
|
||||
if os.path.isdir(_GLOBAL_CACHE_DIR):
|
||||
pyi_dirs.append(_GLOBAL_CACHE_DIR)
|
||||
_scanned_sha1: set[str] = set()
|
||||
for scan_dir in pyi_dirs:
|
||||
if not os.path.isdir(scan_dir):
|
||||
continue
|
||||
@@ -1161,15 +1162,24 @@ class Phase2Translator:
|
||||
sha1_key = fname.replace('.vpi', '').replace('.pyi', '')
|
||||
if sha1_key not in valid_sha1_keys:
|
||||
continue
|
||||
if sha1_key in struct_sha1_map:
|
||||
# 同一模块在两处目录都有文件时只解析一次(与下面的类名归属判据无关)
|
||||
if sha1_key in _scanned_sha1:
|
||||
continue
|
||||
_scanned_sha1.add(sha1_key)
|
||||
pyi_path = os.path.join(scan_dir, fname)
|
||||
# 复用 parse_python_file 的 AST 缓存(.pyi 文件可能被多处解析)
|
||||
_, tree = parse_python_file(pyi_path)
|
||||
if tree is None:
|
||||
continue
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.ClassDef):
|
||||
# 每类只登记一次,**先扫到的为准**:判据必须是**类名**。
|
||||
# 原实现写成 `if sha1_key in struct_sha1_map` —— 用模块 SHA1 去查一张
|
||||
# 按**类名**建的表,判据恒假 ⇒ 实际是"后扫到的覆盖先扫到的",
|
||||
# 同名类的归属随 os.listdir 顺序漂移(现场:includes/ast/astaux.vp 与
|
||||
# includes/ast/tokens.vp 各有一个 `class Keyword`,定义端拿到的前缀
|
||||
# 可能是**另一个模块**的 sha1 ⇒ 同一类出现两个 struct 实例、
|
||||
# `__before_init__` 声明与调用点分叉)。
|
||||
if isinstance(node, ast.ClassDef) and node.name not in struct_sha1_map:
|
||||
struct_sha1_map[node.name] = sha1_key
|
||||
return struct_sha1_map
|
||||
|
||||
|
||||
@@ -694,6 +694,29 @@ class LlvmGeneratorMixin:
|
||||
self.LlvmGen.hotspot_sites_all = getattr(self, 'HotspotSitesAll', False)
|
||||
self.LlvmGen.hotspot_callees = getattr(self, 'HotspotCallees', None)
|
||||
self.LlvmGen.hotspot_sites_module_local = getattr(self, 'HotspotSitesModuleLocal', False)
|
||||
# 【本模块声明的类:SHA1 前缀钉死为本模块(2026-09-30)】
|
||||
# 必须**先于** setup_from_symbol_table —— 其内部 _generate_structs 会为符号表里的
|
||||
# 每个类建 struct,IR 名前缀取自 `class_sha1_map`(空)→ `_struct_sha1_map`
|
||||
# (Phase2 扫描各 .vpi 得到的"裸类名 → 定义模块 SHA1")。两张表都按**裸类名**
|
||||
# 索引,两个模块各定义同名类时只能留一个:现场 includes/ast/astaux.vp 的
|
||||
# `class Keyword(AST)` 与 includes/ast/tokens.vp 的 `class Keyword(t.CEnum)`
|
||||
# 同名,`_build_struct_sha1_map` 的"每类只登记一次"判据误用了 sha1 键(恒假、
|
||||
# 实为后扫到的覆盖先扫到的)⇒ astaux 自己那份被命名成
|
||||
# `%"fef783da4d24330d.Keyword"`(= tokens 的 sha1)。于是同一类在本模块里出现
|
||||
# 两个 struct 实例:`__new__`/`__init__` 用按本模块前缀另建的那份
|
||||
# `%"8312ce86a2fd82c0.Keyword"*`,而 `HandlesClassDef._EmitNewFunctionLlvm`
|
||||
# 生成的 `__before_init__` 用 `Gen.structs['Keyword']`(错名前缀那份)⇒ 声明与
|
||||
# 调用点分叉,llvmlite 报
|
||||
# "Type of #1 arg mismatch: %"fef783da4d24330d.Keyword"* != %"8312ce86a2fd82c0.Keyword"*"
|
||||
# (报文是 `形参 != 实参`,下标 1-based ⇒ 指的其实是第 0 个形参 self)。
|
||||
# "本文件顶层 ClassDef 名单"是**正向事实**(就在本次要翻译的 AST 里,且
|
||||
# class_sha1_map 是 Gen 实例级、每模块重建)⇒ 命中即钉死为本模块 SHA1;
|
||||
# 非本模块声明的类不受影响,仍走原来的全局映射。
|
||||
_DefSha1: str | None = getattr(self, '_module_sha1', None)
|
||||
if _DefSha1:
|
||||
for _Nd in ast.iter_child_nodes(Tree):
|
||||
if isinstance(_Nd, ast.ClassDef) and getattr(_Nd, 'name', None):
|
||||
self.LlvmGen.class_sha1_map[_Nd.name] = _DefSha1
|
||||
self.LlvmGen.setup_from_symbol_table(self.SymbolTable)
|
||||
if hasattr(self, 'CurrentFile') and self.CurrentFile:
|
||||
self.LlvmGen._set_source_info(self.CurrentFile)
|
||||
|
||||
Reference in new issue
Block a user