2010-06-23 15 views
19

Estoy jugando con la API LLVM C++. Me gustaría JIT compilar el código y ejecutarlo.¿Puedo vincular un método existente a una Función LLVM * y utilizarlo desde el código compilado JIT?

Sin embargo, necesito llamar a un método C++ desde dicho código JIT compilado. Normalmente, LLVM trata las llamadas a métodos como llamadas a funciones con el puntero del objeto pasado como primer argumento, por lo que las llamadas no deberían ser un problema. El verdadero problema es hacer que esa función entre en LLVM.

Por lo que puedo ver, es posible usar un enlace externo para funciones y obtenerlo por su nombre. El problema es que, dado que es un método de C++, su nombre va a ser destrozado, así que no creo que sea una buena idea ir por 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?

Respuesta

15

Los tipos de la lista de correo de LLVM eran helpful enough to provide a better solution. No dijeron cómo sacar el puntero del método a la función, pero ya descubrí esta parte, así que está bien.

EDITAR una forma limpia de hacer esto es simplemente para envolver su método en una función:

int Foo_Bar(Foo* foo) 
{ 
    return foo->bar(); 
} 

A continuación, utilice 'dirección de s en lugar de tratar de obtener Foo::bar' Foo_Bar s. Use llvm::ExecutionEngine::addGlobalMapping para agregar la asignación como se muestra a continuación.

Como siempre, la solución más simple tiene algunos beneficios interesantes. Por ejemplo, funciona con funciones virtuales sin interrupciones. (Pero es mucho menos entretenido. El resto de la respuesta se mantiene con propósitos históricos, principalmente porque me divertí mucho hurgando en el funcionamiento interno de C++. También tenga en cuenta que no es portátil.)


tendrá algo en este sentido para averiguar la dirección de un método (se advirtió, eso es un truco sucio que probablemente sólo será compatible con el Itanium ABI):

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; 
} 

a continuación, utilice llvm::ExecutionEngine::addGlobalMapping para asignar una función a la dirección que ha recibido. Para llamarlo, páselo como el primer parámetro y el resto como de costumbre. 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)); 
+0

¿Te importaría mostrarnos cómo resolvió el problema al final? Estoy luchando con el mismo problema. – FFox

+0

@FFox: seguro. He editado la respuesta para proporcionar un ejemplo útil. – zneak

+0

@zneak, en el tipo de función que pasa a 'llvm :: Function :: Create' ¿qué tipo usó para el primer argumento (el puntero del objeto)? usaste void *? – lurscher

8

Una forma es un contenedor 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 las convenciones de llamada uso función de C y evita cualquier nombre mangling. Ver http://www.parashift.com/c++-faq-lite/mixing-c-and-cpp.html#faq-32.6 para obtener detalles sobre interoperabilidad C/C++ incluyendo extern "C"

También debe probablemente será capaz de obtener la dirección de la función en su código C++ y luego almacenar esa dirección en un mundial conocido a LLVM.

+0

Pensé que podría haber una manera más directa. – zneak

3

Huh, utilizando el no estándar dladdr y una forma ridículamente intrincada e insegura de lanzar punteros a punteros de método, parece haber una manera de obtener el nombre de un método de 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 arrojar punteros a void * (que es requerido por dladdr para funcionar) incluso con el todopoderoso lanzamiento de C, pero puede engañar.

#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 ""; 
} 

A partir de ahí:

int main() 
{ 
    std::cout << getMethodName(&Foo::bar) << std::endl; 
    // prints something like "_ZN3Foo3barEv" 
} 

... Aaaand usted debe ser capaz de utilizar ese nombre de símbolo con LLVM. Pero no funcionará con métodos virtuales (otra buena razón para no usarlo).

EDIT Hackeando mucho, mucho más profundo sobre cómo se manejan los punteros de método virtual, he creado una función más elaborada que también funciona para ellos. Solo el más valiente de ustedes debería follow this link.

+0

Voidify, eh. Bonito. –

+0

@Paul Nathan: emitió algunas advertencias, sin embargo ("el control llega al final de la función no válida"). Lo he "embellecido" para que se integre mejor con C++. – zneak

Cuestiones relacionadas