[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)|0x7fffffff=0xffffffff
1>2 编译为:((1-2)&0x80000000)+0xffffffff=0x7fffffff
= 使用cmp进行比较(可以比较不同类型,具体参考Equal的实现)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?
[swarthmore cs75] Compiler 3 – Cobra的更多相关文章
- [swarthmore cs75] Compiler 6 – Garbage Snake
课程回顾 Swarthmore学院16年开的编译系统课,总共10次大作业.本随笔记录了相关的课堂笔记以及第9次大作业. 赋值的副作用:循环元组 下面的代码展示了Python3是如何处理循环列表(pri ...
- [swarthmore cs75] Compiler 6 – Fer-de-lance
课程回顾 Swarthmore学院16年开的编译系统课,总共10次大作业.本随笔记录了相关的课堂笔记以及第8次大作业. First-class function: It treats function ...
- [swarthmore cs75] Compiler 5 – Egg-eater
课程回顾 Swarthmore学院16年开的编译系统课,总共10次大作业.本随笔记录了相关的课堂笔记以及第7次大作业. 抽象语法: 存储方式: 栈中的数据如果最后三位(tag bits)是001表示元 ...
- [swarthmore cs75] Compiler 4 – Diamondback
课程回顾 Swarthmore学院16年开的编译系统课,总共10次大作业.本随笔记录了相关的课堂笔记以及第6次大作业. 函数声明 增加函数声明.函数调用的抽象语法:在转换成anf之前还要检查函数声明和 ...
- [swarthmore cs75] Compiler 2 – Boa
课程回顾 Swarthmore学院16年开的编译系统课,总共10次大作业.本随笔记录了相关的课堂笔记以及第4次大作业. A-Normal Form 在80年代,函数式语言编译器主要使用Continua ...
- [swarthmore cs75] Compiler 1 – Adder
课程回顾 Swarthmore学院16年开的编译系统课,总共10次大作业.本随笔记录了相关的课堂笔记以及第3次大作业. 编译的过程:首先解析(parse)源代码,然后成抽象语法树(AST),再生成汇编 ...
- [swarthmore cs75] inlab1 — Tiny Compiler
课程回顾 Swarthmore学院16年开的编译系统课,总共10次大作业.本随笔记录了inlab1的实践过程. tiny compiler 这个迷你的编译器可以将一个源文件,编译成可执行的二进制代码. ...
- [swarthmore cs75] Lab 1 — OCaml Tree Programming
课程回顾 Swarthmore学院16年开的编译系统课,总共10次大作业.本随笔记录了相关的课堂笔记以及第2大次作业. 比较两个lists的逻辑: let rec cmp l ll = match ( ...
- [swarthmore cs75] Lab 0 Warmup & Basic OCaml
课程回顾 Swarthmore学院16年开的编译系统课,总共10次大作业.本随笔记录了相关的课堂笔记以及第1次大作业. 什么是编译 编译就是执行Program->Program'转换的过程,如下 ...
随机推荐
- nginx+ flume
nginx 作用: 做负载均衡 nginx和lvs的区别:nginx可以做反向代理 1.上传nginx安装包 tar -zxvf tengine-2.1.02.安装环境 依赖 gcc opens ...
- 一些matlab命令
expand: R = exprnd(MU) returns an array of random numbers chosen from the exponential distribution w ...
- linux,无法进行写操作怎么办?read-only file system
一句命令搞定: mount -o remount rw /
- odoo KeyError
one2many字段对应的表名不存在造成
- String笔记
String string = new String("Hello World!"); replace('e', '*') //替换字符串 String newStr = stri ...
- Android横竖屏切换生命周期变化
1. AndroidMenifest没有设置configChanged属性. 竖屏启动(横屏启动相同): onCreate -->onStart-->onResume 切换横屏: onPa ...
- CSRedisCore 在net core中的使用
背景:与net core配套的StackExchange.Redis客户端总是间歇性的发生timeout异常. 由complexer单例对象创建的IDatabase对象,在产生Timeout异常后会导 ...
- python中的继承和多态
继承 继承的表现方式: class Animal(): pass class Cat(Animal): #animal是cat的父类,也可以说是基类 pass print(Cat.__bases__) ...
- sourcetree 跳过首次登录
定位到用户缓存数据目录:(需要在文件夹选项中 开启不隐藏文件夹和不隐藏文件扩展名) 一般为: C:\Users\{用户名}\AppData\Local\Atlassian 进入sourcetree目录 ...
- Swift.Operator-and-Items-in-Swift(1)
Operator and Item 1. ..< a for-in loop and the half-open range operator (..<) // Check each pa ...