【问题标题】:how to implement virtual table by using llvm如何使用llvm实现虚拟表
【发布时间】:2019-02-26 06:54:53
【问题描述】:

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

我尝试编写一些 C++ 代码并将其转换为 ir 代码,但遗憾的是我仍然无法理解输出。我检查了 Clang 的源代码,甚至无法弄清楚这部分的位置......(好吧,我得到了代码,它似乎位于 lib/CodeGen/CGClass.cpp,但 Clang 是一个复杂的项目,我仍然无法理解它是如何实现 v-table 的)

那么知道如何做到这一点,或者是否有一些 llvm api 可以帮助我实现这一点?

【问题讨论】:

    标签: llvm llvm-ir


    【解决方案1】:

    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_fooA_barB_bar 的函数,它们分别采用AB 指针并分别包含A.fooA.barB.bar 的代码(确切的当然,命名将取决于您的名称修改方案)。然后你会生成两个全局变量A_vtableB_vtable,看起来像这样:

    @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 (...)*)
    ]
    

    这将对应于这个 C 代码(希望它更具可读性):

    typedef void (*fpointer_t)();
    fpointer_t A_vtable[] = {(fpointer_t) A_foo, (fpointer_t) A_bar};
    fpointer_t B_vtable[] = {(fpointer_t) A_foo, (fpointer_t) B_bar};
    

    f 可以这样翻译:

    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 类中有 10 个虚函数,而 B 类只覆盖了一个函数,这是否意味着我也必须将其他 9 个条目放入 B 的 vtable 中?如果继承树很大,子类的vtable不是更大吗?
    • @reavenisadesk 问题是fB 一无所知,它只是访问给定的A* 的vtable,其索引对应于它要调用的方法。为此,vtable 必须至少有f 期望的一样多的条目,并且所有条目必须是有效的函数指针(但它不像将空指针放在那里会保存任何东西)。所以是的,如果 A 有 10 个虚拟方法,B 的 vtable 也必须有 10 个条目(加上 B 定义的任何虚拟方法)。请注意,vtable 每个类只存在一次,不会影响...
    • ... 对象的大小。所以每个类只消耗几个字节的常量内存,这通常不是问题。
    • @sepp2k 你能解释更多关于 void (...)* 的信息吗?它是什么意思,你是如何创造它的?
    • @worldterminator void (...)* 是一个指向可变参数 void 函数的指针,但确切的类型并不重要,因为我们总是在使用之前对其进行比特转换。你可以使用任何你想要的函数指针类型(或者任何指针类型——我只是使用函数指针来强调它总是指向一个函数)。我不确定您所说的“创建”它是什么意思。你的意思是我是如何使用 LLVM API 生成类型的?我没有,我只是为这个答案手动编写了 LLVM 代码,但你这样做的方式是使用 llvm::PointerTypellvm::FunctionType
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-12
    • 2015-05-02
    • 2013-10-05
    • 2016-06-15
    • 1970-01-01
    相关资源
    最近更新 更多