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:
TermiNexus committed 2026-10-01 15:00:55 +08:00
1 parent dc4e06d88b
commit f748cae562
1 file changed
+110 -12
+110 -12
View File
@@ -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*,