基于MIR(中等内部表示)的轻量级JIT编译器

2020-12-06 20:01:51

MIR项目的目标是为实现快速,轻量级的口译员和JIT提供基础

该代码处于开发的初始阶段。仅用于熟悉项目。绝对没有保证将来不会更改MIR,并且该代码适用于任何测试,除了此处给出的测试以及在x86_64 Linux / OSX和aarch64 / ppc64be / ppc64le / s390x Linux以外的平台上进行的测试

MIR由模块组成每个函数都有签名(参数和返回类型),局部变量(包括函数参数)和指令每个局部变量的类型只能是64位整数,float,double或long double

内存操作数具有类型,位移,基数和索引整数局部变量,以及整数常数作为索引的小数位。内存类型可以是8位,16位,32位和64位有符号或无符号整数类型,浮点类型,双精度型或long double类型使用整数存储值时,将其以符号或零扩展为64位整数值

引用操作数用于引用当前模块,其他MIR模块或C外部函数或声明中的函数和声明。

有用于在不同的32位和64位有符号和无符号值,浮点,双精度和长双精度值之间进行转换的转换指令

在32位和64位有符号和无符号值,浮点,双精度和长双精度值上有算术指令(加,减,乘,除,模)

存在对32位和64位有符号和无符号值起作用的逻辑指令(或异或运算) 有些比较指令适用于32位和64位有符号和无符号值,浮点,双精度和长双精度值 有分支insns(无条件跳转,并且跳转到零或非零值),它们将一个标签作为其操作数 有组合的比较和分支指令,以一个标签为一个操作数,两个32位和64位有符号和无符号值,浮点,双精度和长双精度值 有切换指令,可根据作为第一个操作数给出的索引从作为操作数给出的标签跳转到标签 有些返回指令适用于32位和64位整数值,float,double和long double值 您可以通过API来创建MIR,该API包含用于创建模块,函数,指令,操作数等的函数

了解MIR的最好方法是使用文本MIR表示

#define Size 819000 int sieve(int N){int64_t i,k,prime,count,n;字符标志[大小];对于(n = 0; n< N; n ++){计数= 0; for(i = 0; i< Size; i ++)标志[i] = 1;对于(i = 0; i< Size; i ++)如果(flags [i]){素数= i + i + 3;对于(k = i +素数; k<大小; k + =素数)标志[k] = 0;数++; }}返回计数;} void ex100(void){printf("筛(100)=%d \&#34 ;,筛(100)); }

m_sieve:模块导出筛子筛子:func i32,i32:N local i64:iter,i64:count,i64:i,i64:k,i64:prime,i64:temp,i64:flags alloca标志,819000 mov iter,0循环:bge fin,iter,N mov count,0; mov i,0 loop2:bge fin2,i,819000 mov u8:(flags,i),1;加i,i,1 jmp loop2 fin2:mov i,0 loop3:bge fin3,i,819000 beq cont3,u8:(flags,i),0 add temp,i,i;添加质数,温度3;加k,i,主循环4:bge fin4,k,819000 mov u8:(flags,k),0;添加k,k,素数jmp loop4 fin4:添加计数,计数,1 cont3:添加i,i,1个jmp loop3 fin3:添加iter,iter,1个jmp循环fin:ret count endfunc endmodule m_ex100:模块格式:字符串&# 34;筛(10)=%d \ n" p_printf:proto p:fmt,i32:result p_sieve:proto i32,i32:iter export ex100 import sieve,printf ex100:func v,0 local i64:r调用p_sieve,sieve,r,100调用p_printf,printf,format,r endfunc结束模块

func描述函数的签名(采用32位signedinteger参数并返回32位带符号整数值)和函数参数N,它们将是64位带符号整数类型的局部变量。函数结果首先按类型进行描述,并且没有名称。参数始终具有名称,并在结果描述之后

函数可能具有多个结果,但是当前机器定义了可能的数量和结果类型的组合

我们可以在计算中使用32位指令,如果使用32位CPU,这将是有意义的。当使用32位指令时,我们仅使用64位操作数的32位有效部分,而结果的高32位部分是机器已定义(因此,如果您编写可移植的MIR代码,请考虑未定义高32位部件值)

