c++ - compiler - ¿Puedo vincular un método existente a una función LLVM*y usarlo desde el código compilado por JIT?
llvm tutorial (3)
Huh, al utilizar el dladdr
no estándar y una forma ridículamente complicada e insegura de lanzar punteros a métodos para anular los punteros, parece haber una forma de obtener el nombre de un método desde su puntero.
Esto es ciertamente más peligroso que las armas de fuego. No hagas esto en casa (o en el trabajo, para el caso).
C ++ prohíbe lanzar punteros de método para anular * (que es requerido por dladdr
para que funcione) incluso con el elenco de C todopoderoso, pero puedes hacer trampa.
#include <string>
#include <dlfcn.h>
template<typename T>
static void* voidify(T method)
{
asm ("movq %rdi, %rax"); // should work on x86_64 ABI compliant platforms
}
template<typename T>
const char* getMethodName(T method)
{
Dl_info info;
if (dladdr(voidify(method), &info))
return info.dli_sname;
return "";
}
Desde allí:
int main()
{
std::cout << getMethodName(&Foo::bar) << std::endl;
// prints something like "_ZN3Foo3barEv"
}
... aaaand usted debería poder usar ese nombre de símbolo con LLVM. Pero no funcionará con métodos virtuales (otra buena razón para no usarlo).
EDITAR Al piratear mucho, mucho más profundamente sobre cómo se manejan los punteros de los métodos virtuales, he creado una función más elaborada que también funciona para ellos. Solo los más valientes de ustedes deben seguir este enlace .
Estoy jugando con la API LLVM C ++. Me gustaría JIT compilar código y ejecutarlo.
Sin embargo, necesito llamar a un método C ++ desde dicho código compilado por JIT. Normalmente, LLVM trata las llamadas de método como llamadas de función con el puntero del objeto pasado como primer argumento, por lo que la llamada no debería ser un problema. El problema real es conseguir esa función en LLVM.
Por lo que puedo ver, es posible utilizar un enlace externo para las funciones y obtenerlo por su nombre. El problema es que, dado que se trata de un método C ++, su nombre será mutilado, por lo que no creo que sea una buena idea seguir ese camino.
Hacer el objeto FunctionType
es bastante fácil. Pero a partir de ahí, ¿cómo puedo informar a LLVM de mi método y obtener un objeto Function
para ello?
Los tipos de la lista de correo de LLVM fueron lo suficientemente útiles para proporcionar una mejor solución . No dijeron cómo obtener el puntero del método a la función, pero ya he descubierto esta parte, así que está bien.
EDITAR Una forma limpia de hacerlo es simplemente envolver su método en una función:
int Foo_Bar(Foo* foo)
{
return foo->bar();
}
Luego use la dirección de Foo_Bar
lugar de intentar obtener Foo::bar
''s. Use llvm::ExecutionEngine::addGlobalMapping
para agregar la asignación como se muestra a continuación.
Como de costumbre, la solución más simple tiene algunos beneficios interesantes. Por ejemplo, funciona con funciones virtuales sin ningún problema. (Pero es mucho menos entretenido. El resto de la respuesta se mantiene con fines históricos, principalmente porque me divertí mucho hurgando en el interior de mi tiempo de ejecución de C ++. También tenga en cuenta que no es portátil).
Necesitará algo en esta línea para calcular la dirección de un método (tenga cuidado, es un truco sucio que probablemente solo será compatible con el ABI de Itanium):
template<typename T>
const void* void_cast(const T& object)
{
union Retyper
{
const T object;
void* pointer;
Retyper(T obj) : object(obj) { }
};
return Retyper(object).pointer;
}
template<typename T, typename M>
const void* getMethodPointer(const T* object, M method) // will work for virtual methods
{
union MethodEntry
{
intptr_t offset;
void* function;
};
const MethodEntry* entry = static_cast<const MethodEntry*>(void_cast(&method));
if (entry->offset % sizeof(intptr_t) == 0) // looks like that''s how the runtime guesses virtual from static
return getMethodPointer(method);
const void* const* const vtable = *reinterpret_cast<const void* const* const* const>(object);
return vtable[(entry->offset - 1) / sizeof(void*)];
}
template<typename M>
const void* getMethodPointer(M method) // will only work with non-virtual methods
{
union MethodEntry
{
intptr_t offset;
void* function;
};
return static_cast<const MethodEntry*>(void_cast(&method))->function;
}
Luego use llvm::ExecutionEngine::addGlobalMapping
para asignar una función a la dirección que ha obtenido. Para llamarlo, pásale tu objeto como primer parámetro y el resto como siempre. Aquí hay un ejemplo rápido.
class Foo
{
void Bar();
virtual void Baz();
};
class FooFoo : public Foo
{
virtual void Baz();
};
Foo* foo = new FooFoo;
const void* barMethodPointer = getMethodPointer(&Foo::Bar);
const void* bazMethodPointer = getMethodPointer(foo, &Foo::Baz); // will get FooFoo::Baz
llvm::ExecutionEngine* engine = llvm::EngineBuilder(module).Create();
llvm::Function* bar = llvm::Function::Create(/* function type */, Function::ExternalLinkage, "foo", module);
llvm::Function* baz = llvm::Function::Create(/* function type */, Function::ExternalLinkage, "baz", module);
engine->addGlobalMapping(bar, const_cast<void*>(barMethodPointer)); // LLVM always takes non-const pointers
engine->addGlobalMapping(baz, const_cast<void*>(bazMethodPointer));
Una forma es una envoltura de C alrededor del método deseado, es decir,
extern "C" {
void wrapped_foo(bar *b, int arg1, int arg2) {
b->foo(arg1, arg2);
}
}
El bit extern "C"
hace que la función use las convenciones de llamada de C y evita cualquier alteración de nombres. Consulte http://www.parashift.com/c++-faq-lite/mixing-c-and-cpp.html#faq-32.6 para obtener detalles sobre la interoperabilidad C / C ++, incluida la extern "C"
Probablemente, también debería poder obtener la dirección de la función en su código C ++ y luego almacenar esa dirección en un nombre global conocido por LLVM.