YARV: hello worldなRubyプログラムをVMの命令列に変換する実験

はじめに

Rubyのtrunkでは最近YARVがマージされました。YARVが入ったrubyで遊んでみたい人も多いはず。そこで、今回はバージョン管理されているRubyの最新版のソースコードをゲットして、おなじみのhello worldなプログラムをYARV仮想マシンの命令列に変換して、それをダンプする実験をしてみます。なお、今回の説明ではリビジョン11585を使います。

YARVコアが入ったrubyのビルド

以下の手順で、YARVコアが入ったrubyをビルドできます。

% svn co http://svn.ruby-lang.org/repos/ruby/trunk ruby-trunk
% cd ruby-trunk
% autoconf
% ./configure && make

configureスクリプトを生成するために、autoconfを予めインストールしておく必要があります。僕の環境では2.59がインストールしてあります。
また、Fedora Core 5でビルドできることを確認しました。残念ながら、Mac OS X 10.4.8では、うまくビルドできませんでした。*1

% autoconf --version
autoconf (GNU Autoconf) 2.59
Written by David J. MacKenzie and Akim Demaille.

Copyright (C) 2003 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

% uname -a
Linux devel 2.6.15-1.2054_FC5smp #1 SMP Tue Mar 14 16:05:46 EST 2006 i686 i686 i386 GNU/Linux
% 

無事、ビルドが成功すると、ruby-trunkディレクトリ以下に「ruby」という実行ファイルが生成されます。
これが、YARVコアが入ったrubyです。

YARV仮想マシンの命令列をダンプする

ruby-trunkディレクトリ以下に、「tool」というディレクトリがあります。このディレクトリの中にparse.rbというプログラムが入っています。parse.rbを使うと、parse.rbに与えられたRubyプログラムを、YARV仮想マシンの命令列に変換して、それをわかりやすくダンプしてくれます。

リビジョン11585のソースコードでは、実はparse.rbが動作しません。ちょっとしたパッチを適用する必要があります。以下がそのパッチです。YARVCoreというクラス名をVMに変更するだけです。

% svn diff
Index: tool/parse.rb
===================================================================
--- tool/parse.rb       (revision 11585)
+++ tool/parse.rb       (working copy)
@@ -6,7 +6,7 @@
 puts $str
 puts '# ' + '-' * 70
 
-$parsed = YARVCore::InstructionSequence.compile_file($file)
+$parsed = VM::InstructionSequence.compile_file($file)
 puts "# disasm result: "
 puts '# ' + '-' * 70
 puts $parsed.disasm

今回は、おなじみのhello worldなプログラムをYARV仮想マシンの命令列に変換してみます。一応、1+2の計算結果も出力するコードも追加しました。

% cat hello.rb
puts "hello, world"
puts 1+2

以下は、parse.rbを使ってhello.rbをYARV仮想マシンの命令列に変換して、その結果をダンプした様子です。
うまくダンプできたみたいですね。

YARV仮想マシンの命令セットについては、YARV Maniacs 【第 4 回】 命令セット (1) YARV 命令セットの初歩の初歩なんかが参考になると思います。

% ./ruby tool/parse.rb hello.rb
# ----------------------------------------------------------------------
# target program: 
# ----------------------------------------------------------------------
puts "hello, world"
puts 1+2
# ----------------------------------------------------------------------
# disasm result: 
# ----------------------------------------------------------------------
== disasm: <ISeq:<main>@hello.rb>=======================================
local scope table (size: 1, argc: 0)

0000 putnil                                                           (   1)
0001 putstring        "hello, world"
0003 send             :puts, 1, nil, 8, <ic>
0009 pop              
0010 putnil                                                           (   2)
0011 putobject        1
0013 putobject        2
0015 opt_plus         
0016 send             :puts, 1, nil, 8, <ic>
0022 leave            
# ----------------------------------------------------------------------

寄り道: tool/parse.rbに対するパッチ作成までの過程

ここで、tool/parse.rbに対するパッチをどのようにして作成したか、その過程をちょっと書いておきます。

まず、tool/parse.rbがそのままでは動作しませんでした。

