コンパイラ作成(50) 引数のある関数の呼び出しを修正

今回の目標

前回、Segmentation faultになっちゃったのを頑張って修正するよ。

// 引数のある関数の呼び出し
int main()
{
   int a = 5, b = 12, c;
   printf("%d + %d = %d\n",a,b,add(a,b));
}

printfを呼ぶためにセットしたレジスタの値が、addの呼び出しで壊されちゃってた。レジスタを退避して壊れなくするよ。

initialize

管理用の変数を追加。

  # コンストラクタ
  def initialize(fname)
    @fname = fname                        # ソースファイルのファイル名
    @asmfname = fname.sub(/\.myc$/,'.s')  # アセンブリコードのファイル名
=begin
    @exefname = fname.sub(/\.myc$/,'')    # 実行ファイル名
=end
    @regs32 = ["edi", "esi","edx","ecx","r8d","r9d"]  # 32bitレジスタ
    @regs64 = ["rdi", "rsi","rdx","rcx","r8", "r9" ]  # 64bitレジスタ
    @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 = []                      # コードバッファ
    @numuseregs = 0                       # 関数コールで使用しているレジスタの数
  end

numuseregsってやつね。あと、regs32とregs64をここに持ってきたよ。

codegen_func

レジスタの退避、復元の処理を追加。

  # 関数コールのコード生成
  def codegen_func operand
    if @numuseregs != 0 then
      if @numuseregs % 2 == 1 then codegen "  sub  rsp, 8" end
      (0...@numuseregs).each do |i| codegen "  push #{@regs64[i]}" end
    end
    (0...operand.size-2).each do |i|
      save = @numuseregs
      @numuseregs = i
      codegen_el operand[i+2]
      @numuseregs = save
      codegen "  mov  #{@regs32[i]}, eax"
    end
    codegen "  call " + operand[0].str
    if @numuseregs != 0 then
      (0...@numuseregs).reverse_each do |i| codegen "  pop  #{@regs64[i]}" end
      if @numuseregs % 2 == 1 then codegen "  add  rsp, 8" end
    end
  end

push/popするよりmovでスタック上に移動する方が、実行サイクル上有利なのかなあ。分かり易いんでこうしちゃったんだけど。それと退避するレジスタが奇数の場合は、rspを調整して16byte境界になるようにしてる。

printfの処理

numuseregsを設定してるよ。

      # 標準関数printfの処理
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != "(" then perror end
      kind, str = @lex.gettoken
      i = 0
      loop do
        if kind == TK::STRING then
          label = addliteral str
          codegen "  lea  "+@regs64[i]+", "+label
          kind, str = @lex.gettoken
        else
          save = @numuseregs
          @numuseregs = i
          kind, str = expr kind, str
          @numuseregs = save
          codegen "  mov  "+@regs32[i]+", eax"
        end
        if kind == TK::SYMBOL && str == ")" then break end
        if kind != TK::SYMBOL || str != "," then perror end
        kind, str = @lex.gettoken
        i += 1
      end
      codegen "  mov  al, 0"
      codegen "  call printf"
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != ";" then
        perror "expected ';' after function"
      end

早くこの処理を削除したいよ。すべての関数コールをexprで処理できたら削除できるんだけどね。その為にはもうちょっと頑張らないとね。printfの最初の引数の型がchar*なんだけど、これに対応するためにはexprで型を意識しないとダメ。

動作テスト

どうかな。codegen_funcが再帰codegen_elを呼んでたりしてるんで、正しくコーディングできてるか心配だよ。考えてるうちに頭の中がこんがらがってくるしね。

~/myc$ myc o4.myc add2.o
~/myc$ ./o4
add: a = 5 b = 12
5 + 12 = 17
~/myc$

ふう、ちゃんと動いたよ。

.intel_syntax noprefix
.global main
main:
  push rbp
  mov  rbp, rsp
  sub  rsp, 16
  mov  eax, 5
  mov  dword ptr [rbp - 4], eax
  mov  eax, 12
  mov  dword ptr [rbp - 8], eax
  lea  rdi, .L.str
  mov  eax, dword ptr [rbp - 4]
  mov  esi, eax
  mov  eax, dword ptr [rbp - 8]
  mov  edx, eax
  sub  rsp, 8
  push rdi
  push rsi
  push rdx
  mov  eax, dword ptr [rbp - 4]
  mov  edi, eax
  mov  eax, dword ptr [rbp - 8]
  mov  esi, eax
  call add
  pop  rdx
  pop  rsi
  pop  rdi
  add  rsp, 8
  mov  ecx, eax
  mov  al, 0
  call printf
.RET_main:
  add  rsp, 16
  pop  rbp
  ret
.L.str:
  .asciz  "%d + %d = %d\n"

addのcallの前後でちゃんとpush/popされてる。これで引数のある関数の呼び出しはできるようになったよ。次は呼ばれる方か。これもやっかいだな。レジスターに設定されてる引数を保存しとかないといけないしね。なんだかx86_64のプログラミングって面倒なこと多いな。昔のCPUはもっと扱いが楽だったよね。今日はここまで。