From 7aa12263f508d8bec20f446a9e97952868d8850a Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Wed, 30 Sep 2026 14:35:50 +0800 Subject: [PATCH] =?UTF-8?q?fix(HandlesExprAttr/BaseGen):=20=5Fget=5Fattr?= =?UTF-8?q?=5Fptr=20=E7=9A=84=20self=20=E5=88=86=E6=94=AF=E8=A1=A5=20opaqu?= =?UTF-8?q?e=20=E5=9F=BA=E5=9D=80=E6=94=B6=E5=8F=A3=EF=BC=88=E5=AD=AA?= =?UTF-8?q?=E7=94=9F=E4=BD=93=20bitcast=EF=BC=89+=20GEP=20=E5=AE=88?= =?UTF-8?q?=E5=8D=AB=E6=8A=A5=E5=87=BA=20TPC=20=E5=8F=91=E5=B0=84=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 【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。 --- lib/core/Handles/HandlesExprAttr.py | 34 ++++++++++++++++++++++++++++- lib/core/LLVMCG/BaseGen.py | 19 ++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/lib/core/Handles/HandlesExprAttr.py b/lib/core/Handles/HandlesExprAttr.py index b388871..875061a 100644 --- a/lib/core/Handles/HandlesExprAttr.py +++ b/lib/core/Handles/HandlesExprAttr.py @@ -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) diff --git a/lib/core/LLVMCG/BaseGen.py b/lib/core/LLVMCG/BaseGen.py index 0af055d..7c3157a 100644 --- a/lib/core/LLVMCG/BaseGen.py +++ b/lib/core/LLVMCG/BaseGen.py @@ -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)不承担导入开销,也避免循环导入。