コンパイラ作成(93) int型→double型の変換

今回の目標

型変換を頑張るよ。

// int型→double型
int func(void)
{
    return 5;
}

int func2(double a)
{
    return 10;
}

int main()
{
    int a = 12;
    double x, *p = &x;
    x = a;
    printf("x = %f\n", x);
    x = x * 2;
    printf("x = %f\n", x);
    x = x - a;
    printf("x = %f\n", x);
    x = x + (1 + 2);
    printf("x = %f\n", x);
    x = x + func();
    printf("x = %f\n", x);
    x = 7 + x;
    printf("x = %f\n", x);
    x = x + func2(2);
    printf("x = %f\n", x);
    *p = 55;
    printf("x = %f\n", x);
}

いろんな場合があるんで結構大変だよ。多分これで全部の場合を網羅できてると思うんだけど自信はないな。int型からの変換はcvtsi2sdってのでできるみたいなんで、これをあっちこっちに入れてくよ。

代入

まずは一番単純な場合。

  # 代入のコード生成
  def codegen_assign(el)
    if el.size != 3 then perror end
    if el[2] == nil then
      perror "broken expression"
    end
    type_r = codegen_el [el[2]]
    if el[0].kind_of?(Array) then
      if el[0][0].str == "*" then
        codegen "  sub  rsp, 8"
        codegen "  push rax"
        type_l = codegen_el [el[0][1]]
        if !is_pointer_type? type_l then
          perror
        end
        type_l = type_l[0,type_l.length-1]
        codegen "  mov  r10, rax"
        codegen "  pop  rax"
        codegen "  add  rsp, 8"
        if type_r == "void*" && is_pointer_type?(type_l) then
          type_r = type_l    # 暗黙の型変換
        elsif type_r == "int" && type_l == "char" then
          type_r = type_l    # 暗黙の型変換
        elsif type_r == "int" && type_l == "double" then
          codegen "  cvtsi2sd xmm8, eax"
          type_r = type_l    # 暗黙の型変換
        end
        if type_l != type_r then perror end
        if is_pointer_type? type_l then
          codegen "  mov  qword ptr [r10], rax"
        elsif type_l == "double"
          codegen "  movsd  qword ptr [r10], xmm8"
        elsif type_l == "char"
          codegen "  mov  byte ptr [r10], al"
        else
          codegen "  mov  dword ptr [r10], eax"
        end
      else
        perror
      end
    else
      if el[0].kind != TK::ID then perror end
      v = get_var el[0].str
      if v == nil then
        perror "undeclared variable \"" + el[0].str + "\""
      end
      type_l = v[0]
      if type_r == "void*" && is_pointer_type?(type_l) then
        type_r = type_l    # 暗黙の型変換
      elsif type_r == "int" && type_l == "char" then
        type_r = type_l    # 暗黙の型変換
      elsif type_r == "int" && type_l == "double" then
        codegen "  cvtsi2sd xmm8, eax"
        type_r = type_l    # 暗黙の型変換
      end
      if type_l != type_r then perror end
      if is_pointer_type? type_l then
        codegen "  mov  qword ptr [rbp - " + v[1].to_s + "], rax"
      elsif type_l == "double"
        codegen "  movsd  qword ptr [rbp - " + v[1].to_s + "], xmm8"
      elsif type_l == "char"
        codegen "  mov  byte ptr [rbp - " + v[1].to_s + "], al"
      else
        codegen "  mov  dword ptr [rbp - " + v[1].to_s + "], eax"
      end
    end
    return type_l
  end

単なる代入とポインターを介した代入があるんで二箇所修正。

二項演算

