跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

c++ jit即时编译 c++如何使用llvm orc jit编译和执行代码

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(func_ptr)(42)
直接 crash。问题出在:JIT 返回的不是裸地址,而是
Expected
,需要解包并检查是否可执行。 C知道 CSDN推出的一款AI技术问答工具 下载 正确姿势是:
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(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,比读文档快十倍。

相关文章