コンパイラ作成(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は関数定義の情報全然参照してなくて、その辺からやらないと駄目なんで今回は諦めたよ。次回はその辺をやるつもりだよ。

コンパイラ作成(92) 引数の無い関数の定義でのvoid

今回の目標

いくつかテストしてる時に気が付いたんだけど、voidを使って関数に引数が無いことを明示する記述の仕方、まだ対応してなかったよ。

// 関数の呼び出し
int answer(void)
{
    //  Answer to the Ultimate Question of Life, the Universe, and Everything
    return 42;
}

int main()
{
    printf("%d\n",answer());
}

簡単にできそうなんでやってみるよ。

function

引数の処理のところを修正するよ。

    # 引数の処理
    kind, str = @lex.gettoken
    loop do
      if kind == TK::SYMBOL && str == ")" then break end
      if kind == TK::TYPE then
        if str == "extern" then perror "invalid 'extern'" end
        type = str
        kind, str = @lex.gettoken
        loop do
          if kind != TK::SYMBOL || str != "*" then break end
          type += str
          kind, str = @lex.gettoken
        end
        if type == "void" \
           && kind == TK::SYMBOL && str == ")" \
           && paratype == []
        then
          break
        end
        paratype << type
        if kind != TK::ID then
          if type == "void" then
            perror "invalid type 'void'"
          else
            perror "wrong parameter name"
          end
        end
        print "para "+str+"\n" if $opt_d
        size = sizeof type
        @lvarsize += size
        parametersize << size
        if check_var str then perror "redefinition parameter \"" + str +"\"" end
        if type == "void" then
          perror "invalid type 'void'"
        end
        set_var str, [type,@lvarsize]
      else
        perror
      end
      if kind != TK::SYMBOL || str != ")" then
        kind, str = @lex.gettoken
      end
      if kind == TK::SYMBOL && str == "," then
        kind, str = @lex.gettoken
      end
    end

voidが一個だけの場合は受け付けるようにして、それ以外の時はエラーになるように修正。初めは単純な処理だったのが、こんがらがった処理になっちゃったなあ。

動作テスト
~/myc$ myc l6.myc
~/myc$ ./l6
42
~/myc$

コンパイル通るようになったよ。もういっちょ行ってみる。

// 関数の呼び出し
int answer(int a,void)
{
    //  Answer to the Ultimate Question of Life, the Universe, and Everything
    return 42;
}

int main()
{
    printf("%d\n",answer());
}

今度はエラーにしなきゃいけないパターン。

~/myc$ myc err44.myc
err44.myc:2:22 error: invalid type 'void'
~/myc$

ちゃんとエラーになったよ。次回はint型からdouble型への変換をやりたいなあ。今回それをやろうと思ってたんだけど、voidが未対応だって気付いたら、気になって気になってついついそっちを先にやっちゃったよ。

コンパイラ作成(91) 浮動小数点数リテラルの処理

今回の目標

重複したリテラルを一個に纏めるよ。

// double型
double dadd(double a, double b)
{
    printf("dadd: %f %f\n", a, b);
    return a + b;
}

int main()
{
    double x = 12.3, y = -2.8;
    printf("%f %f %f\n", x, y, dadd(5.4, 3.2));
    printf("%d %d %f\n", 12, 7, dadd(5.4, 3.2));
}

それとリテラルに付けるラベル名を.L.floatから.L.doubleに変更するよ。

リテラル管理
  # n番目のリテラルのラベル名
  def literallabel(num,type)
    if type == :string then
      l = ".L.str"
    else
      l = ".L.double"
    end
    return "#{l}.#{num}"
  end

  # リテラルをテーブルに追加
  def addliteral(str, type=:string)
    @literaltable.each_with_index do |x, i|
      if x == [type,str] then return literallabel(i,type) end
    end
    num = @literalcnt
    @literalcnt += 1
    @literaltable << [type,str]
    return literallabel(num,type)
  end

  # リテラルを出力
  def genliterals
    @literaltable.each_with_index do |literal, i|
      if literal[0] == :string then
        codegen (literallabel(i,:string) + ":")
        codegen ("  .asciz  \"" + literal[1] + "\"")
      elsif literal[0] == :double then
        codegen (literallabel(i,:double) + ":")
        ieee754binary = [literal[1].to_f]
                        .pack('G').bytes
                        .map{|n| "%08b" % n}
                        .join.to_i(2).to_s(16)
        codegen "  .quad  #{ieee754binary}H    # #{literal[1]}"
      end
    end
    codeflush
  end

addliteralで登録する前に同じのが既にあるかチェックするようにしたよ。

コード生成部

codegen_elfの浮動小数点数リテラルの処理部。

    elsif operand.kind == TK::FLOAT then
      # 浮動小数点数リテラル
      type = "double"
      label = addliteral operand.str, :double
      codegen "  movsd  xmm8, qword ptr [#{label}]"

codegen_elsの浮動小数点数リテラルの処理部。

    elsif operand.kind == TK::FLOAT then
      # 浮動小数点数リテラル
      type_r = "double"
      label = addliteral operand.str, :double
      str = "qword ptr [#{label}]"

floatをdoubleに変更。これトークンがTK::FLOATなんでそれにつられてfloatにしちゃったんだな。失敗失敗。

動作テスト
~/myc$ myc p25.myc
~/myc$ ./p25
dadd: 5.400000 3.200000
12.300000 -2.800000 8.600000
dadd: 5.400000 3.200000
12 7 8.600000
~/myc$

動作は問題ないね。リテラルが纏められてるかアセンブリコード見てみるよ。

.intel_syntax noprefix
.global dadd
dadd:
  push rbp
  mov  rbp, rsp
  sub  rsp, 16
  movsd  qword ptr [rbp - 8], xmm0
  movsd  qword ptr [rbp - 16], xmm1
  lea  rax, .L.str.0
  mov  rdi, rax
  movsd  xmm8, qword ptr [rbp - 8]
  movsd  xmm0, xmm8
  movsd  xmm8, qword ptr [rbp - 16]
  movsd  xmm1, xmm8
  mov  al, 2
  call printf
  movsd  xmm8, qword ptr [rbp - 8]
  addsd  xmm8, qword ptr [rbp - 16]
  movsd  xmm0, xmm8
.RET_dadd:
  add  rsp, 16
  pop  rbp
  ret
.global main
main:
  push rbp
  mov  rbp, rsp
  sub  rsp, 16
  movsd  xmm8, qword ptr [.L.double.1]
  movsd  qword ptr [rbp - 8], xmm8
  movsd  xmm8, qword ptr [.L.double.2]
  movq   rax, xmm8
  movabs r10, 8000000000000000H
  xor    rax, r10
  movq   xmm8, rax
  movsd  qword ptr [rbp - 16], xmm8
  lea  rax, .L.str.3
  mov  rdi, rax
  movsd  xmm8, qword ptr [rbp - 8]
  movsd  xmm0, xmm8
  movsd  xmm8, qword ptr [rbp - 16]
  movsd  xmm1, xmm8
  sub    rsp, 32
  mov    qword ptr [rsp + 0], rdi
  movsd  qword ptr [rsp + 8], xmm0
  movsd  qword ptr [rsp + 16], xmm1
  movsd  xmm8, qword ptr [.L.double.4]
  movsd  xmm0, xmm8
  movsd  xmm8, qword ptr [.L.double.5]
  movsd  xmm1, xmm8
  call dadd
  movsd  xmm8, xmm0
  mov    rdi, qword ptr [rsp + 0]
  movsd  xmm0, qword ptr [rsp + 8]
  movsd  xmm1, qword ptr [rsp + 16]
  add    rsp, 32
  movsd  xmm2, xmm8
  mov  al, 3
  call printf
  lea  rax, .L.str.6
  mov  rdi, rax
  mov  eax, 12
  mov  esi, eax
  mov  eax, 7
  mov  edx, eax
  sub    rsp, 32
  mov    qword ptr [rsp + 0], rdi
  mov    qword ptr [rsp + 8], rsi
  mov    qword ptr [rsp + 16], rdx
  movsd  xmm8, qword ptr [.L.double.4]
  movsd  xmm0, xmm8
  movsd  xmm8, qword ptr [.L.double.5]
  movsd  xmm1, xmm8
  call dadd
  movsd  xmm8, xmm0
  mov    rdi, qword ptr [rsp + 0]
  mov    rsi, qword ptr [rsp + 8]
  mov    rdx, qword ptr [rsp + 16]
  add    rsp, 32
  movsd  xmm0, xmm8
  mov  al, 1
  call printf
.RET_main:
  add  rsp, 16
  pop  rbp
  ret
.L.str.0:
  .asciz  "dadd: %f %f\n"
.L.double.1:
  .quad  402899999999999aH    # 12.3
.L.double.2:
  .quad  4006666666666666H    # 2.8
.L.str.3:
  .asciz  "%f %f %f\n"
.L.double.4:
  .quad  401599999999999aH    # 5.4
.L.double.5:
  .quad  400999999999999aH    # 3.2
.L.str.6:
  .asciz  "%d %d %f\n"

二回ずつ使われてる5.4と3.2のリテラルが一個になってる。うまくいったよ。さて次回は何やろうかな。int型とdouble型の変換でもやろうかな。

コンパイラ作成(90) ネストした関数呼出

今回の目標

double型引数の関数呼出が入子になってる場合に対応するよ。

// double型
double dadd(double a, double b)
{
    printf("dadd: %f %f\n", a, b);
    return a + b;
}

int main()
{
    double x = 12.3, y = -2.8;
    printf("%f %f %f\n", x, y, dadd(5.4, 3.2));
    printf("%d %d %f\n", 12, 7, dadd(5.4, 3.2));
}

関数daddの呼出で既にセットされてるxmmレジスタの値が潰されちゃうんで、daddコールの前後で退避・復帰する処理を追加する。昔、int型の時にも同じような処理を入れたけど、そこを改変するよ。

initialize
  # コンストラクタ
  def initialize(fname)
    @fname = fname                        # ソースファイルのファイル名
    @asmfname = fname.sub(/\.myc$/,'.s')  # アセンブリコードのファイル名
    @regs32 = ["edi", "esi","edx","ecx","r8d","r9d"]  # 32bitレジスタ
    @regs64 = ["rdi", "rsi","rdx","rcx","r8", "r9" ]  # 64bitレジスタ
    @lex = Lexer.new(@fname)              # 字句解析
    @funcname = nil                       # 現在処理している関数名
    @labelcnt = nil                       # 自動生成するラベルの個数(関数単位)
    @literalcnt = 0                       # 文字列リテラルの数
    @literaltable = []                    # 文字列リテラルのリスト
    @functions = Hash.new                 # 関数
    @lvars = nil                          # ローカル変数
    @lvarsize = nil                       # スタックに確保する領域のサイズ
    @breaklabel = nil                     # breakの飛び先のラベル
    @codebuffer = []                      # コードバッファ
    @numuseregs = 0                       # 関数コールで使用している一般レジスタの数
    @numusexmms = 0                       # 関数コールで使用しているxmmレジスタの数
    @numblock = nil                       # blockの個数
    @blocks = nil                         # ネストしたblock(["B2#","B1#",""])
  end

使用しているxmmレジスタの数用の変数を一個追加。

codegen_func
  # 関数コールのコード生成
  def codegen_func operand
    rettype = "int"
    xmm = 0    # xmmレジスタの使用数
    f = @functions[operand[0].str]
    # 既に使用されているレジスタをスタックに保存する
    if @numuseregs + @numusexmms != 0 then
      size = (@numuseregs + @numusexmms) * 8
      if size % 16 != 0 then size += 8 end
      codegen "  sub    rsp, #{size}"
      offset = 0
      (0...@numuseregs).each do |i|
        codegen "  mov    qword ptr [rsp + #{offset}], #{@regs64[i]}"
        offset += 8
      end
      (0...@numusexmms).each do |i|
        codegen "  movsd  qword ptr [rsp + #{offset}], xmm#{i}"
        offset += 8
      end
    end
    if f != nil then
      if operand.size - 2 != f[1].size then
        perror "wrong number of parameters"
      end
    end
    # 引数を順番に評価する
    (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 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
    # 関数をコール
    if f == nil then
      codegen "  mov  al, #{xmm}"
    end
    codegen "  call " + operand[0].str
    if f != nil then
      rettype = f[0]
    end
    if rettype == "double"
      codegen "  movsd  xmm8, xmm0"
    end
    # スタックに保存したレジスタを復帰する
    if @numuseregs + @numusexmms != 0 then
      size = (@numuseregs + @numusexmms) * 8
      if size % 16 != 0 then size += 8 end
      offset = 0
      (0...@numuseregs).each do |i|
        codegen "  mov    #{@regs64[i]}, qword ptr [rsp + #{offset}]"
        offset += 8
      end
      (0...@numusexmms).each do |i|
        codegen "  movsd  xmm#{i}, qword ptr [rsp + #{offset}]"
        offset += 8
      end
      codegen "  add    rsp, #{size}"
    end
    return rettype
  end

int型の時はゼネラルパーパスレジスタの退避/復帰にpush/pop使ってたんだけど、xmmレジスタはpush/popできないんでmovsd使ってるよ。で、この際なんでゼネラルパーパスレジスタの方もpush/popやめてmovにした。ちゃんと確かめてないけどこっちの方がクロック数的にお得なんじゃないかな。違うかな。

動作テスト
~/myc$ myc p25.myc
~/myc$ ./p25
dadd: 5.400000 3.200000
12.300000 -2.800000 8.600000
dadd: 5.400000 3.200000
12 7 8.600000
~/myc$

大丈夫だよね。アセンブリコードも見ておくよ。

.intel_syntax noprefix
.global dadd
dadd:
  push rbp
  mov  rbp, rsp
  sub  rsp, 16
  movsd  qword ptr [rbp - 8], xmm0
  movsd  qword ptr [rbp - 16], xmm1
  lea  rax, .L.str.0
  mov  rdi, rax
  movsd  xmm8, qword ptr [rbp - 8]
  movsd  xmm0, xmm8
  movsd  xmm8, qword ptr [rbp - 16]
  movsd  xmm1, xmm8
  mov  al, 2
  call printf
  movsd  xmm8, qword ptr [rbp - 8]
  addsd  xmm8, qword ptr [rbp - 16]
  movsd  xmm0, xmm8
.RET_dadd:
  add  rsp, 16
  pop  rbp
  ret
.global main
main:
  push rbp
  mov  rbp, rsp
  sub  rsp, 16
  movsd  xmm8, qword ptr [.L.float.1]
  movsd  qword ptr [rbp - 8], xmm8
  movsd  xmm8, qword ptr [.L.float.2]
  movq   rax, xmm8
  movabs r10, 8000000000000000H
  xor    rax, r10
  movq   xmm8, rax
  movsd  qword ptr [rbp - 16], xmm8
  lea  rax, .L.str.3
  mov  rdi, rax
  movsd  xmm8, qword ptr [rbp - 8]
  movsd  xmm0, xmm8
  movsd  xmm8, qword ptr [rbp - 16]
  movsd  xmm1, xmm8
  sub    rsp, 32
  mov    qword ptr [rsp + 0], rdi
  movsd  qword ptr [rsp + 8], xmm0
  movsd  qword ptr [rsp + 16], xmm1
  movsd  xmm8, qword ptr [.L.float.4]
  movsd  xmm0, xmm8
  movsd  xmm8, qword ptr [.L.float.5]
  movsd  xmm1, xmm8
  call dadd
  movsd  xmm8, xmm0
  mov    rdi, qword ptr [rsp + 0]
  movsd  xmm0, qword ptr [rsp + 8]
  movsd  xmm1, qword ptr [rsp + 16]
  add    rsp, 32
  movsd  xmm2, xmm8
  mov  al, 3
  call printf
  lea  rax, .L.str.6
  mov  rdi, rax
  mov  eax, 12
  mov  esi, eax
  mov  eax, 7
  mov  edx, eax
  sub    rsp, 32
  mov    qword ptr [rsp + 0], rdi
  mov    qword ptr [rsp + 8], rsi
  mov    qword ptr [rsp + 16], rdx
  movsd  xmm8, qword ptr [.L.float.7]
  movsd  xmm0, xmm8
  movsd  xmm8, qword ptr [.L.float.8]
  movsd  xmm1, xmm8
  call dadd
  movsd  xmm8, xmm0
  mov    rdi, qword ptr [rsp + 0]
  mov    rsi, qword ptr [rsp + 8]
  mov    rdx, qword ptr [rsp + 16]
  add    rsp, 32
  movsd  xmm0, xmm8
  mov  al, 1
  call printf
.RET_main:
  add  rsp, 16
  pop  rbp
  ret
.L.str.0:
  .asciz  "dadd: %f %f\n"
.L.float.1:
  .quad  402899999999999aH    # 12.3
.L.float.2:
  .quad  4006666666666666H    # 2.8
.L.str.3:
  .asciz  "%f %f %f\n"
.L.float.4:
  .quad  401599999999999aH    # 5.4
.L.float.5:
  .quad  400999999999999aH    # 3.2
.L.str.6:
  .asciz  "%d %d %f\n"
.L.float.7:
  .quad  401599999999999aH    # 5.4
.L.float.8:
  .quad  400999999999999aH    # 3.2

daddの前後でちゃんと退避・復帰されてる。昨日の夜遅い時間に考えてた時は難しく感じたんだけど、実際にやってみたらさほど難しくはなかったよ。遅い時間だと頭のクロック数が落ちてるからかな。
さて次回はなにやるかな。上のアセンブリコード見てたらリテラルが重複してるの気になるから修正しようかな。clang先輩はちゃんと一個に纏めてたよなあ。それに.L.floatってのも変だよなあ。どうして.L.doubleにしなかったんだろ。将来double型以外にfloat型をサポートするとき困るよなあ。うん、次回修正しよう。

コンパイラ作成(89) 式のエラーチェック

今回の目標

前回に続きエラーチェック。

// 壊れてる式
int main()
{
    int i;
    i = ;
}

壊れた代入。

codegen_assign
  # 代入のコード生成
  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]]

