コンパイラ作成(40) System V AMD64 ABIへの準拠

mycの問題点

以前、ABIに付いて調べたときから気になっていた点を見直すよ。mycの問題点は2つ。1件目はrbxが保存されてないこと。これは対処はそう難しくないかな。rbxの使用をやめて、r10にすれば良いんだと思う。r10は保存する必要ないからね。2件目はスタックのアライメントの話。こっちは厄介かなあ。

調査

現状どうなってるか調べてみる。まず、調査用の関数を作成するよ。

.intel_syntax noprefix
.global rspcheck
rspcheck:
  push rbp
  mov  rbp, rsp
  lea  rdi, .L.str
  mov  rsi, rsp
  mov  al, 0
  call printf
  pop  rbp
  ret
.L.str:
  .asciz  "rsp = %016x\n"

見て分かる通りrspの値をprintfで表示する関数。clangでアセンブルしてrspcheck.oを作っとくよ。で、これをコールするプログラムを作る。

// rsp
int main()
{
    rspcheck();
    sub() + rspcheck();
}

int sub()
{
    return 42;
}

試してみるよ。

~/myc$ myc m23.myc
/tmp/m23-4c6d95.o: 関数 `main' 内:
(.text+0x9): `rspcheck' に対する定義されていない参照です
(.text+0x14): `rspcheck' に対する定義されていない参照です
clang: error: linker command failed with exit code 1 (use -v to see invocation)
~/myc$ clang-5.0 m23.s rspcheck.o -o m23
~/myc$ ./m23
rsp = 0000000043f54770
rsp = 0000000043f54768
~/myc$ 

やっぱり2個目のrspcheckはスタックのアライメントが16byte境界になってないね。計算式の途中でraxを保存するときにずれちゃってる。clangのエラーが出てるのは気にしないでね。mycが自動でclang呼ぶときrspcheck.oをリンクするようにしてないからこうなっちゃう。myc m23.myc rspcheck.oみたいにできるようにすれば良いんだけどね。この辺は将来対処するつもり。(いつになるか分からないけど)

~/myc$ cd clang
~/myc/clang$ ./m23
rsp = 0000000057219370
rsp = 0000000057219370
~/myc/clang$ 

話を元に戻してアライメントの件、同じソースをclangでコンパイルして確かめてみた。ちゃんとアライメントが揃ってるよ。さすがclang先輩だ。

コード生成

てことでコード生成部を修正するよ。

  # 式のコード生成(二項演算の右側被演算子)
  def codegen_els(op, operand)
    if op.str == "+" then
      ostr = "add "
    elsif op.str == "-" then
      ostr = "sub "
    elsif op.str == "*" then
      ostr = "mul "
    elsif op.str == "/" then
      ostr = "div "
    elsif op.str == "==" then
      ostr = "cmp "
    elsif op.str == "!=" then
      ostr = "cmp "
    elsif op.str == "<" || op.str == "<" || op.str == ">" || op.str == "<=" || op.str == ">=" then
      ostr = "cmp "
    else
      perror "unknown operator \"" + op.str + "\""
    end

    if operand.kind_of?(Array) then
      if operand[0].size == 2 && operand[0].kind == TK::ID && operand[1].str == "()" then
        codegen "  sub  rsp, 8"
        codegen "  push rax"
        codegen "  call " + operand[0].str
        codegen "  mov  r10d, eax"
        codegen "  pop  rax"
        codegen "  add  rsp, 8"
      else
        codegen "  sub  rsp, 8"
        codegen "  push rax"
        codegen_el operand
        codegen "  mov  r10d, eax"
        codegen "  pop  rax"
        codegen "  add  rsp, 8"
      end
      str = "r10d"
    elsif operand.kind == TK::ID then
      v = @lvars[operand.str]
      if v == nil then
        perror "undeclared variable \"" + operand.str + "\""
      end
      str = "dword ptr [rbp - " + v[1].to_s + "]"
    elsif operand.kind == TK::NUMBER then
      str = operand.str
    end

    if op.str == "==" then
      codegen "  " + ostr + " eax, " + str
      codegen "  sete al"
      codegen "  and  eax, 1"
    elsif op.str == "!=" then
      codegen "  " + ostr + " eax, " + str
      codegen "  setne al"
      codegen "  and  eax, 1"
    elsif op.str == "<" then
      codegen "  " + ostr + " eax, " + str
      codegen "  setl al"
      codegen "  and  eax, 1"
    elsif op.str == ">" then
      codegen "  " + ostr + " eax, " + str
      codegen "  setg al"
      codegen "  and  eax, 1"
    elsif op.str == "<=" then
      codegen "  " + ostr + " eax, " + str
      codegen "  setle al"
      codegen "  and  eax, 1"
    elsif op.str == ">=" then
      codegen "  " + ostr + " eax, " + str
      codegen "  setge al"
      codegen "  and  eax, 1"
    elsif op.str == "*" || op.str == "/" then
      if str != "r10d" then codegen "  mov  r10d, " + str end
      codegen "  mov  r11, rdx"
      if op.str == "/" then
        codegen "  xor  edx, edx"
      end
      codegen "  " + ostr + " r10d"
      codegen "  mov  rdx, r11"
    else
      codegen "  " + ostr + " eax, " + str
    end
  end

修正箇所はここだけかなあ。

動作テスト

では行ってみるよ。

~/myc$ myc m23.myc
/tmp/m23-5aed64.o: 関数 `main' 内:
(.text+0x9): `rspcheck' に対する定義されていない参照です
(.text+0x18): `rspcheck' に対する定義されていない参照です
clang: error: linker command failed with exit code 1 (use -v to see invocation)
~/myc$ clang-5.0 m23.s rspcheck.o -o m23
~/myc$ ./m23
rsp = 000000008c5062c0
rsp = 000000008c5062b0
~/myc$ 

アライメント揃ったよ。

.intel_syntax noprefix
.global main
main:
  push rbp
  mov  rbp, rsp
  sub  rsp, 64
  call rspcheck
  call sub
  sub  rsp, 8
  push rax
  call rspcheck
  mov  r10d, eax
  pop  rax
  add  rsp, 8
  add  eax, r10d
.RET_main:
  add  rsp, 64
  pop  rbp
  ret
.global sub
sub:
  push rbp
  mov  rbp, rsp
  sub  rsp, 64
  mov  eax, 42
  jmp  .RET_sub
.RET_sub:
  add  rsp, 64
  pop  rbp
  ret

生成されたコードはこんな感じ。今回の件はコーディング自体は大したことないんだけど、ABIを正しく理解するのが難しかったよ。