What & How & Why

差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录前一修订版
后一修订版
前一修订版
cs:comp_n_arch:courses:fnti_ii:week_1 [2025/05/23 01:15] – [Two tiers compilation] codingharecs:comp_n_arch:courses:fnti_ii:week_1 [2025/05/24 02:17] (当前版本) – [Memory segments] codinghare
行 4: 行 4:
 VM(//Virtual Machine//)是 Jack 语言的中间层,其负责将 high-level 语言转化为汇编:\\ \\  VM(//Virtual Machine//)是 Jack 语言的中间层,其负责将 high-level 语言转化为汇编:\\ \\ 
 {{ :cs:comp_n_arch:courses:fnti_ii:levels.jpg?600 |}} {{ :cs:comp_n_arch:courses:fnti_ii:levels.jpg?600 |}}
-====Compilcation Overview====+====Complication Overview==== 
 +>We can only see a short distance ahead, but we can see plenty there that needs to be done. --//Alan Turing//
 ===Two tiers compilation=== ===Two tiers compilation===
 为什么会有这种架构: 为什么会有这种架构:
   * 计算器的构成不同,导致机器语言不同   * 计算器的构成不同,导致机器语言不同
   * 直接面向机器语言的编译器是 machine language depended,编译器实现不同。   * 直接面向机器语言的编译器是 machine language depended,编译器实现不同。
 +<WRAP center round tip 100%>
 +就应用上来说,这种设计下的高级语言都有**跨平台**的优势。
 +</WRAP>
 +
 ==Tier I: virtual machine== ==Tier I: virtual machine==
 //VM// 的理念就是将所有的高级语言翻译为一种抽象化的代码://VM code//(bytecode)。在这个层级中,编译器不关心具体的机器语言实现,而是将所有的计算机统一视作虚拟机(//Virtual Machine// //VM// 的理念就是将所有的高级语言翻译为一种抽象化的代码://VM code//(bytecode)。在这个层级中,编译器不关心具体的机器语言实现,而是将所有的计算机统一视作虚拟机(//Virtual Machine//
行 16: 行 21:
   * Jack compiler: Jack 语言 -> VM code   * Jack compiler: Jack 语言 -> VM code
   * VM emulator:VMcode to PC & Hack computer   * VM emulator:VMcode to PC & Hack computer
 +====VM Abstraction: the Stack====
 +VM code 有两点需要关注:
 +  * VM code 需要足够的接近高级语言层,这样编译器的实现会相对简单
 +  * VM code 也需要足够接近底层,这样 VM implementaion 也会相对简单
 +===Stack machine abstraction===
 +//Stack machine abstraction// 是一种抽象结构,能够在上述两点要求中找到较为平衡的位置。其包含架构部分和命令部分
 +==Stack 的基础操作==
 +Stack 由一块 RAM 和一个 Stack 组成。
 +Stack 拥有两种基础操作:
 +  * ''push'':在最高点之上添加元素
 +  * ''pop'':移除最高点的元素
 +{{ :cs:comp_n_arch:courses:fnti_ii:stack_op.svg?400 |}}
 +==Stack 的运算==
 +基于上述两种基础操作,Stack 中定义了一系列算术运算。这些算术运算的基础流程为:
 +  * 将所有需要 argument 从 stack 中 pop 出来
 +  * 计算结果
 +  * 将结果 push 回 stack
 +比如下面的命令 ''add'' ,实质是将 stack 最上面的两层元素进行相加。其流程为:
 +    * 先将两个元素都 pop 出来
 +    * 进行加法运算
 +    * 在 push 到原有的 stack 里
 +{{ :cs:comp_n_arch:courses:fnti_ii:stack_add.svg?300 |}}
 +\\
 +常见的命令:
 +  * 算术:''add'' / ''sub'' / ''neg''
 +  * 逻辑:''eq'' / ''gt'' / ''lt'' | ''and'' / ''or'' / ''not''
 +<WRAP center round info 100%>
 +  * 常见的命令分几个部分:算术 / 逻辑运算,内存相关运算,Branching & 函数相关运算
 +  * 这一系列的命令序列是基于高级语言的内容,通过编译器转化而来
 +  * 运算的算子按 top-most 的顺序来计算:也就是左算子是栈顶值,而右算子是栈顶的下一个值
 +</WRAP>
 +
 +<WRAP center round box 100%>
 +可以看到一个有趣的现象,Stack 的结构非常利于将复合运算分解为子运算的过程,用序列化的指令表达出来。
 +</WRAP>
 +====VM Abstraction: Memory Segments====
 +**//为什么需要 memory segment?//**\\ \\ 
 +我们在编程中通常会根不同作用域的变量打交道。比如下面的 jack 语言:
 +<code clike>
 +static inst s1, s2;
 +function int bar(int x, int y)
 +{
 +    var int a, b, c;
 +    ...
 +    let c = s1 + y;
 +    ...
 +}
 +</code>
 +如果翻译为 VM code,这几个变量的作用域实际上完全不同。有些是需要在函数运行完毕后销毁的局部变量,而有些则是需要保存下来全局变量:
 +<code armasm>
 +push s1 // global
 +push y // argument
 +add
 +pop c // local
 +</code>
 +===Memory segments===
 +本课的 VM 设计中提出了 memory segment 这一概念来解决该问题。其原理是将不同类型的变量存储到对应的 memory segment 中。比如上面的例子:\\ \\ 
 +{{ :cs:comp_n_arch:courses:fnti_ii:mem_seg_1.svg?300 |}}
 +\\ 
 +实际上通过 memory segment 对所有的变量进行了一次分组。因此对应的命令实际上可以改写为:
 +<code asm>
 +push static 0 // s1
 +push argument 1 // y
 +add
 +pop local 2 // c
 +</code>
 +实际上,VM 编译器无法识别变量名,在翻译的过程中,所有的变量名都会被解释为 ''segment + index'' 的形式。也就是说,stack 的命令对应的就是指定 segment 的某一个部分。因此,push 和 pop 的基础语义可以做进一步的延伸:
 +  * ''push'':将指定内存区域的内容送到 stack 的栈顶
 +  * ''pop'':将栈顶的元素移除出 stack,并送入到指定内存区域中
 +比如下面的例子:
 +<code armasm>
 +push local 1 // 将 local[1] 的内容推到栈顶
 +pop local 1 // 将栈顶的内容移除,并存储到 local[1]
 +</code>
 +本课的 VM 提供了八种类型的 memory segments:
 +<code armasm>
 +local // 局部变量
 +argument // 参数变量
 +this // 类对象
 +that // 
 +constant // 常量
 +static //
 +</code>
 +<WRAP center round important 100%>
 +  * 注意 ''constant'' 后面的数字不是 ''index'',而是对应的常数。 比如:''push constant 1'' 意味着往栈顶加入常数 ''1''
 +  * 双目运算的算子出栈后**不会**写回对应的 segment
 +</WRAP>
 +====VM Implementation: the Stack====