The Ruby Compiler Survey
The Ruby Compiler Survey is a project cataloguing, preserving, and dissecting compilers for the Ruby programming language. In this context, a compiler is something that eventually produces machine code from Ruby code, whether that’s ahead-of-time or just-in-time. There are a surprisingly large number of attempts to write compilers for Ruby, using very varied approaches, so a comparative study is interesting. Some of these compilers were being lost to time and action was needed now to preserve and document them.
We’re interested in how these compilers are designed and implemented, their history, and the output they produce. We’re focusing on compilation in order to keep the project in some kind of scope, but we’ll describe other parts of the implementation, such as the garbage collector or interpreter, where they’re relevant. We apply some judgement for how much we consider to be part of the Ruby implementation. For example where components are shared between multiple language implementations, we consider them to be part of the Ruby compiler when they are co-developed. Some of these compilers are much more serious than others - several of them are learning exercises, demos, or toys.
This isn’t a benchmarking game and this isn’t a competition. The compilers target a variety of architectures and have diverse goals beyond simple performance so comparing quantitatively would not make sense. We aim to be objective but we do offer informed commentary on the techniques employed and how successful they are in producing efficient machine code from Ruby. The author is the founder of the TruffleRuby compiler but is trying to be neutral.
There’s a page describing each compiler implementation, and an appendix describing how to build and use it. We assume medium-to-expert knowledge of compilers. Much of the information in this site is archaeological, so we apologies for any omissions or errors. Corrections welcome.
The compilers
Compiler | Years active | Base VM | Stage | General approach | Frontend | Interpreter | Intermediate representations | Key authors |
---|---|---|---|---|---|---|---|---|
Hokstad | 2008-present | Custom Ruby | AOT | Template compilation of an AST | Custom recursive descent and operator precedence parser | None | Enhanced AST | Hokstad |
Hyperdrive ⎋ | 2019-2020 | MRI | JIT | Tracing of YARV instructions then template compilation to Cranelift | Tracing YARV interpreter | Instrumented base interpreter | None | Matthews |
IronRuby ⎋ | 2007-2011 | Custom C# | JIT | Generation of CIL | Lam | |||
JRuby | 2006-present | Custom Java | JIT 1 | Generation of JVM bytecode | Parser to AST, to internal IR | Internal IR interpreter | CFG of linear RTL instructions | Nutter, Enebo, Sastry |
LLRB ⎋ | 2017 | MRI | JIT | Generation of LLVM IR | Kokubun | |||
Ludicrous ⎋ | 2008-2009 | MRI | JIT | Template compilation through DotGNU LibJIT | Brannan | |||
MacRuby ⎋ | 2008-2013 | MRI | AOT/JIT | Generation of LLVM IR | Sansonetti | |||
MagLev ⎋ | 2008-2016 | Custom Gemstone Smalltalk | JIT | McLain, Felgentreff | ||||
MRuby JIT ⎋ | Hideki | |||||||
Natalie ⎋ | 2019-present | Custom C++ | AOT | AST incrementally lowered to C++ | Enhanced AST | Morgan | ||
Ruby+OMR ⎋ | 2016-2017 | MRI | JIT | Generation of J9 IR | Gaudet, Stoodley | |||
RTL MJIT ⎋ | 2017 | MRI | JIT | Generation of C | Makarov | |||
Rubinius | 2008-2016 | Custom C++ and Ruby | JIT | Generation of LLVM IR | Parser to AST, to custom stack bytecode | Stack bytecode | None | Phoenix, Bussink, Shirai |
RuJIT ⎋ | 2015 | MRI | JIT | Tracing | Ide | |||
Rhizome ⎋ | 2017 | MRI, JRuby, Rubinius | JIT | Conventional speculative compiler with in-process assembler | Base bytecode or IR to custom bytecode | Stack bytecode | Graphical sea-of-nodes | Seaton |
RubyComp ⎋ | 2004 | AOT | Alexandersson | |||||
RubyX ⎋ | 2014-2020 | AOT | Conventional compiler with in-process assembler | Parser to AST | None | Multiple IRs gradually removing abstraction and lowering from AST to linear | Rüger | |
Ruby.NET ⎋ | 2008 | Custom C# | Generation of CIL | Kelly | ||||
Rucy ⎋ | 2021 | AOT | Template compilation to eBPF | Uchio | ||||
Sorbet ⎋ | 2019-present | MRI | AOT | Generation of MRI LLVM IR 'C' extension | Parser to AST | None | Sorbet's typechecking IR | Tarjan, Petrashko, Froyd |
TenderJIT ⎋ | 2021 | MRI | JIT | Lazy Basic Block Versioning with in-process assembler | Template compiler of YARV bytecode | Base interpreter | None | Patterson |
Topaz ⎋ | 2012-2014 | Custom RPython and Ruby | JIT | Metatracing of a stack bytecode interpreter | Parser to AST | Stack bytecode interpreter | Gaynor, Felgentreff | |
TruffleRuby ⎋ | 2013-present | Custom Java and Ruby | JIT | Partial evaluation of self-specialising AST | Parser to AST | Self-specialising AST interpreter | Graphical sea-of-nodes | Seaton, Daloze, Menard, Chalupa, MacGregor |
XRuby ⎋ | 2006-2008 | Custom Java | AOT | Template compilation to Java bytecode | Parser to AST | None | None | Zhi |
yarv2llvm ⎋ | 2008-2010 | MRI | JIT | Generation of LLVM IR | Hideki | |||
YARV MJIT ⎋ | 2018-present | MRI | JIT | Generation of C | Base interpreter | Kokubun | ||
YJIT ⎋ | 2020-present | MRI | JIT | Lazy Basic-Block Versioning with in-process assembler | Template compiler of YARV bytecode | Base interpreter | None | Chevalier-Boisvert |
YTL ⎋ | 2009-2014 | Hideki |
Out of scope but related
Wilson is a Ruby library for assembling IA-32 machine code.
ronin-asm is a Ruby library for assembling IA-32 and AMD64 machine code.
Fisk is a Ruby library for assembling AMD64 machine code.
Ruby Compiler is a tool to package Ruby code with a Ruby interpreter, giving a stand-alone executable, but not actually translating the Ruby code to machine code.
Sources
We use a running set of example programs throughout the survey.
The ruby-compiler-survey
repository contains this content, files that can be used to build and run the compilers, including in some cases patches, and output taken at the time of writing the survey. Compiler source code itself is archived in the ruby-compiler-survey
GitHub organisation where possible.