ずっとデバッグビルドで開発をしていて、古いデバイスでちょっと時間のかかってしまう処理を何とかしないといけなくなったのだけど、リリースビルドにしたらどれくらい時間がかかるのか試しにビルドしたらEXC_ARM_BREAKPOINTでクラッシュするようになってしまった。
Xcode 6.1を使っていて、クラッシュするのはarmv7のデバイスのみ。しかも最適化レベルをO2以上にしたときだけクラッシュする。
止まるのはif文の後が多く、アセンブリにしたコードを見てみるとブランチ命令の後に”trap”という命令が挿入されていて、どうもそこで止まっているみたい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
LBB0_52: ldr r0, [sp, #288] cmp r0, #10 bls LBB0_55 @ BB#53: trap # <-- ここで止まる. LBB0_54: @ %.lr.ph.i.i.i.preheader trap LBB0_55: tbb [pc, r0] LJTI0_0_0: .data_region jt8 .byte (LBB0_56-LJTI0_0_0)/2 .byte (LBB0_56-LJTI0_0_0)/2 .byte (LBB0_56-LJTI0_0_0)/2 .byte (LBB0_57-LJTI0_0_0)/2 .byte (LBB0_57-LJTI0_0_0)/2 .byte (LBB0_57-LJTI0_0_0)/2 .byte (LBB0_58-LJTI0_0_0)/2 .byte (LBB0_58-LJTI0_0_0)/2 .byte (LBB0_58-LJTI0_0_0)/2 .byte (LBB0_58-LJTI0_0_0)/2 .end_data_region .align 1 LBB0_56: @ %.nonloopexit.loopexit |
trapが挿入されているところを調べてみると、ほとんどがtbb命令の前。tbbって知らなかったのだけど、調べてみたらテーブルブランチとのこと。つまり、条件文をジャンプテーブルで最適化しているようだ。
つまり上のアセンブリのコードは
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
switch (r0) { case 0: case 1: case 2: goto LBB0_56; case 3: case 4: case 5: goto LBB0_57; case 6: case 7: case 8: case 9: goto LBB0_58; default: assert(false); // <-- ここで止まる. } |
ということを意味している。もちろん自分ではこんなコードは書いてなくて、コンパイラが勝手に最適化して勝手にassertを仕込んで、勝手にassertにひっかかっているのである。それに最適化を切れば普通に動いているわけで、まあ十中八九コンパイラのバグだと思うわけです。
だいたい、最適化されたコード(fastest, smallest)のはずなのにassertが挿入されるってどういうことよ。このコンパイラはまだ開発中で自信がないからassertを入れてるということでしょうか?
でもまあ実際にバグがあるわけで、バグでおかしな挙動をしたり別の場所でクラッシュするよりは、最適化の失敗の場所がわかるところでクラッシュしてくれた方がありがたいのではあるんだけどね。
調べてみるとXcode 6.3 betaがあるので、それでもテストをしてみた(わざわざOSをYosemiteにアップデートまでして)。
すると上の箇所ではクラッシュしなかったものの、他の場所でクラッシュしてた。そのケースではtbbは使ってなかったけど、やはりブランチ命令の後にtrapが仕込まれていて、そこで引っかかっている。
1 2 3 4 5 6 7 8 |
0xe2878: ldr r1, [r1, #0x4] 0xe287a: cmp r1, #0x1 0xe287c: ittt ne 0xe287e: movne r1, #0x0 0xe2880: strne r1, [r0] 0xe2882: bxne lr 0xe2884: trap # <-- ここで止まる. 0xe2886: mov r8, r8 |
そしてそのコードからif文を取り除いてみたところ、今度は関数の先頭のところにtrap命令が仕込まれるようになって、やはりクラッシュする。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
.section __TEXT,__text,regular,pure_instructions .private_extern __ZN4test9CrashTest6Crash2Ev .globl __ZN4test9CrashTest6Crash2Ev .align 2 .code 16 @ @_ZN4test9CrashTest6Crash2Ev .thumb_func __ZN4test9CrashTest6Crash2Ev __ZN4test9CrashTest6Crash2Ev: .cfi_startproc @DEBUG_VALUE: Crash2:this <- R0 @DEBUG_VALUE: Iterator:index <- 0 @DEBUG_VALUE: Iterator:index <- 0 @DEBUG_VALUE: GetData:index <- 0 @DEBUG_VALUE: numMines <- 0 trap # <-- ここで止まる. ヒドイ... .cfi_endproc .section __TEXT,__StaticInit,regular,pure_instructions .align 2 .code 16 @ @__cxx_global_var_init1 .thumb_func ___cxx_global_var_init1 ___cxx_global_var_init1: Lfunc_begin1: .loc 18 257 0 @ |
とは言え、Xcode 6.3 betaで少し改善されたようで、クラッシュする箇所が減ったおかげで、問題のあった関数ひとつだけを別ファイルにして、そのファイルだけ最適化レベルをO1にしたら動くようになった。なのでしばらくはXcode 6.3 betaで開発することにしよう。
幸い(?)、開発は順調に遅れていてリリースまでまだしばらくかかるので、とりあえずAppleにバグレポ出して様子見。リリースまでに直ってくれてるといいなあ。