[swarthmore cs75] Compiler 3 – Cobra
课程回顾
Swarthmore学院16年开的编译系统课,总共10次大作业。本随笔记录了相关的课堂笔记以及第5次大作业。
- 增加了bool数据表示和比较运算符的支持,具体语法参考下图:
- 第一种int和bool数据表示的方法:用2个字来表示一个int或bool类型的值。
比如:要表示int数值,可以先存入一个flag=1,再存入具体的数值37。最后返回flag的指针,在C语言的接口做相应的处理。
- 第二种表示int和bool数据表示的方法:使用Tag Bit。
如下图:True表示为:0x80000001。False表示为:0x00000001。int值则左移一位(末尾为0表示int)。
注意:加法和乘法运算,运算结果分别被扩大了2倍和4倍。
- 实现LessThan:
第一种:利用cmp和jg跳转指令。
第二种:首先做减法,然后与上0x80000000,再加上bool的flag。
- 实现is_bool(x):
- 为CPrim2增加类型检查:
对于加法运算,需要满足两个操作数都是整数,因为末尾为0的表示int数据,可以用如下与运算来进行类型检查。
- 提前分配存储空间:
由于生成的汇编代码是作为C运行时环境(C Runtime Stack)中的一个函数被调用,要提前为需要的变量分配存储空间。
也就是要将esp移动到某个位置N, 所以需要计算嵌套变量的最大深度N。
编程作业
本次大作业是为Cobra语言实现一个小型编译器,Cobra增加了比较运算符(>、=、<),类型检查(num/bool)以及溢出判断(int)等功能。
-
具体语法
<expr> := | let <bindings> in <expr> | if <expr>: <expr> else: <expr> | <binop-expr> <binop-expr> := | <identifier> | <number> | true | false | add1(<expr>) | sub1(<expr>) | isnum(<expr>) | isbool(<expr>) | print(<expr>) | <expr> + <expr> | <expr> - <expr> | <expr> * <expr> | <expr> < <expr> | <expr> > <expr> | <expr> == <expr> | ( <expr> ) <bindings> := | <identifier> = <expr> | <identifier> = <expr>, <bindings>
-
抽象语法
type prim1 = | Add1 | Sub1 | Print | IsNum | IsBool type prim2 = | Plus | Minus | Times | Less | Greater | Equal type expr = | ELet of (string * expr) list * expr | EPrim1 of prim1 * expr | EPrim2 of prim2 * expr * expr | EIf of expr * expr * expr | ENumber of int | EBool of bool | EId of string
-
抽象语法的ANF形式
type immexpr = | ImmNumber of int | ImmBool of bool | ImmId of string and cexpr = | CPrim1 of prim1 * immexpr | CPrim2 of prim2 * immexpr * immexpr | CIf of immexpr * aexpr * aexpr | CImmExpr of immexpr and aexpr = | ALet of string * cexpr * aexpr | ACExpr of cexpr
-
语义分析
- true 表示为常量 0xFFFFFFFF。
- false 表示为常量 0x7FFFFFFF。
- 数字最后一位为0,最高位为符号位 , 例如: 2 表示为 0x00000004。
- print函数需要能打印出正确的值,例如:print(true)显示true,print(false)显示false,print(-2)显示-2。
- -, +, *, <, 以及 > 会抛出错误,如果操作数不是整数。
- add1 以及 sub1 会抛出错误,如果表达式运算结果不是整数。
- +, -, 以及 * 会抛出错误,如果运算结果溢出;其中,整形的表示的范围为:-2^30 ~ 2^30-1;(注意:乘法可以先算数右移1,避免溢出,再计算结果)。
另外:在输入整形时,也应该做溢出判断。如下图:
- if 会抛出错误,如果条件表达式不是布尔类型。
-
代码例子
程序 输出 0 == false false 1 == true false 0 == 0 true false == false true 1 < 2 true 2 < 1 false let x = 1 in
let y = print(x + 1) in
print(y + 2)2
4
4if 0 == false: true else: false false isnum(let x = 40 in x) true isbool(let x = true in x) true let c1 = true in
let c2 = false in
(let x = print(if c1: 5 + 5 else: 6 * 2) in
(let y = print(if c2: x * 3 else: x + 5) in
(x + y)))10
15
25add1(true) Error: expected a number if 1: true else: false Error: expected a boolean in if, got 1 1073741823 + 1 Error: arithemetic overflow
注:整形最大为:2^30 - 1-1073741824 - 1 Error: arithemetic overflow
注:整形最小为:-2^30536870912 * 2 Error: arithemetic overflow -536870912 * 2 -1073741824 (-2) * 1 -2
注:根据目前的数据存储格式,乘积比实际数据大4倍;
负数右位移需使用算数右移,而不是逻辑右移。
main.c
- 这里实现的是一个运行时环境, 主要用于打印以及错误处理;编译后的代码将作为一个函数our_code_starts_here()来调用。
#include <stdio.h> #include <stdlib.h> extern int our_code_starts_here() asm("our_code_starts_here"); extern int print(int val) asm("print"); extern void error_non_number(int val) asm("error_non_number"); extern void error_non_boolean(int val) asm("error_non_boolean"); extern void error_overflow(int val) asm("error_overflow"); int print(int val) { switch (val) { case 0xffffffff: printf("true"); break; case 0x7fffffff: printf("false"); break; default: printf("%d", val >> 1); break; } printf("\n"); return val; } void error_non_number(int val) { fprintf(stderr, "Error: expected a number"); exit(1); } void error_non_boolean(int val) { fprintf(stderr, "Error: expected a boolean in if, got %d", val); exit(1); } void error_overflow(int val) { fprintf(stderr, "Error: arithemetic overflow"); exit(1); } int main(int argc, char** argv) { int result = our_code_starts_here(); print(result); return 0; }
compile.ml
-
为了在运行时能调用编译后的代码,首先需要做一些处理,模板如下:
push ebp # esp-4, old ebp存入[esp] mov ebp, esp 计算最大变量嵌套深度N,移动ESP到-4 * N处。 ... mov esp, ebp # 更新后esp指向old ebp pop ebp # 恢复old ebp,同时esp+4 ret 增加错误处理的label
当函数our_code_starts_here()被调用时,stack中的情况如下图:
代码如下:
let compile_to_string prog = let anfed = (anf prog (fun i -> ACExpr(CImmExpr(i)))) in let prelude = "section .text extern error extern print extern error_non_number extern error_non_boolean extern error_overflow global our_code_starts_here our_code_starts_here:" in let stack_setup = [ IPush(Reg(EBP)); IMov(Reg(EBP), Reg(ESP)); ISub(Reg(ESP), Const(4 * count_vars anfed)); ] in let postlude = [ IMov(Reg(ESP), Reg(EBP)); IPop(Reg(EBP)); IRet; ILabel("internal_error_non_number"); IPush(Reg(EAX)); ICall("error_non_number"); ILabel("internal_error_non_boolean"); IPush(Reg(EAX)); ICall("error_non_boolean"); ILabel("internal_error_overflow"); IPush(Reg(EAX)); ICall("error_overflow"); ] in let compiled = (acompile_expr anfed 1 []) in let as_assembly_string = (to_asm (stack_setup @ compiled @ postlude)) in sprintf "%s%s\n" prelude as_assembly_string
-
生成汇编代码
功能描述 实现逻辑 整数类型检查 将eax中的数值与1进行与运算,如果为0就是整数;否则,跳转到internal_error_non_number标签 布尔类型检查 将eax中的数值与1进行与运算,如果不为1,就跳转到internal_error_non_boolean 内置函数的实现 print 编译为: push eax; call print;
isnum 编译为:((eax&1)<<31) xor 0xffffffff
isbool 编译为:((eax&1)<<31) or 0x7fffffff
溢出判断 超过-2^30 ~ 2^30-1的表示范围,OF标志位置1,使用jo跳转到internal_error_overflow 比较运算符的实现 1<2 编译为:```((1-2)&0x80000000) let const_true = HexConst(0xffffffff) let const_false = HexConst(0x7fffffff) let rec acompile_step (s : cexpr) (si : int) (env : (string * int) list) : instruction list = let postlude = [ IAnd(Reg(EAX), Const(1)); ICmp(Reg(EAX), Sized(DWORD_PTR, Const(0))); IJne("internal_error_non_number"); ] in match s with | CPrim1(op, e) -> let eGen = acompile_imm e si env in let prelude = eGen @ postlude in begin match op with | Add1 -> prelude @ eGen @ [IAdd(Reg(EAX), Const(2));] | Sub1 -> prelude @ eGen @ [ISub(Reg(EAX), Const(2));] | Print -> eGen @ [IPush(Reg(EAX)); ICall("print");] | IsNum -> eGen @ [IAnd(Reg(EAX), Const(1)); IShl(Reg(EAX), Const(31)); IXor(Reg(EAX), const_true);] | IsBool -> eGen @ [IAnd(Reg(EAX), Const(1)); IShl(Reg(EAX), Const(31)); IOr(Reg(EAX), const_false);] end | CPrim2(op, left, right) -> let lGen = acompile_imm left si env in let rGen = acompile_imm right si env in let prelude = lGen @ postlude @ rGen @ postlude in let imma = acompile_imm_arg right si env in begin match op with | Plus -> prelude @ lGen @ [IAdd(Reg(EAX), imma);] @ [IJo("internal_error_overflow");] | Minus -> prelude @ lGen @ [ISub(Reg(EAX), imma);] @ [IJo("internal_error_overflow");] | Times -> prelude @ lGen @ [ISar(Reg(EAX), Const(1)); IMul(Reg(EAX), imma);] @ [IJo("internal_error_overflow");] | Less -> prelude @ lGen @ [ISub(Reg(EAX), imma); IAnd(Reg(EAX), HexConst(0x80000000)); IOr(Reg(EAX), const_false);] | Greater -> prelude @ lGen @ [ISub(Reg(EAX), imma); IAnd(Reg(EAX), HexConst(0x80000000)); IAdd(Reg(EAX), const_true);] | Equal -> let end_label = gen_temp "end" in lGen @ [ ICmp(Reg(EAX), imma); IMov(Reg(EAX), const_false); IJne(end_label); IMov(Reg(EAX), const_true); ILabel(end_label); ] end | CIf(cond, thn, els) -> let preclude = acompile_imm cond si env in let else_label = gen_temp "else" in let endif_label = gen_temp "endif" in preclude @ [ IAnd(Reg(EAX), Const(1)); ICmp(Reg(EAX), Sized(DWORD_PTR, Const(1))); ] @ preclude @ [ IJne("internal_error_non_boolean"); ICmp(Reg(EAX), const_false); IJe(else_label) ] @ acompile_expr thn si env @ [ IJmp(endif_label); ILabel(else_label); ] @ acompile_expr els si env @ [ ILabel(endif_label); ] | CImmExpr(i) -> acompile_imm i si env
参考资料
starter-cobra
x86 assembly - How to use SHR to halve a negative number effectively?