codegen_elを呼ぶ前にチェックをするように修正。

動作テスト
~/myc$ myc err42.myc
err42.myc:5:8 error: broken expression
~/myc$

大丈夫だね。今回は一個しかチェックできなかったよ。二回続けてエラーチェックまわりをやったけど、次回はdouble型関連に戻るかな。

コンパイラ作成(88) 式のエラーチェック

今回の目標

double型関連にちょっと疲れたんで別の事やるよ。壊れてる式をチェックしてエラーに。

// 壊れてる式
int main()
{
    1 + ;
}
// 壊れてる式
int main()
{
     + ;
}
// 壊れてる式
int main()
{
     / ;
}
// 壊れてる式
int main()
{
    int i;
    i++;
}

ちょっとづつ違う4パターン。最後のはC言語としては正しいんだけど、mycはまだインクリメント演算子をサポートしてないんでエラー。

modify_el_unaryop
  # elの変形(単行演算子の処理)
  #   [-, 2, +, 3]
  #   =>[[-, 2], + 3]
  #   [-, 2, +, -, 3]
  #   =>[[-, 2], + [-, 3]]
  def modify_el_unaryop(el, opl)
    mel = []
    prev = nil
    loop do
      if el.size == 0 then break end
      x = el.shift
      if ((prev == nil) || (!prev.kind_of?(Array) && (prev.kind == TK::SYMBOL))) \
         && !x.kind_of?(Array) && x.kind == TK::SYMBOL
      then
        if opl.include? x.str then
          tel = []
          tel << x
          if el == [] then
            perror "broken expression"
          end
          x = el.shift
          if x.kind_of?(Array) then
            x = modify_el_unaryop x, opl
          elsif opl.include? x.str then
            sel = []
            loop do
              sel << x
              if !opl.include? x.str then break end
              if el.size == 0 then break end
              x = el.shift
            end
            x = modify_el_unaryop sel, opl
            if x.size == 1 && x[0].kind_of?(Array) then
              x = x[0]
            end
          end
          tel << x
          mel << tel
          prev = tel
        else
          perror "broken expression"
        end
      else
        if x.kind_of?(Array) then x = modify_el_unaryop x, opl end
        mel << x
        prev = x
      end
    end
    return mel
  end

