首页 文章

在程序集中编写JIT编译器

提问于
浏览
23

我用C编写了一个虚拟机,它对非JIT VM有很好的性能,但我想学习一些新东西,并提高性能 . 我当前的实现只是使用一个开关从VM字节码转换为指令,并将其编译为跳转表 . 就像我说的那样,它的性能不错,但是我遇到了一个只能用JIT编译器克服的障碍 .

不久前我已经问了一个关于自我修改代码的类似问题,但我开始意识到我并没有问正确的问题 .

所以我的目标是为这个C虚拟机编写一个JIT编译器,我想在x86汇编中完成它 . (我使用NASM作为我的汇编程序)我不太确定如何去做这个 . 我对汇编感到满意,并且我已经查看了一些自我修改的代码示例,但我还没有弄清楚如何进行代码生成 .

到目前为止,我的主要部分是使用我的参数将指令复制到可执行的内存块 . 我'm aware that I can label a certain line in NASM, and copy the entire line from that address with the static arguments, but that'不是很动态,不适用于JIT编译器 . 我需要能够解释字节码中的指令,将其复制到可执行内存,解释第一个参数,将其复制到内存,然后解释第二个参数,并将其复制到内存中 .

我已经了解了几个可以简化这项任务的库,比如GNU闪电,甚至是LLVM . 但是,在使用外部资源之前,我想首先手动编写,以了解它是如何工作的 .

这个社区可以提供任何资源或示例来帮助我开始这项任务吗?一个简单的例子显示了两个或三个指令,例如“add”和“mov”用于生成可执行代码,带有参数,动态地,在内存中,会产生奇迹 .

2 回答

  • 7

    我不建议在汇编中编写JIT . 在汇编中编写解释器中最频繁执行的位有很好的理由 . 有关这样看的例子,请参阅LuaJIT的作者comment from Mike Pall .

    至于JIT,有许多不同的级别,具有不同的复杂性:

    • 通过简单地复制解释器的代码来编译基本块(一系列非分支指令) . 例如,一些(基于寄存器的)字节码指令的实现可能如下所示:
    ; ebp points to virtual register 0 on the stack
    instr_ADD:
        <decode instruction>
        mov eax, [ebp + ecx * 4]  ; load first operand from stack
        add eax, [ebp + edx * 4]  ; add second operand from stack
        mov [ebp + ebx * 4], eax  ; write back result
        <dispatch next instruction>
    instr_SUB:
        ... ; similar
    

    因此,给定指令序列 ADD R3, R1, R2SUB R3, R3, R4 ,简单的JIT可以将解释器实现的相关部分复制到新的机器代码块中:

    mov ecx, 1
        mov edx, 2
        mov ebx, 3
        mov eax, [ebp + ecx * 4]  ; load first operand from stack
        add eax, [ebp + edx * 4]  ; add second operand from stack
        mov [ebp + ebx * 4], eax  ; write back result
        mov ecx, 3
        mov edx, 4
        mov ebx, 3
        mov eax, [ebp + ecx * 4]  ; load first operand from stack
        sub eax, [ebp + edx * 4]  ; add second operand from stack
        mov [ebp + ebx * 4], eax  ; write back result
    

    这只是复制相关代码,因此我们需要初始化相应使用的寄存器 . 更好的解决方案是将其直接转换为机器指令 mov eax, [ebp + 4] ,但现在您必须手动编码所请求的指令 .

    这种技术消除了解释的开销,但在其他方面并没有提高效率 . 如果代码只执行一次或两次,那么首先将其转换为机器代码可能是不值得的(这需要刷新至少部分I-cache) .

    • 虽然一些JIT使用上述技术而不是解释器,但它们对频繁执行的代码采用了更复杂的优化机制 . 这涉及将执行的字节码转换为中间表示(IR),在该中间表示上执行附加的优化 .

    根据源语言和JIT的类型,这可能非常复杂(这就是许多JIT将此任务委派给LLVM的原因) . 基于方法的JIT需要处理连接控制流图,因此它们使用SSA形式并对其进行各种分析(例如,Hotspot) .

    跟踪JIT(如LuaJIT 2)仅编译直线代码,这使得许多事情更容易实现,但是您必须非常小心地选择跟踪以及如何有效地将多条跟踪链接在一起 . Gal和Franz在this paper (PDF)中描述了一种方法 . 有关另一种方法,请参阅LuaJIT源代码 . 两个JIT都是用C语言编写的(或者也许是C语言) .

  • 18

    我建议你看一下项目http://code.google.com/p/asmjit/ . 通过使用它提供的框架,您可以节省大量精力 . 如果你想手工编写所有东西,只需阅读源代码并自己重写,我认为这不是很难 .

相关问题