コンパイラ作成(100) 配列の参照

今回の目標

今回は配列の参照だよ。

// 配列
int main()
{
    int a[10], *p;
    p = a;
    *p = 42;
    printf("a[0] = %d\n", a[0]);
    printf("0[a] = %d\n", 0[a]);
    printf("10 + a[0] = %d\n", 10 + a[0]);
    printf("10 + 0[a] = %d\n", 10 + 0[a]);
}

配列への代入は今回はやらないんでポインタを通して値を設定してる。

read_el

今回は修正箇所多いよ。まずは式リストの読み込み処理。

  # 式の最後までのトークンを読み込む
  #   返り値
  #     el   : expression's token list
  #     kind : 処理しなかったトークン(セミコロンもしくは閉じ括弧)
  #     str  : 処理しなかったトークン(セミコロンもしくは閉じ括弧)
  def read_el(fkind,fstr,skind,sstr)
    el = []
    if fkind == TK::ID && sstr == "(" then
      # 関数呼出の処理
      sel = []
      sel << Token.new(fkind,fstr)
      sel << Token.new(skind,"()")
      skind, sstr = @lex.gettoken
      loop do
        if skind == TK::SYMBOL && sstr == ")" then break end
        fkind, fstr = skind, sstr
        skind, sstr = @lex.gettoken
        pel, skind, sstr = read_modify_el fkind, fstr, skind, sstr
        sel << pel
        if skind == TK::SYMBOL && sstr == "," then
          skind, sstr = @lex.gettoken
        elsif skind != TK::SYMBOL || sstr != ")" then
          perror
        end
      end
      el << sel
      if skind != TK::SYMBOL || sstr != ")" then perror end
      skind, sstr = @lex.gettoken
    elsif fstr == "(" then
      # 括弧の処理
      fkind, fstr = skind, sstr
      skind, sstr = @lex.gettoken
      sel, skind, sstr = read_el fkind, fstr, skind, sstr
      el << sel
      skind, sstr = @lex.gettoken
    elsif sstr == "[" then
      # 配列の処理
      el << Token.new(fkind,fstr)
      el << Token.new(skind,"[]")
      fkind, fstr = @lex.gettoken
      skind, sstr = @lex.gettoken
      sel, skind, sstr = read_el fkind, fstr, skind, sstr
      if sel.size > 1 then
        el << sel
      else
        el << sel[0]
      end
      skind, sstr = @lex.gettoken
    else
      el << Token.new(fkind,fstr)
    end
    loop do
      if skind == TK::EOF then break end
      if sstr == ";" then break end
      if sstr == ")" then break end
      if sstr == "]" then break end
      if sstr == "," then break end
      if sstr == "}" then break end
      fkind, fstr = skind, sstr
      skind, sstr = @lex.gettoken
      if fkind == TK::ID && sstr == "(" then
        # 関数呼出の処理
        sel = []
        sel << Token.new(fkind,fstr)
        sel << Token.new(skind,"()")
        skind, sstr = @lex.gettoken
        loop do
          if skind == TK::SYMBOL && sstr == ")" then break end
          fkind, fstr = skind, sstr
          skind, sstr = @lex.gettoken
          pel, skind, sstr = read_el fkind, fstr, skind, sstr
          sel << pel
          if skind == TK::SYMBOL && sstr == "," then
            skind, sstr = @lex.gettoken
          elsif skind != TK::SYMBOL || sstr != ")" then
            perror
          end
        end
        el << sel
        if skind != TK::SYMBOL || sstr != ")" then perror end
        skind, sstr = @lex.gettoken
      elsif fstr == "(" then
        # 括弧の処理
        fkind, fstr = skind, sstr
        skind, sstr = @lex.gettoken
        sel, skind, sstr = read_el fkind, fstr, skind, sstr
        el << sel
        skind, sstr = @lex.gettoken
      elsif sstr == "[" then
        # 配列の処理
        el << Token.new(fkind,fstr)
        el << Token.new(skind,"[]")
        fkind, fstr = @lex.gettoken
        skind, sstr = @lex.gettoken
        sel, skind, sstr = read_el fkind, fstr, skind, sstr
        if sel.size > 1 then
          el << sel
        else
          el << sel[0]
        end
        skind, sstr = @lex.gettoken
      else
        el << Token.new(fkind,fstr)
      end
    end
    return el, skind, sstr
  end

