I'm toying around with the LLVM C++ API. I'd like to JIT compile code and run it.
However, I need to call a C++ method from said JIT-compiled code. Normally, LLVM treats method calls as function calls with the object pointer passed as the first argument, so calling shouldn't be a problem. The real problem is to get that function into LLVM.
As far as I can see, it's possible to use external linkage for functions and get it by its name. Problem is, since it's a C++ method, its name is going to be mangled, so I don't think it's a good idea to go that way.
Making the FunctionType
object is easy enough. But from there, how can I inform LLVM of my method and get a Function
object for it?
The LLVM JIT compiler is function-based because it is able to compile a single function at a time. This defines the granularity at which the compiler works, which is an important decision of a JIT system.
LLJIT is a suitable replacement for MCJIT in most cases (note: some more advanced features, e.g. JITEventListeners are not supported yet). The LLLazyJIT extends LLJIT and adds a CompileOnDemandLayer to enable lazy compilation of LLVM IR.
The dudes from the LLVM mailing list were helpful enough to provide a better solution. They didn't say how to get the pointer from the method to the function, but I've already figured out this part so it's okay.
EDIT A clean way to do this is simply to wrap your method into a function:
int Foo_Bar(Foo* foo) { return foo->bar(); }
Then use Foo_Bar
's address instead of trying to get Foo::bar
's. Use llvm::ExecutionEngine::addGlobalMapping
to add the mapping as shown below.
As usual, the simplest solution has some interesting benefits. For instance, it works with virtual functions without a hiccup. (But it's so much less entertaining. The rest of the answer is kept for historical purposes, mainly because I had a lot of fun poking at the internals of my C++ runtime. Also note that it's non-portable.)
You'll need something along these lines to figure the address of a method (be warned, that's a dirty hack that probably will only be compatible with the 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; }
Then use llvm::ExecutionEngine::addGlobalMapping
to map a function to the address you've gotten. To call it, pass it your object as the first parameter, and the rest as usual. Here's a quick example.
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));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With