LLVM 14+ 必须用 ORC JIT(如 LLJIT),旧 ExecutionEngine 已移除;硬绕 ORC 会因缺符号解析、内存保护等导致 SIGSEGV;最简可行方案是基于 orc::KaleidoscopeJIT 的三层架构,需显式注册系统库并注意目标平台匹配。

直接用 LLVM Core 实现“简单 JIT”不是写几个函数就能跑起来的事——LLVMExecutionEngine 在 14+ 版本已移除,orc::ThreadSafeContext 和 orc::KaleidoscopeJIT 是当前唯一受支持路径,硬绕开 ORC 会卡在符号解析或内存保护上。
为什么不能只用 llvm::IRBuilder + llvm::ExecutionEngine
LLVM 14 起彻底删除了旧式 ExecutionEngine API;即使降级到 LLVM 12,createJITCompilerForModule 也早已标记为 deprecated。你写的 IR 可以生成,但没 ORC 的 symbol resolver、object layer、compile layer,getSymbolAddress 必然返回 0,调用时 SIGSEGV。
- 旧教程里常见的
EngineBuilder+create()在 LLVM 15 中编译不过 -
llvm::sys::DynamicLibrary::LoadLibraryPermanently(nullptr)不再自动暴露主机符号,必须显式注册 - 未启用
-DLLVM_USE_SANITIZER=Address构建的 LLVM,JIT 内存页默认不可执行(mprotect拒绝PROT_EXEC)
最简可行 JIT:基于 orc::KaleidoscopeJIT 的三步链
官方 Kaleidoscope 教程第4章的 JIT 模板仍是目前最轻量、可直接复用的起点。它把 JIT 拆成三层:内存管理(orc::ObjectLinkingLayer)、编译调度(orc::IRCompileLayer)、符号解析(orc::SymbolResolver)。你不需要全写,只需继承并微调。
#include "llvm/ExecutionEngine/Orc/CompileUtils.h"
#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h"
#include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h"
#include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h"
#include "llvm/ExecutionEngine/Orc/ThreadSafeModule.h"
#include "llvm/ExecutionEngine/Orc/LLJIT.h"
#include "llvm/IR/IRBuilder.h"
auto jit = llvm::orc::LLJITBuilder().create();
if (!jit) {
// handle error
}
// 注册 C 标准库符号(否则 printf 报 unresolved)
jit->getMainJITDylib().addGenerator(
std::make_unique(
llvm::sys::DynamicLibrary::getPermanentLibrary(nullptr)));
// 创建模块并插入函数
auto &ctx = jit->getContext();
auto module = std::make_unique("jit_module", ctx);
module->setTargetTriple(llvm::sys::getDefaultTargetTriple());
llvm::IRBuilder<> builder(llvm::Type::getInt32Ty(ctx));
auto funcTy = llvm::FunctionType::get(builder.getInt32Ty(), false);
auto func = llvm::Function::Create(funcTy, llvm::Function::ExternalLinkage,
"add_one", module.get());
auto bb = llvm::BasicBlock::Create(ctx, "entry", func);
builder.SetInsertPoint(bb);
builder.CreateRet(builder.CreateAdd(builder.getInt32(42), builder.getInt32(1)));
jit->addIRModule(llvm::orc::ThreadSafeModule(std::move(module), ctx));
// 获取函数指针并调用
auto addr = jit->lookup("add_one");
if (addr) {
auto f = reinterpret_cast(addr.getValue());
int result = f(); // 返回 43
}
LLJIT 初始化失败的三个高频原因
多数人卡在 LLJITBuilder().create() 返回 ErrorCode,而不是后续调用阶段。核心是目标平台和运行时环境不匹配。
立即学习“C++免费学习笔记(深入)”;
-
JITTargetMachineBuilder::detectHost()失败 → 确保LLVM_TARGETS_TO_BUILD="host"编译 LLVM,且运行机器架构(x86_64/aarch64)与构建时一致 -
DynamicLibrarySearchGenerator传nullptr后仍找不到printf→ macOS 需额外链接-lSystem,Linux 需确保LD_LIBRARY_PATH包含libc.so.6所在路径 -
addIRModule报 “symbol ‘main’ already defined” → 检查是否重复调用addIRModule或模块里误声明了main函数(JIT 模块不能有main)
ORC 的设计哲学是“每个组件可替换”,但入门时别急着自定义 ObjectLinkingLayer —— 先让 LLJIT 跑通,再逐步替换 IRCompileLayer 为 ConcurrentIRCompiler 或接入自己的 object cache。真正的复杂点不在 IR 构建,而在符号生命周期管理和跨模块调用时的 MaterializationResponsibility 分发逻辑。











