Пересылка аргументов в LLVM


Мне нужен совет по "пересылке" аргументов вызываемому абоненту (в LLVM-IR).

Предположим, что у меня есть функция F, которая вызывается в начале всех других функций в модуле. Из F Мне нужно получить доступ (прочитать) аргументы, переданные его непосредственному вызывающему объекту.

Прямо сейчас для этого я помещаю все аргументы вызывающего объекта в структуру и передаю i8* указатель на структуру в F вместе с идентификатором, указывающим, какой вызывающий объект F вызывается. от. F имеет затем гигантский переключатель, который разветвляется на соответствующий код распаковки. Это необходимо сделать, потому что функции в модуле имеют разные сигнатуры (различное число аргументов / возвращаемых значений и типы; даже разные соглашения о вызовах), но это явно неоптимально (как с точки зрения производительности, так и размера кода), потому что мне нужно выделить структуру в стеке, скопировать аргументы внутри нее, передать дополнительный указатель на F и затем выполнить функцию. распаковка.

Мне было интересно, есть ли лучший способ сделать это, т. е. способ доступа из функции к кадру стека ее непосредственного вызывающего объекта (зная, благодаря идентификатору, из которого вызывается функция) или, более в целом, произвольные значения, определенные в ее непосредственном вызывающем объекте. Есть предложения?

Примечание: весь смысл того, над чем я работаю, состоит в том, чтобы иметь единственную функцию F, которая делает все это; разбиение/встраивание/специализация / создание шаблонов F - это не вариант.


Для уточнения предположим, что у нас есть следующие функции FuncA и FuncB (Примечание: то, что следует ниже, является просто псевдо-c-кодом, всегда помните, что мы говорим о LLVM-IR!)

Type1 FuncA(Type2 ArgA1) {
  F();
  // ...
}

Type3 FuncB(Type4 ArgB1, Type5 ArgB2, Type6 ArgB3) {
  F();
  // ...
}

Что мне нужно, так это эффективный способ для функции F сделать следующее:

void F() {
  switch (caller) {
    case FuncA:
      // do something with ArgA1
      break;
    case FuncB:
      // do something with ArgB1, ArgB2, ArgB3
      break;
  }
}
Как я уже объяснял в первой части, сейчас мой F выглядит так:
struct Args_FuncA { Type2 ArgA1 };
struct Args_FuncB { Type4 ArgB1, Type5 ArgB2, Type6 ArgB3 };

void F(int callerID, void *args) {
  switch (callerID) {
    case ID_FuncA:
      Args_FuncA *ArgsFuncA = (Args_FuncA*)args;
      Type2 ArgA1 = ArgsFuncA->ArgA1;
      // do something with ArgA1
      break;
    case ID_FuncB:
      Args_FuncB *ArgsFuncB = (Args_FuncB*)args;
      Type4 ArgB1 = ArgsFuncB->ArgB1;
      Type5 ArgB2 = ArgsFuncB->ArgB2;
      Type6 ArgB3 = ArgsFuncB->ArgB3;
      // do something with ArgB1, ArgB2, ArgB3
      break;
  }
}

И эти две функции становятся:

Type1 FuncA(Type2 ArgA1) {
  Args_FuncA args = { ArgA1 };
  F(ID_FuncA, (void*)&args);
  // ...
}

Type3 FuncB(Type4 ArgB1, Type5 ArgB2, Type6 ArgB3) {
  Args_FuncB args = { ArgB1, ArgB2, ArgB3 };
  F(ID_FuncB, (void*)&args);
  // ...
}
2 7

2 ответа:

ИМХО, ты все сделал правильно. Хотя в сборке machinecode есть решения, я боюсь, что в сборке LLVM может не быть решения, так как это "более высокий уровень". Если вы хотите запустить функцию в начале некоторых функций, вы думали о проверке

  • источники отладчика (например, gdb)
  • бинарное инструментирование с Valgrind

Я знаю, что это не прямой ответ, но я надеюсь, что это может быть полезно в некотором роде ;).

Не уверен, что это поможет, но у меня была аналогичная проблема, и я обошел ограничения анализа tbaa LLVM, используя вектор llvm для хранения промежуточных значений. Проходы оптимизации LLVM позже смогли оптимизировать векторную загрузку / хранение в скалярные регистры.

Насколько я помню, было несколько предостережений. Дайте мне знать, если вы исследуете этот маршрут, и я смогу найти какой-нибудь код.