今回一番修正が多かった場所。

  # 式のコード生成(二項演算の右側被演算子)
  def codegen_els(op, operand, type_l)
    # 右被演算子を評価
    type_r = "int"
    if operand.kind_of?(Array) then
      if operand[0].size == 2 && operand[0].kind == TK::ID && operand[1].str == "()" then
        if type_l == "double" then
          codegen "  sub    rsp, 16"
          codegen "  movsd  [rsp], xmm8"
          type_r = codegen_func operand
          if type_r == "double" then
            codegen "  movsd  xmm9, xmm8"
            str = "xmm9"
          else
            codegen "  mov  r10d, eax"
            str = "r10d"
          end
          codegen "  movsd  xmm8, [rsp]"
          codegen "  add    rsp, 16"
        else
          codegen "  sub  rsp, 8"
          codegen "  push rax"
          type_r = codegen_func operand
          if type_r == "double" then
            codegen "  movsd  xmm9, xmm8"
            str = "xmm9"
          else
            codegen "  mov  r10d, eax"
            str = "r10d"
          end
          codegen "  pop  rax"
          codegen "  add  rsp, 8"
        end
      else
        if type_l == "double" then
          codegen "  sub    rsp, 16"
          codegen "  movsd  [rsp], xmm8"
          type_r = codegen_el operand
          if type_r == "double" then
            codegen "  movsd  xmm9, xmm8"
            str = "xmm9"
          else
            codegen "  mov  r10d, eax"
            str = "r10d"
          end
          codegen "  movsd  xmm8, [rsp]"
          codegen "  add    rsp, 16"
        else
          codegen "  sub  rsp, 8"
          codegen "  push rax"
          type_r = codegen_el operand
          if type_r == "double" then
            codegen "  movsd  xmm9, xmm8"
            str = "xmm9"
          else
            codegen "  mov  r10d, eax"
            str = "r10d"
          end
          codegen "  pop  rax"
          codegen "  add  rsp, 8"
        end
      end
    elsif operand.kind == TK::ID then
      v = get_var operand.str
      if v == nil then
        perror "undeclared variable \"" + operand.str + "\""
      end
      type_r = v[0]
      if is_pointer_type? type_r then
        str = "qword ptr [rbp - " + v[1].to_s + "]"
      elsif type_r == "double" then
        str = "qword ptr [rbp - " + v[1].to_s + "]"
      elsif type_r == "char" then
        str = "byte ptr [rbp - " + v[1].to_s + "]"
      else
        str = "dword ptr [rbp - " + v[1].to_s + "]"
      end
    elsif operand.kind == TK::NUMBER then
      str = operand.str
    elsif operand.kind == TK::STRING then
      type_r = "char*"
      label = addliteral operand.str
      codegen "  lea  r10, "+label
      str = "r10"
    elsif operand.kind == TK::FLOAT then
      # 浮動小数点数リテラル
      type_r = "double"
      label = addliteral operand.str, :double
      str = "qword ptr [#{label}]"
    else
      perror
    end

    # 型チェック
    if type_l == "double" && type_r == "int" then
      if str != "r10d" then
        codegen "  mov    r10d, #{str}"
      end
      codegen "  cvtsi2sd xmm9, r10d"
      str = "xmm9"
      type_r = type_l
    elsif type_l == "int" && type_r == "double" then
      codegen "  cvtsi2sd xmm8, eax"
      type_l = type_r
    end
    if type_l != type_r then
      if is_pointer_type?(type_l) && type_r == "int" then
        if op.str != "+" && op.str != "-" then
          perror "mismatched types to binary operation"
        end
      elsif type_l == "int" && is_pointer_type?(type_r) then
        if op.str != "+" && op.str != "-" then
          perror "mismatched types to binary operation"
        end
      else
        perror "mismatched types to binary operation"
      end
    elsif is_pointer_type? type_l then
      perror "mismatched types to binary operation"
    elsif type_l == "double" && op.str == "%" then
      perror "mismatched types to binary operation"
    end

    reg = "eax"
    if type_l == "double" then
      reg = "xmm8"
    elsif type_l == "char" then
      reg = "al"
    end

    # 左被演算子と右被演算子とで計算
    ostr = mnemonic op, type_l

    if op.str == "==" then
      codegen "  " + ostr + " #{reg}, " + str
      if type_l == "double" then
        codegen "  sete  al"
        codegen "  setnp r10b"
        codegen "  and   al, r10b"
        codegen "  and   eax, 1"
        type_l = "int"
      else
        codegen "  sete al"
        codegen "  and  eax, 1"
      end
    elsif op.str == "!=" then
      codegen "  " + ostr + " #{reg}, " + str
      if type_l == "double" then
        codegen "  setne al"
        codegen "  setp  r10b"
        codegen "  or    al, r10b"
        codegen "  and   eax, 1"
        type_l = "int"
      else
        codegen "  setne al"
        codegen "  and  eax, 1"
      end
    elsif op.str == "<" then
      codegen "  " + ostr + " #{reg}, " + str
      if type_l == "double" then
        codegen "  setb  al"
        codegen "  and   eax, 1"
        type_l = "int"
      else
        codegen "  setl al"
        codegen "  and  eax, 1"
      end
    elsif op.str == ">" then
      codegen "  " + ostr + " #{reg}, " + str
      if type_l == "double" then
        codegen "  seta  al"
        codegen "  and   eax, 1"
        type_l = "int"
      else
        codegen "  setg al"
        codegen "  and  eax, 1"
      end
    elsif op.str == "<=" then
      codegen "  " + ostr + " #{reg}, " + str
      if type_l == "double" then
        codegen "  setbe al"
        codegen "  and   eax, 1"
        type_l = "int"
      else
        codegen "  setle al"
        codegen "  and  eax, 1"
      end
    elsif op.str == ">=" then
      codegen "  " + ostr + " #{reg}, " + str
      if type_l == "double" then
        codegen "  setae al"
        codegen "  and   eax, 1"
        type_l = "int"
      else
        codegen "  setge al"
        codegen "  and  eax, 1"
      end
    elsif type_l != "double" && (op.str == "*" || op.str == "/" || op.str == "%") then
      mov = "mov"
      if type_r == "char" then mov = "movsx" end
      if type_l == "char" then codegen  "  movsx  eax, al" end
      if str != "r10d" then codegen "  #{mov}  r10d, " + str end
      codegen "  mov  r11, rdx"
      if op.str == "/" || op.str == "%" then
        codegen "  cdq"
      end
      codegen "  " + ostr + " r10d"
      if op.str == "%" then
        codegen "  mov  eax, edx"
      end
      codegen "  mov  rdx, r11"
    else
      if is_pointer_type?(type_l) && type_r == "int" then
        # ポインタ型+int型の処理
        size = sizeof type_l[0,type_l.length-1]
        if str == op.str then
          codegen "  " + ostr + " rax, " + (str.to_i*size).to_s
        elsif str == "r10d" then
          codegen "  movsx r10, r10d"
          if size == 4 then
            codegen "  shl  r10, 2"
            codegen "  " + ostr + " rax, r10"
          elsif size == 8 then
            codegen "  shl  r10, 2"
            codegen "  " + ostr + " rax, r10"
          else
            (0...size).each do
              codegen "  " + ostr + " rax, r10"
            end
          end
        else
          codegen "  mov  r10d, " + str
          codegen "  movsx r10, r10d"
          if size == 4 then
            codegen "  shl  r10, 2"
            codegen "  " + ostr + " rax, r10"
          elsif size == 8 then
            codegen "  shl  r10, 3"
            codegen "  " + ostr + " rax, r10"
          else
            (0...size).each do
              codegen "  " + ostr + " rax, r10"
            end
          end
        end
      elsif type_l == "int" && is_pointer_type?(type_r) then
        # int型+ポインタ型の処理
        size = sizeof type_r[0,type_r.length-1]
        codegen "  movsx rax, eax"
        if size == 1 then
        elsif size == 4 then
          codegen "  shl  rax, 2"
        elsif size == 8 then
          codegen "  shl  rax, 3"
        else
          codegen "  mov  r11, rax"
          (0...size-1).each do
            codegen "  add  rax, r11"
          end
        end
        codegen "  " + ostr + " rax, " + str
        type_l = type_r
      else
        codegen "  " + ostr + " #{reg}, " + str
       end
    end
    return type_l
  end

