コンパイラ作成(81) double型の単純な四則演算

今回の目標

double型の計算を実装するよ。

// double型

int main()
{
    double a = 2.7, b = 5.1, c;
    c = a + b;
    printf("%f + %f = %f\n", a, b, c);
    c = a - b;
    printf("%f - %f = %f\n", a, b, c);
    c = a * b;
    printf("%f * %f = %f\n", a, b, c);
    c = a / b;
    printf("%f / %f = %f\n", a, b, c);
    c = a + 2.0;
    printf("%f + 2.0 = %f\n", a, c);
    c = a - 2.0;
    printf("%f - 2.0 = %f\n", a, c);
    c = a * 2.0;
    printf("%f * 2.0 = %f\n", a, c);
    c = a / 2.0;
    printf("%f / 2.0 = %f\n", a, c);
}

今回は単純な四則演算。

コード生成部
  # ニーモニック
  def mnemonic(op, type)
    if type == "double" then
      if op.str == "+" then
        return "addsd "
      elsif op.str == "-" then
        return "subsd "
      elsif op.str == "*" then
        return "mulsd "
      elsif op.str == "/" then
        return "divsd "
      elsif op.str == "%" then
        return "divsd "
      elsif op.str == "==" then
        return "cmp "
      elsif op.str == "!=" then
        return "cmp "
      elsif op.str == "<" || op.str == "<" || op.str == ">" || op.str == "<=" || op.str == ">=" then
        return "cmp "
      else
        perror "unknown operator \"" + op.str + "\""
      end
    else
      if op.str == "+" then
        return "add "
      elsif op.str == "-" then
        return "sub "
      elsif op.str == "*" then
        return "imul"
      elsif op.str == "/" then
        return "idiv"
      elsif op.str == "%" then
        return "idiv"
      elsif op.str == "==" then
        return "cmp "
      elsif op.str == "!=" then
        return "cmp "
      elsif op.str == "<" || op.str == "<" || op.str == ">" || op.str == "<=" || op.str == ">=" then
        return "cmp "
      else
        perror "unknown operator \"" + op.str + "\""
      end
    end
  end

  # 式のコード生成(二項演算の右側被演算子)
  def codegen_els(op, operand, type_l)
    ostr = mnemonic op, 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
        codegen "  sub  rsp, 8"
        codegen "  push rax"
        type_r = codegen_func operand
        codegen "  mov  r10d, eax"
        codegen "  pop  rax"
        codegen "  add  rsp, 8"
      else
        codegen "  sub  rsp, 8"
        codegen "  push rax"
        type_r = codegen_el operand
        codegen "  mov  r10d, eax"
        codegen "  pop  rax"
        codegen "  add  rsp, 8"
      end
      str = "r10d"
    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, :float
      str = "qword ptr [#{label}]"
    else
      perror
    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

    # 左被演算子と右被演算子とで計算
    if op.str == "==" then
      codegen "  " + ostr + " #{reg}, " + str
      codegen "  sete al"
      codegen "  and  eax, 1"
    elsif op.str == "!=" then
      codegen "  " + ostr + " #{reg}, " + str
      codegen "  setne al"
      codegen "  and  eax, 1"
    elsif op.str == "<" then
      codegen "  " + ostr + " #{reg}, " + str
      codegen "  setl al"
      codegen "  and  eax, 1"
    elsif op.str == ">" then
      codegen "  " + ostr + " #{reg}, " + str
      codegen "  setg al"
      codegen "  and  eax, 1"
    elsif op.str == "<=" then
      codegen "  " + ostr + " #{reg}, " + str
      codegen "  setle al"
      codegen "  and  eax, 1"
    elsif op.str == ">=" then
      codegen "  " + ostr + " #{reg}, " + str
      codegen "  setge al"
      codegen "  and  eax, 1"
    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型のときはeaxとr10dで計算してたのを、double型のときはxmm8とxmm9で計算するようにした。それと剰余演算はエラーになるようにチェック。

動作テスト
~/myc$ myc p9.myc
~/myc$ ./p9
2.700000 + 5.100000 = 7.800000
2.700000 - 5.100000 = -2.400000
2.700000 * 5.100000 = 13.770000
2.700000 / 5.100000 = 0.529412
2.700000 + 2.0 = 4.700000
2.700000 - 2.0 = 0.700000
2.700000 * 2.0 = 5.400000
2.700000 / 2.0 = 1.350000
~/myc$ 

大丈夫そうだね。今回は単純な式にしか対応できなかったよ。2.0 + 3.5 * 4.1みたいなちょっと複雑なだけで計算できなくなる。次回はその辺頑張るよ。