← Blog

IronRuby 4.0:.NET 8で動くRuby 4.0

IronRubyは、.NETのランタイム上で動くRubyです。2007年にMicrosoftがIronPythonと同じDynamic Language Runtime(DLR)の上で開発を始め、2010年にコミュニティへ引き継がれました。最後のリリースは2011年です。対応していたのはRuby 1.9.2で、.NET Framework 4が必要でした。それ以来、プロジェクトはほとんど止まっていました。

私はフォークlargo/ironrubyで、IronRubyをよみがえらせました。LinuxとWindowsの .NET 8 でビルド・実行でき、2011年に手作業で移植された1.9の文法の代わりに、CRubyと同じパーサーである prism でRubyを解析します。RUBY_VERSION は 4.0.0 になりました。

そこで名前も IronRuby 4.0 にしました。これからは、IronRubyのバージョン番号は実装しているRubyのバージョンに合わせます。IronRuby 4.0はRuby 4.0で、Ruby 4.1に対応したら4.1になります。最後の数字はIronRuby自身のリリースの番号です。最初のバイナリ版、4.0.0-preview1を公開しました。

実際の様子

$ ./irb.sh
irb(main):001> RUBY_DESCRIPTION
=> "IronRuby 4.0.0-preview1 (4.0.0) on .NET 8.0.31 [x86_64-linux]"
irb(main):002> def fib(n) = n < 2 ? n : fib(n - 1) + fib(n - 2)
=> :fib
irb(main):003> (1..10).map { fib(_1) }
=> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
irb(main):004> Point = Struct.new(:x, :y)
=> Point
irb(main):005> case Point.new(3, 4)
irb(main):006*   in [Integer => x, Integer => y]
irb(main):007*     Math.hypot(x, y)
irb(main):008*   end
=> 5.0

これはCRuby 4.0に同梱されているものと同じirb 1.16.0とrelineで、シンタックスハイライト、自動インデント、Tab補完、履歴が使えます。CRubyでは io/console はC拡張なので、IronRubyではtermiosを使ってC#で実装しています。

パターンマッチ、キーワード引数、エンドレスメソッド、ぼっち演算子(&.)、it や _1、... による引数の転送など、最近のRubyの構文が使えます。

case config
in {db: {host: String => host, port: Integer => port}} then "#{host}:#{port}"
in {db: {socket:}} then socket
end

def connect(host:, port: 5432, **opts) = Client.new(host, port, **opts)

users&.filter_map { it.name if it.active? }

変わったこと

  IronRuby 1.1(2011年) IronRuby 4.0(2026年)
ランタイム .NET Framework 4 .NET 8(Linux、Windows)
ビルド 古いmsbuild dotnet build
パーサー 手作業で移植した1.9の文法 prism
RUBY_VERSION 1.9.2 4.0.0
標準ライブラリ Ruby 1.9 Ruby 4.0(同梱)
gem RubyGems 1.3.7 RubyGems 4.0とBundler
実行 DLRインタプリタとIL さらに メソッドJITとOSR

フロントエンドとしてのprism

prismはCのライブラリです。IronRubyはP/Invokeで libprism を読み込み、prismが構文木をバイナリにシリアライズしたものを生成されたC#のローダーで読み取り、IronRubyの既存のASTに変換します。コンパイラのそれ以降の部分はそのままです。JRubyもJavaのローダーで同じ仕組みを使っています。152個のC#のノードクラスは、prism自身の config.yml からRubyのスクリプトで生成します。そのため、prismを更新するにはライブラリをビルドし直してジェネレーターを再実行するだけです。

1.9時代のASTに対応するノードがない構文は、ASTがすでに知っている構造に変換します。たとえばパターンマッチは、ローカル変数への束縛を伴う ===、deconstruct、deconstruct_keys のチェックの連続になります。

互換性はどのくらい?

JRubyやTruffleRubyなど、ほかの実装も使っているテストスイートruby/specで測っています。基準はCRuby 4.0.6です。IronRubyとspecの結果が食い違ったら、ruby の動きを確かめて、specではなくIronRubyのほうを直します。

スイート 例の数 Linuxでの失敗
spec/language 2934 0
spec/command_line 175 0
spec/security 34 0
spec/library 6398 0
spec/core 23136 21(うち20が ObjectSpace.each_object)

ObjectSpace.each_object はヒープをたどる必要がありますが、.NETは標準ではそれを提供していません。JRubyと同じように、オプション(-X:ObjectSpace)で有効にできます。

