Open-Source Wikis

/

Ruby

/

Systems

/

Virtual machine

ruby/ruby

Virtual machine

The VM is what runs Ruby bytecode. It's a stack-based interpreter (called YARV historically) defined across roughly 25 C files whose names start with vm_. The dispatch loop is generated from insns.def by tool/ruby_vm/. The same iseqs feed YJIT and ZJIT when those JITs are enabled.

Purpose

  • Execute compiled iseqs on behalf of Ruby threads.
  • Manage Ruby-level frames (rb_control_frame_t) on the per-thread VM stack.
  • Implement method dispatch, block invocation, exception propagation, and trace points.
  • Cache method and constant lookups via inline caches.
  • Coordinate with the GC, the threading subsystem, and the JITs.

Files

File Purpose
vm.c VM lifecycle, top-level entry, frame setup, environment manipulation. ~157 KB.
vm_core.h The master VM struct definitions. ~71 KB.
vm_eval.c rb_funcall* and other dispatch helpers.
vm_method.c Method table, method definitions, refinements, method lookup.
vm_insnhelper.c Implementations of complex opcodes called from the dispatch loop. ~250 KB.
vm_insnhelper.h Inline helpers used in insns.def bodies.
vm_args.c Argument processing: positional, keyword, splat, post args.
vm_callinfo.h rb_callinfo/rb_callcache/inline cache structs.
vm_exec.c The generated dispatch loop. Two flavours (switch and threaded).
vm_exec.h Dispatcher macros (DISPATCH, NEXT_INSN).
vm_dump.c Crash-time dumper.
vm_trace.c TracePoint implementation.
vm_sync.c, vm_sync.h Per-VM mutex and barrier helpers.
vm_backtrace.c Backtrace allocation and formatting.
vm_opts.h Compile-time VM tuning knobs.

Core types

typedef struct rb_vm_struct {
    /* per-process VM */
    VALUE self;
    rb_global_vm_lock_t gvl;
    struct list_head ractor_list;
    /* trampolines, signal handlers, ... */
} rb_vm_t;

typedef struct rb_thread_struct {
    rb_execution_context_t *ec;          /* per-thread state */
    rb_ractor_t *ractor;
    /* native thread handle, status, priority */
} rb_thread_t;

struct rb_execution_context_struct {
    VALUE *vm_stack;                     /* the value stack */
    size_t vm_stack_size;
    rb_control_frame_t *cfp;             /* current frame pointer */
    /* errinfo, raised flags, machine state for fibers */
};

typedef struct rb_control_frame_struct {
    const VALUE *pc;                     /* program counter */
    VALUE *sp;                           /* stack pointer */
    const rb_iseq_t *iseq;
    VALUE self;
    const VALUE *ep;                     /* environment pointer (locals + binding) */
    VALUE block_handler;
} rb_control_frame_t;

The macros GET_VM(), GET_THREAD(), GET_EC() retrieve thread-local pointers. The current frame is GET_EC()->cfp.

The dispatch loop

The dispatch loop is in vm_exec.c, generated from insns.def. Two implementations coexist depending on compiler/build flags:

graph LR
    cfp[ec->cfp] -->|read pc| insn[Fetch insn opcode]
    insn -->|switch / threaded| body[Insn body from insns.def]
    body -->|side effects| stack[Stack push/pop]
    body -->|advance pc| next[Next insn]
    next --> cfp
    body -->|leave / throw| return[Pop frame or unwind]
  • Switch dispatch: a giant switch (insn) { case BIN(opt_plus): ... }. Used on compilers that don't support computed gotos.
  • Threaded dispatch: each instruction body ends with goto *labels[next_insn], where labels[] is a label-as-value array. Faster on GCC/Clang.

Selected by the build (--with-vm-dispatch=...).

Method dispatch

Calls go through the callinfo / callcache machinery in vm_callinfo.h:

struct rb_callinfo {
    VALUE flag;            /* simple call / block given / kwargs / splat */
    int argc;
    VALUE kwarg;
    VALUE mid;             /* method id */
};

