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:
1 parent
415916b3dd
commit
d305d3369b
2 files changed
+95
-9
No files matched your search
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user