* feat!: add Rego Virtual Machine (RVM) implementation This commit introduces a register-based virtual machine for executing Rego policies with bytecode-style instructions. Unlike the existing tree-walking interpreter, the RVM compiles policies into instruction sequences that operate on virtual registers, offering better performance and optimization potential. Core Components: Instruction Set Architecture: - Define instruction types for data operations, control flow, and builtins - Implement instruction parameter encoding and display formatting - Add instruction parser with comprehensive test coverage Virtual Machine Engine: - Register-based execution model with program counter management - Loop execution supporting iterators, comprehensions, and quantifiers - Function call handling with argument evaluation and context management - Rule evaluation with default value resolution and virtual data support - Arithmetic and comparison operation implementations Program Representation: - Program listing builder with instruction sequencing - Rule tree construction for organizing policy rules - Binary and JSON serialization for compiled programs - Recompilation support for program modification Testing Infrastructure: - Extensive YAML test suites covering all VM features - Rust unit tests for VM execution and instruction parsing - Test suites for loops, comprehensions, builtins, and control flow BREAKING CHANGE: Introduces new VM execution path alongside interpreter Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com> * docs: add detailed RVM architecture references Introduce architecture.md explaining program artifacts, serialization, and runtime subsystems. Document the full opcode catalog in instruction-set.md, including operands, parameter tables, and outcomes. Walk through execution flow, stacks, and operational guidance in vm-runtime.md, tying the runtime to the new architecture docs. Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com> --------- Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
12 KiB
VM Runtime Walkthrough
This document explains the runtime architecture implemented under
src/rvm/vm. It focuses on the RegoVM struct, execution modes, and the
responsibilities of each support module.
1. RegoVM structure
src/rvm/vm/machine.rs defines the public entry point. The table below maps its
fields to responsibilities.
| Field | Purpose | Related modules |
|---|---|---|
registers: Vec<Value> |
Active register window for the current frame. | execution.rs, dispatch.rs |
pc: usize |
Instruction pointer in run-to-completion mode. | execution.rs |
program: Arc<Program> |
Loaded program artifact. | program/core.rs |
compiled_policy |
Optional legacy default-rule support. | crate::CompiledPolicy |
rule_cache: Vec<(bool, Value)> |
Memoized rule results (bool = computed). | rules.rs |
data, input |
Global documents injected by host. | dispatch.rs, virtual_data.rs |
loop_stack |
Stack of LoopContext for run-to-completion loops. |
loops.rs |
call_rule_stack |
Stack of CallRuleContext for nested rule calls. |
rules.rs |
register_stack |
Saves prior register windows during run-to-completion rule calls. | rules.rs, state.rs |
comprehension_stack |
Active ComprehensionContext objects. |
comprehension.rs |
base_register_count |
Root window size derived from program metadata. | load_program |
register_window_pool |
Recycled register vectors to reduce allocations. | state.rs, rules.rs |
max_instructions, executed_instructions |
Instruction budget and counter. | execution.rs |
evaluated |
Cache for virtual document lookups. | virtual_data.rs |
cache_hits |
Counters aiding diagnostics. | virtual_data.rs |
execution_stack |
Explicit frame stack for suspendable mode. | execution_model.rs |
execution_state |
ExecutionState enum capturing Ready/Running/Suspended/Error/Completed. |
execution_model.rs, execution.rs |
breakpoints |
Set of PCs that trigger suspension. | execution_model.rs |
step_mode |
Enables single-step suspension after each instruction. | execution.rs |
host_await_responses |
Pre-scripted responses keyed by identifier (run-to-completion). | dispatch.rs |
execution_mode |
RunToCompletion or Suspendable. |
execution.rs |
frame_pc_overridden |
Tracks manual PC updates inside frames. | execution.rs, loops.rs, comprehension.rs |
strict_builtin_errors |
Configures builtin failure handling (error vs undefined). |
machine.rs, arithmetic.rs, dispatch.rs |
Key methods
new/new_with_policy: initialise VM with default register windows and instruction limits.load_program: attaches a compiledProgram, resizes registers, seeds rule cache and resets counters.set_data/set_input: inject host documents.set_datarunsProgram::check_rule_data_conflictsto guard against rule/data collisions.set_max_instructions,set_execution_mode,set_step_mode: configure runtime policy.set_host_await_responses: used in run-to-completion mode when host await responses are known ahead of time.set_strict_builtin_errors: toggles builtin failure semantics betweenVmError::ArithmeticErrorand returningValue::Undefined.- Accessors (
get_pc,get_registers,get_loop_stack, etc.) aid debugging and visualisation tooling.
2. Execution modes
Run-to-completion
- Entry path:
execute()orexecute_entry_point_by_*whenExecutionMode::RunToCompletion. execute_run_to_completionresets state, marksExecutionState::Runningand callsjump_to(start_pc).jump_toloops over instructions, updatingpcand callingexecute_instruction. The loop stops onReturn,Break, orVmError.Break(emitted byRuleReturnandDestructuringSuccess) returns register 0 to the caller for compatibility with rule evaluation.- Suspension is not allowed; encountering an instruction that would suspend
(e.g.
HostAwait) raises an internal error. - Instruction budgets trigger
VmError::InstructionLimitExceededand switch the state toExecutionState::Error.
Suspendable
- Entry path: same public API, but the VM calls
run_stackless_from. run_stackless_frompushes an initialExecutionFrame::main(start_pc, 0)ontoexecution_stackand dispatches instructions viarun_stackless_loop.- Each frame tracks its own
pcandFrameKind(Main,Rule,Loop,Comprehension). RuleFrameDatacarries scheduling cursors, register window sizing, and saved copies of the caller's registers and stacks so finalisation can restore the original context.- Suspension:
InstructionOutcome::Suspendrecords aSuspendReason(host await, breakpoint, step) and stores the last result snapshot. Host code callsresume(resume_value)to continue. - Completion: when
execution_stackbecomes empty the VM setsExecutionState::Completed { result }.
execution_model.rs defines the frame types and state machine:
ExecutionFrame: captures the frame-localpctogether with itsFrameKindpayload.RuleFrameData: tracks rule index, scheduling phase, register window sizing, and the saved caller state (saved_registers,saved_loop_stack,saved_comprehension_stack).SuspendReason: currently surfaced values are host await, breakpoint, and step; additional variants (SuspendInstruction,InstructionLimit,External) are reserved for future instructions.
3. Instruction dispatch
dispatch.rs routes each Instruction variant through layered helpers
(execute_load_and_move, execute_arithmetic_instruction, execute_call_instruction, etc.). Control
flow hinges on the InstructionOutcome enum:
Continue: normal execution; the caller increments the framepc.Return(Value): unwinds the current rule/function frame, propagating the value upward.Break: used for rule-specific constructs (destructuring success, rule return) to exit to the owning frame without returning a value.Suspend { reason }: used exclusively in suspendable mode.
Arithmetic and comparison opcodes live in arithmetic.rs, honouring
strict_builtin_errors when operand types differ. Collection, loop, and
virtual-data operations share helpers that convert Value variants with runtime
type checking. Errors become VmError variants to ensure consistent reporting.
Halt returns the value stored in register 0, allowing bytecode to terminate
early without suspending.
4. Loops and comprehensions
loops.rs implements iteration. Major components:
LoopContext: stores iteration state (IterationStateenum), key/value/result registers, body and exit PCs, counters, and loop mode.IterationState: variants for arrays, objects, sets. Tracks progress for both execution modes.LoopMode:Any,Every,ForEachcontrols short-circuit behaviour.
execute_loop_start initialises iteration, pushing the context onto
loop_stack (run-to-completion) or embedding it into a FrameKind::Loop
(suspendable). LoopParams carries the bytecode offsets, registers, and
destinations required by the instruction. LoopNext evaluates the previous
iteration outcome, updates success_count, advances the iterator, overrides
the caller's PC when needed, and decides whether to continue or exit.
comprehension.rs parallels loops.rs but maintains builder collections in
ComprehensionContext. The context stores:
- Builder value (array, set, object).
- Pending key/value registers.
body_start/comprehension_endPCs.iteration_statefor nested loops bound to the comprehension.
ComprehensionYield writes to the builder, respecting set uniqueness and object
key/value pairing. ComprehensionEnd publishes the result to result_reg and
pops the context.
5. Rule execution and caching
rules.rs and functions.rs coordinate rule calls:
execute_call_ruledispatches based on execution mode. Both paths consultrule_cacheand short-circuit if the result is already available.- Run-to-completion (
execute_call_rule_common): swaps the active register, loop, and comprehension stacks; pushes them ontoregister_stack; and drives bodies viajump_to. Successful results are cached for non-function rules. - Suspendable (
execute_call_rule_suspendable): builds aRuleFrameDatacontaining saved registers/stacks and pushes aFrameKind::Ruleso the stackless loop can schedule destructuring, bodies, and finalisation. execute_rule_initwrites the rule result register toValue::Undefined(or initialises sets/objects) before running the bodies and records the result register in the activeCallRuleContext.execute_rule_returnlets the scheduler finalise the frame and propagate the cached value to the caller.functions.rs::execute_function_callprepares argument registers and delegates toexecute_call_rule*, enforcing arity viaBuiltinInfometadata.
Rule destructuring relies on CallRuleContext,
RuleFramePhase::ExecutingDestructuring, and the DestructuringSuccess
instruction to detect when pattern matching succeeded before entering the body.
6. Virtual data lookups
virtual_data.rs implements VirtualDataDocumentLookup and caches intermediate
path results in the VM's evaluated field (using Value::Undefined as a
sentinel) to avoid repeated rule evaluations. The compiler may set
Program::needs_runtime_recursion_check; the runtime currently relies on the
instruction budget and caching to prevent runaway recursion. Paths consist of
literals and register values supplied via VirtualDataDocumentLookupParams.
ChainedIndex follows a similar pattern but operates on register roots instead
of the global data namespace.
7. Error handling and diagnostics
errors.rs defines the VmError enum. Common variants include:
InstructionLimitExceededLiteralIndexOutOfBoundsRegisterNotArray/RegisterNotObjectInvalidEntryPointIndex/EntryPointNotFoundArithmeticErrorRuleDataConflictHostAwaitResponseMissingInternal(String)for invariant violations
execution.rs::handle_instruction_error centralises error propagation. In
suspendable mode it unwinds frames while preserving partial results where
possible. Run-to-completion mode returns the error immediately.
state.rs provides helpers to reset the VM and emit debug snapshots used in
assertion messages. When an internal invariant fails, the error message includes
self.get_debug_state() to help diagnose the issue.
8. Operational guidance
- Instruction budgets: adjust via
set_max_instructionswhen running untrusted policies. Inspectexecuted_instructionsafter completion. - Breakpoints & stepping: populate
breakpointswith bytecode PCs (see the assembly listing) and enableset_step_mode(true)to pause after each instruction. - Host await: in run-to-completion mode, configure
set_host_await_responsesbefore execution. In suspendable mode, expectExecutionState::Suspended { reason: HostAwait { .. } }and resume with the chosen value. - Builtin strictness:
set_strict_builtin_errors(true)reports type mismatches asVmError::ArithmeticError; leave itfalseto coerce results toValue::Undefined. - State inspection: use getters (
get_registers,get_call_stack,get_loop_stack,get_cache_hits) to instrument evaluation or build debugging UIs.get_debug_state()provides a concise snapshot for logs. - Testing: YAML suites under
tests/rvm/vm/suitesexercise loops, comprehensions, virtual data, host awaits, and serialization.complex.yamlcombines nested loops, comprehensions, function calls, and host awaits.