配列をa[0]から(a[]0)へと変形してる。こうすることで二項演算子と同じになるんで後の処理が楽になるよ。今回の修正ではエラー処理とかすっ飛ばしちゃってるよ。とりあえず正常なソースをコンパイルできるところまで何とかするけど、それ以外はあちこち手抜きだよ。

read_modify_el

次はここ。

  # 式の最後までのトークンを読み込み、変形する
  def read_modify_el(fkind,fstr,skind,sstr)
    el, kind, str = read_el fkind, fstr, skind, sstr
    puts to_str(el) if $opt_d   # デバッグ用
    el = modify_el_unaryop el, ["+","-","&","*"]
    el = modify_el el, ["[]"]
    el = modify_el el, ["*","/","%"]
    el = modify_el el, ["+","-"]
    el = modify_el el, ["<",">","<=",">="]
    el = modify_el el, ["==","!="]
    el = modify_el el, ["="], :r_to_l
    puts to_str(el) if $opt_d   # デバッグ用
    return el, kind, str
  end

[]を追加したんだけどこれ問題ありだよ。C言語の仕様では配列は単項演算子より優先順位が上なんで、本当はel = modify_el el, ["[]"]をel = modify_el_unaryop el, ["+","-","&","*"]より前に持っていかなきゃならないんだけど、modify_elは単項演算子が混じってるelに対応できるようになってないんだよね。それで今回はこういう順番にしちゃった。配列のサポートがある程度出来上がったら修正しようと思ってるよ。

新しいメソッド

codegen_elsから処理を切り出したよ。

  # ポインタ型+int型のコード生成
  def codegen_pointer_int(type_l,op,ostr,str)
    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
  end

  # int型+ポインタ型のコード生成
  def codegen_int_pointer(type_r,op,ostr,str)
    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
  end

配列の参照処理はポインタとint型の加算と同じような処理なんで切り出したメソッドを活用してくよ。

mnemonic
  # ニーモニック
  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 "ucomisd"
      elsif op.str == "!=" then
        return "ucomisd"
      elsif op.str == "<" || op.str == "<" || op.str == ">" || op.str == "<=" || op.str == ">=" then
        return "ucomisd"
      elsif op.str == "[]" then
        return "addsd "
      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 "
      elsif op.str == "[]" then
        return "add "
      else
        perror "unknown operator \"" + op.str + "\""
      end
    end
  end

ここは[]を追加しただけ。

codegen_els

コード生成部。あちこちに手を入れたよ。

  # 式のコード生成(二項演算の右側被演算子)
  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_array_type? type_r then
        codegen "  lea  r10, [rbp - " + v[1].to_s + "]"
        str = "r10"
      elsif 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
      elsif is_array_type?(type_l) && type_r == "int" then
        if op.str != "[]" then
          perror "mismatched types to binary operation"
        end
      elsif type_l == "int" && is_array_type?(type_r) then
        if 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型の処理
        codegen_pointer_int type_l, op, ostr, str
      elsif type_l == "int" && is_pointer_type?(type_r) then
        # int型+ポインタ型の処理
        codegen_int_pointer type_r, op, ostr, str
        type_l = type_r
      elsif is_array_type?(type_l) && type_r == "int" then
        # ポインタ型+int型の処理
        type_l = array_to_pointer type_l
        codegen_pointer_int type_l, op, ostr, str
        codegen "  mov  eax, [rax]"
        type_l = type_l[0,type_l.length-1]
      elsif type_l == "int" && is_array_type?(type_r) then
        type_r = array_to_pointer type_r
        codegen_int_pointer type_r, op, ostr, str
        codegen "  mov  eax, [rax]"
        type_l = type_r[0,type_r.length-1]
      else
        codegen "  " + ostr + " #{reg}, " + str
       end
    end
    return type_l
  end

