fix(TPV/CallGenCall): 判据化重加兄弟 Mixin 方法解析(修 NoVTableCrossMixinTest 未定义符号)
现场:Test/NoVTableCrossMixinTest 链接期报
ld.lld: error: undefined symbol: 1e19037e6cfdce36.BaseB.helper
>>> referenced by ...(1e19037e6cfdce36.BaseB.caller)
ld.lld: error: undefined symbol: 1e19037e6cfdce36.MixinC2.mid2
>>> referenced by ...(1e19037e6cfdce36.MixinC2.outer2)
这是 455c760 删除"兄弟 Mixin 兜底"的必然结果:NoVTable 多继承下,Mixin 内的
self.<兄弟方法>()(Combined(BaseA, BaseB) 中 BaseB.caller 调 BaseA.helper)本类
MRO 查不到、跨模块 stub 也没有 ⇒ 调用名退化成 {sha1}.BaseB.helper(该符号不存在)。
改动(不是恢复原兜底,而是**判据化解析**,不做"同名即绑"的猜测):
- CallGenCall._translate_method_call:在自身 MRO 查找失败后新增兄弟 Mixin 解析,
两条**正向判据**缺一不可:
① 接收类必须是某个已注册类的**直接父类**(它确实被组合使用,是真 Mixin)——
挡掉 includes/w32/fileio.vp 的 FileW.__init__ → self.seek() 误绑 File.seek
(FileW 无人继承,不是任何人的 Mixin);
② 候选类与接收类必须同处某个已注册**组合类的 MRO**(真·兄弟)。
命中后 found_func / mc_anc 都指向定义类 ⇒ 调用名用定义类 SHA1 前缀(符号存在),
this 由既有的 mc_anc this-adjust 分支按组合布局前移(该分支未改动)。
只取**本模块内已有函数**的候选(跨模块定义类需经其 text.ll 取签名,本轮不启用);
接收类为跨模块类时不启用(沿用原 sib_recv_local 守卫,避免误绑跨模块同名方法)。
- 新增两个判据辅助方法:_recv_is_mixin_base / _mixin_sibling_same_composite。
待验证:重编译 TransPyV(自举两遍)→ NoVTableCrossMixinTest / WebTest / FileIoTest。
This commit is contained in:
1 parent
dc4e06d88b
commit
f748cae562
1 file changed
+110
-12
@@ -807,16 +807,50 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
|
||||
break
|
||||
mi += 1
|
||||
|
||||
# 【已删除:兄弟 Mixin 兜底(2026-10-01,用户决定"不兜底、fail-fast")】
|
||||
# 原实现:found_func 未命中时遍历全部已注册 struct,取"本模块存在同名方法"的
|
||||
# 第一个候选类并绑定(this 再按组合布局前移)。该扫描的候选判据里**没有**
|
||||
# "接收类自身是否声明了该方法"这一条(TPC 侧 HandlesExprCall.py:3877-3914 有),
|
||||
# 因而会把同类的方法误绑到无关类:includes/w32/fileio.vp 的 FileW.__init__
|
||||
# 调用 self.seek(...) 时(FileW.seek 尚未发射)被绑成 File.seek——旧 IR 靠
|
||||
# `bitcast %"…FileW"* to %"…File"*` 静默掩盖,指针规则收紧后以 CastError 报出。
|
||||
# 根因已由"先声明后定义"消除(ClassGenVTable._translate_oop_methods 在翻译
|
||||
# 方法体前对本类全部方法跑 sig_only=1 建前向声明):方法查找不再依赖类体
|
||||
# 书写顺序,未命中即为真未定义,落下方 fail-fast,不再猜候选类。
|
||||
# 兄弟 Mixin 方法解析(判据化,**不做"同名即绑"的猜测**)。
|
||||
# NoVTable 多继承下,Mixin 内的 self.<兄弟方法>() 接收者是**本 Mixin 类型**,
|
||||
# 方法却定义在兄弟 Mixin 里(现场 Test/NoVTableCrossMixinTest:
|
||||
# Combined(BaseA, BaseB) 中 BaseB.caller 调 self.helper(),helper 定义在 BaseA)
|
||||
# ——本类 MRO 查不到、跨模块 stub 也没有,但组合类布局里确有该字段区,
|
||||
# 须绑定到**定义类**并由下方 this-adjust(mc_anc 分支)前移指针。
|
||||
# 两条正向判据,缺一不可:
|
||||
# ① 接收类必须是某个已注册类的直接父类(它真的被组合使用)——挡掉
|
||||
# includes/w32/fileio.vp 的 FileW.__init__ → self.seek() 误绑 File.seek
|
||||
# (FileW 无人继承,不是任何 Mixin);
|
||||
# ② 候选类与接收类必须同处某个已注册组合类的 MRO(真·兄弟)。
|
||||
# 仅取**本模块内已有函数**的候选(跨模块定义类需经其 text.ll 取签名,未启用);
|
||||
# 接收类为跨模块类时不启用(其方法必须经该类自身模块解析)。
|
||||
if found_func is None:
|
||||
sib_recv_local: int = 1
|
||||
if trans is not None and trans.ModuleSha1 is not None:
|
||||
sib_recv_sha1: str = None
|
||||
if mc_entry is not None and mc_entry.ModuleSha1 is not None:
|
||||
sib_recv_sha1 = mc_entry.ModuleSha1
|
||||
else:
|
||||
sib_recv_sha1 = self.Gen.StructGen.get_struct_sha1(class_name)
|
||||
if sib_recv_sha1 is not None:
|
||||
if string.strcmp(sib_recv_sha1, trans.ModuleSha1) != 0:
|
||||
sib_recv_local = 0
|
||||
if sib_recv_local != 0:
|
||||
if self._recv_is_mixin_base(class_name) != 0:
|
||||
sc_i: int = 0
|
||||
sc_n: int = self.Gen.StructGen.get_struct_count()
|
||||
while sc_i < sc_n and found_func is None:
|
||||
cand_entry: GenTypes.StructEntry* = self.Gen.StructGen._get_struct_entry(sc_i)
|
||||
if cand_entry is not None and cand_entry.Name is not None:
|
||||
cand_name: str = cand_entry.Name
|
||||
if string.strcmp(cand_name, class_name) != 0:
|
||||
# 泛型特化类名含 '[':不是 Mixin 候选
|
||||
if string.strchr(cand_name, '[') is None:
|
||||
if self._mixin_sibling_same_composite(pool, class_name, cand_name) != 0:
|
||||
cand_lookup: str = pool.alloc(128)
|
||||
if cand_lookup is not None:
|
||||
viperlib.snprintf(cand_lookup, 128, "%s.%s", cand_name, method_name)
|
||||
cand_func: llvmlite.Function* = self.find_func_in_module(mod, cand_lookup)
|
||||
if cand_func is not None:
|
||||
found_func = cand_func
|
||||
mc_anc = cand_name
|
||||
sc_i += 1
|
||||
|
||||
# 确定调用名(默认 "ClassName.method";found_func 未命中时沿 MRO 用
|
||||
# 方法实际定义类的 SHA1 构造,如 Module.append → AST 定义的 append,
|
||||
@@ -1933,14 +1967,78 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
|
||||
self._ensure_method_declare(pool, mod, call_name, ret_ty_sp, head, arg_count_sp)
|
||||
is_variadic_sp: int = self.Gen.CallGenVa._check_and_promote_variadic_call(pool, builder, mod, call_name, head, arg_count_sp, cargs_sp, 1, trans)
|
||||
return llvmlite.build_call(builder, call_name, head, arg_count_sp, ret_ty_sp, is_variadic_sp)
|
||||
# ============================================================
|
||||
# _recv_is_mixin_base - 判据①:接收类是否"被当作父类组合使用"(真 Mixin)
|
||||
#
|
||||
# 用于兄弟 Mixin 方法解析:只有当接收类确实是某个已注册类的**直接父类**时,
|
||||
# 才可能存在"组合类的布局里有该字段区"的情形。反例:FileW 无人继承 ⇒
|
||||
# FileW.__init__ 里的 self.seek() 不得绑到同名方法 File.seek。
|
||||
# ============================================================
|
||||
def _recv_is_mixin_base(self, class_name: str) -> int:
|
||||
"""接收类是否为某个已注册类的直接父类(1=是 / 0=否)"""
|
||||
if class_name is None:
|
||||
return 0
|
||||
if self.Gen is None:
|
||||
return 0
|
||||
j: int = 0
|
||||
n: int = self.Gen.StructGen.get_struct_count()
|
||||
while j < n:
|
||||
e: GenTypes.StructEntry* = self.Gen.StructGen._get_struct_entry(j)
|
||||
if e is not None and e.Name is not None:
|
||||
pc: int = self.Gen.StructGen.get_parent_count(e.Name)
|
||||
pj: int = 0
|
||||
while pj < pc:
|
||||
pn: str = self.Gen.StructGen.get_parent_name_at(e.Name, pj)
|
||||
if pn is not None:
|
||||
if string.strcmp(pn, class_name) == 0:
|
||||
return 1
|
||||
pj += 1
|
||||
j += 1
|
||||
return 0
|
||||
|
||||
# ============================================================
|
||||
# _mixin_sibling_same_composite - 判据②:两类是否同处某个组合类的 MRO
|
||||
#
|
||||
# 兄弟 Mixin 的充要条件:存在一个已注册类 K,其 MRO 同时含 class_name 与
|
||||
# cand_name(如 Combined(BaseA, BaseB):MRO 含 BaseA 与 BaseB ⇒ 二者是兄弟)。
|
||||
# 只有满足它才允许把 self.<method>() 绑到同名的兄弟类方法上。
|
||||
# ============================================================
|
||||
def _mixin_sibling_same_composite(self, pool: memhub.MemBuddy*,
|
||||
class_name: str, cand_name: str) -> int:
|
||||
"""两类是否同处某个已注册组合类的 MRO(1=是 / 0=否)"""
|
||||
if class_name is None or cand_name is None or pool is None:
|
||||
return 0
|
||||
if self.Gen is None:
|
||||
return 0
|
||||
j: int = 0
|
||||
n: int = self.Gen.StructGen.get_struct_count()
|
||||
while j < n:
|
||||
k_entry: GenTypes.StructEntry* = self.Gen.StructGen._get_struct_entry(j)
|
||||
if k_entry is not None and k_entry.Name is not None:
|
||||
mro: sizet* = self.Gen.StructGen.get_mro_chain(pool, k_entry.Name)
|
||||
if mro is not None:
|
||||
has_self: int = 0
|
||||
has_cand: int = 0
|
||||
mi: int = 0
|
||||
while mro[mi] != 0:
|
||||
nm: str = rawptr(mro[mi])
|
||||
if nm is not None:
|
||||
if string.strcmp(nm, class_name) == 0:
|
||||
has_self = 1
|
||||
elif string.strcmp(nm, cand_name) == 0:
|
||||
has_cand = 1
|
||||
if has_self != 0 and has_cand != 0:
|
||||
return 1
|
||||
mi += 1
|
||||
j += 1
|
||||
return 0
|
||||
|
||||
# ============================================================
|
||||
# _field_byte_offset — 计算字段在结构体布局中的字节偏移
|
||||
#
|
||||
# 按 LLVM 布局遍历 struct elements 带对齐累加(与 ClassGenMixin._get_type_size
|
||||
# 的 padding 规则一致)。field_index 是 FieldEntry.Index(OOP 类 __vtable__ 占 0)。
|
||||
# 供多继承 this 调整(字节偏移法)计算 delta。
|
||||
# ============================================================
|
||||
|
||||
# ============================================================
|
||||
def _field_byte_offset(self, pool: memhub.MemBuddy*,
|
||||
entry: GenTypes.StructEntry*,
|
||||
|
||||
Reference in new issue
Block a user