这一章没多少新内容,主要是按照规则写个汇编器。。。

概述

从第6章开始将开始关注计算机的软件层次,其中,汇编器是最基础的模块。

符号汇编指令和相应的二进制编码之间的关系很直接,所以写一个汇编器不难。唯一的困难是管理用户定义符号并解析到物理内存地址,通常会使用符号表(数据结构是哈希表)来完成,这在许多软件翻译项目中都有用到。

由于二进制码的含义相当晦涩,通常机器语言会指定它的二进制码(Binary codes)和符号助记符(Symbolic mnemonics)。我们不仅可以借助符号读程序,也可以这样写程序而不必使用二进制。我们可以使用一个文本处理程序,将符号解析为二进制机器指令。这个符号就叫做汇编语言(Assembly),这个程序叫做汇编器(Assembler),这个过程叫做汇编。

符号

汇编语言中会出现符号,符号指代内存中的地址。

变量

程序员用到的符号变量名,翻译器会自动分配到内存地址。

标签

程序员用标签标记程序中的不同位置。

符号解析

把有符号的程序转换成无符号的代码。

一个例子:

一个例子
一个例子

汇编器

我们看到汇编器本质上是一个提供翻译服务的文本处理程序。

汇编器需要遵守机器语言规范(machine language specification),执行以下任务(不一定是这个顺序):

  • 将符号指令解析为其基础字段。
  • 对于每个字段,用机器语言生成相应的位。
  • 用内存的数字地址替换所有符号引用(如果有)。
  • 将二进制代码汇编成完整的机器指令。

除了替换符号为地址这一步,其它3步都比较简单。

Hack汇编翻译规范

语法传统和文件格式

二进制文件(Binary code files)

扩展名为hack。一行一个16位01串,指定一个机器语言指令。第n行地址为n(n是从0开始的)。

汇编文件(Assembly language files)

扩展名为asm。

每行为一个Instruction或者 (Symbol)。

常量非负且为10进制,用户定义的符号可以是字母、数字、下划线(_)、点(.)、美元符号($)、冒号(:),不能以数字开头。

//后为单行注释。

空白、空行忽略。

约定助记符、标记名大写,变量名小写。

指令

A
A
C
C
comp
comp
dest
dest
jump
jump

符号

预定义符号(Predefined symbols)

RAM的一部分地址可以用预定义符号指定。

  • 虚拟寄存器(Virtual registers):用R0R15指定RAM[0]RAM[15]。
  • 预定义指针(Predefined pointers):用SP、LCL、ARG、THIS、THAT指定RAM[0]~RAM[4]。
  • I/O指针(I/O pointers):用SCREEN、KBD指定RAM[16384](RAM[0x4000])和RAM[24576](RAM[0x6000]),这是屏幕和键盘的内存映射的基地址。

标记符号(Label symbols)

用户定义的用于表示goto的目的地址的符号,用伪命令“(Xxx)”来声明。

变量符号(Variable symbols)

用户定义的符号Xxx,Xxx不是预定义符号和标记符号,那么就是一个变量,汇编器会分配一个唯一的内存地址,从RAM[0x0010]开始。

API

官方提供了可以参考的API。

Parser

parser
parser
parser
parser

Code

parser
parser

无符号汇编器

用以上2个模块即可。

SymbolTable

就是一个哈希表。

parser
parser

有符号汇编器

用上符号表,按照以下步骤实现有符号汇编器:

  • 初始化:把预定义符号添加到符号表。
  • 第一遍:扫描一遍,把标记符号添加到符号表。
  • 第二遍:扫描一遍,把变量符号(只会出现在A指令)添加到符号表,完成指令的翻译。

项目

用任意编程语言开发一个汇编器,能将asm文件汇编为hack文件。要求和官方提供的汇编器的汇编功能一致。可能用到的工具依然是官方提供的汇编器。。。

(我的代码见github