我希望自我教育的目的是为动态语言实现一个简单的虚拟机,更喜欢用 C 语言。像 Lua VM、Parrot 或 Python VM 之类的东西,但更简单。除了查看现有 VM 的代码和设计文档之外,是否有任何好的资源/教程来实现这一目标?
编辑:为什么要关闭投票?我不明白 - 这不是编程吗?如果我的问题有具体问题,请发表评论。
我希望自我教育的目的是为动态语言实现一个简单的虚拟机,更喜欢用 C 语言。像 Lua VM、Parrot 或 Python VM 之类的东西,但更简单。除了查看现有 VM 的代码和设计文档之外,是否有任何好的资源/教程来实现这一目标?
编辑:为什么要关闭投票?我不明白 - 这不是编程吗?如果我的问题有具体问题,请发表评论。
我假设您想要一个虚拟机,而不仅仅是一个解释器。我认为它们是连续统一体上的两个点。解释器处理接近程序原始表示的东西。虚拟机处理更原始(和自包含)的指令。这意味着您需要一个编译阶段来将一个转换为另一个。我不知道您是否想先处理这个问题,或者您是否已经考虑过输入语法。
对于动态语言,您需要在某个地方存储数据(作为键/值对)和一些对其进行操作的操作。VM 维护存储。在其上运行的程序是一系列指令(包括控制流)。您需要定义指令集。我建议从一个简单的集合开始,例如:
您可能希望像许多 VM 那样使用基于堆栈的计算方法来进行算术运算。上面还没有太多的动态。为此,我们需要两件事:在运行时计算变量名称的能力(这仅意味着字符串操作),以及将代码视为数据的一些处理。这可能就像允许函数引用一样简单。
理想情况下,VM 的输入应该是字节码。如果您还没有编译器,则可以从基本汇编器(可能是 VM 的一部分)生成它。
VM 本身由循环组成:
1. Look at the bytecode instruction pointed to by the instruction pointer.
2. Execute the instruction:
* If it's an arithmetic instruction, update the store accordingly.
* If it's control flow, perform the test (if there is one) and set the instruction pointer.
* If it's print, print a value from the store.
3. Advance the instruction pointer to the next instruction.
4. Repeat from 1.
处理计算变量名称可能很棘手:一条指令需要指定计算名称所在的变量。这可以通过允许指令引用输入中提供的字符串常量池来完成。
一个示例程序(在汇编和字节码中):
offset bytecode (hex) source
0 01 05 0E // LOAD 5, .x
3 01 03 10 // .l1: LOAD 3, .y
6 02 0E 10 0E // ADD .x, .y, .x
10 03 0E // PRINT .x
12 04 03 // GOTO .l1
14 78 00 // .x: "x"
16 79 00 // .y: "y"
隐含的指令代码是:
"LOAD x, k" (01 x k) Load single byte x as an integer into variable named by string constant at offset k.
"ADD k1, k2, k3" (02 v1 v2 v3) Add two variables named by string constants k1 and k2 and put the sum in variable named by string constant k3.
"PRINT k" (03 k) Print variable named by string constant k.
"GOTO a" (04 a) Go to offset given by byte a.
当变量由其他变量等命名时,您需要变体(并且间接级别变得难以推理)。汇编器查看诸如“ADD .x, .y, .x”之类的参数,并生成正确的字节码,以便从字符串常量(而不是计算变量)中添加。
好吧,这不是关于用 C 实现 VM,但由于它是我在看到这个问题之前打开的最后一个选项卡,我觉得我需要指出一篇关于使用标签在 JavaScript 中实现 QBASIC 字节码编译器和虚拟机<canvas>
的文章用于展示。它包括所有源代码,以获得足够的 QBASIC 实现以运行“nibbles”游戏,并且是关于编译器和字节码解释器系列文章的第一篇;这一篇描述了虚拟机,他也很有希望在未来的文章中描述编译器。
顺便说一句,我没有投票结束你的问题,但你得到的结束投票是去年关于如何学习实现虚拟机的问题的重复。我认为这个问题(关于教程或相对简单的问题)与应该保持开放的问题有很大不同,但您可能想参考该问题以获得更多建议。
另一个要查看的资源是Lua 语言的实现。它是一种基于寄存器的虚拟机,在性能方面享有盛誉。源代码采用ANSI C89 格式,通常可读性很强。
与大多数高性能脚本语言一样,最终用户看到的是一种可读的高级动态语言(具有闭包、尾调用、不可变字符串、数字和哈希表等功能作为主要数据类型,函数作为一等值等等) . 源文本被编译为 VM 的字节码,供 VM 实现执行,其大纲与Edmund 的回答所描述的差不多。
为了使 VM 本身的实现既便携又高效,我们付出了巨大的努力。如果需要更高的性能,则针对 32 位 x86 存在从 VM 字节码到本机指令的即时编译器,并且针对 64 位处于 beta 版本。
对于开始(即使不是C,而是C++)你可以看看muParser。
它是一个数学表达式解析器,使用简单的虚拟机来执行操作。我认为即使是你也需要时间来理解一切;无论如何,这段代码比能够运行真正完整程序的完整 VM 更简单。(顺便说一句,我正在用 C# 设计一个类似的库——它处于早期阶段,但下一个版本将允许编译为 .NET/VM IL或者可能是一个新的简单 VM,如 muParser)。
另一个有趣的事情是NekoVM(它执行 .n 字节码文件)。这是一个用 C 编写的开源项目,它的主要语言 (.neko) 被认为是由源到源编译器技术生成的。本着上一个主题的精神,请参阅同一作者的Haxe(也是开源的)。
和你一样,我也一直在研究虚拟机和编译器,我可以推荐的一本好书是Compiler Design: Virtual Machines。它通过为每个 VM 提供指令集以及如何为该 VM 编译高级语言的教程来描述用于命令式、函数式、逻辑和面向对象语言的虚拟机。我只为命令式语言实现了 VM,这已经是一个非常有用的练习。
如果您刚刚开始,那么我可以推荐的另一个资源是PL101。它是一组交互式 JavaScript 课程,可指导您完成为各种语言实现解析器和解释器的过程。
我参加聚会迟到了,但我会推荐 Game Scripting Mastery,它可以让您从零开始编写工作脚本语言及其虚拟机。并且几乎没有先决条件。