如何使用llvm实现虚拟表
我正在编写一个玩具编译器,希望我的语言支持虚拟方法,但我不知道如何做到这一点,它似乎不像其他语句那样直截了当,可以轻松地转换为IR代码,无需再三考虑,v表的概念在我的脑海中就像一些图形和线条一样存在,就像一些高级说明。这对于使用OOP语言来说可能已经足够了,但对于编写OOP语言来说似乎还不够 我试图编写一些C++代码并将其转换成IR代码,但遗憾的是,我仍然不能理解输出。我检查了Clang的源代码,甚至不知道这部分在哪里…(我得到了代码,它似乎位于如何使用llvm实现虚拟表,llvm,llvm-ir,Llvm,Llvm Ir,我正在编写一个玩具编译器,希望我的语言支持虚拟方法,但我不知道如何做到这一点,它似乎不像其他语句那样直截了当,可以轻松地转换为IR代码,无需再三考虑,v表的概念在我的脑海中就像一些图形和线条一样存在,就像一些高级说明。这对于使用OOP语言来说可能已经足够了,但对于编写OOP语言来说似乎还不够 我试图编写一些C++代码并将其转换成IR代码,但遗憾的是,我仍然不能理解输出。我检查了Clang的源代码,甚至不知道这部分在哪里…(我得到了代码,它似乎位于lib/CodeGen/CGClass.cpp,但
lib/CodeGen/CGClass.cpp
,但Clang是一个复杂的项目,我仍然不明白它是如何实现v表的)
你知道怎么做吗,或者有什么LLVMAPI可以帮我实现吗?vtable是一个函数指针数组。在单个继承上下文中,每个类都有一个这样的数组,其中数组的元素是类的虚拟方法。然后,每个对象将包含一个指向其类的vtable的指针,并且每个虚拟方法调用将只调用vtable中相应的指针(在将其转换为所需类型之后) 假设您正在编译一个如下所示的程序:
class A {
int x,y;
virtual int foo() { return x+y; }
virtual int bar() { return x*y; }
}
class B inherits A {
int z;
override int bar() { return x*y+z; }
}
int f(A a) {
return a.foo() + a.bar();
}
@A_vtable = global [2 x void (...)*] [
void (...)* bitcast (i32 (%struct.A*)* @A_foo to void (...)*),
void (...)* bitcast (i32 (%struct.A*)* @A_bar to void (...)*)
]
@B_vtable = global [2 x void (...)*] [
void (...)* bitcast (i32 (%struct.A*)* @A_foo to void (...)*),
void (...)* bitcast (i32 (%struct.B*)* @B_bar to void (...)*)
]
define i32 @f(%struct.A*) {
%2 = getelementptr inbounds %struct.A, %struct.A* %0, i64 0, i32 0
%3 = bitcast %struct.A* %0 to i32 (%struct.A*)***
%4 = load i32 (%struct.A*)**, i32 (%struct.A*)*** %3
%5 = load i32 (%struct.A*)*, i32 (%struct.A*)** %4
%6 = call i32 %5(%struct.A* %0)
%7 = load void (...)**, void (...)*** %2
%8 = getelementptr inbounds void (...)*, void (...)** %7, i64 1
%9 = bitcast void (...)** %8 to i32 (%struct.A*)**
%10 = load i32 (%struct.A*)*, i32 (%struct.A*)** %9
%11 = call i32 %10(%struct.A* %0)
%12 = add nsw i32 %11, %6
ret i32 %12
}
然后,您可以定义名为A_-foo
、A_-bar
和B_-bar
的函数,这些函数使用A
或B
指针,分别包含A.foo
、A.bar
和B.bar
的代码(确切的命名当然取决于您的名称管理方案)。然后生成两个全局变量A_vtable
和B_vtable
,如下所示:
class A {
int x,y;
virtual int foo() { return x+y; }
virtual int bar() { return x*y; }
}
class B inherits A {
int z;
override int bar() { return x*y+z; }
}
int f(A a) {
return a.foo() + a.bar();
}
@A_vtable = global [2 x void (...)*] [
void (...)* bitcast (i32 (%struct.A*)* @A_foo to void (...)*),
void (...)* bitcast (i32 (%struct.A*)* @A_bar to void (...)*)
]
@B_vtable = global [2 x void (...)*] [
void (...)* bitcast (i32 (%struct.A*)* @A_foo to void (...)*),
void (...)* bitcast (i32 (%struct.B*)* @B_bar to void (...)*)
]
define i32 @f(%struct.A*) {
%2 = getelementptr inbounds %struct.A, %struct.A* %0, i64 0, i32 0
%3 = bitcast %struct.A* %0 to i32 (%struct.A*)***
%4 = load i32 (%struct.A*)**, i32 (%struct.A*)*** %3
%5 = load i32 (%struct.A*)*, i32 (%struct.A*)** %4
%6 = call i32 %5(%struct.A* %0)
%7 = load void (...)**, void (...)*** %2
%8 = getelementptr inbounds void (...)*, void (...)** %7, i64 1
%9 = bitcast void (...)** %8 to i32 (%struct.A*)**
%10 = load i32 (%struct.A*)*, i32 (%struct.A*)** %9
%11 = call i32 %10(%struct.A* %0)
%12 = add nsw i32 %11, %6
ret i32 %12
}
这与C代码相对应(希望可读性更好):
f
可以这样翻译:
class A {
int x,y;
virtual int foo() { return x+y; }
virtual int bar() { return x*y; }
}
class B inherits A {
int z;
override int bar() { return x*y+z; }
}
int f(A a) {
return a.foo() + a.bar();
}
@A_vtable = global [2 x void (...)*] [
void (...)* bitcast (i32 (%struct.A*)* @A_foo to void (...)*),
void (...)* bitcast (i32 (%struct.A*)* @A_bar to void (...)*)
]
@B_vtable = global [2 x void (...)*] [
void (...)* bitcast (i32 (%struct.A*)* @A_foo to void (...)*),
void (...)* bitcast (i32 (%struct.B*)* @B_bar to void (...)*)
]
define i32 @f(%struct.A*) {
%2 = getelementptr inbounds %struct.A, %struct.A* %0, i64 0, i32 0
%3 = bitcast %struct.A* %0 to i32 (%struct.A*)***
%4 = load i32 (%struct.A*)**, i32 (%struct.A*)*** %3
%5 = load i32 (%struct.A*)*, i32 (%struct.A*)** %4
%6 = call i32 %5(%struct.A* %0)
%7 = load void (...)**, void (...)*** %2
%8 = getelementptr inbounds void (...)*, void (...)** %7, i64 1
%9 = bitcast void (...)** %8 to i32 (%struct.A*)**
%10 = load i32 (%struct.A*)*, i32 (%struct.A*)** %9
%11 = call i32 %10(%struct.A* %0)
%12 = add nsw i32 %11, %6
ret i32 %12
}
或在C中:
typedef int (*A_int_method_t)(struct A*);
int f(struct A* a) {
return ((A_int_method_t) a->vtable[0])(a) + ((A_int_method_t) a->vtable[1])(a);
}
有没有办法减少参赛人数?假设我们在类A中有十个虚函数,只有一个函数被类B覆盖,这是否意味着我也必须在B的vtable中放入其他9个条目?如果继承树很大,那么子类的vtable不是更大吗?@reavenisadesk问题是
f
对B
一无所知,它只是在它想要调用的方法对应的索引处访问给定的A*
的vtable。要使其正常工作,vtable必须至少具有f
所期望的条目数量,并且所有条目都必须是有效的函数指针(但将空指针放在其中并不会保存任何内容)。因此,是的,如果A
有10个虚拟方法,B
的vtable也必须有10个条目(加上B
定义的虚拟方法)。请注意,vtable在每个类中只存在一次,并且不影响。。。。。。对象的大小。所以每个类只需要消耗几个字节的常量内存,这通常不是问题。@sepp2k您能解释一下void(…)*?它是什么意思?您是如何创建的?@worldterminatorvoid(…)*
是指向可变void函数的指针,但确切的类型并不重要,因为我们总是在使用之前对其进行位广播。您可以使用任何想要的函数指针类型(或者实际上是任何指针类型-我只是使用函数指针来强调它将始终指向函数)。我不知道你所说的“创造”是什么意思。你是说我是如何使用LLVM API生成类型的?我没有,我只是为这个答案手工编写了LLVM代码,但是您可以使用LLVM::PointerType
和LLVM::FunctionType
来完成。