如何在c++中实现一个简单的JIT编译器? (LLVM Core入门)

12次阅读

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

如何在c++中实现一个简单的JIT编译器? (LLVM Core入门)

直接用 LLVM Core 实现“简单 JIT”不是写几个函数就能跑起来的事——LLVMExecutionEngine 在 14+ 版本已移除,orc::ThreadSafeContextorc::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)与构建时一致
  • DynamicLibrarySearchGeneratornullptr 后仍找不到 printfmacOS 需额外链接 -lSystemlinux 需确保 LD_LIBRARY_PATH 包含 libc.so.6 所在路径
  • addIRModule 报 “symbol ‘main’ already defined” → 检查是否重复调用 addIRModule 或模块里误声明了 main 函数(JIT 模块不能有 main

ORC 的设计哲学是“每个组件可替换”,但入门时别急着自定义 ObjectLinkingLayer —— 先让 LLJIT 跑通,再逐步替换 IRCompileLayerConcurrentIRCompiler 或接入自己的 object cache。真正的复杂点不在 IR 构建,而在符号生命周期管理和跨模块调用时的 MaterializationResponsibility 分发逻辑。

text=ZqhQzanResources