書いた人: ささだ
YARV: Yet Another RubyVM を解説するこの連載。連載 4 回目にして、やっと YARV を触ります。
YARV は Ruby プログラムをコンパイラによって YARV 命令列に変換して、その命令列を実行します。今回は、Ruby プログラムがどのような YARV 命令列に変換されるか、簡単なところを紹介します。
今まで YARV は Ruby の拡張ライブラリとして開発してきましたが、先日 Ruby から従来の評価器を取り除き、YARV だけを使うバージョンを作りました (YARV 0.3.0)。今後はこれをさらに拡張し、レガシーなコードを取り除いていく作業をやっていきます。
今回の連載はこいつを使って解説していきたいので、ビルド方法を紹介します。ただし、今現在のビルド方法を解説しますので、将来はこの方法ではビルド出来ないかもしれません。いや、多分出来ないっぽい。
まず、YARV を取ってきます。現在の最新版は YARV 0.3.1 です。また、Subversion リポジトリを公開していますので、Subversion がインストールされている環境では以下のコマンドで最新版を取ってくることができます。
このコマンドで、yarv というディレクトリにソースコード一式がダウンロードされます。
さて、その状態で YARV をビルドしましょう。
ふつーに Ruby のソースコードをビルドする方法と同じですね。Windows で VC な人は ../yarv/win32/configure.bat を configure の代わりに実行してください (make の代わりに nmake にするのも忘れずに)。
では、テストしましょう。
テストがガンガン走り出すはずです。問題がなければ
みたいなメッセージが表示されると思います。そうじゃなかったら、バグですのでご連絡ください。
これで準備は整いました。
では、ちょっと使ってみましょう。yarv/test.rb というファイルに、とてつもなく簡単な Ruby プログラムを書いてみましょう。
その上で、YARV をビルドしたディレクトリ (build/) で、make run と入力してみてください。
実行されました。成功です。miniruby 云々は make コマンドが実行するコマンドラインを表示しているだけですので無視してください。ちなみに、この実行は cygwin 上で行いました (だから、”.exe” が付いている)。
ここで、miniruby を動かしていることに注目してください。ruby コマンドじゃないです。これは、現在の YARV の実装では、ruby コマンドを作るにはちょっと足りない部分があるので、とりあえず miniruby だけ動かしている、という意味です。
さて、もうちょっと頑張りましょう。make run の代わりに、make runp と入力してみてください。
なにやらずらずらと出てきました。ここで表示されるのは、「yarv/test.rb に記述した Ruby プログラム」と、「Ruby プログラムを YARV 命令セットに変換した命令列」、そして「実際にそれを実行した結果」です。
今回は、この「ずらずらと出てきた命令列」についてご紹介します。
では、上記の「ずらずらと出てきた命令列」の例を解説します。
という Ruby プログラムをコンパイルした結果、
このような出力が得られたのでした。
この例の最初の 3 行はいわゆるヘッダで、命令列に関する付加的な情報を表示しています。たとえば、1 行目に <ISeq:<main>@../yarv/test.rb> と書いてあるのは、この命令列が ../yarv/test.rb というファイルに書いてあるトップレベルのプログラム (メソッド内などではない) であることを表しています。
まぁ、そういうのはおいといて、4 行目の表示を見てみましょう。
ここには、命令番地、命令名、命令オペランド、そして行番号が表示されます。ただし、putself 命令の場合、命令オペランドは無いのでこの例では表示されていません。
命令番地は 0 から数えるため、0000 と表示されています。行番号はもちろん 1 行目から数えるので (1) になっています。
さて、putself というのが、もちろん命令名なのですが、何をするんでしょうね。名前から想像すると、self と関係ありそうです。でも、元のプログラムには self なんて単語は一切書かれていません。
そもそも、put ってなんでしょうね。どこに put するんでしょうか。次の命令は putstring ですね。こっちも put。命令オペランドは “Hello World!” という文字列なので、きっとプログラム中に現れた文字列をあらわしていると思うのですが。
putself 命令はどこに何を put するのか、ですが、まずは「どこに put するのか」という疑問に先に答えておきましょう。YARV はスタックマシンなので、そのものずばり、スタックトップに置きます。
putself は、ご想像のとおり、スタックに self を積みます。putstring は文字列を dup してスタックに積みます。
pushself のほうがわかりやすいじゃん、と思われるかもしれませんが、…あまり考えて命名しませんでした。
Ruby のコードで書いてみると、こんな感じです。
というか、まさにこれだけです。
さて、積むだけ積んでみました。では、誰かが pop すると思うんですが、誰が pop するんですかね。
という行が 3 番目に来ています。:puts という命令オペランドがあるので、きっと puts メソッドの起動を表しているんでしょう。よくみると、命令名は send です。Ruby でメソッドを起動するメソッドは Object#send でした。まぁ、そのまんまですね。そういうわけで、これがメソッドを起動する YARV の命令です。
命令オペランドが :puts の後にもまだ続いていますね。1 は、引数を 1 つで起動、という意味です。そのほかは、面倒なんで今は説明しません。
この命令は、引数の数分だけの値と、レシーバをスタック上から取って (pop して)、メソッドを起動する、という意味になります。つまり、先ほど push した値は、ここで利用され、pop されます。
ちょっとわかりづらいですね。Ruby で書いてみましょう。
ということを、send 命令 1 つでやっています (実際にはこんな複雑なことはやっていませんが、Ruby で書くとこんな感じ、という意味です)。
ちょっとおさらいしてみましょう。
という命令列は、
というようになります。
最後に end という命令がありますが、これはこのスコープから抜けろ、という意味です。スコープについては次号で紹介する予定です。まぁ、これで終わり、と思っていただければ間違いじゃないです。積まれているスタックトップを持って前のスコープに戻ります。
ちなみに、self を最初に積んでいるのは、Ruby では self.puts も self (レシーバ) を書かないで puts だけ記述するのも、「ほぼ」同じ意味だからです。厳密にはほんのちょっとだけ違うんですが、このレベルでは同じです。なので、self.puts に合わせている、ということです。
ほかの例を試して見ましょう。
人を馬鹿にしてるのか、というくらい簡単な Ruby プログラムですが、この命令列を見てみると、こんな感じです。
1+2 を実行しているのが 1番地から5番地の
この部分です。1+2 は、Ruby では立派なメソッド呼び出しですから (1.+(2)と同じ意味) 、このようにコンパイルされるのです。このとき、1 がレシーバオブジェクト、2 が引数として、:+ メソッドが呼ばれています。さっきの例と同じですね。
そして、最後に self.p(arg) を実行 (arg は 1+2 の結果) して、end でその返り値 (p の返り値は nil) を返します。
さて、では他にどんな命令があるんでしょうか。
YARV: Yet another RubyVM / Instruction Table というページがあります。ここに、命令一覧をまとめてあります。ただし、このページの内容は最新の YARV に追従していない場合もありますのでご注意ください。
この命令一覧の見方を説明します。
type はどんな命令か、を示しています。Index は、とりあえず命令に番号を付けています。Instruction には命令名、Operands は命令オペランドが示されています。Stacks には、その命令を実行したら、スタックの状態がどのように変化するかを示しています。
たとえば、putnil という命令なら、Stacks には
と、記述されていますが、これは val (なんらかの値) が putnil 命令実行後にスタックに詰まれる、という意味です。val はもちろん nil です。スタックサイズは一個増えました。
たとえば、putnot という命令では、Stacks には
とありますが、これはスタックトップに積んである値を取り出し (pop)、その値を obj として、!obj を val として、val をスタックトップに積みます (push)。pop して push したので、スタックサイズの増減はありません。
Ruby で擬似コードを書いてみるとこんな感じです。
各命令がどんなことをするのか、というのは名前をじーっと見るとわかる気がするんですが、わからないような気もします。
各命令が具体的に何をやっているかを詳しく知りたい人は、YARV ソースツリーの yarv/insns.def を見てください。このファイルに、色々と細かいことが書いてあります。具体的には、日本語、英語での簡単な説明と、C プログラムでその命令が何をするか詳細に記述してあります。これを見れば、わかる人にはわかるんじゃないかと思います。
yarv/insns.def の書式は、普通の C 言語の書式ではないし、そもそも拡張子が “.def” という怪しげなものになっていますが、このファイルは YARV のビルド時に Ruby プログラムによって C 言語プログラムに変換されます。これについては、いつか解説します。
本稿では YARV の命令セットの紹介のさわりを書きました。次号からは、もうちょっと大きなプログラムを YARV 命令セットに変換してみて、その内容を解説していこうと思っています。
YARV のビルドが出来た方は、今回紹介した方法を使っていろいろな Ruby プログラムを YARV 命令列に変換してみてください (いわゆる逆アセンブル)。YARV がどんなことをするのか、少しずつ見えてくると思います。
今回は YARV に特化した内容でしたので、他に参照するところはありません。
YARV 命令の一覧は YARV: Yet another RubyVM / Instruction Table に用意してあります。
各命令の詳細は本文中で紹介したソースコード yarv/insns.def を見てください。
ささだこういち。学生。
最近マシンを新しくしたので、家で YARV の開発ができるぜー、と思っていたら、なんか事務処理とかばかりしている気がする。LLDN の準備とか研究会の準備とか若手の会の準備とか色々。まぁいいか。