Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/assembly/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C 在汇编语言中编写JIT编译器_C_Assembly_Code Generation_Jit_Self Modifying - Fatal编程技术网

C 在汇编语言中编写JIT编译器

C 在汇编语言中编写JIT编译器,c,assembly,code-generation,jit,self-modifying,C,Assembly,Code Generation,Jit,Self Modifying,我已经用C编写了一个虚拟机,对于非JIT虚拟机来说,它的性能相当不错,但我想学习一些新的东西,并提高性能。我当前的实现只是使用一个开关将VM字节码转换为指令,指令被编译成跳转表。正如我所说,它的性能相当不错,但我遇到了一个只有JIT编译器才能克服的障碍 不久前我已经问过一个类似的问题,关于自我修改代码,但我意识到我问的问题不对 所以我的目标是为这个C虚拟机编写一个JIT编译器,我想在x86汇编中实现它。(我使用NASM作为我的汇编器)我不太确定该如何进行这项工作。我对汇编很熟悉,我已经看过一些自

我已经用C编写了一个虚拟机,对于非JIT虚拟机来说,它的性能相当不错,但我想学习一些新的东西,并提高性能。我当前的实现只是使用一个开关将VM字节码转换为指令,指令被编译成跳转表。正如我所说,它的性能相当不错,但我遇到了一个只有JIT编译器才能克服的障碍

不久前我已经问过一个类似的问题,关于自我修改代码,但我意识到我问的问题不对

所以我的目标是为这个C虚拟机编写一个JIT编译器,我想在x86汇编中实现它。(我使用NASM作为我的汇编器)我不太确定该如何进行这项工作。我对汇编很熟悉,我已经看过一些自我修改的代码示例,但我还没有弄清楚如何生成代码

到目前为止,我的主要任务是用我的参数将指令复制到一块可执行内存中。我知道我可以在NASM中标记某一行,并使用静态参数从该地址复制整行,但这不是非常动态的,并且不适用于JIT编译器。我需要能够从字节码解释指令,将其复制到可执行内存,解释第一个参数,将其复制到内存,然后解释第二个参数,并将其复制到内存

我已经被告知了一些可以使这项任务更容易的库,比如GNULightning,甚至LLVM。然而,在使用外部资源之前,我想先手写这篇文章,以了解它是如何工作的


这个社区有什么资源或例子可以帮助我开始这项任务吗?举一个简单的例子,展示两个或三个指令,比如“add”和“mov”,用来生成可执行代码,并在内存中动态地使用参数,这会很奇妙。

我不建议在汇编中编写JIT。在汇编中编写解释器最频繁执行的位有很好的理由。如需了解其外观的示例,请参见LuaJIT的作者

至于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
    
    这只是复制相关代码,因此我们需要相应地初始化所使用的寄存器。更好的解决方案是将其直接转换为机器指令
    mov eax,[ebp+4]
    ,但现在您已经需要手动编码请求的指令

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

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

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

    跟踪JIT(如LuaJIT 2)只编译直线代码,这使得许多事情更容易实现,但您必须非常小心如何拾取跟踪以及如何将多个跟踪高效地链接在一起。Gal和Franz在中描述了一种方法。有关另一种方法,请参阅LuaJIT源代码。这两种JIT都是用C(或者可能是C++)编写的


  • 我建议你看看这个项目。通过使用它提供的框架,您可以节省大量能源。如果你想手工编写所有东西,只要阅读源代码并自己重写就行了,我认为这并不难。

    仅仅因为抖动生成机器代码并不意味着它本身就需要在汇编中编写。这样做没有任何意义。尝试的中间步骤是使用GCC的计算goto扩展(使用
    void*optable[]={&&op\u add,&&op\u subtract,…}
    进行线程分派,每个操作数都是
    op\u add:…goto*optable[*ip++];
    )。我已经看到了你所描述的转换口译员的巨大进步。对,在每个基本模块的末尾,你必须回到一个决定分支位置的例程。这将产生一个新的字节码地址,该地址必须映射到相应机器码的地址。有一种称为JIT的技术,允许更平滑地集成解释器和JIT。其主要思想是将分支转换为实际的机器指令,以便分支预测器可以看到它们。提供的url不起作用
        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