単項演算子の後に適切なオペランドが無かったらエラー。

codegen_el
  # 式のコード生成
  def codegen_el(el)
    type = "int"
    if !el[0].kind_of?(Array) && el[0].kind == TK::SYMBOL
      type = codegen_unaryop el[0], [el[1]]
    elsif (el.size > 1) && el[1].str == "=" then
      type = codegen_assign el
    else
      type = codegen_elf el.shift
      loop do
        if el == [] then break end
        op = el.shift
        operand = el.shift
        if operand == nil then
          perror "broken expression"
        end
        type = codegen_els op, operand, type
      end
    end
    return type
  end

二項演算子の場合もオペランドが無かったらエラー。

動作テスト

4つ纏めて行くよ。

~/myc$ myc err38.myc
err38.myc:4:8 error: broken expression
~/myc$ myc err39.myc
err39.myc:4:7 error: broken expression
~/myc$ myc err40.myc
err40.myc:4:7 error: broken expression
~/myc$ myc err41.myc
err41.myc:5:8 error: broken expression
~/myc$

みんなエラーになったよ。今回はこの4件をチェックしたけど、まだまだ漏れてるのあるだろうなあ。代入演算子、アドレス演算子、参照演算子がらみのチェックは不十分だよなあ。次回もエラーチェックかな。

