以前iPod touchをJailbreakした際にRubyをインストールしたものの、irbは起動しないし添付ライブラリを読みこんだだけで落ちてしまって、全く使いものにならなかった。その原因が気になっていたので今日はそいつを追っかけてみた。てっきりarm-darwin版のRubyが何かバグを抱えているもんだとばかり思って愚痴ってたのだけども、結論から先に言うとどうもそうではないようだ。で、タイトルのようなことになるのだけども。

とりあえずエラーを吐いてる箇所を探す

エラーを吐いてる、と言うのは正しくない。どのライブラリでも関係なく、requireした時点で[BUG]とだけ言ってAbort trapしてしまうツンツンぶりで、まったくとりあってもらえないというのが現状。数は多くないけど、日本でもarm-darwin版rubyに泣かされてる人は何人かいるみたいだ。

っても、インタープリタのコードもライブラリのコードも読んだことないし、arm-darwinのバイナリを作る開発環境もないしで、インタープリタのバグだったりしたらお手上げだったりするんだけど。まぁ、とりあえず小さそうなところで、URIライブラリを選んでみる。

bash-2.3# ruby -e "require 'uri'"
/opt/iphone/lib/ruby/1.8/uri/generic.rb:1121 : [BUG] terminated node (0x21294c)
ruby 1.8.6 (2007-03-13) [arm-darwin]

Abort trap

ぐ。ここまでは既に何度も経験したので、この程度のツンツン加減じゃへこたれないからね。しかしgeneric.rbの時点で落ちるとはどういうことだ。1121行目に一体何があるのか見てみるべくuri/generic.rbを開いてみる。

1106     def inspect
1107       sprintf("#<%s:%#0x URL:%s>", self.class.to_s, self.object_id, self.to_s)
1108     end
1109 
1110     def coerce(oth)
1111       case oth
1112       when String
1113         oth = URI.parse(oth)
1114       else
1115         super
1116       end
1117 
1118       return oth, self
1119     end
1120   end
1121 end

endやん。さらっと見てみたけど当然のことながらおかしなところは何もない。他のRubyライブラリか、あるいはもっとローレベルな機能に依存してるとこはないかとも思ったけど、どう見てもごく普通のRubyコード。一応、まさかsprintfが?とか裏切りのcase式か?とか一通り自分で書いて試してみたけど、別に問題なく通る。というか、ここModuleやClassの定義の箇所だし、そもそも実行すらされてないはず。何なんだ一体。

行けるとこまで行かせてみる

何箇所かコメントアウトしてみたりしたら、一応generic.rbは読み込めたりするんだけど、今度は他のライブラリで止まるし、ひっかかる箇所を見比べても共通点が見えてこない。仕方ないのでライブラリのソースにdebug printを仕掛けつつ、上から順番に地道に読みすすめていくことにする。

まずはuri.rbからrequireしてるものをgeneric.rb以外全部コメントアウトして、generic.rbの方では__END__で後ろの処理を無効にして実行してみると、その段階では問題なく読み込める。徐々に読み込む範囲を広げていったら、ついに「そこから先は必ずAbort trapする」行を見つけた。

 914     def route_from(oth)
 915       # you can modify `rel', but can not `oth'.
 916       begin
 917         oth, rel = route_from0(oth)
 918       rescue
 919         raise $!.class, $!.message
 920       end
 921       if oth == rel
 922         return rel
 923       end
 924       
 925       rel.set_path(route_from_path(oth.path, self.path))
 926       if rel.path == './' && self.query
 927         # "./?foo" -> "?foo"
 928         rel.set_path('')
 929       end
 930 
 931       return rel
 932     end
 933         
 934     alias - route_from

これの925行目の後、return relより前に何かの式を入れるとそこで必ずAbort trapする。ついに、ついに見つけたぜこの野郎。

…待て。うん、この箇所も全然おかしくない。何の変哲もないif文。試しに色々なif文を書いてみる。全部ダメ。unlessもwhileもbegin-rescueもダメ。putsは…ダメ。じゃあ、1。あ、これは通るのか。1+1は…な、なんだって、これも通らないだと?…なんか、すごく嫌な予感がする。手に負えないバグを見つけるよりもよっぽど嫌な予感が。

さて。どうも926行目に書いてあることが原因じゃないようだ。ふむ。じゃあ、コードの順番を入れかえてみたり、前後のコードを減らしたり増やしたりしてみたらどうだろう。

…あはは。コードを減らすと通るんだ。それも、どの箇所かに関係なく。

vim*1を閉じて、おもむろにtop、と打ちこんでみた。うふふふふふ。そういうこと。そういうことなんだ。

メモリが全然なかった。なんでか知らんがhttpd*2起動してるし。誰だそんなふざけたものをiPodに入れたやつは。俺か。俺だよなぁ。

100MBのメモリでできること

結局、iPod touchがあまりに貧弱なため、インタープリタがrequireしたコードを解析中にメモリを使い切ってしまい、どうしようもなくなって[BUG]と断末魔を残して死んでたんだろう、ということだった。だから、コードをあれこれいじってたときに、条件分岐が複雑なコードやオブジェクトを余計に生成するコードが即座に息の根を止めてたんだな。ちょっと考えればわかりそうなもんで、多分携帯電話とかで開発してる人だったら、そんなリソース馬鹿食いしそうなRubyなんかがモバイル端末でまともに動くわけないじゃん、って言いそうだ。あるいは、いやいや、数年落ちのビンテージ環境でもRuby自体は動くわい、それはiPod touchが貧弱なんじゃい、っていう声も違う方から聞こえてきそう。

でもさ。iPhone/iPod touchが携帯電話とも骨董PCとも違うのは、あれは高機能なGUI環境なんだってことなんだよな。サブセットとは言えdarwinが、いやMacOSXが載ってるわけで、だからこそモバイル端末にあるまじき過剰な期待をしてしまうし、(皮肉にも)それが故に骨董PCでもできるようなことが出来なかったりする。確かにiPod touchのインターフェースレベルの層を排してdarwinだけにすれば、Rubyくらいは平気で動くと思うけど、それって何の意味があるんだろう。かと言ってあんなにスムーズに動くUIがあって、音楽・動画が再生できて、ネットワークにも繋がるのに、ハードの制約でいじれる部分が少ないというのも切ない話。

やっぱりObjective-Cで書いて最適化しまくってなんぼって世界か、あるいはWidget程度の小さなアプリがせいぜいってところなのか。Cocoaは好きなんでそれはそれでいいんだけどさ。折角RubyやPerl*3があるのにまともに使えないのは残念過ぎる。まぁ、最初からAppleが言うようにWebアプリで作ればいいじゃんって話だが*4

*1:最初vimがあることに気付かなくて、しばらくviを使ってた。いや、だって入ってないと思ってたし。会社のサーバだってviしか入ってないんだぜ。

*2:以前ネタで入れたApacheがlauchd経由で自動起動するように設定されてたらしい。使いみちもないし、即座に消した。

*3:もしかしたらPerlならRubyよりまともに動くかもしれないけど、CPANフル活用してバリバリ、というわけにもいかないだろうからどっちみち期待はできない

*4:しかしSafariのJSはそれはそれでなんだかなって代物だけど。未だに。