1

I suspect that this will end up linker related, but looking for any suggestions.

Have a simple C++ code that loads an llvm IR file as a module, compiles it and executes a function from that module. IR module conversely declares a C function from the main executable as an external, and calls that one.

This works fine on MacOS (llvm 12).

On FreeBSD the code crashes when IR function is invoked.

If I move the C function into a shared library, and link with it - IR code is able to run and invoke the function from back in the main program.

I suspect that something about FreeBSD runtime linker makes function in the program itself unavailable to the IR code. Any suggestions on how to remedy that appreciated.

Sample C++ code (skipped includes):

extern "C" {
   int test_fun(int arg)
   {
      printf("Super test fun %d\n", arg);
      return arg + 1;
   }
}

int main(int ac, char **av)
{
   InitializeNativeTarget();
   InitializeNativeTargetAsmPrinter();
   InitializeNativeTargetAsmParser();

   llvm::LLVMContext ctx;
   llvm::SMDiagnostic diag;

   auto M = parseIRFile( av[1], diag, ctx );
   printf("Module %llx\n", (uint64_t)M.get());

   std::string err;
   llvm::EngineBuilder EB(std::move(M));
   EB.setEngineKind(llvm::EngineKind::JIT).setErrorStr(&err);
   llvm::ExecutionEngine* EE = EB.create();
   if (!EE) return 0;
   EE->finalizeObject();

   const auto fa = (int(*)())EE->getFunctionAddress(av[2]);
   printf("IR fun '%s' returned %d\n", av[2], fa());

   return 0;
}

Sample IR code:

declare i32 @test_fun(i32)
define i32 @test_ll() {
    %v1 = mul i32 4, 5
    %v2 = add i32 3, %v1
    %v3 = call i32(i32) @test_fun(i32 %v2)
    ret i32 %v3
}
mrx
  • 21
  • 2
  • getFunctionAddress may return a null pointer when it can not find the symbol, I guess that's the reason it crashes. Could you please double-check you have correctly compiled and link the specific target for your FreeBSD machine in LLVM_TARGETS_TO_BUILD? – 西风逍遥游 Oct 09 '21 at 06:21
  • The code here is cleaned up, some of the debugging code is removed. I verified that the function pointer for the IR module function "test_ll" is returned correctly and is not null. What makes a difference is whether or not the IR code calls back to the C function in the main file. If it does - there is a crash (on FreeBSD but not MacOS). I built llvm12 using FreeBSD ports, so I am assuming that it selected the correct target - but will check. – mrx Oct 09 '21 at 15:16

1 Answers1

1

Ok, so as I suspected it's a linker issue. The IR compiled code behaves like a shared library. There is a question here on stackoverflow that asks that, and an (extremely underrated) correct answer there.

Will copy it here:

gcc -Wl,--export-dynamic

So, adding that option on the compiler/linker command line results in main executable symbols being exported in the way that makes it possible for jit compiled IR code to link to them.

Documentation for this option explains why this works:

-E
--export-dynamic
--no-export-dynamic

    When creating a dynamically linked executable, using the -E option or the --export-dynamic option causes the linker to add all symbols to the dynamic symbol table. The dynamic symbol table is the set of symbols which are visible from dynamic objects at run time.

    If you do not use either of these options (or use the --no-export-dynamic option to restore the default behavior), the dynamic symbol table will normally contain only those symbols which are referenced by some dynamic object mentioned in the link.

    If you use dlopen to load a dynamic object which needs to refer back to the symbols defined by the program, rather than some other dynamic object, then you will probably need to use this option when linking the program itself. 
mrx
  • 21
  • 2