This document details the core runtime structures of the Ribbon Virtual Machine (RVM). While the Isa document specifies the instructions the RVM executes, this document describes the state and memory layout of the execution environment itself.
The central component of the RVM is the Fiber.
Contents
The Fiber: A Lightweight Execution Context
A Fiber encapsulates all the state required for an independent thread of execution within the RVM. Unlike OS threads, fibers are managed entirely in user space, making them extremely lightweight and efficient to create, manage, and switch between. A single OS thread can manage thousands of fibers.
Each Fiber is a single, contiguous block of memory, which allows for very fast allocation and deallocation and improves data locality.
Memory Layout
The memory for a Fiber is laid out sequentially to ensure no padding is needed between sections. The total size is typically only a few megabytes.
FiberHeader: A fixed-size structure at the beginning of the memory block that contains pointers and stack-tops for all other sections. It acts as the control panel for the fiber.- Register Stack Block: The memory region for the
RegisterStack. - Data Stack Block: The memory region for the
DataStack. - Call Stack Block: The memory region for the
CallStack. - Set Stack Block: The memory region for the
SetStack.
This contiguous layout is a key design feature, enabling high performance and simplifying the VM’s implementation.
The Fiber Header: The Control Panel
The FiberHeader is the entry point to a fiber’s state. It contains the top pointers for all stacks and the necessary metadata for execution.
Stacks
The RVM uses several distinct stacks, each with a specific purpose. These are simple stack allocators that manage regions of the fiber’s contiguous memory block.
- Register Stack (
RegisterStack): This stack holds arrays of virtual registers (RegisterArray). A newRegisterArrayis pushed for each function call, providing the function with its own set of25664-bit registers. - Data Stack (
DataStack): A general-purpose stack for operand data. It is used for values that are larger than a single register (64 bits) or values that need to be addressable in memory (e.g., via theaddr_linstruction). - Call Stack (
CallStack): This stack tracks active function calls. Each time a function is called, aCallFrameis pushed onto this stack.- A
CallFramecontains:ip: The instruction pointer, pointing to the next instruction to execute in the function’s bytecode.function: A pointer to thecore.Functionorcore.BuiltinFunctionbeing executed.vregs: A pointer to the current function’sRegisterArrayon the register stack.data: A pointer to the base of the current function’s frame on theDataStack.output: The register in the caller’s frame where the return value of this function should be stored.set_frame: A pointer to the current effect handlerSetFrame.evidence: A pointer to theEvidencestructure if this is an effect handler call.
- A
- Set Stack (
SetStack): This stack manages active effect handler sets. When apush_setinstruction is executed, aSetFrameis pushed onto this stack.- A
SetFramelinks aHandlerSetto theCallFramethat activated it. It’s used to find the correct cancellation context when an effect handler executes acancelinstruction.
- A
Effect Handling Machinery
The RVM’s static guarantees for Algebraic Effects are supported by two key runtime structures in the FiberHeader:
- Evidence Buffer: A fixed-size array (
[pl.MAX_EFFECT_TYPES]?*Evidence) that serves as a direct-access map. It is indexed by an effect’s unique ID. When apromptinstruction needs to invoke a handler for a specific effect, it can find the currentEvidencein O(1) time. Evidence: A structure that represents a live, scoped effect handler. It contains:- A pointer to the actual handler function.
- A pointer to the
SetFramethat activated this handler. - A pointer to the
previousEvidencefor the same effect, forming a linked list. When aSetFrameis popped, this allows the RVM to efficiently restore the previously active handler.
Execution Model
The core of the VM is a dispatch loop that executes the instruction pointed to by the ip of the top-most CallFrame.
- Fetch: The VM reads the 64-bit word at the
ip. - Decode: The word is split into a 16-bit opcode and 48 bits of operand data.
- Dispatch: The opcode is used to jump to the logic for that instruction.
- Execute: The instruction logic is performed, modifying the
Fiber’s state (e.g., writing to registers, changing theip, pushing to stacks). - Loop: The
ipis advanced (unless it was a jump/branch instruction), and the process repeats.
This loop continues until a halt instruction is reached, an error occurs, or (in debugging mode) a breakpoint is hit.
Function Calls
The call, call_c, and prompt instructions follow a similar procedure to create a new stack frame:
- A new
CallFrameis pushed onto theCallStack. Itsfunctionpointer is set to the target function. - The
outputregister is recorded in the new frame, indicating where the return value should be placed in the caller’s register set. - A new
RegisterArrayis pushed onto theRegisterStackand its pointer is stored in the newCallFrame’svregsfield. - Arguments are copied from the caller’s registers into the new register frame.
- The
ipin the newCallFrameis set to the entry point of the target function. - Execution transfers to the new frame.
Returning and Cancelling
-
return: Thereturninstruction pops the currentCallFrame,RegisterArray, andDataStackframe. It reads theoutputregister from the popped frame and copies the return value into that register in the new top-most frame (the caller). Execution resumes at the caller’sip. -
cancel: This instruction is only valid within an effect handler. It triggers a more significant stack unwinding. Instead of just popping one frame, it unwinds theCallStack,RegisterStack,DataStack, andSetStackuntil it reaches theSetFramethat initiated the effect. It then places the cancellation value in the designated register of that frame and jumps to thecancellation.addressdefined in theHandlerSet, effectively short-circuiting the computation back to the point after the effect was handled.