Ruby.NET コンパイラの開発

更新 2005/2/28

Ruby という言葉は既存の ruby インタプリタの仕様もしくは 既存の Ruby の仕様を差します。 ClassName#method_name は ClassName クラスのインスタンスメソッド method_name を指し、 ClassName.method_name は ClassName クラスのクラスメソッド method_name を指します。
以下の説明においては Ruby.NET コンパイラが実際に 作成する IL コードを根本的な動作に変わりがない程度で簡略化して、C# で表現しています。

クラスの表現

.NET の中間言語であるMSIL(以下IL)は Java のバイトコードと同じように オブジェクト思考なアセンブラです。C#のコードとほぼ一対一の対応が可能です。 例えば以下のようなC#コードは

namespace Test
{
  class Class1
  {
    [STAThread]
    static void Main(string[] args)
    {
    }

    void test()
    {
    }
  }
}
以下のようなILコードにコンパイルされます。
.namespace Test
{
  .class private auto ansi beforefieldinit Class1
         extends [mscorlib]System.Object
  {
    .method private hidebysig static void 
            Main(string[] args) cil managed
    {
      .entrypoint
      .custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 ) 
      .maxstack  0
      IL_0000:  nop
      IL_0001:  ret
    }

    .method private hidebysig instance void 
            test() cil managed
    {
      .maxstack  0
      IL_0000:  nop
      IL_0001:  ret
    }
    .method public hidebysig specialname rtspecialname 
            instance void  .ctor() cil managed
    {
      .maxstack  1
      IL_0000:  ldarg.0
      IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
      IL_0006:  ret
    }
  }
}
このようにC#と対応が可能ですので以下の説明ではILで記述する変わりにC#のコードを説明に用います。
Rubyではクラス以下のように表現します。
class Test
  def test0
  end

  def Test.test1
  end
end
Ruby.NETコンパイラではこれを次のようにコンパイルします。
class Test
{
  IObject test0(IObject[] args, IObject proc)
  {
    return Object.nil_;
  }
}
class cTest
{
  IObject test1(IObject[] args, IObject proc)
  {
    return Object.nil_;
  }
}
すべてのメソッドのシグニチャは同じ形をしており、 IObject[] と IObject を引数に取り IObject を返します。
Test と cTest と2つに分かれているのは、以下のようなRubyのコードを実現するためです。
class Test
  def test0
  end

  def Test.test1
  end
end

t = Test.new
t.test0       # Test#test0 が呼ばれる
Test.test1    # Test.test1 が呼ばれる

t = Test
t.test1       # Test.test1 が呼ばれる
このコードは以下のようにコンパイルされます。
class Test {}
class cTest {}

IObject t = new Test();
IObject Test = new cTest();

t.test0(EmptyArgs, null);
Test.test1(EmptyArgs, null);

t = Test
t.Test1(EmptyArgs, null);
Rubyではクラスを変数に代入することが可能です。 よって Ruby.NET コンパイラでは Ruby のクラスが2つのクラスにコンパイルされます。 上の例ではC#でいうインスタンスメソッドはTestに、C#でいうスタティックメソッド(クラスメソッド)は cTest に定義され、Test という名前の変数に cTest のインスタンスが代入されます。 cTest のインスタンスはプログラム中にただ1つしか存在しません。

クラス再定義

class Test
  def test0
    return true
  end
end

class Test
  def test0
    return false
  end
end

class Test
  def test1
    return true
  end
end
以上のコードは Ruby.NET コンパイラでは次のようにコンパイルされます。
class Test
{
  IObject test0(IObject[] args, IObject proc)
  {
    return Object.false_;
  }

  IObject test1(IObject[] args, IObject proc)
  {
    return Object.true_;
  }
}

class cTest
{
}
Ruby ではクラスを途中で再定義することが可能ですが、 Ruby.NET コンパイラではクラスの最終状態をそのクラスの定義としています。
class Test
  def test0
    true
  end
end

t = Test.new
puts t.test0      # => true

class Test
  def test0
    false
  end
end

t = Test.new
puts t.test0      # => false
Ruby では以上のコードでは test0 の結果がクラスの再定義により変わりますが、 Ruby.NET コンパイラではどちらも false を表示します。 コンパイラの複雑さの軽減とプログラムの高速化のためにこのような実装にしました。 今後 Ruby と同じ結果になるように変更するかもしれません。