これで全部修正できたかな。

動作テスト
~/myc$ myc q4.myc
~/myc$ ./q4
a[0] = 42
0[a] = 42
10 + a[0] = 52
10 + 0[a] = 52
~/myc$ 

おお、動いたよ。今回は修正箇所多くて大変だったけどなんとか上手く行った。さて次は何やろう。配列への代入かな。

コンパイラ作成(99) 配列のポインタへの変換

今回の目標

配列の実装を進めるよ。

// 配列
int main()
{
    int a[10], *p;
    p = a;
    printf("p = %016lx\n", p);
}

簡単そうなところから。

ヘルパーメソッド
  # 配列型?
  def is_array_type?(type)
    return type.match(/\[.*\]$/)
  end

  # 配列型→pointer型
  def array_to_pointer(type)
    return type.sub(/\[.*\]$/,"*")
  end

型情報関連のメソッドを二つ追加。正規表現でごにょごにょやってるけど、これどう考えても遅いよね。型情報の持ち方を工夫すれば良いのかな。この辺は将来的な課題だな。

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]]
    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    # 暗黙の型変換
        elsif is_array_type? type_r then
          type_r = array_to_pointer type_r
        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    # 暗黙の型変換
      elsif is_array_type? type_r then
        type_r = array_to_pointer type_r
      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

さっきのヘルパーメソッドを使って型を変換してるよ。

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" && type == "int" then
          codegen "  movsx rax, eax"
          type = f[1][i]
        elsif f[1][i] == "void*" && is_pointer_type?(type) then
          type = f[1][i]
        elsif f[1][i] == "double" && type == "int" then
          codegen "  cvtsi2sd xmm8, eax"
          type = f[1][i]
        elsif is_array_type? type then
          type = array_to_pointer type
        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 q2.myc
~/myc$ ./q2
p = 00007ffd3a476548
~/myc$

上手く行ったかな。良く分からないんでアセンブリコードも見てみるよ。

.intel_syntax noprefix
.global main
main:
  push rbp
  mov  rbp, rsp
  sub  rsp, 48
  lea  rax, [rbp - 40]
  mov  qword ptr [rbp - 48], rax
  lea  rax, .L.str.0
  mov  rdi, rax
  mov  rax, qword ptr [rbp - 48]
  mov  rsi, rax
  mov  al, 0
  call printf
.RET_main:
  add  rsp, 48
  pop  rbp
  ret
.L.str.0:
  .asciz  "p = %016lx\n"

うん、ちゃんとコード生成されてる。もう一個試してみるよ。

// repl
extern char *gets(char *s);
extern int puts(char *s);
extern int strcmp(char *string1, char *string2);

int main()
{
    char buffer[256];
    for(;;) {
        printf("repl:");
        gets(buffer);
        if(strcmp(buffer,"quit") == 0) break;
        printf("=>");
        puts(buffer);
   }
}

昔書いたreplもどきをmallocから配列に変更してみたよ。

~/myc$ ./q3
repl:123
=>123
repl:abc
=>abc
repl:12+16
=>12+16
repl:quit
~/myc$

ちゃんと動いてるね。次回は配列の参照かな。式の構文解析からやらないといけないから結構大変かなあ。コード生成部は前に作ったポインタ型とint型の加算と同じようにやれば良いんだよなあ。うーん、良く考えよう。

コンパイラ作成(98) 配列の宣言

今回の目標

いきなりだけど配列に手を出してみるよ。

// 配列
int main()
{
    int a[10], b;
}

まずは配列変数の宣言から。

Lexer

gettokenの修正。

      # symbolの切り出し
      elsif m = @line[@idx,@line.length]
                .match(/^(==|!=|<=|>=|=|<|>|\+|\-|\*|\/|\%|&|:|,|;|\(|\)|\{|\}|\[|\])/)  then
        str = m.to_s
        @idx += str.length
        return TK::SYMBOL, str
      end

括弧[]をを追加しただけ。

変数宣言
  # 変数宣言の処理
  def var_decl(kind, str)
    basetype = str
    loop do
      type = basetype
      ary = ""
      ary_size = nil
      kind, str = @lex.gettoken
      loop do
        if kind != TK::SYMBOL || str != "*" then break end
        type += str
        kind, str = @lex.gettoken
      end
      if kind != TK::ID then perror end
      var_name = str
      skind, sstr = @lex.gettoken
      if skind == TK::SYMBOL && sstr == "[" then
        ary += sstr
        skind, sstr = @lex.gettoken
        if skind != TK::NUMBER then perror end
        ary += sstr
        ary_size = sstr.to_i
        skind, sstr = @lex.gettoken
        if skind != TK::SYMBOL || sstr != "]" then perror end
        ary += sstr
        skind, sstr = @lex.gettoken
      end
      print "var #{var_name}:#{type}#{ary}\n" if $opt_d
      if ary_size then
        @lvarsize += (sizeof(type) * ary_size)
      else
        @lvarsize += sizeof(type)
      end
      if check_var str then
        perror "redefinition variable \"" + var_name +"\""
      end
      if type == "void" then
        perror "invalid type 'void'"
      end
      set_var var_name, [type+ary,@lvarsize]
      if skind == TK::SYMBOL && sstr == "=" then
        kind, str = expr2 kind, str, skind, sstr;
      else
        kind, str = skind, sstr;
      end
      if kind != TK::SYMBOL || str != "," then break end
    end
    return kind, str
  end

変数の型をどう表現するか悩んだけどint[10]と表わすことにしたよ。扱いやすい形式じゃないから将来見直すかも。配列のサイズ情報は別に持った方が良いかもなあ。悩ましいけどとりあえずこれで実装してみるよ。あと配列の初期化については今のとこ考慮してないよ。多次元配列とかもね。まずは一番シンプルなとこから行くよ。

動作テスト
~/myc$ myc -dc q.myc
var a:int[10]
var b:int
{"a"=>["int[10]", 40], "b"=>["int", 44]}
{"main"=>["int", []]}
~/myc$

ちゃんと40byteの領域が確保されてる。大丈夫そうだね。えーと、次はポインターへの代入とかやってみるかな。割と簡単そうだしね。配列への代入や参照は難しそうだもんなあ。頑張ってちょっとずつ実装してこう。

コンパイラ作成(97) return文での型変換

今回の目標

int型→double型の型変換。

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

int main()
{
    double x;
    x = func();
    printf("x = %f\n", x);
}

最近、return文のとこばかりだな。

statement

return文の処理を修正。

    if kind == TK::RESERVE && str == "return" then
      # return文の処理
      f = @functions[@funcname]
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL && str != ";" then
        kind, str, type = expr kind, str
        if f[0] == "double" && type == "int" then
          codegen "  cvtsi2sd xmm0, eax"
          type = f[0]
        elsif f[0] == "double" then
          codegen "  movsd  xmm0, xmm8"
        end
        if f[0] != type then
          if f[0] == "void" then
            perror "return a value at void function"
          else
            perror "return with incompatible result type"
          end
        end
      else
        if f[0] != "void" then
          perror "return no value at non-void function"
        end
      end
      codegen "  jmp  .RET_" + @funcname
      if kind != TK::SYMBOL || str != ";" then
        perror "expected ';' after return statement"
      end

int型以外からの変換も入れちゃおうかと思ったけど、中途半端になるかなと思ってやめちゃった。

動作テスト
~/myc$ myc p28.myc
~/myc$ ./p28
x = 5.000000
~/myc$

ちゃんと変換されてる。これでint型→double型の変換は終わったよな。さて次回は何やろうかな。char型からの変換とかもやんなきゃいけないんだけど、ちょっと飽きてきたんで違うことやろうかな。

コンパイラ作成(96) 型変換のバグ修正

バグ修正

プログラム眺めてたらバグを見っけたよ。

// 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));
}

この前、int型→double型の変換処理を入れたけど、変換が要らないときにもcvtsi2sdしてたよ。

関数コール

functionの引数の処理部。

    # 引数を順番に評価する
    (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" && type == "int" then
          codegen "  movsx rax, eax"
          type = f[1][i]
        elsif f[1][i] == "void*" && is_pointer_type?(type) then
          type = f[1][i]
        elsif f[1][i] == "double" && type == "int" 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

if文の条件が間違ってたよ。size_tの場合も拙かったんで併せて修正。

動作テスト
~/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$

修正完了。修正箇所のプログラムをよく見て一人レビューするの大事だな。それで怪しいとこ見つけたら即テスト。次回は今回やるはずだったreturn文での型変換をやるつもりだよ。

コンパイラ作成(95) return文での型のチェック

今回の目標

前回の続きでreturn文。

// 関数の呼び出し
int *sub(void)
{
    return 2.5;
}

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

型をチェックするよ。

expr
  # 式の構文解析
  def expr2(fkind,fstr,skind,sstr)
    el, kind, str = read_modify_el fkind, fstr, skind, sstr
    return kind, str, codegen_el(el)
  end

型の情報を返すようインターフェースを修正。

statement

return文の処理。

    if kind == TK::RESERVE && str == "return" then
      # return文の処理
      f = @functions[@funcname]
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL && str != ";" then
        kind, str, type = expr kind, str
        if f[0] != type then
          if f[0] == "void" then
            perror "return a value at void function"
          else
            perror "return with incompatible result type"
          end
        end
        if f[0] == "double" then
          codegen "  movsd  xmm0, xmm8"
        end
      else
        if f[0] != "void" then
          perror "return no value at non-void function"
        end
      end
      codegen "  jmp  .RET_" + @funcname
      if kind != TK::SYMBOL || str != ";" then
        perror "expected ';' after return statement"
      end

exprからの型情報と関数情報テーブルの返値の型を見比べてエラーを出してるんだけど、色んな場合があって面倒だよ。

動作テスト
~/myc$ myc err45.myc
err45.myc:4:15 error: return with incompatible result type
~/myc$ 

ちゃんとエラーになってるね。さらに別のパターンを二つチェックするよ。

// 関数の呼び出し
void sub(void)
{
    return 42;
}

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

void関数で値を返してる場合。

// 関数の呼び出し
int sub(void)
{
    return;
}

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

逆にvoid関数じゃないのに値を返してない場合。

~/myc$ myc err46.myc
err46.myc:4:14 error: return a value at void function
~/myc$ myc err47.myc
err47.myc:4:11 error: return no value at non-void function
~/myc$

大丈夫だね。一応これで全部のパターンチェックしたつもり。次回はreturn文でのint型→double型の変換をやるつもりだよ。

コンパイラ作成(94) 値を返さないreturn文

今回の目標

単純なreturn文。

// 関数の呼び出し
void sub(void)
{
    return;
}

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

これサポートしてなかったよ。

statement

return文の処理。

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

if文を一個追加しただけ。

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

コンパイル通るようになったよ。返値の型のチェックも入れようと思ったんだけど、exprにも手を入れて型を返すようにしないと駄目なんで今回は諦めたよ。今回、ちっとも進んでないなあ。明日頑張ろう。