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], wherelabels[]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:
- The VM compares the receiver's class to
cc->klass. - If equal → use
cc->cmedirectly. Constant-time call. - 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_missingfallback.- 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 addrleave 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_tallocation 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), updatecompile.c/prism_compile.cto emit it, update YJIT/ZJIT codegen. - Fix a method dispatch bug: search
vm_callinfo.h/vm_method.cfor the relevantvm_call_*variant. Add a regression test intest/ruby/test_method.rbortest/ruby/test_call.rb. - Tweak inline caches: structs are in
vm_callinfo.h; the cache fill paths are invm_method.c::rb_callable_method_entry_or_negative. - Stack overflow / frame mgmt:
vm_check_canaryand therb_vm_check_redefinition_*family invm_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.