JIT其实就是Just-In-Time也就是即时编译,在程序运行的时候会将代码翻译成机器码并且去执行,与之相对的就是AOT(Ahead Of Time),它在程序运行之前就会将代码翻译成机器码,JIT结合了AOT和解释执行的优势,它能够产生高效的机器码,并且具备足够的灵活性
首先我们定义一个执行引擎作为全局静态变量
static ExecutionEngine *TheExecutionEngine;
然后就是在main函数当中增加如下代码
//InitializeNativeTarget——主程序应该调用此函数来初始化与主机对应的本机目标。这对于JIT应用程序非常有用,以确保目标被正确地链接。客户端对这个函数进行多次调用是合法的。
InitializeNativeTarget();
//主程序应该调用此函数来初始化本机目标asm打印机
InitializeNativeTargetAsmPrinter();
//InitializeNativeTargetAsmParser——主程序应该调用这个函数来初始化本机目标asm解析器。
InitializeNativeTargetAsmParser();
还有下面的代码
std::unique_ptr<Module> Owner = make_unique<Module>("my compiler", *llvmcx);
Module_Ob = Owner.get();
std::string ErrStr;
TheExecutionEngine =
EngineBuilder(std::move(Owner))
.setErrorStr(&ErrStr)
.setMCJITMemoryManager(llvm::make_unique<SectionMemoryManager>())
.create();
最后修改顶级表达式的解析器,修改为如下所示
//封装的函数
static void HandleTopExpression() {
if(FunctionDefnAST *F = top_level_parser()) {
if(Function *LF = F->Codegen()) {
//确保模块已被完全处理并可用
TheExecutionEngine->finalizeObject();
//实现这个来说明函数指针应该是什么样子。当参数被破坏时
//ExecutionEngine将删除它的全局映射并释放任何机器代码
//当这种情况发生时,请确保没有线程在F中运行
void *FPtr = TheExecutionEngine->getPointerToFunction(LF);
double (*FP)() = (double (*)())(intptr_t)FPtr;
fprintf(stderr, "Evaluated to %f\n", FP());
}
}
else {
next_token();
}
}
去分析4+5;的表达式,得到的结果如下所示
Evaluated to 0.000000
; ModuleID = 'my compiler'
source_filename = "my compiler"
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
define i32 @0() {
entry:
ret i32 9
}
LLVM当中的JIT编译器会匹配本机的ABI平台,它会把得到的指针转成相应类型的函数指针,然后直接去调用,这样JIT编译得到的代码与静态编译链接的本地机器码没有区别
关于ABI平台其实就是应用程序二进制接口(ABI-Application Binary Interface)定义了一组在PowerPC系统软件上编译应用程序所需要遵循的一套规则。主要包括基本数据类型,通用寄存器的使用,参数的传递规则,以及堆栈的使用等等
ABI涵盖了各种细节:如数据类型、大小和对齐;调用约定(控制着函数的参数如何传送以及如何接受返回值);系统调用的编码和一个应用如何向操作系统进行系统调用;以及在一个完整的操作系统ABI中,目标文件的二进制格式、程序库等等。一个完整的ABI,像Intel二进制兼容标准 (iBCS)[1] ,允许支持它的操作系统上的程序不经修改在其他支持此ABI的操作体统上运行