From 415916b3dd65ef8d9356214290a0a329b83ef1e7 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 29 Sep 2026 18:18:49 +0800 Subject: [PATCH] =?UTF-8?q?fix(tpc):=20=E6=88=90=E5=91=98=E5=81=8F?= =?UTF-8?q?=E7=A7=BB=E4=BB=A5=E3=80=8C=E5=AE=9E=E9=99=85=20GEP=20=E7=94=A8?= =?UTF-8?q?=E7=9A=84=20struct=20=E5=AE=9E=E4=BE=8B=E3=80=8D=E4=B8=BA?= =?UTF-8?q?=E5=87=86=EF=BC=8C=E5=B9=B6=E6=8A=A5=E5=87=BA=E5=8F=8C=E5=AE=9E?= =?UTF-8?q?=E4=BE=8B=E4=B8=8D=E4=B8=80=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 类别:a(编译器缺陷 —— 偏移与 GEP 不同源,整类字段偏移平移) 现场:TransPyV/App/lib/core/Gen/Class/ClassGenGeneric.vp:346 return ast.Constant(pool, cnst.const_kind, cnst.int_val, cnst.float_val, cnst.str_val, cnst.lineno, cnst.col_offset) 报「调用参数类型不匹配:指针类型 %"c772afbbfd495aca.MemManager"* 不能隐式转换为 整数参数 i32」,逐项列表显示实参 [6](= cnst.lineno)求值成 MemManager*。 取证(TransPyV/temp/5df907d3a02a0bac.stub.ll:21/:23): %"…Constant" = { [0]i8*(vtable) [1]AST*(parent) [2]MemManager*(pool) [3]i32(lineno) [4]i32(col_offset) … } __new__(Constant*, MemManager*, i32, i64, double, i8*, i32, i32) 成员表扁平序为 [parent, pool, lineno, col_offset, …] ⇒ base=1 时 lineno 偏移 3 (正确),base=0 时偏移 2 = pool,正是报出的那个指针类型;而 col_offset 偏移 3 落到 lineno 仍是 i32,所以**只有 [6] 报错** —— 与现象逐位吻合。 根因:FuncGen._member_base 的布局取自**类键** self.structs[cls],而 GEP 用的是 **取值处** ObjVal.type.pointee 那一份实例。同名类双注册(或一份已从 stub 加载完整、 另一份仍 opaque)时两份布局不同 ⇒ 用一份算偏移、用另一份读,整类字段偏移平移。 _class_has_vtable 对跨模块 AST 类恒为 False(其 docstring 已记录该已知静默路径), base 只能靠 delta + 类型校准救回,两实例不同源时即失守。 改动: 1. FuncGen._member_base(ClassName, StructHint=None):hint 为**同类**(短名相同)的 struct 实例时,以它计算 delta / 首元素 vtable 判据 / 类型校准;当它与类键上的 实例不是同一对象时,每类一次 vlog().warning 报出两侧名字与元素数(opaque 或 元素数),使"双注册"这一事实不再静默。 2. FuncGen._get_member_offset(..., StructHint=None) → _BaseNow() 透传(base 仍现算, 不缓存)。 3. HandlesExprAttr._handle_attr_name:把 ObjVal.type.pointee 作为 hint 传入。 不传 hint 时行为与改前逐字一致;其余偏移消费点(字段写入 / 构造初始化等)本轮未动。 --- lib/core/Handles/HandlesExprAttr.py | 12 +++++++- lib/core/LLVMCG/FuncGen.py | 44 ++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/lib/core/Handles/HandlesExprAttr.py b/lib/core/Handles/HandlesExprAttr.py index 3c53ea0..2bec0c1 100644 --- a/lib/core/Handles/HandlesExprAttr.py +++ b/lib/core/Handles/HandlesExprAttr.py @@ -708,7 +708,17 @@ class ExprAttrHandle(BaseHandle): Gen.class_member_bitoffsets[ClassName][AttrName] = bo return self._load_bitfield_member(ObjVal, ClassName, AttrName) - offset = Gen._get_member_offset(AttrName, ClassName) + # 偏移与 GEP 必须**同源**:把"实际将被索引的那个 struct 实例"传给偏移计算。 + # 同一类键上可能存在两个实例(同名类 sha1 冲突双注册 / 一方仍 opaque), + # 用类键上那份算偏移、用 ObjVal 这份做 GEP ⇒ 整类字段偏移平移 + # (现场:`Constant.lineno` 偏移 2 落到 `pool`(MemManager*) → 第 6 个实参被报 + # "指针不能隐式转整数 i32")。_member_base 收到不同实例时会报出事实。 + _StructHint: Any = None + if isinstance(ObjVal.type, ir.PointerType): + _ObjPointee: Any = ObjVal.type.pointee + if isinstance(_ObjPointee, ir.IdentifiedStructType): + _StructHint = _ObjPointee + offset = Gen._get_member_offset(AttrName, ClassName, _StructHint) # 检查 offset 是否超出实际结构体 if isinstance(ObjVal.type, ir.PointerType): actual_pointee: Any = ObjVal.type.pointee diff --git a/lib/core/LLVMCG/FuncGen.py b/lib/core/LLVMCG/FuncGen.py index ad3a49a..ca6e902 100644 --- a/lib/core/LLVMCG/FuncGen.py +++ b/lib/core/LLVMCG/FuncGen.py @@ -351,9 +351,17 @@ class FuncGenMixin: _Cls = _hit return _Cls - def _member_base(self, ClassName: str | None) -> int: + def _member_base(self, ClassName: str | None, StructHint: Any = None) -> int: """类 struct 的成员偏移基址(vtable 占位槽数)——**全仓唯一真源**。 + `StructHint`(可选):调用方手里**实际要用于 GEP 的那个 struct 实例** + (通常就是 `ObjVal.type.pointee`)。同一类键上可能同时存在两个实例 + (同名类 sha1 冲突的双注册,或一个已从 stub 加载完整、另一个仍 opaque), + 而偏移基址必须与"实际 GEP 用的那份布局"一致 —— 用键上那份算、用另一份读, + 会让整类字段偏移平移(现场:`Constant.lineno` 偏移 2 落到 `pool`(MemManager*), + 实参第 6 项被报"指针不能隐式转整数 i32")。传 hint 时以 hint 为准并**报出** + 两侧差异(每类一次),不传时行为与改前逐字一致。 + `_get_member_offset` 与所有"自算 base 再 GEP 字段"的消费点(构造/初始化、 字段默认值、继承方法 this 调整等)都必须走本方法,禁止各自直查 `class_vtable`/`_cross_module_vtable_classes`——旧实现只认这两个装饰器集合, @@ -393,7 +401,34 @@ class FuncGenMixin: _vlog().warning(f"补齐类成员展平失败: {_e}", "Exception") _ByInheritVtable: bool = self._class_has_vtable(_Cls) base: int = 1 if _ByInheritVtable else 0 - _StructBase: ir.IdentifiedStructType | None = self.structs.get(_Cls) + # 布局来源:优先调用方**实际 GEP 用的那个实例**(StructHint)——偏移必须与 + # 真正被索引的那份 body 同源。只在 hint 不可用时才回落到类键上的实例。 + _RegBase: ir.IdentifiedStructType | None = self.structs.get(_Cls) + _StructBase: ir.IdentifiedStructType | None = _RegBase + if isinstance(StructHint, ir.IdentifiedStructType): + # 仅接受**同类**的 hint:接收者静态类型是基类、实际指向派生类实例时, + # hint 的布局属于派生类,拿它配 `class_members[_Cls]` 会算错 delta。 + _HintShort: str = self._extract_short_name(getattr(StructHint, 'name', '') or '') + if _HintShort == _Cls: + _StructBase = StructHint + if _RegBase is not None and _RegBase is not StructHint: + # 同一类键上两个 struct 实例:报出事实(每类一次),避免"用一份算、 + # 用另一份读"的静默偏移平移(本函数此处即该平移的判定源)。 + _Rep: set = getattr(self, '_MemberBaseInstMismatch', None) + if _Rep is None: + _Rep = set() + self._MemberBaseInstMismatch = _Rep + if _Cls not in _Rep: + _Rep.add(_Cls) + _RegN: str = (f"{len(_RegBase.elements)} 元素" + if _RegBase.elements is not None else 'opaque') + _HintN: str = (f"{len(StructHint.elements)} 元素" + if StructHint.elements is not None else 'opaque') + _vlog().warning( + f"[成员偏移] 类 '{_Cls}' 存在两个 struct 实例:类键上 " + f"{_RegBase.name}({_RegN}) vs 取值处 {StructHint.name}({_HintN})" + f";偏移基址已按取值处实例计算(两者布局不同会让字段偏移平移)", + "MemberBase") if (isinstance(_StructBase, ir.IdentifiedStructType) and _StructBase.elements is not None and len(_StructBase.elements) > 0): _NumElems: int = len(_StructBase.elements) @@ -445,7 +480,8 @@ class FuncGenMixin: base = _BestB return base - def _get_member_offset(self, field_name: str, ClassName: str | None = None) -> int | None: + def _get_member_offset(self, field_name: str, ClassName: str | None = None, + StructHint: Any = None) -> int | None: # base 判定用未归一的原始类名:_member_base 内部自行做同一套归一 # (_NormalizeMemberClass),二者结果逐字一致(避免二次归一在 # "rawptr[X] 展开命中 sha1 前缀 struct 键"这一角落改变取值)。 @@ -500,7 +536,7 @@ class FuncGenMixin: # 偏移整体差 1(ast.ExceptHandler.children 落到 end_col_offset(i32), # len 分派拿到 i32)。 def _BaseNow() -> int: - return self._member_base(_MemberBaseKey) + return self._member_base(_MemberBaseKey, StructHint) # 预热(与原行为逐字一致):先触发一次 EnsureClassMembersFlattened, # 再决定下面的懒加载。 _BaseNow()