Stack-based execution
The VM uses an operand stack for expression evaluation. Operations pop operands from the stack and push results back:Vec<Value> in src/vm/mod.rs:94, where Value is a compact 16-byte enum representing all Walrus types.
Call frame architecture
Unlike naive interpreters that create child VMs for each function call, Walrus uses a call frame stack for memory efficiency and deep recursion support. EachCallFrame tracks (from src/vm/mod.rs:40):
return_ip: instruction pointer to return to after the function completesframe_pointer: index into the shared locals vector where this frame’s variables startstack_pointer: operand stack position when the frame started (for cleanup on return)instructions: the function’s bytecode (shared viaRcto avoid cloning)function_name: for debugging and stack tracesreturn_override: optional value for struct constructors
Example call sequence
- Setup: Arguments are pushed to the operand stack:
[5, 3] - Call opcode: VM creates a new
CallFramewithframe_pointer = locals.len() - Move arguments: Arguments drain from stack to
localsvector - Execute: VM sets
ip = 0and begins executing the function’s bytecode - Return: Result is pushed to caller’s stack, frame is popped,
iprestored
Shared locals vector
All call frames share a singleVec<Value> for local variables (src/vm/mod.rs:96). Each frame’s locals start at frame_pointer and are indexed relative to it:
- Avoids per-frame allocations
- Supports deep recursion without copying
- Allows fast frame creation/destruction
PopLocal(n) opcodes emitted by the compiler when scopes end.
Global variables
Globals are stored separately inglobals: Vec<Value> (src/vm/mod.rs:100), shared across all call frames. They’re accessed via:
LoadGlobal(index)- load global variableStoreGlobal(index)- store to global variableReassignGlobal(index)- update existing global
LoadGlobal0 through LoadGlobal3) for the first four globals to reduce instruction size.
Memory model
Heap-allocated values (strings, lists, dicts, functions) are stored in a global arena and referenced by typed keys. The VM never holds heap data directly:slotmap::DenseSlotMap for type-safe, generation-indexed access (see src/arenas.rs).
Tail call optimization
Walrus supports tail call optimization via theTailCall opcode. When the compiler detects a return statement that directly returns a function call, it emits TailCall instead of Call:
TailCall opcode (handled in src/vm/handlers/call.rs:160):
- Pops the current frame before calling
- Reuses the current frame’s slot
- Avoids stack growth for recursive calls
Garbage collection integration
The VM periodically triggers garbage collection based on allocation count and memory thresholds. GC roots are collected from:- Operand stack values
- All local variables in the shared locals vector
- All global variables
- Constants in all call frames’ instruction sets
Performance characteristics
- Frame creation: O(1) - just push to call stack
- Local variable access: O(1) - direct indexing with frame pointer
- Global variable access: O(1) - direct indexing
- Heap operations: O(1) - key-based lookup in DenseSlotMap
- Stack operations: Amortized O(1) - standard Vec operations
Source references
- VM struct definition:
src/vm/mod.rs:94 - CallFrame definition:
src/vm/mod.rs:40 - Main execution loop:
src/vm/mod.rs:447 - Call handling:
src/vm/handlers/call.rs