コンパイラ作成(48) 最適化

今回の目標

突然だけど最適化処理を追加するよ。って言っても超簡単なやつだけどね。

main()
{
    return 42;
}

これ一番最初にコンパイルしたテストプログラムだよ。

.intel_syntax noprefix
.global main
main:
  push rbp
  mov  rbp, rsp
  mov  eax, 42
  jmp  .RET_main
.RET_main:
  pop  rbp
  ret

今はこういう風にコンパイルされるんだけど、jmp命令が不要なんで最適化処理でこれを削除するよ。

optimize

メソッドを追加。

  # 最適化
  def optimize
    if @codebuffer.size < 2 then return end
    for i in 0..(@codebuffer.size-1) do
      code1 = @codebuffer[i]
      if code1 == nil then next end
      j = i + 1
      code2 = nil
      loop do
        if j == @codebuffer.size then break end
        code2 = @codebuffer[j]
        if code2 != nil then break end
        j += 1
      end
      if code2 == nil then next end
      if md1 = code1.match(/^\s+jmp\s+(\S+)/) then
        if md2 = code2.match(/^(\S+):/) then
          if md1[1].to_s == md2[1].to_s then
            @codebuffer[i] = nil
          end
        end
      end
    end
  end

codebufferをスキャンして無駄なjmpをnilに置き換えて行ってる。

function

あとここを修正。

  # 関数の構文解析
  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   # デバッグ用
    optimize if $opt_O != 0
    codeflush
    @funcname = nil
    return true
  end

codeflushする前にoptimizeを呼んでる。

動作テスト

どうかな。

~/myc$ myc -c b.myc
~/myc$
.intel_syntax noprefix
.global main
main:
  push rbp
  mov  rbp, rsp
  mov  eax, 42
.RET_main:
  pop  rbp
  ret

jmpいなくなったよ。

~/myc$ myc -c b.myc -O0
~/myc$ 
.intel_syntax noprefix
.global main
main:
  push rbp
  mov  rbp, rsp
  mov  eax, 42
  jmp  .RET_main
.RET_main:
  pop  rbp
  ret

最適化しないとjmpは残ったままだよ。この前、codebufferに一旦溜めるようにしたんで、こういった最適化処理ができるようになったよ。違うパターンも組み込めば多少は賢いコードになるんだと思う。ただ、こういうことをやるなら、適当な中間コードを生成して、その上でやった方が良いかもね。テキストデータだとスキャンしにくいよ。その場合どういった中間コードにするかが問題かな。対象CPUに依存しないよう抽象化する方が良いのかな。この辺は遠い将来の課題だな。

mycの課題
  1. 引数のある関数
  2. int以外の型
  3. 単項演算子
  4. マイナスの数値リテラル
  5. 配列、ポインタ、構造体
  6. プリプロセッサ

ぱっと思いつくのでもこのぐらいかな。積み残しで宿題になってるのも多いしなあ。大変だけどちょっとずつ頑張ろう。次は関数関連をなんとかするか。