fix(gen): TPV isinstance 运行链路修复(构造 vtable 槽/this_adjust/虚分派/NoVTable)

- 构造展开跳过 __vtable__ 字段,修复 Dog("dog") 覆盖 vtable 导致 isinstance 全失败
- this_adjust 改字节偏移法(delta=子类偏移-父类偏移),覆盖 vtable 父类
- _do_virtual_call 新增 vtable_base:读 vtable 用对象头,调用用调整后 self
- NoVTable 类无参构造分配内存而非返回 ConstZero
- ClassGenMixin:类体 CDefine 兜底注册 + vtable linkage 默认 external
This commit is contained in:
TermiNexus committed 2026-08-10 21:15:43 +08:00
1 parent 6677da99f7
commit 45ba3a4b5a
3 files changed
+476 -20

No files matched your search

+459 -17
View File
@@ -19,6 +19,7 @@ import lib.core.Handles.HandlesNonlocal as HandlesNonlocal
import lib.core.Handles.HandlesStruct as HandlesStruct
import lib.core.Handles.HandlesImports as HandlesImports
import lib.core.Handles.HandlesClassDef as HandlesClassDef
import lib.core.Handles.HandlesFunctions as HandlesFunctions
import lib.core.StubMerger as StubMerger
import lib.core.Handles.HandlesExprCall as HandlesExprCall
@@ -1977,7 +1978,25 @@ class CallGenMixin:
continue
fe: HandlesStruct.FieldEntry | t.CPtr = None
if ctor_entry is not None:
fe = HandlesStruct._get_field_entry(ctor_entry, ai)
# OOP 结构体字段表第 0 个字段是 __vtable__(vtable 指针占位),
# 构造参数按"用户字段顺序"存储:必须跳过 __vtable__ 字段,
# 否则参数会覆盖 vtable 槽(对象头读到垃圾指针,
# isinstance 的 icmp 全部不匹配 + 方法分派错乱)。
# 原实现直接 _get_field_entry(ctor_entry, ai) 取到 __vtable__
# → GEP Index 0 → 覆盖 vtable(Dog("dog") 场景崩溃漂移根因)。
target_idx: int = 0
for fi in range(ctor_entry.FieldCount):
cand_fe: HandlesStruct.FieldEntry | t.CPtr = HandlesStruct._get_field_entry(
ctor_entry, fi)
if cand_fe is None:
continue
cand_name: str = HandlesStruct.get_field_name_ptr(cand_fe)
if cand_name is not None and string.strcmp(cand_name, "__vtable__") == 0:
continue
if target_idx == ai:
fe = cand_fe
break
target_idx += 1
else:
fe = HandlesStruct.get_field_by_index(class_name, ai)
if fe is None:
@@ -2187,12 +2206,21 @@ class CallGenMixin:
args_head: llvmlite.Value | t.CPtr,
args_count: int,
found_func: llvmlite.Function | t.CPtr,
ret_ty: llvmlite.LLVMType | t.CPtr) -> llvmlite.Value | t.CPtr:
"""通过虚表间接调用虚方法,返回 call 结果(void 方法返回 None)"""
ret_ty: llvmlite.LLVMType | t.CPtr,
vtable_base: llvmlite.Value | t.CPtr = None) -> llvmlite.Value | t.CPtr:
"""通过虚表间接调用虚方法,返回 call 结果(void 方法返回 None)
vtable_base: 对象头指针(vtable 所在位置,即未调整的 obj_ptr)。
多继承 this 调整后 self_ptr 可能前移(如 obj+4 指向父类字段区),
从调整后位置读 vtable 会读到字段值 → 垃圾指针崩溃。默认 None 时
vtable_base = self_ptr(未调整场景,读对象头)。
"""
if pool is None or builder is None or class_name is None or method_name is None:
return None
if self_ptr is None or args_head is None:
return None
if vtable_base is None:
vtable_base = self_ptr
# 1. 获取结构体类型(用 find_struct_by_type 规避跨模块同名 find_struct 找错)
vc_entry: HandlesStruct.StructEntry | t.CPtr = None
@@ -2234,8 +2262,10 @@ class CallGenMixin:
# 4. bitcast 到 i8** 访问 __vtable__ 字段(索引 0)
# vtable 指针总是在对象第一个字段,无需 GEP,直接 bitcast 到 i8** 即可。
# 这样即使 struct_ty 是 opaque(跨模块未加载完整定义)也能正常工作。
# 注意用 vtable_base(对象头)而非 self_ptr:多继承 this 调整后
# self_ptr 已前移,其头部不是 vtable 而是父类字段值。
vtable_slot: llvmlite.Value | t.CPtr = llvmlite.build_bitcast(
builder, self_ptr, i8_ptr_ptr_ty)
builder, vtable_base, i8_ptr_ptr_ty)
if vtable_slot is None:
return None
@@ -2588,7 +2618,6 @@ class CallGenMixin:
if cmop_has_vt == 1 and cmop_method_idx >= 0:
return self._do_virtual_call(pool, builder, mod, class_name, method_name,
self_ptr, head, total_count, found_func, call_ret_ty)
# 跨模块调用(found_func 为 None):创建 declare 声明,避免 llc 报 undefined value
if found_func is None:
self._ensure_method_declare(pool, mod, call_name, call_ret_ty, head, total_count)
@@ -3471,15 +3500,21 @@ class CallGenMixin:
if kw_calls is not None:
kwc = kw_calls.__len__()
# 多继承 this 调整:继承方法定义在"无 vtable 的父类"且该父类字段区在子类中
# 偏移 > 0 时(如 C(A,B).get_b,B 的字段区在 C 中从 b 开始),父类方法按自身
# 布局读 self.field@0 会读到前面父类的字段。调整:GEP 到父类第一个用户字段
# 在子类中的位置,bitcast 成父类指针作为 self。
# 有 vtable 的父类布局是子类布局前缀(vptr 共享),self 无需调整。
# 多继承 this 调整(字节偏移法,2026-08-10,与 TPC HandlesExprCall._adjust_this_for_inherited_method 一致):
# 继承方法体按"定义类自身布局"读 self 字段(如 B.get_b 内 GEP(self_B, [0,1])
# 的字节偏移是 B 布局中 b 的 offset 12)。父类字段区在子类中的字节偏移可能
# 不同(如 AB={vtable,a@8,b@12} 中 b 偏移 16),self 需按字节差前移,使父类
# 方法内二次 GEP 恰好落到子类字段。
# delta = 字段在子类中字节偏移 − 字段在父类自身布局中字节偏移
# delta==0(第一父类,字段区前缀对齐)无需调整;delta>0 时按字节 GEP 到
# 父类字段区在子类中的等效基址,再 bitcast 成父类指针。
# 原实现仅覆盖"无 vtable 的父类"(其首字段偏移 0,GEP 到首字段即父类起点),
# 且用 GEP 字段法——对 vtable 父类(首字段偏移非 0,如 B)被直接跳过不调整
# → AB 场景 B.get_b 按 B 布局读 self.b@12 落到 a → AB 字段/方法 FAILED。
self_arg: llvmlite.Value | t.CPtr = obj_ptr
if mc_anc is not None and string.strcmp(mc_anc, class_name) != 0:
anc_entry_th: HandlesStruct.StructEntry | t.CPtr = HandlesStruct.find_struct(mc_anc)
if anc_entry_th is not None and anc_entry_th.HasVTable == 0 and anc_entry_th.FieldCount > 0:
if anc_entry_th is not None and anc_entry_th.FieldCount > 0:
anc_first_name_th: str = None
afi_th: int = 0
while afi_th < anc_entry_th.FieldCount:
@@ -3495,13 +3530,27 @@ class CallGenMixin:
sub_entry_th: HandlesStruct.StructEntry | t.CPtr = HandlesStruct.find_struct(
class_name)
if sub_entry_th is not None:
anc_field_th: HandlesStruct.FieldEntry | t.CPtr = HandlesStruct.lookup_field(
anc_entry_th.Ty, anc_first_name_th)
sub_field_th: HandlesStruct.FieldEntry | t.CPtr = HandlesStruct.lookup_field(
sub_entry_th.Ty, anc_first_name_th)
if sub_field_th is not None and sub_field_th.Index > 0:
anc_ptr_ty_th: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, anc_entry_th.Ty)
adj_th: llvmlite.Value | t.CPtr = llvmlite.build_gep_struct(
builder, sub_entry_th.Ty, sub_field_th.Ty, obj_ptr, sub_field_th.Index)
self_arg = llvmlite.build_bitcast(builder, adj_th, anc_ptr_ty_th)
if anc_field_th is not None and sub_field_th is not None:
anc_off_th: int = self._field_byte_offset(
pool, anc_entry_th, anc_field_th.Index)
sub_off_th: int = self._field_byte_offset(
pool, sub_entry_th, sub_field_th.Index)
delta_th: int = sub_off_th - anc_off_th
if delta_th > 0:
i8_ty_th: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
i8_ptr_th: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty_th)
obj_i8_th: llvmlite.Value | t.CPtr = llvmlite.build_bitcast(
builder, obj_ptr, i8_ptr_th)
delta_val_th: llvmlite.Value | t.CPtr = llvmlite.const_int32(pool, delta_th)
adj_th: llvmlite.Value | t.CPtr = llvmlite.build_gep(
builder, i8_ty_th, obj_i8_th, delta_val_th)
if adj_th is not None:
anc_ptr_ty_th: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, anc_entry_th.Ty)
self_arg = llvmlite.build_bitcast(builder, adj_th, anc_ptr_ty_th)
# 构建参数链表: self → arg1 → arg2 → ...
llvmlite.value_set_next(self_arg, None)
head: llvmlite.Value | t.CPtr = self_arg
@@ -3666,8 +3715,12 @@ class CallGenMixin:
mc_method_idx = mc_mi
break
if mc_has_vt == 1 and mc_method_idx >= 0:
# 虚分派 self 必须用调整后的 self_arg(多继承 this 调整),
# 否则 B.get_b 收到未调整的 AB*,方法体内按 B 布局读字段错位。
# vtable 读取必须用对象头 obj_ptr(调整后 self 头部是父类字段值)。
return self._do_virtual_call(pool, builder, mod, class_name, method_name,
obj_ptr, head, total_count, found_func, call_ret_ty)
self_arg, head, total_count, found_func, call_ret_ty,
obj_ptr)
# 跨模块调用:创建 declare 声明,避免 llc 报 undefined value
self._ensure_method_declare(pool, mod, call_name, call_ret_ty, head, total_count)
@@ -3675,6 +3728,48 @@ class CallGenMixin:
is_variadic_3441: int = HandlesExprCall._check_and_promote_variadic_call(pool, builder, mod, call_name, head, total_count)
return llvmlite.build_call(builder, call_name, head, total_count, call_ret_ty, is_variadic_3441)
# ============================================================
# _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 | t.CPtr,
entry: HandlesStruct.StructEntry | t.CPtr,
field_index: int) -> int:
"""计算字段在结构体中的字节偏移(含对齐 padding),无法计算返回 0"""
if pool is None or entry is None or entry.Ty is None or field_index < 0:
return 0
match entry.Ty:
case llvmlite.LLVMType.Struct(fields, fcount, name):
offset_fb: int = 0
cur_fb: llvmlite.ParamNode | t.CPtr = fields
i_fb: int = 0
while cur_fb is not None and i_fb < field_index:
if cur_fb.Ty is not None:
fty_fb: llvmlite.LLVMType | t.CPtr = (llvmlite.LLVMType | t.CPtr)(cur_fb.Ty)
fsize_fb: int = HandlesClassDef._get_type_size(fty_fb)
falign_fb: int = HandlesClassDef._get_type_align(fty_fb)
if falign_fb > 1:
rem_fb: int = offset_fb % falign_fb
if rem_fb != 0:
offset_fb += falign_fb - rem_fb
offset_fb += fsize_fb
cur_fb = cur_fb.Next
i_fb += 1
# 字段自身对齐
if cur_fb is not None and cur_fb.Ty is not None:
fty2_fb: llvmlite.LLVMType | t.CPtr = (llvmlite.LLVMType | t.CPtr)(cur_fb.Ty)
falign2_fb: int = HandlesClassDef._get_type_align(fty2_fb)
if falign2_fb > 1:
rem2_fb: int = offset_fb % falign2_fb
if rem2_fb != 0:
offset_fb += falign2_fb - rem2_fb
return offset_fb
case _:
return 0
# ============================================================
# _kwarg_name_matches — 检查关键字参数名是否匹配函数参数名
#
@@ -4759,6 +4854,17 @@ class CallGenMixin:
i64_ty_ln2: llvmlite.LLVMType | t.CPtr = llvmlite.Int64(pool)
return llvmlite.build_call(builder, "strlen", len_coerced, 1, i64_ty_ln2, 0)
# 检测 isinstance() 内置函数调用 — 运行时 vtable 子类集合比较
# isinstance(obj, Cls):obj 头部 vtable 指针 ∈ {Cls 及全部子类 vtable};
# NoVTable 目标类回退编译时静态判断(详见 _translate_isinstance)。
if cl.func is not None and cl.func.kind() == ast.ASTKind.Name:
if func_name is not None and string.strcmp(func_name, "isinstance") == 0:
if can >= 2:
is_res: llvmlite.Value | t.CPtr = self._translate_isinstance(
pool, builder, mod, cargs.get(0), cargs.get(1), trans)
if is_res is not None:
return is_res
# 检测类名静态/类方法调用: ClassName.method(args)
# 当 cl.func 是 Attribute、value 是已注册结构体类名(非普通变量)时,
# 且方法带 @staticmethod/@classmethod 元数据,走静态调用路径
@@ -5184,6 +5290,16 @@ class CallGenMixin:
if ctor_is_oop != 0:
return self._translate_struct_ctor(
pool, builder, mod, struct_ty, func_name, cargs, 0, trans)
# @t.NoVTable 类(无方法、类语义/指针对象):无参构造也应分配内存
# 返回指针。否则按值结构体返回 ConstZero(struct_ty),LHS 联合注解
# (如 Plain|CPtr)存 0 指针 → p.v 解引用 NULL 段错误
# (TpvIsinstanceTest Plain 场景崩溃根因)。
ctor_is_novt: int = 0
if gen_ctor_entry is not None:
ctor_is_novt = gen_ctor_entry.IsNoVTable
if ctor_is_novt != 0:
return self._translate_struct_ctor(
pool, builder, mod, struct_ty, func_name, cargs, 0, trans)
# 普通结构体:检查是否有默认值(用 entry 直接访问)
gen_has_defaults: int = 0
if gen_ctor_entry is not None:
@@ -5681,6 +5797,332 @@ class CallGenMixin:
is_variadic_call: int = HandlesExprCall._check_and_promote_variadic_call(pool, builder, mod, call_name, head, can)
return llvmlite.build_call(builder, call_name, head, can, call_ret_ty, is_variadic_call)
# ============================================================
# isinstance - 运行时 vtable 子类集合比较(2026-08-10)
#
# CVTable 对象头部 vtable 指针唯一标识对象实际类。isinstance(obj, Cls)
# 语义 = Cls 是实际类的祖先(实际类 == Cls 或继承自 Cls)。运行时只能
# 拿到实际类 vtable 地址,无法向上推导祖先链,因此在编译期沿
# StructEntry.ParentNames 反向 BFS 枚举 Cls 的全部已知子类(含自身),
# 运行时比较 obj->vtable ∈ {Cls 子类 vtable 地址集合}。
# NoVTable / 未知类回退编译时静态判断(obj 静态类型 MRO 判断)。
# ============================================================
# isinstance 子类收集上限(防数组越界;t.CDefine 注解由 TPC 解析为 i32)
ISINSTANCE_CLASS_MAX: t.CDefine = 64
def _translate_isinstance(self, pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
obj_node: ast.AST | t.CPtr,
cls_node: ast.AST | t.CPtr,
trans: HT.Translator | t.CPtr = None) -> llvmlite.Value | t.CPtr:
"""isinstance(obj, Cls):运行时 vtable 子类集合比较,返回 i1"""
if obj_node is None or cls_node is None:
return llvmlite.const_int(pool, 1, 0)
dbg_enter: t.CChar | t.CPtr = VLogger.fmt_buf()
if dbg_enter is not None:
viperlib.snprintf(dbg_enter, 1024, "[ISINSTANCE] ENTER kind=%d", cls_node.kind())
VLogger.info(dbg_enter, "ISINSTANCE")
obj_val: llvmlite.Value | t.CPtr = HandlesExpr.translate_value(
builder, pool, mod, obj_node, None, 0, trans)
dbg_a: t.CChar | t.CPtr = VLogger.fmt_buf()
if dbg_a is not None:
viperlib.snprintf(dbg_a, 1024, "[ISINSTANCE] A obj_set=%d",
0 if obj_val is None else 1)
VLogger.info(dbg_a, "ISINSTANCE")
if obj_val is None or obj_val.Ty is None:
return llvmlite.const_int(pool, 1, 0)
# 对象指针规整:二级指针(变量 alloca / 联合 Class|t.CPtr)先 load
obj_ptr: llvmlite.Value | t.CPtr = obj_val
match obj_val.Ty:
case llvmlite.LLVMType.Ptr(inner_ty):
match inner_ty:
case llvmlite.LLVMType.Ptr(_):
loaded: llvmlite.Value | t.CPtr = llvmlite.build_load(
builder, inner_ty, obj_val)
if loaded is not None:
obj_ptr = loaded
case _:
pass
obj_is_ptr: int = HandlesExpr.is_ptr_type(obj_ptr.Ty)
i8_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty)
i8_ptr_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ptr_ty)
# 对象 vtable 指针惰性读取(首个 CVTable 类比较时执行一次)
vtable_ptr: llvmlite.Value | t.CPtr = None
vtable_ready: int = 0
res: llvmlite.Value | t.CPtr = None
cls_names: t.CSizeT | t.CPtr = self._extract_isinstance_class_names(pool, cls_node)
dbg_b: t.CChar | t.CPtr = VLogger.fmt_buf()
if dbg_b is not None:
viperlib.snprintf(dbg_b, 1024, "[ISINSTANCE] B cls_names=%d cls0=%lld",
0 if cls_names is None else 1,
cls_names[0] if cls_names is not None else -1)
VLogger.info(dbg_b, "ISINSTANCE")
if cls_names is None:
return llvmlite.const_int(pool, 1, 0)
dbg0: t.CChar | t.CPtr = VLogger.fmt_buf()
if dbg0 is not None:
viperlib.snprintf(dbg0, 1024, "[ISINSTANCE] C kind=%d ptr=%d cls0=%lld",
cls_node.kind(), obj_is_ptr, cls_names[0])
VLogger.info(dbg0, "ISINSTANCE")
ci: int = 0
while cls_names[ci] != 0:
cn: str = (str | t.CPtr)(t.CVoid(cls_names[ci], t.CPtr))
if cn is not None:
dbg1: t.CChar | t.CPtr = VLogger.fmt_buf()
if dbg1 is not None:
viperlib.snprintf(dbg1, 1024, "[ISINSTANCE] cn_ptr=%lld nv=%d vt=%d",
cls_names[ci],
HandlesStruct.is_novtable_by_name(cn),
HandlesStruct.has_vtable_by_name(cn))
VLogger.info(dbg1, "ISINSTANCE")
if HandlesStruct.is_novtable_by_name(cn) != 0 \
or HandlesStruct.has_vtable_by_name(cn) == 0:
# NoVTable / 未知类 → 编译时静态判断(obj 静态类型 MRO)
st: int = self._isinstance_compile_time(pool, obj_ptr, cn)
c1: llvmlite.Value | t.CPtr = llvmlite.const_int(pool, 1, 1 if st > 0 else 0)
if res is None:
res = c1
else:
res = llvmlite.build_or(builder, res, c1)
elif obj_is_ptr != 0:
# CVTable → 运行时 vtable 比较(读对象头部 vtable 指针)
if vtable_ready == 0:
vtable_slot: llvmlite.Value | t.CPtr = llvmlite.build_bitcast(
builder, obj_ptr, i8_ptr_ptr_ty)
if vtable_slot is not None:
vtable_ptr = llvmlite.build_load(builder, i8_ptr_ty, vtable_slot)
vtable_ready = 1
if vtable_ptr is not None:
subs: t.CSizeT | t.CPtr = self._collect_subclass_vtable_classes(pool, cn)
dbg2: t.CChar | t.CPtr = VLogger.fmt_buf()
if dbg2 is not None:
viperlib.snprintf(dbg2, 1024, "[ISINSTANCE] cn_ptr=%lld subs0=%lld subs1=%lld",
cls_names[ci],
subs[0] if subs is not None else -1,
subs[1] if subs is not None else -1)
VLogger.info(dbg2, "ISINSTANCE")
if subs is not None:
k: int = 0
while subs[k] != 0:
scn: str = (str | t.CPtr)(t.CVoid(subs[k], t.CPtr))
if scn is not None:
sub_i8: llvmlite.Value | t.CPtr = self._make_vtable_ref_i8(
pool, builder, mod, scn, trans)
if sub_i8 is not None:
cmp: llvmlite.Value | t.CPtr = llvmlite.build_icmp(
builder, llvmlite.ICMP_EQ, vtable_ptr, sub_i8)
if cmp is not None:
if res is None:
res = cmp
else:
res = llvmlite.build_or(builder, res, cmp)
else:
dbg_ci: t.CChar | t.CPtr = VLogger.fmt_buf()
if dbg_ci is not None:
viperlib.snprintf(dbg_ci, 1024, "[ISINSTANCE] CMP-NONE subs_k=%lld", subs[k])
VLogger.info(dbg_ci, "ISINSTANCE")
else:
dbg_ri: t.CChar | t.CPtr = VLogger.fmt_buf()
if dbg_ri is not None:
viperlib.snprintf(dbg_ri, 1024, "[ISINSTANCE] REF-NONE subs_k=%lld", subs[k])
VLogger.info(dbg_ri, "ISINSTANCE")
k += 1
else:
dbg_sn: t.CChar | t.CPtr = VLogger.fmt_buf()
if dbg_sn is not None:
viperlib.snprintf(dbg_sn, 1024, "[ISINSTANCE] SUBS-NONE cn=%lld", cls_names[ci])
VLogger.info(dbg_sn, "ISINSTANCE")
ci += 1
if res is None:
dbg_rn: t.CChar | t.CPtr = VLogger.fmt_buf()
if dbg_rn is not None:
viperlib.snprintf(dbg_rn, 1024, "[ISINSTANCE] RES-NONE")
VLogger.info(dbg_rn, "ISINSTANCE")
return llvmlite.const_int(pool, 1, 0)
return res
def _extract_isinstance_class_names(self, pool: memhub.MemBuddy | t.CPtr,
node: ast.AST | t.CPtr) -> t.CSizeT | t.CPtr:
"""从 isinstance 第二参数提取类名数组(Name→id / Attribute→attr / Tuple→遍历)"""
arr: t.CSizeT | t.CPtr = pool.alloc(8 * 16)
if arr is None:
return None
# 全数组清零:pool 分配内存非零时,未初始化槽位会被 while cls_names[ci]!=0
# 误当类名指针解引用 → 段错误
string.memset(arr, 0, 8 * 16)
arr[0] = 0
if node is None:
return arr
count: int = 0
k: int = node.kind()
if k == ast.ASTKind.Name:
nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(node)
if nm.id is not None:
arr[0] = t.CSizeT(nm.id)
elif k == ast.ASTKind.Attribute:
at: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(node)
if at.attr is not None:
arr[0] = t.CSizeT(at.attr)
elif k == ast.ASTKind.Tuple:
tp: ast.Tuple | t.CPtr = (ast.Tuple | t.CPtr)(node)
elts: list[ast.AST | t.CPtr] | t.CPtr = tp.elts
if elts is not None:
n: t.CSizeT = elts.__len__()
for i in range(n):
if count >= 15:
break
elt: ast.AST | t.CPtr = elts.get(i)
if elt is None:
continue
ek: int = elt.kind()
if ek == ast.ASTKind.Name:
enm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(elt)
if enm.id is not None:
arr[count] = t.CSizeT(enm.id)
count += 1
elif ek == ast.ASTKind.Attribute:
eat: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(elt)
if eat.attr is not None:
arr[count] = t.CSizeT(eat.attr)
count += 1
arr[count] = 0
return arr
def _collect_subclass_vtable_classes(self, pool: memhub.MemBuddy | t.CPtr,
class_name: str) -> t.CSizeT | t.CPtr:
"""反向 BFS 收集目标类及全部子类(含自身,仅 HasVTable==1),返回 0 结尾数组"""
if class_name is None:
return None
names: t.CSizeT | t.CPtr = pool.alloc(8 * (ISINSTANCE_CLASS_MAX + 1))
queue: t.CSizeT | t.CPtr = pool.alloc(8 * ISINSTANCE_CLASS_MAX)
visited: t.CSizeT | t.CPtr = pool.alloc(8 * ISINSTANCE_CLASS_MAX)
if names is None or queue is None or visited is None:
return None
string.memset(names, 0, 8 * (ISINSTANCE_CLASS_MAX + 1))
string.memset(queue, 0, 8 * ISINSTANCE_CLASS_MAX)
string.memset(visited, 0, 8 * ISINSTANCE_CLASS_MAX)
qcount: int = 0
outcount: int = 0
vcount: int = 0
queue[0] = t.CSizeT(class_name)
qcount = 1
while qcount > 0:
qcount -= 1
cur_addr: t.CSizeT = queue[qcount]
cur: str = (str | t.CPtr)(t.CVoid(cur_addr, t.CPtr))
if cur is None:
continue
dup: int = 0
for vi in range(vcount):
va: t.CSizeT = visited[vi]
if va != 0:
vn: str = (str | t.CPtr)(t.CVoid(va, t.CPtr))
if vn is not None and string.strcmp(vn, cur) == 0:
dup = 1
break
if dup != 0:
continue
if vcount < ISINSTANCE_CLASS_MAX:
visited[vcount] = t.CSizeT(cur)
vcount += 1
centry: HandlesStruct.StructEntry | t.CPtr = HandlesStruct.find_struct(cur)
if centry is not None and centry.HasVTable != 0:
if outcount < ISINSTANCE_CLASS_MAX:
names[outcount] = t.CSizeT(cur)
outcount += 1
# 子类入队:遍历全部已注册 struct,ParentNames 含 cur 者
scnt: int = HandlesStruct.get_struct_count()
for i in range(scnt):
e2: HandlesStruct.StructEntry | t.CPtr = HandlesStruct._get_struct_entry(i)
if e2 is None or e2.Name is None:
continue
if e2.ParentCount > 0 and e2.ParentNames is not None:
parr: t.CSizeT | t.CPtr = (t.CSizeT | t.CPtr)(t.CVoid(e2.ParentNames, t.CPtr))
for j in range(e2.ParentCount):
pa: t.CSizeT = parr[j]
if pa != 0:
pn: str = (str | t.CPtr)(t.CVoid(pa, t.CPtr))
if pn is not None and string.strcmp(pn, cur) == 0:
if qcount < ISINSTANCE_CLASS_MAX:
queue[qcount] = t.CSizeT(e2.Name)
qcount += 1
break
names[outcount] = 0
return names
def _make_vtable_ref_i8(self, pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
class_name: str,
trans: HT.Translator | t.CPtr = None) -> llvmlite.Value | t.CPtr:
"""构造类 vtable 地址引用(i8*):本模块已定义直接引用,跨模块声明 external"""
if class_name is None:
return None
i8_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty)
buf: t.CChar | t.CPtr = pool.alloc(256)
if buf is None:
return None
viperlib.snprintf(buf, 256, "%s.vtable", class_name)
vn: str = (str | t.CPtr)(buf)
sha1: str = HandlesStruct.get_struct_sha1(class_name)
if sha1 is None:
if trans is None:
return None
sha1 = trans.ModuleSha1
if sha1 is None:
return None
mangled: str = HandlesFunctions._mangle_name_with_sha1(sha1, vn)
ref_name: t.CChar | t.CPtr = pool.alloc(256)
if ref_name is None:
return None
# vtable 名含 sha1 点号前缀,一律 @"..." 引号形式
viperlib.snprintf(ref_name, 256, "@\"%s\"", mangled)
gv: llvmlite.GlobalVariable | t.CPtr = HandlesExpr.find_global_in_module(mod, mangled)
ref_ty: llvmlite.LLVMType | t.CPtr = None
if gv is not None:
# 本模块已定义 vtable(constant {i8* x N})→ 引用类型 Ptr(struct)
ref_ty = llvmlite.Ptr(pool, gv.Ty)
else:
# 跨模块:声明 @mangled = external global i8*,引用类型 i8**
gv2: llvmlite.GlobalVariable | t.CPtr = llvmlite.new_global_variable(
pool, mangled, i8_ptr_ty)
if gv2 is None:
return None
llvmlite.module_add_global(mod, gv2)
ref_ty = llvmlite.Ptr(pool, i8_ptr_ty)
ref: llvmlite.Value | t.CPtr = llvmlite.SSAValue(pool, ref_ty, ref_name)
if ref is None:
return None
return llvmlite.build_bitcast(builder, ref, i8_ptr_ty)
def _isinstance_compile_time(self, pool: memhub.MemBuddy | t.CPtr,
obj_ptr: llvmlite.Value | t.CPtr,
class_name: str) -> int:
"""编译时静态判断:obj 静态类型 == class_name 或 class_name 在其 MRO 中"""
if obj_ptr is None or obj_ptr.Ty is None or class_name is None:
return 0
match obj_ptr.Ty:
case llvmlite.LLVMType.Ptr(obj_struct_ty):
sname: str = HandlesStruct.get_class_name_by_type(pool, obj_struct_ty)
if sname is not None:
if string.strcmp(sname, class_name) == 0:
return 1
mro: t.CSizeT | t.CPtr = HandlesStruct.get_mro_chain(pool, sname)
if mro is not None:
k: int = 0
while mro[k] != 0:
mn: str = (str | t.CPtr)(t.CVoid(mro[k], t.CPtr))
if mn is not None and string.strcmp(mn, class_name) == 0:
return 1
k += 1
case _:
pass
return 0
# ============================================================
# P4 簇 5+6 迁移方法(函数体来自 HandlesExprCall.vp:
# 闭包调用/函数指针/printf/print/stdarg 糖/变长参数提升/函数表/全局注册表)
+14 -1
View File
@@ -2424,6 +2424,15 @@ class ClassGenMixin:
if tgt.id is None:
continue
# 类体 CDefine 常量(任意位置,2026-08-10):注册到全局 CDefine 表。
# 方法内裸名(如 ISINSTANCE_CLASS_MAX)经 translate_name_value 查
# CDefine 表解析为常量值;类体开头的常量(MRO_MAX 等)由早期预注册
# 路径注册,但方法之后的 AnnAssign 不经过那些路径 → 此处统一兜底注册。
# 不 continue:CDefine 字段仍按普通字段收集(i32,与 MRO_MAX 一致)。
if HandlesAnnAssign.is_cdefine_annotation(aa.annotation) != 0:
if aa.value is not None:
HandlesAnnAssign._register_cdefine_from_value(pool, tgt.id, aa.value)
# 跳过编译期元数据字段(__provides__/__requires__/__require_must__)
# 这些字段只用于 with 上下文的静态可达性检查,不生成运行时代码
# 避免类体中 list 字面量翻译失败(类体无 pool 变量上下文)
@@ -3117,7 +3126,11 @@ class ClassGenMixin:
return 0
gv.Initializer = init_buf
gv.IsConstant = 1
gv.Linkage = "internal"
# 默认 linkage(external,2026-08-10):isinstance 内建函数需要跨模块
# 引用其他模块类的 vtable 符号({模块sha1}.Class.vtable),internal 不对外
# 导出会导致链接失败。vtable 名含模块 sha1 前缀全局唯一,外部可见不冲突。
# llvmlite 打印规则:Initializer 非空 + external 时不打印 external 关键字,
# 输出 @name = constant {...}(合法外部强定义)。
# 添加到模块
llvmlite.module_add_global(mod, gv)
+3 -2
View File
@@ -647,13 +647,14 @@ def _do_virtual_call(pool: memhub.MemBuddy | t.CPtr,
args_head: llvmlite.Value | t.CPtr,
args_count: int,
found_func: llvmlite.Function | t.CPtr,
ret_ty: llvmlite.LLVMType | t.CPtr) -> llvmlite.Value | t.CPtr:
ret_ty: llvmlite.LLVMType | t.CPtr,
vtable_base: llvmlite.Value | t.CPtr = None) -> llvmlite.Value | t.CPtr:
"""通过虚表间接调用虚方法,返回 call 结果(void 方法返回 None)(委托到 gen._do_virtual_call)"""
import lib.core.Gen.Generator as Generator
g: Generator.Generator | t.CPtr = (Generator.Generator | t.CPtr)(t.CVoid(_g_gen, t.CPtr))
if g is not None:
return g._do_virtual_call(pool, builder, mod, class_name, method_name,
self_ptr, args_head, args_count, found_func, ret_ty)
self_ptr, args_head, args_count, found_func, ret_ty, vtable_base)
return None
# ============================================================