May 12, 2023·4 min read·engineeringrubyjitparserpreview

Ruby 3.3's first preview separates JIT experimentation from production use

The first Ruby 3.3 preview replaces MJIT with experimental RJIT, improves YJIT, and adopts a Ruby-written parser generator to reduce Bison compatibility problems.

A JIT implemented in Ruby makes compiler development accessible through the language it compiles. That does not make it the recommended production compiler. Ruby 3.3's first preview introduces RJIT with a clear experimental purpose while continuing to direct production users toward YJIT.

The May 12 preview announcement replaces MJIT with RJIT and changes parser generation from Bison to Lrama. It also reports YJIT improvements to supported calls, code generation, and metadata memory use. These are changes to the runtime's implementation and development workflow, not a new source-language model for ordinary Ruby applications.

I find Ruby more consistent and approachable than the Perl style from which I came to it. A Ruby-written component can make parts of the runtime easier for Ruby developers to investigate. That is a contributor benefit even before there is a reason to select that component for an application.

RJIT removes the runtime C compiler dependency

RJIT is a pure-Ruby JIT compiler in this preview. It supports x86_64 on Unix platforms and does not require a C compiler at runtime, unlike MJIT. The announcement explicitly limits its purpose to experimentation.

The runtime dependency distinction is specific. It concerns how JIT compilation happens while an application runs. It should not be expanded into a claim that building Ruby or installing arbitrary native extensions no longer involves a compiler.

The project also states the intended production choice plainly: continue using YJIT. RJIT's inclusion is therefore not a recommendation to replace an application's existing production JIT with the new experimental one.

That division allows different kinds of work to proceed. Researchers and contributors can explore a JIT written in Ruby, while production-oriented optimization continues in YJIT. The preview's value includes exposing both efforts without presenting them as equivalent deployment choices.

Platform limits are part of that decision. An x86_64 Unix experiment cannot be assumed to support every environment where Ruby itself runs. The preview announcement names the supported architecture and platform family instead of implying that the language's portability automatically applies to RJIT.

YJIT expands the code it can compile

The preview improves support for splat and rest arguments and compiles more calls involving optional arguments. These changes address the amount of ordinary Ruby execution that can remain in generated code. An optimization matters less if a common call shape repeatedly sends execution elsewhere.

The announcement also lists specialized treatment for operations such as integer and string inequality and several common type or block checks. These are particular operations with targeted handling. They do not establish that all dynamic dispatch has become equivalent to a statically resolved call.

Instance-variable handling improves for megamorphic object shapes. The release notes say these accesses no longer exit to the interpreter for that case. The change extends the cases handled by YJIT rather than requiring an application to stop using instance variables.

Generated-code metadata uses less memory, and ARM64 code generation also improves. YJIT also allocates registers for virtual-machine stack operations, another implementation change listed in the preview announcement. Memory retained to support compilation is part of a JIT's operating cost alongside the machine code itself. The announcement identifies that cost without giving one universal memory figure for every application.

This matters to my view of Ruby's continuing runtime work. Readable source can still depend on a sophisticated implementation to execute common dynamic behavior efficiently. The preview's details make that work visible at the level of argument handling and instance-variable access.

Delaying compilation changes when work begins

The preview can start YJIT in a paused state and enable it later. The announcement gives the option and API as follows.

ruby --yjit-pause application.rb
RubyVM::YJIT.resume

These are the names documented for this first preview. The example does not imply that an application should invoke the API without deciding where its boot phase ends. The point is that compilation can begin after startup work has completed.

Boot code and sustained application execution have different lifetimes. Giving the application an explicit point to resume JIT activity lets it distinguish those phases. The announcement presents that as the reason to offer paused startup.

Exit tracing also gains a sampling option. That is diagnostic support for investigating generated-code exits, distinct from choosing when compilation starts. The preview exposes more control over both observation and timing without turning every application into a compiler experiment.

Lrama changes how Ruby's parser is generated

The Lrama adoption proposal, by Kaneko Yuichiro, describes a pure-Ruby LALR parser generator that accepts Ruby's parse.y and produces a compatible parse.c. The immediate goal is to stop depending on differing Bison versions across build environments.

This is a parser-generator replacement. It is not a statement that the parser's accepted language is being redesigned by this change. Keeping the grammar input and compatible generated C output explains how the implementation can change while preserving that boundary.

The issue discussion supports the dependency argument. Hiroshi Shibata favors reducing required dependencies, and Yukihiro Matsumoto points to avoiding Bison compatibility problems. More ambitious parser improvements are left for other issues.

That separation is useful. A build-tool replacement can remove environmental variation before further grammar or parser work begins. Attributing every possible future parser capability to the initial generator switch would hide the specific problem this proposal addresses.

The preview includes a standard-library transition

The announcement retires ext/readline from the standard distribution and points to Reline, a pure-Ruby implementation compatible with its API. Users needing the extension can install readline-ext separately. The stated benefit includes no longer requiring libraries such as libreadline or libedit for that standard component.

This follows a similar contributor-facing direction to the parser-generator change, but at a different boundary. Lrama generates parser code during development and builds. Reline supplies interactive line editing as Ruby library code.

Neither change removes every native dependency from Ruby applications. They reduce particular dependencies in the runtime and its distribution. As of the first preview, RJIT experimentation, YJIT optimization, and these tooling changes remain separate pieces of the work leading toward Ruby 3.3.