Ruby と同様に Ruby.NET コンパイラでもライブラリで定義されているクラスを再定義した場合に動作が変わります。
class Array
  def size
    10
  end
end

a = Array.new
puts a.size      # => 10
Ruby.NET コンパイラではライブラリで定義されているすべてのクラスにラッパクラスが作られます。 上のコードでは Array のラッパクラスにおいて size メソッドが再定義されることになります。
class Fixnum
{
  IObject to_s(IObject[] args, IObject proc)
  {
    return new String( value );
  }
}
ライブラリで定義されているクラス Fixnum に to_s というメソッドがあるとして、 このメソッドは同じくライブラリで定義されている String クラスのインスタンスを返すとします。 Ruby のプログラムで次のように String クラスを再定義します。
class String
  def length
    10
  end
end

t = 1       # IObject t = new Fxinum(1);
s = t.to_s  # IObject s = t.to_s(EmptyArgs, null);
このとき、ライブラリで定義されている Fixnum#to_s は再定義された新しい String クラスのインスタンスを返さなければなりません。
これを実現するために、 ライブラリで定義されるすべてのクラスはそのインスタンスを生成するファクトリーメソッド により生成されます。
class Fixnum
{
  IObject to_s(IObject[] args, IObject proc)
  {
    return main_.StringFactory( value );
  }
}
StringFactory は仮想関数として定義されます。 新しい String を返す StringFactory が定義されたクラスがコンパイラにより作られ、 スタティック変数 main_ にそのインスタンスが代入されています。 このため、常に main_.StringFactory() の呼び出しにより新しい String のインスタンスが返されます。

メソッドディスパッチの実現

Ruby において呼び出されるメソッドは実行時に決定されます。 例えば以下のようなコードにおいて、実行時に cond の真偽によって t には Test1 のインスタンスか Test2 のインスタンスが代入されます。このため t.test の呼び出しで Test1#test が呼び出されるか Test2#test が呼び出されるかコンパイル時には判断できません。

class Test1
  def test
    true
  end
end

class Test2
  def test
    false
  end
end

if cond
  t = Test1.new
else
  t = Test2.new
end

t.test  # => Test1#test or Test2#test
メソッドディスパッチの実現するために、Ruby.NET コンパイラでは、 仮想関数 ___send を用いて実行時に正しい関数を呼び出します。
interface IObject
{
  IObject ___send( string name, Object[] args ) { }
}

class C : IObject
{
  virtual IObject ___send( string name, Object[] args )
  {
    if( name == “hoge” )
      return  this.hoge (args);
  }
  
  virtual IObject hoge(Object[] args) {}
}

Object t = null;
if( cond )
    t = new C();
else
    t = new D();

t.___send (“hoge”, null );
このように、すべてのクラスは interface IObject の仮想関数 ___send を実装し、 ___send に呼び出すメソッド名と引数を渡すことで目的のメソッドを呼び出します。 ___send は仮想関数ですので、常に現在のオブジェクトの ___send が呼ばれ、 各オブジェクトの ___send メソッドは自分のオブジェクトの全メソッドを把握していますので、 ___send メソッドはどのメソッドを呼ぶべきか判断できます。
実際の Ruby.NET コンパイラでは、高速に呼び出すメソッドを決めるために、 メソッドIDと2分検索を用いています。 各メソッドには名前に対してユニークなメソッドIDがコンパイル時に割り振られます。 違うクラスに定義されているメソッドであっても同じ名前であれば同じIDが振られます。 ___send ではメソッド名の比較ではなくメソッドIDの比較により呼び出すメソッドを決定します。 メソッドIDの比較を最小限にするために二分検索を使っています。
interface IObject
{
  IObject ___send(int mid, string methodName, IObject[] args, IObject proc);
}

class Object : IObject
{
  virtual IObject ___send(int mid, string methodName, IObject[] args, IObject proc)
  {
    if( has_singleton_method? ) {
      Method method = getSingletonMethod(methodName);
        if( method != null )
          return method.call(args, proc);
     }
     if( mid > 50 ) {

       if( mid > 75 ) {
          if( mid == 80 )
             return this.hoge(args, proc);
          if( mid == 90 )
             return this.test1(args, proc);
       }
       ....
     }
     .......
  }
}
この実装は、ハッシュテーブルを使うのに比べ非常に高速に動作します。 has_singleton_method? は 特異メソッドの有無を調べています。

