======Assembler Roadmap====== //Week 6 notes// ---- ====Assembly Languages and Assemblers==== * //Assembler//(汇编器) 通过 //Cross-compiling// 的方式实现,即使用已存在的计算机生成新计算机的 Assembler。 * Assemblers 是软件层的第一层 ===Basic Assmbler Logic=== 汇编器重复以下的功能: - 读取下一条汇编命令 - 将命令分解为几个部分 - 查找每部分对应的二进制代码 - 将这些代码组合为一整条机器语言命令 - 输出该机器语言命令 ==读取逻辑== 以下面的汇编代码为例: // Start processing the table Load R1, 18 - 读取时,会按行读取。读取时会忽略掉 whitespace 内容,得到的内容会存到一个数组中 - 将读取的指令分为几个部分(字符串),比如:''Load'', ''R1'', ''18'' - 通过command-opcode 的对照表来进行翻译(主要是命令),某些数字可以直接进行翻译 - 将所有的内容组合在一起,形成机器语言指令,并进行输出 \\ {{ :cs:comp_n_arch:courses:fnti_i:ass_logic.svg?450 |}} ===Symbols=== 汇编语言中存在着 //symbols//,以便提高程序的可读性,比如: * Labels:''JMP loop'' * Variables:''Load R1, weight'' ==symbol table== 而在汇编器翻译的过程中,这些 symbols 都会被替代为其对应的**地址**,比如 ''loop'' 对应的是跳转后的指令地址,''weight'' 对应的是在 RAM 里面的寄存器所属地址。这种翻译也是依赖对应的 map 来实现的: \\ \\ {{ :cs:comp_n_arch:courses:fnti_i:symbol_table.svg?150 |}} ==allocation of variables== 第一次进入 symbol table 的变量需要汇编器为其安排 table 中的位置。通常,汇编器会在 RAM 中找到第一个可用的内存单元(//Unallocated memory cell//)来存放该变量。 ==Labels== Label 需要处理额外的两件事: * 保证 Label 下的所有命令都是基于 Label 的地址 * 如果引用 Label 的位置发生在第一次看到 Label 定义之前(也就是表内还没有 记录Label 地址的时候),有两种处理方式: * 将未定义的标签放入一个待解决的 table 中,将其标记为尚未解析的状态,之后看到该 label 的定义时,再回填正确地址到引用该 label 的位置 * 或是做两遍全扫描(C++ 类就是这么干的): 第一遍只看标签的定义,将其与地址关联起来,第二遍才是真正的将指令翻译为机器码的过程 ====Hack Assembly Language==== * A-instruction * C-instruciton * Symbols: * 预定义的(//Pre-defined//) 的 symbol * label 声明 * 变量声明 ===Assembly program 中的元素=== 需要汇编器处理的元素有: * White space:会被处理中被忽略 * Empty lines / indentation * In-line & line comments * Instructions * Symbols * References * Label ====Instruction Handling==== ===外部总逻辑=== 对于 ROM 所有的指令: * 首先对其进行解析,并将其拆分为指定的部分 * 其次根据指令的类型,来进行对应的转换 * 将所有转化的结果组合起来,并输出到对应的文件中 ===Translating A-instructions=== A 指令的结构表现为: ''@ + value'' ,因此将 A 指令转化为二进制取决于: * 如果 value 是十进制常量(比如 ''@21''),那么直接转成对应的 ''15'' 位的二进制常量 * 如果 value 是 symbol,交给之后的 symbol 区域处理。 ===Translating C-instructions=== 首先回顾一下,C 指令的机器语言组成如下图所示: \\ \\ {{ :cs:comp_n_arch:courses:fnti_i:c-ins.svg?400 |}} \\ 因此,如果希望翻译 C 指令,那么首先需要做的就是将汇编语言按照机器语言的组成来进行划分。通常这部分会由 //Parser//(解析器)来完成。以下面的汇编语句为例: // 将 D + 1 的值存储到 M 和 D 寄存器中 MD = D + 1 接下来按如下的顺序进行翻译: - C 指令中 ''15-13'' 这三位永远都是 ''111'',可以将其先填入 - 对 ''comp'' 这部分 ''12..6''(''a'' 和 ''comp'')进行翻译。这部分通过对照 C 指令的 table 来获取二进制代码。比如例子中的计算部分 ''D+1'',对应的 ''a'' 是 ''0'',对应的六位 ''comp'' 是 ''011111'',那么这一部分对应的代码就是 ''0011111'' - 对 ''dest'' 这部分 ''5..3'' 进行翻译。这部分的翻译也是通过查表:比如例子中的 ''MD'',对应的是 ''011'' - 对 ''jump'' 这部分 ''2..0'' 进行翻译,同样也是通过查表:本例中没有跳转部分(null),对应的 bits 是 ''000'' - 最后得到的翻译结果是 ''111 0011111 011 000'' ====Symbols Handling==== 之前介绍过,根据 symbol 的内容,可以将 symbol 划分为三种类型: * //varible symbol//:代表着用于数据操作的**内存位置**,该地址映射由汇编器管理 * //label symbol//:代表着 ''goto'' 跳转的**指令位置** * //pre-defined symbols//:代表着系统占用的特殊内存位置 ===pre-defined symbols=== 由于这些 symbol 只会在 A 指令中出现,因此只需要根据对照表,将 symbol 转化为对应的地址值即可:比如 // R1 corresponds address 1 @R1->@1 ===Label symbols=== label 类型的 symbol 主要用于: * 标记 goto 命令的终点,比如 ''@loop''。这类 symbol 会被直接替换成其值(与 pre-defined symbol 类似),比如下面例子中的 ''LOOP'' 会被替换为 ''16'' * 对应的伪代码声明,比如 ''(loop)'':这类 label 通常不会进行翻译,但汇编器遇到这类 label 时,会将其对应的 symbol 与其起始行关联起来。比如: @i // line 0 M=1 @sum M=0 (LOOP) // not a instruction line @i // line 4 ... @LOOP //line 16 这里的 ''(LOOP)'' 标签,对应的 block 起始行为 ''4'';这个关系将会使用一张表来维护: ^Symbol^value^ |LOOP|4| ===Variable symbols=== 变量类型的 symbol 指非提前定义的,且没有使用 driective 在别处进行定义的(比如 ''LOOP'')的 symbol。上面例子中的 ''@i'',''@sum'' 都是变量类型的 symbol。之前提到过,这类 symbol 都会被存储在一个独特地址的内存单元中(地址从 ''16'' 开始)。因此这类 symbol 对应的值,是他们所在内存单元的地址: ^Symbol^value^ |i|16| |sum|17| 由于变量可能会被多次用到,当第一次用到时,该变量 symbol 和其对应地址会被加入到映射表中;之后再使用时,汇编器会从映射表中查找到该对应关系,并把 symbol 翻译为对应的地址。 ===Symbol table=== 可以看到的是,上述的所有翻译都依赖于一种数据结构://Symbol table//,用于建立翻译的映射关系。Hack 计算机的 Symbol table 通过几个阶段进行构造: * 初始化:这个阶段会建立一张空表,并将 pre-defined symbol 的映射关系写入表中 * 寻找 label 声明:这个阶段(//first pass//)会扫描整个指令序列,将所有的 label 声明找出来 * 当遇到 ''('' 的时候,就识别为 label 的声明 * 同时维护一个计数器,对行数计数。记录的是**已经扫描过的行数**:比如之前的 ''(LOOP)'',其对应的数据是之前已经扫描过的行数 ''4'' * 然后接着扫描,直到遇到下一个 ''('' 开头的 label 声明,重复上面的过程,直到构建出整个 Symbol table 中的 label 声明数据 * 变量处理(//second pass//):这个阶段会再次扫描指令序列,寻找**变量**,再将其与 RAM地址关联,并写入关系到 //Symbol table// 中: * 新变量会直接加入 symbol table * 已存在的变量会直接读取 symbol table 中的对应映射关系 ===Assembly process=== // init - construct an empty symbol table - add the pre-defined symbols to the symbol table // first pass // adding label declaration to the symbol table - scan the program - For each instruction of the form(xxx): - add the pair(xxx, address) to the symbol table, where address is the number of the instruction following (xxx) // second pass // adding variable symbol to the symbol table - scan the program - For each instruction: - if the insturction is @symbol, look up the symbol in the table - if (symbol, value) is found, use value to complete the instruction's translation - if not found - add(symbol, n) to the symbol table - use n to complete the instruction translation - ++n - if the instruction is a C-instruction, complate the instruction translation - write the translated instruction to the output file