fix(LLVMCG): 成员表重名/联合类型字段不再打断布局配对,ExceptHandler.name 取到槽 9
类别: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 通过。
This commit is contained in:
1 parent
8f1ec5b715
commit
bd92dfdadf
2 files changed
+69
-2
No files matched your search
@@ -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 中时(常见于跨模块导入
|
||||
|
||||
@@ -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`;
|
||||
|
||||
Reference in new issue
Block a user