ずっとデバッグビルドで開発をしていて、古いデバイスでちょっと時間のかかってしまう処理を何とかしないといけなくなったのだけど、リリースビルドにしたらどれくらい時間がかかるのか試しにビルドしたら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にバグレポ出して様子見。リリースまでに直ってくれてるといいなあ。