コンパイラ作成(87) 引数がdouble型の関数定義

今回の目標

double型の関数だよ。

// double型
double dadd(double a, double b)
{
    return a + b;
}

int main()
{
    double x = 12.3, y = -2.8, z = dadd(x, y);
    printf("%f + %f = %f\n", x, y, z);
}

引数も返値もdouble型の場合。

function
    # レジスタで渡された引数をスタックの領域に移す
    n = @lvars.size
    offset = 0
    gpr_cnt = xmm_cnt = 0
    (0...n).each do |i|
      offset += parametersize[i]
      if paratype[i] == "double" then
        codegen "  movsd  qword ptr [rbp - #{offset}], xmm#{xmm_cnt}"
        xmm_cnt += 1
      else
        if parametersize[i] == 4 then
          codegen "  mov  dword ptr [rbp - #{offset}], #{@regs32[gpr_cnt]}"
        else
          codegen "  mov  qword ptr [rbp - #{offset}], #{@regs64[gpr_cnt]}"
        end
        gpr_cnt += 1
      end
    end

引数の型を見て処理を分けてるよ。そういやここの処理、引数がレジスタから溢れてスタックに入れられてる場合に対応してないなあ。面倒そうなんで放ったらかしにしてたよ。

statement
      # return文の処理
      kind, str = @lex.gettoken
      kind, str = expr kind, str
      f = @functions[@funcname]
      if f[0] == "double" then
        codegen "  movsd  xmm0, xmm8"
      end
      codegen "  jmp  .RET_" + @funcname
      if kind != TK::SYMBOL || str != ";" then
        perror "expected ';' after return statement"
      end

ここも修正が必要。レジスタ割り当てが適切ならこの処理要らないんだよなあ。ていうかmycはレジスタの割り当てしてないからなあ。この辺は遠い将来の課題だな。

動作テスト

そいではテスト。

~/myc$ myc p23.myc
~/myc$ ./p23
12.300000 + -2.800000 = 9.500000
~/myc$

えーと、計算合ってるよね。もう一個テスト。

// double型
double dimul(double a, int b)
{
    double sum = 0.0;
    for(int i = 0; i < b; i = i + 1)
        sum = sum + a;
    return sum;
}

int main()
{
    int y = 10;
    double x = 12.3, z = dimul(x, y);
    printf("%f * %d = %f\n", x, y, z);
}

引数がdouble型とint型のミックスの場合。

~/myc$ myc p24.myc
~/myc$ ./p24
12.300000 * 10 = 123.000000
~/myc$ 

これもできたよ。これ本当はreturn a * b;ってやりたいんだけど、型変換の論理が入ってないんでまだ無理。これもやんなきゃいけないんだけど、次回はネストした関数の呼出かな。