From bd92dfdadf309c92ab9dc281c65973317aa18486 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 29 Sep 2026 23:48:52 +0800 Subject: [PATCH] =?UTF-8?q?fix(LLVMCG):=20=E6=88=90=E5=91=98=E8=A1=A8?= =?UTF-8?q?=E9=87=8D=E5=90=8D/=E8=81=94=E5=90=88=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E4=B8=8D=E5=86=8D=E6=89=93=E6=96=AD=E5=B8=83?= =?UTF-8?q?=E5=B1=80=E9=85=8D=E5=AF=B9=EF=BC=8CExceptHandler.name=20?= =?UTF-8?q?=E5=8F=96=E5=88=B0=E6=A7=BD=209?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 类别:a 编译器缺陷(TPC 侧) 现象:翻译 TransPyV/App/lib/core/Gen/AsyncGenMixin.vp 时 `self._add_local(trans, hd.name)` (:667)的第 2 个实参求值失败并终止编译。基址诊断给出的事实链:类 ExceptHandler (%"831ce86a2fd82c0..." 即 astaux.vp)成员表 10 项 = ['parent','pool','lineno','col_offset','end_lineno','end_col_offset','children','children','type','name'] ('children' 重复一次),struct body 10 槽 = [i8*, AST*, MemManager*, i32×4, list*, AST*(type), i8*(name)],字段 'name' 计算偏移 = 10 ⇒ 越界(正确槽 = 9)。 判据与修法(两条,均已写进代码注释与告警报文): 1) 布局配对断链:成员表里 'children' 的声明类型是**联合类型折叠后的通用指针** (`list[rawptr[AST]] | t.CPtr` ⇒ i8*),而 body 槽是具体指针(%"...list[...]"*)⇒ 原配对只比 `is` / `str()`,在这一条断链,其后所有条目不再消费槽位 ⇒ 目标字段退化为 "base + 表内下标"('name' ⇒ 1 + 9 = 10)。修:两侧都是指针时复用存储/建 body 侧同一 判据 `_MemberSlotAccepts`(通用指针 i8* 放行、具体指针按被指类型),标量仍走严格比较 ——不引入新的过配对。修后 type = 8、name = 9 ✓。 2) 重名条目使长度判据失真:`_expected_count_pre = len(class_members) + vtable` 把重复项 也算一槽 ⇒ 误判"body 不完整"而用**带重复项的表**重建 body(本模块布局比其它模块多一 槽 ⇒ 跨模块 ABI 撕裂)。新增判据:表有重名**且** body 槽数恰等于「去重名数 + vtable」 ⇒ 认定 body 已是权威布局(来自定义模块 stub),只报事实、不重建。 另在"未被 body 配对命中"的回退路径上,把"成员表含重名条目 + 表项数/去重项数 + 该 返回值取自回退路径可能失准"作为事实告警报出(每类一次)。 验证:冒烟复刻本现场(表含重名 'children' 且其类型为联合 i8*;body 10 槽)——逐字段 1..9 全部命中(parent=1 … children=7、type=8、name=9),无重名的自洽表行为不变; FuncGen / StructGen 两文件 py_compile 通过。 --- lib/core/LLVMCG/FuncGen.py | 44 ++++++++++++++++++++++++++++++++++-- lib/core/LLVMCG/StructGen.py | 27 ++++++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/lib/core/LLVMCG/FuncGen.py b/lib/core/LLVMCG/FuncGen.py index f1ebcc5..2238834 100644 --- a/lib/core/LLVMCG/FuncGen.py +++ b/lib/core/LLVMCG/FuncGen.py @@ -650,7 +650,19 @@ class FuncGenMixin: # 配对类型必须同口径,否则该类字段会被整条跳过。 _mtEff: Any = _mtP if _mtP is not None else ir.IntType(32) _etP: Any = _OffEls[_Cursor] - if _mtEff is _etP or str(_mtEff) == str(_etP): + _PairOk: bool = (_mtEff is _etP or str(_mtEff) == str(_etP)) + if (not _PairOk and isinstance(_mtEff, ir.PointerType) + and isinstance(_etP, ir.PointerType)): + # 联合类型字段在成员表里存成通用指针(如 AST.children = + # `list[rawptr[AST]] | t.CPtr` ⇒ i8*),而 body 槽是具体指针 + # (`%"...list[...]"*`):严格串比较会在这条**断链**,其后的 + # 所有条目都不再消费槽位(cursor 不动)⇒ 目标字段全部退化成 + # "base + 表内下标"(现场:ExceptHandler.name 表内下标 9 ⇒ + # 偏移 10,越界 fail-fast,而正确槽是 9)。判据与存储/建 body + # 侧同一套(_MemberSlotAccepts:两侧都是指针时按指针转换规则, + # i8* 通用指针放行);标量仍走严格串比较,不引入新的过配对。 + _PairOk = self._MemberSlotAccepts(_mtEff, _etP) + if _PairOk: _PairedNames.add(_nmP) if _nmP == field_name: # 与旧下标法不一致时留痕:这正是"成员表与 struct 布局 @@ -677,7 +689,35 @@ class FuncGenMixin: return _Cursor _Cursor += 1 if _ResolvedClass and _ResolvedClass in self.class_members: - for i, (name, _) in enumerate(self.class_members[_ResolvedClass]): + # 只有走到这里(布局配对未命中)才做一次重名检查:重名条目**不占布局槽**, + # 却会让 `base + 表内下标` 整体后移(现场:ExceptHandler 表 10 项、其中 + # 'children' 出现两次 ⇒ 'name' 下标 9 ⇒ 偏移 10 越界)。此处把事实报出来 + # (每类一次),并标注该返回值取自回退路径、可能失准。 + _MemList: list = self.class_members[_ResolvedClass] + _SeenNm: set = set() + _DupNm: list[str] = [] + for _nmC, _ in _MemList: + if _nmC in _SeenNm: + if _nmC not in _DupNm: + _DupNm.append(_nmC) + else: + _SeenNm.add(_nmC) + if _DupNm: + _DupRep: set = getattr(self, '_MemberDupNamesReported', None) + if _DupRep is None: + _DupRep = set() + self._MemberDupNamesReported = _DupRep + if _ResolvedClass not in _DupRep: + _DupRep.add(_ResolvedClass) + _vlog().warning( + f"[成员偏移] 类 '{_ResolvedClass}' 的成员表含**重名条目** " + f"{_DupNm!r}(表 {len(_MemList)} 项 / 去重 {len(_SeenNm)} 项)——" + f"重名项不是布局槽位,`base + 表内下标` 会因此整体后移;" + f"本次字段 '{field_name}' 走的是回退路径(未被 body 配对命中)," + f"返回值可能失准。重名来源通常是「同一字段被两条收集路径各登记一次" + f"(类内 AnnAssign 声明 + 方法体 self.x 赋值)」或继承前缀重复前置。", + "MemberBase") + for i, (name, _) in enumerate(_MemList): if name == field_name: return _BaseNow() + i # 继承链回退:当字段不在当前类的 class_members 中时(常见于跨模块导入 diff --git a/lib/core/LLVMCG/StructGen.py b/lib/core/LLVMCG/StructGen.py index b745a4e..ac5c88b 100644 --- a/lib/core/LLVMCG/StructGen.py +++ b/lib/core/LLVMCG/StructGen.py @@ -614,6 +614,33 @@ class StructGenMixin: if ClassName in self.class_packed and not existing_st.packed: existing_st.packed = True continue + # 成员表**重名**时长度判据不可信:重名条目不是布局槽位(body 由定义模块的 + # stub 决定),却会让 `_expected_count_pre` 多算一项 ⇒ 误判"body 不完整" + # 而用带重复项的表重建 body(本模块布局比其它模块多一槽 ⇒ 跨模块 ABI 撕裂)。 + # 现场:astaux.ExceptHandler 表 10 项含两个 'children',而定义模块 stub 的 + # body 恰为 10 槽 = vtable + 9 个不同字段名。判据:表有重名**且** body 槽数 + # 恰等于「去重名数 + vtable」⇒ body 已是权威布局,只报事实、不重建。 + _uniq_names_pre: set[str] = {n for n, _ in _members_pre} + if (len(_uniq_names_pre) < len(_members_pre) + and len(existing_st.elements) == len(_uniq_names_pre) + (1 if _has_vtable_precheck else 0) + and _vtable_first_ok and not _has_t_placeholder_pre): + _DupRep: set = getattr(self, '_StructDupMembersReported', None) + if _DupRep is None: + _DupRep = set() + self._StructDupMembersReported = _DupRep + if ClassName not in _DupRep: + _DupRep.add(ClassName) + _vlog().warning( + f"[结构体布局] 类 '{ClassName}' 的成员表含**重名条目**:表 " + f"{len(_members_pre)} 项 / 去重 {len(_uniq_names_pre)} 项," + f"现有 body {len(existing_st.elements)} 槽恰等于「去重名数 + vtable」" + f"⇒ 认定 body 为权威布局(来自定义模块 stub),**不按重复表重建**" + f"(重建会多出一槽、与其它模块布局不一致)。" + f"请核对成员表为何重复登记同一字段名。", + "StructGen") + if ClassName in self.class_packed and not existing_st.packed: + existing_st.packed = True + continue # struct body 不完整,清除 elements 和 stub 缓存以便重新加载。 # 注意:本仓 llvmlite 版本的字段名是**公开属性 `elements`** # (types.py: `self.elements = None`;`is_opaque` = `elements is None`;