Пересылка аргументов в 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 ответа:
ИМХО, ты все сделал правильно. Хотя в сборке machinecode есть решения, я боюсь, что в сборке LLVM может не быть решения, так как это "более высокий уровень". Если вы хотите запустить функцию в начале некоторых функций, вы думали о проверке
- источники отладчика (например, gdb)
- бинарное инструментирование с Valgrind
Я знаю, что это не прямой ответ, но я надеюсь, что это может быть полезно в некотором роде ;).
Не уверен, что это поможет, но у меня была аналогичная проблема, и я обошел ограничения анализа tbaa LLVM, используя вектор llvm для хранения промежуточных значений. Проходы оптимизации LLVM позже смогли оптимизировать векторную загрузку / хранение в скалярные регистры.
Насколько я помню, было несколько предостережений. Дайте мне знать, если вы исследуете этот маршрут, и я смогу найти какой-нибудь код.