From 8f1ec5b715e5913d35c7c11e61cdc5e789dccd84 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 29 Sep 2026 23:31:57 +0800 Subject: [PATCH] =?UTF-8?q?fix(LLVMCG):=20llvmlite=20Function=20=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E4=BA=8B=E5=8A=A1=E5=8C=96=20+=20=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E5=90=9E=20fail-fast=EF=BC=8C=E6=82=AC=E7=A9=BA=E5=90=8D?= =?UTF-8?q?=E5=AD=97=E4=B8=8D=E5=86=8D=E5=A4=8D=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 类别:a 编译器缺陷(TPC 侧) 现象:编译 StubMergerIR.vp 时 `_EmitExternalFuncDeclLlvm` 报「函数声明名字悬空占用: '50741ad4b4b0934d.LLVMTypeCore.__init__' 已进 module.scope,但 module.globals 中无对应 对象」,且该 FailFastError 被 `_LoadModuleDeclarationsFromFile` 的节点级 `except Exception` 吞成一行 WARN「忽略节点 ClassDef LLVMTypeCore 异常」后 continue。 机制(判据已写进代码注释与报文字段): 1) llvmlite 的 `Function.__init__` 不是事务性的:名字在 `_set_name` 即进 module.scope, 之后逐个建 Argument/ReturnValue 才做类型断言,最后才 add_global;中间任一步抛错都不 回滚 scope ⇒ 留下「globals 查无、scope 已占用」的悬空名字,后续同名创建只能报裸 DuplicatedNameError(或本仓的「名字悬空占用」),而真因——上一处被吞的创建失败——已丢失。 修法:BaseGen 新增 `InstallFuncCreateGuard()`(随模块导入装一次),在**失败路径**上 回滚名字(补 llvmlite 缺的那半截事务,模块状态可重试)并抛 FailFastError,报文含 函数名 / 本次签名 / 原始异常 / 创建调用点(栈上首个非 llvmlite、非包装函数的帧); `DuplicatedNameError` 原样上抛——那是各调用点有意处理的正常分支(如「已存在则复用」)。 2) 首建失败与该 FailFastError 被 5 处兜底吞掉,使真因退化成一行 WARN: · HandlesImports._LoadModuleDeclarationsFromFile 节点级 except(本现场的直接来源) · HandlesImports stub 声明加载循环的两层 except · HandlesImports._TryLoadFuncDeclsFromOutputStub 的裸 `except Exception: pass` · HandlesFunctions._replace_function_decl 的 `except Exception: return old_func` (此处 old_func 必为 None ⇒ 调用点把 None 当函数声明用) 修法:一律 `except FailFastError: raise`;其余异常维持原有口径(严格模式上抛 / 其余告警)。 验证:4 例冒烟(非法签名 → FailFastError 且报文含调用点;名字已回滚 ⇒ 同名可重试成功; 重复创建仍是 DuplicatedNameError 且旧名未被回滚;合法创建零副作用)全部通过; BaseGen / HandlesImports / HandlesFunctions 三文件 py_compile 通过。 --- lib/core/Handles/HandlesFunctions.py | 13 ++++- lib/core/Handles/HandlesImports.py | 31 +++++++++++- lib/core/LLVMCG/BaseGen.py | 72 ++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 3 deletions(-) diff --git a/lib/core/Handles/HandlesFunctions.py b/lib/core/Handles/HandlesFunctions.py index 7732f5b..79352de 100644 --- a/lib/core/Handles/HandlesFunctions.py +++ b/lib/core/Handles/HandlesFunctions.py @@ -1305,6 +1305,11 @@ class FunctionHandle(BaseHandle): if MangledName == FuncName or FuncName not in Gen._export_extern_funcs: Gen.functions[FuncName] = Gen.functions[MangledName] except Exception as _e: + # fail-fast 一律上抛(本仓既定口径):吞掉它 = 声明静默缺失 + + # 半注册名字留给下一轮,真因与现场脱节。 + from lib.core.LLVMCG.FuncGen import FailFastError as _FFE + if isinstance(_e, _FFE): + raise if _config_mode == "strict": self.Trans.LogWarning(f"异常被忽略: {_e}") self.Trans.FunctionDefCache[FuncName] = Node @@ -1315,7 +1320,13 @@ class FunctionHandle(BaseHandle): if old_func is None: try: return ir.Function(Gen.module, NewFuncType, name=MangledName) - except Exception: # 回退:函数已存在时返回旧函数 + except Exception as _e: # 回退:函数已存在时返回旧函数 + from lib.core.LLVMCG.FuncGen import FailFastError as _FFE + if isinstance(_e, _FFE): + # 创建中途失败(如形参/返回类型不是 llvmlite Type)由 BaseGen 的 + # Function 守卫转成 fail-fast:必须上抛,原实现会 `return old_func` + # (此处 old_func 必为 None)⇒ 调用点把 None 当声明用。 + raise return old_func try: old_name: str = old_func.name diff --git a/lib/core/Handles/HandlesImports.py b/lib/core/Handles/HandlesImports.py index 601143f..5642d6a 100644 --- a/lib/core/Handles/HandlesImports.py +++ b/lib/core/Handles/HandlesImports.py @@ -664,6 +664,15 @@ class ImportHandle(BaseHandle): 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}", @@ -3624,10 +3633,18 @@ class ImportHandle(BaseHandle): 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") @@ -3707,9 +3724,19 @@ class ImportHandle(BaseHandle): short_fn = func_name.split('.', 1)[1] Gen.functions[short_fn] = func loaded_any = True - except Exception: - pass + 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") diff --git a/lib/core/LLVMCG/BaseGen.py b/lib/core/LLVMCG/BaseGen.py index 7ca84a1..63ea982 100644 --- a/lib/core/LLVMCG/BaseGen.py +++ b/lib/core/LLVMCG/BaseGen.py @@ -79,6 +79,78 @@ def RequirePointerOperand(Value: Any, OpName: str, Gen: Any = None, Detail: str raise FailFastError(_Msg) +_TPC_FuncInitGuardInstalled: bool = False + + +def InstallFuncCreateGuard() -> None: + """给 llvmlite 的 `ir.Function.__init__` 装一次守卫:创建中途失败**不留悬空名字**。 + + 背景(llvmlite 的构造不是事务性的):`Function.__init__` 的顺序是 + super().__init__ → `_set_name` → `module.scope.register(name)` 【名字占用】 + → 逐形参 `Argument(...)` / `ReturnValue(...)`(内部 `assert isinstance(typ, types.Type)`) + → `parent.add_global(self)` 【进入 globals】 + 中间任一步抛错都不会回滚 scope ⇒ globals 查无、scope 却已占用的"悬空名字"。此后同名 + 创建只会得到 llvmlite 的裸 DuplicatedNameError(报文只有名字),或本仓 + `_CreateOrReuseFuncDecl` 的"名字悬空占用"fail-fast —— 两者都只能指向"上一处失败被 + 吞了",而**那一处**的调用点与异常早已丢失(现场:50741ad4b4b0934d.LLVMTypeCore.__init__, + 上游首建失败被 HandlesImports 的节点级 `except Exception` 吞成一行 WARN)。 + + 本守卫做两件事(均在**失败路径**上,合法创建零额外开销): + 1. 回滚本次注册的名字(`scope._useset.discard`)——补上 llvmlite 缺的那半截事务, + 模块状态可重试,不再积累悬空名字; + 2. 抛 FailFastError,报文给出函数名、签名、原始异常,以及**发起创建的 TPC 调用点** + (栈上第一个不在 llvmlite 包内、也不是本包装函数的帧)。 + `DuplicatedNameError`(名字已被占用)原样上抛:那是各调用点**有意**处理的正常分支 + (如 `_replace_function_decl` 的"已存在则复用"),不属于"创建中途失败"语义。 + """ + global _TPC_FuncInitGuardInstalled + if _TPC_FuncInitGuardInstalled: + return + _TPC_FuncInitGuardInstalled = True + _OrigInit: Any = ir.Function.__init__ + + def _GuardedFuncInit(self: Any, module: Any, ftype: Any, name: Any) -> None: + try: + _OrigInit(self, module, ftype, name) + return + except Exception as _e: + from llvmlite.ir._utils import DuplicatedNameError as _Dup + if isinstance(_e, _Dup): + raise + # 名字已在 `_set_name` 阶段注册(进入本分支即证明 register 成功)⇒ 回滚 + _scope: Any = getattr(module, 'scope', None) + _useset: Any = getattr(_scope, '_useset', None) + if _useset is not None: + _useset.discard(name) + _useset.discard(str(name)) + import traceback as _tb + _site: str = '(栈中未找到非 llvmlite 帧)' + for _fr in reversed(_tb.extract_stack()): + _fn: str = _fr.filename.replace('\\', '/') + if 'site-packages/llvmlite' in _fn or _fr.name == '_GuardedFuncInit': + continue + _site = f"{_fr.filename}:{_fr.lineno} (in {_fr.name})" + break + from lib.core.LLVMCG.FuncGen import FailFastError + raise FailFastError( + f"函数创建失败(已回滚悬空名字):'{name}'" + f"\n · 本次签名: {ftype}" + f"\n · 原始异常: {type(_e).__name__}: {_e}" + f"\n · 创建调用点: {_site}" + f"\n 成因:llvmlite 的 Function 构造不是事务性的——名字在 `_set_name` 就进了" + f" module.scope,之后逐个建 Argument/ReturnValue 时才做类型断言,最后才" + f" add_global。任何形参 / 返回类型不是 llvmlite Type(典型 None:注解或 stub" + f" declare 的类型没解析出来)都会在「已注册、未进 globals」之间失败 ⇒ 留下" + f"悬空名字,后续同名创建只报裸 DuplicatedNameError 或「名字悬空占用」。" + f"\n 本守卫已回滚名字(模块状态可重试);请修上面那一行的签名来源。" + ) from _e + + ir.Function.__init__ = _GuardedFuncInit + + +InstallFuncCreateGuard() + + class _GuardIrBuilder(ir.IRBuilder): """默认 IR 构建器:在 GEP/load/store 处对**指针操作数**做类型守卫。