Windowsでも spec/language と spec/security は失敗ゼロです。それ以外は、シンボリックリンク、fork、シグナルといったUnixの機能のために、まだ粗い部分があります。macOS版もありますが、ほとんどテストしていません。

gemとRails

igem install rack や ir -S bundle install で、rubygems.orgから本物のTLSでgemをインストールできます。rake、minitest、rexml、csvなど、CRuby 4.0のbundled gemsはIronRubyに同梱されています。json、psych、openssl、bigdecimal、zlib、date など、CRubyがCで実装しているライブラリはC#で書いています。さらにmsgpack、oj、bcryptのC部分をC#に移植し、nio4rとpumaも動きます。

Rails 8.1のアプリケーションが起動し、HTTPリクエストに応答します。ActiveSupport、ActionPack、ERBテンプレートを使うActionView、ActiveJob、ActiveRecordはすべて読み込めます。完全なRailsアプリに足りないものは二つで、どちらもC拡張です。nokogiriと、データベースアダプターです。sqlite3、pg、mysql2、trilogyはすべてネイティブだからです。

一般に、IronRubyはC拡張を読み込めません。コンパイルに使う ruby.h がないからです。拡張がないときのための純粋なRubyの代替実装を持つgemも多く、IronRubyはその代替実装を使います。

パフォーマンス

二つのコンパイラが、プログラムで実際に使われる型に合わせてコードを特殊化します。どちらも標準で有効です。

50個のベンチマークの幾何平均では、IronRubyはCRuby 4.0.6の約3倍遅いです。純粋な計算では速く、fib、mandelbrot、int_arith、float_arith はCRubyの0.27〜0.74倍の時間で終わります。弱点は例外とFiberです。.NETではバックトレースの作成が重く、FiberひとつひとつがCLRのスレッドだからです。

Rubyから.NETを、.NETからRubyを

これこそIronRubyが作られた目的です。Rubyのコードから.NETの型を直接使えます。

date = System::DateTime.new(2000, 1, 1)
System::Environment.OSVersion.Platform

逆に、.NETのアプリケーションにIronRubyを組み込み、プラグインやスクリプト言語としてRubyのスクリプトを実行することもできます。

irubyc:アプリケーションのパッケージ化

OCRANのメンテナーとして、Rubyをインストールしていない人にもRubyのプログラムを届けられることを大切にしています。JRubyの jrubyc にならって名付けた irubyc は、Rubyのプログラムを.NETのアプリケーションにします。

$ ./irubyc.sh --single-file --self-contained -t out app.rb lib/

Rubyのファイルは、IronRubyや標準ライブラリと一緒に、リソースとしてアセンブリに埋め込まれます。--single-file で実行ファイルひとつになり、--self-contained を付けると.NETのランタイムも同梱されるので、.NETのないマシンでも動きます。--library を使うと、IronRubyを内蔵したDLLひとつを作り、.NETのホストアプリケーションからプラグインとして読み込めます。

irubyc はまだRubyをILにコンパイルしません。埋め込まれたソースは起動時に解析されます。RubyからILを経てNativeAOTの実行ファイルまで作る本当の事前コンパイルのプロトタイプは、Util/aot(英語)にあります。

試してみる

最初のプレビュー版IronRuby 4.0.0-preview1は、Linux x64、Windows x64、AppleシリコンのmacOS向けにダウンロードできます。.NETのランタイムが同梱されているので、ほかに何もインストールする必要はありません。

$ tar xzf ironruby-4.0.0-preview1-linux-x64.tar.gz
$ cd ironruby-4.0.0-preview1-linux-x64
$ ./irb.sh
$ ./ir.sh script.rb
$ ./igem.sh install rack

Windowsではzipを展開して、irb.cmd、ir.cmd、igem.cmd を使います。macOS版は起動しますが、まだほとんどテストしていません。ダウンロードした版でも irubyc は使えますが、アプリケーションをコンパイルするには.NET SDKが必要です。

自分でビルドしたい場合は、README(英語)に手順があります。.NET SDK、Ruby、prism用のCコンパイラが必要です。

IronRuby 4.0はまだプレビュー版です。それでも、今のRubyを今の.NETで動かせますし、ruby/specを回すたびにCRubyとの差は縮まっています。試してみて動かないところがあれば、issueを立てるか、メールでお知らせください。

リンク