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.HandlesStruct as HandlesStruct
import lib.core.Handles.HandlesImports as HandlesImports import lib.core.Handles.HandlesImports as HandlesImports
import lib.core.Handles.HandlesClassDef as HandlesClassDef import lib.core.Handles.HandlesClassDef as HandlesClassDef
import lib.core.Handles.HandlesFunctions as HandlesFunctions
import lib.core.StubMerger as StubMerger import lib.core.StubMerger as StubMerger
import lib.core.Handles.HandlesExprCall as HandlesExprCall import lib.core.Handles.HandlesExprCall as HandlesExprCall
@@ -1977,7 +1978,25 @@ class CallGenMixin:
continue continue
fe: HandlesStruct.FieldEntry | t.CPtr = None fe: HandlesStruct.FieldEntry | t.CPtr = None
if ctor_entry is not 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: else:
fe = HandlesStruct.get_field_by_index(class_name, ai) fe = HandlesStruct.get_field_by_index(class_name, ai)
if fe is None: if fe is None:
@@ -2187,12 +2206,21 @@ class CallGenMixin:
args_head: llvmlite.Value | t.CPtr, args_head: llvmlite.Value | t.CPtr,
args_count: int, args_count: int,
found_func: llvmlite.Function | t.CPtr, found_func: llvmlite.Function | t.CPtr,
ret_ty: llvmlite.LLVMType | t.CPtr) -> llvmlite.Value | t.CPtr: ret_ty: llvmlite.LLVMType | t.CPtr,
"""通过虚表间接调用虚方法,返回 call 结果(void 方法返回 None)""" 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: if pool is None or builder is None or class_name is None or method_name is None:
return None return None
if self_ptr is None or args_head is None: if self_ptr is None or args_head is None:
return None return None
if vtable_base is None:
vtable_base = self_ptr
# 1. 获取结构体类型(用 find_struct_by_type 规避跨模块同名 find_struct 找错) # 1. 获取结构体类型(用 find_struct_by_type 规避跨模块同名 find_struct 找错)
vc_entry: HandlesStruct.StructEntry | t.CPtr = None vc_entry: HandlesStruct.StructEntry | t.CPtr = None
@@ -2234,8 +2262,10 @@ class CallGenMixin:
# 4. bitcast 到 i8** 访问 __vtable__ 字段(索引 0) # 4. bitcast 到 i8** 访问 __vtable__ 字段(索引 0)
# vtable 指针总是在对象第一个字段,无需 GEP,直接 bitcast 到 i8** 即可。 # vtable 指针总是在对象第一个字段,无需 GEP,直接 bitcast 到 i8** 即可。
# 这样即使 struct_ty 是 opaque(跨模块未加载完整定义)也能正常工作。 # 这样即使 struct_ty 是 opaque(跨模块未加载完整定义)也能正常工作。
# 注意用 vtable_base(对象头)而非 self_ptr:多继承 this 调整后
# self_ptr 已前移,其头部不是 vtable 而是父类字段值。
vtable_slot: llvmlite.Value | t.CPtr = llvmlite.build_bitcast( 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: if vtable_slot is None:
return None return None
@@ -2588,7 +2618,6 @@ class CallGenMixin:
if cmop_has_vt == 1 and cmop_method_idx >= 0: if cmop_has_vt == 1 and cmop_method_idx >= 0:
return self._do_virtual_call(pool, builder, mod, class_name, method_name, return self._do_virtual_call(pool, builder, mod, class_name, method_name,
self_ptr, head, total_count, found_func, call_ret_ty) self_ptr, head, total_count, found_func, call_ret_ty)
# 跨模块调用(found_func 为 None):创建 declare 声明,避免 llc 报 undefined value # 跨模块调用(found_func 为 None):创建 declare 声明,避免 llc 报 undefined value
if found_func is None: if found_func is None:
self._ensure_method_declare(pool, mod, call_name, call_ret_ty, head, total_count) 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: if kw_calls is not None:
kwc = kw_calls.__len__() kwc = kw_calls.__len__()
# 多继承 this 调整:继承方法定义在"无 vtable 的父类"且该父类字段区在子类中 # 多继承 this 调整(字节偏移法,2026-08-10,与 TPC HandlesExprCall._adjust_this_for_inherited_method 一致):
# 偏移 > 0 时(如 C(A,B).get_b,B 的字段区在 C 中从 b 开始),父类方法按自身 # 继承方法体按"定义类自身布局"读 self 字段(如 B.get_b 内 GEP(self_B, [0,1])
# 布局读 self.field@0 会读到前面父类的字段。调整:GEP 到父类第一个用户字段 # 的字节偏移是 B 布局中 b 的 offset 12)。父类字段区在子类中的字节偏移可能
# 在子类中的位置,bitcast 成父类指针作为 self。 # 不同(如 AB={vtable,a@8,b@12} 中 b 偏移 16),self 需按字节差前移,使父类
# 有 vtable 的父类布局是子类布局前缀(vptr 共享),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 self_arg: llvmlite.Value | t.CPtr = obj_ptr
if mc_anc is not None and string.strcmp(mc_anc, class_name) != 0: 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) 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 anc_first_name_th: str = None
afi_th: int = 0 afi_th: int = 0
while afi_th < anc_entry_th.FieldCount: while afi_th < anc_entry_th.FieldCount:
@@ -3495,13 +3530,27 @@ class CallGenMixin:
sub_entry_th: HandlesStruct.StructEntry | t.CPtr = HandlesStruct.find_struct( sub_entry_th: HandlesStruct.StructEntry | t.CPtr = HandlesStruct.find_struct(
class_name) class_name)
if sub_entry_th is not None: 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_field_th: HandlesStruct.FieldEntry | t.CPtr = HandlesStruct.lookup_field(
sub_entry_th.Ty, anc_first_name_th) sub_entry_th.Ty, anc_first_name_th)
if sub_field_th is not None and sub_field_th.Index > 0: if anc_field_th is not None and sub_field_th is not None:
anc_ptr_ty_th: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, anc_entry_th.Ty) anc_off_th: int = self._field_byte_offset(
adj_th: llvmlite.Value | t.CPtr = llvmlite.build_gep_struct( pool, anc_entry_th, anc_field_th.Index)
builder, sub_entry_th.Ty, sub_field_th.Ty, obj_ptr, sub_field_th.Index) sub_off_th: int = self._field_byte_offset(
self_arg = llvmlite.build_bitcast(builder, adj_th, anc_ptr_ty_th) 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 → ... # 构建参数链表: self → arg1 → arg2 → ...
llvmlite.value_set_next(self_arg, None) llvmlite.value_set_next(self_arg, None)
head: llvmlite.Value | t.CPtr = self_arg head: llvmlite.Value | t.CPtr = self_arg
@@ -3666,8 +3715,12 @@ class CallGenMixin:
mc_method_idx = mc_mi mc_method_idx = mc_mi
break break
if mc_has_vt == 1 and mc_method_idx >= 0: 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, 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 # 跨模块调用:创建 declare 声明,避免 llc 报 undefined value
self._ensure_method_declare(pool, mod, call_name, call_ret_ty, head, total_count) 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) 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) 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 — 检查关键字参数名是否匹配函数参数名 # _kwarg_name_matches — 检查关键字参数名是否匹配函数参数名
# #
@@ -4759,6 +4854,17 @@ class CallGenMixin:
i64_ty_ln2: llvmlite.LLVMType | t.CPtr = llvmlite.Int64(pool) i64_ty_ln2: llvmlite.LLVMType | t.CPtr = llvmlite.Int64(pool)
return llvmlite.build_call(builder, "strlen", len_coerced, 1, i64_ty_ln2, 0) 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) # 检测类名静态/类方法调用: ClassName.method(args)
# 当 cl.func 是 Attribute、value 是已注册结构体类名(非普通变量)时, # 当 cl.func 是 Attribute、value 是已注册结构体类名(非普通变量)时,
# 且方法带 @staticmethod/@classmethod 元数据,走静态调用路径 # 且方法带 @staticmethod/@classmethod 元数据,走静态调用路径
@@ -5184,6 +5290,16 @@ class CallGenMixin:
if ctor_is_oop != 0: if ctor_is_oop != 0:
return self._translate_struct_ctor( return self._translate_struct_ctor(
pool, builder, mod, struct_ty, func_name, cargs, 0, trans) 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 直接访问) # 普通结构体:检查是否有默认值(用 entry 直接访问)
gen_has_defaults: int = 0 gen_has_defaults: int = 0
if gen_ctor_entry is not None: 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) 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) 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: # P4 簇 5+6 迁移方法(函数体来自 HandlesExprCall.vp:
# 闭包调用/函数指针/printf/print/stdarg 糖/变长参数提升/函数表/全局注册表) # 闭包调用/函数指针/printf/print/stdarg 糖/变长参数提升/函数表/全局注册表)
+14 -1
View File
@@ -2424,6 +2424,15 @@ class ClassGenMixin:
if tgt.id is None: if tgt.id is None:
continue 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__) # 跳过编译期元数据字段(__provides__/__requires__/__require_must__)
# 这些字段只用于 with 上下文的静态可达性检查,不生成运行时代码 # 这些字段只用于 with 上下文的静态可达性检查,不生成运行时代码
# 避免类体中 list 字面量翻译失败(类体无 pool 变量上下文) # 避免类体中 list 字面量翻译失败(类体无 pool 变量上下文)
@@ -3117,7 +3126,11 @@ class ClassGenMixin:
return 0 return 0
gv.Initializer = init_buf gv.Initializer = init_buf
gv.IsConstant = 1 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) 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_head: llvmlite.Value | t.CPtr,
args_count: int, args_count: int,
found_func: llvmlite.Function | t.CPtr, 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)""" """通过虚表间接调用虚方法,返回 call 结果(void 方法返回 None)(委托到 gen._do_virtual_call)"""
import lib.core.Gen.Generator as Generator import lib.core.Gen.Generator as Generator
g: Generator.Generator | t.CPtr = (Generator.Generator | t.CPtr)(t.CVoid(_g_gen, t.CPtr)) g: Generator.Generator | t.CPtr = (Generator.Generator | t.CPtr)(t.CVoid(_g_gen, t.CPtr))
if g is not None: if g is not None:
return g._do_virtual_call(pool, builder, mod, class_name, method_name, 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 return None
# ============================================================ # ============================================================