コンパイラ作成(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型だけだけどさ。早く新しい型のサポートしたいなあ。でも敷居高いかな。もうちょっと先の話だな。さて次はどうするかな。やんなきゃいけないことはまだまだあるけど簡単にできそうなことをちょっとづつだな。