【问题标题】:How can I compile VM-specific code into x86 machine code?如何将特定于 VM 的代码编译为 x86 机器代码?
【发布时间】:2017-03-02 00:10:51
【问题描述】:

我有一个基于寄存器的 16 位虚拟机,我想知道将它编译成实际的 x86 机器代码的步骤是什么?我不打算制作 JIT 编译器除非必须能够将编译后的代码与另一个可执行文件/DLL 链接。

制作虚拟机时,如果将虚拟机添加到项目中,则可以添加特殊的语言结构。 (例如,如果它嵌入到游戏引擎中,可能会添加“实体”对象类型,并且可能会暴露引擎中的几个 C 函数。)这将导致代码完全依赖于某些暴露的 C 函数或暴露的 C++ 类,在它嵌入的应用程序中。

如果脚本代码从 VM 字节码编译成原生 EXE,这种“链接”怎么可能实现?

它也像 Lua 的 VM 一样基于寄存器,因为所有基本变量都存储在“寄存器”中,这是一个巨大的 C 数组。当作用域改变时,寄存器指针会递增或递减,因此寄存器编号是相对的,类似于堆栈指针。例如:

int a = 5;
{
    int a = 1;
}

可能是,在虚拟机伪汇编中:

mov_int (%r0, $5)

 ; new scope, the "register pointer" is then incremented by the number
 ; of bytes that are used to store local variables in this new scope. E.g. int = 4 bytes
 ; say $rp is the "register pointer"

add     (%rp, $4) ; since size of int is usually 4 bytes
                  ; this is if registers are 1 bytes in size, if they were
                  ; 4 bytes in size it would just be adding $1

mov_int (%r0, $1) ; now each register "index" is offset by 4,
                  ; this is now technically setting %r4
                  ; different instructions are used to get values above current scope

sub    (%rp, $4) ; end of scope so reset %rp

我关于这部分的问题是,我必须使用堆栈指针来处理这种事情吗?基指针?我可以用什么来代替这个概念?

【问题讨论】:

  • 您的问题对于问答网站来说太宽泛了。你需要一本书,或者一门大学课程。
  • 我让它变得不那么宽泛,更清晰,所以它归结为两个简单的问题。
  • 我认为你在这里有一些实际可以回答的问题,但我发现很难理解你在问什么。我想帮助你,但如果你能试着重新表述一下这个问题,那真的会……帮助我帮助你;)
  • @Cauterite 希望现在更清楚了。特别是关于寄存器指针的部分。

标签: compiler-construction virtual-machine executable jit machine-code


【解决方案1】:

如果我正确理解您的问题,那么是的,您将不得不在此处使用 SP/BP 等。这就是编译为本机机器码的含义:将程序的高级行为转换为符合其运行的操作系统约定的等效机器指令。

因此,如果您从汇编程序中调用主机提供的函数,则基本上必须执行与调用主机提供的函数相同的操作。这通常意味着将函数参数的值粘贴在适当的寄存器中/将它们压入堆栈,根据需要进行转换,然后生成 CALL 或 JMP 指令或 CPU 期望实际跳转到给定函数的内存地址的任何指令。

你需要有一个函数名表到主机提供给你的函数指针映射,然后从那里查找地址。

一旦函数返回,如果需要,你可以将函数返回的值转换回你的内部类型,然后继续你的快乐方式。 (这基本上是所有那些“外部函数接口”库在内部所做的)。

根据您的语言及其用途,也有可能在这里作弊。您可以使用自己的内部伪堆栈,只需添加一个特殊的“调用本机函数”指令。该指令将接收有关函数作为参数的信息(例如,它采用/返回的参数类型,如何查找函数指针),然后将使用外部函数接口库进行实际的函数调用。

这意味着调用本机函数会产生少量开销,但意味着您可以保持 VM 原样,同时仍允许人们调用本机代码以与您的应用程序集成。

【讨论】:

    【解决方案2】:

    制作虚拟机时,如果将虚拟机添加到项目中,则可以添加特殊的语言结构。 (例如,如果它嵌入到游戏引擎中,可能会添加“实体”对象类型,并且可能会暴露引擎中的几个 C 函数。)这将导致代码完全依赖于某些暴露的 C 函数或暴露的 C++ 类,在它嵌入的应用程序中。

    有很多方法可以实现这种跨语言接口。除非您需要一个开销非常低的接口,否则您是在运行 VM 字节码还是本机机器码并不重要。主要考虑因素是您的语言的性质——尤其是它是静态类型还是动态类型。

    一般来说,这是两种最常见的方法(您可能已经熟悉它们):

    • (a)foreign-function-interface”方法,您的语言/运行时提供了自动包装来自 C 的函数和数据的工具。示例包括LuaJIT FFIjs-ctypesP/Invoke。大多数 FFI 可以在 CDECL/STDCALL 函数和 POD 结构上运行;有些对 C++ 或 COM 类有不同程度的支持。

    • (b)runtime-API”方法,您的运行时公开一个 C API,您可以使用它来手动构造/操作对象以用于你的语言。 Lua 有一个广泛的 API (example) 和 Python

    如果脚本代码从 VM 字节码编译成原生 EXE,这种“链接”怎么可能实现?

    所以你可能正在考虑如何例如将外部函数地址烘焙到您生成的机器代码中。好吧,如果您拥有适当的 FFI 基础设施,那么您没有理由不能这样做,只要您了解共享库导入的工作原理(导入地址表、重定位、修复等)。

    如果您不太了解共享库,我认为通过花一些时间研究该领域,您将开始更清楚地了解在编译器中实现 FFI 的方法。

    但是,如果采用更动态的方法可能更容易,例如:LoadLibrary()GetProcAddress(),然后将函数指针包装为您的语言的对象。

    不幸的是,在不了解相关语言/VM 的情况下很难给出更具体的建议。


    [...] 我对这部分的问题是,我是否必须使用堆栈指针来处理这类事情?基指针?我可以用什么来代替这个概念?

    我不完全确定这个“寄存器数组”方案的目的是什么。

    在具有词法作用域的语言中,据我了解,在编译函数时,您通常会枚举在其主体中声明的每个变量并分配一块足够大的堆栈空间以容纳所有变量(忽略 CPU 寄存器分配的复杂主题)。代码可以使用堆栈指针或(更常见的)基指针来寻址这些变量。

    如果像您的示例一样,如果内部作用域中的变量遮盖了外部作用域中的变量,则会在堆栈上为它们分配单独的内存空间——因为就编译器而言,它们是不同的变量。

    如果不了解 VM 使用的任何方案背后的基本原理,我无法真正建议它应该如何转换为机器码。也许有更多字节码编译器编程经验的人可以给你你想要的答案。

    但是,您的 VM 的方法实际上可能与我所描述的相似,在这种情况下,将其用于机器代码编译实际上应该非常简单 - 只需将您的虚拟局部变量内存空间转换为堆栈空间.

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-08-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-13
      相关资源
      最近更新 更多