feat(TPC): 实现 isinstance 内建 + 多继承 this_adjust 字节偏移修复

isinstance 沿 class_parents 反向 BFS 收集目标类全部子类 vtable,运行时比较对象头部 vtable 指针,修复"目标是实际类祖先"场景误判;this_adjust 改用字节偏移(字段在子类与父类自身布局的字节差)修正第二父类方法体内二次 GEP 越界读。新增 Test/IsinstanceTest 覆盖 5 大场景 14 断言。
This commit is contained in:
TermiNexus committed 2026-08-10 19:11:18 +08:00
1 parent d2d319eb21
commit 8783340385
4 files changed
+363 -47

No files matched your search

+127
View File
@@ -0,0 +1,127 @@
from stdint import *
import t, c
import testcheck
# ============================================================
# isinstance 内建函数测试(运行时 vtable 继承链比较)
#
# 场景覆盖:
# 1. CVTable 自身/子类→基类
# 2. 运行时多态(基类指针指向子类对象)
# 3. 元组 (A, B)
# 4. 多继承(AB 同时是 A 和 B 的实例)
# 5. NoVTable 类(编译时静态判断)
# ============================================================
# ========== CVTable 继承 ==========
@t.CVTable
class Animal:
Name: str
def __init__(self, name: str):
self.Name = name
def speak(self) -> str:
return "..."
@t.CVTable
class Dog(Animal):
def __init__(self, name: str):
self.Name = name
def speak(self) -> str:
return "Woof"
@t.CVTable
class Cat(Animal):
def speak(self) -> str:
return "Meow"
# ========== 多继承 ==========
@t.CVTable
class A:
a: CInt
def get_a(self) -> CInt:
return self.a
@t.CVTable
class B:
b: CInt
def get_b(self) -> CInt:
return self.b
@t.CVTable
class AB(A, B):
def __init__(self, a: CInt, b: CInt):
self.a = a
self.b = b
# ========== NoVTable(编译时判断) ==========
@t.NoVTable
class Plain:
v: CInt
def main() -> CInt | CExport:
testcheck.begin("IsinstanceTest: isinstance 内建函数测试")
# --- 1. 自身类型 + 子类→基类 ---
d: Dog | CPtr = Dog("dog")
testcheck.check(isinstance(d, Dog) == 1, "isinstance(d, Dog) 自身 OK",
"isinstance(d, Dog) FAILED")
testcheck.check(isinstance(d, Animal) == 1, "isinstance(d, Animal) 子类→基类 OK",
"isinstance(d, Animal) FAILED")
testcheck.check(isinstance(d, Cat) == 0, "isinstance(d, Cat) 不匹配 OK",
"isinstance(d, Cat) FAILED")
# --- 2. 运行时多态:基类指针指向子类对象 ---
a: Animal | CPtr = (Animal | CPtr)(d)
testcheck.check(isinstance(a, Dog) == 1,
"isinstance(Animal*, Dog 对象) 运行时多态 OK",
"isinstance(Animal*, Dog) FAILED")
testcheck.check(isinstance(a, Animal) == 1,
"isinstance(Animal*, Animal) 基类自身 OK",
"isinstance(Animal*, Animal) FAILED")
testcheck.check(isinstance(a, Cat) == 0,
"isinstance(Animal*, Cat) 运行时 False OK",
"isinstance(Animal*, Cat) FAILED")
# --- 3. 元组 ---
testcheck.check(isinstance(d, (Cat, Dog)) == 1, "isinstance 元组命中 OK",
"isinstance 元组 FAILED")
testcheck.check(isinstance(d, (Cat, A)) == 0, "isinstance 元组未命中 OK",
"isinstance 元组未命中 FAILED")
# --- 4. 多继承 ---
ab: AB | CPtr = AB(1, 2)
testcheck.check(isinstance(ab, A) == 1, "isinstance(AB, A) 多继承 OK",
"isinstance(AB, A) FAILED")
testcheck.check(isinstance(ab, B) == 1, "isinstance(AB, B) 多继承 OK",
"isinstance(AB, B) FAILED")
testcheck.check(isinstance(ab, Animal) == 0, "isinstance(AB, Animal) False OK",
"isinstance(AB, Animal) FAILED")
testcheck.check(ab.get_a() == 1 and ab.get_b() == 2, "AB 字段/方法正常 OK",
"AB 字段/方法 FAILED")
# --- 5. NoVTable(编译时静态判断) ---
p: Plain | CPtr = Plain()
p.v = 42
testcheck.check(isinstance(p, Plain) == 1, "isinstance NoVTable 编译时 OK",
"isinstance NoVTable FAILED")
testcheck.check(p.v == 42, "NoVTable 字段正常 OK", "NoVTable 字段 FAILED")
testcheck.ok("isinstance 全部测试完成")
return testcheck.end()
+29
View File
@@ -0,0 +1,29 @@
{
"$schema": "https://raw.githubusercontent.com/TermiNexus/TransPyC/main/schemas/project-schema.json",
"name": "IsinstanceTest",
"version": "1.0.0",
"source_dir": "./App",
"build_dir": "./.tpv_build",
"compiler": {
"cmd": "llc",
"flags": ["-filetype=obj", "-relocation-model=pic"]
},
"linker": {
"cmd": "clang++",
"flags": ["-Wl,--allow-multiple-definition", "-Wl,--unresolved-symbols=ignore-in-object-files", "-lmsvcrt", "-lucrt", "-lpthread", "-lmingwex", "-lkernel32", "-luser32", "-latomic"],
"output": "IsinstanceTest.exe"
},
"includes": [
"../../includes"
],
"target": {
"triple": "x86_64-pc-windows-gnu",
"datalayout": "e-m:w-p270:32:32-p271:32:32-p272:64:64-f80:128-n8:16:32:64-S128"
},
"options": {
"slice_level": 3,
"sha1_slice_level": 1,
"target": "llvm",
"strict_mode": true
}
}
+151 -32
View File
@@ -166,38 +166,11 @@ class ExprBuiltinHandle(BaseHandle):
elif FuncName == 'input':
return self._HandleInputLlvm(Node)
elif FuncName == 'isinstance':
# isinstance(obj, Type) — 静态类型系统下编译时解析
# TransPyC 是静态类型系统,运行时无 RTTI。
# 对于编译时已知类型,若变量类型是目标类型的子类则生成 true,否则 false。
# 若无法确定(如基类指针指向子类),保守生成 false。
# 注意:这主要让编译时辅助方法(如 c.py 中的 Asm._parse_asm_descr)能通过编译,
# 这些方法不在运行时被调用,行为不正确不影响程序。
if len(Node.args) >= 2:
ObjVal: ir.Value | None = self.HandleExprLlvm(Node.args[0])
TypeNode: ast.expr = Node.args[1]
TargetClassName: str = ''
if isinstance(TypeNode, ast.Name):
TargetClassName = TypeNode.id
elif isinstance(TypeNode, ast.Attribute):
TargetClassName = TypeNode.attr
if ObjVal is not None and TargetClassName:
ObjType: ir.Type = ObjVal.type
# 解引用指针获取 pointee 类型
if isinstance(ObjType, ir.PointerType):
ObjType = ObjType.pointee
# 检查 pointee 是否是目标结构体类型
if isinstance(ObjType, ir.IdentifiedStructType):
ObjStructName: str = getattr(ObjType, 'name', '')
ShortName: str = ObjStructName.split('.')[-1] if '.' in ObjStructName else ObjStructName
if ShortName == TargetClassName:
return ir.Constant(ir.IntType(1), 1)
# 检查 Gen.var_struct_class
if isinstance(Node.args[0], ast.Name):
VarClassName: str | None = Gen.var_struct_class.get(Node.args[0].id)
if VarClassName == TargetClassName:
return ir.Constant(ir.IntType(1), 1)
return ir.Constant(ir.IntType(1), 0)
return ir.Constant(ir.IntType(1), 0)
# isinstance(obj, Type) — 运行时 vtable 继承链比较(2026-08-10)
# CVTable 对象头部 vtable 指针唯一标识类;isinstance = 对象 vtable
# ∈ 目标类及其 MRO 祖先的 vtable 集合(运行时真多态判断)。
# NoVTable/基础类型/无法解析时回退编译时静态判断。
return self._HandleIsinstanceLlvm(Node)
elif FuncName == 'ord':
if Node.args:
return self._HandleOrdLlvm(Node.args[0])
@@ -228,6 +201,152 @@ class ExprBuiltinHandle(BaseHandle):
return self._HandleTypeLlvm(Node)
return None
# ============================================================
# isinstance - 运行时 vtable 子类集合比较(2026-08-10)
#
# CVTable 对象头部 vtable 指针唯一标识对象**实际类**(创建对象时写入
# 实际类的 vtable)。isinstance(obj, Cls) 语义 = Cls 是实际类的祖先
# (实际类 == Cls 或继承自 Cls)。运行时只能拿到实际类 vtable 地址,
# 无法向上推导祖先链,因此在编译期沿 class_parents 反向 BFS 枚举
# Cls 的全部已知子类(含自身),比较 obj->vtable ∈ {Cls 子类 vtable}。
# class_parents 由 Phase1 声明阶段对全部可达文件填充,子类集合完整。
# NoVTable / 基础类型 / 无 vtable 可解析 → 回退编译时静态判断。
# ============================================================
def _HandleIsinstanceLlvm(self, Node: ast.Call) -> ir.Value:
"""isinstance(obj, Cls):运行时 vtable 继承链比较(元组 Cls 支持 or 合并)"""
Gen: LlvmGeneratorMixin = self.Trans.LlvmGen
if len(Node.args) < 2:
return ir.Constant(ir.IntType(1), 0)
ObjVal: ir.Value | None = self.HandleExprLlvm(Node.args[0])
if ObjVal is None:
return ir.Constant(ir.IntType(1), 0)
ClsNames: list[str] = self._ExtractIsinstanceClassNames(Node.args[1])
if not ClsNames:
return ir.Constant(ir.IntType(1), 0)
Res: ir.Value | None = None
for CN in ClsNames:
R: ir.Value = self._IsinstanceOne(Gen, ObjVal, CN)
if Res is None:
Res = R
else:
Res = Gen.builder.or_(Res, R, name="isinstance_or")
return Res if Res is not None else ir.Constant(ir.IntType(1), 0)
def _ExtractIsinstanceClassNames(self, TypeNode: ast.expr) -> list[str]:
"""从 isinstance 第二参数提取类名列表(支持单类 / (A, B) 元组 / 泛型特化)"""
if isinstance(TypeNode, ast.Tuple):
names: list[str] = []
for elt in TypeNode.elts:
names.extend(self._ExtractIsinstanceClassNames(elt))
return names
if isinstance(TypeNode, ast.Name):
return [TypeNode.id]
if isinstance(TypeNode, ast.Attribute):
return [TypeNode.attr]
if isinstance(TypeNode, ast.Subscript):
spec = self.Trans.ExprCallHandle._ResolveGenericSlice(TypeNode)
if spec:
return [spec]
return []
def _IsinstanceOne(self, Gen, ObjVal: ir.Value, ClassName: str) -> ir.Value:
"""单类判断:优先运行时 vtable 比较,失败回退编译时静态判断"""
VtableAddrs: list = self._CollectSubclassVtables(Gen, ClassName)
if VtableAddrs:
try:
return self._EmitVtableCompare(Gen, ObjVal, VtableAddrs)
except Exception:
pass
return self._IsinstanceCompileTime(Gen, ObjVal, ClassName)
def _CollectSubclassVtables(self, Gen, ClassName: str) -> list:
"""收集目标类及**所有已知子类**的 vtable 全局地址(沿 class_parents 反向 BFS)。
isinstance(obj, Cls) 的正确语义:obj 头部 vtable 标识对象实际类 C,
判断 Cls 是否 ∈ C 的祖先链。运行时无法从 C_vtable 向上推导祖先,
因此在编译期反向枚举 Cls 的子树(含 Cls 自身)的 vtable 地址,
运行时比较 obj->vtable ∈ 该集合(C 是 Cls 子类 → 命中)。
"""
addrs: list = []
# 反向子类表:父类名 -> [子类名...](class_parents 由 Phase1 全量填充)
children: dict[str, list[str]] = {}
for cn, parents in getattr(Gen, 'class_parents', {}).items():
for p in parents:
children.setdefault(p, []).append(cn)
queue: list[str] = [ClassName]
seen: set[str] = set()
while queue:
cn: str = queue.pop(0)
if cn in seen:
continue
seen.add(cn)
vt = self._ResolveVtableAddr(Gen, cn)
if vt is not None:
addrs.append(vt)
for ch in children.get(cn, []):
if ch not in seen:
queue.append(ch)
return addrs
def _ResolveVtableAddr(self, Gen, ClassName: str):
"""解析类 ClassName 的 vtable 全局地址(本地 Vtables / 跨模块外部引用),
无 vtable(NoVTable / 未注册)返回 None。
"""
vt = Gen.Vtables.get(ClassName) if hasattr(Gen, 'Vtables') else None
if vt is not None:
return vt
sha1_map: dict = getattr(Gen, 'class_sha1_map', {})
sha1 = sha1_map.get(ClassName)
if not sha1:
return None
vname: str = f"{sha1}.{ClassName}_Vtable"
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)
ext.linkage = 'external'
return ext
except Exception:
return None
def _EmitVtableCompare(self, Gen, ObjVal: ir.Value, VtableAddrs: list) -> ir.Value:
"""运行时:读对象头部 vtable 指针,与集合中每个 vtable 地址比较"""
ObjPtr: ir.Value = ObjVal
if isinstance(ObjVal.type, ir.PointerType) and isinstance(ObjVal.type.pointee, ir.PointerType):
# 二级指针(变量 alloca / 联合 Class|t.CPtr)→ 先 load 出对象指针
ObjPtr = Gen._load(ObjVal, name="isinstance_load")
if not isinstance(ObjPtr.type, ir.PointerType):
return self._IsinstanceCompileTime(Gen, ObjVal, '')
ObjAsVt: ir.Value = Gen.builder.bitcast(ObjPtr, ir.PointerType(ir.PointerType(ir.IntType(8))), name="isinstance_cast")
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")
Cmp: ir.Value = Gen.builder.icmp_unsigned('==', VtPtr, AddrI8, name="isinstance_cmp")
if Res is None:
Res = Cmp
else:
Res = Gen.builder.or_(Res, Cmp, name="isinstance_or")
return Res if Res is not None else ir.Constant(ir.IntType(1), 0)
def _IsinstanceCompileTime(self, Gen, ObjVal: ir.Value, ClassName: str) -> ir.Value:
"""编译时静态判断:obj 静态类型 == ClassName 或其 MRO 后代 → 1,否则 0"""
if ClassName:
ObjType: ir.Type = ObjVal.type
if isinstance(ObjType, ir.PointerType):
ObjType = ObjType.pointee
if isinstance(ObjType, ir.IdentifiedStructType):
ShortName: str = Gen._extract_short_name(ObjType.name)
mro: list = Gen._get_mro_chain(ClassName) if hasattr(Gen, '_get_mro_chain') else [ClassName]
if ShortName == ClassName or ShortName in mro:
return ir.Constant(ir.IntType(1), 1)
# obj 是 ClassName 的子类(obj 的 MRO 含 ClassName)
obj_mro: list = Gen._get_mro_chain(ShortName) if hasattr(Gen, '_get_mro_chain') else []
if ClassName in obj_mro:
return ir.Constant(ir.IntType(1), 1)
# 无法判断 → 保守 false
return ir.Constant(ir.IntType(1), 0)
def _HandlePrintLlvm(self, Node: ast.Call) -> ir.Value | None:
Gen: LlvmGeneratorMixin = self.Trans.LlvmGen
printf_func: ir.Function = Gen.get_or_declare_c_func('printf', ir.FunctionType(ir.IntType(32), [ir.PointerType(ir.IntType(8))], var_arg=True))
+56 -15
View File
@@ -2369,22 +2369,57 @@ class ExprCallHandle(BaseHandle):
# 失败时恢复 declare_only 标记,避免后续调用跳过补发
Gen._generic_class_specializations[spec_key] = (cached_name, True)
def _compute_field_byte_offset(self, Gen: LlvmGeneratorMixin, ClassName: str, field_name: str) -> int | None:
"""计算字段在 struct 中的字节偏移(按 LLVM 布局含对齐)。
遍历 struct elements 带对齐累加(与 _get_struct_size/_get_align 一致)。
有 vtable 的类 struct 首元素为 vtable 指针(i8*),class_members 不含它。
返回 None 表示无法计算(struct 未定义 / 字段不存在)。
"""
st = Gen.structs.get(ClassName)
if not st or not isinstance(st, ir.IdentifiedStructType) or st.elements is None:
return None
members: list = Gen.class_members.get(ClassName, [])
has_vtable: bool = (ClassName in Gen.class_vtable
or ClassName in Gen._cross_module_vtable_classes)
base: int = 1 if has_vtable else 0
idx: int | None = None
for i, (name, _) in enumerate(members):
if name == field_name:
idx = base + i
break
if idx is None or idx >= len(st.elements):
return None
offset: int = 0
for i in range(idx):
et = st.elements[i]
align: int = Gen._get_align(et)
size: int = Gen._get_struct_size(et)
if align > 1:
offset = ((offset + align - 1) // align) * align
offset += size
align = Gen._get_align(st.elements[idx])
if align > 1:
offset = ((offset + align - 1) // align) * align
return offset
def _adjust_this_for_inherited_method(self, Gen: LlvmGeneratorMixin, ObjVal: ir.Value,
ClassName: str, method_anc: str | None) -> ir.Value:
"""多继承 this 调整(与 TPV _translate_method_call 一致):
继承方法定义在"无 vtable 的父类"且该父类字段区在子类中偏移 > 0 时
(如 C(A,B).get_b,B 的字段区从 b 开始),父类方法按自身布局读
self.field@0 会读到前面父类的字段。调整:GEP 到父类第一个用户字段
在子类中的位置(索引 > 0),bitcast 成父类指针作为 self。
有 vtable 的父类布局是子类前缀(vptr 共享),self 无需调整。
继承方法体按"定义类自身布局"读 self 字段(如 B.get_b 内
GEP(self_B, [0, 1]),字节偏移为 B 布局中 b 的 offset 8)。父类字段区
在子类中的**字节偏移**可能不同(如 AB={i8*,a@8,b@12} 中 b 偏移 12),
self 需按字节差前移,使父类方法内二次 GEP 恰好落到子类字段。
修复(2026-08-10):字段索引法在"父类与子类元素大小不同"时错位
(GEP 索引按元素个数计,非字节等价)。改用字节偏移:
delta = 字段在子类中字节偏移 − 字段在父类自身布局中字节偏移
delta==0(第一父类,字段区前缀对齐)无需调整;delta>0 时按字节
GEP 到父类字段区在子类中的等效基址,再 bitcast 成父类指针。
"""
if method_anc is None or method_anc == ClassName:
return ObjVal
anc_has_vtable: bool = (method_anc in Gen.class_vtable
or method_anc in Gen._cross_module_vtable_classes)
if anc_has_vtable:
return ObjVal
anc_members: list = Gen.class_members.get(method_anc, [])
if not anc_members:
return ObjVal
@@ -2396,15 +2431,21 @@ class ExprCallHandle(BaseHandle):
break
if first_field_name is None:
return ObjVal
field_idx: int | None = Gen._get_member_offset(first_field_name, ClassName)
if field_idx is None or field_idx <= 0:
sub_off: int | None = self._compute_field_byte_offset(Gen, ClassName, first_field_name)
if sub_off is None:
return ObjVal
anc_off: int | None = self._compute_field_byte_offset(Gen, method_anc, first_field_name)
if anc_off is None:
return ObjVal
delta: int = sub_off - anc_off
if delta <= 0:
return ObjVal
if method_anc not in Gen.structs:
return ObjVal
adj: ir.Value = Gen.builder.gep(
ObjVal,
[ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), field_idx)],
name=f"this_adjust_{method_anc}")
obj_i8: ir.Value = Gen.builder.bitcast(ObjVal, ir.PointerType(ir.IntType(8)),
name=f"this_adjust_i8_{method_anc}")
adj: ir.Value = Gen.builder.gep(obj_i8, [ir.Constant(ir.IntType(64), delta)],
name=f"this_adjust_{method_anc}")
return Gen.builder.bitcast(adj, ir.PointerType(Gen.structs[method_anc]),
name=f"this_adjust_cast_{method_anc}")