Spinel – Ruby-AOT-Native-Compiler
(github.com/matz)- Ein AOT-Compiler, der Ruby-Quellcode nach programweiter Typherleitung in C-Code umwandelt und eigenständige native Binärdateien erzeugt
- Direkt vom Ruby-Schöpfer matz entwickelt; das Compiler-Backend selbst ist in Ruby geschrieben und nutzt eine Self-Hosting-Architektur, in der es sich selbst kompiliert
- Gegenüber miniruby (Ruby 4.1.0dev) etwa 11,6-mal schneller; Conway's Game of Life ist 86,7-mal, ackermann 74,8-mal und mandelbrot 58,1-mal schneller
- Die Compile-Pipeline wandelt Ruby mit einem Prism-basierten Parser zunächst in AST-Text um; anschließend übernimmt das Self-Hosting-Backend Typherleitung und C-Code-Erzeugung, bevor mit einem Standard-C-Compiler ein Standalone-Binary erstellt wird
- Unterstützt ein breites Spektrum an Ruby-Funktionen wie Klassen, Vererbung, Blöcke, Exception-Handling, Fiber, die eingebaute NFA-Regexp-Engine, automatisch hochgestufte Bigints und Pattern Matching
- Kleine Klassen mit bis zu 8 skalaren Feldern werden als Value Types automatisch auf dem Stack alloziert, wodurch der GC-Overhead vollständig entfällt (1 Million Allokationen 85 ms → 2 ms)
- String-Chaining mit
a + b + c + dwird mit einem einzigen malloc flach zusammengeführt;splitinnerhalb von Schleifen verwendet sp_StrArray wieder, um unnötige Allokationen zu vermeiden - Zahlreiche Compile-Time-Optimierungen wie Hoisting schleifeninvarianter Längen, Constant Propagation, automatisches Inlining von Methoden mit bis zu 3 Anweisungen und frühzeitiger Abbruch iterativer Inferenz (~14 % kürzere Bootstrap-Zeit)
- Die erzeugten Binärdateien haben keine Runtime-Abhängigkeiten und lassen sich nur mit libc + libm ausführen
eval, Metaprogrammierung, Thread und Mutex werden nicht unterstützt (nur Fiber wird unterstützt)- MIT-Lizenz
1 Kommentare
Hacker-News-Kommentare
Wenn es von Matz stammt, vertraut man darauf, dass er auch die Grenzen der Ruby-Semantik gut kennt
Meine Masterarbeit war ebenfalls ein AOT-JS-Compiler; er lief zwar, aber die Einschränkungen bei den Eingabedaten waren so groß, dass ich das Projekt schließlich aufgegeben habe
Damals waren JS-Entwickler nicht besonders daran gewöhnt, solche Einschränkungen selbst diszipliniert einzuhalten, und grundsätzlich nicht vorhersagbare Eingaben wie bei
JSON.parsewaren ein HindernisHeute könnte es dank TypeScript deutlich realistischer sein als damals
Schon beim allgemeinen Lambda-Kalkül sind die Grenzen der Typinferenz klar, und in Arbeiten von Matt Might oder bei Shed-skin Python treten ähnliche Beschränkungen zutage
Ich frage mich, wie verbreitet
eval,send,method_missingunddefine_methodin realem Ruby-Code tatsächlich sind, und auch, wie man typfreies Parsing, etwa bei JSON-Eingaben, normalerweise handhabtRuby zu parsen ist fast schwieriger als die eigentliche Übersetzung, deshalb wird Prism verwendet, und als Ergebnis wird C erzeugt
Die grundlegende Ruby-Semantik selbst ist gar nicht so schwer umzusetzen
Ich selbst hänge dagegen an einem alten self-hosting AOT-Compiler, der in purem Ruby geschrieben ist, und habe mir absichtlich den deutlich schwereren Weg ausgesucht, weil ich unbedingt einen eigenen Parser verwenden wollte
Früh habe ich gelernt, dass man die ersten 80 % grob zusammenbauen kann und dann schon ein beträchtlicher Teil von Ruby-Code läuft; die wirklich schwierigen „zweiten 80 %“ stecken in den Dingen, die Matz in diesem Projekt und in mruby weggelassen hat, etwa Encodings oder allerlei Randfunktionen
Ehrlich gesagt hat Ruby einige Features, die ich in echtem Code noch nie gesehen habe, daher fände ich es bei manchen nicht einmal seltsam, wenn sie deprecated würden
send,method_missingunddefine_methodsind sehr verbreitetDie Einschränkungen ähneln denen von mruby, und selbst unter solchen Einschränkungen gibt es Einsatzmöglichkeiten
send,method_missingunddefine_methodzu unterstützen, ist vergleichsweise einfacheval() zu unterstützen ist dagegen extrem schmerzhaft
Ein großer Teil von
eval()in Ruby lässt sich aber statisch auf die Block-Variante von instance_eval zurückführen, und in solchen Fällen wird AOT-Kompilierung recht einfachWenn man zum Beispiel den String, der in
eval()hineingeht, statisch kennt oder zerlegen kann, hat man viel SpielraumTatsächlich ist viel
eval()-Nutzung unnötig oder eher ein Umgehen einfacher Introspection, sodass man das mit statischer Analyse abfangen kannIn meinem Compiler würde ich dort ansetzen, wenn das zum Engpass wird
Auch typfreie JSON-Verarbeitung dürfte wahrscheinlich auf solche Mechanismen setzen
Wenn man das entfernt, bleibt eine kleine, gut lesbare Sprache übrig, die nicht so stark typisiert ist wie Crystal, sich aber auch nicht so stark auf Metaprogrammierung stützt wie offizielles Ruby
Das Potenzial wirkt daher ziemlich groß, aber beurteilen lässt es sich erst mit der Zeit
evalhäufig verwendenIch könnte auch ohne auskommen, aber für mich ist das einfach ergonomischer
eval,exec,define_methodsowie Muster wie das Erzeugen neuer Klassen mitClass.newundStruct.newinteressantDer Großteil dieser Nutzung konzentriert sich auf den Boot-Zeitpunkt der App oder auf die Phase, in der Dateien per require geladen werden; in gewisser Hinsicht ist das ohnehin schon fast ein Kompilierungsschritt
Das ist etwas, das Matz gerade auf der RubyKaigi 2026 vorgestellt hat
Es ist experimentell, wurde aber mit Hilfe von Claude in etwa einem Monat gebaut, und auch die Live-Demo hat funktioniert
Der Name stammt von Matz’ neuer Katze; deren Name wiederum kommt von der Katze aus Card Captor Sakura, wo sie wiederum ein Gegenstück zu einer Figur namens Ruby bildet
Bei jemandem wie Matz könnte das sogar eher von 100x auf 500x gehen
https://en.wikipedia.org/wiki/Spinel
Das Video scheint noch nicht live zu sein; offenbar werden sie hier nach und nach hochgeladen
https://www.youtube.com/@rubykaigi4884/videos
Auch der Projektname wirkt, als wäre er emotional gewählt worden
Das ist zweifellos sehr beeindruckend, aber ohne AI agent scheint es kaum wartbar
spinel_codegen.rbhat 21.000 Zeilen, und manche Methoden sind bis zu 15 Ebenen tief verschachteltCompiler-Code ist von Natur aus selten schön, aber selbst nach diesem Maßstab wirkt das für Menschen extrem schwer wartbar
Compiler haben klare Grenzen zwischen Subsystemen und eindeutige Übergaben zwischen den Phasen; gerade deshalb gehören sie eher zu den Dingen, die sich am leichtesten modular aufbauen lassen
Das Problem ist meist, dass man erst einmal etwas Lauffähiges baut und dann keine Zeit mehr zum Refactoring bleibt; so wächst die Unordnung immer weiter
spinel_codegen.rbist fast schon eldritch horrorMit Claude produziere ich auch ständig solchen Spaghetti-Code und habe mich schon gefragt, ob ich etwas falsch mache
Aber wenn ich bei einem wirklich interessanten Projekt eines Programmierers, den ich zur absoluten Spitzenklasse zähle, ebenfalls an vielen Stellen ziemlich schlechte Codequalität sehe, dann bin ich wohl nicht allein
Zum Beispiel ist
infer_comparison_type()nicht das allerschlimmste Beispiel und auch nicht unlesbar, aber es gäbe eine viel einfachere und klarere Umsetzung, zu der Claude einfach nicht findetWenn man die Vergleichsoperatoren in einem
Setsammelt und mitinclude?prüft, wäre es kürzer, schneller, lesbarer und leichter wartbarClaude driftet aber immer zu if-return-Ketten ab und wirkt fast so, als wäre ihm sogar if-else fremd
Meine eigene Claude-Codebasis ist voll von solchen Mustern; jetzt weiß ich immerhin, dass das nicht nur bei mir so ist
Andere Dateien sind dagegen viel besser, und insbesondere das
lib-Verzeichnis scheint demext-Verzeichnis des Haupt-Ruby-Repos zu entsprechen und wirkt qualitativ ordentlichAuch die API trägt klar den Einfluss von MRI Ruby; selbst wenn die Implementierung recht anders ist, scheint Matz Claude dahin gelenkt zu haben, Teile der Original-API nachzuahmen, wodurch die Ausgabe aufgeräumter wirkt
[1] https://github.com/matz/spinel/blob/98d1179670e4d6486bbd1547...
Solange Tests und Benchmarks durchlaufen, bin ich erst einmal zufrieden
Ich frage mich aber, ob riesige Dateien auch für AI gut handhabbar sind
Ich versuche, Dateien auf unter 300 Zeilen zu begrenzen, und glaube, dass Code, den Menschen leicht verstehen, auch für coding agents leichter ist
Die Einschränkungen sollen diese sein
No eval:
eval,instance_eval,class_evalNo metaprogramming:
send,method_missing,define_method(dynamisch)No threads:
Thread,Mutex(Fiber wird unterstützt)No encoding: Annahme von UTF-8/ASCII
No general lambda calculus: tief geschachtelte
-> x { }mit[]-AufrufenDie Annahme von UTF-8/ASCII ist für mich persönlich keine große Einschränkung, aber der Rest dürfte in ziemlich vielen Programmen real relevant sein
Und um das wieder einzubauen, scheint erheblicher Aufwand nötig zu sein
Ich nutze Ruby schon lange, und als jemand, der alle aufgelisteten Features tatsächlich verwendet hat, merke ich: Genau so eine Version von einfachem Ruby ist letztlich das, was ich mir nach all der Entwicklung eher wünsche
Sie ist einfacher und leichter zu verstehen, ohne dass Rubys eigener ästhetischer Charakter verloren geht
Durch LLMs ist die Produktivität bei der Codeerzeugung inzwischen so hoch, dass man Metaprogrammierung nicht mehr so sehr braucht wie früher, um Boilerplate für die Produktivität von Entwicklern zu reduzieren
Schließlich schreiben Entwickler selbst insgesamt einen kleineren Anteil des Codes
Die Syntax ist ähnlich, und es gibt ein statisches Typsystem, was zu effizienter kompiliertem Code führt
evalfehlt, finde ich eher positiv, aber dass sogar threads und mutexes fehlen, ist schadeDas Fehlen von
define_methodkann ich angesichts seines Einsatzzwecks noch nachvollziehenAber
sendundmethod_missingsind in bestehender Bibliothekslandschaft verbreitet, und ihre Implementierung scheint auch nicht völlig unmöglich, etwa indem man Lookup-Tabellen im Speicher zur Compile-Zeit aufbautDaher weiß ich nicht, ob sie absichtlich ausgeschlossen wurden oder ob man einfach noch nicht so weit ist
Ich hoffe auf Letzteres, aber zumindest vorerst wäre produktiver Einsatz wegen der Kompatibilität wohl schwierig
Sondern darin, weniger Code lesen zu müssen
Das ist wirklich cool, und ich warte schon lange auf einen AOT-Compiler für Ruby
Schade ist nur, dass es kein Fallback für
evaloder Metaprogrammierung gibt; vermutlich hat man sich bewusst auf ein kleines, performantes Subset konzentriertEs wäre schön, wenn mit diesem AOT-Compiler gebaute Gems gut mit MRI interagieren würden
Für das Paketieren oder Bundeln von Standard-Ruby und Gems braucht man weiterhin Dinge wie tebako, kompo oder ocran; früher gab es auch Projekte wie ruby-packer, traveling ruby oder jruby warbler
Es ist gut, eine weitere Option zu haben, aber ich hoffe auf eine endgültige Lösung mit besserer Developer-UX
Es war einfach viel zu lange nicht aktualisiert worden
Ich frage mich, warum no threads gilt
Rubys Scheduler und die zugrunde liegende pthread-Implementierung müssten doch auch im C-Bereich gut funktionieren; vielleicht zielt man auf zero dependency ab
Falls nicht geplant ist, später eine optionale Erweiterung einzubauen, und Threads nicht nur vorläufig fehlen, wirkt diese Entscheidung etwas seltsam
Wahrscheinlich ist man schlicht noch nicht so weit
Multithreading sauber zu bauen, ist ohnehin sehr schwierig
Überraschend, dass das in etwas mehr als einem Monat gebaut wurde
Man kann über AI sagen, was man will, aber in den Händen eines fähigen Entwicklers sorgt sie für enorme Geschwindigkeitsgewinne
Bei Matz wirkt es, als würden einfach
gem env|infoundfindreichenDa das von Matz stammt, frage ich mich, wie realistisch es ist, dass es künftig Teil von Ruby core wird
Und falls das passiert, wie sehr es Crystal bedrohen könnte
Diese Eigenschaften sind für das Kompilieren und Warten großer Programme praktisch unverzichtbar
Hier geht es dagegen um ein eingeschränktes Ruby-Subset, sodass die meisten populären Ruby-Gems unverändert nicht laufen werden
Als Sprachsubset, das auf C-Kompilierung abzielt, erinnert es eher an PreScheme
In diesem Stadium sehe ich die beiden nicht in direkter Konkurrenz im selben Bereich
Vollständiges Ruby wird mit hoher Wahrscheinlichkeit JIT brauchen
[1]: https://prescheme.org/
Das wäre die Revanche von Werkzeugen wie Rational Unified Process oder Enterprise Architect
Der Unterschied bestünde nur darin, dass statt UML-Diagrammen Markdown-Dateien geliefert würden
Dafür dürfte es im Bereich Infrastructure Tools nützlich sein
Man könnte sich zum Beispiel einen statisch kompilierten bundler vorstellen, der in Ruby geschrieben ist, aber zugleich die Rolle eines Ruby-Installationstools wie RVM übernimmt
Bestehende Ruby-Buildpacks sind in Ruby geschrieben, müssen aber per bash gebootstrapped werden, was nervig ist und Edge Cases erzeugt
CNB wurde geschrieben, um genau dieses Problem zu vermeiden, und die Idee, ein abhängigkeitsfreies Single-Binary auszuliefern, ist wirklich stark