How to call native functions from bytecode run in JIT?

Hello,

can anyone help me calling native functions from LLVM-Bytecode functions run in the JIT?

I have a program which creates an LLVM execution engine and adds modules and functions
to it on the fly. I need to call some native functions of my program from bytecode functions which causes some troubles as it appears not to be documented. My test scenario works like the following:

I have a simple function "int get5()" which I want to call from a bytecode function "int callget5()" which is generated at runtime. I register the native function "get5" using addGlobalMapping in the execution engine. I then try to call it using runFunction. This works under OS X, but fails in Linux giving something like "tried to call unknown function int* () get5". Calling module->getFunction("foo5") does not return NULL, though. Some relevant code snippets:

// the native function

int get5() { return 5; }

// registering it

std::vector<const Type*> emptyArgList;
FunctionType* get5Type = FunctionType::get(Type::Int32Ty, emptyArgList, false);
Function* get5Function = new Function(get5Type, GlobalValue::ExternalLinkage, "get5", m);
EE->addGlobalMapping( get5Function, (void*)get5 );

// verifying it's existance

Function* func = m->getFunction("get5");
if( func == NULL ) {
   std::cout << "registered native function not found\n";
   exit(-1);
}

// calling it

EE->runFunction(func, noargs).IntVal.getLimitedValue()

As said, it will fail calling the function in Linux (but works in OS X). Version is LLVM 2.0, OS X 10.4.9, several linux distros

How can I fix this?

When I try to construct a function which will call "get5" it will fail on OS X, too. I have attached the whole .cpp file containing the complete code. It's adapted from the JIT example in the LLVM source distribution. I compile it using

g++ -c codegen1.cpp -o codegen1.o -D__STDC_LIMIT_MACROS
g++ -o codegen1 -L${LLVM_LIB_DIR} codegen1.o ${LLVM_LIB_DIR}/LLVMPowerPC.o -lLLVMSelectionDAG ${LLVM_LIB_DIR}/LLVMInterpreter.o ${LLVM_LIB_DIR}/LLVMJIT.o -lLLVMCodeGen -lLLVMScalarOpts -lLLVMTransformUtils -lLLVMipa -lLLVMAnalysis ${LLVM_LIB_DIR}/LLVMExecutionEngine.o -lLLVMTarget -lLLVMCore -lLLVMSupport -lLLVMbzip2 -lLLVMSystem -lpthread -ldl

greetings,
Jan Rehders

codegen1.cpp (5.12 KB)

Are you able make calls to well known external functions such as printf? As far as I known, this capability is well tested on x86 / Linux.

I am wondering if there is some name mangling issue?

Evan

Are you able make calls to well known external functions such as
printf? As far as I known, this capability is well tested on x86 /
Linux.

