コンパイラ作成(46) ローカル変数領域のサイズ
今回の目標
ローカル変数領域のサイズが64byte固定になってる件を修正するよ。
// 変数26個 int main() { int a,b,c,d,e,f,g,h,i,j; int k,l,m,n,o,p,q,r,s,t; int u,v,w,x,y,z; }
64byteに収まらない数のint変数を宣言してる。
initialize
まずはここ。
# コンストラクタ def initialize(fname) @fname = fname # ソースファイルのファイル名 @asmfname = fname.sub(/\.myc$/,'.s') # アセンブリコードのファイル名 =begin @exefname = fname.sub(/\.myc$/,'') # 実行ファイル名 =end @lex = Lexer.new(@fname) # 字句解析 @funcname = nil # 現在処理している関数名 @labelcnt = nil # 自動生成するラベルの個数(関数単位) @literalcnt = 0 # 文字列リテラルの数 @literaltable = [] # 文字列リテラルのリスト =begin @needprint = false # print.oのリンクが必要か =end @functions = Hash.new # 関数 @lvars = nil # ローカル変数 @lvarsize = nil # スタックに確保する領域のサイズ @breaklabel = nil # breakの飛び先のラベル @codebuffer = [] # コードバッファ end
codebuffを追加した。直接ファイルに出力せずに、一旦バッファに溜めていく。こうすれば後から書き換えられるからね。
codegen
今回修正のメイン。
# アセンブリコードの生成 def codegen(code) @codebuffer << code return @codebuffer.size - 1 end # アセンブリコードの変更 def codechange(i,code) @codebuffer[i] = code end # アセンブリコードの出力 def codeflush() @codebuffer.each do |code| @fout.puts code + "\n" end @codebuffer = [] end
codechangeとcodeflushを追加した。
function
codechangeを使って書き換えてるところ。
# 関数の構文解析 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 # 正しいサイズでコードを生成し置き換える codechange idx," sub rsp, #{size}" codegen " add rsp, #{size}" 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
一旦sub rsp, xxでcodegenしといて、スタック上に確保するサイズが確定した後でcodechangeで書き換え。サイズは16の倍数になるように切り上げて調整してるよ。System V AMD64 ABIへの準拠の為にね。
その他
必要なところにcodeflushを埋め込み。
# 文字列リテラルを出力 def genliterals @literaltable.each_with_index do |literal, i| codegen (literallabel(i) + ":") codegen (" .asciz \"" + literal + "\"") end codeflush end
# programの構文解析 def program begin @fout = File.open(@asmfname,"w") rescue SystemCallError => e puts %Q(class=[#{e.class}] message=[#{e.message}]) end codegen ".intel_syntax noprefix" codeflush while function do end genliterals p @functions if $opt_d # デバッグ用 @fout.close @lex.close end
これで全部かな。
動作テスト
それでは行ってみるよ。
~/myc$ myc m27.myc ~/myc$ ./m27 ~/myc$
コンパイルはできたけどこれじゃ良く分からないな。アセンブリコードを見てみるか。
.intel_syntax noprefix .global main main: push rbp mov rbp, rsp sub rsp, 112 .RET_main: add rsp, 112 pop rbp ret
お、ちゃんと書き換えられてる。えーと、int変数26個だから4*26で104byteかな。で16の倍数だから16*7で112。うんうん、合ってる。これで好きなだけint変数宣言できるようになったよ。まあ、int型だけだけどさ。早く新しい型のサポートしたいなあ。でも敷居高いかな。もうちょっと先の話だな。さて次はどうするかな。やんなきゃいけないことはまだまだあるけど簡単にできそうなことをちょっとづつだな。