fix(tpc): struct body 冻结前逐元素校验类型一致性(错位 body 不再被当"完整")

a 编译器缺陷:_generate_structs Step 4 判定既有 struct body 是否"完整"时只比
「元素个数 / 首元素是 vtable 指针 / 是否含 T 占位」—— **类型错位**的 body
(个数命中、元素名也全对,但某槽被冻结成通用多层指针)会被直接 continue;
而 llvmlite 的 set_body 只能设一次 ⇒ 错布局被永久冻结,后续 .vpi / .stub 重载
只修 class_members、改不了 body ⇒ 赋值时报「层数不同」。

现场(成员槽来源明细实锤:声明侧正确、布局侧坏):
  class_members[Generator] 项数=47 base=0 下标=3
      声明槽类型=%"d827f3045bd28467.TypeGenCDef"*
  布局 struct name='fd4cd0ec583f42b9.Generator' 元素数=47 elements[3]=i8**
  → self.TypeGenCDef = SubTypeGenCDef
    指针隐式转换已禁止(层数不同):不能把 %…TypeGenCDef"* 隐式转为 i8**

修法:新增 _MemberSlotAccepts(判据与存储侧同一套 _ClassifyPtrImplicitConvert),
把 class_members 声明类型与 elements[base+i] 逐项比对;只有**存储时必被拒绝**的
组合(层数不同 / 指针与非指针混位)判为不一致 ⇒ 走既有「清 _elements + 清 stub
缓存」路径重建布局(通用指针槽 i8*、继承上转、数组退让照旧放行,不误清)。
This commit is contained in:
TermiNexus committed 2026-09-29 00:06:11 +08:00
1 parent c49dc1d915
commit b8ed33a200
1 file changed
+49 -1
+49 -1
View File
@@ -293,6 +293,32 @@ class StructGenMixin:
return ir_text
def _MemberSlotAccepts(self, Declared: Any, Slot: Any) -> bool:
"""声明类型能否**隐式写进**该 struct 槽(判据与存储侧同一套)。
用于判定已冻结的 struct body 是否与 `class_members` 自洽。判定放宽到与
存储侧一致,避免误清:通用指针槽(i8*/void*)照旧放行(历史 stub 普遍
如此),继承上转/数组退让亦放行;只有**存储时必被拒绝**的组合——层数
不同、指针与非指针混位——才判为不一致,触发重建。
"""
if Declared is None or Slot is None:
return True
if Declared == Slot or str(Declared) == str(Slot):
return True
_d_ptr: bool = isinstance(Declared, ir.PointerType)
_s_ptr: bool = isinstance(Slot, ir.PointerType)
if _d_ptr != _s_ptr:
return False
if not _d_ptr:
# 数值/浮点宽度差异不在本次收紧范围:保持既有行为(不清 body)。
return True
try:
Allowed, _Reason = self._ClassifyPtrImplicitConvert(Declared, Slot)
return bool(Allowed)
except Exception:
# 判据不可用(缺上下文)时保守放行,避免误清已正确的布局。
return True
def _generate_structs(self) -> None:
all_classes: set[str] = set(self.class_methods.keys()) | set(self.class_members.keys())
@@ -470,7 +496,29 @@ class StructGenMixin:
if _short_pre == 'T':
_has_t_placeholder_pre = True
break
if len(existing_st.elements) >= _expected_count_pre and _vtable_first_ok and not _has_t_placeholder_pre:
# 逐元素类型一致性(治本):仅比"个数 / 首元素是 vtable 指针 / T 占位"
# 会漏掉**类型错位**的已冻结 body —— 现场 Generator:47 个元素个数正确、
# 元素名字也全对,但第 4 槽是 i8** 而 class_members 声明
# %"d827f3045bd28467.TypeGenCDef"*,个数命中即被当作"完整"直接 continue;
# 而 llvmlite 的 set_body 只能设一次 ⇒ 错布局被永久冻结(后续 .vpi 重载
# 只修 class_members,改不了 body)⇒ 赋值时报"层数不同"。
# 判据与存储侧同一套(_MemberSlotAccepts → _ClassifyPtrImplicitConvert):
# 声明类型**能隐式写进该槽**才算一致 —— 通用指针槽(i8*)照旧放行,不误清。
_slot_types_ok: bool = True
_members_pre: list = self.class_members.get(ClassName, [])
if _members_pre:
_base_pre: int = 1 if _has_vtable_precheck else 0
_elems_pre: list = list(existing_st.elements)
for _i_pre, _item_pre in enumerate(_members_pre):
_j_pre: int = _base_pre + _i_pre
if _j_pre >= len(_elems_pre):
break
_decl_pre: Any = _item_pre[1] if isinstance(_item_pre, tuple) and len(_item_pre) >= 2 else None
if not self._MemberSlotAccepts(_decl_pre, _elems_pre[_j_pre]):
_slot_types_ok = False
break
if (len(existing_st.elements) >= _expected_count_pre and _vtable_first_ok
and not _has_t_placeholder_pre and _slot_types_ok):
if ClassName in self.class_packed and not existing_st.packed:
existing_st.packed = True
continue