コンパイラ作成(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に依存しないよう抽象化する方が良いのかな。この辺は遠い将来の課題だな。