0

我有一个基于16位寄存器的虚拟机,我想知道将它编译为实际x86机器代码的步骤是什么?我不打算做一个JIT编译器除非有必要能够将编译的代码链接到另一个可执行文件/ DLL。如何将VM特定的代码编译为x86机器代码?

虚拟机的制作使得如果将虚拟机添加到项目中,可以添加特殊的语言结构。 (例如,如果将其嵌入到游戏引擎中,则可能会添加“实体”对象类型,并且可能会暴露引擎中的几个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 

我对这个部分的问题是,我将不得不使用堆栈指针这样的事情?基地指针?我可以用什么来取代这个概念?

+1

您的问题对于问答网站来说太广泛了。你需要一本书或大学课程。 – EJP

+0

我把它简化得不那么宽泛,而且更清晰,因此可以归结为两个简单的问题。 – Accumulator

+0

我想你在这里有一些实际的可回答的问题,但我发现很难理解你问的是什么。我想帮助你,但是如果你可以试着重新解释一下这个问题,那么这真的会帮助我......) – Cauterite

回答

0

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

有很多方法可以实现这种跨语言接口。无论您是运行虚拟机字节码还是本地机器码,在这里都不会有太大影响,除非您需要一个开销非常低的接口。主要考虑的是你的语言的性质 - 尤其是它是静态还是动态输入。

一般来说这是两种最常用的方法(你可能已经熟悉了他们):

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

  • (B)的“运行时API”的方法,在运行时暴露了一个C API,你可以使用手动构建/操纵物体在你的语言使用。 Lua拥有广泛的API(example),与Python一样。

如果脚本代码是从VM字节码编译成原生EXE如何将这种“链接”的可能呢?

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

如果你不知道很多关于共享库,我认为花一些时间研究该地区,你会开始得到的,你可以在你的编译器实现FFI的方式更清楚地了解。

但是,如果采取稍微更加动态的方法(例如:LoadLibrary(),GetProcAddress()),则可以将函数指针作为语言对象包装起来。

这是不幸的是很难给出更具体的建议,不知道有关问题的语言/ VM什么。


[...]我对这个部分的问题是,我将不得不使用堆栈指针这样的事情?基地指针?我可以用什么来取代这个概念?

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

在用词汇范围界定的语言中,我的理解是,在编译函数时,通常会枚举在其主体中声明的每个变量,并分配一个足够容纳所有变量的栈空间块(忽略CPU寄存器的复杂主题分配)。代码可以使用堆栈指针或(更经常)基址指针来解决这些变量。

如果在内部范围的变量影子在喜欢你的例子外范围的变量,他们分配在栈上独立的存储器空间 - 因为据编译器来说它们是不同的变量。

不理解任何方案的VM使用我真的不能认为它应该如何翻译成machinecode背后的基本原理。也许有更多编程经验的编程人员可以给你答案。

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

1

如果我正确理解你的问题,那么是的,你将不得不在这里使用SP/BP等。这就是编译到本地机器代码的意思:将程序的更高级别行为转换为遵循其运行的操作系统惯例的等效机器指令。

所以,你会基本上是必须做你必须做的呼叫主机提供的功能,如果你从汇编叫他们同样的事情。这通常意味着将函数参数的值粘贴在适当的寄存器中/将它们推入堆栈,根据需要转换它们,然后生成CALL或JMP指令或CPU期望实际跳到给定函数的存储器地址的任何内容。

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

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

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

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