本 Wiki 开启了 HTTPS。但由于同 IP 的 Blog 也开启了 HTTPS,因此本站必须要支持 SNI 的浏览器才能浏览。为了兼容一部分浏览器,本站保留了 HTTP 作为兼容。如果您的浏览器支持 SNI,请尽量通过 HTTPS 访问本站,谢谢!
这是本文档旧的修订版!
We can only see a short distance ahead, but we can see plenty there that needs to be done. –Alan Turing
为什么会有这种架构:
就应用上来说,这种设计下的高级语言都有跨平台的优势。
VM 的理念就是将所有的高级语言翻译为一种抽象化的代码:VM code(bytecode)。在这个层级中,编译器不关心具体的机器语言实现,而是将所有的计算机统一视作虚拟机(Virtual Machine)
本层级负责具体的,基于计算机类型的实现。具体的工作是将虚拟机中的 bytecode 转化为基于特定机器语言的映射实现。
VM code 有两点需要关注:
Stack machine abstraction 是一种抽象结构,能够在上述两点要求中找到较为平衡的位置。其包含架构部分和命令部分
基于上述两种基础操作,Stack 中定义了一系列算术运算。这些算术运算的基础流程为:
比如下面的命令 add
,实质是将 stack 最上面的两层元素进行相加。其流程为:
add
/ sub
/ neg
eq
/ gt
/ lt
| and
/ or
/ not
可以看到一个有趣的现象,Stack 的结构非常利于将复合运算分解为子运算的过程,用序列化的指令表达出来。
为什么需要 memory segment?
我们在编程中通常会根不同作用域的变量打交道。比如下面的 jack 语言:
static inst s1, s2;
function int bar(int x, int y)
{
var int a, b, c;
...
let c = s1 + y;
...
}
如果翻译为 VM code,这几个变量的作用域实际上完全不同。有些是需要在函数运行完毕后销毁的局部变量,而有些则是需要保存下来全局变量:
push s1 // global
push y // argument
add
pop c // local
本课的 VM 设计中提出了 memory segment 这一概念来解决该问题。其原理是将不同类型的变量存储到对应的 memory segment 中。比如上面的例子:
实际上通过 memory segment 对所有的变量进行了一次分组。因此对应的命令实际上可以改写为:
push static 0 // s1
push argument 1 // y
add
pop local 2 // c
实际上,VM 编译器无法识别变量名,在翻译的过程中,所有的变量名都会被解释为 segment + index
的形式。也就是说,stack 的命令对应的就是指定 segment 的某一个部分。因此,push 和 pop 的基础语义可以做进一步的延伸:
push
:将指定内存区域的内容送到 stack 的栈顶pop
:将栈顶的元素移除出 stack,并送入到指定内存区域中比如下面的例子:
push local 1 // 将 local[1] 的内容推到栈顶
pop local 1 // 将栈顶的内容移除,并存储到 local[1]
本课的 VM 提供了八种类型的 memory segments:
local // 局部变量
argument // 参数变量
this // 类对象
that //
constant // 常量
static //
constant
后面的数字不是 index
,而是对应的常数。 比如:push constant 1
意味着往栈顶加入常数 1
。