swiftコードがどのようにアセンブラに出力されているのかをみてみましょう。
Xcodeのプロジェクトで、コマンドラインプロジェクトを生成し、以下のソースをmain.swiftに書き込みます。
import Foundation func plus(_ a: Int64, _ b: Int64) -> (Int64) { return a + b } let r = plus(3, 7) print("\(r)")
Xcodeの Debug - Debug Workflow - Always Show Disassembly にチェックをつけます。
return a + b
の行にbreakpointを設定して実行してみましょう。
aaa`plus(_:_:): 0x100001e60 <+0>: pushq %rbp 0x100001e61 <+1>: movq %rsp, %rbp 0x100001e64 <+4>: movq %rdi, -0x8(%rbp) 0x100001e68 <+8>: movq %rsi, -0x10(%rbp) -> 0x100001e6c <+12>: addq %rsi, %rdi 0x100001e6f <+15>: seto %al 0x100001e72 <+18>: movq %rdi, -0x18(%rbp) 0x100001e76 <+22>: movb %al, -0x19(%rbp) 0x100001e79 <+25>: jo 0x100001e81 ; <+33> at main.swift:13 0x100001e7b <+27>: movq -0x18(%rbp), %rax 0x100001e7f <+31>: popq %rbp 0x100001e80 <+32>: retq 0x100001e81 <+33>: ud2
push %rbp
move %rsp, %rbp
は関数の先頭なので、必ずこの2行が入ります。
movq %rdi, -0x8(%rbp)
の行は第1引数の値を第2引数で指定した位置にコピーします。
movq のqは8バイト分(64bit)分のコピーを意味します。
このコマンドは%rsiレジスタの値をレジスタ%rbpが指すアドレスの8バイトマイナス方向の位置にコピーすることを意味しています。
%rbpがスタックの位置を表しており、マイナス方向がpush、プラス方向がpopです。
マイナス方向が上、プラス方向が下とイメージしておくと、スタックのイメージと合います。
ここでレジスタの値をみるには、(lldb)のところに、register read と入れます。
(lldb) register read General Purpose Registers: rax = 0x0000000000000007 rbx = 0x0000000000000000 rcx = 0x0000000000000003 rdx = 0x0000000000000007 rdi = 0x0000000000000003 rsi = 0x0000000000000007 rbp = 0x00007ffeefbff4f0 rsp = 0x00007ffeefbff4f0 r8 = 0x0000000000000000 r9 = 0x000000000007b857 r10 = 0x00007fff944ee0c8 atexit_mutex + 24 r11 = 0x00007fff944ee0d0 atexit_mutex + 32 r12 = 0x0000000000000000 r13 = 0x0000000000000000 r14 = 0x0000000000000000 r15 = 0x0000000000000000 rip = 0x0000000100001e6c aaa`aaa.plus(Swift.Int64, Swift.Int64) -> Swift.Int64 + 12 at main.swift:13 rflags = 0x0000000000000206 cs = 0x000000000000002b fs = 0x0000000000000000 gs = 0x0000000000000000 (lldb)
関数の第一引数の値はrdiレジスタ、第二引数の値はrsiレジスタに入ります。
rdiには3、rsiには7が入っていることが分かります。
レジスタにはサイズがあり、レジスタ名の先頭がrは64ビット、先頭eが32ビットのような感じになっていますが、
それ以外は以下を参照して下さい。
x64 Architecture | Microsoft Docs
https://ja.wikibooks.org/wiki/X86アセンブラ/x86アーキテクチャ
addqは右のレジスタに入っている値に左のレジスタに入っている値を加えます。
qは64bit演算であることを意味しています。
seto %al
はオーバーフローが発生していたら%alに1、発生していなければ0を設定します。
%alは8bitレジスタです。
joはオーバーフローが発生していたら指定アドレスに処理をジャンプします。
movq -0x18(%rbp), %rax
によって、plus関数の結果をraxレジスタにセットしています。
戻り値が一つの場合raxレジスタに値をセットして、関数の呼び元に値を伝えます。
popq %rbp
retq
は関数の終わりは必ずこの形になり、callqで呼び出した元の処理に戻ります。
ここでpopq %bpの行にプレークポイントを設定して、デバッグ続行してみましょう。
ここでもう一度レジスタの値を確認します。
(lldb) register read General Purpose Registers: rax = 0x000000000000000a rbx = 0x0000000000000000 rcx = 0x0000000000000003 rdx = 0x0000000000000007 rdi = 0x000000000000000a rsi = 0x0000000000000007 rbp = 0x00007ffeefbff4f0 rsp = 0x00007ffeefbff4f0 r8 = 0x0000000000000000 r9 = 0x000000000007b857 r10 = 0x00007fff944ee0c8 atexit_mutex + 24 r11 = 0x00007fff944ee0d0 atexit_mutex + 32 r12 = 0x0000000000000000 r13 = 0x0000000000000000 r14 = 0x0000000000000000 r15 = 0x0000000000000000 rip = 0x0000000100001e7f aaa`aaa.plus(Swift.Int64, Swift.Int64) -> Swift.Int64 + 31 at main.swift:13 rflags = 0x0000000000000206 cs = 0x000000000000002b fs = 0x0000000000000000 gs = 0x0000000000000000
raxレジスタに3+7の結果である10 = 0xaが入っていることが分かります。
以外にアセンブラも読めるものであることが分かったのではないでしょうか。
戻り値が2つ以上のtupleの場合
plus関数の戻り値が(Int64, Int64)のようなタプルの場合には、戻り値がrax, rdxに入ります。
その他コマンド
leaq メモリ上の場所のアドレス値をレジスタに格納するもの