fix(HandlesExprAttr/BaseGen): _get_attr_ptr 的 self 分支补 opaque 基址收口(孪生体 bitcast)+ GEP 守卫报出 TPC 发射点
【a 编译器缺陷】现场:Phase2 翻译 includes/ast/astaux.vp:165
`self.arg = _copy_str(pool, arg)`,守卫报
"getelementptr(GEP) 的基址指向**未具化**的具名结构体:基址类型=%"8312ce86a2fd82c0.Keyword"*,
GEP 名='arg'",而同短名实例清单显示 'Keyword' 键上那份已具化(10 槽)。
根因:`_get_attr_ptr` 的 **self 分支**是全仓"self 字段取址"家族里唯一**没有**做 opaque
收口的一处 —— 同函数下方各分支(变量分支 / 属性链分支 / i8* 分支 / 强转基座分支)都先
`_TryLoadStructFromStub` 补 body、再 bitcast 到已具化实例;`HandlesAssign` 的 `self.X = v`
写路径与读路径 `_gep_and_Load_member` 也都有同一套孪生体搜索(`_FindConcreteTwin`)。
本分支直接 `gep(SelfPtr, …)`,typed pointer 下 llvmlite 必须在编译期从 pointee 算元素类型
⇒ 撞 opaque 即抛裸 TypeError(被 IR 边界守卫拦下)。
修法(与同函数其余分支同口径,不引入新判据):
· 基址 pointee 为 opaque 时,先在类键(`Gen.structs[ClassName]`)上 `_TryLoadStructFromStub`
补 body;类键那份仍不可用则退回 `Gen._FindConcreteTwin`(同短名、取槽数最多);
· 命中即 bitcast 到该实例,并把该实例作为 `StructHint` 传给 `_get_member_offset`
—— 偏移与 GEP 必须同源(与 a43513c9 的"hint 只接受同类"口径一致);
· 顺带补 `offset is None ⇒ return None`(原实现会把 None 塞进 `ir.Constant(i32, None)`,
属同一函数其余分支已有的判断)。
【a 编译器缺陷·取证】`RequirePointerOperand` 的 opaque 分支新增一行
「TPC 发射点(由内向外)」:取 Python 栈中位于本仓 `lib/`、且不在 BaseGen.py(守卫自身)
的最内 3 帧,报出 `文件:行(函数)`。理由:该守卫的报文只能指到**语句**,而同一语句里常有
多个候选 GEP 点(本例 astaux.vp:165 在读写两侧共有 4 个 self 字段 GEP 点),逐个读代码
无法判定是哪一个 —— 本次即靠该取证能力闭环(先按"唯一漏收口的分支"修正,下一次构建若
仍失败会直接指出真实发射点)。只在失败路径执行,热路径零开销。
冒烟(临时脚本已删):
· `_get_attr_ptr` 复刻现场(self 槽持有 opaque `%8312…Keyword*`、'Keyword' 键那份 10 槽)
⇒ 输出 GEP 基址类型 `%"fef783da4d24330d.Keyword"*`(经 `cast_self_Keyword`),PASS;
· 守卫取证:伪造 lib/ 下的发射帧 ⇒ 报文含
`TPC 发射点(由内向外): core/Handles/_FakeProbe.py:2(_LibEmit)`,PASS。
This commit is contained in:
1 parent
49930c56c7
commit
7aa12263f5
2 files changed
+52
-1
No files matched your search
@@ -1781,11 +1781,43 @@ class ExprAttrHandle(BaseHandle):
|
||||
if VarName == 'self':
|
||||
ClassName: str | None = self.Trans._CurrentCpythonObjectClass
|
||||
if ClassName:
|
||||
offset: int | None = Gen._get_member_offset(Node.attr, ClassName)
|
||||
SelfVar: Any = Gen._get_var_ptr('self')
|
||||
if SelfVar:
|
||||
SelfPtr: Any = Gen._load(SelfVar, name="self")
|
||||
if isinstance(SelfPtr.type, ir.PointerType):
|
||||
# 基址 pointee 若**未具化**(opaque):typed pointer 下 GEP 必须
|
||||
# 在编译期算出元素类型,否则 llvmlite 内部抛裸 TypeError
|
||||
# ('NoneType' object is not subscriptable)。与**同函数下方
|
||||
# 各分支**(属性链分支 / i8* 分支:均先 _TryLoadStructFromStub
|
||||
# 补 body、再 bitcast 到已具化实例)同口径 —— 偏移与 GEP 必须
|
||||
# 同源;本分支此前是唯一漏了这套处理的分支。
|
||||
# 现场(2026-09-30):includes/ast/astaux.vp:165
|
||||
# `self.arg = _copy_str(pool, arg)`,GEP 名 'arg',基址 pointee
|
||||
# 是 opaque 的 `%"8312ce86a2fd82c0.Keyword"*`,而同短名的
|
||||
# 'Keyword' 键上那份已具化(10 槽)。
|
||||
_SelfPointee: Any = SelfPtr.type.pointee
|
||||
if (isinstance(_SelfPointee, ir.IdentifiedStructType)
|
||||
and _SelfPointee.elements is None):
|
||||
_ST: Any = Gen.structs.get(ClassName)
|
||||
if isinstance(_ST, ir.IdentifiedStructType) and not _ST.elements:
|
||||
self.Trans.ImportHandler._TryLoadStructFromStub(ClassName, Gen)
|
||||
_ST = Gen.structs.get(ClassName, _ST)
|
||||
_Twin: Any = None
|
||||
if isinstance(_ST, ir.IdentifiedStructType) and _ST.elements:
|
||||
_Twin = _ST
|
||||
else:
|
||||
try:
|
||||
_Twin = Gen._FindConcreteTwin(_SelfPointee)
|
||||
except Exception:
|
||||
_Twin = None
|
||||
if _Twin is not None:
|
||||
SelfPtr = Gen.builder.bitcast(
|
||||
SelfPtr, ir.PointerType(_Twin),
|
||||
name=f"cast_self_{ClassName}")
|
||||
_SelfPointee = _Twin
|
||||
offset: int | None = Gen._get_member_offset(Node.attr, ClassName, _SelfPointee)
|
||||
if offset is None:
|
||||
return None
|
||||
MemberPtr: Any = Gen.builder.gep(SelfPtr, [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), offset)], name=Node.attr)
|
||||
return MemberPtr
|
||||
ClassName = Gen.var_struct_class.get(VarName)
|
||||
|
||||
@@ -88,6 +88,25 @@ def RequirePointerOperand(Value: Any, OpName: str, Gen: Any = None, Detail: str
|
||||
_Kin.append(f"{_k_n}({getattr(_k_v, 'name', '?')})={_k_cnt}")
|
||||
if _Kin:
|
||||
_MsgP += "\n 同短名 struct 实例(key(IR 名)=槽数): " + ", ".join(_Kin)
|
||||
# TPC 发射点(由内向外,最内 3 帧):行号只能指到**语句**,无法区分
|
||||
# 同一语句里是哪个 GEP 点(现场 astaux.vp:165 `self.arg = _copy_str(...)`
|
||||
# 在读写两侧共有多个候选发射点,逐个读代码无法判定)。取栈中位于本仓
|
||||
# lib/、且不在本文件(守卫自身)的最内 3 帧,报出 文件:行(函数)。
|
||||
try:
|
||||
import traceback as _tb
|
||||
_Sites: list[str] = []
|
||||
for _fr in reversed(_tb.extract_stack()):
|
||||
_fn: str = (getattr(_fr, 'filename', '') or '').replace('\\', '/')
|
||||
if '/lib/' not in _fn or _fn.endswith('/LLVMCG/BaseGen.py'):
|
||||
continue
|
||||
_Sites.append(f"{_fn.split('/lib/', 1)[-1]}:{getattr(_fr, 'lineno', 0)}"
|
||||
f"({getattr(_fr, 'name', '?')})")
|
||||
if len(_Sites) >= 3:
|
||||
break
|
||||
if _Sites:
|
||||
_MsgP += "\n TPC 发射点(由内向外): " + " ← ".join(_Sites)
|
||||
except Exception:
|
||||
pass
|
||||
raise FailFastError(_MsgP)
|
||||
return
|
||||
# 失败路径才导入:热路径(每次 gep/load/store)不承担导入开销,也避免循环导入。
|
||||
|
||||
Reference in new issue
Block a user