右側被演算子の処理のところをまず修正。さらに型チェックの前にint型→double型の変換処理を追加。それとmnemonic メソッドを呼ぶタイミングを変更。

関数コール

codegen_funcの引数の処理部。

    # 引数を順番に評価する
    (0...operand.size-2).each do |i|
      savegpr, savexmm = @numuseregs, @numusexmms
      @numuseregs, @numusexmms = i-xmm, xmm
      type = codegen_el operand[i+2]
      @numuseregs, @numusexmms = savegpr, savexmm
      if f != nil then
        if f[1][i] == "size_t" then
          codegen "  movsx rax, eax"
          type = f[1][i]
        elsif f[1][i] == "void*" && is_pointer_type?(type) then
          type = f[1][i]
        end
        if f[1][i] == "double" then
          codegen "  cvtsi2sd xmm8, eax"
          type = f[1][i]
        end
        if type != f[1][i] then perror "incompatible type parameter" end
      end
      if type == "int" || type == "char" then
        codegen "  mov  #{@regs32[i-xmm]}, eax"
      elsif type == "size_t"
        codegen "  mov  #{@regs64[i-xmm]}, rax"
      elsif is_pointer_type?(type) then
        codegen "  mov  #{@regs64[i-xmm]}, rax"
      elsif type == "double" then
        codegen "  movsd  xmm#{xmm}, xmm8"
        xmm += 1
      else
        perror
      end
    end

引数の型と実際の型を見比べて必要なら変換。

動作テスト
~/myc$ myc p26.myc
~/myc$ ./p26
x = 12.000000
x = 24.000000
x = 12.000000
x = 15.000000
x = 20.000000
x = 27.000000
x = 37.000000
x = 55.000000
~/myc$ 

大丈夫そうだね。

// モンテカルロ法による円周率の計算
extern double sqrt(double x);
extern size_t time(size_t *t);
extern void srand48(size_t seed);
extern double drand48();
int main()
{
    size_t t;
    srand48(time(&t));
    int count = 0;
    for(int i = 0; i < 1000000; i = i + 1) {
        double x = drand48();
        double y = drand48();
        if( x*x + y*y <= 1.0 ) count = count + 1;
    }
    printf("pi = %10.8f\n",count / 1000000.0 * 4.0);
}

前に作ったのをちょっと修正してみたよ。以前はcountがdouble型になってたんだけどint型に変更。今回の修正でこれもコンパイル通るはず。

~/myc$ myc p27.myc
~/myc$ ./p27
pi = 3.14179600
~/myc$ 

うん、ちゃんと計算されてる。あ、そうだ。int型とdouble型をミックスして計算できるようになったから、drand48じゃなくて普通のrandを呼べるようになったのか。確か今のLinuxの標準関数だとrandの方が精度の良い乱数が生成されるんだったよね。今度やってみよう。
これでint型→double型の変換はできるようになったけど、まだ未対応のとこがあるよ。return文での型変換。今回それもやっちゃおうと思ったんだけど、今のmycは関数定義の情報全然参照してなくて、その辺からやらないと駄目なんで今回は諦めたよ。次回はその辺をやるつもりだよ。