课程回顾

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
    4
    if 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
    25
    add1(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^30
    536870912 * 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的更多相关文章

  1. [swarthmore cs75] Compiler 6 – Garbage Snake

    课程回顾 Swarthmore学院16年开的编译系统课,总共10次大作业.本随笔记录了相关的课堂笔记以及第9次大作业. 赋值的副作用:循环元组 下面的代码展示了Python3是如何处理循环列表(pri ...

  2. [swarthmore cs75] Compiler 6 – Fer-de-lance

    课程回顾 Swarthmore学院16年开的编译系统课,总共10次大作业.本随笔记录了相关的课堂笔记以及第8次大作业. First-class function: It treats function ...

  3. [swarthmore cs75] Compiler 5 – Egg-eater

    课程回顾 Swarthmore学院16年开的编译系统课,总共10次大作业.本随笔记录了相关的课堂笔记以及第7次大作业. 抽象语法: 存储方式: 栈中的数据如果最后三位(tag bits)是001表示元 ...

  4. [swarthmore cs75] Compiler 4 – Diamondback

    课程回顾 Swarthmore学院16年开的编译系统课,总共10次大作业.本随笔记录了相关的课堂笔记以及第6次大作业. 函数声明 增加函数声明.函数调用的抽象语法:在转换成anf之前还要检查函数声明和 ...

  5. [swarthmore cs75] Compiler 2 – Boa

    课程回顾 Swarthmore学院16年开的编译系统课,总共10次大作业.本随笔记录了相关的课堂笔记以及第4次大作业. A-Normal Form 在80年代,函数式语言编译器主要使用Continua ...

  6. [swarthmore cs75] Compiler 1 – Adder

    课程回顾 Swarthmore学院16年开的编译系统课,总共10次大作业.本随笔记录了相关的课堂笔记以及第3次大作业. 编译的过程:首先解析(parse)源代码,然后成抽象语法树(AST),再生成汇编 ...

  7. [swarthmore cs75] inlab1 — Tiny Compiler

    课程回顾 Swarthmore学院16年开的编译系统课,总共10次大作业.本随笔记录了inlab1的实践过程. tiny compiler 这个迷你的编译器可以将一个源文件,编译成可执行的二进制代码. ...

  8. [swarthmore cs75] Lab 1 — OCaml Tree Programming

    课程回顾 Swarthmore学院16年开的编译系统课,总共10次大作业.本随笔记录了相关的课堂笔记以及第2大次作业. 比较两个lists的逻辑: let rec cmp l ll = match ( ...

  9. [swarthmore cs75] Lab 0 Warmup & Basic OCaml

    课程回顾 Swarthmore学院16年开的编译系统课,总共10次大作业.本随笔记录了相关的课堂笔记以及第1次大作业. 什么是编译 编译就是执行Program->Program'转换的过程,如下 ...

随机推荐

  1. nginx+ flume

    nginx 作用: 做负载均衡  nginx和lvs的区别:nginx可以做反向代理 1.上传nginx安装包  tar -zxvf tengine-2.1.02.安装环境  依赖 gcc opens ...

  2. 一些matlab命令

    expand: R = exprnd(MU) returns an array of random numbers chosen from the exponential distribution w ...

  3. linux,无法进行写操作怎么办?read-only file system

    一句命令搞定: mount -o remount rw /

  4. odoo KeyError

    one2many字段对应的表名不存在造成

  5. String笔记

    String string = new String("Hello World!"); replace('e', '*') //替换字符串 String newStr = stri ...

  6. Android横竖屏切换生命周期变化

    1. AndroidMenifest没有设置configChanged属性. 竖屏启动(横屏启动相同): onCreate -->onStart-->onResume 切换横屏: onPa ...

  7. CSRedisCore 在net core中的使用

    背景:与net core配套的StackExchange.Redis客户端总是间歇性的发生timeout异常. 由complexer单例对象创建的IDatabase对象,在产生Timeout异常后会导 ...

  8. python中的继承和多态

    继承 继承的表现方式: class Animal(): pass class Cat(Animal): #animal是cat的父类,也可以说是基类 pass print(Cat.__bases__) ...

  9. sourcetree 跳过首次登录

    定位到用户缓存数据目录:(需要在文件夹选项中 开启不隐藏文件夹和不隐藏文件扩展名) 一般为: C:\Users\{用户名}\AppData\Local\Atlassian 进入sourcetree目录 ...

  10. Swift.Operator-and-Items-in-Swift(1)

    Operator and Item 1. ..< a for-in loop and the half-open range operator (..<) // Check each pa ...