別に大したことはできないだが、Swiftのビルド環境を構築した。
Swiftのビルド環境ができたことに感動して、いろいろやろうと思ってチュートリアルを見て驚き。最初のプログラムは、
vagrant@vagrant-ubuntu-vivid-64:~/work/helloworld$ swift helloworld.swift Hello World! vagrant@vagrant-ubuntu-vivid-64:~/work/helloworld$ swiftc helloworld.swift vagrant@vagrant-ubuntu-vivid-64:~/work/helloworld$ ls -lt total 20 -rwxrwxr-x 1 vagrant vagrant 10008 Dec 11 16:52 helloworld -rw-rw-r-- 1 vagrant vagrant 23 Dec 11 16:51 helloworld.swift -rw-rw-r-- 1 vagrant vagrant 25 Dec 11 16:51 helloworld.swift~ vagrant@vagrant-ubuntu-vivid-64:~/work/helloworld$ ./helloworld Hello World! vagrant@vagrant-ubuntu-vivid-64:~/work/helloworld$
あらま。もちろん、swiftを呼び出してインタプリタとして動作させることができるが、swiftcを使ってコンパイルすることができる。 ということは、swiftのプログラム、所謂スクリプト言語に近いものを逆アセンブルしたりすると、どういうことが分かるだろう?やってみよう。
1から10まで数る関数をSwiftで書いて、コンパイルしてみる
以下のようなSwiftの関数を書いてみた。sumTotal()関数は、1からcountまでの値を総和する関数だ。
func sumTotal (count: Int) -> Int { var val = 0 for i in 1...count { val = val + i } return val } print ("sumtotal(10) = \(sumTotal(10))\n")
実行してみると以下のようになる。
$ swift ./function.swift sumtotal(10) = 55
さて、これをコンパイルしてみると、
$ swiftc ./function.swift $ ./function sumtotal(10) = 55
同じように実行することができるね。
生成したバイナリを解析する
Readelfによるセクションの取得
まずは、readelfでどのようなセクションが入っているのかを見てみよう。
$ readelf -l function Elf file type is EXEC (Executable file) Entry point 0x400eb0 There are 8 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040 0x00000000000001c0 0x00000000000001c0 R E 8 INTERP 0x0000000000000200 0x0000000000400200 0x0000000000400200 0x000000000000001c 0x000000000000001c R 1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x000000000000174c 0x000000000000174c R E 200000 LOAD 0x0000000000001750 0x0000000000601750 0x0000000000601750 0x0000000000000330 0x0000000000000350 RW 200000 DYNAMIC 0x0000000000001770 0x0000000000601770 0x0000000000601770 0x0000000000000220 0x0000000000000220 RW 8 NOTE 0x000000000000021c 0x000000000040021c 0x000000000040021c 0x0000000000000020 0x0000000000000020 R 4 GNU_EH_FRAME 0x00000000000015fc 0x00000000004015fc 0x00000000004015fc 0x000000000000003c 0x000000000000003c R 4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 10 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .swift1_autolink_entries .eh_frame_hdr .eh_frame 03 .init_array .fini_array .swift2_protocol_conformances .jcr .dynamic .got .got.plt .data .bss 04 .dynamic 05 .note.ABI-tag 06 .eh_frame_hdr 07
いわゆる普通のプログラムだね!ちゃんと.textセクションも入っているし、普通のx86のバイナリとして動作するように構成されているように見える。
objdumpでどのような命令が含まれているのか解析
次に、objdumpを使ってfunctionがどのような命令群に変換されているかを見てみよう。
$ objdump -d function > function.dmp $ less function.dmp 0000000000401150 <_TF8function8sumTotalFSiSi>: 401150: 55 push %rbp 401151: 48 89 e5 mov %rsp,%rbp 401154: 53 push %rbx 401155: 48 81 ec d8 00 00 00 sub $0xd8,%rsp 40115c: 48 c7 45 f0 00 00 00 movq $0x0,-0x10(%rbp) 401163: 00 401164: 48 89 7d a8 mov %rdi,-0x58(%rbp) 401168: eb 00 jmp 40116a <_TF8function8sumTotalFSiSi+0x1a> 40116a: eb 00 jmp 40116c <_TF8function8sumTotalFSiSi+0x1c> 40116c: eb 00 jmp 40116e <_TF8function8sumTotalFSiSi+0x1e> 40116e: eb 00 jmp 401170 <_TF8function8sumTotalFSiSi+0x20> 401170: eb 00 jmp 401172 <_TF8function8sumTotalFSiSi+0x22> 401172: eb 00 jmp 401174 <_TF8function8sumTotalFSiSi+0x24> 401174: eb 00 jmp 401176 <_TF8function8sumTotalFSiSi+0x26> 401176: eb 00 jmp 401178 <_TF8function8sumTotalFSiSi+0x28> 401178: b8 01 00 00 00 mov $0x1,%eax 40117d: 89 c1 mov %eax,%ecx 40117f: 48 8b 55 a8 mov -0x58(%rbp),%rdx 401183: 48 39 d1 cmp %rdx,%rcx 401186: 40 0f 9e c6 setle %sil 40118a: 40 80 f6 ff xor $0xff,%sil 40118e: 40 f6 c6 01 test $0x1,%sil 401192: 75 02 jne 401196 <_TF8function8sumTotalFSiSi+0x46> 401194: eb 79 jmp 40120f <_TF8function8sumTotalFSiSi+0xbf> 401196: eb 00 jmp 401198 <_TF8function8sumTotalFSiSi+0x48> 401198: eb 00 jmp 40119a <_TF8function8sumTotalFSiSi+0x4a> 40119a: eb 00 jmp 40119c <_TF8function8sumTotalFSiSi+0x4c> 40119c: eb 00 jmp 40119e <_TF8function8sumTotalFSiSi+0x4e> 40119e: 48 8d 3d 91 03 00 00 lea 0x391(%rip),%rdi # 401536 <_IO_stdin_used+0x16> 4011a5: b8 0b 00 00 00 mov $0xb,%eax 4011aa: 89 c6 mov %eax,%esi 4011ac: b8 02 00 00 00 mov $0x2,%eax 4011b1: 48 8d 0d 98 03 00 00 lea 0x398(%rip),%rcx # 401550 <_IO_stdin_used+0x30> 4011b8: ba 21 00 00 00 mov $0x21,%edx 4011bd: 41 89 d0 mov %edx,%r8d 4011c0: 4c 8d 0d b9 03 00 00 lea 0x3b9(%rip),%r9 # 401580 <_IO_stdin_used+0x60> 4011c7: ba 38 00 00 00 mov $0x38,%edx 4011cc: 41 89 d2 mov %edx,%r10d 4011cf: ba c3 00 00 00 mov $0xc3,%edx 4011d4: 41 89 d3 mov %edx,%r11d 4011d7: 89 c2 mov %eax,%edx 4011d9: 4c 89 4d a0 mov %r9,-0x60(%rbp) 4011dd: 41 89 c1 mov %eax,%r9d 4011e0: 48 8b 5d a0 mov -0x60(%rbp),%rbx 4011e4: 48 89 1c 24 mov %rbx,(%rsp) 4011e8: 48 c7 44 24 08 38 00 movq $0x38,0x8(%rsp) 4011ef: 00 00 4011f1: c7 44 24 10 02 00 00 movl $0x2,0x10(%rsp) 4011f8: 00 4011f9: 48 c7 44 24 18 c3 00 movq $0xc3,0x18(%rsp) 401200: 00 00 401202: 4c 89 5d 98 mov %r11,-0x68(%rbp) 401206: 4c 89 55 90 mov %r10,-0x70(%rbp) 40120a: e8 71 fc ff ff callq 400e80 <_TFs18_fatalErrorMessageFTVs12StaticStringS_S_Su_T_@plt> 40120f: eb 00 jmp 401211 <_TF8function8sumTotalFSiSi+0xc1> 401211: eb 00 jmp 401213 <_TF8function8sumTotalFSiSi+0xc3> 401213: eb 00 jmp 401215 <_TF8function8sumTotalFSiSi+0xc5> 401215: eb 00 jmp 401217 <_TF8function8sumTotalFSiSi+0xc7> 401217: eb 00 jmp 401219 <_TF8function8sumTotalFSiSi+0xc9> 401219: eb 00 jmp 40121b <_TF8function8sumTotalFSiSi+0xcb> 40121b: eb 00 jmp 40121d <_TF8function8sumTotalFSiSi+0xcd> 40121d: eb 00 jmp 40121f <_TF8function8sumTotalFSiSi+0xcf> 40121f: eb 00 jmp 401221 <_TF8function8sumTotalFSiSi+0xd1> 401221: eb 00 jmp 401223 <_TF8function8sumTotalFSiSi+0xd3> 401223: eb 00 jmp 401225 <_TF8function8sumTotalFSiSi+0xd5> 401225: 48 8b 45 a8 mov -0x58(%rbp),%rax 401229: 48 ff c0 inc %rax 40122c: 0f 90 c1 seto %cl 40122f: 48 8b 55 a8 mov -0x58(%rbp),%rdx 401233: 48 39 d0 cmp %rdx,%rax 401236: 40 0f 9f c6 setg %sil 40123a: 40 80 f6 ff xor $0xff,%sil 40123e: 40 f6 c6 01 test $0x1,%sil 401242: 88 4d 8f mov %cl,-0x71(%rbp) 401245: 75 02 jne 401249 <_TF8function8sumTotalFSiSi+0xf9> 401247: eb 7f jmp 4012c8 <_TF8function8sumTotalFSiSi+0x178> 401249: eb 00 jmp 40124b <_TF8function8sumTotalFSiSi+0xfb> ...
何だか異様に長い命令列が生成されているなあ。。。本体となる部分はどこだろう?
良く分からないので、加算のところを乗算に変えてどこが差分になっているのかを観察する
$ cat function.swift func sumTotal (count: Int) -> Int { var val = 0 for i in 1...count { val = val * i } return val } print ("sumtotal(10) = \(sumTotal(10))\n") $ swiftc ./function.swift $ objdump -d function > function.mul.dmp
加算val=val+iを乗算val=val*iに変更しただけだ。objdump結果の差分はどこだろう?
$ diff -y -W200 function.dmp function.mul.dmp | less ... 40134c: 48 89 85 58 ff ff ff mov %rax,-0xa8(%rbp) 40134c: 48 89 85 58 ff ff ff mov %rax,-0xa8(%rbp) 401353: 75 02 jne 401357 <_TF8function8sumTotalFSiSi+0x207> 401353: 75 02 jne 401357 <_TF8function8sumTotalFSiSi+0x207> 401355: eb 2a jmp 401381 <_TF8function8sumTotalFSiSi+0x231> | 401355: eb 2b jmp 401382 <_TF8function8sumTotalFSiSi+0x232> 401357: 48 8b 85 58 ff ff ff mov -0xa8(%rbp),%rax 401357: 48 8b 85 58 ff ff ff mov -0xa8(%rbp),%rax 40135e: 48 03 45 f0 add -0x10(%rbp),%rax | 40135e: 48 0f af 45 f0 imul -0x10(%rbp),%rax 401362: 0f 90 c1 seto %cl | 401363: 0f 90 c1 seto %cl 401365: 48 89 85 50 ff ff ff mov %rax,-0xb0(%rbp) | 401366: 48 89 85 50 ff ff ff mov %rax,-0xb0(%rbp) 40136c: 88 8d 4f ff ff ff mov %cl,-0xb1(%rbp) | 40136d: 88 8d 4f ff ff ff mov %cl,-0xb1(%rbp) 401372: 70 1b jo 40138f <_TF8function8sumTotalFSiSi+0x23f> | 401373: 70 1b jo 401390 <_TF8function8sumTotalFSiSi+0x240> 401374: 48 8b 85 50 ff ff ff mov -0xb0(%rbp),%rax | 401375: 48 8b 85 50 ff ff ff mov -0xb0(%rbp),%rax 40137b: 48 89 45 f0 mov %rax,-0x10(%rbp) | 40137c: 48 89 45 f0 mov %rax,-0x10(%rbp) 40137f: eb a0 jmp 401321 <_TF8function8sumTotalFSiSi+0x1d1> | 401380: eb 9f jmp 401321 <_TF8function8sumTotalFSiSi+0x1d1> 401381: 48 8b 45 f0 mov -0x10(%rbp),%rax | 401382: 48 8b 45 f0 mov -0x10(%rbp),%rax 401385: 48 81 c4 d8 00 00 00 add $0xd8,%rsp | 401386: 48 81 c4 d8 00 00 00 add $0xd8,%rsp 40138c: 5b pop %rbx | 40138d: 5b pop %rbx 40138d: 5d pop %rbp | 40138e: 5d pop %rbp 40138e: c3 retq | 40138f: c3 retq 40138f: 0f 0b ud2 | 401390: 0f 0b ud2 401391: 66 66 66 66 66 66 2e data16 data16 data16 data16 data16 nopw %cs:0x0(%rax,%ra | 401392: 66 66 66 66 66 2e 0f data16 data16 data16 data16 nopw %cs:0x0(%rax,%rax,1) 401398: 0f 1f 84 00 00 00 00 | 401399: 1f 84 00 00 00 00 00 40139f: 00 <
ここだ!つまり、add命令が、imul命令に変わっているだけだ、と見ることができる。つまり、この関数sumTotalのキモとなる部分は、ここだけなのだ。 それにしても前後のお膳立てが多すぎる!
ちなみ、C++で同じことを書くとどうなる?
これだけだ。短い!
$ cat function.cpp int sumTotal(int count) { int val = 0; for (int i = 0; i < count; i++) { val += i; } } $ g++ -c function.cpp vagrant@vagrant-ubuntu-vivid-64:~/work/function$ objdump -d function.o function.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <_Z8sumTotali>: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: 89 7d ec mov %edi,-0x14(%rbp) 7: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%rbp) e: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp) 15: eb 0a jmp 21 <_Z8sumTotali+0x21> 17: 8b 45 fc mov -0x4(%rbp),%eax 1a: 01 45 f8 add %eax,-0x8(%rbp) 1d: 83 45 fc 01 addl $0x1,-0x4(%rbp) 21: 8b 45 fc mov -0x4(%rbp),%eax 24: 3b 45 ec cmp -0x14(%rbp),%eax 27: 7c ee jl 17 <_Z8sumTotali+0x17> 29: 5d pop %rbp 2a: c3 retq
じゃあ、Swiftのコンパイラの性能は悪いのか?
決してそんなことは無い。インタプリタ型の言語が元になっているということ、さらに、様々なプラットフォームに対応しなければならないことを考えると、様々な理由でこれだけ無駄なコードが入っているものと想像できる。 大体、真に効率が悪いコードが出ているのならば、アップルの技術者が放っておくはずが無いと思うのだが。。。
おまけ: 最適化オプションが存在した
あれ、-Oオプションがあるじゃん。
$ swiftc -help OVERVIEW: Swift compiler USAGE: swiftc [options] <inputs> ... -O Compile with optimizations ...
試してみよう。
$ swiftc -O ./function.swift $ objdump -d function > function.dmp $ less function.dmp 0000000000401770 <_TF8function8sumTotalFSiSi>: 401770: 55 push %rbp 401771: 48 89 e5 mov %rsp,%rbp 401774: 48 85 ff test %rdi,%rdi 401777: 7e 36 jle 4017af <_TF8function8sumTotalFSiSi+0x3f> 401779: 48 b8 ff ff ff ff ff movabs $0x7fffffffffffffff,%rax 401780: ff ff 7f 401783: 48 39 c7 cmp %rax,%rdi 401786: 74 27 je 4017af <_TF8function8sumTotalFSiSi+0x3f> 401788: 31 c9 xor %ecx,%ecx 40178a: 31 c0 xor %eax,%eax 40178c: 48 85 ff test %rdi,%rdi 40178f: 74 1c je 4017ad <_TF8function8sumTotalFSiSi+0x3d> 401791: 66 66 66 66 66 66 2e data16 data16 data16 data16 data16 nopw %cs:0x0(%rax,%rax,1) 401798: 0f 1f 84 00 00 00 00 40179f: 00 4017a0: 48 ff c1 inc %rcx 4017a3: 48 01 c8 add %rcx,%rax 4017a6: 70 07 jo 4017af <_TF8function8sumTotalFSiSi+0x3f> 4017a8: 48 39 cf cmp %rcx,%rdi 4017ab: 75 f3 jne 4017a0 <_TF8function8sumTotalFSiSi+0x30> 4017ad: 5d pop %rbp 4017ae: c3 retq 4017af: 0f 0b ud2 4017b1: 66 66 66 66 66 66 2e data16 data16 data16 data16 data16 nopw %cs:0x0(%rax,%rax,1) 4017b8: 0f 1f 84 00 00 00 00 4017bf: 00
おお、なかなかのコードが掃き出されている。
結論
高速なSwiftバイナリを作りたければ、-Oオプションは必須。 ただし、欠点はコンパイル時間が異常に遅い。
$ time swiftc -O ./function.swift real 0m6.281s user 0m6.240s sys 0m0.024s $ time swiftc ./function.swift real 0m0.485s user 0m0.460s sys 0m0.012s
何だこの遅さ。