如何使用llvm实现虚拟表

如何使用llvm实现虚拟表,llvm,llvm-ir,Llvm,Llvm Ir,我正在编写一个玩具编译器,希望我的语言支持虚拟方法,但我不知道如何做到这一点,它似乎不像其他语句那样直截了当,可以轻松地转换为IR代码,无需再三考虑,v表的概念在我的脑海中就像一些图形和线条一样存在,就像一些高级说明。这对于使用OOP语言来说可能已经足够了,但对于编写OOP语言来说似乎还不够 我试图编写一些C++代码并将其转换成IR代码,但遗憾的是,我仍然不能理解输出。我检查了Clang的源代码,甚至不知道这部分在哪里…(我得到了代码,它似乎位于lib/CodeGen/CGClass.cpp,但

我正在编写一个玩具编译器,希望我的语言支持虚拟方法,但我不知道如何做到这一点,它似乎不像其他语句那样直截了当,可以轻松地转换为IR代码,无需再三考虑,v表的概念在我的脑海中就像一些图形和线条一样存在,就像一些高级说明。这对于使用OOP语言来说可能已经足够了,但对于编写OOP语言来说似乎还不够

我试图编写一些C++代码并将其转换成IR代码,但遗憾的是,我仍然不能理解输出。我检查了Clang的源代码,甚至不知道这部分在哪里…(我得到了代码,它似乎位于
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(…)*?它是什么意思?您是如何创建的?@worldterminator
void(…)*
是指向可变void函数的指针,但确切的类型并不重要,因为我们总是在使用之前对其进行位广播。您可以使用任何想要的函数指针类型(或者实际上是任何指针类型-我只是使用函数指针来强调它将始终指向函数)。我不知道你所说的“创造”是什么意思。你是说我是如何使用LLVM API生成类型的?我没有,我只是为这个答案手工编写了LLVM代码,但是您可以使用
LLVM::PointerType
LLVM::FunctionType
来完成。