[swarthmore cs75] Compiler 5 – Egg-eater
课程回顾
Swarthmore学院16年开的编译系统课,总共10次大作业。本随笔记录了相关的课堂笔记以及第7次大作业。
- 抽象语法:
- 存储方式:
- 栈中的数据如果最后三位(tag bits)是001表示元组。
- 堆中元组的起始地址最后三位都是000。
- 通过引入ESI寄存器可以实现堆区数据的存取。
编程作业
本次的大作业是实现Egg-Eater语言:支持函数,数字,布尔值以及元组;元组的语法(egg)非常像一个🥚,故得其名。
-
元组的实现细节
-
抽象语法:
type expr = ... | ETuple of expr list | EGetItem of expr * expr type prim1 = | IsTuple type cexpr = ... | CTuple of immexpr list | CGetItem of immexpr * immexpr
具体实现:
let rec anf_list (es : expr list) (k : immexpr list -> aexpr) : aexpr = match es with | [] -> k [] | e::rest -> anf e (ImmHole(fun imm -> anf_list rest (fun imms -> k (imm::imms)))) | ETuple(elts) -> anf_list elts (fun imms -> fill_c h (CTuple(imms))) | EGetItem(coll, index) -> anf coll (ImmHole(fun limm -> anf index (ImmHole(fun rimm -> (fill_c h (CGetItem(limm, rimm)))))))
-
堆布局(Heap Layout)
(4 bytes) (4 bytes) (4 bytes) (4 bytes) -------------------------------------------------------- | # elements | element_0 | element_1 | ... | element_n | --------------------------------------------------------
首先,需要明确一点:ESI的起始地址的最后三位必须是000,为了达到目的,可以将ESI指向:(堆区起始地址+8)&0xFFFFFFF8。
ESI = 0xada0 0xada0 | --------------------| | *** used space ***| ---------------------
其次,如果存储一个元组后,ESI指向的地址的最后三位不是000,就需要加上padding(dead space),使ESI指向地址的最后三位满足条件。
ESI = 0xadac 0xada0 0xadac | | --------------------|--------------------------------------| | *** used space ***| 0x00000002 | 0x00000004 | 0xFFFFFFFF | -----------------------------------------------------------|
计算方式为:ESI = (堆区尾地址+4)&0xFFFFFFF8。
ESI = 0xadb0 0xada0 0xadb0 | (size) (value 4) (value true) | --------------------|-------------------------------------------------| | *** used space ***| 0x00000002 | 0x00000008 | 0xFFFFFFFF | padding | ----------------------------------------------------------------------|
- 语义分析
首先对()中的表达式求值,然后对[]中的表达式求值。(6, 7, 8, 9)[1 + 2]
检查表达式(6, 7, 8, 9)的tag bits,即是否是元组,如果不是跳转到错误处理label。
检查index是否是数字,如果不是跳转到错误处理label。
检查index是否在0~size-1这个范围内,否则跳转到错误处理的label。
进行求值并返回index位置的元素。计算逻辑为:mov eax, [eax + ecx * 4 + offset],eax是元组首地址+1 (包括tag bits), ecx存储了元素下标;当然还需要加上第一个元素(size)的偏移量。
具体实现:
打印方法:调用c函数处理。let check_tuple arg = let label_end = gen_temp "end" in [ IMov(Reg(EAX), arg); IAnd(Reg(EAX), Const(0x00000007)); ICmp(Reg(EAX), Const(0x00000001)); IJne(error_non_tuple) ] | CTuple(elts) -> let size = List.length elts in [ IMov(RegOffset(0, ESI), Sized(DWORD_PTR, Const(size))); ] @ List.flatten (List.mapi (fun i elt -> let arg = acompile_imm_arg elt si env in [ IMov(Reg(EAX), Sized(DWORD_PTR, arg)); IMov(RegOffset(4 * (i + 1), ESI), Reg(EAX)); ] ) elts) @ [ IMov(Reg(EAX), Reg(ESI)); IAdd(Reg(EAX), Const(1)); IAdd(Reg(ESI), Const((size + 1) * 4)); IAdd(Reg(ESI), Const(4)); IAnd(Reg(ESI), HexConst(0xFFFFFFF8)); ] | CGetItem(coll, index) -> let coll_as_arg = acompile_imm_arg coll si env in let index_as_arg = acompile_imm_arg index si env in let checked = check_tuple coll_as_arg in checked @ [ IMov(Reg(ECX), index_as_arg); (* Check that the index value is a number *) ITest(Reg(ECX), Const(0x00000001)); IJnz(error_non_int); ISar(Reg(ECX), Const(1)); ICmp(Reg(ECX), Const(0)); IJl(error_too_small); IMov(Reg(EAX), coll_as_arg); ICmp(Reg(ECX), RegOffset(-1, EAX)); IJge(error_too_large); IMov(Reg(EAX), RegOffsetReg(EAX, ECX, 4, 3)) ]
具体实现:print((4, (true, 3))) (* 需要处理嵌套的情况 *)
比较方法:调用c函数处理。const int TRUE = 0xFFFFFFFF; const int FALSE = 0x7FFFFFFF; int ispair(int val) { return (val & 0x00000007) == 0x00000001; } void print_rec(int val) { if ((val & 0x00000001) ^ 0x00000001) { printf("%d", val >> 1); } else if (val == TRUE) { printf("true"); } else if (val == FALSE) { printf("false"); } else if (ispair(val)) { int *p = (int *) (val - 1); // size printf("("); for (size_t i = 1; ;i++) { print_rec(*(p + i)); if (i < *p) printf(", "); else break; } printf(")"); } else { printf("Unknown value: %#010x", val); } } int print(int val) { print_rec(val); printf("\n"); return val; }
具体实现:let t = (4, 5) in t == t (* 地址相等,返回true *) (4,5) == (4,5) (* 会返回false,因为在Diamondback中比较的是地址 *)
| Equal -> let leave_false = gen_temp "equals" in [ IPush(Sized(DWORD_PTR ,left_as_arg)); IPush(Sized(DWORD_PTR ,right_as_arg)); ICall("equal"); IAdd(Reg(ESP), Const(8)); ]
int equal(int val1, int val2) { if (val1 == val2) { return TRUE; } else if (ispair(val1) && ispair(val2)) { int *p1 = (int *)(val1 - 1); // size of pair1 int *p2 = (int *)(val2 - 1); // size of pair2 if (*p1 != *p2) { return FALSE; } else { for (size_t i = 1; i <= *p1; i++) if (equal(*(p1 + i), *(p2 + i)) == FALSE) return FALSE; return TRUE; } } else { return FALSE; } }
-
-
测试代码
- 为了测试能够正确编译新的语法,实现了下面几个方法:
def link(first, rest): (first, rest) def length(l): if l == false: 0 else: 1 + length(l[1]) def sum(l): if l[1] == false: l[0] else: l[0] + sum(l[1]) def append(l1, l2): let data = l1[0], next = l1[1] in if (next == false): link(data, l2) else: link(data, append(next, l2)) def reverse(l, next): if l[1] == false: link(l[0], next) else: reverse(l[1], link(l[0], next))
- 接下来,可以用下面的方法来调用定义的函数:
let mylist1 = link(1, link(2, link(3, false))) in let mylist2 = link(4, link(5, link(6, false))) in let newlist = append(mylist1, mylist2) in reverse(newlist, false)
- 下面展示了调用reverse这个函数(反转一个链表)的规约过程:
reverse(link(1, link(2, link(3, link(4, link(5, link(6, false)))))), false) reverse(link(2, link(3, link(4, link(5, link(6, false))))), link(1, false)) reverse(link(3, link(4, link(5, link(6, false)))), link(2, link(1, false))) reverse(link(4, link(5, link(6, false))), link(3, link(2, link(1, false)))) reverse(link(5, link(6, false)), link(4, link(3, link(2, link(1, false))))) reverse(link(6, false), link(5, link(4, link(3, link(2, link(1, false)))))) (6, link(5, link(4, link(3, link(2, link(1, false))))))
- 为了测试能够正确编译新的语法,实现了下面几个方法:
遗留问题
在实现Diamondback中,我发现'>', '<'的实现逻辑有误;现做修改,例如:1 < 1 和 1 > 1 都应该返回false。
| Less ->
[
IMov(Reg(EAX), left_as_arg);
ISub(Reg(EAX), right_as_arg);
IAnd(Reg(EAX), HexConst(0x80000000));
IOr(Reg(EAX), HexConst(0x7FFFFFFF));
]
| Greater ->
[
IMov(Reg(EAX), right_as_arg);
ISub(Reg(EAX), left_as_arg);
IAnd(Reg(EAX), HexConst(0x80000000));
IOr(Reg(EAX), HexConst(0x7FFFFFFF));
]