课程回顾
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)的偏移量。
具体实现:
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))
]
- 打印方法:调用c函数处理。
print((4, (true, 3))) (* 需要处理嵌套的情况 *)
- 具体实现:
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;
}
- 比较方法:调用c函数处理。
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));
]