fix(tpc): IR 边界补「指针操作数类型守卫」,非指针值不再抛裸 AttributeError

类别 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=(发射点标识)+ 当前节点位置。
This commit is contained in:
TermiNexus committed 2026-09-29 19:24:27 +08:00
1 parent 415916b3dd
commit d305d3369b
2 files changed
+95 -9

No files matched your search

+85 -9
View File
@@ -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:
+10
View File
@@ -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)