LLVM JIT:通过 JIT 代码将 C++ 异常传递回主机应用程序

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

我正在开发一个项目,我使用 clang 生成一些 LLVM IR,然后在我的主机应用程序中进行 JIT 编译和运行。 JIT 代码调用宿主应用程序中的某些函数可能会引发异常。我希望通过 JIT 代码抛出异常并在主机应用程序中捕获。据我所知,这应该适用于 LLVM,但不幸的是,我的测试应用程序总是因 "terminate called after throwing an instance of 'int'" 而崩溃。让我举个简单的例子。

我使用 clang 3.5 将以下简单程序编译成 LLVM IR:

extern void test() ;

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

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

结果是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)"}

我的主机应用程序如下所示:

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;
}

我使用的是用 cmake 编译的 LLVM 3.5。我设置了 LLVM_ENABLE_EH=ON 和 LLVM_ENABLE_RTTI=ON。我是不是在编译 LLVM 时遗漏了什么,或者我的主机应用程序代码有误?

谢谢!

终于成功了,这里有一些解决问题所必需的东西。

首先确保包含 MCJIT.h 很重要,否则 MCJIT 不会被链接。不幸的是,如果 MCJIT.h 没有被包含,即使 MCJIT LLVM 也会默默地退回到旧的 JIT 实现已明确要求:

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

只有 MCJIT 支持适当的异常处理。

在问题的例子中我使用了

Execution::Engine::addGlobalMapping()

不适用于 MCJIT。必须通过

重新注册外部函数
llvm::sys::DynamicLibrary::AddSymbol()

完整示例如下:

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;
}

此外,您现在还可以通过添加以下内容来获取 JIT 代码的调试符号:

Opts.JITEmitDebugInfo = true;