【CS 143 Compiler】Assignment 4.1:阅读源码 & 理解任务
汇编码布局
Spim 手册中的图示如下,编译器输出的 目标文件(Object File)布局为:
代码存放在 .text
段,而代码的总入口是 _start
,_start
会做一些准备工作(包括初始化垃圾处理程序, 初始化 Main 函数) 最后调用 Main 对象实例 中的 main 方法 开始执行 COOL 程序.
_start
作为入口符号是链接器的默认设置(也可以自定义)。参考:A Whirlwind Tutorial on Creating Really Teensy ELF Executables for Linux (muppetlabs.com)
_start
也是大多数编程语言的 runtime 的入口函数。_start
作为引导程序,包括一些准备工作,然后引导进入主程序。参考: gcc - What is the use of _start() in C? - Stack Overflow
runtime 不一定是和主程序并行的进程或线程,也包括为主程序代码作准备工作和收尾的辅助代码。
目标代码如何运行
下面就是引导程序 _start
。根据 COOL 手册中提示, runtime 代码包含在 [cool root]/lib/trap.handler
中。_start
作为开始入口,必然也在其中。
.globl __start
__start:
li $v0 9 # 指定系统调用代码 9: sbrk
move $a0 $zero # $a0 用来指定 sbrk 的入参, 这里值为 0, 表示分配内存大小
syscall # sbrk: 程序启动时, 向 OS 申请一块内存, $v0 保存了这块内存的地址
move $a0 $sp # initialize the garbage collector
li $a1 MemMgr_REG_MASK
move $a2 $v0
jal _MemMgr_Init # sets $gp and $s7 (limit) 初始化了垃圾处理程序
la $t9 _exception_handler # Exception: Set uncaught Exception Address
la $a0 Main_protObj # create the Main object
jal Object.copy # Call copy
addiu $sp $sp -4
sw $a0 4($sp) # save the Main object on the stack
move $s0 $a0 # set $s0 to point to self
jal Main_init # initialize the Main object 执行 Main.init()
jal Main.main # Invoke main method 执行 main()
要点:
.globl _start
中.globl
用来声明 符号_start
对此文件外的文件可见.syscall
指令抛出一个 陷入 (trap) 来调用底层的系统调用. 调用哪个系统调用 (syscall编码) 由$v0
中的内容指定.- 这一块内存 是在 程序进程的内存空间中的 Heap 中分配的. (C 语言中 malloc 就是基于这个系统调用实现的.)
- 申请了 大小为 0 的堆空间, 实际上是得到了 Heap 的首地址. 相应的
$sp
是栈顶, 实际上也是 堆 的结束地址. - 程序载入时 OS 会将所有寄存器清零,只有
$sp
会赋值为栈顶第一个空余位置。
“代码生成”(cgen)生成什么
通过文件流,输出一个完整的汇编代码。这段代码中同样包含 .data
.text
这些段,它们会被链接器和 runtime 代码 链接在一起。
cgen 程序总入口为:
// cgen.cc
void program_class::cgen(ostream &os)
{
// spim wants comments to start with '#'
os << "# start of generated code\n";
initialize_constants();
CgenClassTable *codegen_classtable = new CgenClassTable(classes,os);
os << "\n# end of generated code\n";
}
初始只执行了 CgenClassTable 的构造函数:
// cgen.cc
CgenClassTable::CgenClassTable(Classes classes, ostream& s) : nds(NULL) , str(s)
{
stringclasstag = 0 /* Change to your String class tag here */;
intclasstag = 0 /* Change to your Int class tag here */;
boolclasstag = 0 /* Change to your Bool class tag here */;
enterscope();
if (cgen_debug) cout << "Building CgenClassTable" << endl;
install_basic_classes();
install_classes(classes);
build_inheritance_tree();
code();
exitscope();
}
enterscope()
与 exitscope()
之间,前三步用依赖图的形式把所有的 Class_
组织起来。
重点在于 code()
(如下)。code_xxx()
执行的顺序,也是汇编代码从上到下的顺序。前三个(以及第一个注释区)都是生成 .data
段 的代码;而 后面的部分是生成 .text
段的代码。
// cgen.cc
void CgenClassTable::code()
{
if (cgen_debug) cout << "coding global data" << endl;
code_global_data();
if (cgen_debug) cout << "choosing gc" << endl;
code_select_gc();
if (cgen_debug) cout << "coding constants" << endl;
code_constants();
// Add your code to emit
// - prototype objects
// - class_nameTab
// - dispatch tables
if (cgen_debug) cout << "coding global text" << endl;
code_global_text();
// Add your code to emit
// - object initializer
// - the class methods
// - etc...
}
code_constants()
中注释为写入编译时需要使用的常量,包括编译过程中通过 stringtable::add_string
创建的 Int、Str、Bool 值的 Symbol。因为 Bool 只有两个值,所以 true 也存进来了。