FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

普通のやつらの下を行け(Swift分解編)

別に大したことはできないだが、Swiftのビルド環境を構築した。

msyksphinz.hatenablog.com

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

何だこの遅さ。