x86-64のCalling Convention
x86-64(AMD64)のCalling Conventionについてメモ.ネット上に資料はたくさんありますが,断片的だったり,一覧性が低かったり,互いに矛盾しているように見えたりしたので自分でまとめることにしました.
(と言っておきながら,Agner Fogさんの資料*1のp.10 Register Usage に全部書いてあったので,それを読めば良いと思います)
Calling Conventionとは
関数を呼び出すとき,機械語レベルでは,レジスタをどう使うかが重要になります.スタックは関数ごとに独立してメモリを利用できますが,レジスタは共有資源なので関数の呼び出し元(caller)と呼び出し先(callee)で協調して利用する必要があります.
ポイントとしては,
- 引数をどうやって渡すか
- 戻り値をどうやって返すか
- それ以外のレジスタをどう使うか(レジスタがVolatileかどうか)
があります.
前述のようにレジスタは共有資源なので,利用方法について合意する必要があります.「Volatileかどうか」とは,その取り決めのことです.
以下の2通りのやり方があります(C言語の volatile
とは関係ない).
- Caller-save (= volatile or scratch) :callee が自由に使って良い(破壊して良い).よって,必要であればCallerが自分で内容を保存しておく責任がある
- Callee-save (=non-volatile):callee側が使って良いが,callerから見て呼び出し前と呼び出し後で値が同じである必要がある.言い換えると,calleeが現状復帰する責任がある
呼び出し規約の種類
x86-64で用いられる呼び出し規約は大きく分けて2種類あります.
- System V AMD64 ABI - 主に Solaris, Linux, FreeBSD, macOS で用いられる.
- Microsoft x64 calling convention - Windowsで用いられる.(vectorcall というのもあるが,これはMS-x64の拡張)
呼び出し規約の詳細
MS-x86とSysVの共通事項
- ある個数の引数まではレジスタで渡す,レジスタに載らない引数はスタックで渡す.スタックに詰む順番は右から左 (RTL)
System V AMD64 ABI
- 関数呼び出し
- 整数 or ポインタ RDI, RSI, RDX, RCX, R8, R9
- 不動小数点 XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 and XMM7
- 戻り値
- 整数64ビットまでRAX
- 整数128ビットまでRAX & RDX
- 不動小数点 XMM0 and XMM1
- caller/callee-save
- RBX, RSP, RBP, and R12–R15 : non-volatile (= callee save)
- RAX, RCX, RDX, RSI, RDI, R8-R11 XMM0-XMM15, YMM0-YMM15, ZMM0-ZMM31: volatile (= caller save)
- システムコールでは RCX の代わりに R10 を使用
Microsoft x64 calling convention
- 関数呼び出
- 整数 RCX, RDX, R8, R9
- 不動小数点 XMM0, XMM1, XMM2, XMM3
- 戻り値 = RAX or XMM0
- caller/callee-save
- RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, XMM6, XMM7 : non-volatile (= callee save)
- RAX, RCX, RDX, R8-R11, XMM0-XMM5, (YMM, ZMMレジスタ,ただしXMM6-XMM15の下位128ビット以外) volatile (=caller-save)