Мне нужен совет по "пересылке" аргументов вызываемому объекту (в 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);
// ...
}