繁体   English   中英

如何将特定于VM的代码编译为x86机器代码?

[英]How can I compile VM-specific code into x86 machine code?

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

制作VM时,如果将VM添加到项目中,则可以添加特殊的语言构造。 (例如,如果将其嵌入游戏引擎中,则可能会添加“实体”对象类型,并且可能会公开该引擎中的多个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

关于这一部分的问题是,我是否必须使用堆栈指针来进行此类操作? 基本指针? 我可以用什么来代替这个概念?

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

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

您需要有一个函数名称表,以供主机提供给您的函数指针映射,并从那里查找地址。

函数返回后,如果需要,您可以将函数返回的值转换回内部类型,然后按照自己的喜好进行。 (这基本上是所有这些“外部函数接口”库在内部执行的操作)。

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

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

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

有很多方法可以实现这种跨语言接口。 除非您需要开销非常低的接口,否则在这里运行虚拟机字节码还是运行本机代码都无关紧要。 主要考虑因素是语言的性质-尤其是具有静态或动态类型的语言。

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

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

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

如果脚本代码从VM字节码编译为本地EXE,如何进行这种“链接”?

因此,您可能正在考虑如何将外部函数地址烘烤到生成的机器代码中。 好吧,如果您拥有适当的FFI基础结构,那么只要您知道共享库导入的工作方式(导入地址表,重定位,修复等),就没有理由不能这样做。

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

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

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


[…]关于这部分的问题是,我是否必须使用堆栈指针进行此类操作? 基本指针? 我可以用什么来代替这个概念?

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

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

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

如果不了解虚拟机正在使用的任何方案背后的基本原理,我无法真正建议它应如何转换为机器代码。 也许有更多编程字节码编译器经验的人可以为您提供答案。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM