コンパイラ作成(47) バグ修正

バグ2件

最近恒例になってきたバグ修正。今回は2件だよ。

main()
{
    print(42);
}

前回、スタックフレームの作成を修正したけど、こういう風に変数が1個も使われてない場合に、sub rsp,0ってコードが出力されてたよ。これでも動くから問題ないんだけど、恰好悪いんで修正することにした。

// 関数の呼び出し
int main()
{
    int a = answer();
    printf("%d\n",a);
}

int answer()
{
    //  Answer to the Ultimate Question of Life, the Universe, and Everything
    return 42;
}

代入a = answer();がコンパイルできなかったよ。こんな単純な場合のチェックが漏れてたとはちょっとショック。

function

1件目の修正。

  # 関数の構文解析
  def function()
    @labelcnt = 0
    @lvars = Hash.new
    @lvarsize = 0;
    rettype = nil
    kind, str = @lex.gettoken
    if kind == 	TK::RESERVE && str == "int" then
      rettype = str
      kind, str = @lex.gettoken
    end
    if kind == TK::EOF then return false end
    if kind != TK::ID then perror "expected identifier" end
    @funcname = str
    kind, str = @lex.gettoken
    if kind != TK::SYMBOL || str != "(" then perror end
    kind, str = @lex.gettoken
    if kind != TK::SYMBOL || str != ")" then perror end
    codegen ".global "+@funcname
    codegen @funcname+":"
    codegen "  push rbp"
    codegen "  mov  rbp, rsp"
    # 仮のコードを作成
    idx = codegen "  sub  rsp, xx"
    kind, str = @lex.gettoken
    if kind != TK::SYMBOL || str != "{" then perror end
    kind, str = block
    codegen ".RET_" + @funcname + ":"
    # 16の倍数になるように揃える
    size = (@lvarsize+15) / 16 * 16
    # 正しいサイズでコードを生成し置き換える
    if size != 0 then
      codechange idx,"  sub  rsp, #{size}"
      codegen "  add  rsp, #{size}"
    else
      codechange idx,nil
    end
    codegen "  pop  rbp"
    codegen "  ret"
    if @functions[@funcname] != nil then perror "redefinition of \"" + @funcname + "\"" end
    @functions[@funcname] = [rettype,[]]
    p @lvars if $opt_d   # デバッグ用
    codeflush
    @funcname = nil
    return true
  end

sizeが0だったらnilに置き換えるようにした。

codeflush

で、nilのときは出力しないように。

  # アセンブリコードの出力
  def codeflush()
    @codebuffer.each do |code|
      if code != nil then @fout.puts code + "\n" end
    end
    @codebuffer = []
  end

これで1件目の修正はOK。

codegen_assign

続いて2件目の修正。

  # 代入のコード生成
  def codegen_assign(el)
    if el.size != 3 then perror end
=begin
    if !el[2].kind_of?(Array) && el[2].kind == TK::NUMBER then
      codegen "  mov  eax, " + el[2].str
    elsif !el[2].kind_of?(Array) && el[2].kind == TK::ID then
      v = @lvars[el[2].str]
      if v == nil then
        perror "undeclared variable \"" + el[2].str + "\""
      end
      codegen "  mov  eax, dword ptr [rbp - " + v[1].to_s + "]"
    else
      codegen_el el[2]
    end
=end
    codegen_el [el[2]]
    if el[0].kind_of?(Array) then perror end
    if el[0].kind != TK::ID then perror end
    v = @lvars[el[0].str]
    if v == nil then
      perror "undeclared variable \"" + el[0].str + "\""
    end
    codegen "  mov  dword ptr [rbp - " + v[1].to_s + "], eax"
  end

代入演算子の右側を求める部分がおかしかったよ。独自に色々やってたけど関数呼出の対応が抜けてた。でよく考えたらcodegen_el [el[2]]の呼び出しで済むことに気付いたよ。10行のコードが消えてすっきり。

動作テスト

ではチェック。まずは1件目から。

~/myc$ myc c.myc print.o
~/myc$ ./c
42
~/myc$ 
.intel_syntax noprefix
.global main
main:
  push rbp
  mov  rbp, rsp
  mov  eax, 42
  mov edi, eax
  call print
.RET_main:
  pop  rbp
  ret

sub rsp,0はいなくなったよ。続いて2件目。

~/myc$ myc l3.myc
~/myc$ ./l3
42
~/myc$

ちゃんとコンパイルできるようになったよ。代入周りはまだバグあるかもなあ。テスト項目増やそう。