Files
regorus/tests/rvm/vm/suites/function_calls.yaml
Anand Krishnamoorthi 49bd3c22f3 feat!: add Rego Virtual Machine (RVM) implementation (#495)
* 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>
2025-11-14 11:43:19 -06:00

372 lines
16 KiB
YAML

# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Function Call Instructions Test Suite
# Tests the VM's function call mechanism with user-defined function rules
# Covers FunctionCall instruction, argument passing, and return values
cases:
# Basic Function Call Tests
- note: simple_function_call
description: Test basic function call instruction with one argument
example_rego: "add_ten(5) where add_ten(x) := x + 10"
literals:
- {}
- 5 # Argument value
- 10 # Constant 10 for addition
instruction_params:
object_create_params:
- dest: 6
template_literal_idx: 0
literal_key_fields: []
fields: []
function_call_params:
- func: 0 # Rule index for add_ten function (rule 0)
dest: 2 # Destination register for result
args: [1] # Argument registers
rule_infos:
- rule_type: Complete
definitions:
- [3] # Entry point for add_ten function body (PC 3)
instructions:
- "Load { dest: 1, literal_idx: 1 }" # Load argument 5 into register 1
- "FunctionCall { params_index: 0 }" # Call function
- "Return { value: 2 }" # Return result
# Function body starts at PC 3 (entry point)
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
- "Load { dest: 3, literal_idx: 2 }" # Load constant 10 into register 3
- "Add { dest: 0, left: 1, right: 3 }" # Add argument + 10 (result in register 0)
- "RuleReturn {}" # Return from function
want_result: 15
- note: function_call_multiple_args
description: Test function call with multiple arguments
example_rego: "add(7, 3) where add(x, y) := x + y"
literals:
- {}
- 0 # Rule index for add function
- 7 # First argument
- 3 # Second argument
instruction_params:
function_call_params:
- func: 0 # Register containing function rule index
dest: 3 # Destination register for result
args: [1, 2] # Argument registers
rule_infos:
- rule_type: Complete
definitions:
- [5] # Entry point for add function body
instructions:
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
- "Load { dest: 1, literal_idx: 2 }" # Load first argument
- "Load { dest: 2, literal_idx: 3 }" # Load second argument
- "FunctionCall { params_index: 0 }" # Call function
- "Return { value: 3 }" # Return result
# Function body
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
- "Add { dest: 0, left: 1, right: 2 }" # Add arguments (result in register 0)
- "RuleReturn {}" # Return from function
want_result: 10
- note: function_call_no_args
description: Test function call with no arguments
example_rego: "get_constant() where get_constant() := 42"
literals:
- {}
- 0 # Rule index for get_constant function
- 42 # Constant value
instruction_params:
function_call_params:
- func: 0 # Register containing function rule index
dest: 1 # Destination register for result
args: [] # No arguments
rule_infos:
- rule_type: Complete
definitions:
- [3] # Entry point for get_constant function body
instructions:
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
- "FunctionCall { params_index: 0 }" # Call function
- "Return { value: 1 }" # Return result
# Function body
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
- "Load { dest: 0, literal_idx: 2 }" # Load constant 42 (result in register 0)
- "RuleReturn {}" # Return from function
want_result: 42
- note: function_call_with_multiplication
description: Test function that performs multiplication
example_rego: "square(6) where square(x) := x * x"
literals:
- {}
- 0 # Rule index for square function
- 6 # Argument value
instruction_params:
function_call_params:
- func: 0 # Register containing function rule index
dest: 2 # Destination register for result
args: [1] # Argument registers
rule_infos:
- rule_type: Complete
definitions:
- [4] # Entry point for square function body
instructions:
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
- "Load { dest: 1, literal_idx: 2 }" # Load argument 6
- "FunctionCall { params_index: 0 }" # Call function
- "Return { value: 2 }" # Return result
# Function body
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
- "Mul { dest: 0, left: 1, right: 1 }" # Multiply x * x (result in register 0)
- "RuleReturn {}" # Return from function
want_result: 36
- note: nested_function_calls
description: Test nested function calls
example_rego: "double(add_one(5)) where double(x) := x * 2; add_one(x) := x + 1"
literals:
- {}
- 1 # Rule index for add_one function
- 0 # Rule index for double function
- 5 # Initial argument
- 1 # Constant 1
- 2 # Constant 2
instruction_params:
function_call_params:
- func: 1 # First call: add_one (rule index 1)
dest: 4 # Temporary result
args: [2] # Argument register
- func: 0 # Second call: double (rule index 0)
dest: 5 # Final result
args: [4] # Use result from first call
rule_infos:
- rule_type: Complete # double function
definitions:
- [10] # Entry point for double function body (updated)
- rule_type: Complete # add_one function
definitions:
- [6] # Entry point for add_one function body
instructions:
- "Load { dest: 0, literal_idx: 1 }" # Load add_one rule index
- "Load { dest: 1, literal_idx: 2 }" # Load double rule index
- "Load { dest: 2, literal_idx: 3 }" # Load argument 5
- "FunctionCall { params_index: 0 }" # Call add_one(5)
- "FunctionCall { params_index: 1 }" # Call double(result)
- "Return { value: 5 }" # Return final result
# add_one function body (starts at PC 6)
- "RuleInit { result_reg: 0, rule_index: 1 }" # Initialize result register for add_one
- "Load { dest: 3, literal_idx: 4 }" # Load constant 1
- "Add { dest: 0, left: 1, right: 3 }" # x + 1 (result in register 0)
- "RuleReturn {}" # Return result
# double function body (starts at PC 10)
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register for double
- "Load { dest: 3, literal_idx: 5 }" # Load constant 2
- "Mul { dest: 0, left: 1, right: 3 }" # x * 2 (result in register 0)
- "RuleReturn {}" # Return result
want_result: 12
- note: function_call_undefined_result
description: Test function call that returns undefined due to failed condition
example_rego: "safe_div(1, 0) where safe_div(x, y) := x / y if y != 0"
literals:
- {}
- 0 # Rule index for safe_div function
- 1 # Numerator
- 0 # Denominator (zero)
instruction_params:
function_call_params:
- func: 0 # Register containing function rule index
dest: 3 # Destination register for result
args: [1, 2] # Argument registers
rule_infos:
- rule_type: Complete
definitions:
- [5] # Entry point for safe_div function body
instructions:
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
- "Load { dest: 1, literal_idx: 2 }" # Load numerator
- "Load { dest: 2, literal_idx: 3 }" # Load denominator
- "FunctionCall { params_index: 0 }" # Call function
- "Return { value: 3 }" # Return result
# Function body with condition that fails
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
- "LoadFalse { dest: 4 }" # Condition fails (simulated y == 0)
- "AssertCondition { condition: 4 }" # Assert fails, body fails
- "Div { dest: 0, left: 1, right: 2 }" # This won't execute
- "RuleReturn {}" # This won't execute
want_result: "#undefined"
- note: function_call_with_object_result
description: Test function call that returns an object
example_rego: "make_pair(1, 2) where make_pair(x, y) := {\"first\": x, \"second\": y}"
literals:
- {}
- 0 # Rule index for make_pair function
- 1 # First value
- 2 # Second value
- "first" # Key for first element
- "second" # Key for second element
instruction_params:
object_create_params:
- dest: 6
template_literal_idx: 0
literal_key_fields: []
fields: []
function_call_params:
- func: 0 # Register containing function rule index
dest: 5 # Destination register for result
args: [1, 2] # Argument registers
rule_infos:
- rule_type: Complete
definitions:
- [5] # Entry point for make_pair function body
instructions:
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
- "Load { dest: 1, literal_idx: 2 }" # Load first value
- "Load { dest: 2, literal_idx: 3 }" # Load second value
- "FunctionCall { params_index: 0 }" # Call function
- "Return { value: 5 }" # Return result
# Function body
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
- "ObjectCreate { params_index: 0 }"
- "Load { dest: 3, literal_idx: 4 }" # Load "first" key
- "ObjectSet { obj: 6, key: 3, value: 1 }" # Set first: x
- "Load { dest: 4, literal_idx: 5 }" # Load "second" key
- "ObjectSet { obj: 6, key: 4, value: 2 }" # Set second: y
- "Move { dest: 0, src: 6 }" # Move object to result register
- "RuleReturn {}" # Return object
want_result: {"first": 1, "second": 2}
- note: function_call_with_array_result
description: Test function call that returns an array
example_rego: "make_range(3, 5) where make_range(start, end) := [start, end]"
literals:
- {}
- 0 # Rule index for make_range function
- 3 # Start value
- 5 # End value
instruction_params:
function_call_params:
- func: 0 # Register containing function rule index
dest: 3 # Destination register for result
args: [1, 2] # Argument registers
rule_infos:
- rule_type: Complete
definitions:
- [5] # Entry point for make_range function body
instructions:
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
- "Load { dest: 1, literal_idx: 2 }" # Load start value
- "Load { dest: 2, literal_idx: 3 }" # Load end value
- "FunctionCall { params_index: 0 }" # Call function
- "Return { value: 3 }" # Return result
# Function body
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
- "ArrayNew { dest: 4 }" # Create new array
- "ArrayPush { arr: 4, value: 1 }" # Push start value
- "ArrayPush { arr: 4, value: 2 }" # Push end value
- "Move { dest: 0, src: 4 }" # Move array to result register
- "RuleReturn {}" # Return array
want_result: [3, 5]
- note: function_call_with_comparison
description: Test function that performs comparison
example_rego: "max(7, 3) where max(x, y) := x if x >= y"
literals:
- {}
- 0 # Rule index for max function
- 7 # First value
- 3 # Second value
instruction_params:
function_call_params:
- func: 0 # Register containing function rule index
dest: 3 # Destination register for result
args: [1, 2] # Argument registers
rule_infos:
- rule_type: Complete
definitions:
- [5] # Entry point for max function body
instructions:
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
- "Load { dest: 1, literal_idx: 2 }" # Load first value (7)
- "Load { dest: 2, literal_idx: 3 }" # Load second value (3)
- "FunctionCall { params_index: 0 }" # Call function
- "Return { value: 3 }" # Return result
# Function body: return x if x >= y
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
- "Ge { dest: 4, left: 1, right: 2 }" # x >= y
- "AssertCondition { condition: 4 }" # Assert condition
- "Move { dest: 0, src: 1 }" # Return x (move to result register)
- "RuleReturn {}" # Return result
want_result: 7
- note: function_call_three_args
description: Test function call with three arguments
example_rego: "sum_three(2, 3, 4) where sum_three(a, b, c) := a + b + c"
literals:
- {}
- 0 # Rule index for sum_three function
- 2 # First argument
- 3 # Second argument
- 4 # Third argument
instruction_params:
function_call_params:
- func: 0 # Register containing function rule index
dest: 4 # Destination register for result
args: [1, 2, 3] # Argument registers
rule_infos:
- rule_type: Complete
definitions:
- [6] # Entry point for sum_three function body
instructions:
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
- "Load { dest: 1, literal_idx: 2 }" # Load first argument (2)
- "Load { dest: 2, literal_idx: 3 }" # Load second argument (3)
- "Load { dest: 3, literal_idx: 4 }" # Load third argument (4)
- "FunctionCall { params_index: 0 }" # Call function
- "Return { value: 4 }" # Return result
# Function body: a + b + c
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
- "Add { dest: 5, left: 1, right: 2 }" # a + b
- "Add { dest: 0, left: 5, right: 3 }" # (a + b) + c (result in register 0)
- "RuleReturn {}" # Return result
want_result: 9
- note: function_call_inconsistent_definitions
description: Test function with multiple definitions that produce different values (should fail)
example_rego: "f(5) where f(x) := x + 1; f(x) := x + 2"
literals:
- {}
- 0 # Rule index for f function
- 5 # Argument value
- 1 # Constant 1
- 2 # Constant 2
instruction_params:
function_call_params:
- func: 0 # Register containing function rule index
dest: 2 # Destination register for result
args: [1] # Argument registers
rule_infos:
- rule_type: Complete
definitions:
- [4] # First definition: x + 1
- [8] # Second definition: x + 2 (should produce different result)
entry_points:
"data.test.compute": [4, 8] # Multiple definitions for the same function
instructions:
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
- "Load { dest: 1, literal_idx: 2 }" # Load argument (5)
- "FunctionCall { params_index: 0 }" # Call function
- "Return { value: 2 }" # Return result
# First definition: x + 1 (entry point 4)
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
- "Load { dest: 3, literal_idx: 3 }" # Load constant 1
- "Add { dest: 0, left: 1, right: 3 }" # x + 1 = 6 (result in register 0)
- "RuleReturn {}" # Return result
# Second definition: x + 2 (entry point 8)
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
- "Load { dest: 4, literal_idx: 4 }" # Load constant 2
- "Add { dest: 0, left: 1, right: 4 }" # x + 2 = 7 (result in register 0)
- "RuleReturn {}" # Return result
want_result: "#undefined"