dm1299

[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
    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)
    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?

posted on 2019-02-14 19:46  dm1299  阅读(138)  评论(0编辑  收藏  举报

导航