fix(tpc): struct body 解冻改用 llvmlite 公开属性 elements(原 _elements 是空操作)

类别: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。
This commit is contained in:
TermiNexus committed 2026-09-29 13:13:17 +08:00
1 parent 9d8aedec4a
commit a6c9003a19
2 files changed
+22 -10

No files matched your search

+5 -2
View File
@@ -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)
+17 -8
View File
@@ -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,