diff --git a/App/lib/core/Gen/CallGenMixin.vp b/App/lib/core/Gen/CallGenMixin.vp index 56fe776..3d20933 100644 --- a/App/lib/core/Gen/CallGenMixin.vp +++ b/App/lib/core/Gen/CallGenMixin.vp @@ -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 糖/变长参数提升/函数表/全局注册表) diff --git a/App/lib/core/Gen/ClassGenMixin.vp b/App/lib/core/Gen/ClassGenMixin.vp index 5955758..f0bab3f 100644 --- a/App/lib/core/Gen/ClassGenMixin.vp +++ b/App/lib/core/Gen/ClassGenMixin.vp @@ -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) diff --git a/App/lib/core/Handles/HandlesExprCall.vp b/App/lib/core/Handles/HandlesExprCall.vp index 8881b08..870b41e 100644 --- a/App/lib/core/Handles/HandlesExprCall.vp +++ b/App/lib/core/Handles/HandlesExprCall.vp @@ -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 # ============================================================