как реализовать виртуальную таблицу с помощью llvm - PullRequest
0 голосов
/ 26 февраля 2019

Я пишу игрушечный компилятор и хочу, чтобы мой язык поддерживал виртуальные методы, но я понятия не имею, как это сделать, это кажется не таким простым, как другие утверждения, которые можно легко превратить в код IR, не задумываясьКонцепция V-таблицы в моем сознании существует в виде некоторых графиков и линий, как это показано на некотором высоком уровне.Этого может быть достаточно для использования языка ООП, но, кажется, недостаточно для его написания.

Я попытался написать некоторый код C ++ и превратить его в код ir, но, к сожалению, до сих пор не могу понять вывод.Я проверил исходный код Clang и даже не мог понять, где находится эта часть ... (ну, я получил код, кажется, он расположен на lib/CodeGen/CGClass.cpp, но Clang - сложный проект, и я до сих пор не могу понять,как это реализовать v-таблицу)

Итак, есть идеи, как это сделать, или есть какие-то API-интерфейсы llvm, чтобы помочь мне реализовать это?

1 Ответ

0 голосов
/ 27 февраля 2019

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_foo, A_bar и B_bar, взяв указатель A или B и содержащий код для A.foo,A.bar и B.bar соответственно (точное название будет, конечно, зависеть от вашей схемы искажения имени).Затем вы сгенерируете два глобальных значения A_vtable и B_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);
}
...