Open-Source Wikis

/

Ruby

/

Ruby

/

Architecture

ruby/ruby

Architecture

Ruby is implemented as a tree of cooperating subsystems written mostly in C, with selected components in Rust (the JITs) and Ruby (the standard library and many built-in method bodies). At the highest level, source code flows from text → AST → bytecode (an iseq) → either the interpreter loop or one of the JITs, while the rest of the runtime — GC, threading, IO, and core classes — backs each step.

Top-level execution flow

graph LR
    src[Ruby source code] -->|tokenize + parse| parser
    parser[Parser: parse.y or Prism]
    parser -->|AST| compiler[Bytecode compiler\ncompile.c / prism_compile.c]
    compiler -->|iseq| vm[VM dispatch loop\nvm.c / vm_exec.c]
    vm -->|hot code| yjit[YJIT\nyjit/]
    vm -->|opt-in| zjit[ZJIT\nzjit/]
    yjit -->|machine code| cpu[CPU]
    zjit -->|machine code| cpu
    vm -->|interp ops| cpu
    vm <-->|alloc / sweep| gc[Garbage collector\ngc.c / gc/]
    vm <-->|threads / IO| os[OS scheduler]

Source text is lexed and parsed into an AST. The compiler walks the AST to emit a stack-based bytecode instruction sequence (an iseq, defined in iseq.h/iseq.c). The VM dispatches those instructions in a switch-or-threaded loop (vm_exec.c, generated from insns.def). When YJIT or ZJIT is enabled, it observes hot iseqs and generates native machine code that the VM jumps into in place of interpretation.

Parsers

Ruby ships with two parsers:

  • parse.y — the historical Bison grammar (now built with Lrama, a Ruby-implemented Bison-compatible parser generator under tool/lrama/). It produces the NODE tree (node.h, node.c).
  • prism/ — a hand-written portable C parser (originally YARP) that produces a typed AST (prism/prism.h, prism/config.yml). Prism is also distributed as a standalone library and powers tools like Ruby LSP, RuboCop, and Sorbet.

The active parser is selected at compile/build time and per-run via RUBYOPT=--parser=prism|parse.y. The compiler bridge for Prism is prism_compile.c; the bridge for parse.y is the compile.c family.

Compiler and VM

compile.c walks the AST and emits a packed iseq containing:

  • A flat array of bytecode opcodes and operands, defined in insns.def.
  • A constant pool, line table, catch table (for rescue/ensure), and metadata.

The VM is defined by:

  • vm_core.h — the master header for rb_vm_t, rb_thread_t, rb_control_frame_t, the call cache, and inline cache structures.
  • vm.c, vm_eval.c, vm_method.c, vm_insnhelper.c — frame management, method dispatch, and instruction helpers.
  • vm_exec.c (auto-generated from insns.def via tool/ruby_vm/) — the actual dispatch loop. CRuby supports both a switch-based dispatcher and a token-threaded dispatcher.

Method lookup uses inline caches (vm_callinfo.h) and shapes (shape.c, shape.h) so that instance variable access and method calls do not have to rewalk class hierarchies on every dispatch.

JIT compilers

Both JITs sit behind the VM and are optional:

  • YJIT (yjit/, entry in yjit.c) uses lazy basic-block versioning (LBBV). It compiles individual basic blocks of an iseq on first execution, specializes on observed types, and patches in branch stubs as new versions are needed. It targets x86_64 and arm64.
  • ZJIT (zjit/, entry in zjit.c) uses a higher-level IR (HIR/LIR) and an offline-style optimizer. It is the more recent of the two and lives next to YJIT in the same Cargo workspace (Cargo.toml).

Both are statically linked Rust crates, configured in the top-level Cargo.toml workspace. See jits/index.md.

Memory and GC

Objects live in heap pages managed by the garbage collector. The GC interface is pluggable: gc/gc.h and gc/gc_impl.h define the contract; gc/default/ is the in-tree mark-sweep-compact implementation, and gc/mmtk/ binds to the MMTk research framework. The high-level API (gc.c) routes object allocation, marking, sweeping, and compaction to whichever implementation is selected at build time.

Object internals are organized around shapes (shape.c) — small immutable transition records that describe the layout of instance variables. Shapes let the VM and JIT specialize on object structure without inspecting class state on every read.

Concurrency

Three concurrency primitives coexist:

  • Native threads (thread.c, thread_pthread.c, thread_win32.c). All threads share one Global VM Lock (GVL) so only one thread runs Ruby code at a time, but blocking syscalls and explicit rb_thread_call_without_gvl regions release it.
  • Fibers (cont.c, coroutine/). Cooperative coroutines whose stack switching is implemented in hand-written assembly under coroutine/<arch>/Context.S.
  • Ractors (ractor.c, ractor_sync.c). Actor-style isolated VMs that can run truly in parallel. Each Ractor has its own GVL.

Async IO uses Fiber Schedulers (scheduler.c) — a pluggable interface that lets gems like async plug their own event loops in.

Standard library and extensions

  • lib/ — pure-Ruby standard library (e.g., lib/optparse.rb, lib/uri/, lib/net/http.rb).
  • ext/ — bundled C extensions built with mkmf (e.g., ext/socket/, ext/openssl/, ext/json/, ext/psych/, ext/digest/).
  • gems/bundled_gems — gems that are pulled in at release time but maintained out-of-tree.

Many "default gems" are vendored — their authoritative sources live in standalone gem repositories and are synced via tool/sync_default_gems.rb.

Build system

The build is autoconf + make:

  • configure.acconfigureMakefile
  • common.mk is the platform-independent Makefile body.
  • Rust crates are built via yjit/yjit.mk and zjit/zjit.mk, invoked from the main make.
  • tool/m4/, tool/lrama/, and many tool/*.rb scripts generate code (the parser, instruction handlers, transcoding tables, etc.) at build time.

See build/index.md for the full pipeline.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Architecture – Ruby wiki | Factory