Edit
I'm basically trying to do this but with llvm's orc
Jit api (llvm-13)
I have a library that JIT's some code using llvm (13). I have some functions in that library that I want to make available to the JIT without writing them in LLVM IR.
Here's some code:
#include "llvm/Analysis/AliasAnalysis.h"
#include "llvm/ExecutionEngine/JITSymbol.h"
#include "llvm/ExecutionEngine/Orc/CompileUtils.h"
#include "llvm/ExecutionEngine/Orc/Core.h"
#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h"
#include "llvm/ExecutionEngine/Orc/IRCompileLayer.h"
#include "llvm/ExecutionEngine/Orc/IRTransformLayer.h"
#include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h"
#include "llvm/ExecutionEngine/Orc/Mangling.h"
#include "llvm/ExecutionEngine/Orc/LLJIT.h"
#include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/ExecutionEngine/Orc/ExecutorProcessControl.h"
#include "llvm/ExecutionEngine/SectionMemoryManager.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/IRReader/IRReader.h"
#include "llvm/Support/TargetSelect.h"
using namespace llvm;
using namespace llvm::orc;
// this is just a demo module that creates a function that adds 1 to an int
ThreadSafeModule makeSimpleModule() {
auto Context = std::make_unique<LLVMContext>();
auto M = std::make_unique<Module>("test", *Context);
// Create the add1 function entry and insert this entry into module M. The
// function will have a return type of "int" and take an argument of "int".
Function *Add1F =
Function::Create(FunctionType::get(Type::getInt32Ty(*Context),
{Type::getInt32Ty(*Context)}, false),
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 constant `1'.
Value *One = builder.getInt32(1);
// Get pointers to the integer argument of the add1 function...
assert(Add1F->arg_begin() != 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 add instruction, inserting it into the end of BB.
Value *Add = builder.CreateAdd(One, ArgX);
// Create the return instruction and add it to the basic block
builder.CreateRet(Add);
return {std::move(M), std::move(Context)};
}
// this represents a function in my library that I want to make available to the JIT.
namespace mylibsubnamespace {
extern "C" {
int add2(int a) {
return a + 2;
}
}
}
int main(int argc, const char *argv[]) {
// do some JIT initialization
llvm::InitLLVM X(argc, argv);
llvm::InitializeNativeTarget();
llvm::InitializeNativeTargetAsmPrinter();
llvm::InitializeNativeTargetAsmParser();
// Create an LLJIT instance.
auto J = LLJITBuilder().create();
// this code seems to enable symbol resolution for when the missing symbol is
// in the standard C library (and presumably included).
// This is what allows the "cos" function below to work (comment it out and we get a seg fault)
auto DLSG = llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess(
(*J)->getDataLayout().getGlobalPrefix());
if (!DLSG) {
llvm::logAllUnhandledErrors(
std::move(DLSG.takeError()),
llvm::errs(),
"DynamicLibrarySearchGenerator not built successfully"
);
}
(*J)->getMainJITDylib().addGenerator(std::move(*DLSG));
auto M = makeSimpleModule();
(*J)->addIRModule(std::move(M));
// Look up the JIT'd function, cast it to a function pointer, then call it.
// This function is written in LLVM IR directly.
auto Add1Sym = (*J)->lookup("add1");
int (*Add1)(int) = (int (*)(int)) Add1Sym->getAddress();
int Result = Add1(42);
outs() << "add1(42) = " << Result << "\n";
// Look up the JIT'd function, cast it to a function pointer, then call it.
// This function is defined in the standard C library. Its symbol is resolved
// by DynamicLibrarySearchGenerator above
auto CosSym = (*J)->lookup("cos");
double (*Cos)(double) = (double (*)(double)) CosSym->getAddress();
outs() << "Cos(50) = " << Cos(50) << "\n";
So far so good. What I haven't been able to work out is how to make the add2
function available in a cachable way. I have successfully been able to follow the instructions here to enable hardcoding of the address in the current session, like so:
auto symbolStringPool = (*J)->getExecutionSession().getExecutorProcessControl().getSymbolStringPool();
orc::SymbolStringPtr symbPtr = symbolStringPool->intern("add2");
// JITTargetAddress is uint64 typedefd
llvm::JITSymbolFlags flg;
llvm::JITEvaluatedSymbol symb((std::int64_t) &mylibsubnamespace::add2, flg);
if (llvm::Error err = (*J)->getMainJITDylib().define(
llvm::orc::absoluteSymbols({{symbPtr, symb}}))) {
llvm::logAllUnhandledErrors(std::move(err), llvm::errs(), "Could not add symbol add2");
}
But the instructions explicitly advice against this strategy, since symbols resolved this way are not cachable. However, resolving the symbol with something like how the instructions suggest:
JD.addGenerator(DynamicLibrarySearchGenerator::Load("/path/to/lib"
DL.getGlobalPrefix()));
isn't possible because there is no /path/to/lib
. What is the normal way to handle such situations?