LLVM 开发指南 / 第 10 章:JIT 编译
第 10 章:JIT 编译
“JIT 编译让你在运行时生成和执行机器码,如同代码会自我进化。”
10.1 JIT 编译概述
10.1.1 什么是 JIT?
JIT(Just-In-Time)编译是在程序运行时将 IR 或字节码编译为机器码并执行的技术。
AOT (Ahead-of-Time) 编译:
源码 → [编译器] → 可执行文件 → [运行]
JIT (Just-In-Time) 编译:
源码/IR → [运行时] → 机器码 → [立即执行]
↑
编译和执行在同一进程中
10.1.2 JIT 的应用场景
| 场景 | 说明 | 示例 |
|---|---|---|
| 交互式解释器 | REPL 环境 | Julia, Cling |
| 数据库查询 | 动态生成查询计划 | Spark, DuckDB |
| 游戏引擎 | 运行时脚本编译 | Unity (IL2CPP) |
| 机器学习 | 动态计算图 | PyTorch (TorchScript) |
| 编译器开发 | 动态编译 DSL | MLIR JIT |
| 单元测试 | 测试 LLVM IR 生成 | LLVM 测试套件 |
10.1.3 LLVM JIT 框架演进
| 框架 | 状态 | 说明 |
|---|---|---|
| MCJIT | 已废弃 | LLVM 3.5 引入,旧框架 |
| ORC JIT v1 | 已废弃 | LLVM 3.7+,Object-Linking |
| ORC JIT v2 | ✅ 推荐 | LLVM 9+,当前推荐方案 |
10.2 ORC JIT 基础
10.2.1 核心概念
| 概念 | 说明 |
|---|---|
| JITDylib | 符号表,类似动态库 |
| MaterializationUnit | 懒编译单元 |
| ExecutionSession | JIT 会话管理 |
| IRCompileLayer | IR 编译层 |
| ObjectLayer | 目标文件链接层 |
| LLJIT | 简化的 JIT API |
10.2.2 最简单的 ORC JIT 示例
// simple_jit.cpp
#include "llvm/ExecutionEngine/Orc/LLJIT.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/Error.h"
#include <iostream>
using namespace llvm;
using namespace llvm::orc;
int main() {
// 1. 初始化 LLVM 目标
InitializeNativeTarget();
InitializeNativeTargetAsmPrinter();
InitializeNativeTargetAsmParser();
// 2. 创建 LLJIT 实例
auto JIT = LLJITBuilder().create();
if (!JIT) {
errs() << "创建 JIT 失败: " << toString(JIT.takeError()) << "\n";
return 1;
}
// 3. 创建 LLVM 模块
auto Ctx = std::make_unique<LLVMContext>();
auto M = std::make_unique<Module>("jit_module", *Ctx);
// 4. 生成 IR: int add(int a, int b) { return a + b; }
IRBuilder<> Builder(*Ctx);
FunctionType *FuncType = FunctionType::get(
Builder.getInt32Ty(),
{Builder.getInt32Ty(), Builder.getInt32Ty()},
false
);
Function *Func = Function::Create(
FuncType, Function::ExternalLinkage, "add", M.get()
);
BasicBlock *Entry = BasicBlock::Create(*Ctx, "entry", Func);
Builder.SetInsertPoint(Entry);
Value *Sum = Builder.CreateAdd(Func->getArg(0), Func->getArg(1), "sum");
Builder.CreateRet(Sum);
// 5. 将模块添加到 JIT
auto TSM = ThreadSafeModule(std::move(M), std::move(Ctx));
if (auto E = (*JIT)->addIRModule(std::move(TSM))) {
errs() << "添加模块失败: " << toString(std::move(E)) << "\n";
return 1;
}
// 6. 查找并调用函数
auto Addr = (*JIT)->lookup("add");
if (!Addr) {
errs() << "查找函数失败: " << toString(Addr.takeError()) << "\n";
return 1;
}
// 7. 转换为函数指针并调用
auto *AddFunc = Addr->toPtr<int(int, int)>();
int Result = AddFunc(3, 4);
std::cout << "add(3, 4) = " << Result << "\n";
return 0;
}
# 编译
clang++ -std=c++17 simple_jit.cpp -o simple_jit \
$(llvm-config --cxxflags --ldflags --libs core native orcjit support)
# 运行
./simple_jit
# 输出: add(3, 4) = 7
10.3 ORC JIT 进阶
10.3.1 从 LLVM IR 文件 JIT
// file_jit.cpp
#include "llvm/ExecutionEngine/Orc/LLJIT.h"
#include "llvm/IRReader/IRReader.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/TargetSelect.h"
using namespace llvm;
using namespace llvm::orc;
int main(int argc, char **argv) {
if (argc < 2) {
errs() << "用法: " << argv[0] << " <input.ll>\n";
return 1;
}
InitializeNativeTarget();
InitializeNativeTargetAsmPrinter();
InitializeNativeTargetAsmParser();
auto JIT = ExitOnErr(LLJITBuilder().create());
// 加载 LLVM IR 文件
auto Ctx = std::make_unique<LLVMContext>();
SMDiagnostic Err;
auto M = parseIRFile(argv[1], Err, *Ctx);
if (!M) {
Err.print(argv[0], errs());
return 1;
}
// 添加模块
ExitOnErr(JIT->addIRModule(
ThreadSafeModule(std::move(M), std::move(Ctx))));
// 查找 main 函数
auto MainAddr = ExitOnErr(JIT->lookup("main"));
// 调用
auto *MainFunc = MainAddr.toPtr<int()>();
int Result = MainFunc();
outs() << "main() = " << Result << "\n";
return 0;
}
10.3.2 添加外部符号
// 注册外部函数,使 JIT 代码可以调用
auto &MainJD = JIT->getMainJITDylib();
// 方式一:使用符号映射
MainJD.addGenerator(
cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess(
JIT->getDataLayout().getGlobalPrefix())));
// 方式二:手动注册符号
SymbolMap M;
M[Mangle("printf")] = {
pointerToJITTargetAddress(&printf),
JITSymbolFlags::Exported
};
M[Mangle("my_custom_func")] = {
pointerToJITTargetAddress(&my_custom_func),
JITSymbolFlags::Exported
};
ExitOnErr(MainJD.define(absoluteSymbols(std::move(M))));
10.3.3 懒编译 (Lazy Compilation)
// 懒编译:函数在首次调用时才编译
#include "llvm/ExecutionEngine/Orc/CompileOnDemandLayer.h"
auto JIT = ExitOnErr(LLJITBuilder()
.setCompileFunctionCreator([](JITTargetMachineBuilder TM)
-> Expected<std::unique_ptr<IRCompileLayer::IRCompiler>> {
return std::make_unique<TMOwningSimpleCompiler>(std::move(TM));
})
.setLazyCompile(true) // 启用懒编译
.create());
10.4 Kaleidoscope JIT
LLVM 教程中的 Kaleidoscope 语言使用 JIT 实现 REPL:
// 简化的 REPL 循环
while (true) {
std::cout << "ready> ";
getNextToken();
switch (CurTok) {
case tok_eof:
return 0;
case ';': // 忽略分号
getNextToken();
continue;
case tok_def: // 函数定义
HandleDefinition();
break;
case tok_extern: // 外部声明
HandleExtern();
break;
default: // 表达式
HandleTopLevelExpression();
break;
}
}
// 处理顶层表达式 — JIT 编译并执行
void HandleTopLevelExpression() {
auto F = ParseTopLevelExpr();
if (F) {
auto *CF = TheJIT->compileExpr(std::move(F));
double Result = CF(); // JIT 编译并执行!
std::cout << "结果: " << Result << "\n";
}
}
10.5 MCJIT(已废弃)
注意: MCJIT 已废弃,仅供了解历史。新代码请使用 ORC JIT。
// MCJIT 示例(仅供参考)
#include "llvm/ExecutionEngine/MCJIT.h"
#include "llvm/ExecutionEngine/SectionMemoryManager.h"
// 创建 MCJIT 引擎
auto EE = EngineBuilder(std::move(M))
.setEngineKind(EngineKind::JIT)
.setMCJITMemoryManager(std::make_unique<SectionMemoryManager>())
.create();
// 获取函数地址并调用
auto *Func = (int(*)())EE->getPointerToFunction(F);
int Result = Func();
10.6 JIT 事件监听
#include "llvm/ExecutionEngine/JITEventListener.h"
// 注册 GDB JIT 接口(调试器支持)
EE->registerJITEventListener(
JITEventListener::createGDBRegistrationListener());
// 注册 perf 事件监听(性能分析支持)
EE->registerJITEventListener(
JITEventListener::createPerfJITEventListener());
// 自定义事件监听
class MyListener : public JITEventListener {
void notifyObjectLoaded(ObjectKey K, const object::ObjectFile &Obj,
const RuntimeDyld::LoadedObjectInfo &L) override {
outs() << "JIT 加载对象: " << Obj.getFileName() << "\n";
}
void notifyFreeingObject(ObjectKey K) override {
outs() << "JIT 释放对象: " << K << "\n";
}
};
10.7 JIT 调试
# JIT 调试技巧
# 1. 查看生成的汇编代码
# 设置环境变量输出 JIT 编译结果
LLVM_JIT_DUMP=1 ./my_jit_program
# 2. 使用 perf 查看 JIT 函数
perf record ./my_jit_program
perf report # JIT 函数应该可见
# 3. 禁用 JIT 优化(调试)
# 在构建 LLJIT 时不设置优化层
# 4. 打印 IR
M->print(outs(), nullptr); // 在添加到 JIT 前打印
10.8 本章小结
| 概念 | 要点 |
|---|---|
| ORC JIT | 推荐的 JIT 框架 |
| LLJIT | 简化的 JIT API |
| JITDylib | 符号表 |
| 懒编译 | 首次调用时编译 |
| 外部符号 | 动态库符号注入 |
| MCJIT | 已废弃,不要使用 |
扩展阅读
- ORC JIT Design — ORC JIT 设计文档
- Kaleidoscope Tutorial — JIT 教程
- Building a JIT — 构建 JIT 系列教程
- ORC API Reference — API 参考
下一章: 第 11 章:MC 层 — 学习 LLVM MC 层的汇编器、反汇编器和目标文件生成。