From d305d3369bcc93228ca7489034c7eeb0c5673dc9 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 29 Sep 2026 19:24:27 +0800 Subject: [PATCH] =?UTF-8?q?fix(tpc):=20IR=20=E8=BE=B9=E7=95=8C=E8=A1=A5?= =?UTF-8?q?=E3=80=8C=E6=8C=87=E9=92=88=E6=93=8D=E4=BD=9C=E6=95=B0=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E5=AE=88=E5=8D=AB=E3=80=8D=EF=BC=8C=E9=9D=9E=E6=8C=87?= =?UTF-8?q?=E9=92=88=E5=80=BC=E4=B8=8D=E5=86=8D=E6=8A=9B=E8=A3=B8=20Attrib?= =?UTF-8?q?uteError?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 类别 a(编译器缺陷/诊断缺失):llvmlite 的 IRBuilder.gep 不做任何类型校验, GEPInstr.__init__ 直接读 ptr.type.is_opaque;LoadInstr 同族亦同(instructions.py :440/:558)。上游一旦把非指针值(典型 i32)当指针传入,抛出的就是裸 `AttributeError: 'IntType' object has no attribute 'is_opaque'` —— 报文里既没有 指令名、没有操作数、也没有当前语句,无法定位发射点(现场:Phase2 翻译 TransPyV/App/lib/core/Gen/TranslatorGenMixin.vp 时只带 713/787/788 三条语句位置)。 - BaseGen:新增 RequirePointerOperand(IR 边界先行判定,不做兜底转换)与 _GuardIrBuilder(无条件覆盖 gep/load/store,合法 IR 与原生 ir.IRBuilder 一致); _HotspotSiteBuilder 改继承 _GuardIrBuilder;_NewIrBuilder 未启用热点统计时 由原生 ir.IRBuilder 改为返回 _GuardIrBuilder。 - MemoryOps:_EmitLoad/_EmitStore 的 volatile 分支直接构造 LoadInstr/StoreInstr, 绕过了 builder.load/store 的类型守卫,补同一判定(非指针 ptr 的 store 会静默 生成非法 IR 文本)。 报文含:指令名、操作数类型、操作数名、GEP 的 name=(发射点标识)+ 当前节点位置。 --- lib/core/LLVMCG/BaseGen.py | 94 ++++++++++++++++++++++++++++++++---- lib/core/LLVMCG/MemoryOps.py | 10 ++++ 2 files changed, 95 insertions(+), 9 deletions(-) diff --git a/lib/core/LLVMCG/BaseGen.py b/lib/core/LLVMCG/BaseGen.py index 2884e2c..056fad2 100644 --- a/lib/core/LLVMCG/BaseGen.py +++ b/lib/core/LLVMCG/BaseGen.py @@ -17,11 +17,84 @@ class VaArgInstruction(ir.Instruction): ptr.type, ptr.get_reference(), self.type)) -class _HotspotSiteBuilder(ir.IRBuilder): +def RequirePointerOperand(Value: Any, OpName: str, Gen: Any = None, Detail: str = '') -> None: + """IR 边界守卫:指针类指令(GEP/load/store)的**指针操作数**必须是指针类型。 + + 为什么需要本守卫:llvmlite 的 `IRBuilder.gep` **不做任何类型校验**, + `GEPInstr.__init__` 直接读 `ptr.type.is_opaque`;`LoadInstr.__init__` 同族 + 亦同(见 llvmlite/ir/instructions.py:440/558,builder.py:777)。因此上游一旦 + 把一个**非指针值**(典型:i32)当指针传进来,抛出的就是裸 + `AttributeError: 'IntType' object has no attribute 'is_opaque'` —— + 报文里既没有指令名、没有操作数、也没有当前语句,无法定位发射点 + (现场:Phase2 翻译 TransPyV/App/lib/core/Gen/TranslatorGenMixin.vp 时报错 + 只带 713/787/788 三条语句位置,剩下的全被 llvmlite 内部吞掉)。 + + 本函数在 IR 边界先行判定,**不做任何兜底转换**:操作数不是指针立即抛 + FailFastError,报文给出指令名、操作数类型、操作数名、GEP 的 `name=`(即发射 + 点标识)与当前节点位置,让"上游把非指针值当指针使用"的根因一眼可见。 + + 合法路径下(操作数确实是指针)零副作用,只多一次 isinstance 判定。 + """ + if isinstance(getattr(Value, 'type', None), ir.PointerType): + return + # 失败路径才导入:热路径(每次 gep/load/store)不承担导入开销,也避免循环导入。 + from lib.core.LLVMCG.FuncGen import FailFastError + _TypeStr: str = str(getattr(Value, 'type', None)) + _ValName: Any = getattr(Value, 'name', '') + _Msg: str = f"{OpName} 的指针操作数不是指针类型:操作数类型={_TypeStr}" + if _ValName: + _Msg += f",操作数名='{_ValName}'" + if Detail: + _Msg += f",{Detail}" + _Msg += ("\n llvmlite 只会在此处内部访问 `ptr.type.is_opaque`,抛裸 AttributeError" + "('IntType' object has no attribute 'is_opaque');" + "本守卫在 IR 边界 fail-fast,暴露上游把非指针值当指针使用的根因" + "(不做兜底转换)") + if Gen is not None and hasattr(Gen, '_get_node_info'): + _Msg += "\n " + Gen._get_node_info() + raise FailFastError(_Msg) + + +class _GuardIrBuilder(ir.IRBuilder): + """默认 IR 构建器:在 GEP/load/store 处对**指针操作数**做类型守卫。 + + 与 `_HotspotSiteBuilder`(--hotspot-sites 专用)不同,本类**无条件启用** + (BaseGenMixin._NewIrBuilder 返回它):只做"操作数不是指针 → fail-fast" + 一件事,合法 IR 的生成结果与原生 ir.IRBuilder 完全一致。 + + 为什么必须无条件启用:`IRBuilder.gep` 无类型校验(见 RequirePointerOperand + 的说明),而 TPC 侧全部 GEP 都走 `Gen.builder.gep`(无一处传 source_etype, + 故必走 GEPInstr 的 `ptr.type.is_opaque` 分支);load/store 虽在 llvmlite 内 + 有 isinstance 守卫,但报错不带源码位置。此处在唯一入口补上判定与位置信息。 + """ + + def __init__(self, block: ir.Block | None = None, gen: Any = None) -> None: + super().__init__(block) + self._GuardGen: Any = gen + + def gep(self, ptr: Any, indices: Any, inbounds: bool = False, + name: str = '', source_etype: Any = None) -> Any: + RequirePointerOperand(ptr, 'getelementptr(GEP)', self._GuardGen, + f"GEP 名='{name}',索引 {len(indices)} 项") + return super().gep(ptr, indices, inbounds=inbounds, name=name, + source_etype=source_etype) + + def load(self, ptr: Any, name: str = '', align: int | None = None, + typ: Any = None) -> Any: + RequirePointerOperand(ptr, 'load', self._GuardGen, f"load 名='{name}'") + return super().load(ptr, name=name, align=align, typ=typ) + + def store(self, value: Any, ptr: Any, align: int | None = None) -> Any: + RequirePointerOperand(ptr, 'store', self._GuardGen, + f"store 值名='{getattr(value, 'name', '')}'") + return super().store(value, ptr, align=align) + + +class _HotspotSiteBuilder(_GuardIrBuilder): """--hotspot-sites 专用 IRBuilder:为每条新生成的 call 指令挂源码位置。 仅当调用点热点统计开关打开时才被实例化(见 BaseGenMixin._NewIrBuilder); - 未启用时一律使用原生 ir.IRBuilder,不产生任何额外属性、行为与既有完全一致。 + 未启用时使用 _GuardIrBuilder(同为 IR 边界守卫,见其类注释)。 重写 call() 只在返回的 ir.CallInstr 对象上追加两个普通 Python 属性 (_src_file / _src_line),不改变指令语义、操作数,也不参与 IR 渲染 @@ -30,7 +103,7 @@ class _HotspotSiteBuilder(ir.IRBuilder): """ def __init__(self, block: ir.Block | None, gen: Any) -> None: - super().__init__(block) + super().__init__(block, gen) self._HotspotGen: Any = gen def call(self, fn: Any, args: Any, name: str = '', cconv: Any = None, @@ -1431,16 +1504,19 @@ class BaseGenMixin: # ========== 调用点热点统计(--hotspot-sites) ========== def _NewIrBuilder(self, block: ir.Block) -> ir.IRBuilder: - """创建 IR 构建器:仅在 --hotspot-sites 启用时挂钩源码位置。 + """创建 IR 构建器:IR 边界守卫(无条件)+ --hotspot-sites 源码位置挂钩。 - 未启用(hotspot_sites_file 为 None)时返回原生 ir.IRBuilder,行为与既有 - 完全一致(零影响,不挂任何属性);启用时返回 _HotspotSiteBuilder,仅在 - 每条新生成的 call 指令对象上追加 _src_file/_src_line 两个普通属性,供 - 调用点热点插桩读取源码位置。 + 未启用热点统计(hotspot_sites_file 为 None)时返回 _GuardIrBuilder: + 合法 IR 与原生 ir.IRBuilder 完全一致,只在 GEP/load/store 的指针操作数 + 不是指针时 fail-fast(否则 llvmlite 会抛无位置信息的裸 AttributeError + 'IntType' object has no attribute 'is_opaque',见 RequirePointerOperand)。 + 启用时返回 _HotspotSiteBuilder(同为 _GuardIrBuilder 子类,守卫不变), + 仅在每条新生成的 call 指令对象上追加 _src_file/_src_line 两个普通属性, + 供调用点热点插桩读取源码位置。 """ if self.hotspot_sites_file: return _HotspotSiteBuilder(block, self) - return ir.IRBuilder(block) + return _GuardIrBuilder(block, self) @staticmethod def _HotspotCalleeBase(name: str) -> str: diff --git a/lib/core/LLVMCG/MemoryOps.py b/lib/core/LLVMCG/MemoryOps.py index 46d83a0..ccd7444 100644 --- a/lib/core/LLVMCG/MemoryOps.py +++ b/lib/core/LLVMCG/MemoryOps.py @@ -503,6 +503,11 @@ class MemoryOpsMixin: """ if not volatile: return self.builder.load(ptr, name=name, align=align) + # volatile 分支直接构造 LoadInstr,绕过了 builder.load 的类型守卫, + # 故此处先行判定(否则 llvmlite 在 LoadInstr.__init__ 里访问 + # ptr.type.is_opaque 抛无位置信息的裸 AttributeError)。 + from lib.core.LLVMCG.BaseGen import RequirePointerOperand + RequirePointerOperand(ptr, 'load volatile', self, f"load 名='{name}'") instr: ir.LoadInstr = _VolatileLoadInstr(self.builder.block, ptr, name=name) instr.align = align self.builder._insert(instr) @@ -513,6 +518,11 @@ class MemoryOpsMixin: """发射一条 store;volatile=True 时走 _VolatileStoreInstr(LLVM `store volatile`)。""" if not volatile: return self.builder.store(val, ptr, align=align) + # 同 _EmitLoad:绕过 builder.store 的守卫,先判定指针操作数 + # (非指针 ptr 会静默生成非法 IR 文本,而不是报错)。 + from lib.core.LLVMCG.BaseGen import RequirePointerOperand + RequirePointerOperand(ptr, 'store volatile', self, + f"store 值类型={getattr(val, 'type', None)}") instr: ir.StoreInstr = _VolatileStoreInstr(self.builder.block, val, ptr) instr.align = align self.builder._insert(instr)