Adding an Object File to JIT

I created a modified version of HowToUseJIT.cpp (llvm version 11.x) that uses IRBuilder class to build a function that calls an external defined in an shared object file.

This example works fine (on my system) when the external has an int argument and return value, but it fails when the argument and return value are double .

The Source for the int case is included below. In addition, the source has instructions, at the top, for transforming it to the double case.

/*
This file is a modified version of examples/HowToUseJIT/HowToUseJIT.cpp:

The file callee.c contains the following text:
    int callee(int arg)
    {   return arg + 1; }

The shared library callee.so is created from callee.c as follows:
    clang -shared callee.c -o callee.so

This example calls the funciton callee from a function that is generated using 
the IRBuilder class. It links callee by loading callee.so into its LLJIT.

This works on my sytesm where the progam output is
    add1(42) = 43
which is correct.

If I change the type of the function callee from "int (*)(int)" to 
"double (*)(double)", the program output is
    add1(42) = 4.200000e+01
which is incorrect.

I use following command to change callee.c so that is uses double:
    sed -i callee.c \
        -e 's|int callee(int arg)|double callee(double arg)|' \
        -e 's|return arg + 1;|return arg + 1.0;|'

I use the following command to change this file so that is should porperly 
link to the double version of callee:
    sed -i add_obj2jit.cpp \
        -e '30,$s|"int"|"double"|' \
        -e '30,$s|getInt32Ty|getDoubleTy|g' \
        -e '/getAddress/s|int|double|g' \
        -e 's|int Result = Add1(42);|double Result = Add1(42.0);|

What is wrong with the double version of this example ?
*/
#include "llvm/ExecutionEngine/Orc/LLJIT.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;
using namespace llvm::orc;

ExitOnError ExitOnErr;

// --------------------------------------------------------------------------
void add_obj2jit(LLJIT* jit, const std::string filename)
{   // load object file into memory_buffer
    ErrorOr< std::unique_ptr<MemoryBuffer> > error_or_buffer =
        MemoryBuffer::getFile(filename);
    std::error_code std_error_code = error_or_buffer.getError();
    if( std_error_code )
    {   std::string msg = "add_obj2jit: " + filename + "\n";
        msg            += std_error_code.message();   
        std::fprintf(stderr, "%s\n", msg.c_str() );
        std::exit( std_error_code.value() );
    }
    std::unique_ptr<MemoryBuffer> memory_buffer( 
        std::move( error_or_buffer.get() )
    );
    // move object file into jit
    Error error = jit->addObjectFile( std::move(memory_buffer) );
    if( error )
    {   std::fprintf(stderr, "Can't load object file %s", filename.c_str());
        std::exit(1);
    }
}
// --------------------------------------------------------------------------
ThreadSafeModule createDemoModule() {
  auto Context = std::make_unique<LLVMContext>();
  auto M = std::make_unique<Module>("test", *Context);

  // functiont_t
  // function has a return type of "int" and take an argument of "int".
  FunctionType* function_t = FunctionType::get(
    Type::getInt32Ty(*Context), {Type::getInt32Ty(*Context)}, false
  );

  // declare the callee function
  AttributeList empty_attributes;
  FunctionCallee callee = M->getOrInsertFunction(
    "callee", function_t, empty_attributes
  );

  // Create the add1 function entry and insert this entry into module M.  
  Function *Add1F = Function::Create(
    function_t, Function::ExternalLinkage, "add1", M.get()
  );

  // Add a basic block to the function. As before, it automatically inserts
  // because of the last argument.
  BasicBlock *BB = BasicBlock::Create(*Context, "EntryBlock", Add1F);

  // Create a basic block builder with default parameters.  The builder will
  // automatically append instructions to the basic block `BB'.
  IRBuilder<> builder(BB);

  // Get pointers to the integer argument of the add1 function...
  assert(Add1F->arg_begin() +1 == Add1F->arg_end()); // Make sure there's an arg
  Argument *ArgX = &*Add1F->arg_begin();          // Get the arg
  ArgX->setName("AnArg"); // Give it a nice symbolic name for fun.

  // Create the call instruction, inserting it into the end of BB.
  Value *Add = builder.CreateCall( callee, {ArgX}, "Add=callee(ArgX)" );

  // Create the return instruction and add it to the basic block
  builder.CreateRet(Add);

  return ThreadSafeModule(std::move(M), std::move(Context));
}
// --------------------------------------------------------------------------
int main(int argc, char *argv[]) {
  // Initialize LLVM.
  InitLLVM X(argc, argv);

  InitializeNativeTarget();
  InitializeNativeTargetAsmPrinter();

  cl::ParseCommandLineOptions(argc, argv, "add_obj2jit");
  ExitOnErr.setBanner(std::string(argv[0]) + ": ");

  // Create an LLJIT instance.
  auto J = ExitOnErr(LLJITBuilder().create());
  auto M = createDemoModule();

  ExitOnErr(J->addIRModule(std::move(M)));

  add_obj2jit(J.get(), "callee.so");

  // Look up the JIT'd function, cast it to a function pointer, then call it.
  auto Add1Sym = ExitOnErr(J->lookup("add1"));
  int (*Add1)(int) = (int (*)(int))Add1Sym.getAddress();

  int Result = Add1(42);
  outs() << "add1(42) = " << Result << "\n";

  // return error number
  if( Result != 43 )
    return 1;
  return 0;
}
1 Like

There was a mistake in the second sed command in the comments of the example. Adding the missing single quote at the end, properly edited the file example file and fixed the problem.

For completeness of this as an example, I add the line

    llvm::outs() << *M;

just before the line

   return ThreadSafeModule(std::move(M), std::move(Context));

The corresponding output for the int case is:

; ModuleID = 'test'
source_filename = "test"

declare i32 @callee(i32)

define i32 @add1(i32 %AnArg) {
EntryBlock:
  %0 = call i32 @callee(i32 %AnArg)
  ret i32 %0
}
add1(42) = 43

The output for the dobule case is

; ModuleID = 'test'
source_filename = "test"

declare double @callee(double)

define double @add1(double %AnArg) {
EntryBlock:
  %0 = call double @callee(double %AnArg)
  ret double %0
}
add1(42) = 4.300000e+01

There was another mistake in the comments of the example,

clang -shared callee.c -o callee.so

must be repalced by

clang -c callee.c -o callee.so

for he example to work. For some reason addObjectFile does not seem to work with shared object files ?