特異メソッドの実現

Ruby にはインスタンス固有のメソッドである特異メソッドという物があります。

class Test
  def test0
    1
  end
end

t = Test.new

t.test0  # => 1

def t.test1
  2
end

t.test1 # => 2

def t.test0
  3
end

t.test0 # => 3
この機能を実現するため、Ruby.NETコンパイラでは次のようなRubyコードに対して以下のようなコードを生成します。
class Test
end

t = Test.new

def t.test
   puts "hello"
end
class Test
{
}

class Singleton_test : Method
{
  public Singleton_test(IObject self)
  {
    self_ = self;
  }

  override IObject call( IObject[] args, IObject proc )
  {
    self_.puts( "hello" );
  }
}

IObject t = new Test();

t.addSingletonMethod( "test", new Singleton_test(this) );
特異メソッドは関数オブジェクトとして扱われ、 各インスタンスの特異メソッドテーブルに追加されます。 特異メソッドテーブルはハッシュテーブルを用いて実装されています。
先の ___send では特異メソッドが1つでも定義されている場合は、 まず特異メソッドテーブルを調べ、呼び出そうとしているメソッドが特異メソッドとして 定義されていないかを調べ、特異メソッドテーブルに見つかれば、そのメソッドオブジェクトに対して call メソッドを呼び出し、見つからなければインスタンスメソッドの中を2分検索します。 メソッドオブジェクトのコンストラクタに this ポインタを渡しているのは、 特異メソッド定義内におけるコンテキストを維持するためです。

ブロック / クロージャの実現

Ruby にはブロック付きメソッド呼び出しという機能があります。

def test()
  yield
end

test() {
   puts "hello"
}
{ puts "hello" } の部分がメソッド test に渡され、test 内で yield を呼び出すことにより、 test は渡されたブロックを実行します。
Ruby.NET コンパイラではこのブロック部分がオブジェクト化されてメソッドに渡されます。
virtual IObject test(IObject[] args, IObject proc)
{
   proc.yield( Object.EmptyArgs, null);
}

class Proc_001 : Proc
{
  override IObject yield(IObject[] args, IObject proc)
  {
    self_.puts( "hello" );
  }
}

test( EmptyArgs, new Proc_001(this) );
すべてのメソッドは IObject methodName(IObject[] args, IObject proc) という形をしており、 この二番目の引数 proc としてブロックがオブジェクト化され渡されます。
特異メソッドの時と同様にブロック内のコンテキストはブロックの外のコンテキストと同じですので、 this ポインタをコンストラクタに渡しています。
次のRubyコードを見てください。
class Test
  def save &proc
    @proc = proc
  end

  def call
    @proc.yeild
  end
end

a = 1
t = Test.new
t.save {
  puts a
}
a = 2

t.call # => 2
このプログラムでメソッド save に渡されるブロック内でロール変数 a を参照しています。 ここでこのローカル変数はブロックの外で a = 1 として定義される a と同じ物を参照します。 save にブロックを渡したとで a = 2 とこのローカル変数の値を変更しています。 この後 t.call と先ほど save に渡されたブロックを実行すると、puts a が実行され、 2 が表示されます。
このようにブロックの内側では、ブロックの外側の変数が見え、参照できます。 また、ブロックを保存してから、実行するまでに、ブロックの外側の環境が変化した場合、 ブロック内からもそれを関知できないといけません。
先ほどのブロックをオブジェクト化する手法の場合、コンパイル時にブロック部分をオブジェクト化 するため、ブロックの外側のローカル変数の変更を関知できません。
そこで Ruby.NET コンパイラでは、ブロックの外側を参照するすべてのローカル変数をコンパイル時に調べ、 それをヒープに割り付けることで Ruby と同じ動作を実現します。
a = 1
t = Test.new
t.save {
  puts a
}
a = 2
これは以下のようにコンパイルされます。
IObject[] locals = new IObject[1];

a[0] = new Fixnum(1);

t.save( EmptyArgs, new Proc_001(this, locals) );

