ORCv2是LLVM14后唯一可用的JIT接口,需用KaleidoscopeJIT封装orc::ThreadSafeContext、ExecutionSession和JITDylib,显式注册符号、处理lookup错误并校验getAddress返回值。

orc v2 是当前唯一能直接上手的 LLVM JIT 接口
LLVM 14 之后,orc::LegacyJIT 和旧版 ExecutionEngine 已被移除,硬切到 orc::ThreadSafeContext + orc::ExecutionSession + orc::JITDylib 这套组合。网上大量基于 createMCJITCompilerForModule 或 getSymbolAddress 的示例全失效——不是编译不过,就是运行时段错误或符号找不到。
真正可用的最小闭环是:用 orc::KaleidoscopeJIT(LLVM 自带的参考实现)改写,它封装了 session、dylib、resource tracker 等细节,避免手动管理内存生命周期出错。
-
KaleidoscopeJIT不是玩具,它的设计就是为生产级 JIT 场景服务,支持增量添加函数、跨模块符号解析、懒编译(lazy compilation) - 别自己拼
ExecutionSession+IRCompileLayer+ObjectLinkingLayer,初始化顺序错一环就卡在addIRModule报std::bad_alloc或死锁 - 必须用
ThreadSafeContext包裹LLVMContext,否则多线程下parseIR可能崩溃,且orc::JITDylib内部会静默失败
编译 IR 模块前必须先 resolve 符号依赖
直接把含 printf、malloc 或自定义 C++ 函数调用的 IR 丢给 JIT,大概率触发 llvm::orc::SymbolsNotFound 异常,或执行时 SIGILL。JIT 不自动链接 libc 或你的程序符号——它只认你显式注册进去的东西。
解决方法不是“加个 -lc”,而是用 orc::DynamicLibrarySearchGenerator 把当前进程的符号表注入 JITDylib:
立即学习“C++免费学习笔记(深入)”;
auto dylib = jit.createJITDylib("main");
dylib->addGenerator(
orc::DynamicLibrarySearchGenerator::GetForCurrentProcess(
jit.getDataLayout().getGlobalPrefix()));
- 这一步必须在
addIRModule之前完成,否则后续模块里所有外部调用都查不到 - 如果调用的是你自己写的 C++ 函数(比如
int add(int, int)),得用dylib->define(orc::absoluteSymbols({{mangle("add"), JITEvaluatedSymbol::fromPointer(&add, JITSymbolFlags::Exported)}})) -
mangle("add")不能手写,要用orc::MangleAndInterner生成,C++ 符号名带 ABI 编码(如_Z3addii),写错就等于没注册
获取函数指针必须用 lookup + getAddress 两步走
很多人卡在最后一步:IR 编译成功、没报错,但调用 reinterpret_cast<int>(func_ptr)(42)</int> 直接 crash。问题出在:JIT 返回的不是裸地址,而是 Expected<jitevaluatedsymbol></jitevaluatedsymbol>,需要解包并检查是否可执行。
正确姿势是:
auto sym = jit.lookup("my_func");
if (!sym) {
// 处理 llvm::Error,比如打印 sym.takeError()
}
void *addr = sym->getAddress();
if (!addr) {
// getAddress() 返回 nullptr 表示符号存在但不可执行(比如被优化掉或未编译)
}
int (*f)(int) = reinterpret_cast<int(*)(int)>(addr);
- 漏掉
sym的Error检查,takeError()不调用会导致后续异常累积,在奇怪位置崩溃 -
getAddress()返回值必须判空,JIT 可能因内联、优化或 lazy 编译策略延迟生成机器码 - C++ 成员函数、模板实例、lambda 闭包无法直接 JIT 执行——它们依赖 this 指针或捕获环境,JIT 只处理纯 C 风格函数签名
调试段错误最该先看 ExecutionSession::setDispatchMaterialization
段错误不报具体行号,gdb 停在 __lldb_init_debugger 或随机地址?八成是 materialization(代码生成)阶段出问题:比如模块里用了未声明的全局变量、结构体布局和 JIT 假设不一致、或者 DataLayout 没对齐宿主平台。
开启 materialization 日志能快速定位卡点:
jit.getExecutionSession().setDispatchMaterialization(true);
- 这会让 JIT 在每次触发编译时打印 “Compiling module X for symbol Y”,看到某符号卡住,就知道问题出在对应 IR 的生成逻辑
- 常见陷阱:
IRBuilder创建的AllocaInst没设 alignment,或StructType字段顺序与 C++struct不一致,导致栈帧错位 - 别依赖
dump()查 IR——它不反映 JIT 实际看到的优化后 IR;用jit.dump()(如果启用了 debug 构建)或llvm-dis反汇编生成的 object 文件更可靠
LLVM JIT 的复杂性不在语法,而在它把编译器内部状态(context、session、dylib、tracker)全暴露给你管。少一个 ThreadSafeContext、漏一次 lookup 错误处理、或符号名 mangling 差一个字符,都会让整个链条静默失败。动手前先跑通 llvm/examples/Kaleidoscope/BuildingAJIT 里的 Chapter2,比读文档快十倍。



