% ./ruby tool/parse.rb hello.rb
# ----------------------------------------------------------------------
# target program: 
# ----------------------------------------------------------------------
puts "hello, world"
puts 1+2
# ----------------------------------------------------------------------
tool/parse.rb:9:in `<main>': uninitialized constant YARVCore (NameError)

tool/parse.rbのソースコードを見ると、YARV命令列のダンプには、YARVCore::InstructionSequence#disasmを使っているようだったので、それに関連するソースコード(iseq.c)を見ました。iseq.cを見ると、InstructionSequenceクラスはrb_cVMで定義されたクラスの配下の名前空間に位置することがわかりました。

% vim tool/parse.rb
[...]
$parsed = YARVCore::InstructionSequence.compile_file($file)
[...]
puts $parsed.disasm
[...]
% vim iseq.c
[...]
void
Init_ISeq(void)
{
    /* declare YARVCore::InstructionSequence */
    rb_cISeq = rb_define_class_under(rb_cVM, "InstructionSequence", rb_cObject);
    rb_define_alloc_func(rb_cISeq, iseq_alloc);
    rb_define_method(rb_cISeq, "inspect", iseq_inspect, 0);
    rb_define_method(rb_cISeq, "disasm", iseq_disasm, 0);
[...]

そこで、rb_cVMを定義しているyarvcore.cを見てみました。Init_vm()関数を見てみると、rb_cVMには「VM」というクラス名がつけられていることがわかりました。従って、正しい名前空間は「YARVCore::InstructionSequence」ではなく、「VM::InstructionSequence」であることが判明しました。

% vim yarvcore.c
[...]
void
Init_vm(void)
{
    Init_ISeq();

    /* ::VM */
    rb_cVM = rb_define_class("VM", rb_cObject);
[...]

何が間違っていたのかわかったので、tool/parse.rbを上で示したパッチのように修正を加えました。そうすると、tool/parse.rbが
動作するようになりました。

以上がパッチ作成までの流れです。

おわりに

今回、rubyの最新のソースコードの中にあるtool/parse.rbを使って、hello worldRubyプログラムをYARV仮想マシンの命令列に変換し、その結果をダンプする実験を行いました。YARVアーキテクチャを理解するには、RubyプログラムをYARV仮想マシンの命令列に変換した結果をイメージできるようになる必要があると思います。rubySubversionリポジトリにある最新版は、動作が不安定ですが、面白いネタがいっぱいありそうです。これからも、また面白いネタを紹介できたらと思います。

追記#1(2007/01/30 00:10)

matzさんにパッチ取り込んでもらえました!!
本当に感激です。
従って、最新のソースコードでは、このエントリで説明したパッチを適用する必要がなくなりました。

% svn diff -r11585:11586
Index: ChangeLog
===================================================================
--- ChangeLog   (リビジョン 11585)
+++ ChangeLog   (リビジョン 11586)
@@ -1,3 +1,8 @@
+Mon Jan 29 14:14:35 2007  Yukihiro Matsumoto  <matz@ruby-lang.org>
+
+       * tool/parse.rb: replace YARVCore by VM class.
+         http://d.hatena.ne.jp/ysano2005/20070128
+
 Sun Jan 28 08:41:49 2007  Masaki Suketa  <masaki.suketa@nifty.ne.jp>
 
        * ext/win32ole/win32ole.c: refactoring.
Index: version.h
===================================================================
--- version.h   (リビジョン 11585)
+++ version.h   (リビジョン 11586)
@@ -1,7 +1,7 @@
 #define RUBY_VERSION "1.9.0"
-#define RUBY_RELEASE_DATE "2007-01-28"
+#define RUBY_RELEASE_DATE "2007-01-29"
 #define RUBY_VERSION_CODE 190
-#define RUBY_RELEASE_CODE 20070128
+#define RUBY_RELEASE_CODE 20070129
 #define RUBY_PATCHLEVEL 0
 
 #define RUBY_VERSION_MAJOR 1
@@ -9,7 +9,7 @@
 #define RUBY_VERSION_TEENY 0
 #define RUBY_RELEASE_YEAR 2007
 #define RUBY_RELEASE_MONTH 1
-#define RUBY_RELEASE_DAY 28
+#define RUBY_RELEASE_DAY 29
 
 RUBY_EXTERN const char ruby_version[];
 RUBY_EXTERN const char ruby_release_date[];
Index: tool/parse.rb
===================================================================
--- tool/parse.rb       (リビジョン 11585)
+++ tool/parse.rb       (リビジョン 11586)
@@ -6,7 +6,7 @@
 puts $str
 puts '# ' + '-' * 70
 
-$parsed = YARVCore::InstructionSequence.compile_file($file)
+$parsed = VM::InstructionSequence.compile_file($file)
 puts "# disasm result: "
 puts '# ' + '-' * 70
 puts $parsed.disasm

追記#2(2007/02/02 18:55)

r11609からMac OS Xでもrubyをビルドできるようになりました。

matz    2007-02-02 18:32:28 +0900 (Fri, 02 Feb 2007)

 New Revision: 11609

 Modified files:
   trunk/ChangeLog
   trunk/eval.c
   trunk/version.h

 Log:
   * eval.c: remove duplicated global variables rb_cProc and
     rb_cBinding.  [ruby-dev:30242]

パッチをruby-devに送っておいて良かったです。
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-dev/30242

*1:追記#2を参照