课程回顾

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. fiddler抓https包

    若手机端安装证书后还是无法抓取到https请求,请注意手机端证书开关是否开启: eg:ios 设置---通用---关于本机---证书信任设置:开启证书信任 若还是无法抓包,则可以进行一下操作: 给fi ...

  2. mysql主从脚本

    #!/bin/bash #auto make install mysql AB Replication #by author ale #-- :: MYSQL_SOFT="mariadb m ...

  3. nginx入门之编译安装

    nginx是什么 nginx是一个开源的,支持高性能,高并发的www服务和代理服务软件.它是一个俄罗斯人lgor sysoev开发的,作者将源代码开源出来供全球使用. nginx比它大哥apache性 ...

  4. DataTable序列化

    DataTable是复杂对象,无法直接序列化,必须通过其他的方式来实现 下面介绍一下常用的几种方式 1.先转换为List,再序列化List 下面是DataTable转换为List的方法 protect ...

  5. Spring MVC随笔记录

    根据https://blog.csdn.net/abc997995674/article/details/80353410整理 @ModelAttribute 可以用在方法.方法参数上,也可以和@re ...

  6. del_cursor 批量删除游标

    declare   cursor [del_cursor] is select a.*, a.rowid row_id from [table_name] a order by a.rowid;   ...

  7. PhoenixFD插件流体模拟——UI布局【Gird】详解

    流体网格 本文主要讲解Grid折叠栏中的内容 主要内容 Overview 综述 Parameters 参数 General 普通参数 Example: Scene Scale Example: Gri ...

  8. linux学习笔记:linux常用的命令

    2018-11-19                                      常见命令快速查询一览表 命令 功能 ls 列出目录内容 cat 链接文件并打印到标准输出设备上(通常用来 ...

  9. Java 数据类型与运算符

    JAVA数据类型分基本(内置)数据类型和引用数据类型. 区别: 基本数据类型在被创建时,在栈上给其划分一块内存,将数值直接存储在栈上. 引用数据类型在被创建时,首先要在栈上给其引用(句柄)分配一块内存 ...

  10. 1-蓝桥杯套路-java

    决定参加蓝桥杯用java了,当然得重新刷点题目,熟悉一下,以后要是考研失败了,可能回去找java的工作!!! 经验贴: 1. https://blog.csdn.net/wqy20140101/art ...