diff --git a/Test/IsinstanceTest/App/geom_lib.vp b/Test/IsinstanceTest/App/geom_lib.vp new file mode 100644 index 0000000..494642a --- /dev/null +++ b/Test/IsinstanceTest/App/geom_lib.vp @@ -0,0 +1,43 @@ +import t +from t import CInt + + +# ============================================================ +# 跨模块 isinstance 测试库 +# +# 供 main.vp import 使用:验证 isinstance 对"定义在其他模块 +# 的类"也能正确比较(对象 vtable 指针跨 obj 链接解析)。 +# ============================================================ + + +@t.CVTable +class Shape: + def area(self) -> CInt: + return 0 + + +@t.CVTable +class Circle(Shape): + r: CInt + + def __init__(self, r: CInt): + self.r = r + + def area(self) -> CInt: + return self.r + + +@t.CVTable +class Square(Shape): + s: CInt + + def __init__(self, s: CInt): + self.s = s + + def area(self) -> CInt: + return self.s + + +@t.NoVTable +class PlainShape: + tag: CInt diff --git a/Test/IsinstanceTest/App/main.vp b/Test/IsinstanceTest/App/main.vp index 95a94f2..bc166d4 100644 --- a/Test/IsinstanceTest/App/main.vp +++ b/Test/IsinstanceTest/App/main.vp @@ -1,6 +1,7 @@ from stdint import * import t, c import testcheck +import geom_lib # ============================================================ @@ -123,5 +124,29 @@ def main() -> CInt | CExport: "isinstance NoVTable FAILED") testcheck.check(p.v == 42, "NoVTable 字段正常 OK", "NoVTable 字段 FAILED") + # --- 6. 跨模块 isinstance(vtable 跨 obj 链接解析) --- + cc: geom_lib.Circle | CPtr = geom_lib.Circle(5) + testcheck.check(isinstance(cc, geom_lib.Circle) == 1, + "跨模块 isinstance 自身 OK", "跨模块 isinstance 自身 FAILED") + testcheck.check(isinstance(cc, geom_lib.Shape) == 1, + "跨模块 isinstance 子类→基类 OK", "跨模块 isinstance 子类→基类 FAILED") + testcheck.check(isinstance(cc, geom_lib.Square) == 0, + "跨模块 isinstance 不匹配 OK", "跨模块 isinstance 不匹配 FAILED") + sq: geom_lib.Square | CPtr = geom_lib.Square(3) + testcheck.check(isinstance(sq, geom_lib.Shape) == 1, + "跨模块 Square→Shape OK", "跨模块 Square→Shape FAILED") + testcheck.check(isinstance(sq, geom_lib.Circle) == 0, + "跨模块 Square vs Circle OK", "跨模块 Square vs Circle FAILED") + shp: geom_lib.Shape | CPtr = (geom_lib.Shape | CPtr)(cc) + testcheck.check(isinstance(shp, geom_lib.Circle) == 1, + "跨模块运行时多态 OK", "跨模块运行时多态 FAILED") + testcheck.check(shp.area() == 5, "跨模块虚方法分派 OK", "跨模块虚方法分派 FAILED") + testcheck.check(cc.area() == 5, "跨模块 Circle.area OK", "跨模块 Circle.area FAILED") + + # --- 7. NoVTable 跨模块静态判断 --- + ps: geom_lib.PlainShape | CPtr = geom_lib.PlainShape() + testcheck.check(isinstance(ps, geom_lib.PlainShape) == 1, + "跨模块 NoVTable 静态 OK", "跨模块 NoVTable 静态 FAILED") + testcheck.ok("isinstance 全部测试完成") return testcheck.end() diff --git a/lib/core/Handles/HandlesExprBuiltin.py b/lib/core/Handles/HandlesExprBuiltin.py index c4e67a4..aaaf611 100644 --- a/lib/core/Handles/HandlesExprBuiltin.py +++ b/lib/core/Handles/HandlesExprBuiltin.py @@ -289,9 +289,17 @@ class ExprBuiltinHandle(BaseHandle): return addrs def _ResolveVtableAddr(self, Gen, ClassName: str): - """解析类 ClassName 的 vtable 全局地址(本地 Vtables / 跨模块外部引用), - 无 vtable(NoVTable / 未注册)返回 None。 + """解析类 ClassName 的 vtable 全局地址(本地 Vtables / 跨模块外部引用)。 + + 仅对 CVTable 类返回地址;NoVTable 类(class_vtable 与 + _cross_module_vtable_classes 均不含)没有 vtable 全局变量, + 返回 None 让调用方回退编译时静态判断(避免生成对不存在的 + sha1.Class_Vtable 符号的引用导致链接失败)。 """ + has_vtable: bool = (ClassName in Gen.class_vtable + or ClassName in Gen._cross_module_vtable_classes) + if not has_vtable: + return None vt = Gen.Vtables.get(ClassName) if hasattr(Gen, 'Vtables') else None if vt is not None: return vt @@ -303,7 +311,9 @@ class ExprBuiltinHandle(BaseHandle): if vname in Gen.module.globals: return Gen.module.globals[vname] try: - ext = ir.GlobalVariable(Gen.module, ir.ArrayType(ir.PointerType(ir.IntType(8)), 1), name=vname) + # 外部引用声明为 i8*(而非 [N x i8*]),避免与定义模块 + # 不同长度的 array 类型在链接时 type mismatch + ext = ir.GlobalVariable(Gen.module, ir.PointerType(ir.IntType(8)), name=vname) ext.linkage = 'external' return ext except Exception: @@ -321,7 +331,10 @@ class ExprBuiltinHandle(BaseHandle): VtPtr: ir.Value = Gen._load(ObjAsVt, name="isinstance_vt") Res: ir.Value | None = None for addr in VtableAddrs: - AddrI8: ir.Value = Gen.builder.bitcast(addr, ir.PointerType(ir.IntType(8)), name="vt_addr") + if addr.type == ir.PointerType(ir.IntType(8)): + AddrI8: ir.Value = addr + else: + AddrI8 = Gen.builder.bitcast(addr, ir.PointerType(ir.IntType(8)), name="vt_addr") Cmp: ir.Value = Gen.builder.icmp_unsigned('==', VtPtr, AddrI8, name="isinstance_cmp") if Res is None: Res = Cmp diff --git a/lib/core/LLVMCG/StructGen.py b/lib/core/LLVMCG/StructGen.py index 4a3c7eb..1582a9a 100644 --- a/lib/core/LLVMCG/StructGen.py +++ b/lib/core/LLVMCG/StructGen.py @@ -560,7 +560,10 @@ class StructGenMixin: VtableType: ir.ArrayType = ir.ArrayType(ir.PointerType(ir.IntType(8)), len(methods)) Vtable: ir.GlobalVariable = ir.GlobalVariable(self.module, VtableType, name=self._mangle_name(f"{ClassName}_Vtable")) Vtable.initializer = ir.Constant(VtableType, [ir.Constant(ir.PointerType(ir.IntType(8)), None)] * len(methods)) - Vtable.linkage = 'internal' + # 保持默认 linkage(空字符串,带 initializer 自动打印为外部强定义 + # global,llvmlite descr 规则:initializer 非空时 linkage 归一为空)。 + # 2026-08-10:跨模块 isinstance 需引用 sha1.Class_Vtable 符号, + # vtable 名含模块 sha1 前缀全局唯一,外部可见不会冲突。 self.Vtables[ClassName] = Vtable def _create_vtable_for_class(self, ClassName: str) -> None: @@ -589,7 +592,7 @@ class StructGenMixin: VtableType: ir.ArrayType = ir.ArrayType(ir.PointerType(ir.IntType(8)), len(methods)) Vtable: ir.GlobalVariable = ir.GlobalVariable(self.module, VtableType, name=_vtable_name) Vtable.initializer = ir.Constant(VtableType, [ir.Constant(ir.PointerType(ir.IntType(8)), None)] * len(methods)) - Vtable.linkage = 'internal' + # 默认 linkage(外部强定义),跨模块 isinstance 引用(与 _create_Vtable_globals 一致) self.Vtables[ClassName] = Vtable def _fix_global_var_init(self) -> None: