Calling functions with arbitrary number and argument types in JIT

I’m building a JIT compiler and I’m able to call a function easily like this:

auto EntrySym = (*JIT)->lookup("MyFunction");
double *data;
int data_size;
auto *Entry = (void(*)(int N, double* ptr))EntrySym.get().getAddress();
Entry(data_size, data);

But I need the JIT to be able to call functions with an arbitrary number and types of arguments, which is something this scheme does not allow. Then, I tried something like the following:

llvm::ExecutionEngine *EE;
Function* myF = EE->FindFunctionNamed("MyFunction");
double *data;
int data_size;
std::vector<GenericValue> args(8);
args[0].IntVal = data_size;
args[1].PointerVal = data;
GenericValue gv = EE->runFunction(myF, args);

But I got:

LLVM ERROR: MCJIT::runFunction does not support full-featured argument passing. Please use ExecutionEngine::getFunctionAddress and cast the result to the desired function pointer type.

Which essentially tells me to go back to my previous scheme, which is not flexible enough for my case. I saw that in the upstream version of LLVM we still have this limitation. I also saw other people suffering this issue in 2020.

What can I do to call arbitrary functions (arbitrary number of arguments, arbitrary type of arguments)? Do we have a new way of doing this in 2022?

So I tried implementing the same idea with ORCv2 instead of MCJIT (like the first code I posted) because I need to load dynamic libraries on the fly (which is something I assume MCJIT cannot do) but still don’t know how to call functions with arbitrary arguments dynamically. Any ideas?

You want to generate code at runtime to call a function with an arbitrary signature? Sounds like a job for a JIT. :slight_smile:

Yes, codes above correspond to a JIT. But I don’t know how to use them to call a function with an arbitrary signature. In summary, what I found so far is that there are two ways of doing that:

  • MCJIT (Old way): There are ways to call functions with arbitrary signatures but:
    • Plain MCJIT: Gives the MCJIT::runFunction does not support full-featured argument passing error.
    • Interpreter: Works, but it is not possible to load dynamic libraries. So you can’t run a code that contains a simple malloc.
  • ORCv2 (New way): Allows to load dynamic libraries but the are no ways to call functions with arbitrary signatures. Just the lookup function.

My best bet right now is that LLVM has no support for what I’m asking. But then, what can I do? Are there any alternatives?

And by the way @akorobeynikov I’m not sure if this should go to the Beginners category. It might seem this is a question about how to do something that I don’t know how to do, but seems more like a question about if there is actually a way of doing something (is it possible)? Is it implemented in LLVM?

Well, originally it was in “infrastructure” which is certainly a wrong category for it.

Maybe it should be just LLVM Project?

Not really: this category is for things that span across the entire project. It does not seem totally off for the “beginner” section to me, seems like the kind of questions people trying to get started with the JIT would want answered.
It could fit a JIT category if we had one (maybe we should? It is a quite large/common enough component?).

To be a bit more explicit, I was suggesting that you can emit LLVM IR that performs the call you want, compile that, and then call it.

Alternatively, you could use a library like libffi.

2 Likes

+1 to @efriedma-quic’s answer.

You could use clang or libclang to generate a wrapper from C/C++, or LLVM to generate an LLVM IR wrapper. Otherwise libffi is the way to go.

1 Like

My first bet was to design an LLVM IR wrapper like you said, but definitely libffi is a way cleaner solution. I never heard of that library, it’s nice! Thanks for the suggestion, it definitely suits my needs.

A personal thought: As the LLVM is so big and complete, sometimes it’s hard to remember that there is functionality that LLVM does not (and probably should not) provide, but that you can always integrate external tools to make it even more powerful.