字符串以C字符串的形式描述数据C字符串可以直接用作insn操作数。在这种情况下,数据将添加到模块中,并且数据地址将用作操作数

导入描述了应在其他MIR模块中定义的模块功能或数据

创建MIR模块(通过MIR API或读取MIR二进制或文本文件)后,您应该加载模块

链接后,您可以从模块解释功能,也可以为MIR JIT编译器(生成器)生成的功能调用机器代码。函数的执行方式通常由设置接口定义。生成的代码的生成方式(延迟在第一次调用时或提前生成)也可以取决于接口

上面示例中的运行代码如下所示(此处m1和m2是模块m_sieve和m_e100,func是函数ex100,sieve是函数sieve):

/ * ctx是由MIR_init创建的上下文* / MIR_load_module(ctx,m1); MIR_load_module(ctx,m2); MIR_load_external(ctx," printf&#34 ;, printf); MIR_link(ctx,MIR_set_interp_interface,import_resolver); / *或使用MIR_set_gen_interface来生成和使用机器代码* / / *或使用MIR_set_lazy_gen_interface来在其第一次调用时生成功能代码* / / *使用MIR_gen(ctx,func)来显式生成功能机器代码* / MIR_interp(ctx ,func,& result,0); / *零是参数编号* / / *或((void(*)(void))func-> addr)();打电话给interpr。或gen。通过接口代码* /

二进制MIR代码通常比类似的MIR文本代码小10倍,读取速度快10倍

从WASM到MIR的转换应该非常简单明了仅需要为MIR提供用于WASM浮点数insns的小型WASM运行时

到/从MIR和从/到MIR编译器的LLVM IR的Java字节代码的实现将是一个挑战: 也可以将GCC移植到MIR。 经验丰富的GCC开发人员可以实施6到12个月 据我估计,将MIR JIT编译器移植到mips64或sparc64将需要每个目标1-2个月的工作 注重性能的将MIR JIT编译器移植到32位目标将需要实现附加的小分析传递,以获取仅在32位指令中使用的64位变量的信息。 我们使用一种Braun算法的形式来构建SSA(M。Braun等人,“静态单项分配表单的简单而有效的构造”) 构建SSA:通过将phi节点和SSA边添加到操作数来构建单个静态分配表单 走出SSA:移除phi节点和SSA边缘(我们一直保持传统的SSA)

文件mir.h和mir.c包含主要的API代码,包括MIR二进制和MIR文本表示的输入/输出

文件mir-dlist.h,mir-mp.h,mir-varr.h,mir-bitmap.h,mir-htab.h包含对应于双链表,内存池,可变长度数组,位图,哈希表的通用代码。文件mir-hash.h是哈希表使用的通用,简单,高质量的哈希函数

文件mir-interp.c包含用于解释MIR代码的代码。它包含在mir.cand中,从未单独编译

文件c2mir / c2mir.h,c2mir / c2mir.c,c2mir / c2mir-driver.c和c2mir / mirc.h包含用于C到MIR编译器的代码。目录c2mir / x86_64和c2mir / aarch64,c2mir / ppc64和c2mir / s390x中的文件分别包含用于C到MIR编译器的x86_64,aarch64,ppc64和s390x机器相关代码

当前代码只能用于使将来的用户熟悉该项目及其使用的方法

[1]是基于筛网代码(无任何包含文件和GCC使用内存文件系统)的编译时间的100倍。使用的优化级别为1

[3]基于使用MIR发电机优化级别1的10次运行的最佳壁挂时间

[4]基于针对GCC和MIR内核的cc1的剥离大小以及针对MIR的解释器或生成器 [5]基于生成空C文件的目标代码或通过API生成emptyMIR模块的时间 [6]仅基于x86-64 C编译器所需的文件以及用于创建和运行MIR代码的最少程序的文件 我只看到三个可以视为或改编为真正的通用轻量级JIT竞争对手的项目 QBE:它生成的汇编代码使QBE 30的机器代码生成速度比MIR生成器慢 LIBJIT开始作为DotGNU项目的一部分:LIBJIT更大:80K C行(用于不带动态Pascal编译器的LIBJIT)与MIR的10K C行(不包括C到MIR编译器)