From f28ff1b414fef09b17f9724d8305378489447ceb Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 29 Sep 2026 19:57:57 +0800 Subject: [PATCH] =?UTF-8?q?fix(tpc):=20=E5=AD=97=E6=AE=B5=E5=81=8F?= =?UTF-8?q?=E7=A7=BB=E6=94=B9=E4=B8=BA=E3=80=8C=E5=B8=83=E5=B1=80=E7=9C=9F?= =?UTF-8?q?=E6=BA=90=E4=BC=98=E5=85=88=E3=80=8D=EF=BC=8C=E6=88=90=E5=91=98?= =?UTF-8?q?=E8=A1=A8=E6=BC=82=E7=A7=BB=E4=B8=8D=E5=86=8D=E6=95=B4=E4=BD=93?= =?UTF-8?q?=E5=B9=B3=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 类别 a(编译器缺陷):_get_member_offset 返回 `base + 成员表下标`,但 class_members 是**可变**表(继承展平会在父类就绪后前置前缀、跨模块可按需重载/重跑),而 struct body 一旦 set_body 即**永久冻结** ⇒ 表与布局的序位一旦漂移,整类字段偏移整体平移。 现场(IR 实证):Converter 模块读 `Alias.name` 落在 struct 槽 9(= asname),而定义 模块 Alias.__init__ 写入槽 8;`import t` 无 as 子句 ⇒ asname=NULL ⇒ TransPyV StubGen 误报「import 别名 name 为空(_copy_str 返回 None)」并终止 includes/stdint.vp 的 vpi 生成。同族旧现场:ast.Constant.lineno 取到 pool 槽(已由成员偏移同源修复过)。 - StructGen._generate_structs:set_body 时把「成员名 → 槽位」映射冻结到 struct 实例上(_tpc_member_slots / _tpc_member_base)。用**实例属性**而非全局表:同一 类名可能存在不止一个 struct 实例,偏移必须与被 GEP 的那一份布局同源;仅当 member_types 恰为 `[vtable?] + 全部成员` 时记录(位域类折叠为单一 storage 槽, 其字段位置另由 class_member_bitoffsets 表达;stub 已置 body 的情形不记录)。 - FuncGen._get_member_offset:新增两条「只看布局」的路径,优先于原下标法—— ① 冻结映射命中即返回; ② 用**类型**把成员表逐项配对到 elements(类型不匹配、重名的条目视为方法/占位/ 漂移项,不占槽位),配对中类型相等的槽位即字段真实槽位。 表与布局自洽时两者给出与旧下标法逐字相同的结果;都覆盖不到才落回下标法(对既有 正确行为零变化)。另在「配对结果 ≠ 下标法」时每类每字段告警一次,报出成员表项数 与 struct 槽数,把漂移来源变成可见事实。 本地冒烟(三类,均通过):自洽表 8 / 漂移表(类型不匹配)8(旧下标法 9)/ 全同型漂移且命中冻结映射 8。 --- lib/core/LLVMCG/FuncGen.py | 67 ++++++++++++++++++++++++++++++++++++ lib/core/LLVMCG/StructGen.py | 22 ++++++++++++ 2 files changed, 89 insertions(+) diff --git a/lib/core/LLVMCG/FuncGen.py b/lib/core/LLVMCG/FuncGen.py index ca6e902..e5d60e3 100644 --- a/lib/core/LLVMCG/FuncGen.py +++ b/lib/core/LLVMCG/FuncGen.py @@ -555,6 +555,73 @@ class FuncGenMixin: # 依赖补齐(上面的懒加载)后再确认一次展平(幂等);偏移基址由 _BaseNow() # 在真正 return 处现算,不在此缓存。 _BaseNow() + # ---- 布局真源优先(配对式偏移):偏移只认"运行时真正被 GEP 的那份 struct body" ---- + # class_members 是**可变**表(继承展平会在父类就绪后前置前缀、跨模块可按需重载), + # 而 struct body 一旦 set_body 即**永久冻结** ⇒ "base + 成员表下标"会随表漂移 + # 整体平移(现场:Converter 模块读 `Alias.name` 取到槽 9(= asname), + # `import t` 无 as 子句 ⇒ asname=NULL ⇒ vpi 生成误报"import 别名 name 为空"; + # 同族旧现场:ast.Constant.lineno 取到 pool 槽)。改为两条"只看布局"的路径: + # ① 建 body 时冻结下来的"成员名 → 槽位"映射(StructGen._generate_structs 挂载); + # ② 用**类型**把成员表逐项配对到 elements:类型不匹配的条目视为方法/占位/漂移项, + # 不占槽位;配对中类型相等的那个槽位即该字段的真实槽位。 + # 表与布局自洽时两条路径给出与旧下标法**逐字相同**的结果(body 本按该表建); + # 都覆盖不到时落回旧下标法(对既有正确行为零变化)。 + if field_name: + _OffStruct: Any = self.structs.get(ClassName) + if isinstance(StructHint, ir.IdentifiedStructType) and ClassName: + # 与 _member_base 同口径:只接受**同类**的 hint(接收者静态类型可能是基类, + # 实际指向派生类实例,其布局不属于本类)。 + if self._extract_short_name(getattr(StructHint, 'name', '') or '') == ClassName: + _OffStruct = StructHint + if isinstance(_OffStruct, ir.IdentifiedStructType): + _OffEls: Any = _OffStruct.elements + _Frozen: Any = getattr(_OffStruct, '_tpc_member_slots', None) + if isinstance(_Frozen, dict) and field_name in _Frozen: + _FrozenSlot: Any = _Frozen[field_name] + if isinstance(_FrozenSlot, int) and (not _OffEls or 0 <= _FrozenSlot < len(_OffEls)): + return _FrozenSlot + _OffMembers: list = self.class_members.get(_ResolvedClass) or [] + _BitFields: Any = getattr(self, 'class_member_bitfields', None) + if _OffEls and _OffMembers and not (_BitFields and _BitFields.get(ClassName)): + _Cursor: int = _BaseNow() + _ElemN: int = len(_OffEls) + _PairedNames: set = set() + for _nmP, _mtP in _OffMembers: + if _Cursor >= _ElemN: + break + # 重名条目不是布局槽位(表构造/重载产生的重复项)⇒ 不占槽位 + if _nmP in _PairedNames: + continue + # None 成员在 body 里被物化为 i32(见 StructGen 建 body 分支), + # 配对类型必须同口径,否则该类字段会被整条跳过。 + _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): + _PairedNames.add(_nmP) + if _nmP == field_name: + # 与旧下标法不一致时留痕:这正是"成员表与 struct 布局 + # 不自洽"的现场事实(每类每字段一次,便于定位漂移来源)。 + _OldIdx: int = -1 + for _i3, (_n3, _) in enumerate(_OffMembers): + if _n3 == field_name: + _OldIdx = _i3 + break + if _OldIdx >= 0 and _BaseNow() + _OldIdx != _Cursor: + _RepKey: tuple = (_ResolvedClass, field_name) + _RepSet: set = getattr(self, '_MemberSlotDriftReported', None) + if _RepSet is None: + _RepSet = set() + self._MemberSlotDriftReported = _RepSet + if _RepKey not in _RepSet: + _RepSet.add(_RepKey) + _vlog().warning( + f"[成员偏移] 类 '{_ResolvedClass}' 的成员表与 struct " + f"布局不自洽:字段 '{field_name}' 下标法={_BaseNow() + _OldIdx}" + f"(成员表 {len(_OffMembers)} 项) vs 布局配对比={_Cursor}" + f"(struct {_ElemN} 槽)——已采用布局配对结果", + "MemberBase") + return _Cursor + _Cursor += 1 if _ResolvedClass and _ResolvedClass in self.class_members: for i, (name, _) in enumerate(self.class_members[_ResolvedClass]): if name == field_name: diff --git a/lib/core/LLVMCG/StructGen.py b/lib/core/LLVMCG/StructGen.py index 6fb5c0a..f2d54d7 100644 --- a/lib/core/LLVMCG/StructGen.py +++ b/lib/core/LLVMCG/StructGen.py @@ -755,6 +755,28 @@ class StructGenMixin: # 让后续 _TryLoadStructFromStub 能正确设置 body。 if member_types: struct_type.set_body(*member_types) + # ---- 冻结"成员名 → 槽位下标"映射(布局真源,挂在 struct 实例上)---- + # struct body 一旦 set_body 即**永久冻结**,而 class_members 是**可变** + # 表(继承展平会在父类就绪后前置前缀、跨模块可按需重载/重跑), + # 两者序位会漂移。而字段访问用的是 `_get_member_offset` 的 + # `base + 成员表下标`,漂移即"整类字段偏移平移"——现场: + # Converter 模块读 `Alias.name` 取到槽 9(= asname),`import t` + # 无 as 子句 ⇒ asname=NULL ⇒ vpi 生成报"import 别名 name 为空"。 + # 这里把"建 body 那一刻"的真实槽位表记到 struct 实例上 + # (用实例属性而非全局表:同一类名可能存在不止一个 struct 实例, + # 偏移必须与被 GEP 的那一份同源)。仅当 member_types 恰为 + # `[vtable?] + 全部成员` 时记录(位域类折叠成单一 storage 槽, + # 其字段下标另由 class_member_bitoffsets 表达,不在此记录)。 + _ExpectN: int = (1 if has_vtable else 0) + len(all_members) + if len(member_types) == _ExpectN: + _SlotMap: dict[str, int] = {} + _SlotIdx: int = 1 if has_vtable else 0 + for _mnS, _ in all_members: + if _mnS not in _SlotMap: + _SlotMap[_mnS] = _SlotIdx + _SlotIdx += 1 + struct_type._tpc_member_slots = _SlotMap + struct_type._tpc_member_base = 1 if has_vtable else 0 def _create_Vtable_globals(self) -> None: # 「本模块有定义(非 declare)」的函数名所蕴含的全部**点分前缀**集合。