JIT 编译器如何领先于执行机器代码?
How do JIT compilers stay ahead of executing machine code?
如果我没理解错的话,JIT 编译器会即时将代码(通常是字节码)编译成本地机器代码,并将其插入已知内存中的适当位置。
该进程启动后,JIT 编译器如何领先于正在执行的机器代码?如何确保执行代码不会遇到空白内存,因为 JIT 还没有弄清楚接下来要放什么?
例如,给定一些(假的)字节码:
03 01 move variable 1 onto the stack
b3 02 do something with the contents
在生成第一行本机代码并将下一行放置为 运行 之后,我假设 JIT 会将本机代码 "GOTO" 分配给一组空内存其中到 运行 下一批指令。但是,如果机器代码在 JIT 编译器有时间将第 2 行的机器代码放入该插槽之前到达那里怎么办?
这因具体实施而异。可能的策略是 运行 解释器中的代码 - 或者在分层 JIT 的情况下是先前的编译层 - 编译或暂停执行直到编译完成。
正确性由以下两条规则保证:
绝不允许执行未完成的代码
JIT 编译器将首先完成编译它所处理的任何代码区域,这可能是一个基本块、一个函数或代码中的任意跟踪。只有在它完成后,它才会允许处理器执行该代码。所以执行永远不会遇到未完成的翻译。
不生成未定义的跳转
每当 JIT 编译器遇到离开正在编译的代码区域的跳转时,它会生成一个跳转回解释器代码,确定继续执行的位置,可能是通过编译其他代码区域,但是从不 未定义的位置。在编译区域的末尾也是如此。
一些 JIT 也编译为遵循机器调用约定的函数,因此可以只使用普通的 return(LLVM JIT 就是一个例子)。在这种情况下,"JITed" 代码只是通过函数指针调用,而代码只是 returns 到作为解释器的调用者。
其他 JIT 编译器为生成的代码生成自定义序言和结尾,以确保处理器在执行 jit 代码后处于定义的状态,并且继续执行所需的所有信息都可用。
作为优化,JIT 可能会注意到跳转到已经过 JIT 编译或静态预编译(例如库函数)的代码,并在那里发出直接跳转,或者他们可以创建一个跳转指令,稍后可以修补以转到新编译的代码段(QEMU 就是这样做的)。
如果我没理解错的话,JIT 编译器会即时将代码(通常是字节码)编译成本地机器代码,并将其插入已知内存中的适当位置。
该进程启动后,JIT 编译器如何领先于正在执行的机器代码?如何确保执行代码不会遇到空白内存,因为 JIT 还没有弄清楚接下来要放什么?
例如,给定一些(假的)字节码:
03 01 move variable 1 onto the stack
b3 02 do something with the contents
在生成第一行本机代码并将下一行放置为 运行 之后,我假设 JIT 会将本机代码 "GOTO" 分配给一组空内存其中到 运行 下一批指令。但是,如果机器代码在 JIT 编译器有时间将第 2 行的机器代码放入该插槽之前到达那里怎么办?
这因具体实施而异。可能的策略是 运行 解释器中的代码 - 或者在分层 JIT 的情况下是先前的编译层 - 编译或暂停执行直到编译完成。
正确性由以下两条规则保证:
绝不允许执行未完成的代码
JIT 编译器将首先完成编译它所处理的任何代码区域,这可能是一个基本块、一个函数或代码中的任意跟踪。只有在它完成后,它才会允许处理器执行该代码。所以执行永远不会遇到未完成的翻译。
不生成未定义的跳转
每当 JIT 编译器遇到离开正在编译的代码区域的跳转时,它会生成一个跳转回解释器代码,确定继续执行的位置,可能是通过编译其他代码区域,但是从不 未定义的位置。在编译区域的末尾也是如此。
一些 JIT 也编译为遵循机器调用约定的函数,因此可以只使用普通的 return(LLVM JIT 就是一个例子)。在这种情况下,"JITed" 代码只是通过函数指针调用,而代码只是 returns 到作为解释器的调用者。
其他 JIT 编译器为生成的代码生成自定义序言和结尾,以确保处理器在执行 jit 代码后处于定义的状态,并且继续执行所需的所有信息都可用。
作为优化,JIT 可能会注意到跳转到已经过 JIT 编译或静态预编译(例如库函数)的代码,并在那里发出直接跳转,或者他们可以创建一个跳转指令,稍后可以修补以转到新编译的代码段(QEMU 就是这样做的)。