2014-01-06

冬休みなのでNaClで遊んでいた


せっかくの休みなので仕事と関係ないことでもすっか、と思い、NativeClientで遊んでいました(微妙に関係するのかなこれ)。Google+で #冬休みの自由研究 というハッシュタグを勝手に作って適当にあれこれ書いていたんだけど、そういえばなぜか限定公開にしてたので誰でも見える状態にはなってませんでしたね……。

なのでブログで簡単に記録を残しておきます。

やったこと:GaucheをNative Clientで動かそうと四苦八苦して挫折。その後 chibi-scheme を動かそうとしたらあっさり達成

いちおうはじめから書きましょう。

NativeClient(通称NaCl)とは何か?

NativeClientは、ブラウザ(Chrome)のなかでネイティブコードを動かすためのモノです。SDKはカスタムメイドなCコンパイラでして、少し特殊なかんじのバイナリを生成します(nexeという拡張子がついています)。Googleが作ってます。

作ったバイナリは、objectやembedなどの要素でページに埋め込み、別プロセスで実行されます(この時にバイナリを検証します。で、NaClのコンパイラで生成したバイナリにはちょっとした制約があるため、セキュリティ的に問題のあるようなコードが動かない、つまり、サンドボックスを突破して利用者側に不利益のかかるような悪意のあるプログラムを配布できないようになっています)。ページのJSからは postMessage / onMessage を使ってやりとりするというかたちになります。

よくasm.jsと比較されることがあるのですが、個人的には性格が異なるかなーと思ってます。NativeClientは(特殊な調整がされているとはいえ)普通のELFバイナリなので、けっこういろんなことができます。pthreadでスレッドもあれこれできたり、環境によるけどdlopenできたり。asm.jsは最終的にはJSの意味論に落ちるので、やるのが難しいことがあるんじゃないかなぁと思うんだけどどうなのかなあ。

GaucheをNaClで動かす

そういうわけでNativeClientではネイティブコードが動くわけですが、実際の用途として想定されているのは、パフォーマンスが大事なエリアだけCで書いて頑張る、といったことかなと思います(そういうサンプルがSDKに添付されてるので)。だけど、こう、折角だからスクリプト言語とか動かすと面白いよね、と思う人はいっぱいいるようで、Ruby、Python、LuaについてはnaclportsというNaCl用のパッケージ集みたいなところに入っています。

で、Lisp系の言語はなかったみたいだしGaucheどうかなと、思ったわけです。うっかり。

目算はありました。というのは、GaucheはガベージコレクションとしてBoehm-GCを使っているわけですが、Boehm-GCはnaclportsに入っているのですね。だからそれほど問題はないのかなと思ったわけですが……。

naclportsのBoehm-GCが全然動かねえ!

という次第でありました。NaClもいちおう普通のELFバイナリではあるとはいえ、それなりにおかしなところもあるので、そう簡単な話ではなかったというわけです。

NaClが動かない場合のデバッグは大変で、いちおうSDKにはgdbが付属しているんですが、年末帰省でChromebookしかない状況だったため、gdbつきでNaClバイナリを動かすことが出来ません。デベロッパーツールを見るとクラッシュしたことだけがわかる(スタックトレースも何もわからない)という状態。

ただ、後述する方法によってstderrに書きだしたものはIPCを経由してconsole.errorとして吐き出されるようにできるということはわかったのが救いで、これを使ってprintfデバッグという超原始的な手法に着手しました。とはいえfflushしてもフラッシュしてくれないので、クラッシュするタイミングとIPCの調子によっては実行してるのにログが吐かれないということもあったりするため、何度もクラッシュさせてみて「ここまで出ることはあるからここで落ちているんだろう」と推測して、とかしていました。

そういう謎の苦労のすえにわかったのは、GCの初期化時の問題だということでした。Boehm-GCはマーク&スイープのルートオブジェクトの集合として、スタック上の変数とグローバル変数をとっているわけですが、どうやら Boehm-GC がグローバル変数の領域と思っているエリアがおかしい気がする。といったところに当たりをつけたぐらいで神のような方が教えを授けてくれたわけですが、まぁ要約するとこれは無理っぽいなと。