a[0] = new Fixnum(2);
ブロックの中で参照されているローカル変数だけがヒープに割り付けられ、その他のローカル変数は、 通常通りローカル変数としてスタックに割り付けられます。
a0 = 1
a1 = 2
1.times {
  b0 = 3
  b1 = 4
  1.times {
    a1 = 5
    b1 = 6
    c0 = 7
  }
}
上のようにブロックがネストしている場合も同様に、それより内側のブロックで参照されている a1、b1 はヒープに割り付けられ、その他はスタックに割り付けられます。 このとき一番内側のブロックのコンストラクタは Proc_002(this, locals0, locals1) となります。 locals0 は aX のレベルのローカル変数が割り付けられるヒープ、locals1 は bX のレベルのローカル変数が割り付けられるヒープへのポインタです。

組み込み関数とトップレベルのメソッド

Ruby には C# でいうスタティック関数(クラス関数)は存在しません。 一見組み込み関数はスタティック関数のように見えますが、 実際には Object (がincludeしているKernel)に定義されているインスタンスメソッドです。

class Test
  def test
    puts "hello"
  end
end
上のコードの Test#test の中で呼ばれている puts は組み込み関数と呼ばれるメソッドですが、 これは Test クラスが暗黙に継承している Object クラスに定義されているインスタンスメソッドです。
def puts( arg )
  print "puts start\n"
  print arg + "\n"
  pritn "puts end\n"
end

class Test
  def test
    puts "hello"
  end
end
上のコードでは、組み込み関数 puts が再定義されています。 ところで、Test#test の中で呼ばれている puts は Test クラスのベースクラスである Object に定義されているインスタンスメソッドでした。 上のコードで、Test#test 内の puts の呼び出しでは再定義された puts が呼び出されます。
トップレベルのメソッドはすべて Object クラスに定義されるという事をふまえて、 先ほどの Ruby コードに対して、Ruby.NET では次のようなコードを生成します。
class Object : RubyLibrary.Object
{
  override IObject puts(IObject[] args, IObjec proc)
  {
    print(...);
    print(...);
    print(...);
    return Object.nil_;
  }
}

class Test : Object
{
  virtual IObject test(IObject[] args, IObject proc)
  {
     puts( new IObject[] {"hello"}, null );
     return Object.nil_;
  }
}
クラスの再定義の項目で説明したように、ライブラリで定義されているすべてのクラスに対して、 Ruby.NET コンパイラはラッパクラスを作成します。 上のコードで、class Object : RubyLibrary.Object は ライブラリの Object クラスに対するラッパクラスです。 この中で、Object のメソッド puts を再定義しています。 Test クラスでは、このラッパクラス Object を継承しているので、 Test#test 内の puts の呼び出しでは、再定義された puts を呼び出すことになります。
ところで、以下の場合はどうでしょうか。
def puts( arg )
  print "puts start\n"
  print arg + "\n"
  pritn "puts end\n"
end

class Array
  def new_method
    puts "hello"
  end
end
Array#new_method 内の puts は先ほどと同様に Object クラスで定義されている puts を呼び出します。 Ruby.NET コンパイラは Array クラスをライブラリで定義されている Array クラスのラッパクラスとして以下のように生成します。
class Array : RubyLibrary.Array
{
  virtual IObject new_method(IObject[] args, IObject proc)
  {
     puts( new IObject[] { "hello" }, null );
     return Object.nil_;
  }
}
ここで puts は Array のベースクラス RubyLibary.Array より親に定義されたメソッドということになります。 ライブラリの中では以下のような継承関係があります。
namespace RubyLibrary
{
  interface IObject {}
  class Object : IObject {}
  class Array : Object {}
}
結局 Array#new_method 内の puts 呼び出しは、Array のベースクラス RubyLibary.Array のベースクラス RubyLibary.Object で定義されているインスタンスメソッドが呼び出されることになります。 しかし、元の Ruby コードで puts が再定義されているので、Array#new_method 内の puts 呼び出しは、 ライブラリで定義された puts ではなく再定義された puts が呼ばれなくてはなりません。 Test クラスの例では、Test クラスが RubyLibrary.Object のラッパクラス Object を継承することで 再定義された puts を呼び出すことが可能でしたが、Array の場合は ラッパクラス Object を継承していません。
Ruby.NET では 再定義された puts が呼び出されるように、以下のようなコードが生成されます。
class Array : RubyLibrary.Array
{
  override IObject puts(IObject[] args, IObjec proc)
  {
    print(...);
    print(...);
    print(...);
    return Object.nil_;
  }

