コンパイラ作成(79) double型の代入・参照

今回の目標

前回、Lexerの修正したんで、今回はdouble型の代入と参照まで実装するよ。

// double型

int main()
{
    double d = 10.78;
    double e = 0.13e-3;
    printf("d = %f\n", d);
    printf("e = %f\n", e);
}
double型追加

まずはここ。

    @typeword = [
      "extern","void","int","char","size_t","double"
    ]

sizeofも修正。

  # 型のサイズ
  def sizeof(type)
    if is_pointer_type?(type) then return 8 end
    case type
      when "void"
        return 1
      when "char"
        return 1
      when "int"
        return 4
      when "size_t"
        return 8
      when "double"
        return 8
      else
        perror "unknown type '#{type}'"
      end
  end

これでdouble型の変数が宣言できるようになるはず。

リテラルの管理

clang先輩のコンパイル結果を見てみたら浮動小数リテラルは文字列リテラルと同じような感じになってた。

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

  # リテラルをテーブルに追加
  def addliteral(str, type=:string)
    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] == :float then
        codegen (literallabel(i,:float) + ":")
        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

文字列リテラルの管理用のメソッドを修正して、浮動小数点数リテラルにも対応できるようにしたよ。

代入処理
  # 代入のコード生成
  def codegen_assign(el)
    if el.size != 3 then perror 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    # 暗黙の型変換
        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    # 暗黙の型変換
      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

double型のときはxmmレジスタを使うように。double型は64bitなんでqwordだよ。

参照処理
  # 式のコード生成(二項演算の左側被演算子)
  def codegen_elf(operand)
    type = "int"
    if operand.kind_of?(Array) then
      if !operand[0].kind_of?(Array) && operand[0].kind == TK::ID && operand[1].str == "()" then
        type = codegen_func operand
      else
        type = codegen_el operand
      end
    elsif operand.kind == TK::NUMBER then
      codegen "  mov  eax, " + operand.str
    elsif operand.kind == TK::ID then
      v = get_var operand.str
      if v == nil then
        perror "undeclared variable \"" + operand.str + "\""
      end
      type = v[0]
      if is_pointer_type? type then
        codegen "  mov  rax, qword ptr [rbp - " + v[1].to_s + "]"
      elsif type == "double" then
        codegen "  movsd  xmm8, qword ptr [rbp - " + v[1].to_s + "]"
      elsif type == "char" then
        codegen "  mov  al, byte ptr [rbp - " + v[1].to_s + "]"
      else
        codegen "  mov  eax, dword ptr [rbp - " + v[1].to_s + "]"
      end
    elsif operand.kind == TK::STRING then
      type = "char*"
      label = addliteral operand.str
      codegen "  lea  rax, "+label
    elsif operand.kind == TK::FLOAT then
      type = "double"
      label = addliteral operand.str, :float
      codegen "  movsd  xmm8, qword ptr [#{label}]"
    else
      perror
    end
    return type
  end

double型変数の参照と、リテラルの処理を追加。

関数コール

printfをコールしたいんで修正。

  # 関数コールのコード生成
  def codegen_func operand
    rettype = "int"
    f = @functions[operand[0].str]
    if @numuseregs != 0 then
      if @numuseregs % 2 == 1 then codegen "  sub  rsp, 8" end
      (0...@numuseregs).each do |i| codegen "  push #{@regs64[i]}" 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|
      save = @numuseregs
      @numuseregs = i
      type = codegen_el operand[i+2]
      @numuseregs = save
      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]}, eax"
      elsif type == "size_t"
        codegen "  mov  #{@regs64[i]}, rax"
=begin
      elsif type == "char*"
        codegen "  mov  #{@regs64[i]}, rax"
      elsif type == "void*"
        codegen "  mov  #{@regs64[i]}, rax"
=end
      elsif is_pointer_type?(type) then
        codegen "  mov  #{@regs64[i]}, rax"
      elsif type == "double" then
        codegen "  movsd  xmm0, xmm8"
      else
        perror
      end
    end
    if f == nil then
      codegen "  mov  al, 1"
    end
    codegen "  call " + operand[0].str
    if f != nil then
      rettype = f[0]
    end
    if @numuseregs != 0 then
      (0...@numuseregs).reverse_each do |i| codegen "  pop  #{@regs64[i]}" end
      if @numuseregs % 2 == 1 then codegen "  add  rsp, 8" end
    end
    return rettype
  end

本当はもっと色々論理を組み込まないといけないんだけど、今回は手抜きの良い加減コーディングだよ。double型変数一個の場合以外は上手く行かないよ。xmm0固定になってるしね。

動作テスト

変更箇所多かったけどどうかな。

~/myc$ myc p7.myc
~/myc$ ./p7
d = 10.780000
e = 0.000130
~/myc$

おお、表示できたよ。アセンブリコードも見てみる。

.intel_syntax noprefix
.global main
main:
  push rbp
  mov  rbp, rsp
  sub  rsp, 16
  movsd  xmm8, qword ptr [.L.float.0]
  movsd  qword ptr [rbp - 8], xmm8
  movsd  xmm8, qword ptr [.L.float.1]
  movsd  qword ptr [rbp - 16], xmm8
  lea  rax, .L.str.2
  mov  rdi, rax
  movsd  xmm8, qword ptr [rbp - 8]
  movsd  xmm0, xmm8
  mov  al, 1
  call printf
  lea  rax, .L.str.3
  mov  rdi, rax
  movsd  xmm8, qword ptr [rbp - 16]
  movsd  xmm0, xmm8
  mov  al, 1
  call printf
.RET_main:
  add  rsp, 16
  pop  rbp
  ret
.L.float.0:
  .quad  40258f5c28f5c28fH    # 10.78
.L.float.1:
  .quad  3f210a137f38c543H    # 0.13e-3
.L.str.2:
  .asciz  "d = %f\n"
.L.str.3:
  .asciz  "e = %f\n"

うんうん良さそうです。今回一番苦労した点はxmmレジスタの使い方だったよ。mov xmm0,xmm8ってやったら怒られちゃったしね。参考になりそうな良いサイト無いかとググってみたんだけど、SIMD関連の情報ばかりで見つからなかったよ。もっと基本的な情報が欲しかったんだけどさあ。Intel® 64 and IA-32 Architectures Software Developer Manuals | Intel® Softwareを見て勉強するしかないのかなあ。
次回は今回サボった関数コールを何とかしたいよ。