从零开始设计一套指令集及其虚拟机
前言
在半年前,我萌生了创造一门独特的编程语言的想法。大约三个月前,脑中的这个想法逐渐变得清晰,我想实现一种可以不用键盘就能编写代码的语言。比较准确得说应该是一门图形化编程语言,不完全是像蓝图脚本那样,而是结合代码和图形的优点。
设想中它是一门高级解释型语言,所以我先命名它为“H”语言,意为高级。但我能力和水平非常有效,在实现的准备过程中就被绊倒了,于是我开始考虑将它实现为静态编译语言,先编译为中间代码,再解释执行。
这篇文章介绍的就是这当中的中间代码,因为它层次比"H"低,所以我命名为“L”语言。
这段时间我暂时放下之前GBA上的光线追踪,开始设计L语言的指令集和虚拟机。现在虚拟机可以高效的运行指令字节码,并调用系统函数,实现输入输出等功能。这篇文章记录了目前的进度。
指令设计的基础应该是虚拟机的架构,因此我先简要说明虚拟机特点,然后是指令集特点,最后详细介绍指令设计过程。
虚拟机
通过中间代码在不同架构,不同平台的系统上运行程序的方法大概叫虚拟机技术,例如JAVA和.NET都是运行各自指令集的虚拟机。
我的虚拟机设计的目标是为了方便运行我的H语言,我将他称为LVM,意为低级虚拟机(和LLVM名字很像 : D 我也是事后发现的)。
学过计算机架构的也许知道基于栈的虚拟机和基于寄存器的虚拟机,我的LVM是基于寄存器的,但有它自己的特点。
在现代计算机中,函数调用需要保存寄存器,移动寄存器和压栈出栈,会占用很多时间。这是因为寄存器的数量有效,而在虚拟机的设计中,完全可以不考虑寄存器的限制,例如可以给每个函数调用分配一组内存模拟的虚拟寄存器,这样就不必保存和恢复现场。
我则采用了一种寄存器指针的方法,寄存器指针指示寄存器窗口的位置,调用函数会移动窗口位置,且原窗口和新窗口位置存在重叠,用于传递参数。
结合此图,函数调用的过程为
- 调用者把参数放到寄存器窗口的末尾。
- 调用者将窗口后移,使原来的末尾变成开头.
- 调用者把PC保存到窗口前之前。
- 调用函数(PC跳转)。
- 被调用函数执行完后,从窗口前读取PC,回到调用前位置。
- 最后将窗口移回原位。
通过这种方法既可以不用保存现场,也不用移动寄存器。在写这篇文章时,我才查到这种方法在我之前就已提出,名称是重叠寄存器窗口技术,用于精简指令集处理器中,其中的范例是SPARC。因此我的虚拟机也可称为基于重叠寄存器窗口技术的虚拟机。
截至目前为止,虚拟机的结构如下:
可以看到:
- 寄存器窗口有16个寄存器,而虚拟寄存器有一万多个。这就是软件虚拟的好处,是SPARC等硬件处理器不能做到的。
- 虽然现在留有SP寄存器,但实际上除了寄存器列,并没有栈,因此也没有栈指令,我想先探究依靠灵活的寄存器窗口和寄存器列能否替代栈。
- 指令译码后储存为VL字节码,加载到内存后由vcode指针连接到虚拟机。
- 没有使用标志位(或者叫程序状态字)
附带一提寄存器统一是32位或64位,目前按32位设计指令。
指令集
很随意的命名为VL指令集,代表变长低级指令集。此外编译器已经写好,可以将其对应的LASM汇编语言编译到VL字节码。
目前指令约80条,包含寄存器移动、内存读取储存、加减乘除、移位、位运算、跳转、分支、寄存器窗口操作和系统指令。
个人感觉VL指令集功能应该在精简和复杂指令集之间。
正如前面所说,指令是依附于机器的,所以指令的Call和Return的操作都比较特殊,还有特殊的寄存器窗口移动指令,但没有栈操作指令。
因为面向的是软件层面,且是实时解释执行的,这些指令的字节码都尽量比较规整,例如操作码是1字节的,两个寄存器各4bit拼一起也是1字节,尽量避免读取译码的额外操作。指令不是像大多精简指令集一样定长,而是以字节为单位,长度在1-6字节之间。这会导致指令执行增加一点变数,速度受到影响,但节约了大量的空间所以也不用心疼。
这是一些字节码的安排(靠右为低字节):
设计指令集
指令集的组成和设计指令集的过程我放到下一篇文章中详细说明。
而下下篇文章,我会继续介绍LVM虚拟机的实现过程和优化心得。