  virtual IObject new_method(IObject[] args, IObject proc)
  {
     puts( new IObject[] { "hello" }, null );
     return Object.nil_;
  }
}
つまり、puts の実装コードが、ラッパクラス Object だけでなく、ラッパクラス Array にもコピーされます。 このようにトップレベルで定義されたメソッド(再定義された組み込み関数を含む)は、 ライブラリで定義されているすべてのクラスのラッパクラス内にコピーされます。

.NET クラスライブラリとの連携

Ruby.NET コンパイラでは Ruby から .NET のクラスのインスタンスを作成したり、そのメソッドを呼び出すことが可能です。 以下のように自然な形で .NET クラスライブラリのクラスを使用できます。

require 'System'

Console = System.Console.type        # static メソッド呼び出しのために type オブジェクトを作成
Console.WriteLine(“Hello, world”) # static メソッドの呼び出し 


a = System.Collections.ArrayList.new # インスタンスの作成
a.Add( “Ruby.NET” )                # インスタンスメソッドの呼び出し
a.Add( “C#” )

puts a.Count # プロパティの参照
このとき、Ruby の String は .NET の System.String に、Ruby の Fixnum は .NET の System.Int32 に、 Ruby の Float は .NET の System.Double に、Ruby の true 及び false は .NET の true 及び false に、 Ruby の nil は .NET の null に相互に自動変換されます。
この機能は、.NET Framework クラスライブラリに限らず、 すべての .NET 言語で作成されれたライブラリにたいしても有効です。 つまり、VB.NET で作成したライブラリも Ruby から使用できます。
.NET クラスライブラリのクラスは .NET のリフレクション機能を利用した実行時バインディングを用いて、 インスタンスの作成やメソッドの呼び出しが行われます。 このため、通常のメソッド呼び出し速度に比べ低速です。
System.Console.type は DotNet.type("System.Console") と同じです。 また、System.String.new は DotNet.new("System.String") と同じです。 DotNet.new の引数にはクラスパスを渡します。 現在の実装では mscorlib.dll にないクラスを呼び出すとき、完全名で指定する必要があります。 System.Console.type は Type オブジェクトを作成します。 スタティック関数を呼び出す場合その関数が定義されているクラスの Type オブジェクトを作成し、 それに対してメソッド呼び出しを行う必要があります。
イベントは、Event に対してブロックを渡すことでイベントハンドラを追加します。 Event名が Click の場合 add_Click {} とします。このとき、add_Click { |sender,event_args| }とすると sender にそのイベントの発生元(System.Object)が、event_args にイベントが返すデータ(System.EventArgs)が渡されます。 プロパティやEventに対して、foo.Bar += hoge; という呼び出しは、.NETにおいて += が再定義されている場合は Ruby.NET では foo.add_Bar(hoge) となります。再定義されていないのであれば foo.Bar += hoge; は Ruby.NET では foo.Bar = foo.Bar + hoge; と同じ意味になります。
type = ", System.Windows.Forms, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"
Point = "System.Drawing.Point, System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
Font = "System.Drawing.Font, System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
Label = "System.Windows.Forms.Label" + type
Form = "System.Windows.Forms.Form" + type
Button = "System.Windows.Forms.Button" + type

form1 = DotNet.new(Form)

label1 = DotNet.new(Label)
label1.Text = ""
label1.Location = DotNet.new(Point, 40, 30);

font = DotNet.new(Font, "Arial", 22)
label1.Font = font
label1.AutoSize = true

button1 = DotNet.new(Button)
button1.Text = "OK"
button1.Location = DotNet.new(Point, 90, 90);
button1.add_Click { label1.Text = "Hello, Ruby.NET" }
 
form1.Text = "未踏ユースデモ"

fsp = DotNet.type("System.Windows.Forms.FormStartPosition" + type )
cs  = fsp.get_field("CenterScreen")

form1.AcceptButton = button1
form1.StartPosition = cs
form1.Controls.Add(label1)
form1.Controls.Add(button1)
form1.ShowDialog()

既存の Ruby の仕様との違い

クラスの再定義で述べた点の他に、既存の Ruby と Ruby.NET では異なる点があります。

基本型

"あいうえお"
'あい\nうえお'
%q(あい\nうえお)
%Q(あいう\nえお)
a = [ 1, 2, [3, 4]]
h = { "one" => 1, "two" => 2 }
1...20
1..19
:symbol
/pattern/im
%r{pattern}

変数

以下の変数が使用できます。

組み込み変数以外の変数スコープ、参照ルールは Ruby と同一です。
組み込み変数 「$」+ 「記号」のスコープが Ruby と異なります。 $~ は Ruby ではグローバル変数の形をしていますが、ローカル変数です。Ruby.NET では実際にグローバル変数です。 例外オブジェクトが格納される $! は Ruby.NET のおいてもローカル変数です。 組み込み変数の多くはまだ実装されていません。グローバル定数や標準オブジェクトも未実装です。

代入

Ruby と完全に同じです。多重代入も可能です。

a, (b, c) = 1, [2, 3]

予約語

Ruby の予約語の他に Ruby.NET では include と require も予約語です。

演算式

defined? が未実装であること以外、優先順位や評価順等 Ruby と同じです。

a[0] = 1
2 ** 4
~123
123 << 2
1 <=> 3
1..4
1 > 3 ? true : false
a *= 4

if a == 3
  ...
else
  ...
end

a = 4 unless b != 4

while i < 100
  ...
  break if c == 11
end

for i in [1,2,3,4]  # スコープルールも同じです
  puts i
end

r = case e
      when 0..3
        "ok"
      else
        "no"
    end

until e != 3
  ...
  if d <= 4
    redo
  elsif d > 6
    next # redo は未実装
  end
end

クラス

次のようなコードの時、Rubyでは Class3 のベースクラスの指定に、クラスを表す物なら なんでも指定できますが、Ruby.NET では、コンパイル時にそのクラスが完全に特定できる形、 つまり :: で区切られれるクラスのパスしか指定できません。 Ruby.NET では、Ruby のクラスを .NET のクラスとして表現しているため、 コンパイル時にベースクラスが特定できる必要があるためです。

class Class1
  class Class2
  end
end

class Class3 < Class1::Class2
end
また次のようなコードは Ruby.NET で有効ですが、 Class3 を定義するスコープがコンパイル時に判断できず、 Class3 はトップレベルに定義されます。 従って、Class3 を継承する場合には Class1::Class3 とは書けず、 Class3 もしくは ::Class3 と記述する必要があります。
class Class1
  class Class2
  end
end

t = Class1
class t::Class3 < Class1::Class2
end
include ににおいても、include するModuleがコンパイル時に完全に特定できる形、 つまり :: 区切りのパスを指定する必要があります。 include は Module のソースをその位置に貼り付けるような形で実装されています。 また、requrie に指定できるものも、 コンパイル時に決定できる名前である必要があります。 require もその位置にファイルを貼り付けます。 include や require はどのコントロールパスを通ろうと、コンパイル時に必ず実行されます。

その他

Ruby.NET コンパイラでは eval 等、実行時にパースが必要がである機能は使用できません。 このほかにも、実行時にコンテキストをオブジェクト化するなどの動的な機能が使用できません。 クラスの再定義についての制限は module の使用においても同様に制限があります。 例外をキャッチする begin ... end は値を返せません。 例外をキャッチしなければ(raise のみ)Ruby と同様です。

未実装な部分

多倍長演算ができません。 メソッドのアクセス制御 private, public 等が指定できません。 alias, undef が使えません。 ヒアドキュメントが使えません。 標準ライブラリが半分ほどしか実装されていません。 これらは、いずれ実装する予定です。
retry が使用できません。これを実現するのは難しそうです。

高速化の工夫

Ruby.NET コンパイラの開発では、コンパイラの中心部分の開発に時間を割いたので、 高速化には力を入れていませんが、Ruby と同等以上の速度を達成するためにいくつかの最適化を行いました。

Fixnum の最適化 1

Ruby.NET コンパイラでは、Fixnum はオブジェクト型のため、 整数演算の度にメソッド呼び出しのコストが発生します。

t = 1 + 1
例えば上のようなコードに対して、Ruby.NET コンパイラは下のようなコードを生成します。
IObject t = (new Fixnum(1)).___send( 123, "+", new IObject[] { new Fixnum(1) }, null );
メソッドテーブルを検索するコストや、___send メソッドを呼び出すコストをさけるために、 特異メソッドとして演算子が再定義されていない場合、以下のようなコードを生成します。
IObject t = (new Fixnum(1)).___op_Add(new IObject[] { new Fixnum(1) }, null );
ここで ___op_Add は interface IObject に宣言されている仮想関数です。 すべてのオブジェクトはメソッド ___op_Add を持つので、___send を介さず直接 ___op_Add を呼び出すことが可能です。 他の演算子も同様に interface IObject に仮想関数として宣言されています。
この場合でも、足し算の度に、仮想関数呼び出しのコストがかかります。 そこで Ruby.NET コンパイラではコンパイルオプションに --fixnum-inline を指定することで、 Fixnum に演算子の再定義がされておらず、かつ演算子の特異メソッド定義が存在しない場合に、 以下のコードを生成します。
IObject t0 = new Fixnum(1);
IObject t1 = new Fixnum(2);
IObject t3;
if( t0 is Fixnum && t1 is Fixnum )
   t3 = new Fixnum( t0.value_ + t1.value_ );
else
   t3 = t0.___op_Add( new IObject[] { t1 }, null );
if( t0 is Fixnum && t1 is Fixnum ) のようなチェックはすべての2項演算に対して行われるので、 実際に t0 や t1 が Fixnum 型でない場合が多いと、それがオーバーヘッドになります。

Fixnum の最適化 2

while i < 1000
  i = i + 1
end
というプログラムでは、1000という整数オブジェクトが比較の度に生成され、 1という整数オブジェクトが繰り返しの度に作られ、 i+1の結果の整数オブジェクトが足し算の度に作られます。 これにより上記プログラムでは、new を 3000 回行うことになり、 これにかなりの時間がかかっています。
そこでなるべくオブジェクトを生成しないように、 256個の整数オブジェクトをキャッシュしておくことにしました。 Finxum オブジェクトを作成するとき、GetFixnum(int i)を呼び出します。 すでに同じ値の整数オブジェクトが作成されていたらそれを返し、 作成されていなかったもしくは、キャッシュになかったら新たに作成します。
public static Fixnum GetFixnum(int val)
{
  if( val == -1 || val == 0 || val == 1 )
    return (Fixnum)fixnumPool_[val+1];

  int pos = val % 253  + 3;
  if( pos < 0 ) pos = -pos;
  Fixnum f = (Fixnum)fixnumPool_[pos];
  if( f != null && f.value_ == val )
    return (Fixnum)fixnumPool_[pos];
  f = main_.___fixnumFactory(val);
  fixnumPool_[pos] = f;
  return f;
}
先ほどの Ruby コードで以下のような最適化を行わなかったのは
tmp1 = new Finxum(1000);
tmp2 = new Finxum(1);
while i < tmp1
   i = i + tmp2
end
先ほどのGetFixnumの場合、以下のようなコードでもオブジェクトの生成回数を減らせるためです。
def get_one
  return 1
end

while i < 1000
  i = i + get_one
end

引数の配列の作成

method 1,2,3
上のコードから Ruby.NET コンパイラでは以下ようなコードを生成します。
this.___send( mid, "method", new IObject[] { new Fixnum(1), new Fixnum(2), new Fixnum(3) }, null );
つまりメソッド呼び出しの度に要素数3の配列を作成しています。 これは無駄なので、要素数が0から10間での配列をあらかじめ作成しておき、 引数の数に応じてその配列を使用するようにしました。引数の数が11より多い場合はあらたに作成します。

パフォーマンス

                                     ruby   Ruby.NET    倍率
空のメソッド呼び出し                 0.68   0.016       x42
ブロック付きメソッド呼び出し+yield   1.54   0.23        x6.7
整数の足し算                         0.59   0.29        x2.0
while                                0.60   0.56(0.14)  x1.1(x4.3)
フィボナッチ数列                     2.72   0.81        x3.3
特異メソッド呼び出し                 0.68   0.23        x2.9
tak                                  20.5   3.36        x6.1
tarai                                17.5   0.031       x650
matrix                               4.93   3.31        x1.5
( ) は --fixnum-inline を有効にした場合

今後

そこそこ形はできていますが、これを完全物に近づけるにはかなりの時間がかかりそうです。