IronRuby 4.0: Ruby 4.0 auf .NET 8
IronRuby ist Ruby auf der .NET-Laufzeitumgebung. Microsoft hat es 2007 auf der Dynamic Language Runtime (DLR) gestartet, derselben Grundlage wie IronPython, und 2010 an die Community übergeben. Die letzte Version erschien 2011. Sie sprach Ruby 1.9.2 und brauchte das .NET Framework 4, und seither war es um das Projekt weitgehend still.
In meinem Fork largo/ironruby habe ich es wieder zum Leben erweckt. Es baut und läuft auf .NET 8 unter Linux und Windows und parst Ruby mit prism, demselben Parser, den CRuby verwendet, statt mit der von Hand portierten 1.9-Grammatik von 2011. RUBY_VERSION ist jetzt 4.0.0.
Deshalb heisst es IronRuby 4.0. Ab jetzt folgt die Versionsnummer von IronRuby der Ruby-Version, die es implementiert: IronRuby 4.0 ist Ruby 4.0 und wird zu 4.1, wenn es auf Ruby 4.1 umsteigt. Die letzte Zahl zählt die eigenen Versionen von IronRuby. Die erste Version zum Herunterladen, 4.0.0-preview1, ist jetzt erschienen.
So sieht es aus
$ ./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
Das ist irb 1.16.0 mit reline, in denselben Versionen, die CRuby 4.0 mitliefert, mit Syntax-Hervorhebung, automatischem Einrücken, Tab-Vervollständigung und History. In CRuby ist io/console eine C-Extension, deshalb implementiert IronRuby es in C# über termios.
Moderne Ruby-Syntax funktioniert: Pattern Matching, Keyword-Argumente, Endless Methods, Safe Navigation, it und _1 sowie das Weiterreichen von Argumenten mit ....
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? }
Was sich geändert hat
| IronRuby 1.1 (2011) | IronRuby 4.0 (2026) | |
|---|---|---|
| Laufzeitumgebung | .NET Framework 4 | .NET 8 (Linux, Windows) |
| Build | altes msbuild | dotnet build |
| Parser | von Hand portierte 1.9-Grammatik | prism |
RUBY_VERSION |
1.9.2 | 4.0.0 |
| Standardbibliothek | Ruby 1.9 | Ruby 4.0, mitgeliefert |
| Gems | RubyGems 1.3.7 | RubyGems 4.0 und Bundler |
| Ausführung | DLR-Interpreter und IL | zusätzlich Methoden-JIT und OSR |
prism als Frontend
prism ist eine C-Bibliothek. IronRuby lädt libprism über P/Invoke, liest prisms binäre Serialisierung des Syntaxbaums mit einem generierten C#-Loader und bildet sie auf den bestehenden AST von IronRuby ab. Der Rest der Compiler-Pipeline bleibt, wie er ist. JRuby verwendet für seinen Java-Loader dieselbe Architektur. Ein Ruby-Skript erzeugt die 152 C#-Knotenklassen aus prisms eigener config.yml. Ein Update von prism bedeutet also: Bibliothek neu bauen und den Generator nochmals laufen lassen.
Syntax, für die der AST aus der 1.9-Zeit keinen Knoten hatte, wird in Konstrukte übersetzt, die er bereits kennt. Pattern Matching zum Beispiel wird zu einer Folge von ===-, deconstruct- und deconstruct_keys-Prüfungen mit Bindungen an lokale Variablen.
Wie kompatibel ist es?
Ich messe mit ruby/spec, der Testsuite, die auch JRuby, TruffleRuby und die anderen Implementierungen verwenden. CRuby 4.0.6 ist die Referenz: Wenn IronRuby und eine Spec sich widersprechen, prüfe ich, was ruby macht, und ändere IronRuby, nicht die Spec.
| Suite | Beispiele | Fehler unter Linux |
|---|---|---|
spec/language |
2934 | 0 |
spec/command_line |
175 | 0 |
spec/security |
34 | 0 |
spec/library |
6398 | 0 |
spec/core |
23136 | 21, davon 20 ObjectSpace.each_object |
ObjectSpace.each_object muss den Heap durchlaufen, was .NET standardmässig nicht anbietet. Wie bei JRuby gibt es das hinter einer Option (-X:ObjectSpace).
Unter Windows laufen spec/language und spec/security ebenfalls ohne Fehler durch. Der Rest ist dort noch holpriger, vor allem wegen Unix-Funktionen wie Symlinks, fork und Signalen. Eine macOS-Version gibt es, sie ist aber kaum getestet.
Gems und Rails
igem install rack und ir -S bundle install installieren Gems von rubygems.org über echtes TLS. Die Bundled Gems von CRuby 4.0, etwa rake, minitest, rexml und csv, werden mit IronRuby mitgeliefert. Die Bibliotheken, die CRuby in C implementiert, zum Beispiel json, psych, openssl, bigdecimal, zlib und date, sind in C# geschrieben. Ausserdem habe ich die C-Teile von msgpack, oj und bcrypt nach C# portiert, und nio4r und puma laufen.
Eine Rails-8.1-Applikation startet und beantwortet HTTP-Anfragen. ActiveSupport, ActionPack, ActionView mit ERB-Templates, ActiveJob und ActiveRecord laden alle. Für eine vollständige Rails-App fehlen zwei Dinge, und beide sind C-Extensions: nokogiri und ein Datenbank-Adapter, denn sqlite3, pg, mysql2 und trilogy sind alle nativ.
Allgemein kann IronRuby keine C-Extensions laden, weil es kein ruby.h gibt, gegen das man kompilieren könnte. Viele Gems haben einen Fallback in reinem Ruby, wenn ihre Extension fehlt, und IronRuby verwendet diesen Fallback.
Performance
Zwei Compiler spezialisieren den Code auf die Typen, die ein Programm tatsächlich verwendet. Beide sind standardmässig eingeschaltet:
- Methoden-JIT: Eine häufig aufgerufene Methode wird durch eine Kopie mit ungeboxten Ganz- und Gleitkommazahlen, CLR-Arithmetik und direkten Aufrufen bei Rekursion ersetzt. Wird eine Methode neu definiert oder läuft eine Ganzzahl über, fällt sie auf die generische Version zurück.
- OSR (On-Stack Replacement): Eine Schleife, die lange genug läuft, wird noch während der Ausführung durch eine spezialisierte Kopie ersetzt. So werden auch heisse Schleifen in einem Block erreicht, der nur einmal aufgerufen wird.
Über 50 Benchmarks ist IronRuby im geometrischen Mittel etwa dreimal langsamer als CRuby 4.0.6. Bei reiner Arithmetik ist es schneller: fib, mandelbrot, int_arith und float_arith brauchen 0,27 bis 0,74 der Zeit von CRuby. Schwachstellen sind Exceptions und Fibers, weil das Erzeugen eines Backtraces auf .NET teuer ist und jede Fiber ein CLR-Thread ist.
.NET aus Ruby, Ruby aus .NET
Dafür wurde IronRuby gemacht. Ruby-Code kann .NET-Typen direkt verwenden:
date = System::DateTime.new(2000, 1, 1)
System::Environment.OSVersion.Platform
Umgekehrt kann eine .NET-Applikation IronRuby einbetten und Ruby-Skripte ausführen, zum Beispiel als Plugin- oder Skriptsprache.
irubyc: Applikationen verpacken
Als Maintainer von OCRAN liegt mir am Herzen, dass Ruby-Programme auch bei Leuten laufen, die kein Ruby installiert haben. irubyc, benannt nach jrubyc von JRuby, macht aus einem Ruby-Programm eine .NET-Applikation:
$ ./irubyc.sh --single-file --self-contained -t out app.rb lib/
Die Ruby-Dateien werden als Ressourcen in die Assembly eingebettet, zusammen mit IronRuby und der Standardbibliothek. --single-file erzeugt eine einzige ausführbare Datei, und --self-contained packt auch die .NET-Laufzeitumgebung mit ein, sodass das Programm auf einem Rechner ohne .NET läuft. Mit --library erzeugt irubyc eine einzelne DLL, die eine .NET-Host-Applikation als Plugin laden kann, mit IronRuby darin.
irubyc kompiliert Ruby noch nicht zu IL: Die eingebetteten Quelltexte werden beim Start geparst. Einen Prototyp für echte Ahead-of-Time-Kompilierung, von Ruby über IL bis zu einer NativeAOT-Datei, gibt es in Util/aot (Englisch).
Ausprobieren
Die erste Vorabversion, IronRuby 4.0.0-preview1, gibt es als Download für Linux x64, Windows x64 und macOS auf Apple Silicon. Die .NET-Laufzeitumgebung ist dabei, es muss also nichts weiter installiert werden:
$ 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
Unter Windows entpacken Sie das Zip-Archiv und verwenden irb.cmd, ir.cmd und igem.cmd. Die macOS-Version startet, ist aber kaum getestet. irubyc funktioniert auch aus dem Download; um eine Applikation zu kompilieren, braucht es das .NET SDK.
Wenn Sie es lieber selbst bauen, steht im README (Englisch), wie das geht. Sie brauchen dafür das .NET SDK, Ruby und einen C-Compiler für prism.
IronRuby 4.0 ist noch eine Vorabversion. Aber es führt heutiges Ruby auf heutigem .NET aus, und mit jeder Runde ruby/spec wird der Abstand zu CRuby kleiner. Wenn Sie es ausprobieren und etwas nicht funktioniert, eröffnen Sie gerne ein Issue oder schreiben Sie mir.