From a6c9003a1959d660860a57da930c45d9f9279aea Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 29 Sep 2026 13:13:17 +0800 Subject: [PATCH] =?UTF-8?q?fix(tpc):=20struct=20body=20=E8=A7=A3=E5=86=BB?= =?UTF-8?q?=E6=94=B9=E7=94=A8=20llvmlite=20=E5=85=AC=E5=BC=80=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=20elements=EF=BC=88=E5=8E=9F=20=5Felements=20?= =?UTF-8?q?=E6=98=AF=E7=A9=BA=E6=93=8D=E4=BD=9C=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 类别:a 编译器缺陷(机制漏豁免:错误的"解冻"字段名使所有 body 重建路径失效)。 现场(连续多轮复现):TransPyV Generator.vp:200 `self.ExprGenFstr = SubExprGenFstr` 报「指针隐式转换已禁止(层数不同): %"a7b68c79f39cb4d3.ExprGenFstr"* → i8**」,[成员槽来源] 显示 class_members[Generator] 下标 11 声明槽类型**正确**,而布局 '47382dc8277c9d28.Generator' 的 elements[11]=i8**。 取证(已核对全部产物与共享符号表,排除"来源侧退化"): - TransPyV/output/*.stub.ll 与 temp/47382dc8277c9d28.stub.ll 里 %"47382dc8277c9d28.Generator" 的 slot11 全部是 %"a7b68c79f39cb4d3.ExprGenFstr"*, **没有任何 stub 含 i8**; - 共享符号表(temp/_shared_sym.json → symbol_table_bytes)里 Generator.Members['ExprGenFstr'] = CTypeInfo(BaseType=CStruct('ExprGenFstr'), PtrCount=1, IsStruct=True),也**正确**。 真根因:llvmlite 本版本 IdentifiedStructType 的字段是**公开属性 `elements`** (types.py: `self.elements = None`;`is_opaque` = `elements is None`; `set_body` 仅在 `elements is not None` 时抛 "already defined")。而本仓所有 "清除 body 以便重建"的路径都写 `st._elements = None`(不存在的属性)⇒ **空操作** ⇒ ⇒ `if struct_type.elements is None` 恒假 ⇒ set_body 永不执行 ⇒ 早期冻结的错误布局 (如 i8**)无法被任何后续权威重建覆盖: · StructGen Step 4 逐元素校验已判定不一致(_slot_types_ok=False),清空失败 ⇒ 既不能 skip 也不能重建 ⇒ 错布局被静默保留; · HandlesClassDef 的本地定义权威重建点(class_members 已重建为正确类型后) 同样无法刷新 body。 两处叠加正是"class_members 正确、布局却是 i8**"的稳定现象,也解释了此前 TypeGenCDef(槽3) / ExprGenFstr(槽11) 在不同构建里交替出现(取决于谁先被冻结)。 修复:四处解冻改为写公开属性 `elements`: · StructGen.py Step 4 前置校验失败后的清空; · StructGen.py sha1 全名 key 的 struct 清空; · StructGen.py T* 占位清空; · HandlesClassDef.py @t.NoVTable 本地定义点的清空。 清空后 is_opaque 恢复为真 ⇒ _TryLoadStructFromStub(declare 侧权威 body)或 class_members 重建可真正 set_body。 --- lib/core/Handles/HandlesClassDef.py | 7 +++++-- lib/core/LLVMCG/StructGen.py | 25 +++++++++++++++++-------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/lib/core/Handles/HandlesClassDef.py b/lib/core/Handles/HandlesClassDef.py index 5f409bf..f23b851 100644 --- a/lib/core/Handles/HandlesClassDef.py +++ b/lib/core/Handles/HandlesClassDef.py @@ -700,10 +700,13 @@ class ClassHandle(BaseHandle): Gen._cross_module_vtable_classes.discard(ClassName) Gen._cross_module_novtable.add(ClassName) # 清除可能从导入 stub 加载的旧 struct body(含错误 vtable 指针), - # 让 _generate_structs 用本地 class_members 重新生成 + # 让 _generate_structs 用本地 class_members 重新生成。 + # 字段名必须是 llvmlite 的**公开属性 `elements`**(is_opaque = elements is None; + # set_body 仅在 elements is not None 时拒绝)——写 `_elements` 在本版本是空操作, + # 会让"权威重建点"永远无法刷新已冻结的错误布局。 _old_st = Gen.structs.get(ClassName) if isinstance(_old_st, ir.IdentifiedStructType) and _old_st.elements is not None: - _old_st._elements = None + _old_st.elements = None _ih = getattr(Gen, '_import_handler_ref', None) if _ih is not None: _ck = (id(Gen), ClassName) diff --git a/lib/core/LLVMCG/StructGen.py b/lib/core/LLVMCG/StructGen.py index 6cbde73..beef76a 100644 --- a/lib/core/LLVMCG/StructGen.py +++ b/lib/core/LLVMCG/StructGen.py @@ -499,7 +499,8 @@ class StructGenMixin: # 治本修复:检查 struct body 是否不完整(元素数少于 class_members + vtable 预期)。 # 常见于跨模块继承:子类 struct 在父类 class_members 加载前从 stub 生成, # stub 中的定义可能不完整或类型不正确(如 GSList* 代替 AST*、i8* 代替 i32)。 - # 此处检测到不完整后清除 _elements 和 stub 缓存,让后续代码重新从 stub 加载。 + # 此处检测到不完整后清除 elements(公开属性,见下方 Step 4 说明)和 + # stub 缓存,让后续代码重新从 stub 加载。 _has_vtable_precheck: bool = ClassName in self.class_vtable or ClassName in self._cross_module_vtable_classes if not _has_vtable_precheck: _p_pre: str | None = self.class_parent.get(ClassName) @@ -554,19 +555,26 @@ class StructGenMixin: if ClassName in self.class_packed and not existing_st.packed: existing_st.packed = True continue - # struct body 不完整,清除 _elements 和 stub 缓存以便重新加载 - existing_st._elements = None - # 同时清除 sha1 全名 key 的 _elements: + # struct body 不完整,清除 elements 和 stub 缓存以便重新加载。 + # 注意:本仓 llvmlite 版本的字段名是**公开属性 `elements`** + # (types.py: `self.elements = None`;`is_opaque` = `elements is None`; + # `set_body` 只在 `elements is not None` 时拒绝)。此前写 + # `_elements = None` 在本版本**不存在该属性** ⇒ 清空是空操作 ⇒ + # 后续 `if struct_type.elements is None` 恒假 ⇒ set_body 永不执行 ⇒ + # 错误布局(现场 Generator elements[11]=i8**)被永久冻结,而 + # class_members 已被权威重建为正确类型 ⇒ 赋值时报"层数不同"。 + existing_st.elements = None + # 同时清除 sha1 全名 key 的 elements: # self.structs 中可能同时存在 'Value'(短名) 和 'f9e36e2cd6fa659f.Value'(sha1全名) 两个 key, # _TryLoadStructFromStub 调用 _get_or_create_struct 时会返回 sha1全名 key 的 struct。 - # 若只清除短名 key 的 _elements,sha1全名 key 的 struct 仍保持错误 body(is_opaque=False), + # 若只清除短名 key 的 elements,sha1全名 key 的 struct 仍保持错误 body(is_opaque=False), # 导致 _TryLoadStructFromStub 跳过 set_body,struct 保持不完整字段数。 _imported_sha1_pre: str = self.class_sha1_map.get(ClassName, '') if _imported_sha1_pre: _full_key_pre: str = f"{_imported_sha1_pre}.{ClassName}" _full_st_pre: ir.IdentifiedStructType | None = self.structs.get(_full_key_pre) if _full_st_pre is not None and isinstance(_full_st_pre, ir.IdentifiedStructType): - _full_st_pre._elements = None + _full_st_pre.elements = None _import_handler_pre: Any = self._import_handler_ref if _import_handler_pre is not None: _cache_key_pre: tuple = (id(self), ClassName) @@ -672,7 +680,8 @@ class StructGenMixin: struct_type = self.structs.get(ClassName, struct_type) # 检测 stub body 中的 T* 占位类型(未特化的泛型类型参数)。 # 若 stub 使用了未特化的基类(如 GSListNode 而非 GSListNode[BasicBlock]), - # 清除 _elements 让 set_body 用正确的 class_members 重新设置。 + # 清除 elements 让 set_body 用正确的 class_members 重新设置 + # (字段名必须是公开属性 `elements`,见上方 Step 4 的说明)。 if struct_type.elements is not None and member_types: _has_t_placeholder_post: bool = False for _elem_post in struct_type.elements: @@ -683,7 +692,7 @@ class StructGenMixin: _has_t_placeholder_post = True break if _has_t_placeholder_post: - struct_type._elements = None + struct_type.elements = None if struct_type.elements is None: # 防御性检查:member_types 为空时不调用 set_body。 # set_body(*[]) 会设置 elements=() 使 is_opaque=False,