その辺は今後解決されることを期待しつつ #define GC_MALLOC malloc 的な手段で(メモリリークを気にせず)やろうかなとも思ったんですが、Gaucheはatomic_opsにも依存しているしGC_baseもあるし、ちょっとだけ手を入れる必要があるみたいだし、といったあたりで気力を失ってしまいました。

まあ、ふだんあまり触らないような低レイヤーの領域の知識がちょっとだけ増えたのでよかったかなと思います。

chibi-schemeをNaClで動かす

で、気分を変えてchibi-schemeを動かしてみることにしたわけです。chibi-schemeを選んだ理由は、scheme処理系のなかでは実装がコンパクトっぽい(と聞いたことがある)のと、自前のシンプルなgcを持っているので↑のようなしんどい目にはあわなくて済みそう、という理由です。

で、こっちは動きました。あまりにも簡単に動いたので逆に拍子抜けしたぐらいです。Gaucheは(というかGCは)なんだかんだで一週間弱ほど苦闘していたわけですが、chibi-schemeにはほとんど罠がありませんでした。

chibi-schemeはpnaclでも普通にビルドができたので、動作環境をふつうにウェブページとして公開しておきます→こちら

pnaclとNaClについて書いておくと、NaClは普通のネイティブコードバイナリを配布するものなのですが、いまだに利用環境に制限があり、通常のウェブページには埋め込めません。 Chromeアプリとして配布されたものでしか使えないのです。pnaclはportable NaClの略で、NaClと違ってネイティブコードでは配布されません。LLVMのビットコードを使ったpexeというファイルで配布され(よく知らないのであやふやな表現)、ブラウザのネイティブ環境に変換されて動きます。こちらはなぜか制限がなく、普通のブラウザでも――ってもちろんNaClに対応しているブラウザなのでChrome限定ですが――見ることができるようになっています。

ppapi_simpleとnacl_ioについて

さて、NaClで苦戦した「遊び」でしたが、多少はほかのことも習得できました。ここではppapi_simpleとnacl_ioを紹介したいと思います。

そもそもNaClのコードは、ちょっと特殊な構造になっています。はじめにこういう名前の関数が呼ばれる、こういうクラスのサブクラスを作っておくと、JSからpostMessageが届くとこのメソッドが呼ばれて、そのときの値はこれこれで、などなど。

ppapi_simpleはその辺のことを面倒見てくれる便利ライブラリで、NaClのSDKに添付されています。普通のCのint main(int argc, char* argv[])のような関数を定義し、PPAPI_SIMPLE_REGISTER_MAIN()というマクロでその関数を登録しておくと、「初期化する」とかいったこまごましたことをひと通りやってくれて、mainのなかでループを書いてメッセージを受け取る、といったことができます。メッセージを標準入出力をとして受け渡しするようなこともでき、REPLの実装などがラクになる感じです。stderrに出力するとconsole.errorになってくれるというのもppapi_simpleの機能だったような気がします。

nacl_ioはファイルIOのためのラッパーです。スクリプト言語を動かす場合、初期化するにしてもライブラリを読むにしても、どこかにあるファイルを読む必要があります。ところがこれが厄介でして、HTML5のfilesystem APIはNaCl用の独自のAPIを呼ばなければなりませんし、NaClバイナリと同じディレクトリにファイルでも置いておくかと思えばHTTPでフェッチしないといけないし、かなーりめんどうなわけです。

nacl_ioはディレクトリをマウントすることでその辺をラップしてくれる便利ライブラリです。"httpfs"という擬似ファイルシステムが指定できて、こいつを指定するとファイルをopenするだけでフェッチしてどうこうみたいなことをまるっとやってくれています。上で指定したchibi-schemeでもこれを使っているので、様々なライブラリをロードできますが、その都度ロードしているのでimportするとネットワークを感じます。

naclportsに入っているLuaやPythonなどでは、必要なライブラリファイルなどをまとめてひとつの .tar ファイルにしておき、これをhttpfsで開いてlibtarを使って(やはりnacl_ioでmemfsとしてマウントされている)ディレクトリに展開する、といったことをやっているようです。都度ロードはやっぱり遅いですからね。

そういうわけで、「NaClで遊ぶ」の一部始終でした。