从零开始设计一套指令集及其虚拟机

前言

在半年前,我萌生了创造一门独特的编程语言的想法。大约三个月前,脑中的这个想法逐渐变得清晰,我想实现一种可以不用键盘就能编写代码的语言。比较准确得说应该是一门图形化编程语言,不完全是像蓝图脚本那样,而是结合代码和图形的优点。

设想中它是一门高级解释型语言,所以我先命名它为“H”语言,意为高级。但我能力和水平非常有效,在实现的准备过程中就被绊倒了,于是我开始考虑将它实现为静态编译语言,先编译为中间代码,再解释执行。

这篇文章介绍的就是这当中的中间代码,因为它层次比"H"低,所以我命名为“L”语言

 这段时间我暂时放下之前GBA上的光线追踪,开始设计L语言的指令集和虚拟机。现在虚拟机可以高效的运行指令字节码,并调用系统函数,实现输入输出等功能。这篇文章记录了目前的进度。

指令设计的基础应该是虚拟机的架构,因此我先简要说明虚拟机特点,然后是指令集特点,最后详细介绍指令设计过程。


 

虚拟机

通过中间代码在不同架构,不同平台的系统上运行程序的方法大概叫虚拟机技术,例如JAVA和.NET都是运行各自指令集的虚拟机。

我的虚拟机设计的目标是为了方便运行我的H语言,我将他称为LVM,意为低级虚拟机(和LLVM名字很像 : D 我也是事后发现的)。

          

学过计算机架构的也许知道基于栈的虚拟机和基于寄存器的虚拟机,我的LVM是基于寄存器的,但有它自己的特点。

在现代计算机中,函数调用需要保存寄存器,移动寄存器和压栈出栈,会占用很多时间。这是因为寄存器的数量有效,而在虚拟机的设计中,完全可以不考虑寄存器的限制,例如可以给每个函数调用分配一组内存模拟的虚拟寄存器,这样就不必保存和恢复现场。

我则采用了一种寄存器指针的方法,寄存器指针指示寄存器窗口的位置,调用函数会移动窗口位置,且原窗口和新窗口位置存在重叠,用于传递参数

 

结合此图,函数调用的过程为

  1. 调用者把参数放到寄存器窗口的末尾。
  2. 调用者将窗口后移,使原来的末尾变成开头.
  3. 调用者把PC保存到窗口前之前。
  4. 调用函数(PC跳转)。
  5. 被调用函数执行完后,从窗口前读取PC,回到调用前位置。
  6. 最后将窗口移回原位。

通过这种方法既可以不用保存现场,也不用移动寄存器。在写这篇文章时,我才查到这种方法在我之前就已提出,名称是重叠寄存器窗口技术,用于精简指令集处理器中,其中的范例是SPARC。因此我的虚拟机也可称为基于重叠寄存器窗口技术的虚拟机

截至目前为止,虚拟机的结构如下:

 可以看到:

  • 寄存器窗口有16个寄存器,而虚拟寄存器有一万多个。这就是软件虚拟的好处,是SPARC等硬件处理器不能做到的。
  • 虽然现在留有SP寄存器,但实际上除了寄存器列,并没有栈,因此也没有栈指令,我想先探究依靠灵活的寄存器窗口和寄存器列能否替代栈。
  • 指令译码后储存为VL字节码,加载到内存后由vcode指针连接到虚拟机。
  • 没有使用标志位(或者叫程序状态字)

 附带一提寄存器统一是32位或64位,目前按32位设计指令。


 

指令集

很随意的命名为VL指令集,代表变长低级指令集。此外编译器已经写好,可以将其对应的LASM汇编语言编译到VL字节码。

目前指令约80条,包含寄存器移动、内存读取储存、加减乘除、移位、位运算、跳转、分支、寄存器窗口操作和系统指令。

个人感觉VL指令集功能应该在精简和复杂指令集之间。

正如前面所说,指令是依附于机器的,所以指令的Call和Return的操作都比较特殊,还有特殊的寄存器窗口移动指令,但没有栈操作指令

因为面向的是软件层面,且是实时解释执行的,这些指令的字节码都尽量比较规整,例如操作码是1字节的,两个寄存器各4bit拼一起也是1字节,尽量避免读取译码的额外操作。指令不是像大多精简指令集一样定长,而是以字节为单位,长度在1-6字节之间。这会导致指令执行增加一点变数,速度受到影响,但节约了大量的空间所以也不用心疼。

这是一些字节码的安排(靠右为低字节):

 


 

设计指令集

指令集的组成和设计指令集的过程我放到下一篇文章中详细说明。

下下篇文章,我会继续介绍LVM虚拟机的实现过程和优化心得。

 

posted @ 2020-02-18 13:00  H5L0  阅读(2202)  评论(0编辑  收藏  举报