0%

weed虚拟机 1.前言

weed虚拟机 1.前言

本次我们用C语言做一个最为简单的玩具虚拟机, 进而探索计算机的一些重要概念。

第一步实现一个解释器, 以后也许加上一个编译器, 类似于javac一样的生成.class文件的编译器。

我们知道c/c++生成本地平台能直接执行的二进制可执行文件, 而Java dotnet这些语言生成一个中间代码, 这就是所谓的字节码文件, 包含了相关语言的虚拟指令集。

换句话说, c/c++程序编译生成后使用的是真正在硬件上运行的CPU指令集, 比如X86; AMD64; ARM等等。

而java dotnet生成一个虚拟指令集, 必须有相关的虚拟机程序来解释执行这些虚拟指令集。

在学习编程语言的时候总是这样介绍, 我们的编程语言分为三种大类型, 高级语言; 汇编语言和机器码。

那么我们的工作就是用c这个高级语言编写一个不存在的虚拟汇编语言解释器。

我们给他命名为weed, 毫不起眼的杂草……

weed语言的源文件的后缀名是.we, 可以用我们的weed解释器解释执行, 这个语言包含了最少的指令和数据类型。

weed语言的指令集和数据类型

有人不是说过, 程序有指令和数据组成, 那么我们简单看看weed语言的指令集和数据类型。

  • 数学运算加减乘除和取余数这五种

  • 比较运算 相等不相等小于小于等于大于和大于等于这六种

  • 逻辑运算 并且或者和非这三种

  • 控制运算输入输出存取完成和跳转这四种

    初步总共也就不到20个指令构成了我们的weed语言的全部指令集, 后续根据需要也许会增加新的指令!

    weed语言目前支持三种数据类型

  • number 包含了32bit有符号整数

  • boolean 表示布尔true和false

  • string 表示文本

    初步也就这么简陋, 反正是玩具, 所以秉承够用原则……

weed语言的程序

接下来看看weed语言的一些程序首先是helloWorld程序:

out "hello, world"

数学答题程序:

sto result 0
sto isRight false
out "20 + 30 = ?"
in result
eq isRight result 50
goto isRight 3
out "Not Pass"
done
out "pass"

最后看看1到100累加程序:

sto n 1
sto sum 0
sto isDone false
add sum n
add n 1
el isDone n 100
goto isDone -3
out sum

weed语法规则

原则是编写weed解释器尽可能简单, 所以这里的语法特别让人崩溃。

1.weed语言有指令和注释组成,
2.指令或者注释前不能出现空白字符, 在空行不可出现空白字符。
3.一个指令占用一行, 不能在一行编写多个指令。
4.寄存器命名必须用字母开头, 可以包含字母数字和下划线, 必须少于31个字符

存取指令

weed规定存取指令sto必须在程序的开头部分, 其他指令的前面, 书写格式为:

sto 寄存器名称 初始值

sto通过寄存器的初始值推断其数据类型。

所以sto指令要求必须初始化寄存器, 寄存器的数据类型不可修改。

sto相当于高级语言的定义变量 var var1 = 0之类的, 要求必须初始化变量, 不可var var1这样。

输入输出和完成指令

done指令没有参数, 功能是结束当前程序。

输入指令严重依赖参数的数据类型,而且不支持输入布尔类型的值, 如果数据类型不匹配程序崩溃, 比如:

sto num 0
in num # 输入有符号整数接受, 输入其他任何字符崩溃
sto ok true
in ok # 崩溃, 所以in指令不支持布尔类型

输出指令不支持输出多个值, 不支持格式化输出

数学运算指令

数学运算指令有两个参数, 工作方式和高级语言的(+=)(-=)等等一样, 把运算结果写道第一个参数。

所以要求第一个参数必须是数字类型的寄存器。

比较运算指令

比较运算指令有三个参数。

运算指令 结果寄存器 参数2 参数3

结果寄存器必须为布尔类型, 参数2和参数3必须是数字类型。

看着比较怪异, 不过也没办法, 暂时就这么书写吧。

逻辑运算指令

逻辑and和or跟我们的数学运算指令一样, 把结果写入到第一个参数, 要求第一个参数必须是布尔类型的寄存器。

not指令接受一个布尔类型的寄存器, 反转这个寄存器的值, 和高级语言的(!)一样的效果。

看了这三个weed程序和weed语法后不知道你有何感想? 算数运算; 比较运算这些看着虽然比较怪异但是也能理解, 但是最迷惑的应该是goto指令吧,

取值执行

我们CPU的最基本的运行方式就是取值执行, 每一个指令都有一个编号, CPU按照这个编号一个一个的获取指令然后执行它。

比如一个程序有100个指令, 从1开始编号, CPU从一号指令开始执行, 到100号指令结束, 在这个过程里有个程序计数器控制CPU的当前要执行的指令编号。

如果我们修改这个程序计数器的话可以在这个100个指令里任意跳转, 比如执行到10号指令是一个跳转指令,

goto true 50

这样的话程序计数器被修改为50, 那么CPU下一次执行50号指令, 50号执行完毕后继续执行下一个指令51号52号等等, 也就是直接跳过了从11号指令到49号指令, 这样就可以实现if语句的功能。

循环也是同样的道理, 还是执行到10号指令

goto true 5

这样的话程序只要到了10号指令, 检查条件成立就会调回去到5号指令开始执行, 什么时候条件不成立了就继续执行11号及后面的指令。

所以我们可以动态的修改程序计数器来实现分支语句和循环语句, 程序计数器简写为PC。

那么我们的weed语言虚拟机内部也有一个PC, 但是我们的跳转语句和真正的CPU的pc工作有很大差距。 假设当前pc值为10

goto true 5 # 往前跳到15号指令开始执行
goto true -4 # 往后跳转到6号指令开始执行

如果你耐心的看到这里应该对weed语言有了个大概的理解了, 那么接下来我们动手去实现这个虚拟机软件。