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 |
最后看看1到100累加程序:
sto n 1 |
weed语法规则
原则是编写weed解释器尽可能简单, 所以这里的语法特别让人崩溃。
1.weed语言有指令和注释组成,
2.指令或者注释前不能出现空白字符, 在空行不可出现空白字符。
3.一个指令占用一行, 不能在一行编写多个指令。
4.寄存器命名必须用字母开头, 可以包含字母数字和下划线, 必须少于31个字符
存取指令
weed规定存取指令sto必须在程序的开头部分, 其他指令的前面, 书写格式为:
sto 寄存器名称 初始值 |
sto通过寄存器的初始值推断其数据类型。
所以sto指令要求必须初始化寄存器, 寄存器的数据类型不可修改。
sto相当于高级语言的定义变量 var var1 = 0之类的, 要求必须初始化变量, 不可var var1这样。
输入输出和完成指令
done指令没有参数, 功能是结束当前程序。
输入指令严重依赖参数的数据类型,而且不支持输入布尔类型的值, 如果数据类型不匹配程序崩溃, 比如:
sto num 0 |
数学运算指令
数学运算指令有两个参数, 工作方式和高级语言的(+=)(-=)等等一样, 把运算结果写道第一个参数。
所以要求第一个参数必须是数字类型的寄存器。
比较运算指令
比较运算指令有三个参数。
运算指令 结果寄存器 参数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号指令开始执行 |
如果你耐心的看到这里应该对weed语言有了个大概的理解了, 那么接下来我们动手去实现这个虚拟机软件。