struct rb_callcache {
    VALUE flags;
    VALUE klass;           /* receiver class at last hit */
    const struct rb_callable_method_entry_struct *cme;
    union {
        struct {
            uintptr_t method_serial;
            uintptr_t aux1;
        } v;
        struct {
            VALUE call;     /* JIT entry */
            uintptr_t aux2;
        } jit;
    } aux_;
};

On every opt_send_without_block:

  1. The VM compares the receiver's class to cc->klass.
  2. If equal → use cc->cme directly. Constant-time call.
  3. Otherwise → walk the method table (vm_method.c::rb_method_entry_at), update the cache, and dispatch.

vm_callinfo.h's vm_call_method is the central dispatcher when caches miss. It handles:

  • Refinements (vm_call_iseq_setup_normal_0start_*).
  • Visibility (private, protected).
  • method_missing fallback.
  • Optimized C-method calls.
  • Kwarg packing/unpacking.
  • Block argument forwarding.

Frame layout on the stack

Each call pushes a new rb_control_frame_t and a value-stack region containing locals, the environment, the block handler, and the operand stack:

high addr
+----------------------+
| operand stack ...    |
+----------------------+
| local var slot N     |
| local var slot N-1   |
| ...                  |
| local var slot 0     |
+----------------------+
| block_handler        |
| environment ptr (ep) |
| ME (method entry)    |
| flags                |
+----------------------+ <- cfp->ep
| self                 |
| iseq                 |
+----------------------+ <- cfp
| ...calling frame...  |
low addr

leave pops back. throw walks frames looking for a matching catch-table entry.

Method table and modules

vm_method.c implements:

  • rb_define_method, rb_define_method_id, rb_define_protected_method, rb_define_private_method.
  • rb_method_entry_t allocation per (class, method_id) pair.
  • rb_method_entry_get_without_cache, rb_method_entry_at — the lookup walk.
  • rb_method_entry_arity, refinements, alias chains.

Method entries are GC objects. A class's method table is an rb_id_table_t keyed by method id.

Trace points

vm_trace.c implements TracePoint. Trace events fire from inside the dispatch loop when ec->trace_arg is non-null. The events are:

  • :line, :class, :end
  • :call, :return, :c_call, :c_return
  • :raise, :rescue
  • :thread_begin, :thread_end, :fiber_switch
  • :b_call, :b_return
  • :script_compiled

Enabling a TracePoint sets a per-iseq flag bit that makes the dispatch loop call into vm_trace_callback before each instruction. Disabling it clears the flag and re-optimises away the check.

VM lock and Ractors

vm_sync.c provides VM-wide barriers used to safely mutate global state (constant tables, GC roots, etc.) while other threads run. Each Ractor has its own GVL; rb_vm_barrier() stops every thread in every Ractor before performing a global change.

Object internals: shapes and embedding

Most objects are T_OBJECTs whose instance variables live inline in the RObject struct, with overflow in a separate iv_index_tbl. Each RObject's shape id (in the flags) tells the VM how to interpret the inline storage.

vm_insnhelper.c::vm_getivar/vm_setivar and the corresponding shape-aware bytecode instructions (getinstancevariable/setinstancevariable with embedded inline-cache slots) read/write IVs in O(1) once the cache warms up.

Entry points for modification

  • Add a new VM instruction: edit insns.def, regenerate (make srcs), update compile.c/prism_compile.c to emit it, update YJIT/ZJIT codegen.
  • Fix a method dispatch bug: search vm_callinfo.h/vm_method.c for the relevant vm_call_* variant. Add a regression test in test/ruby/test_method.rb or test/ruby/test_call.rb.
  • Tweak inline caches: structs are in vm_callinfo.h; the cache fill paths are in vm_method.c::rb_callable_method_entry_or_negative.
  • Stack overflow / frame mgmt: vm_check_canary and the rb_vm_check_redefinition_* family in vm_insnhelper.c.

See compiler.md for what produces the iseqs the VM runs, and jits/index.md for the alternative path that compiles iseqs to machine code.

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

Virtual machine – Ruby wiki | Factory