制作一个JIT编译器

14

我写了一个Brainfuck实现(C ++),它的工作方式如下:

  1. 读取输入的 Brainfuck 文件
  2. 进行一些微小的优化
  3. 将 Brainfuck 转换为虚拟机的机器码
  4. 在虚拟机中执行生成的机器码

这样做已经非常快了,但现在瓶颈在于虚拟机。它使用 C++ 编写,读取标记,执行操作(如果您了解 Brainfuck 的话,这些操作都不多),以此类推。

我想做的是剥离虚拟机并即时生成本地机器码(基本上是 JIT 编译器)。这可以轻松实现 20 倍速度提升。

这意味着步骤 3 将被 JIT 编译器替换,并用生成的机器码来代替步骤 4 中的执行。

我真的不知道从哪里开始,所以我有几个问题:

  1. 这是怎么工作的?生成的机器码如何执行?
  2. 是否有用于生成本地机器码的 C++ 库?
4个回答

17
  1. 生成的机器代码就像普通函数一样通过jmpcall调用。有时还需要禁用存储生成代码的内存中的不执行标志(NX位)。在Linux中,可以使用 mprotect(addr, size, PROT_READ | PROT_WRITE | PROT_EXEC) 来实现。在Windows中,该标志称为 DEP。

  2. 有一些工具可供选择,例如:GNU Lightning(通用)和Nanojit,它用于Firefox JavaScript JIT引擎。更强大、现代化的JIT是LLVM,您只需要将BF代码转换为LLVM IR,然后LLVM可以为许多平台进行优化和代码生成,或者在具有JIT功能的解释器(虚拟机)上运行LLVM IR。这里有一篇关于BF和LLVM的文章,包括一个完整的LLVM JIT编译器:http://www.remcobloemen.nl/2010/02/brainfuck-using-llvm/

另外一个BF + LLVM编译器可以在LLVM的svn中找到:https://llvm.org/svn/llvm-project/llvm/trunk/examples/BrainF/BrainF.cpp


1
只有一个人解释了机器码的称呼,因此他获得了+1和采纳。 - orlp

6

LLVM 是一个完整的 C++ 库(或一组库),用于从中间形式生成本地代码,具有文档和示例,并已用于生成 JITters。

(它还有一个使用该框架的 C/C++ 编译器 - 但是该框架本身可以用于其他语言)。


4
这可能有点晚了,但为了帮助其他人,我发布这个答案。
JIT编译器具有AOT编译器的所有步骤。主要区别在于,AOT编译器将机器相关代码输出到可执行文件(如exe等),而JIT编译器在运行时将机器相关代码加载到内存中(因此会产生性能开销,因为每次都需要重新编译和加载)。
JIT编译器如何在运行时将机器代码加载到内存中?
我不会教你关于机器代码的知识,因为我认为你已经知道了,
例如,汇编代码。
mov    rax,0x1

被翻译成

48 c7 c0 01 00 00 00

你可以动态生成翻译代码并将其保存到向量中,就像这样(这是一个C向量)。
vector machineCode{
   0x48, 0xc7, 0xc0, 0x01, 0x00, 0x00, 0x00, 
}

然后您将此向量复制到内存中,为此需要知道此代码所需的内存大小,可以通过machinecode.size()获得,并记住页面大小。
要将此向量复制到内存中,您需要在C中调用mmap函数。将指针设置为代码开头并调用它。您就可以开始了。
如果有任何不清楚的地方,请查看此帖子以获得简单易懂的解释 https://solarianprogrammer.com/2018/01/10/writing-minimal-x86-64-jit-compiler-cpp/ https://github.com/spencertipping/jit-tutorial

3

GNU Lightning是一组宏,可以为几种不同的架构生成本地代码。在第三步中,您需要对汇编代码有扎实的理解,因为您将使用Lightning宏将机器代码直接发射到稍后将执行的缓冲区中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接