【动手写ToyLang】6-虚拟机设计
# 所谓 "解释器"
我们除了能对遵循 ToyLang 语法的源文件编译之外,还要能够执行编译的结果。
若编译结果需要由其他程序负责执行,该程序便可称之为解释器。
- 如先前提及的 AST 解释器。
百度百科上对解释器的定义也较为模糊:
解释器(英语:Interpreter),又译为直译器,是一种电脑程序,能够把高级编程语言一行一行直接转译运行。解释器不会一次把整个程序转译出来,只像一位 “中间人”,每次运行程序时都要先转成另一种语言再作运行,因此解释器的程序运行速度比较缓慢。它每转译一行程序叙述就立刻运行,然后再转译下一行,再运行,如此不停地进行下去。
按照百度百科的定义,一行一行动态执行高级编程语言的程序,才能算解释器。
但是以现在常见的编程语言,如 Python、Java 等也在某些情况下会被定义为解释型语言来看,已经与 "解释器" 的定义相违背了。
因为 Python 和 Java 并不是逐行解释源代码的,实际上得以 "解释" 的是字节码,而执行字节码的程序又被称为 "虚拟机"。
我的个人理解是,“解释器”、"解释型语言" 等名词的诞生,或许是为了与在当时已成体系的本地编译型语言进行区分,不过历史是否如此我也不好追溯了。
因此,不必过度纠结 "解释器" 和 "虚拟机",可以简单理解成解释器是一种笼统的统称,虚拟机是一种具体的实现,AST 解释器则是另一种实现,最终目标都是为了使得源代码得以以某种方式运行起来。
# 虚拟机简述
想必各位读者读者也经常见过虚拟机这个词。
咱们的 ToyLang 不选择直接解释 AST,而是设计虚拟机以执行编译结果。
虚拟机在不同场景下,所代指的东西存在差异,此处仅解释在编译原理中虚拟机的定义。
实际上,虚拟机的开发应该是在更靠后一些的章节中讲述,在编写指令生成时顺带完成虚拟机。
不过虚拟机的编写并不复杂,并且相对独立,因此就提前将其设计好,代码生成时以我们所设计的虚拟机为准去生成代码,有需要再对虚拟机进行修改与补充。
我们并不选择生成本地代码 (与机器相关的机器指令),而是生成一种被称为 字节码
的编译产物。
如果读者曾经学习过 Java
,应该或多或少听过这种说法:
Java 并不直接生成
机器指令
,而是生成字节码
,交给JVM
去执行。
以我个人的理解看来, 机器指令
和 字节码
并没有特别多的区别,只不过负责执行的对象不一样:
- 一个是直接由硬件执行
- 一个是模拟了硬件执行指令的流程的程序,自己设计了一套指令集。
是的,我们所做的也与 Java
类似,我们也会设计 ToyLang 所生成的字节码,以及执行字节码的虚拟机。
当然这个虚拟机你要叫什么都可以,我在这里就把它叫成 TVM(Toy Virtual Machine)
,
# TVM
我们的 TVM
设计十分简单,只有十几条指令,但也足够我们使用了。
为了实现更加简单, TVM
被设计为基于栈的虚拟机。
# 指令集设计
# 指令结构
字段 | 长度 | 描述 |
---|---|---|
Reserved | 1bit | 保留位 |
Opcode | 7bit | 操作码 |
Immediate | 4byte | 立即数,可选 |
# Opcode
Opcode,即操作码,是指令的一部分,在虚拟机的指令解码器在会根据指令的 Opcode 来进行不同的操作。
以下是 TVM 指令集的 Opcode 表:
助记符 | 编码 | 描述 |
---|---|---|
stop | 0x00 | 停止虚拟机 |
nop | 0x01 | 空指令 |
pushk | 0x02 | 将常量推入栈顶 |
pushv | 0x03 | 将变量推入栈顶 |
pop | 0x04 | 弹出栈顶值并抛弃 |
popv | 0x05 | 弹出栈顶值并保存到变量中 |
add | 0x06 | 依次弹出栈顶的值,stack2 + stack1,压入结果 |
sub | 0x07 | 依次弹出栈顶的值,stack2 - stack1,压入结果 |
mul | 0x08 | 依次弹出栈顶的值,stack2 * stack1,压入结果 |
div | 0x09 | 依次弹出栈顶的值,stack2 /stack1,压入结果 |
call | 0x0a | 函数调用 |
ret | 0x0b | 函数调用返回 |
ne | 0x0c | 不等于比较 |
eq | 0x0d | 等于比较 |
lt | 0x0e | 小于比较 |
le | 0x0f | 小于等于比较 |
gt | 0x10 | 大于比较 |
ge | 0x11 | 大于等于比较 |
jcf | 0x12 | 条件为否则跳转 |
jmp | 0x13 | 无条件跳转 |
# ToyLang 源码 最终转换为 虚拟机指令的简单示例
1 | if a == 1 { |