RunMat
GitHub

Interpreter Dispatch & Execution Loop

The RunMat interpreter executes the bytecode emitted by the VM compiler. It is an async instruction loop over Instr values, with runtime state held in InterpreterState and passed into the dispatch layer as a mutable DispatchState. The loop owns the program counter, value stack, variable slots, try/catch stack, semantic function resolver hooks, and optional native-acceleration fusion plan.

Runtime State

InterpreterState is created from a Bytecode program and the caller's initial variable slots. Before execution begins, the interpreter installs semantic function hooks, prepares workspace state, initializes GC roots, and activates a fusion plan when the native-accel feature is enabled.

Key Runtime Fields

FieldPurpose
bytecodeThe instruction stream, variable metadata, function registry, and fusion metadata.
stackTemporary operand stack used by bytecode instructions.
varsWorkspace or frame-local variable slots addressed by LoadVar, StoreVar, and local fallbacks.
contextCall stack, local frame storage, instruction pointer metadata, and async task tracking.
pcProgram counter into bytecode.instructions.
try_stackStack of catch targets used by try/catch bytecode.
last_exceptionLast caught exception value, used by exception-sensitive built-ins such as rethrow.

Execution Loop

The main loop in run_interpreter_inner repeatedly fetches the instruction at pc, offers eligible spans to the fusion executor, dispatches the instruction, and then applies the resulting control-flow decision.

Loading diagram...

Instruction Dispatch

dispatch_instruction receives three grouped inputs:

  • DispatchMeta: the current instruction, function registry, source metadata, call spans, and call-count context.
  • DispatchState: mutable stack, variables, context, try stack, imports, aliases, persistent state, missing-input slots, and pc.
  • DispatchHooks: callbacks used by the runner for residency cleanup and workspace or persistent-variable synchronization.

The dispatcher first gives broad instruction families to specialized modules. Indexing, object, and arithmetic handlers return true when they consume the instruction. The remaining bytecode variants are handled by the central match in dispatch/mod.rs.

Loading diagram...

Dispatch Categories

CategoryRepresentative InstructionsPrimary Handler
IndexingIndex, StoreIndex, IndexSlice, StoreSliceExprinterpreter/dispatch/indexing.rs
Arithmetic and logical opsAdd, Sub, Mul, comparisons, boolean opsinterpreter/dispatch/arithmetic.rs
Object supportObject literals and object operationsinterpreter/dispatch/object.rs
Variables and stackLoadVar, StoreVar, LoadLocal, Pop, Swapinterpreter/dispatch/mod.rs
Control flowJump, JumpIfFalse, EnterTry, Returnops/control_flow.rs
CallsBuilt-ins, semantic functions, feval, multi-output callsinterpreter/dispatch/calls.rs and call/*
AsyncSemantic futures, Spawn, Awaitinterpreter/dispatch/mod.rs

Indexed assignment instructions push the updated base value back onto the stack. Follow-up bytecode is responsible for storing that value into the target variable or local slot with StoreVar or StoreLocal.

Exception Routing

try/catch is represented directly in bytecode. EnterTry pushes a catch target onto try_stack, and PopTry removes it when the protected region exits normally. If a handler returns RuntimeError, the runner calls redirect_exception_to_catch.

When a catch target exists, the VM:

  • Pops the active catch entry.
  • Converts the runtime error into an MException.
  • Stores that exception into the optional catch variable slot.
  • Updates last_exception.
  • Sets pc to the catch handler.

If no catch entry exists, the error leaves the interpreter.

Semantic Function Hooks

Before entering the loop, the interpreter installs thread-local semantic invoker and resolver hooks backed by the bytecode FunctionRegistry. This lets the runtime call back into bytecode-defined functions from several paths: direct semantic calls, closures, feval, object dispatch, and end-expression calls inside indexing.

Those hooks are scoped by guards. Bound semantic calls depend on them being active; without the interpreter-installed invoker, a semantic function ID cannot be executed by the runtime layer.

From here, call-specific behavior is covered in Callable Resolution & Function Dispatch, and indexing-specific behavior is covered in Indexing Subsystem.