Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LLVM JIT: pass C++ exception through JIT code back to host application

Tags:

c++

jit

llvm

I'm working on a project where I use clang to generate some LLVM IR and then JIT-compile and run it from within my host application. The JIT code calls some functions in the host application which may throw an exception. I expect the exception to be thrown through the JIT code and catched back in the host application. AFAIK this is supposed to work with LLVM, but unfortunately my test application always crashes with "terminate called after throwing an instance of 'int'". Let me give some simple example.

I use clang 3.5 to compile the following simple program into LLVM IR:

extern void test() ;

extern "C" void exec(void*) {
        test();
}

with

./clang -O0 -S -emit-llvm test.cpp -c

The result is test.ll

; ModuleID = 'test.cpp'
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

; Function Attrs: uwtable
define void @exec(i8*) #0 {
  %2 = alloca i8*, align 8
  store i8* %0, i8** %2, align 8
  call void @_Z4testv()
  ret void
}

declare void @_Z4testv() #1

attributes #0 = { uwtable "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.ident = !{!0}

!0 = metadata !{metadata !"clang version 3.5.0 (224841)"}

My host application looks like this:

static void test() {
    throw 1;
}

int main(int, const char **) {
    llvm::InitializeNativeTarget();
    llvm::InitializeNativeTargetAsmPrinter();
    llvm::InitializeNativeTargetAsmParser();

    llvm::LLVMContext &Context = llvm::getGlobalContext();
    llvm::SMDiagnostic Err;
    llvm::Module *Mod = llvm::ParseIRFile("test.ll", Err, Context);

    llvm::ExecutionEngine* m_EE = llvm::EngineBuilder(Mod)
            .setEngineKind(llvm::EngineKind::JIT)
            .create();

    llvm::Function* f = Mod->getFunction("_Z4testv");
    m_EE->addGlobalMapping(f, reinterpret_cast<void*>(test));

    f = Mod->getFunction("exec");

    void* poi = m_EE->getPointerToFunction(f);
    void (*exec)(void*) = reinterpret_cast<void (*)(void*)>(poi);

    try {
        exec(NULL);
    } catch (...) {
        std::cout << "catched exception" << std::endl;
    }

    return 0;
}

I use LLVM 3.5 which I compiled with cmake. I set LLVM_ENABLE_EH=ON and LLVM_ENABLE_RTTI=ON. Did I miss something when compiling LLVM or is my host application code wrong?

Thanks!

like image 368
SteffenG Avatar asked Jan 01 '15 14:01

SteffenG


1 Answers

Finally it works and here are a few things which are necessary to fix the issue.

First it's important to make sure MCJIT.h has been included, otherwise MCJIT is not linked in. Unfortunately LLVM silently falls back to the old JIT implementation if MCJIT.h has not been included even though MCJIT has been explicitly requested by:

llvm::EngineBuilder factory(Mod);
factory.setEngineKind(llvm::EngineKind::JIT);
factory.setUseMCJIT(true);

Only MCJIT supports propper exception handling.

In the example in the question I used

Execution::Engine::addGlobalMapping()

which does not work with MCJIT. External function must be reqistered via

llvm::sys::DynamicLibrary::AddSymbol()

Following the complete example:

static void test() {
    throw 1;
}

int main(int, const char **) {
    llvm::InitializeNativeTarget();
    llvm::InitializeNativeTargetAsmPrinter();
    llvm::InitializeNativeTargetAsmParser();

    llvm::LLVMContext &Context = llvm::getGlobalContext();
    llvm::SMDiagnostic Err;
    llvm::Module *Mod = llvm::ParseIRFile("test.ll", Err, Context);

    std::unique_ptr<llvm::RTDyldMemoryManager> MemMgr(new llvm::SectionMemoryManager());

    // Build engine with JIT
    std::string err;
    llvm::EngineBuilder factory(Mod);
    factory.setErrorStr(&err);
    factory.setEngineKind(llvm::EngineKind::JIT);
    factory.setUseMCJIT(true);
    factory.setMCJITMemoryManager(MemMgr.release());
    llvm::ExecutionEngine *m_EE = factory.create();

    llvm::sys::DynamicLibrary::AddSymbol("_Z4testv", reinterpret_cast<void*>(test));

    llvm::Function* f = Mod->getFunction("exec");

    m_EE->finalizeObject();

    void* poi = m_EE->getPointerToFunction(f);
    void (*exec)(void*) = reinterpret_cast<void (*)(void*)>(poi); 

    try {
        exec(NULL);
    } catch (int e) {
        std::cout << "catched " << e << std::endl;
    }
    return 0;
}

Additionally you can now also get Debug Symbols for the JIT code by adding:

Opts.JITEmitDebugInfo = true;
like image 149
SteffenG Avatar answered Oct 17 '22 02:10

SteffenG