Calling printf works at least on OS X (I'm waiting for a reply whether this works on linux). If I call my native function it fails with PPCJITInfo.cpp:382: failed assertion `ResultPtr >= -(1 << 23) && ResultPtr < (1 << 23) && "Relocation out of range!"'

I am wondering if there is some name mangling issue?

Not that I'm aware of. It appears to find the function using module- >getFunction("get5")

Is there really nobody here who knows how to properly register native functions of the application to the execution engine?

Hi Jan,

Calling printf works at least on OS X (I'm waiting for a reply
whether this works on linux). If I call my native function it fails
with PPCJITInfo.cpp:382: failed assertion `ResultPtr >= -(1 << 23) &&
ResultPtr < (1 << 23) && "Relocation out of range!"'

I know nothing about this, but the failed assertion suggests the PPC
code generator can't cope with a constant that's bigger than expected at
that point. Have you taken a look at PPCJITInfo.cpp:382? It may shed
some light.

Cheers,

Ralph.

Hi,

I know nothing about this, but the failed assertion suggests the PPC
code generator can't cope with a constant that's bigger than expected at
that point. Have you taken a look at PPCJITInfo.cpp:382? It may shed
some light.

It's inside PPCJITInfo::relocate but unfortunately I could not figure out anything from the source. It looks like it's calculating new addresses for functions which does not make much sense for a native function, at all

Greetings,
Jan

On the PPC, unconditional branches are limited to 24 bit signed displacements. When you call a function in a program, the compiler therefore will create a stub which will indirectly load the address of the function (the first time it's called, usually with the help of the dynamic linker) and then perform an indirect branch using this calculated address. This stub will be placed close to the call site, so the displacement to the stub will be in range.

LLVM presumably needs to generate such stubs for native functions introduced at run time as well, which it may not yet do. I have not looked into the code though, so I can't really say for sure what goes wrong here.

Jonas

It's inside PPCJITInfo::relocate but unfortunately I could not figure
out anything from the source. It looks like it's calculating new
addresses for functions which does not make much sense for a native
function, at all

On the PPC, unconditional branches are limited to 24 bit signed
displacements. When you call a function in a program, the compiler
therefore will create a stub which will indirectly load the address
of the function (the first time it's called, usually with the help of
the dynamic linker) and then perform an indirect branch using this
calculated address. This stub will be placed close to the call site,
so the displacement to the stub will be in range.

Right.

LLVM presumably needs to generate such stubs for native functions
introduced at run time as well, which it may not yet do. I have not
looked into the code though, so I can't really say for sure what goes
wrong here.

The LLVM PPC JIT certainly does produce these stubs. The PPCJITInfo::emitFunctionStub method is the one that actually creates them :slight_smile:

Jan, how are you doing this? Are you creating an external LLVM Function object named "get5", then using EE::addGlobalMapping? If 'get5' exists in the address space, why not just let the JIT resolve it (which will then create the stub)?

-Chris

Hi,

Jan, how are you doing this? Are you creating an external LLVM Function
object named "get5", then using EE::addGlobalMapping? If 'get5' exists in
the address space, why not just let the JIT resolve it (which will then
create the stub)?

Yes. I create a Function with matching signature, calling conventions and external linkage which I pass together with the function's address to addGlobalMapping.

I have an application which creates an execution engine and a module and creates some bytecode functions at runtime. I need to call back to some of the functions in the app (for a language runtime). What do you mean with "address space"? The function is part of the application but I don't see how LLVM would find it? If I remove the addGlobalMapping call, the function is not found at all

- Jan

Hi,

I ran into the same issue some time ago, so you might find the
following message from the mailing list useful. You probably want to
look at some of the previous emails in the thread to get the
background.

http://lists.cs.uiuc.edu/pipermail/llvmdev/2006-September/006817.html

/ Andreas

Okay. If the function exists in your application's address space already, just name the LLVM function the same name as the native function and the JIT should find it an do the right thing. This is how it finds printf and a variety of other things. You don't need to call addGlobalMapping at all.

Does this work?

-Chris

Hi,

Okay. If the function exists in your application's address space already,
just name the LLVM function the same name as the native function and the
JIT should find it an do the right thing. This is how it finds printf and
a variety of other things. You don't need to call addGlobalMapping at
all.

Looking at the output of "nm codegen1" I realized that "get5" was a C++ function whose name was mangled to "__Z4get5v". Surrounding it by extern "C" helped a lot :slight_smile: Now the function is found by the JIT and I can call it using EE->runFunction als well as using a CallInst.

Does this work?

However, one strange effet remains: if I first call the function using EE->runFunction and then try to call it using a CallInst inside another function I get the old "relocation" error in PPCJITInfo.cpp, again. Using a CallInst first, then runFunction and then a CallInst again works, though. For my project this is probably a non-issue but it might indicate some problem in either my code or LLVM - any ideas?

Anyway, thank you all for the helpful comments. I'd surely yielded to despair without your help :slight_smile:

- Jan

Hi,

I was able to try this on linux again. Unfortunately it doesn't work function called get5 not known. Calling printf the same way works, though. On linux the function is exported as "get5" from the executable while it is called "_get5" on OS X. I could not spot any other differences.. any thoughts?

greetings,
Jan

Looking at the output of "nm codegen1" I realized that "get5" was a C+
+ function whose name was mangled to "__Z4get5v". Surrounding it by
extern "C" helped a lot :slight_smile: Now the function is found by the JIT and I
can call it using EE->runFunction als well as using a CallInst.

That would definitely do it :slight_smile:

Does this work?

However, one strange effet remains: if I first call the function
using EE->runFunction and then try to call it using a CallInst inside
another function I get the old "relocation" error in PPCJITInfo.cpp,
again. Using a CallInst first, then runFunction and then a CallInst
again works, though. For my project this is probably a non-issue but
it might indicate some problem in either my code or LLVM - any ideas?

There definitely is a bug here: your initial code should have worked. That said, noone has stepped up to fix it, and I'm busy with many other things. If you'd like to dig in and fix the bug, that would be excellent
:slight_smile:

-Chris

I was able to try this on linux again. Unfortunately it doesn't work
function called get5 not known. Calling printf the same way works,
though. On linux the function is exported as "get5" from the
executable while it is called "_get5" on OS X. I could not spot any
other differences.. any thoughts?

The _ shouldn't matter. You'll have to dig into the code a little bit and try to understand what is failing.

-Chris

Hi Jan,

In gcc for Linux, you have the -rdynamic option that allows an
executable to dlsym its symbols. Since llvm use dlsym to find the
symbols, you could try with this option. That's what I did. And don't
forget to use the C++ name if you compile with C++.

Cheers,
Nicolas

Jan Rehders wrote:

Hello,

finally I tried a bit more but could not get the native calls to work.

I checked how dlsym works on Linux and OS X. On the latter I can dlsym any function in a dynamically loaded library as well as in the application. On Linux only functions in loaded dlls can be retrieved using dlsym. Adding -rdynamic as Nicolas suggested allows me to load functions from the exe, too. The following code works fine on Linux and OS X:

void* dllHandle = dlopen( "./codegen1.so", RTLD_LAZY );
void* exeHandle = dlopen( NULL, RTLD_LAZY );

// int get5() is contained in the dll, get6 in the app (both using extern "C")
getAndCall( dllHandle, "get5" );
getAndCall( exeHandle, "get6" );

void getAndCall(void* handle, char* name) {
   int (*get)() = (int(*)()) dlsym( handle, name );

   if( dlerror() == NULL && get != NULL )
     printf( "Calling %s() = %d\n", name, get() );
}

However if I use LLVM like described in my previous mails it only works on OS X and cannot find the functions on Linux. This is both true for functions in the exe as well as in a loaded dll (passing -lfoo or foo.so to the linker). Using addGlobalMapping does not help, neither.

Until now the dlsym behavior is the only relevant difference I could spot between the two systems. However -dynamic should remove this difference. Still LLVM does not find any functions apart from printf in the system libs. My code is identical on both systems.

In gcc for Linux, you have the -rdynamic option that allows an
executable to dlsym its symbols. Since llvm use dlsym to find the
symbols, you could try with this option. That's what I did. And don't
forget to use the C++ name if you compile with C++.

Do you have a working code sample (with LLVM) which I could compare to my own code?

Currently I'm pretty much out of ideas. The next thing to try would probably to install a debug version of LLVM and dive into the source using a debugger. But I fear I don't have enough time for this. I had a short look at some of the functions to identify libraries but could not make too much sense of it, yet.

greetings,
Jan

Hi,

attached is a small testcase I did. It builds two LLVM functions which both call two native functions get5 and get6. The native functions are in the exe and in the dll. On OS X it works like a charm. On Linux none of the two functions can be called.

Maybe someone can try them or have a look at it to see if there is something obviously wrong

greetings,
Jan

codegen1.cpp (3.97 KB)

foo.c (29 Bytes)

makefile (1.88 KB)

Hi Jan,

If I recall correctly, in Linux you get the message:

PPCJITInfo.cpp:382: failed assertion `ResultPtr >= -(1 << 23) && ResultPtr < (1 << 23) && "Relocation out of range!"'

Right? But on OS X you don't have this messsage?

Here's a temporary fix until I find time to investigate on this:

In function PPCISelLowering::LowerCALL, comment the lines:

  if (GlobalAddressSDNode *G = dyn_cast<GlobalAddressSDNode>(Callee))
    Callee = DAG.getTargetGlobalAddress(G->getGlobal(), Callee.getValueType());

This will force an indirect call, and won't use the jump-size limitation of the bl instruction.

Let me know if this helps.

Cheers,
Nicolas

Jan Rehders wrote:

Hello, Nicolas.

This will force an indirect call, and won't use the jump-size limitation
of the bl instruction.

I think we're using some logic to determine, whether call should be
"direct" or "indirect" on x86. Look for GVRequiresExtraLoad() routine in
the X86Subtarget.cpp

Hi,

If I recall correctly, in Linux you get the message:

PPCJITInfo.cpp:382: failed assertion `ResultPtr >= -(1 << 23) &&
ResultPtr < (1 << 23) && "Relocation out of range!"'

Right? But on OS X you don't have this messsage?

Not exactly. There seem to be two problems. Your patch fixes one of them: in OS X I got the above error message when I first called a native function using EE->runFunction and the tried to call it again using a CallInst. This is fixed by your suggested patch, thank you

The other problem is that LLVM does not find any native functions in Linux/x86. When I try to call them using a CallInst I simply get a message telling me I tried to call a non-existing function and the abort()s. I can only call standard C functions like printf. When the function is provided by a loaded dll or its part of the .exe it is not found. The -rdynamic did not solve this either. However, the functions in question are found using dlsym. I currently have no idea how to solve this (see my previous message for various attempts and their results)

I will submit two bug reports when I receive my bugzilla password

Greetings,
Jan