frozone

Ruby implementation in separate load, execute phases

Frozone

Frozone

A Ruby VM implemented in Ruby — with two AoT compiler backends targeting native binaries.

Frozone is built around a two-phase model: a full-featured interpreter runs the dynamic load phase (metaprogramming, requires, runtime-computed constants), then an AoT compiler snapshots the settled, closed-world result and emits native code.

Two AoT backends

Frozone has two AoT backends, written at different points in the project and with different design priorities. Both consume the same interpreter snapshot.

First backend — TI-driven Crystal/C++, perf-first (PoC)

The original backend is a two-phase AOT transpiler to Crystal (and later C++), with type inference (TI) as the load-bearing optimization. TI proves receiver classes, narrows method dispatch, and unlocks primitive specialisation; the emitted code is close to what a Crystal programmer would hand-write. We achieved excellent perf on the benchmark suite — see the numbers below — but the implementation is a proof-of-concept: the TI engine covers exactly the constructs the benchmarks exercise, and broader Ruby semantics (full metaprogramming, every corner of method dispatch) are out-of-scope.

Benchmark Crystal C++ MRI YJIT Best/MRI Best/YJIT
fib(35) ×3 94 37 2326 501 63× 14×
matmul(200) ×20 256 82 7355 2897 90× 35×
nbody ×100 116 110 7259 2644 66× 24×
nqueens 500×12 6566 5890 198033 49711 34× 8.4×
loops_times 123 96 8675 90×
sudoku ×20 418 7 7151 1928 1021× 276×
blurhash ×10 246 241 2504 1057 10× 4.4×
fannkuchredux ×10 857 102 3217 3161 32× 31×
binarytrees ×60 2000 4177 16244 6916 8.1× 3.5×
str_concat ×100 992 2894 5248 4502 5.3× 4.5×
splay ×200 13835 15231 20349 13417 1.5× 1.0×

Wall-clock milliseconds. Both backends beat YJIT on every compute benchmark in this set. See docs/cpp-backend.md and docs/perf-suite.md for architecture and full numbers.

Second backend — C++, correctness-first

The second C++ backend, under active development, is a fresh restart with inverted priorities:

  • Correctness over perf. The goal is full Ruby semantic authenticity — every method, every corner of dispatch, every metaprogramming surface works the way MRI does. Perf comes later.
  • No TI yet. Every value is uniformly boxed (BasicObject* everywhere). No primitive specialisation, no pointer tagging. This trades absolute speed for a uniform model that's easier to make correct and easier to self-compile.
  • Self-compilation as the forcing function. The bar is that Frozone itself compiles end-to-end under this backend (closed-world AOT of the parser, the interpreter, the core library), and the resulting binary can parse and run a Ruby program. Anything Frozone uses internally has to work; there are no shortcuts.

The architecture is BasicObject*-everything, virtual method dispatch via a universal vtable, with optimisations (natural-args calling convention, leaf typeid dispatch, snapshot serialisation, per-class translation units) layered on once correctness is in place. Perf catches up later — once TI is re-introduced on this foundation, it can specialise from a uniformly-boxed starting point without having to also relitigate semantics.

Invoke via FROZONE_BOX_FIRST=1 (internal name retained at the env-var level). See docs/box-first-*.md for the engineering notes.

Interpreter status

Spec-compliance of the tree-walking interpreter — independent of either AoT backend:

Passing Total Rate
ruby/spec language 2615 2630 99.4%
ruby/spec core 22708 22913 99.1%
ruby/spec library 1979 2467 80%
RSpec unit tests 781 781 100%

Self-hosting: Frozone runs itself (Frozone²) with no shims. The full language spec suite passes through Frozone² with identical results. See docs/self-hosting.md.

The interpreter supports two parser front-ends: Prism (default) and whitequark/parser (Frozone fork, invoked with --parser=wq). The whitequark path keeps the interpreter pure Ruby end-to-end (no C extensions), which is what makes self-hosting tractable.

Quick start

# Prerequisites: Ruby 4.0.1 (rbenv install 4.0.1)
git clone --recursive https://github.com/rolandpj1968/frozone.git
cd frozone && bundle install

# Run the interpreter
bundle exec ruby frozone.rb -e "puts 'hello from Frozone'"

# First backend: AoT compile to Crystal or C++ and run
bundle exec ruby frozone.rb --aot bench/stubs/fib.rb
# => Frozone.compile!: wrote crystal/fib.cr
cd crystal && crystal build fib.cr --release -o fib && ./fib

FROZONE_CPP=1 bundle exec ruby frozone.rb --aot bench/stubs/fib.rb
# => Frozone.compile!: wrote cpp/gen/fib.cpp

# Second backend: AoT compile via the correctness-first path
FROZONE_CPP=1 FROZONE_BOX_FIRST=1 bundle exec ruby frozone.rb --aot bench/stubs/fib.rb

# Tests
bundle exec rspec                        # unit tests
bundle exec rake language                # ruby/spec language suite
bundle exec rake core                    # ruby/spec core suite

Documentation

Interpreter:

First backend (TI-driven, perf-first):

Second backend (correctness-first):

Cross-cutting:

Architecture

lib/frozone/vm/                VM runtime (ClassObject, Method, Frame, Context, intrinsics)
lib/frozone/ast/               AST nodes evaluated by the tree-walker
lib/frozone/compiler/          AoT compiler (Codegen, TypeInference, CrystalEmitter, CppEmitter)
lib/frozone/compiler/backend/cpp_box/   Second C++ backend
lib/frozone/vm/parser.rb       Prism-based front-end
lib/frozone/vm/wq_parser.rb    whitequark parser front-end (self-hostable path)
lib/core/4.0/                  Ruby stdlib in Ruby — parsed at VM startup, compilable as user code
crystal/src/                   Crystal runtime (RubyObject, RubyInteger, RubyString, …)
cpp/runtime/                   C++ runtime headers (both backends share intrinsics; box-first runtime in cpp/runtime/)
cpp/gen/                       Generated C++ source files
bench/stubs/                   Compilation stubs for benchmarks
bench/specs/                   Compilable spec files
spec/                          RSpec unit tests + ruby-spec integration

Acknowledgements

Frozone builds on the shoulders of several projects and people:

  • Natalie — a Ruby implementation that compiles to C++. Natalie demonstrated that a full Ruby implementation could target a compiled language, and its architecture was a key inspiration for Frozone's two-phase (interpret-then-compile) approach.
  • Chris Coetzee (chriscz) — for the insight that Crystal is a better compilation target than C++ for a Ruby compiler. Crystal's Ruby-like syntax and type system make the generated code readable and the type mapping natural, which proved essential for debugging and iterating on the original backend.
  • Crystal — the original AoT compilation backend.
  • Prism — Ruby's official parser, used as Frozone's primary front-end.
  • whitequark/parser (Frozone fork) — alternative pure-Ruby parser enabling self-hosting without C extensions.
Repository

frozone

Owner
Statistic
  • 2
  • 0
  • 0
  • 0
  • 0
  • about 9 hours ago
  • March 2, 2026
License

MIT License

Links
Synced at

Sun, 14 Jun 2026 22:40:11 GMT

Languages