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

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

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

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

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

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


для пояснения, предположим, что у нас есть следующие функции FuncA и FuncB (примечание: то, что далее следует - это просто псевдокод, всегда помните, что мы говорим о 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);
  // ...
}

person CAFxX    schedule 22.08.2011    source источник


Ответы (2)


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

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

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

person Grzegorz Wierzowiecki    schedule 22.08.2011
comment
dtrace сделал то же самое, что описывает CAFxX. - person osgx; 22.08.2011

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

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

person Mike Woodworth    schedule 23.08.2011