mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
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>
This commit is contained in:
committed by
GitHub
parent
6dc505c88b
commit
49bd3c22f3
@@ -38,6 +38,8 @@ mod policy_info;
|
||||
mod query;
|
||||
#[cfg(feature = "azure_policy")]
|
||||
pub mod registry;
|
||||
#[cfg(feature = "rvm")]
|
||||
pub mod rvm;
|
||||
mod scheduler;
|
||||
#[cfg(feature = "azure_policy")]
|
||||
mod schema;
|
||||
|
||||
280
src/rvm/instructions/display.rs
Normal file
280
src/rvm/instructions/display.rs
Normal file
@@ -0,0 +1,280 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use alloc::format;
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::{Instruction, InstructionData, LiteralOrRegister};
|
||||
|
||||
impl Instruction {
|
||||
/// Get detailed display string with parameter resolution for debugging
|
||||
pub fn display_with_params(&self, instruction_data: &InstructionData) -> String {
|
||||
match self {
|
||||
Instruction::LoopStart { params_index } => {
|
||||
if let Some(params) = instruction_data.get_loop_params(*params_index) {
|
||||
format!(
|
||||
"LOOP_START {:?} R({}) R({}) R({}) R({}) {} {}",
|
||||
params.mode,
|
||||
params.collection,
|
||||
params.key_reg,
|
||||
params.value_reg,
|
||||
params.result_reg,
|
||||
params.body_start,
|
||||
params.loop_end
|
||||
)
|
||||
} else {
|
||||
format!("LOOP_START P({}) [INVALID INDEX]", params_index)
|
||||
}
|
||||
}
|
||||
Instruction::BuiltinCall { params_index } => {
|
||||
if let Some(params) = instruction_data.get_builtin_call_params(*params_index) {
|
||||
let args_str = params
|
||||
.arg_registers()
|
||||
.iter()
|
||||
.map(|&r| format!("R({})", r))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
format!(
|
||||
"BUILTIN_CALL R({}) B({}) [{}]",
|
||||
params.dest, params.builtin_index, args_str
|
||||
)
|
||||
} else {
|
||||
format!("BUILTIN_CALL P({}) [INVALID INDEX]", params_index)
|
||||
}
|
||||
}
|
||||
Instruction::HostAwait { dest, arg, id } => {
|
||||
format!("HOST_AWAIT R({}) R({}) R({})", dest, arg, id)
|
||||
}
|
||||
Instruction::FunctionCall { params_index } => {
|
||||
if let Some(params) = instruction_data.get_function_call_params(*params_index) {
|
||||
let args_str = params
|
||||
.arg_registers()
|
||||
.iter()
|
||||
.map(|&r| format!("R({})", r))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
format!(
|
||||
"FUNCTION_CALL R({}) RULE({}) [{}]",
|
||||
params.dest, params.func_rule_index, args_str
|
||||
)
|
||||
} else {
|
||||
format!("FUNCTION_CALL P({}) [INVALID INDEX]", params_index)
|
||||
}
|
||||
}
|
||||
Instruction::ObjectCreate { params_index } => {
|
||||
if let Some(params) = instruction_data.get_object_create_params(*params_index) {
|
||||
let mut field_parts = Vec::new();
|
||||
|
||||
// Add literal key fields
|
||||
for &(literal_idx, value_reg) in params.literal_key_field_pairs() {
|
||||
field_parts.push(format!("L({}):R({})", literal_idx, value_reg));
|
||||
}
|
||||
|
||||
// Add non-literal key fields
|
||||
for &(key_reg, value_reg) in params.field_pairs() {
|
||||
field_parts.push(format!("R({}):R({})", key_reg, value_reg));
|
||||
}
|
||||
|
||||
let fields_str = field_parts.join(" ");
|
||||
format!(
|
||||
"OBJECT_CREATE R({}) L({}) [{}]",
|
||||
params.dest, params.template_literal_idx, fields_str
|
||||
)
|
||||
} else {
|
||||
format!("OBJECT_CREATE P({}) [INVALID INDEX]", params_index)
|
||||
}
|
||||
}
|
||||
Instruction::VirtualDataDocumentLookup { params_index } => {
|
||||
if let Some(params) =
|
||||
instruction_data.get_virtual_data_document_lookup_params(*params_index)
|
||||
{
|
||||
let components_str = params
|
||||
.path_components
|
||||
.iter()
|
||||
.map(|comp| match comp {
|
||||
LiteralOrRegister::Literal(idx) => format!("L({})", idx),
|
||||
LiteralOrRegister::Register(reg) => format!("R({})", reg),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(".");
|
||||
format!(
|
||||
"VIRTUAL_DATA_DOCUMENT_LOOKUP R({}) [data.{}]",
|
||||
params.dest, components_str
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"VIRTUAL_DATA_DOCUMENT_LOOKUP P({}) [INVALID INDEX]",
|
||||
params_index
|
||||
)
|
||||
}
|
||||
}
|
||||
Instruction::ComprehensionBegin { params_index } => {
|
||||
if let Some(params) = instruction_data.get_comprehension_begin_params(*params_index)
|
||||
{
|
||||
format!(
|
||||
"COMPREHENSION_BEGIN {:?} R({}) R({}) R({}) {} {}",
|
||||
params.mode,
|
||||
params.collection_reg,
|
||||
params.key_reg,
|
||||
params.value_reg,
|
||||
params.body_start,
|
||||
params.comprehension_end
|
||||
)
|
||||
} else {
|
||||
format!("COMPREHENSION_BEGIN P({}) [INVALID INDEX]", params_index)
|
||||
}
|
||||
}
|
||||
_ => self.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for Instruction {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
let text = match self {
|
||||
Instruction::Load { dest, literal_idx } => {
|
||||
format!("LOAD R({}) L({})", dest, literal_idx)
|
||||
}
|
||||
Instruction::LoadTrue { dest } => format!("LOAD_TRUE R({})", dest),
|
||||
Instruction::LoadFalse { dest } => format!("LOAD_FALSE R({})", dest),
|
||||
Instruction::LoadNull { dest } => format!("LOAD_NULL R({})", dest),
|
||||
Instruction::LoadBool { dest, value } => format!("LOAD_BOOL R({}) {}", dest, value),
|
||||
Instruction::LoadData { dest } => format!("LOAD_DATA R({})", dest),
|
||||
Instruction::LoadInput { dest } => format!("LOAD_INPUT R({})", dest),
|
||||
Instruction::Move { dest, src } => format!("MOVE R({}) R({})", dest, src),
|
||||
Instruction::Add { dest, left, right } => {
|
||||
format!("ADD R({}) R({}) R({})", dest, left, right)
|
||||
}
|
||||
Instruction::Sub { dest, left, right } => {
|
||||
format!("SUB R({}) R({}) R({})", dest, left, right)
|
||||
}
|
||||
Instruction::Mul { dest, left, right } => {
|
||||
format!("MUL R({}) R({}) R({})", dest, left, right)
|
||||
}
|
||||
Instruction::Div { dest, left, right } => {
|
||||
format!("DIV R({}) R({}) R({})", dest, left, right)
|
||||
}
|
||||
Instruction::Mod { dest, left, right } => {
|
||||
format!("MOD R({}) R({}) R({})", dest, left, right)
|
||||
}
|
||||
Instruction::Eq { dest, left, right } => {
|
||||
format!("EQ R({}) R({}) R({})", dest, left, right)
|
||||
}
|
||||
Instruction::Ne { dest, left, right } => {
|
||||
format!("NE R({}) R({}) R({})", dest, left, right)
|
||||
}
|
||||
Instruction::Lt { dest, left, right } => {
|
||||
format!("LT R({}) R({}) R({})", dest, left, right)
|
||||
}
|
||||
Instruction::Le { dest, left, right } => {
|
||||
format!("LE R({}) R({}) R({})", dest, left, right)
|
||||
}
|
||||
Instruction::Gt { dest, left, right } => {
|
||||
format!("GT R({}) R({}) R({})", dest, left, right)
|
||||
}
|
||||
Instruction::Ge { dest, left, right } => {
|
||||
format!("GE R({}) R({}) R({})", dest, left, right)
|
||||
}
|
||||
Instruction::And { dest, left, right } => {
|
||||
format!("AND R({}) R({}) R({})", dest, left, right)
|
||||
}
|
||||
Instruction::Or { dest, left, right } => {
|
||||
format!("OR R({}) R({}) R({})", dest, left, right)
|
||||
}
|
||||
Instruction::Not { dest, operand } => {
|
||||
format!("NOT R({}) R({})", dest, operand)
|
||||
}
|
||||
Instruction::BuiltinCall { params_index } => {
|
||||
format!("BUILTIN_CALL P({})", params_index)
|
||||
}
|
||||
Instruction::HostAwait { dest, arg, id } => {
|
||||
format!("HOST_AWAIT R({}) R({}) R({})", dest, arg, id)
|
||||
}
|
||||
Instruction::FunctionCall { params_index } => {
|
||||
format!("FUNCTION_CALL P({})", params_index)
|
||||
}
|
||||
Instruction::Return { value } => format!("RETURN R({})", value),
|
||||
Instruction::ObjectSet { obj, key, value } => {
|
||||
format!("OBJECT_SET R({}) R({}) R({})", obj, key, value)
|
||||
}
|
||||
Instruction::ObjectCreate { params_index } => {
|
||||
format!("OBJECT_CREATE P({})", params_index)
|
||||
}
|
||||
Instruction::Index {
|
||||
dest,
|
||||
container,
|
||||
key,
|
||||
} => format!("INDEX R({}) R({}) R({})", dest, container, key),
|
||||
Instruction::IndexLiteral {
|
||||
dest,
|
||||
container,
|
||||
literal_idx,
|
||||
} => format!(
|
||||
"INDEX_LITERAL R({}) R({}) L({})",
|
||||
dest, container, literal_idx
|
||||
),
|
||||
Instruction::ChainedIndex { params_index } => {
|
||||
format!("CHAINED_INDEX P({})", params_index)
|
||||
}
|
||||
Instruction::ArrayNew { dest } => format!("ARRAY_NEW R({})", dest),
|
||||
Instruction::ArrayPush { arr, value } => format!("ARRAY_PUSH R({}) R({})", arr, value),
|
||||
Instruction::ArrayCreate { params_index } => {
|
||||
format!("ARRAY_CREATE P({})", params_index)
|
||||
}
|
||||
Instruction::SetNew { dest } => format!("SET_NEW R({})", dest),
|
||||
Instruction::SetAdd { set, value } => format!("SET_ADD R({}) R({})", set, value),
|
||||
Instruction::SetCreate { params_index } => {
|
||||
format!("SET_CREATE P({})", params_index)
|
||||
}
|
||||
Instruction::Contains {
|
||||
dest,
|
||||
collection,
|
||||
value,
|
||||
} => format!("CONTAINS R({}) R({}) R({})", dest, collection, value),
|
||||
Instruction::Count { dest, collection } => {
|
||||
format!("COUNT R({}) R({})", dest, collection)
|
||||
}
|
||||
Instruction::AssertCondition { condition } => {
|
||||
format!("ASSERT_CONDITION R({})", condition)
|
||||
}
|
||||
Instruction::AssertNotUndefined { register } => {
|
||||
format!("ASSERT_NOT_UNDEFINED R({})", register)
|
||||
}
|
||||
Instruction::LoopStart { params_index } => {
|
||||
format!("LOOP_START P({})", params_index)
|
||||
}
|
||||
Instruction::LoopNext {
|
||||
body_start,
|
||||
loop_end,
|
||||
} => {
|
||||
format!("LOOP_NEXT {} {}", body_start, loop_end)
|
||||
}
|
||||
Instruction::CallRule { dest, rule_index } => {
|
||||
format!("CALL_RULE R({}) {}", dest, rule_index)
|
||||
}
|
||||
Instruction::VirtualDataDocumentLookup { params_index } => {
|
||||
format!("VIRTUAL_DATA_DOCUMENT_LOOKUP P({})", params_index)
|
||||
}
|
||||
Instruction::DestructuringSuccess {} => String::from("DESTRUCTURING_SUCCESS"),
|
||||
Instruction::RuleReturn {} => String::from("RULE_RETURN"),
|
||||
|
||||
Instruction::RuleInit {
|
||||
result_reg,
|
||||
rule_index,
|
||||
} => {
|
||||
format!("RULE_INIT R({}) {}", result_reg, rule_index)
|
||||
}
|
||||
Instruction::Halt {} => String::from("HALT"),
|
||||
Instruction::ComprehensionBegin { params_index } => {
|
||||
format!("COMPREHENSION_BEGIN P({})", params_index)
|
||||
}
|
||||
Instruction::ComprehensionYield { value_reg, key_reg } => match key_reg {
|
||||
Some(k) => format!("COMPREHENSION_YIELD R({}) R({})", k, value_reg),
|
||||
None => format!("COMPREHENSION_YIELD R({})", value_reg),
|
||||
},
|
||||
Instruction::ComprehensionEnd {} => String::from("COMPREHENSION_END"),
|
||||
};
|
||||
write!(f, "{}", text)
|
||||
}
|
||||
}
|
||||
381
src/rvm/instructions/mod.rs
Normal file
381
src/rvm/instructions/mod.rs
Normal file
@@ -0,0 +1,381 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
mod display;
|
||||
mod params;
|
||||
mod types;
|
||||
|
||||
pub use params::{
|
||||
ArrayCreateParams, BuiltinCallParams, ChainedIndexParams, ComprehensionBeginParams,
|
||||
FunctionCallParams, InstructionData, LoopStartParams, ObjectCreateParams, SetCreateParams,
|
||||
VirtualDataDocumentLookupParams,
|
||||
};
|
||||
pub use types::{ComprehensionMode, LiteralOrRegister, LoopMode};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// RVM Instructions - simplified enum-based design
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum Instruction {
|
||||
/// Load literal value from literal table into register
|
||||
Load {
|
||||
dest: u8,
|
||||
literal_idx: u16,
|
||||
},
|
||||
|
||||
/// Load true value into register
|
||||
LoadTrue {
|
||||
dest: u8,
|
||||
},
|
||||
|
||||
/// Load false value into register
|
||||
LoadFalse {
|
||||
dest: u8,
|
||||
},
|
||||
|
||||
/// Load null value into register
|
||||
LoadNull {
|
||||
dest: u8,
|
||||
},
|
||||
|
||||
/// Load boolean value into register
|
||||
LoadBool {
|
||||
dest: u8,
|
||||
value: bool,
|
||||
},
|
||||
|
||||
/// Load global data object into register
|
||||
LoadData {
|
||||
dest: u8,
|
||||
},
|
||||
|
||||
/// Load global input object into register
|
||||
LoadInput {
|
||||
dest: u8,
|
||||
},
|
||||
|
||||
/// Move value from one register to another
|
||||
Move {
|
||||
dest: u8,
|
||||
src: u8,
|
||||
},
|
||||
|
||||
/// Arithmetic operations
|
||||
Add {
|
||||
dest: u8,
|
||||
left: u8,
|
||||
right: u8,
|
||||
},
|
||||
Sub {
|
||||
dest: u8,
|
||||
left: u8,
|
||||
right: u8,
|
||||
},
|
||||
Mul {
|
||||
dest: u8,
|
||||
left: u8,
|
||||
right: u8,
|
||||
},
|
||||
Div {
|
||||
dest: u8,
|
||||
left: u8,
|
||||
right: u8,
|
||||
},
|
||||
Mod {
|
||||
dest: u8,
|
||||
left: u8,
|
||||
right: u8,
|
||||
},
|
||||
|
||||
/// Comparison operations
|
||||
Eq {
|
||||
dest: u8,
|
||||
left: u8,
|
||||
right: u8,
|
||||
},
|
||||
Ne {
|
||||
dest: u8,
|
||||
left: u8,
|
||||
right: u8,
|
||||
},
|
||||
Lt {
|
||||
dest: u8,
|
||||
left: u8,
|
||||
right: u8,
|
||||
},
|
||||
Le {
|
||||
dest: u8,
|
||||
left: u8,
|
||||
right: u8,
|
||||
},
|
||||
Gt {
|
||||
dest: u8,
|
||||
left: u8,
|
||||
right: u8,
|
||||
},
|
||||
Ge {
|
||||
dest: u8,
|
||||
left: u8,
|
||||
right: u8,
|
||||
},
|
||||
|
||||
/// Logical operations
|
||||
And {
|
||||
dest: u8,
|
||||
left: u8,
|
||||
right: u8,
|
||||
},
|
||||
Or {
|
||||
dest: u8,
|
||||
left: u8,
|
||||
right: u8,
|
||||
},
|
||||
Not {
|
||||
dest: u8,
|
||||
operand: u8,
|
||||
},
|
||||
|
||||
/// Builtin function calls - optimized for builtin functions
|
||||
BuiltinCall {
|
||||
/// Index into program's instruction_data.builtin_call_params table
|
||||
params_index: u16,
|
||||
},
|
||||
|
||||
/// Suspend execution and yield control to the host
|
||||
HostAwait {
|
||||
/// Destination register to store the resume value
|
||||
dest: u8,
|
||||
/// Register containing the value to pass to the host
|
||||
arg: u8,
|
||||
/// Register containing a unique identifier for this await site
|
||||
id: u8,
|
||||
},
|
||||
|
||||
/// Function rule calls - for user-defined function rules
|
||||
FunctionCall {
|
||||
/// Index into program's instruction_data.function_call_params table
|
||||
params_index: u16,
|
||||
},
|
||||
|
||||
/// Return result
|
||||
Return {
|
||||
value: u8,
|
||||
},
|
||||
|
||||
/// Set object field
|
||||
ObjectSet {
|
||||
obj: u8,
|
||||
key: u8,
|
||||
value: u8,
|
||||
},
|
||||
|
||||
/// Create object with optimized field setting - uses parameter table
|
||||
ObjectCreate {
|
||||
/// Index into program's instruction_data.object_create_params table
|
||||
params_index: u16,
|
||||
},
|
||||
|
||||
/// Index into container (object, array, set)
|
||||
Index {
|
||||
dest: u8,
|
||||
container: u8,
|
||||
key: u8,
|
||||
},
|
||||
|
||||
/// Index into container using literal key (optimization for Load + Index)
|
||||
IndexLiteral {
|
||||
dest: u8,
|
||||
container: u8,
|
||||
literal_idx: u16,
|
||||
},
|
||||
|
||||
/// Multi-level chained indexing (e.g., obj.field1[expr].field2)
|
||||
ChainedIndex {
|
||||
/// Index into program's instruction_data.chained_index_params table
|
||||
params_index: u16,
|
||||
},
|
||||
|
||||
/// Create empty array
|
||||
ArrayNew {
|
||||
dest: u8,
|
||||
},
|
||||
|
||||
/// Push element to array
|
||||
ArrayPush {
|
||||
arr: u8,
|
||||
value: u8,
|
||||
},
|
||||
|
||||
/// Create array from registers - returns undefined if any element is undefined
|
||||
ArrayCreate {
|
||||
/// Index into program's instruction_data.array_create_params table
|
||||
params_index: u16,
|
||||
},
|
||||
|
||||
/// Create empty set
|
||||
SetNew {
|
||||
dest: u8,
|
||||
},
|
||||
|
||||
/// Add element to set
|
||||
SetAdd {
|
||||
set: u8,
|
||||
value: u8,
|
||||
},
|
||||
|
||||
/// Create set from registers - returns undefined if any element is undefined
|
||||
SetCreate {
|
||||
/// Index into program's instruction_data.set_create_params table
|
||||
params_index: u16,
|
||||
},
|
||||
|
||||
/// Check if collection contains value (for membership testing)
|
||||
Contains {
|
||||
dest: u8,
|
||||
collection: u8,
|
||||
value: u8,
|
||||
},
|
||||
|
||||
/// Get count/length of collection (arrays, objects, sets) - returns undefined for non-collections
|
||||
Count {
|
||||
dest: u8,
|
||||
collection: u8,
|
||||
},
|
||||
|
||||
/// Assert condition - if register contains false or undefined, return undefined immediately
|
||||
AssertCondition {
|
||||
condition: u8,
|
||||
},
|
||||
|
||||
/// Assert not undefined - if register contains undefined, return undefined immediately
|
||||
AssertNotUndefined {
|
||||
register: u8,
|
||||
},
|
||||
|
||||
/// Start a loop over a collection with specified semantics - uses parameter table
|
||||
LoopStart {
|
||||
/// Index into program's instruction_data.loop_params table
|
||||
params_index: u16,
|
||||
},
|
||||
|
||||
/// Continue to next iteration or exit loop
|
||||
LoopNext {
|
||||
/// Jump target back to loop body
|
||||
body_start: u16,
|
||||
/// Jump target for loop end
|
||||
loop_end: u16,
|
||||
},
|
||||
|
||||
/// Call rule with caching - checks cache first, executes rule if needed, supports call stack
|
||||
CallRule {
|
||||
/// Destination register to store the result of the rule call
|
||||
dest: u8,
|
||||
/// Rule index to execute
|
||||
rule_index: u16,
|
||||
},
|
||||
|
||||
/// Initialize a rule
|
||||
RuleInit {
|
||||
/// The register where rule's result is accumulated.
|
||||
result_reg: u8,
|
||||
|
||||
/// The rule number of the rule
|
||||
rule_index: u16,
|
||||
},
|
||||
|
||||
/// Lookup in data namespace virtual documents (rules + base data)
|
||||
VirtualDataDocumentLookup {
|
||||
/// Index into program's instruction_data.virtual_data_document_lookup_params table
|
||||
params_index: u16,
|
||||
},
|
||||
|
||||
/// Mark successful completion of parameter destructuring validation
|
||||
DestructuringSuccess {},
|
||||
|
||||
/// Return from rule execution
|
||||
RuleReturn {},
|
||||
|
||||
/// Stop execution
|
||||
Halt {},
|
||||
|
||||
/// Begin a comprehension with specified parameters
|
||||
ComprehensionBegin {
|
||||
/// Index into program's instruction_data.comprehension_begin_params table
|
||||
params_index: u16,
|
||||
},
|
||||
|
||||
/// Yield a value to the current comprehension result
|
||||
ComprehensionYield {
|
||||
/// Register containing the value to yield to the comprehension
|
||||
value_reg: u8,
|
||||
/// Optional register containing the key for object comprehensions
|
||||
key_reg: Option<u8>,
|
||||
},
|
||||
|
||||
/// End a comprehension block
|
||||
ComprehensionEnd {},
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
/// Create a new LoopStart instruction with parameter table index
|
||||
pub fn loop_start(params_index: u16) -> Self {
|
||||
Self::LoopStart { params_index }
|
||||
}
|
||||
|
||||
/// Create a new BuiltinCall instruction with parameter table index
|
||||
pub fn builtin_call(params_index: u16) -> Self {
|
||||
Self::BuiltinCall { params_index }
|
||||
}
|
||||
|
||||
/// Create a new HostAwait instruction
|
||||
pub fn host_await(dest: u8, arg: u8, id: u8) -> Self {
|
||||
Self::HostAwait { dest, arg, id }
|
||||
}
|
||||
|
||||
/// Create a new FunctionCall instruction with parameter table index
|
||||
pub fn function_call(params_index: u16) -> Self {
|
||||
Self::FunctionCall { params_index }
|
||||
}
|
||||
|
||||
/// Create a new ObjectCreate instruction with parameter table index
|
||||
pub fn object_create(params_index: u16) -> Self {
|
||||
Self::ObjectCreate { params_index }
|
||||
}
|
||||
|
||||
/// Create a new ArrayCreate instruction with parameter table index
|
||||
pub fn array_create(params_index: u16) -> Self {
|
||||
Self::ArrayCreate { params_index }
|
||||
}
|
||||
|
||||
/// Create a new SetCreate instruction with parameter table index
|
||||
pub fn set_create(params_index: u16) -> Self {
|
||||
Self::SetCreate { params_index }
|
||||
}
|
||||
|
||||
/// Create a new ComprehensionBegin instruction with parameter table index
|
||||
pub fn comprehension_begin(params_index: u16) -> Self {
|
||||
Self::ComprehensionBegin { params_index }
|
||||
}
|
||||
|
||||
/// Create a new ComprehensionYield instruction
|
||||
pub fn comprehension_yield(value_reg: u8) -> Self {
|
||||
Self::ComprehensionYield {
|
||||
value_reg,
|
||||
key_reg: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new ComprehensionYield instruction for object comprehensions
|
||||
pub fn comprehension_yield_object(key_reg: u8, value_reg: u8) -> Self {
|
||||
Self::ComprehensionYield {
|
||||
value_reg,
|
||||
key_reg: Some(key_reg),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new ComprehensionEnd instruction
|
||||
pub fn comprehension_end() -> Self {
|
||||
Self::ComprehensionEnd {}
|
||||
}
|
||||
}
|
||||
445
src/rvm/instructions/params.rs
Normal file
445
src/rvm/instructions/params.rs
Normal file
@@ -0,0 +1,445 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::types::{ComprehensionMode, LiteralOrRegister, LoopMode};
|
||||
|
||||
/// Loop parameters stored in program's instruction data table
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LoopStartParams {
|
||||
/// Loop mode (Existential/Universal/Comprehension types)
|
||||
pub mode: LoopMode,
|
||||
/// Register containing the collection to iterate over
|
||||
pub collection: u8,
|
||||
/// Register to store current key (same as value_reg if key not needed)
|
||||
pub key_reg: u8,
|
||||
/// Register to store current value
|
||||
pub value_reg: u8,
|
||||
/// Register to store final result
|
||||
pub result_reg: u8,
|
||||
/// Jump target for loop body start
|
||||
pub body_start: u16,
|
||||
/// Jump target for loop end
|
||||
pub loop_end: u16,
|
||||
}
|
||||
|
||||
/// Builtin function call parameters stored in program's instruction data table
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BuiltinCallParams {
|
||||
/// Destination register to store the result
|
||||
pub dest: u8,
|
||||
/// Index into program's builtin_info_table
|
||||
pub builtin_index: u16,
|
||||
/// Number of arguments actually used
|
||||
pub num_args: u8,
|
||||
/// Argument register numbers (unused slots contain undefined values)
|
||||
pub args: [u8; 8],
|
||||
}
|
||||
|
||||
impl BuiltinCallParams {
|
||||
/// Get the number of arguments actually used
|
||||
pub fn arg_count(&self) -> usize {
|
||||
self.num_args as usize
|
||||
}
|
||||
|
||||
/// Get argument register numbers as a slice
|
||||
pub fn arg_registers(&self) -> &[u8] {
|
||||
&self.args[..self.num_args as usize]
|
||||
}
|
||||
}
|
||||
|
||||
/// Function rule call parameters stored in program's instruction data table
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FunctionCallParams {
|
||||
/// Destination register to store the result
|
||||
pub dest: u8,
|
||||
/// Rule index of the function to call
|
||||
pub func_rule_index: u16,
|
||||
/// Number of arguments actually used
|
||||
pub num_args: u8,
|
||||
/// Argument register numbers (unused slots contain undefined values)
|
||||
pub args: [u8; 8],
|
||||
}
|
||||
|
||||
impl FunctionCallParams {
|
||||
/// Get the number of arguments actually used
|
||||
pub fn arg_count(&self) -> usize {
|
||||
self.num_args as usize
|
||||
}
|
||||
|
||||
/// Get argument register numbers as a slice
|
||||
pub fn arg_registers(&self) -> &[u8] {
|
||||
&self.args[..self.num_args as usize]
|
||||
}
|
||||
}
|
||||
|
||||
/// Object creation parameters stored in program's instruction data table
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ObjectCreateParams {
|
||||
/// Destination register to store the result object
|
||||
pub dest: u8,
|
||||
/// Literal index of template object with all keys (undefined values)
|
||||
/// Always present - empty object if no literal keys
|
||||
pub template_literal_idx: u16,
|
||||
/// Fields with literal keys: (literal_key_index, value_register) in sorted order
|
||||
pub literal_key_fields: Vec<(u16, u8)>,
|
||||
/// Fields with non-literal keys: (key_register, value_register)
|
||||
pub fields: Vec<(u8, u8)>,
|
||||
}
|
||||
|
||||
impl ObjectCreateParams {
|
||||
/// Get the total number of fields
|
||||
pub fn field_count(&self) -> usize {
|
||||
self.literal_key_fields.len() + self.fields.len()
|
||||
}
|
||||
|
||||
/// Get literal key field pairs as a slice
|
||||
pub fn literal_key_field_pairs(&self) -> &[(u16, u8)] {
|
||||
&self.literal_key_fields
|
||||
}
|
||||
|
||||
/// Get non-literal key field pairs as a slice
|
||||
pub fn field_pairs(&self) -> &[(u8, u8)] {
|
||||
&self.fields
|
||||
}
|
||||
}
|
||||
|
||||
/// Array creation parameters stored in program's instruction data table
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ArrayCreateParams {
|
||||
/// Destination register to store the result array
|
||||
pub dest: u8,
|
||||
/// Register numbers containing the element values
|
||||
pub elements: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ArrayCreateParams {
|
||||
/// Get the number of elements
|
||||
pub fn element_count(&self) -> usize {
|
||||
self.elements.len()
|
||||
}
|
||||
|
||||
/// Get element register numbers as a slice
|
||||
pub fn element_registers(&self) -> &[u8] {
|
||||
&self.elements
|
||||
}
|
||||
}
|
||||
|
||||
/// Set creation parameters stored in program's instruction data table
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SetCreateParams {
|
||||
/// Destination register to store the result set
|
||||
pub dest: u8,
|
||||
/// Register numbers containing the element values
|
||||
pub elements: Vec<u8>,
|
||||
}
|
||||
|
||||
impl SetCreateParams {
|
||||
/// Get the number of elements
|
||||
pub fn element_count(&self) -> usize {
|
||||
self.elements.len()
|
||||
}
|
||||
|
||||
/// Get element register numbers as a slice
|
||||
pub fn element_registers(&self) -> &[u8] {
|
||||
&self.elements
|
||||
}
|
||||
}
|
||||
|
||||
/// Virtual data document lookup parameters for data namespace access with rule evaluation
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct VirtualDataDocumentLookupParams {
|
||||
/// Destination register to store the result
|
||||
pub dest: u8,
|
||||
/// Path components in order (e.g., for data.users[input.name].config)
|
||||
/// This would be [Literal("users"), Register(5), Literal("config")]
|
||||
/// where register 5 contains the value from input.name
|
||||
pub path_components: Vec<LiteralOrRegister>,
|
||||
}
|
||||
|
||||
impl VirtualDataDocumentLookupParams {
|
||||
/// Get the number of path components
|
||||
pub fn component_count(&self) -> usize {
|
||||
self.path_components.len()
|
||||
}
|
||||
|
||||
/// Check if all components are literals (can be optimized at compile time)
|
||||
pub fn all_literals(&self) -> bool {
|
||||
self.path_components
|
||||
.iter()
|
||||
.all(|c| matches!(c, LiteralOrRegister::Literal(_)))
|
||||
}
|
||||
|
||||
/// Get just the literal indices (for debugging/display)
|
||||
pub fn literal_indices(&self) -> Vec<u16> {
|
||||
self.path_components
|
||||
.iter()
|
||||
.filter_map(|c| match c {
|
||||
LiteralOrRegister::Literal(idx) => Some(*idx),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get just the register numbers (for debugging/display)
|
||||
pub fn register_numbers(&self) -> Vec<u8> {
|
||||
self.path_components
|
||||
.iter()
|
||||
.filter_map(|c| match c {
|
||||
LiteralOrRegister::Register(reg) => Some(*reg),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Chained index parameters for multi-level object access (input, locals, non-rule data paths)
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ChainedIndexParams {
|
||||
/// Destination register to store the result
|
||||
pub dest: u8,
|
||||
/// Root register containing the base object (input, local var, data subset)
|
||||
pub root: u8,
|
||||
/// Path components to traverse from the root
|
||||
pub path_components: Vec<LiteralOrRegister>,
|
||||
}
|
||||
|
||||
impl ChainedIndexParams {
|
||||
/// Get the number of path components
|
||||
pub fn component_count(&self) -> usize {
|
||||
self.path_components.len()
|
||||
}
|
||||
|
||||
/// Check if all components are literals (can be optimized)
|
||||
pub fn all_literals(&self) -> bool {
|
||||
self.path_components
|
||||
.iter()
|
||||
.all(|c| matches!(c, LiteralOrRegister::Literal(_)))
|
||||
}
|
||||
|
||||
/// Get just the literal indices (for debugging/display)
|
||||
pub fn literal_indices(&self) -> Vec<u16> {
|
||||
self.path_components
|
||||
.iter()
|
||||
.filter_map(|c| match c {
|
||||
LiteralOrRegister::Literal(idx) => Some(*idx),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get just the register numbers (for debugging/display)
|
||||
pub fn register_numbers(&self) -> Vec<u8> {
|
||||
self.path_components
|
||||
.iter()
|
||||
.filter_map(|c| match c {
|
||||
LiteralOrRegister::Register(reg) => Some(*reg),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Comprehension parameters stored in program's instruction data table
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ComprehensionBeginParams {
|
||||
/// Type of comprehension being created
|
||||
pub mode: ComprehensionMode,
|
||||
/// Register containing the source collection to iterate over
|
||||
pub collection_reg: u8,
|
||||
/// Register to store the comprehension result collection
|
||||
/// If not specified separately, this will match collection_reg
|
||||
pub result_reg: u8,
|
||||
/// Register to store current iteration key
|
||||
pub key_reg: u8,
|
||||
/// Register to store current iteration value
|
||||
pub value_reg: u8,
|
||||
/// Jump target for comprehension body start
|
||||
pub body_start: u16,
|
||||
/// Jump target for comprehension end
|
||||
pub comprehension_end: u16,
|
||||
}
|
||||
|
||||
/// Instruction data container for complex instruction parameters
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct InstructionData {
|
||||
/// Loop parameter table for LoopStart instructions
|
||||
pub loop_params: Vec<LoopStartParams>,
|
||||
/// Builtin function call parameter table for BuiltinCall instructions
|
||||
pub builtin_call_params: Vec<BuiltinCallParams>,
|
||||
/// Function rule call parameter table for FunctionCall instructions
|
||||
pub function_call_params: Vec<FunctionCallParams>,
|
||||
/// Object creation parameter table for ObjectCreate instructions
|
||||
pub object_create_params: Vec<ObjectCreateParams>,
|
||||
/// Array creation parameter table for ArrayCreate instructions
|
||||
pub array_create_params: Vec<ArrayCreateParams>,
|
||||
/// Set creation parameter table for SetCreate instructions
|
||||
pub set_create_params: Vec<SetCreateParams>,
|
||||
/// Virtual data document lookup parameter table for VirtualDataDocumentLookup instructions
|
||||
pub virtual_data_document_lookup_params: Vec<VirtualDataDocumentLookupParams>,
|
||||
/// Chained index parameter table for ChainedIndex instructions
|
||||
pub chained_index_params: Vec<ChainedIndexParams>,
|
||||
/// Comprehension parameter table for ComprehensionBegin instructions
|
||||
pub comprehension_begin_params: Vec<ComprehensionBeginParams>,
|
||||
}
|
||||
|
||||
impl InstructionData {
|
||||
/// Create a new empty instruction data container
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
loop_params: Vec::new(),
|
||||
builtin_call_params: Vec::new(),
|
||||
function_call_params: Vec::new(),
|
||||
object_create_params: Vec::new(),
|
||||
array_create_params: Vec::new(),
|
||||
set_create_params: Vec::new(),
|
||||
virtual_data_document_lookup_params: Vec::new(),
|
||||
chained_index_params: Vec::new(),
|
||||
comprehension_begin_params: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add loop parameters and return the index
|
||||
pub fn add_loop_params(&mut self, params: LoopStartParams) -> u16 {
|
||||
let index = self.loop_params.len();
|
||||
self.loop_params.push(params);
|
||||
index as u16
|
||||
}
|
||||
|
||||
/// Add builtin call parameters and return the index
|
||||
pub fn add_builtin_call_params(&mut self, params: BuiltinCallParams) -> u16 {
|
||||
let index = self.builtin_call_params.len();
|
||||
self.builtin_call_params.push(params);
|
||||
index as u16
|
||||
}
|
||||
|
||||
/// Add function call parameters and return the index
|
||||
pub fn add_function_call_params(&mut self, params: FunctionCallParams) -> u16 {
|
||||
let index = self.function_call_params.len();
|
||||
self.function_call_params.push(params);
|
||||
index as u16
|
||||
}
|
||||
|
||||
/// Add object create parameters and return the index
|
||||
pub fn add_object_create_params(&mut self, params: ObjectCreateParams) -> u16 {
|
||||
let index = self.object_create_params.len();
|
||||
self.object_create_params.push(params);
|
||||
index as u16
|
||||
}
|
||||
|
||||
/// Add array create parameters and return the index
|
||||
pub fn add_array_create_params(&mut self, params: ArrayCreateParams) -> u16 {
|
||||
let index = self.array_create_params.len();
|
||||
self.array_create_params.push(params);
|
||||
index as u16
|
||||
}
|
||||
|
||||
/// Add set create parameters and return the index
|
||||
pub fn add_set_create_params(&mut self, params: SetCreateParams) -> u16 {
|
||||
let index = self.set_create_params.len();
|
||||
self.set_create_params.push(params);
|
||||
index as u16
|
||||
}
|
||||
|
||||
/// Get loop parameters by index
|
||||
pub fn get_loop_params(&self, index: u16) -> Option<&LoopStartParams> {
|
||||
self.loop_params.get(index as usize)
|
||||
}
|
||||
|
||||
/// Get builtin call parameters by index
|
||||
pub fn get_builtin_call_params(&self, index: u16) -> Option<&BuiltinCallParams> {
|
||||
self.builtin_call_params.get(index as usize)
|
||||
}
|
||||
|
||||
/// Get function call parameters by index
|
||||
pub fn get_function_call_params(&self, index: u16) -> Option<&FunctionCallParams> {
|
||||
self.function_call_params.get(index as usize)
|
||||
}
|
||||
|
||||
/// Get object create parameters by index
|
||||
pub fn get_object_create_params(&self, index: u16) -> Option<&ObjectCreateParams> {
|
||||
self.object_create_params.get(index as usize)
|
||||
}
|
||||
|
||||
/// Get array create parameters by index
|
||||
pub fn get_array_create_params(&self, index: u16) -> Option<&ArrayCreateParams> {
|
||||
self.array_create_params.get(index as usize)
|
||||
}
|
||||
|
||||
/// Get set create parameters by index
|
||||
pub fn get_set_create_params(&self, index: u16) -> Option<&SetCreateParams> {
|
||||
self.set_create_params.get(index as usize)
|
||||
}
|
||||
|
||||
/// Add virtual data document lookup parameters and return the index
|
||||
pub fn add_virtual_data_document_lookup_params(
|
||||
&mut self,
|
||||
params: VirtualDataDocumentLookupParams,
|
||||
) -> u16 {
|
||||
let index = self.virtual_data_document_lookup_params.len();
|
||||
self.virtual_data_document_lookup_params.push(params);
|
||||
index as u16
|
||||
}
|
||||
|
||||
/// Get virtual data document lookup parameters by index
|
||||
pub fn get_virtual_data_document_lookup_params(
|
||||
&self,
|
||||
index: u16,
|
||||
) -> Option<&VirtualDataDocumentLookupParams> {
|
||||
self.virtual_data_document_lookup_params.get(index as usize)
|
||||
}
|
||||
|
||||
/// Add chained index parameters and return the index
|
||||
pub fn add_chained_index_params(&mut self, params: ChainedIndexParams) -> u16 {
|
||||
let index = self.chained_index_params.len();
|
||||
self.chained_index_params.push(params);
|
||||
index as u16
|
||||
}
|
||||
|
||||
/// Get chained index parameters by index
|
||||
pub fn get_chained_index_params(&self, index: u16) -> Option<&ChainedIndexParams> {
|
||||
self.chained_index_params.get(index as usize)
|
||||
}
|
||||
|
||||
/// Get mutable reference to loop parameters by index
|
||||
pub fn get_loop_params_mut(&mut self, index: u16) -> Option<&mut LoopStartParams> {
|
||||
self.loop_params.get_mut(index as usize)
|
||||
}
|
||||
|
||||
/// Add comprehension begin parameters and return the index
|
||||
pub fn add_comprehension_begin_params(&mut self, params: ComprehensionBeginParams) -> u16 {
|
||||
let index = self.comprehension_begin_params.len();
|
||||
self.comprehension_begin_params.push(params);
|
||||
index as u16
|
||||
}
|
||||
|
||||
/// Get comprehension begin parameters by index
|
||||
pub fn get_comprehension_begin_params(&self, index: u16) -> Option<&ComprehensionBeginParams> {
|
||||
self.comprehension_begin_params.get(index as usize)
|
||||
}
|
||||
|
||||
/// Get mutable reference to comprehension begin parameters by index
|
||||
pub fn get_comprehension_begin_params_mut(
|
||||
&mut self,
|
||||
index: u16,
|
||||
) -> Option<&mut ComprehensionBeginParams> {
|
||||
self.comprehension_begin_params.get_mut(index as usize)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for InstructionData {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
47
src/rvm/instructions/types.rs
Normal file
47
src/rvm/instructions/types.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Represents either a literal index or a register number for path components
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum LiteralOrRegister {
|
||||
/// Index into the program's literal table
|
||||
Literal(u16),
|
||||
/// Register number containing the value
|
||||
Register(u8),
|
||||
}
|
||||
|
||||
/// Loop execution modes for different Rego iteration constructs
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum LoopMode {
|
||||
/// Any quantification: some x in arr, x := arr[_], etc.
|
||||
/// Succeeds if ANY iteration succeeds, exits early on first success
|
||||
Any,
|
||||
|
||||
/// Every quantification: every x in arr
|
||||
/// Succeeds only if ALL iterations succeed, exits early on first failure
|
||||
Every,
|
||||
|
||||
/// ForEach processing: processes all elements without early exit
|
||||
/// Used for set membership rules (contains), object rules, and complete rules
|
||||
/// where all candidates must be evaluated. Determined by output constness.
|
||||
ForEach,
|
||||
}
|
||||
|
||||
/// Comprehension execution modes for different comprehension types
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum ComprehensionMode {
|
||||
/// Set comprehension: {expr | condition}
|
||||
/// Collects unique successful iterations into a set
|
||||
Set,
|
||||
/// Array comprehension: [expr | condition]
|
||||
/// Collects successful iterations into an array (preserves order)
|
||||
Array,
|
||||
/// Object comprehension: {key: value | condition}
|
||||
/// Collects successful key-value pairs into an object
|
||||
Object,
|
||||
}
|
||||
16
src/rvm/mod.rs
Normal file
16
src/rvm/mod.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
// RVM - Rego Virtual Machine
|
||||
// A register-based virtual machine for executing Rego policies
|
||||
|
||||
pub mod instructions;
|
||||
pub mod program;
|
||||
pub mod tests;
|
||||
pub mod vm;
|
||||
|
||||
pub use instructions::Instruction;
|
||||
pub use program::{
|
||||
generate_assembly_listing, generate_tabular_assembly_listing, AssemblyListingConfig, Program,
|
||||
};
|
||||
pub use vm::RegoVM;
|
||||
322
src/rvm/program/core.rs
Normal file
322
src/rvm/program/core.rs
Normal file
@@ -0,0 +1,322 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::vec::Vec;
|
||||
use anyhow::Result as AnyResult;
|
||||
use indexmap::IndexMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::types::{BuiltinInfo, ProgramMetadata, RuleInfo, SourceFile, SpanInfo};
|
||||
use crate::builtins::BuiltinFcn;
|
||||
use crate::rvm::instructions::InstructionData;
|
||||
use crate::rvm::Instruction;
|
||||
use crate::value::Value;
|
||||
|
||||
/// Complete compiled program containing all execution artifacts
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Program {
|
||||
/// Compiled bytecode instructions
|
||||
pub instructions: Vec<Instruction>,
|
||||
|
||||
/// Literal value table (skipped in serde, serialized separately as JSON)
|
||||
#[serde(skip, default = "Vec::new")]
|
||||
pub literals: Vec<Value>,
|
||||
|
||||
/// Complex instruction parameter data (for LoopStart, Call, etc.)
|
||||
pub instruction_data: InstructionData,
|
||||
|
||||
/// Builtin function information table
|
||||
pub builtin_info_table: Vec<BuiltinInfo>,
|
||||
|
||||
/// Entry points mapping with preserved insertion order (skipped in serde, serialized separately as JSON)
|
||||
#[serde(skip, default = "IndexMap::new")]
|
||||
pub entry_points: IndexMap<String, usize>,
|
||||
|
||||
/// Source files table with content (skipped in serde, serialized separately as JSON)
|
||||
#[serde(skip, default = "Vec::new")]
|
||||
pub sources: Vec<SourceFile>,
|
||||
|
||||
/// Rule metadata: rule_index -> rule information
|
||||
pub rule_infos: Vec<RuleInfo>,
|
||||
|
||||
/// Span information for each instruction (for debugging)
|
||||
pub instruction_spans: Vec<Option<SpanInfo>>,
|
||||
|
||||
/// Main program entry point
|
||||
pub main_entry_point: usize,
|
||||
|
||||
/// Maximum register window size observed across all rule definitions
|
||||
pub max_rule_window_size: usize,
|
||||
|
||||
/// Register window size needed for entry point dispatch
|
||||
pub dispatch_window_size: usize,
|
||||
|
||||
/// Program metadata
|
||||
pub metadata: ProgramMetadata,
|
||||
|
||||
/// Rule tree for efficient rule lookup (skipped in serde, serialized separately as JSON)
|
||||
/// Maps rule paths (e.g., "data.p1.r1") to rule indices
|
||||
/// Structure: {"p1": {"r1": rule_index}, "p2": {"p3": {"r2": rule_index}}}
|
||||
#[serde(skip, default = "Value::new_object")]
|
||||
pub rule_tree: Value,
|
||||
|
||||
/// Resolved builtins - actual builtin function values fetched from interpreter's builtin map
|
||||
/// This field is skipped during serialization and reinitialized after deserialization
|
||||
#[serde(skip)]
|
||||
pub resolved_builtins: Vec<BuiltinFcn>,
|
||||
|
||||
/// Flag indicating that VirtualDataDocumentLookup instruction was used and runtime recursion checking is needed
|
||||
pub needs_runtime_recursion_check: bool,
|
||||
|
||||
/// Flag indicating that recompilation is needed due to partial deserialization failure
|
||||
/// This is set to true when the artifact section was successfully read but the extensible
|
||||
/// section failed to deserialize (e.g., due to version incompatibility)
|
||||
#[serde(default)]
|
||||
pub needs_recompilation: bool,
|
||||
|
||||
/// Rego language version used for compilation (true for Rego v0, false for Rego v1)
|
||||
/// This must be preserved during recompilation to maintain policy semantics
|
||||
/// Serialized separately in the artifact section for guaranteed availability
|
||||
#[serde(skip, default)]
|
||||
pub rego_v0: bool,
|
||||
}
|
||||
|
||||
impl Program {
|
||||
/// Current serialization format version
|
||||
pub const SERIALIZATION_VERSION: u32 = 3;
|
||||
/// Magic bytes to identify Regorus program files
|
||||
pub const MAGIC: [u8; 4] = *b"REGO";
|
||||
|
||||
/// Create a new empty program
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
instructions: Vec::new(),
|
||||
literals: Vec::new(),
|
||||
instruction_data: InstructionData::new(),
|
||||
builtin_info_table: Vec::new(),
|
||||
entry_points: IndexMap::new(),
|
||||
sources: Vec::new(),
|
||||
rule_infos: Vec::new(),
|
||||
instruction_spans: Vec::new(),
|
||||
main_entry_point: 0,
|
||||
max_rule_window_size: 0,
|
||||
dispatch_window_size: 0,
|
||||
metadata: ProgramMetadata {
|
||||
compiler_version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
compiled_at: "unknown".to_string(),
|
||||
source_info: "unknown".to_string(),
|
||||
optimization_level: 0,
|
||||
},
|
||||
rule_tree: Value::new_object(),
|
||||
resolved_builtins: Vec::new(),
|
||||
needs_runtime_recursion_check: false,
|
||||
needs_recompilation: false,
|
||||
rego_v0: false, // Default to Rego v1
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a source file and return its index
|
||||
pub fn add_source(&mut self, name: String, content: String) -> usize {
|
||||
let source_file = SourceFile::new(name.clone(), content);
|
||||
let index = self.sources.len();
|
||||
self.sources.push(source_file);
|
||||
index
|
||||
}
|
||||
|
||||
/// Add loop parameters and return the index
|
||||
pub fn add_loop_params(&mut self, params: crate::rvm::instructions::LoopStartParams) -> u16 {
|
||||
self.instruction_data.add_loop_params(params)
|
||||
}
|
||||
|
||||
/// Add comprehension begin parameters and return the index
|
||||
pub fn add_comprehension_begin_params(
|
||||
&mut self,
|
||||
params: crate::rvm::instructions::ComprehensionBeginParams,
|
||||
) -> u16 {
|
||||
self.instruction_data.add_comprehension_begin_params(params)
|
||||
}
|
||||
|
||||
/// Add builtin call parameters and return the index
|
||||
pub fn add_builtin_call_params(
|
||||
&mut self,
|
||||
params: crate::rvm::instructions::BuiltinCallParams,
|
||||
) -> u16 {
|
||||
self.instruction_data.add_builtin_call_params(params)
|
||||
}
|
||||
|
||||
/// Add function call parameters and return the index
|
||||
pub fn add_function_call_params(
|
||||
&mut self,
|
||||
params: crate::rvm::instructions::FunctionCallParams,
|
||||
) -> u16 {
|
||||
self.instruction_data.add_function_call_params(params)
|
||||
}
|
||||
|
||||
/// Add builtin info and return the index
|
||||
pub fn add_builtin_info(&mut self, builtin_info: BuiltinInfo) -> u16 {
|
||||
let index = self.builtin_info_table.len();
|
||||
self.builtin_info_table.push(builtin_info);
|
||||
index as u16
|
||||
}
|
||||
|
||||
/// Get builtin info by index
|
||||
pub fn get_builtin_info(&self, index: u16) -> Option<&BuiltinInfo> {
|
||||
self.builtin_info_table.get(index as usize)
|
||||
}
|
||||
|
||||
/// Update loop parameters by index
|
||||
pub fn update_loop_params<F>(&mut self, params_index: u16, updater: F)
|
||||
where
|
||||
F: FnOnce(&mut crate::rvm::instructions::LoopStartParams),
|
||||
{
|
||||
if let Some(params) = self.instruction_data.get_loop_params_mut(params_index) {
|
||||
updater(params);
|
||||
}
|
||||
}
|
||||
|
||||
/// Update comprehension begin parameters by index
|
||||
pub fn update_comprehension_begin_params<F>(&mut self, params_index: u16, updater: F)
|
||||
where
|
||||
F: FnOnce(&mut crate::rvm::instructions::ComprehensionBeginParams),
|
||||
{
|
||||
if let Some(params) = self
|
||||
.instruction_data
|
||||
.get_comprehension_begin_params_mut(params_index)
|
||||
{
|
||||
updater(params);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get detailed instruction display with parameter resolution
|
||||
pub fn display_instruction_with_params(&self, instruction: &Instruction) -> String {
|
||||
instruction.display_with_params(&self.instruction_data)
|
||||
}
|
||||
|
||||
/// Add a source file directly and return its index
|
||||
pub fn add_source_file(&mut self, source_file: SourceFile) -> usize {
|
||||
for (i, existing) in self.sources.iter().enumerate() {
|
||||
if existing.name == source_file.name {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
let index = self.sources.len();
|
||||
self.sources.push(source_file);
|
||||
index
|
||||
}
|
||||
|
||||
/// Get source file by index
|
||||
pub fn get_source_file(&self, index: usize) -> Option<&SourceFile> {
|
||||
self.sources.get(index)
|
||||
}
|
||||
|
||||
/// Get source content by index
|
||||
pub fn get_source(&self, index: usize) -> Option<&str> {
|
||||
self.sources.get(index).map(|s| s.content.as_str())
|
||||
}
|
||||
|
||||
/// Get source name by index
|
||||
pub fn get_source_name(&self, index: usize) -> Option<&str> {
|
||||
self.sources.get(index).map(|s| s.name.as_str())
|
||||
}
|
||||
|
||||
/// Get rule info by index
|
||||
pub fn get_rule_info(&self, rule_index: usize) -> Option<&RuleInfo> {
|
||||
self.rule_infos.get(rule_index)
|
||||
}
|
||||
|
||||
/// Get span information for instruction
|
||||
pub fn get_instruction_span(&self, instruction_index: usize) -> Option<&SpanInfo> {
|
||||
self.instruction_spans
|
||||
.get(instruction_index)
|
||||
.and_then(|span| span.as_ref())
|
||||
}
|
||||
|
||||
/// Add instruction with optional span
|
||||
pub fn add_instruction(&mut self, instruction: Instruction, span: Option<SpanInfo>) {
|
||||
self.instructions.push(instruction);
|
||||
self.instruction_spans.push(span);
|
||||
}
|
||||
|
||||
/// Add literal value and return its index
|
||||
pub fn add_literal(&mut self, value: Value) -> usize {
|
||||
for (i, existing) in self.literals.iter().enumerate() {
|
||||
if existing == &value {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
let index = self.literals.len();
|
||||
self.literals.push(value);
|
||||
index
|
||||
}
|
||||
|
||||
/// Initialize resolved builtins directly from the BUILTINS HashMap
|
||||
/// This should be called after deserialization to populate the skipped field
|
||||
/// Returns an error if any required builtin is missing
|
||||
pub fn initialize_resolved_builtins(&mut self) -> AnyResult<()> {
|
||||
self.resolved_builtins.clear();
|
||||
self.resolved_builtins
|
||||
.reserve(self.builtin_info_table.len());
|
||||
|
||||
for builtin_info in &self.builtin_info_table {
|
||||
if let Some(&builtin_fcn) = crate::builtins::BUILTINS.get(builtin_info.name.as_str()) {
|
||||
self.resolved_builtins.push(builtin_fcn);
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Missing builtin function: {}",
|
||||
builtin_info.name
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get resolved builtin function by index
|
||||
pub fn get_resolved_builtin(&self, index: u16) -> Option<&BuiltinFcn> {
|
||||
self.resolved_builtins.get(index as usize)
|
||||
}
|
||||
|
||||
/// Check if resolved builtins are initialized
|
||||
pub fn has_resolved_builtins(&self) -> bool {
|
||||
!self.resolved_builtins.is_empty()
|
||||
}
|
||||
|
||||
/// Add an entry point mapping from path to rule index
|
||||
pub fn add_entry_point(&mut self, path: String, rule_index: usize) {
|
||||
self.entry_points.insert(path, rule_index);
|
||||
}
|
||||
|
||||
/// Get rule index for an entry point path
|
||||
pub fn get_entry_point(&self, path: &str) -> Option<usize> {
|
||||
self.entry_points.get(path).copied()
|
||||
}
|
||||
|
||||
/// Get all entry points as IndexMap
|
||||
pub fn get_entry_points(&self) -> &IndexMap<String, usize> {
|
||||
&self.entry_points
|
||||
}
|
||||
|
||||
/// Check if recompilation is needed due to partial deserialization failure
|
||||
pub fn needs_recompilation(&self) -> bool {
|
||||
self.needs_recompilation
|
||||
}
|
||||
|
||||
/// Mark that recompilation is needed
|
||||
pub fn set_needs_recompilation(&mut self, needs_recompilation: bool) {
|
||||
self.needs_recompilation = needs_recompilation;
|
||||
}
|
||||
|
||||
/// Check if the program is fully functional (not needing recompilation)
|
||||
pub fn is_fully_functional(&self) -> bool {
|
||||
!self.needs_recompilation
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Program {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
964
src/rvm/program/listing.rs
Normal file
964
src/rvm/program/listing.rs
Normal file
@@ -0,0 +1,964 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use alloc::format;
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::vec::Vec;
|
||||
use core::fmt::Write;
|
||||
|
||||
use crate::rvm::{
|
||||
instructions::{Instruction, InstructionData, LoopMode},
|
||||
program::Program,
|
||||
};
|
||||
|
||||
/// Configuration for assembly listing output
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AssemblyListingConfig {
|
||||
/// Show instruction addresses
|
||||
pub show_addresses: bool,
|
||||
/// Show raw instruction bytes (if available)
|
||||
pub show_bytes: bool,
|
||||
/// Indent size for nested loops
|
||||
pub indent_size: usize,
|
||||
/// Maximum width for instruction column
|
||||
pub instruction_width: usize,
|
||||
/// Show literal values inline
|
||||
pub show_literal_values: bool,
|
||||
/// Column position for comments
|
||||
pub comment_column: usize,
|
||||
}
|
||||
|
||||
impl Default for AssemblyListingConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
show_addresses: true,
|
||||
show_bytes: false,
|
||||
indent_size: 4,
|
||||
instruction_width: 40,
|
||||
show_literal_values: true,
|
||||
comment_column: 50,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate annotated assembly listing for a compiled program
|
||||
pub fn generate_assembly_listing(program: &Program, config: &AssemblyListingConfig) -> String {
|
||||
let mut output = String::new();
|
||||
let mut indent_level: usize = 0;
|
||||
let mut current_rule_index: Option<u16> = None;
|
||||
|
||||
// Track active loops and comprehensions by their end addresses
|
||||
let mut active_ends: Vec<u16> = Vec::new();
|
||||
|
||||
// Add header
|
||||
writeln!(
|
||||
output,
|
||||
"; RVM Assembly - {} instructions, {} literals, {} builtins",
|
||||
program.instructions.len(),
|
||||
program.literals.len(),
|
||||
program.builtin_info_table.len()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Add builtins table
|
||||
if !program.builtin_info_table.is_empty() {
|
||||
writeln!(output, ";").unwrap();
|
||||
writeln!(output, "; BUILTINS TABLE:").unwrap();
|
||||
for (idx, builtin_info) in program.builtin_info_table.iter().enumerate() {
|
||||
writeln!(output, "; B{:2}: {}", idx, builtin_info.name).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// Add literals table
|
||||
if config.show_literal_values && !program.literals.is_empty() {
|
||||
writeln!(output, ";").unwrap();
|
||||
writeln!(output, "; LITERALS (JSON values):").unwrap();
|
||||
for (idx, literal) in program.literals.iter().enumerate() {
|
||||
let literal_json =
|
||||
serde_json::to_string(literal).unwrap_or_else(|_| "<invalid>".to_string());
|
||||
writeln!(output, "; L{:2}: {}", idx, literal_json).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// Add rules table if available
|
||||
if !program.rule_infos.is_empty() {
|
||||
writeln!(output, ";").unwrap();
|
||||
writeln!(output, "; RULES TABLE:").unwrap();
|
||||
for (idx, rule_info) in program.rule_infos.iter().enumerate() {
|
||||
writeln!(output, "; R{:2}: {}", idx, rule_info.name).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(output, ";").unwrap();
|
||||
|
||||
for (pc, instruction) in program.instructions.iter().enumerate() {
|
||||
// Handle rule transitions and add gaps
|
||||
if let Instruction::RuleInit { rule_index, .. } = instruction {
|
||||
// Add gap before new rule (except for the first rule)
|
||||
if current_rule_index.is_some() {
|
||||
writeln!(output).unwrap();
|
||||
}
|
||||
current_rule_index = Some(*rule_index);
|
||||
|
||||
// Add rule name prefix
|
||||
if let Some(rule_info) = program.rule_infos.get(*rule_index as usize) {
|
||||
writeln!(output, "; ===== RULE: {} =====", rule_info.name).unwrap();
|
||||
} else {
|
||||
writeln!(output, "; ===== RULE: rule_{} =====", rule_index).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if current PC matches any active end addresses (loops, comprehensions, rules)
|
||||
let current_pc = pc as u16;
|
||||
while let Some(&end_addr) = active_ends.last() {
|
||||
if current_pc >= end_addr {
|
||||
active_ends.pop();
|
||||
indent_level = indent_level.saturating_sub(1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle explicit end instructions
|
||||
match instruction {
|
||||
Instruction::LoopNext { .. } => {
|
||||
// LoopNext already handled by end address tracking above
|
||||
}
|
||||
Instruction::RuleReturn { .. } => {
|
||||
indent_level = indent_level.saturating_sub(1);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Special case: Block end instructions should be indented at their block level (one level out)
|
||||
let effective_indent_level = match instruction {
|
||||
Instruction::ComprehensionEnd {} => indent_level.saturating_sub(1),
|
||||
Instruction::LoopNext { .. } => indent_level.saturating_sub(1),
|
||||
_ => indent_level,
|
||||
};
|
||||
|
||||
let indent = " ".repeat(effective_indent_level * config.indent_size);
|
||||
|
||||
// Format address
|
||||
let addr_str = if config.show_addresses {
|
||||
format!("{:03}: ", pc)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
// Format instruction with proper indentation and aligned comments
|
||||
let inst_str = format_instruction_readable(
|
||||
instruction,
|
||||
&indent,
|
||||
&program.instruction_data,
|
||||
program,
|
||||
config,
|
||||
);
|
||||
|
||||
writeln!(output, "{}{}", addr_str, inst_str).unwrap();
|
||||
|
||||
// Increase indentation for loop/rule/comprehension starts and track their end addresses
|
||||
match instruction {
|
||||
Instruction::LoopStart { params_index } => {
|
||||
if let Some(params) = program.instruction_data.get_loop_params(*params_index) {
|
||||
active_ends.push(params.loop_end);
|
||||
indent_level += 1;
|
||||
}
|
||||
}
|
||||
Instruction::ComprehensionBegin { params_index } => {
|
||||
if let Some(params) = program
|
||||
.instruction_data
|
||||
.get_comprehension_begin_params(*params_index)
|
||||
{
|
||||
active_ends.push(params.comprehension_end);
|
||||
indent_level += 1;
|
||||
}
|
||||
}
|
||||
Instruction::RuleInit { .. } => {
|
||||
indent_level += 1;
|
||||
// Note: Rules end with RuleReturn, not an address, so we don't track them here
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
/// Helper function to align comments at a specific column
|
||||
fn align_comment(base_text: &str, comment: &str, target_column: usize) -> String {
|
||||
let current_len = base_text.len();
|
||||
if current_len >= target_column {
|
||||
format!("{} ; {}", base_text, comment)
|
||||
} else {
|
||||
let padding = " ".repeat(target_column - current_len);
|
||||
format!("{}{} ; {}", base_text, padding, comment)
|
||||
}
|
||||
}
|
||||
|
||||
/// Format a single instruction with proper indentation and mathematical notation
|
||||
fn format_instruction_readable(
|
||||
instruction: &Instruction,
|
||||
indent: &str,
|
||||
instruction_data: &InstructionData,
|
||||
program: &Program,
|
||||
config: &AssemblyListingConfig,
|
||||
) -> String {
|
||||
match instruction {
|
||||
Instruction::Load { dest, literal_idx } => {
|
||||
let base = format!("{}Load r{} ← L{}", indent, dest, literal_idx);
|
||||
let comment = if *literal_idx < program.literals.len() as u16 {
|
||||
let literal_json = serde_json::to_string(&program.literals[*literal_idx as usize])
|
||||
.unwrap_or_else(|_| "<invalid>".to_string());
|
||||
format!("Load literal: {}", literal_json)
|
||||
} else {
|
||||
"Load literal: <invalid index>".to_string()
|
||||
};
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::LoadTrue { dest } => {
|
||||
let base = format!("{}LoadTrue r{} ← true", indent, dest);
|
||||
align_comment(&base, "Load boolean constant true", config.comment_column)
|
||||
}
|
||||
Instruction::LoadFalse { dest } => {
|
||||
let base = format!("{}LoadFalse r{} ← false", indent, dest);
|
||||
align_comment(&base, "Load boolean constant false", config.comment_column)
|
||||
}
|
||||
Instruction::LoadNull { dest } => {
|
||||
let base = format!("{}LoadNull r{} ← null", indent, dest);
|
||||
align_comment(&base, "Load null value", config.comment_column)
|
||||
}
|
||||
Instruction::LoadBool { dest, value } => {
|
||||
let base = format!("{}LoadBool r{} ← {}", indent, dest, value);
|
||||
let comment = format!("Load boolean constant {}", value);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::LoadData { dest } => {
|
||||
let base = format!("{}LoadData r{} ← data", indent, dest);
|
||||
align_comment(&base, "Load global data document", config.comment_column)
|
||||
}
|
||||
Instruction::LoadInput { dest } => {
|
||||
let base = format!("{}LoadInput r{} ← input", indent, dest);
|
||||
align_comment(&base, "Load global input document", config.comment_column)
|
||||
}
|
||||
Instruction::Move { dest, src } => {
|
||||
let base = format!("{}Move r{} ← r{}", indent, dest, src);
|
||||
let comment = format!("Copy value from r{} to r{}", src, dest);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::Add { dest, left, right } => {
|
||||
let base = format!("{}Add r{} ← r{} + r{}", indent, dest, left, right);
|
||||
let comment = format!("Arithmetic addition: r{} + r{}", left, right);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::Sub { dest, left, right } => {
|
||||
let base = format!("{}Sub r{} ← r{} - r{}", indent, dest, left, right);
|
||||
let comment = format!("Arithmetic subtraction: r{} - r{}", left, right);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::Mul { dest, left, right } => {
|
||||
let base = format!("{}Mul r{} ← r{} × r{}", indent, dest, left, right);
|
||||
let comment = format!("Arithmetic multiplication: r{} × r{}", left, right);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::Div { dest, left, right } => {
|
||||
let base = format!("{}Div r{} ← r{} ÷ r{}", indent, dest, left, right);
|
||||
let comment = format!("Arithmetic division: r{} ÷ r{}", left, right);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::Mod { dest, left, right } => {
|
||||
let base = format!(
|
||||
"{}Mod r{} ← r{} mod r{}",
|
||||
indent, dest, left, right
|
||||
);
|
||||
let comment = format!("Modulo operation: r{} mod r{}", left, right);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::Eq { dest, left, right } => {
|
||||
let base = format!(
|
||||
"{}Eq r{} ← (r{} = r{})",
|
||||
indent, dest, left, right
|
||||
);
|
||||
let comment = format!("Equality test: r{} == r{}", left, right);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::Ne { dest, left, right } => {
|
||||
let base = format!(
|
||||
"{}Ne r{} ← (r{} ≠ r{})",
|
||||
indent, dest, left, right
|
||||
);
|
||||
let comment = format!("Inequality test: r{} != r{}", left, right);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::Lt { dest, left, right } => {
|
||||
let base = format!(
|
||||
"{}Lt r{} ← (r{} < r{})",
|
||||
indent, dest, left, right
|
||||
);
|
||||
let comment = format!("Less than comparison: r{} < r{}", left, right);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::Le { dest, left, right } => {
|
||||
let base = format!(
|
||||
"{}Le r{} ← (r{} ≤ r{})",
|
||||
indent, dest, left, right
|
||||
);
|
||||
let comment = format!("Less or equal comparison: r{} <= r{}", left, right);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::Gt { dest, left, right } => {
|
||||
let base = format!(
|
||||
"{}Gt r{} ← (r{} > r{})",
|
||||
indent, dest, left, right
|
||||
);
|
||||
let comment = format!("Greater than comparison: r{} > r{}", left, right);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::Ge { dest, left, right } => {
|
||||
let base = format!(
|
||||
"{}Ge r{} ← (r{} ≥ r{})",
|
||||
indent, dest, left, right
|
||||
);
|
||||
let comment = format!("Greater or equal comparison: r{} >= r{}", left, right);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::And { dest, left, right } => {
|
||||
let base = format!("{}And r{} ← r{} ∧ r{}", indent, dest, left, right);
|
||||
let comment = format!("Logical AND: r{} && r{}", left, right);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::Or { dest, left, right } => {
|
||||
let base = format!("{}Or r{} ← r{} ∨ r{}", indent, dest, left, right);
|
||||
let comment = format!("Logical OR: r{} || r{}", left, right);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::Not { dest, operand } => {
|
||||
let base = format!("{}Not r{} ← ¬r{}", indent, dest, operand);
|
||||
let comment = format!("Logical NOT: !r{}", operand);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::BuiltinCall { params_index } => {
|
||||
if let Some(params) = instruction_data.get_builtin_call_params(*params_index) {
|
||||
let args_str = params
|
||||
.arg_registers()
|
||||
.iter()
|
||||
.map(|&r| format!("r{}", r))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
let builtin_name = program
|
||||
.builtin_info_table
|
||||
.get(params.builtin_index as usize)
|
||||
.map(|info| info.name.as_str())
|
||||
.unwrap_or("<invalid>");
|
||||
|
||||
let base = format!(
|
||||
"{}BuiltinCall r{} ← {}({})",
|
||||
indent, params.dest, builtin_name, args_str
|
||||
);
|
||||
let comment = format!(
|
||||
"Call builtin '{}' (B{}) with {} args",
|
||||
builtin_name, params.builtin_index, params.num_args
|
||||
);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
} else {
|
||||
let base = format!("{}BuiltinCall [INVALID P({})]", indent, params_index);
|
||||
align_comment(
|
||||
&base,
|
||||
"ERROR: Invalid builtin call parameters",
|
||||
config.comment_column,
|
||||
)
|
||||
}
|
||||
}
|
||||
Instruction::FunctionCall { params_index } => {
|
||||
if let Some(params) = instruction_data.get_function_call_params(*params_index) {
|
||||
let args_str = params
|
||||
.arg_registers()
|
||||
.iter()
|
||||
.map(|&r| format!("r{}", r))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
let func_name = program
|
||||
.rule_infos
|
||||
.get(params.func_rule_index as usize)
|
||||
.map(|info| info.name.as_str())
|
||||
.unwrap_or("<invalid>");
|
||||
|
||||
let base = format!(
|
||||
"{}FunctionCall r{} ← {}({})",
|
||||
indent, params.dest, func_name, args_str
|
||||
);
|
||||
let comment = format!(
|
||||
"Call function '{}' (R{}) with {} args",
|
||||
func_name, params.func_rule_index, params.num_args
|
||||
);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
} else {
|
||||
let base = format!("{}FunctionCall [INVALID P({})]", indent, params_index);
|
||||
align_comment(
|
||||
&base,
|
||||
"ERROR: Invalid function call parameters",
|
||||
config.comment_column,
|
||||
)
|
||||
}
|
||||
}
|
||||
Instruction::HostAwait { dest, arg, id } => {
|
||||
let base = format!(
|
||||
"{}HostAwait r{} ← await r{} (id r{})",
|
||||
indent, dest, arg, id
|
||||
);
|
||||
align_comment(
|
||||
&base,
|
||||
&format!(
|
||||
"Suspend and request host result using r{} with identifier r{}",
|
||||
arg, id
|
||||
),
|
||||
config.comment_column,
|
||||
)
|
||||
}
|
||||
Instruction::Return { value } => {
|
||||
let base = format!("{}Return return r{}", indent, value);
|
||||
let comment = format!("Return value from r{}", value);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::ObjectSet { obj, key, value } => {
|
||||
let base = format!("{}ObjectSet r{}[r{}] ← r{}", indent, obj, key, value);
|
||||
let comment = format!("Set field r{}[r{}] = r{}", obj, key, value);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::ObjectCreate { params_index } => {
|
||||
let params = program
|
||||
.instruction_data
|
||||
.get_object_create_params(*params_index);
|
||||
let base = format!(
|
||||
"{}ObjectCreate r{} ← {{...}}",
|
||||
indent,
|
||||
params.map_or(0, |p| p.dest)
|
||||
);
|
||||
let comment = match params {
|
||||
Some(p) => format!(
|
||||
"Create object with {} fields (P{})",
|
||||
p.field_count(),
|
||||
params_index
|
||||
),
|
||||
None => format!("Create object (P{} - INVALID)", params_index),
|
||||
};
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::Index {
|
||||
dest,
|
||||
container,
|
||||
key,
|
||||
} => {
|
||||
let base = format!(
|
||||
"{}Index r{} ← r{}[r{}]",
|
||||
indent, dest, container, key
|
||||
);
|
||||
let comment = format!("Index operation: get r{}[r{}]", container, key);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::IndexLiteral {
|
||||
dest,
|
||||
container,
|
||||
literal_idx,
|
||||
} => {
|
||||
let base = format!(
|
||||
"{}IndexLiteral r{} ← r{}[L{}]",
|
||||
indent, dest, container, literal_idx
|
||||
);
|
||||
let comment = if *literal_idx < program.literals.len() as u16 {
|
||||
let literal_json = serde_json::to_string(&program.literals[*literal_idx as usize])
|
||||
.unwrap_or_else(|_| "<invalid>".to_string());
|
||||
format!("Index with literal key: r{}[{}]", container, literal_json)
|
||||
} else {
|
||||
format!(
|
||||
"Index with literal: r{}[L{}] (invalid index)",
|
||||
container, literal_idx
|
||||
)
|
||||
};
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::ArrayNew { dest } => {
|
||||
let base = format!("{}ArrayNew r{} ← []", indent, dest);
|
||||
align_comment(&base, "Create new empty array", config.comment_column)
|
||||
}
|
||||
Instruction::ArrayPush { arr, value } => {
|
||||
let base = format!("{}ArrayPush r{}.push(r{})", indent, arr, value);
|
||||
let comment = format!("Append r{} to array r{}", value, arr);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::ArrayCreate { params_index } => {
|
||||
if let Some(params) = instruction_data.get_array_create_params(*params_index) {
|
||||
let elements = params
|
||||
.element_registers()
|
||||
.iter()
|
||||
.map(|r| format!("r{}", r))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let base = format!("{}ArrayCreate r{} ← [{}]", indent, params.dest, elements);
|
||||
let comment = format!(
|
||||
"Create array from {} elements (undefined if any element is undefined)",
|
||||
params.element_count()
|
||||
);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
} else {
|
||||
format!("{}ArrayCreate <invalid params P{}>", indent, params_index)
|
||||
}
|
||||
}
|
||||
Instruction::SetNew { dest } => {
|
||||
let base = format!("{}SetNew r{} ← set()", indent, dest);
|
||||
align_comment(&base, "Create new empty set", config.comment_column)
|
||||
}
|
||||
Instruction::SetAdd { set, value } => {
|
||||
let base = format!("{}SetAdd r{} ∪= r{}", indent, set, value);
|
||||
let comment = format!("Add r{} to set r{}", value, set);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::SetCreate { params_index } => {
|
||||
if let Some(params) = instruction_data.get_set_create_params(*params_index) {
|
||||
let elements = params
|
||||
.element_registers()
|
||||
.iter()
|
||||
.map(|r| format!("r{}", r))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let base = format!("{}SetCreate r{} ← {{{}}}", indent, params.dest, elements);
|
||||
let comment = format!(
|
||||
"Create set from {} elements (undefined if any element is undefined)",
|
||||
params.element_count()
|
||||
);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
} else {
|
||||
format!("{}SetCreate <invalid params P{}>", indent, params_index)
|
||||
}
|
||||
}
|
||||
Instruction::Contains {
|
||||
dest,
|
||||
collection,
|
||||
value,
|
||||
} => {
|
||||
let base = format!(
|
||||
"{}Contains r{} ← (r{} ∈ r{})",
|
||||
indent, dest, value, collection
|
||||
);
|
||||
let comment = format!("Membership test: r{} in r{}", value, collection);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::Count { dest, collection } => {
|
||||
let base = format!("{}Count r{} ← count(r{})", indent, dest, collection);
|
||||
let comment = format!("Get count/length of collection r{}", collection);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::AssertCondition { condition } => {
|
||||
let base = format!("{}Assert assert r{}", indent, condition);
|
||||
let comment = format!("Assert r{} is true (exit if false/undefined)", condition);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::AssertNotUndefined { register } => {
|
||||
let base = format!(
|
||||
"{}AssertNotUndefined assert_not_undefined r{}",
|
||||
indent, register
|
||||
);
|
||||
let comment = format!("Assert r{} is not undefined (exit if undefined)", register);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::LoopStart { params_index } => {
|
||||
if let Some(params) = instruction_data.get_loop_params(*params_index) {
|
||||
let mode_str = match params.mode {
|
||||
LoopMode::Any => "any",
|
||||
LoopMode::Every => "every",
|
||||
LoopMode::ForEach => "foreach",
|
||||
};
|
||||
let base = format!(
|
||||
"{}LoopStart {} r{},r{} in r{} → r{} {{",
|
||||
indent,
|
||||
mode_str,
|
||||
params.key_reg,
|
||||
params.value_reg,
|
||||
params.collection,
|
||||
params.result_reg
|
||||
);
|
||||
let comment = format!(
|
||||
"{} loop over r{}, body: {}-{} (P{})",
|
||||
mode_str, params.collection, params.body_start, params.loop_end, params_index
|
||||
);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
} else {
|
||||
let base = format!("{}LoopStart [INVALID P({})] {{", indent, params_index);
|
||||
align_comment(
|
||||
&base,
|
||||
"ERROR: Invalid loop parameters",
|
||||
config.comment_column,
|
||||
)
|
||||
}
|
||||
}
|
||||
Instruction::LoopNext {
|
||||
body_start,
|
||||
loop_end,
|
||||
} => {
|
||||
let base = format!(
|
||||
"{}}} continue → {} or exit → {}",
|
||||
indent, body_start, loop_end
|
||||
);
|
||||
let comment = format!(
|
||||
"Next iteration or exit loop (body:{}-{})",
|
||||
body_start, loop_end
|
||||
);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::CallRule { dest, rule_index } => {
|
||||
let rule_name = program
|
||||
.rule_infos
|
||||
.get(*rule_index as usize)
|
||||
.map(|info| info.name.as_str())
|
||||
.unwrap_or("<invalid>");
|
||||
|
||||
let base = format!("{}CallRule r{} ← {}", indent, dest, rule_name);
|
||||
let comment = format!("Call rule '{}' (R{}) with caching", rule_name, rule_index);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::RuleInit {
|
||||
result_reg,
|
||||
rule_index,
|
||||
} => {
|
||||
let rule_name = program
|
||||
.rule_infos
|
||||
.get(*rule_index as usize)
|
||||
.map(|info| info.name.as_str())
|
||||
.unwrap_or("<invalid>");
|
||||
|
||||
let base = format!("{}RuleInit {} → r{} {{", indent, rule_name, result_reg);
|
||||
let comment = format!(
|
||||
"Initialize rule '{}' (R{}) evaluation",
|
||||
rule_name, rule_index
|
||||
);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::RuleReturn {} => {
|
||||
let base = format!("{}}} return from rule", indent);
|
||||
align_comment(&base, "End of rule evaluation", config.comment_column)
|
||||
}
|
||||
Instruction::ChainedIndex { params_index } => {
|
||||
let (base, comment) =
|
||||
if let Some(params) = instruction_data.get_chained_index_params(*params_index) {
|
||||
let chain_parts: Vec<String> = params
|
||||
.path_components
|
||||
.iter()
|
||||
.map(|component| match component {
|
||||
crate::rvm::instructions::LiteralOrRegister::Literal(idx) => {
|
||||
if let Some(literal) = program.literals.get(*idx as usize) {
|
||||
match literal {
|
||||
crate::Value::String(s) => format!(".{}", s.as_ref()),
|
||||
other => format!(
|
||||
"[{}]",
|
||||
serde_json::to_string(other)
|
||||
.unwrap_or_else(|_| "?".to_string())
|
||||
),
|
||||
}
|
||||
} else {
|
||||
format!("[L{}?]", idx)
|
||||
}
|
||||
}
|
||||
crate::rvm::instructions::LiteralOrRegister::Register(reg) => {
|
||||
format!("[r{}]", reg)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let chain_display = if chain_parts.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" r{}{}", params.root, chain_parts.join(""))
|
||||
};
|
||||
|
||||
let base_str = format!(
|
||||
"{}ChainedIndex r{} ← r{}{}",
|
||||
indent, params.dest, params.root, chain_display
|
||||
);
|
||||
let comment_str = format!(
|
||||
"Multi-level chained indexing: r{} → r{}",
|
||||
params.root, params.dest
|
||||
);
|
||||
(base_str, comment_str)
|
||||
} else {
|
||||
let base_str = format!("{}ChainedIndex chained_index", indent);
|
||||
let comment_str = "Multi-level chained indexing (invalid params)".to_string();
|
||||
(base_str, comment_str)
|
||||
};
|
||||
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
}
|
||||
Instruction::VirtualDataDocumentLookup { .. } => {
|
||||
let base = format!(
|
||||
"{}VirtualDataDocumentLookup virtual_data_document_lookup",
|
||||
indent
|
||||
);
|
||||
align_comment(
|
||||
&base,
|
||||
"Lookup in data namespace virtual documents",
|
||||
config.comment_column,
|
||||
)
|
||||
}
|
||||
Instruction::DestructuringSuccess {} => {
|
||||
let base = format!("{}DestructuringSuccess ✓", indent);
|
||||
align_comment(
|
||||
&base,
|
||||
"Parameter destructuring validated",
|
||||
config.comment_column,
|
||||
)
|
||||
}
|
||||
Instruction::Halt {} => {
|
||||
let base = format!("{}Halt halt", indent);
|
||||
align_comment(&base, "Stop execution", config.comment_column)
|
||||
}
|
||||
Instruction::ComprehensionBegin { params_index } => {
|
||||
if let Some(params) = instruction_data.get_comprehension_begin_params(*params_index) {
|
||||
let mode_str = match params.mode {
|
||||
crate::rvm::instructions::ComprehensionMode::Array => "array",
|
||||
crate::rvm::instructions::ComprehensionMode::Set => "set",
|
||||
crate::rvm::instructions::ComprehensionMode::Object => "object",
|
||||
};
|
||||
let (source_desc, result_desc) = if params.collection_reg == params.result_reg {
|
||||
(
|
||||
format!("r{}", params.collection_reg),
|
||||
format!("r{}", params.result_reg),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
format!("r{} (src)", params.collection_reg),
|
||||
format!("r{} (dst)", params.result_reg),
|
||||
)
|
||||
};
|
||||
let base = format!(
|
||||
"{}CompBegin {} {} → {} k:{} v:{} {{",
|
||||
indent, mode_str, source_desc, result_desc, params.key_reg, params.value_reg
|
||||
);
|
||||
let comment = format!(
|
||||
"{} comprehension in r{}, body: {}-{} (P{})",
|
||||
mode_str,
|
||||
params.collection_reg,
|
||||
params.body_start,
|
||||
params.comprehension_end,
|
||||
params_index
|
||||
);
|
||||
align_comment(&base, &comment, config.comment_column)
|
||||
} else {
|
||||
let base = format!("{}CompBegin [INVALID P({})] {{", indent, params_index);
|
||||
align_comment(
|
||||
&base,
|
||||
"ERROR: Invalid comprehension parameters",
|
||||
config.comment_column,
|
||||
)
|
||||
}
|
||||
}
|
||||
Instruction::ComprehensionYield { value_reg, key_reg } => {
|
||||
let base = match key_reg {
|
||||
Some(k) => format!("{}CompYield r{} r{}", indent, k, value_reg),
|
||||
None => format!("{}CompYield r{}", indent, value_reg),
|
||||
};
|
||||
align_comment(&base, "Yield value to comprehension", config.comment_column)
|
||||
}
|
||||
Instruction::ComprehensionEnd {} => {
|
||||
let base = format!("{}}} CompEnd", indent);
|
||||
align_comment(&base, "End comprehension block", config.comment_column)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate compact tabular assembly listing
|
||||
pub fn generate_tabular_assembly_listing(
|
||||
program: &Program,
|
||||
_config: &AssemblyListingConfig,
|
||||
) -> String {
|
||||
let mut output = String::new();
|
||||
let mut indent_level: usize = 0;
|
||||
|
||||
// Add header
|
||||
writeln!(output, "; RVM Assembly (Tabular Format)").unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
"; {} instructions, {} literals",
|
||||
program.instructions.len(),
|
||||
program.literals.len()
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(output, ";").unwrap();
|
||||
writeln!(output, "; PC | Instruction | Operation").unwrap();
|
||||
writeln!(output, ";-----|--------------|----------").unwrap();
|
||||
|
||||
for (pc, instruction) in program.instructions.iter().enumerate() {
|
||||
// Handle loop indentation
|
||||
match instruction {
|
||||
Instruction::LoopNext { .. } => {
|
||||
indent_level = indent_level.saturating_sub(1);
|
||||
}
|
||||
Instruction::RuleReturn { .. } => {
|
||||
indent_level = indent_level.saturating_sub(1);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let indent = " ".repeat(indent_level * 2); // Smaller indent for tabular format
|
||||
|
||||
// Format in tabular style
|
||||
let addr_str = format!("{:03}", pc);
|
||||
let inst_name = get_instruction_name(instruction);
|
||||
let operation =
|
||||
format_operation_compact(instruction, &indent, &program.instruction_data, program);
|
||||
|
||||
writeln!(output, "{:>4} | {:12} | {}", addr_str, inst_name, operation).unwrap();
|
||||
|
||||
// Increase indentation for loop/rule starts
|
||||
match instruction {
|
||||
Instruction::LoopStart { .. } => {
|
||||
indent_level += 1;
|
||||
}
|
||||
Instruction::RuleInit { .. } => {
|
||||
indent_level += 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn get_instruction_name(instruction: &Instruction) -> &'static str {
|
||||
match instruction {
|
||||
Instruction::Load { .. } => "LOAD",
|
||||
Instruction::LoadTrue { .. } => "LOAD_TRUE",
|
||||
Instruction::LoadFalse { .. } => "LOAD_FALSE",
|
||||
Instruction::LoadNull { .. } => "LOAD_NULL",
|
||||
Instruction::LoadBool { .. } => "LOAD_BOOL",
|
||||
Instruction::LoadData { .. } => "LOAD_DATA",
|
||||
Instruction::LoadInput { .. } => "LOAD_INPUT",
|
||||
Instruction::Move { .. } => "MOVE",
|
||||
Instruction::Add { .. } => "ADD",
|
||||
Instruction::Sub { .. } => "SUB",
|
||||
Instruction::Mul { .. } => "MUL",
|
||||
Instruction::Div { .. } => "DIV",
|
||||
Instruction::Mod { .. } => "MOD",
|
||||
Instruction::Eq { .. } => "EQ",
|
||||
Instruction::Ne { .. } => "NE",
|
||||
Instruction::Lt { .. } => "LT",
|
||||
Instruction::Le { .. } => "LE",
|
||||
Instruction::Gt { .. } => "GT",
|
||||
Instruction::Ge { .. } => "GE",
|
||||
Instruction::And { .. } => "AND",
|
||||
Instruction::Or { .. } => "OR",
|
||||
Instruction::Not { .. } => "NOT",
|
||||
Instruction::BuiltinCall { .. } => "BUILTIN_CALL",
|
||||
Instruction::FunctionCall { .. } => "FUNC_CALL",
|
||||
Instruction::HostAwait { .. } => "HOST_AWAIT",
|
||||
Instruction::Return { .. } => "RETURN",
|
||||
Instruction::ObjectSet { .. } => "OBJ_SET",
|
||||
Instruction::ObjectCreate { .. } => "OBJ_CREATE",
|
||||
Instruction::Index { .. } => "INDEX",
|
||||
Instruction::IndexLiteral { .. } => "INDEX_LIT",
|
||||
Instruction::ArrayNew { .. } => "ARRAY_NEW",
|
||||
Instruction::ArrayPush { .. } => "ARRAY_PUSH",
|
||||
Instruction::ArrayCreate { .. } => "ARRAY_CREATE",
|
||||
Instruction::SetNew { .. } => "SET_NEW",
|
||||
Instruction::SetAdd { .. } => "SET_ADD",
|
||||
Instruction::SetCreate { .. } => "SET_CREATE",
|
||||
Instruction::Contains { .. } => "CONTAINS",
|
||||
Instruction::Count { .. } => "COUNT",
|
||||
Instruction::AssertCondition { .. } => "ASSERT",
|
||||
Instruction::AssertNotUndefined { .. } => "ASSERT_NOT_UNDEF",
|
||||
Instruction::LoopStart { .. } => "LOOP_START",
|
||||
Instruction::LoopNext { .. } => "LOOP_NEXT",
|
||||
Instruction::CallRule { .. } => "CALL_RULE",
|
||||
Instruction::RuleInit { .. } => "RULE_INIT",
|
||||
Instruction::RuleReturn { .. } => "RULE_RET",
|
||||
Instruction::DestructuringSuccess {} => "DESTRUCT_SUCCESS",
|
||||
Instruction::ChainedIndex { .. } => "CHAINED_INDEX",
|
||||
Instruction::VirtualDataDocumentLookup { .. } => "VIRTUAL_DATA_DOC_LOOKUP",
|
||||
Instruction::Halt {} => "HALT",
|
||||
Instruction::ComprehensionBegin { .. } => "COMP_BEGIN",
|
||||
Instruction::ComprehensionYield { .. } => "COMP_YIELD",
|
||||
Instruction::ComprehensionEnd {} => "COMP_END",
|
||||
}
|
||||
}
|
||||
|
||||
fn format_operation_compact(
|
||||
instruction: &Instruction,
|
||||
indent: &str,
|
||||
instruction_data: &InstructionData,
|
||||
_program: &Program,
|
||||
) -> String {
|
||||
match instruction {
|
||||
Instruction::Load { dest, literal_idx } => {
|
||||
format!("{}r{} ← L{}", indent, dest, literal_idx)
|
||||
}
|
||||
Instruction::LoadInput { dest } => {
|
||||
format!("{}r{} ← input", indent, dest)
|
||||
}
|
||||
Instruction::LoadData { dest } => {
|
||||
format!("{}r{} ← data", indent, dest)
|
||||
}
|
||||
Instruction::Move { dest, src } => {
|
||||
format!("{}r{} ← r{}", indent, dest, src)
|
||||
}
|
||||
Instruction::Add { dest, left, right } => {
|
||||
format!("{}r{} ← r{} + r{}", indent, dest, left, right)
|
||||
}
|
||||
Instruction::Index {
|
||||
dest,
|
||||
container,
|
||||
key,
|
||||
} => {
|
||||
format!("{}r{} ← r{}[r{}]", indent, dest, container, key)
|
||||
}
|
||||
Instruction::IndexLiteral {
|
||||
dest,
|
||||
container,
|
||||
literal_idx,
|
||||
} => {
|
||||
format!("{}r{} ← r{}[L{}]", indent, dest, container, literal_idx)
|
||||
}
|
||||
Instruction::LoopStart { params_index } => {
|
||||
if let Some(params) = instruction_data.get_loop_params(*params_index) {
|
||||
format!(
|
||||
"{}loop r{} in r{} {{",
|
||||
indent, params.value_reg, params.collection
|
||||
)
|
||||
} else {
|
||||
format!("{}loop P({}) {{", indent, params_index)
|
||||
}
|
||||
}
|
||||
Instruction::LoopNext { .. } => {
|
||||
format!("{}}}", indent)
|
||||
}
|
||||
Instruction::CallRule { dest, rule_index } => {
|
||||
format!("{}r{} ← rule_{}", indent, dest, rule_index)
|
||||
}
|
||||
Instruction::HostAwait { dest, arg, id } => {
|
||||
format!("{}await r{} → r{} (id r{})", indent, arg, dest, id)
|
||||
}
|
||||
Instruction::RuleInit {
|
||||
result_reg,
|
||||
rule_index,
|
||||
} => {
|
||||
format!("{}rule_{} → r{} {{", indent, rule_index, result_reg)
|
||||
}
|
||||
Instruction::RuleReturn {} => {
|
||||
format!("{}}}", indent)
|
||||
}
|
||||
Instruction::DestructuringSuccess {} => {
|
||||
format!("{}✓ destructuring validated", indent)
|
||||
}
|
||||
_ => {
|
||||
// For other instructions, use a simplified version
|
||||
format!(
|
||||
"{}{}",
|
||||
indent,
|
||||
instruction
|
||||
.to_string()
|
||||
.replace("R(", "r")
|
||||
.replace(")", "")
|
||||
.replace("L(", "L")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/rvm/program/mod.rs
Normal file
19
src/rvm/program/mod.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
mod core;
|
||||
mod listing;
|
||||
mod recompile;
|
||||
mod rule_tree;
|
||||
mod serialization;
|
||||
mod types;
|
||||
|
||||
pub use core::Program;
|
||||
pub use listing::{
|
||||
generate_assembly_listing, generate_tabular_assembly_listing, AssemblyListingConfig,
|
||||
};
|
||||
pub(crate) use serialization::value::{binaries_to_values, BinaryValue};
|
||||
pub use serialization::{DeserializationResult, VersionedProgram};
|
||||
pub use types::{
|
||||
BuiltinInfo, FunctionInfo, ProgramMetadata, RuleInfo, RuleType, SourceFile, SpanInfo,
|
||||
};
|
||||
22
src/rvm/program/recompile.rs
Normal file
22
src/rvm/program/recompile.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use super::Program;
|
||||
use alloc::string::{String, ToString};
|
||||
|
||||
impl Program {
|
||||
/// Compile a partial deserialized program to a complete one
|
||||
///
|
||||
/// This method takes a partial program (containing only entry_points and sources)
|
||||
/// and recompiles it to create a complete program with all instructions and data.
|
||||
pub fn compile_from_partial(partial_program: Program) -> Result<Program, String> {
|
||||
if partial_program.entry_points.is_empty() {
|
||||
return Err("Partial program must contain entry points".to_string());
|
||||
}
|
||||
if partial_program.sources.is_empty() {
|
||||
return Err("Partial program must contain sources".to_string());
|
||||
}
|
||||
|
||||
Err("Recompilation from partial program is not yet implemented".to_string())
|
||||
}
|
||||
}
|
||||
105
src/rvm/program/rule_tree.rs
Normal file
105
src/rvm/program/rule_tree.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use alloc::format;
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::vec::Vec;
|
||||
use anyhow::Result as AnyResult;
|
||||
|
||||
use crate::value::Value;
|
||||
|
||||
use super::Program;
|
||||
|
||||
impl Program {
|
||||
/// Add a rule to the rule tree
|
||||
/// path: Package path components (e.g., ["p1", "p2"] for data.p1.p2.rule)
|
||||
/// rule_name: Rule name (e.g., "rule")
|
||||
/// rule_index: Index of the rule in rule_infos
|
||||
pub fn add_rule_to_tree(
|
||||
&mut self,
|
||||
path: &[String],
|
||||
rule_name: &str,
|
||||
rule_index: usize,
|
||||
) -> AnyResult<()> {
|
||||
let mut full_path = Vec::with_capacity(path.len() + 1);
|
||||
full_path.extend(path.iter().map(|s| s.as_str()));
|
||||
full_path.push(rule_name);
|
||||
|
||||
let target = self.rule_tree.make_or_get_value_mut(&full_path)?;
|
||||
*target = Value::Number(rule_index.into());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check for conflicts between rule tree and data
|
||||
/// Returns an error if any rule path conflicts with data paths
|
||||
pub fn check_rule_data_conflicts(&self, data: &Value) -> Result<(), crate::rvm::vm::VmError> {
|
||||
let actual_rule_tree = &self.rule_tree["data"];
|
||||
|
||||
match actual_rule_tree {
|
||||
Value::Undefined => return Ok(()),
|
||||
Value::Object(rule_obj) if rule_obj.is_empty() => return Ok(()),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Self::check_conflicts_recursive(actual_rule_tree, data, &mut Vec::new())
|
||||
}
|
||||
|
||||
fn check_conflicts_recursive(
|
||||
rule_tree: &Value,
|
||||
data: &Value,
|
||||
current_path: &mut Vec<String>,
|
||||
) -> Result<(), crate::rvm::vm::VmError> {
|
||||
match rule_tree {
|
||||
Value::Object(rule_obj) => {
|
||||
for (key, rule_value) in rule_obj.iter() {
|
||||
if let Value::String(key_str) = key {
|
||||
current_path.push(key_str.to_string());
|
||||
|
||||
let data_value = &data[key];
|
||||
|
||||
match rule_value {
|
||||
Value::Number(_) => {
|
||||
if data_value != &Value::Undefined {
|
||||
return Err(crate::rvm::vm::VmError::RuleDataConflict(format!(
|
||||
"Conflict: rule defines path '{}' but data also provides this path",
|
||||
current_path.join("."),
|
||||
)));
|
||||
}
|
||||
}
|
||||
Value::Object(_) => {
|
||||
if let Value::Object(_) = data_value {
|
||||
Self::check_conflicts_recursive(
|
||||
rule_value,
|
||||
data_value,
|
||||
current_path,
|
||||
)?;
|
||||
} else if data_value != &Value::Undefined {
|
||||
return Err(crate::rvm::vm::VmError::RuleDataConflict(format!(
|
||||
"Conflict: rule defines subpaths under '{}' but data provides a non-object value at this path",
|
||||
current_path.join("."),
|
||||
)));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(crate::rvm::vm::VmError::RuleDataConflict(format!(
|
||||
"Invalid rule tree structure at path '{}'",
|
||||
current_path.join("."),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
current_path.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(crate::rvm::vm::VmError::RuleDataConflict(
|
||||
"Rule tree root must be an object".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
482
src/rvm/program/serialization/binary.rs
Normal file
482
src/rvm/program/serialization/binary.rs
Normal file
@@ -0,0 +1,482 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use alloc::format;
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::vec::Vec;
|
||||
use bincode::config::standard;
|
||||
use bincode::serde::{decode_from_slice, encode_to_vec};
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use super::super::types::SourceFile;
|
||||
use super::{DeserializationResult, Program};
|
||||
use crate::value::Value;
|
||||
|
||||
use super::value::{
|
||||
binaries_to_values, binary_to_value, BinaryValue, BinaryValueRef, BinaryValueSlice,
|
||||
};
|
||||
type ArtifactData = (IndexMap<String, usize>, Vec<SourceFile>, bool);
|
||||
|
||||
impl Program {
|
||||
/// Serialize program to binary format.
|
||||
/// Uses pure bincode for all sections now that `Value` supports serde.
|
||||
pub fn serialize_binary(&self) -> Result<Vec<u8>, String> {
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
buffer.extend_from_slice(&Self::MAGIC);
|
||||
buffer.extend_from_slice(&Self::SERIALIZATION_VERSION.to_le_bytes());
|
||||
|
||||
let entry_points_bin = encode_to_vec(&self.entry_points, standard())
|
||||
.map_err(|e| format!("Entry points bincode serialization failed: {}", e))?;
|
||||
|
||||
let sources_bin = encode_to_vec(&self.sources, standard())
|
||||
.map_err(|e| format!("Sources bincode serialization failed: {}", e))?;
|
||||
|
||||
let literals_bin = encode_to_vec(BinaryValueSlice(self.literals.as_slice()), standard())
|
||||
.map_err(|e| format!("Literals bincode serialization failed: {}", e))?;
|
||||
|
||||
let rule_tree_bin = encode_to_vec(BinaryValueRef(&self.rule_tree), standard())
|
||||
.map_err(|e| format!("Rule tree bincode serialization failed: {}", e))?;
|
||||
|
||||
let binary_data = encode_to_vec(self, standard())
|
||||
.map_err(|e| format!("Program structure binary serialization failed: {}", e))?;
|
||||
|
||||
buffer.extend_from_slice(&(entry_points_bin.len() as u32).to_le_bytes());
|
||||
buffer.extend_from_slice(&(sources_bin.len() as u32).to_le_bytes());
|
||||
buffer.extend_from_slice(&(literals_bin.len() as u32).to_le_bytes());
|
||||
buffer.extend_from_slice(&(rule_tree_bin.len() as u32).to_le_bytes());
|
||||
buffer.push(if self.rego_v0 { 1 } else { 0 });
|
||||
|
||||
buffer.extend_from_slice(&entry_points_bin);
|
||||
buffer.extend_from_slice(&sources_bin);
|
||||
buffer.extend_from_slice(&literals_bin);
|
||||
buffer.extend_from_slice(&rule_tree_bin);
|
||||
|
||||
buffer.extend_from_slice(&(binary_data.len() as u32).to_le_bytes());
|
||||
buffer.extend_from_slice(&binary_data);
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
/// Deserialize only the artifact section (entry_points and sources) from binary format
|
||||
pub fn deserialize_artifacts_only(data: &[u8]) -> Result<ArtifactData, String> {
|
||||
if data.len() < 9 {
|
||||
return Err("Data too short for artifact header".to_string());
|
||||
}
|
||||
|
||||
if data[0..4] != Self::MAGIC {
|
||||
return Err("Invalid file format - magic number mismatch".to_string());
|
||||
}
|
||||
|
||||
let version = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
|
||||
|
||||
match version {
|
||||
1 => {
|
||||
if data.len() < 17 {
|
||||
return Err("Data too short for artifact header".to_string());
|
||||
}
|
||||
|
||||
let entry_points_len =
|
||||
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
|
||||
let sources_len =
|
||||
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
|
||||
let rego_v0 = data[16] != 0;
|
||||
let entry_points_start = 17;
|
||||
let sources_start = entry_points_start + entry_points_len;
|
||||
let sources_end = sources_start + sources_len;
|
||||
|
||||
if data.len() < sources_end {
|
||||
return Err("Data truncated in artifact section".to_string());
|
||||
}
|
||||
|
||||
let entry_points =
|
||||
decode_from_slice(&data[entry_points_start..sources_start], standard())
|
||||
.map(|(value, _)| value)
|
||||
.unwrap_or_else(|_| IndexMap::new());
|
||||
|
||||
let sources = decode_from_slice(&data[sources_start..sources_end], standard())
|
||||
.map(|(value, _)| value)
|
||||
.unwrap_or_else(|_| Vec::new());
|
||||
|
||||
Ok((entry_points, sources, rego_v0))
|
||||
}
|
||||
2 | 3 => {
|
||||
if data.len() < 25 {
|
||||
return Err("Data too short for artifact header".to_string());
|
||||
}
|
||||
|
||||
let entry_points_len =
|
||||
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
|
||||
let sources_len =
|
||||
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
|
||||
let literals_len =
|
||||
u32::from_le_bytes([data[16], data[17], data[18], data[19]]) as usize;
|
||||
let rule_tree_len =
|
||||
u32::from_le_bytes([data[20], data[21], data[22], data[23]]) as usize;
|
||||
let rego_v0 = data[24] != 0;
|
||||
|
||||
let entry_points_start = 25;
|
||||
let sources_start = entry_points_start + entry_points_len;
|
||||
let literals_start = sources_start + sources_len;
|
||||
let rule_tree_start = literals_start + literals_len;
|
||||
let rule_tree_end = rule_tree_start + rule_tree_len;
|
||||
|
||||
if data.len() < rule_tree_end {
|
||||
return Err("Data truncated in artifact section".to_string());
|
||||
}
|
||||
|
||||
let entry_points =
|
||||
decode_from_slice(&data[entry_points_start..sources_start], standard())
|
||||
.map(|(value, _)| value)
|
||||
.unwrap_or_else(|_| IndexMap::new());
|
||||
|
||||
let sources = decode_from_slice(&data[sources_start..literals_start], standard())
|
||||
.map(|(value, _)| value)
|
||||
.unwrap_or_else(|_| Vec::new());
|
||||
|
||||
Ok((entry_points, sources, rego_v0))
|
||||
}
|
||||
v => Err(format!("Unsupported version {}", v)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserialize program from binary format with version checking
|
||||
pub fn deserialize_binary(data: &[u8]) -> Result<DeserializationResult, String> {
|
||||
if data.len() < 9 {
|
||||
return Err("Data too short for header".to_string());
|
||||
}
|
||||
|
||||
if data[0..4] != Self::MAGIC {
|
||||
return Err("Invalid file format - magic number mismatch".to_string());
|
||||
}
|
||||
|
||||
let version = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
|
||||
if version > Self::SERIALIZATION_VERSION {
|
||||
return Err(format!(
|
||||
"Unsupported version {}. Maximum supported version is {}",
|
||||
version,
|
||||
Self::SERIALIZATION_VERSION
|
||||
));
|
||||
}
|
||||
|
||||
match version {
|
||||
1 => {
|
||||
if data.len() < 25 {
|
||||
return Err("Data too short for header".to_string());
|
||||
}
|
||||
|
||||
let entry_points_len =
|
||||
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
|
||||
let sources_len =
|
||||
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
|
||||
let rego_v0 = data[16] != 0;
|
||||
let entry_points_start = 17;
|
||||
let sources_start = entry_points_start + entry_points_len;
|
||||
let binary_len_start = sources_start + sources_len;
|
||||
|
||||
if data.len() < binary_len_start + 4 {
|
||||
return Err("Data too short for binary length".to_string());
|
||||
}
|
||||
|
||||
let binary_len = u32::from_le_bytes([
|
||||
data[binary_len_start],
|
||||
data[binary_len_start + 1],
|
||||
data[binary_len_start + 2],
|
||||
data[binary_len_start + 3],
|
||||
]) as usize;
|
||||
|
||||
let json_len_start = binary_len_start + 4 + binary_len;
|
||||
if data.len() < json_len_start + 4 {
|
||||
return Err("Data too short for JSON length".to_string());
|
||||
}
|
||||
|
||||
let json_len = u32::from_le_bytes([
|
||||
data[json_len_start],
|
||||
data[json_len_start + 1],
|
||||
data[json_len_start + 2],
|
||||
data[json_len_start + 3],
|
||||
]) as usize;
|
||||
|
||||
let total_expected = json_len_start + 4 + json_len;
|
||||
if data.len() < total_expected {
|
||||
return Err("Data truncated".to_string());
|
||||
}
|
||||
|
||||
let binary_start = binary_len_start + 4;
|
||||
let json_start = json_len_start + 4;
|
||||
|
||||
let entry_points =
|
||||
decode_from_slice(&data[entry_points_start..sources_start], standard())
|
||||
.map(|(value, _)| value)
|
||||
.map_err(|e| format!("Entry points deserialization failed: {}", e))?;
|
||||
|
||||
let sources = decode_from_slice(&data[sources_start..binary_len_start], standard())
|
||||
.map(|(value, _)| value)
|
||||
.map_err(|e| format!("Sources deserialization failed: {}", e))?;
|
||||
|
||||
let mut needs_recompilation = false;
|
||||
|
||||
let mut program = match decode_from_slice::<Program, _>(
|
||||
&data[binary_start..json_start],
|
||||
standard(),
|
||||
) {
|
||||
Ok((prog, _)) => prog,
|
||||
Err(_e) => {
|
||||
needs_recompilation = true;
|
||||
Program::new()
|
||||
}
|
||||
};
|
||||
|
||||
let (literals, rule_tree) = match serde_json::from_slice::<serde_json::Value>(
|
||||
&data[json_start..json_start + json_len],
|
||||
) {
|
||||
Ok(combined) => {
|
||||
let literals = combined
|
||||
.get("literals")
|
||||
.and_then(|v| serde_json::from_value::<Vec<Value>>(v.clone()).ok())
|
||||
.unwrap_or_else(|| {
|
||||
needs_recompilation = true;
|
||||
Vec::new()
|
||||
});
|
||||
|
||||
let rule_tree = combined
|
||||
.get("rule_tree")
|
||||
.and_then(|v| serde_json::from_value::<Value>(v.clone()).ok())
|
||||
.unwrap_or_else(|| {
|
||||
needs_recompilation = true;
|
||||
Value::new_object()
|
||||
});
|
||||
|
||||
(literals, rule_tree)
|
||||
}
|
||||
Err(_e) => {
|
||||
needs_recompilation = true;
|
||||
(Vec::new(), Value::new_object())
|
||||
}
|
||||
};
|
||||
|
||||
program.entry_points = entry_points;
|
||||
program.sources = sources;
|
||||
program.literals = literals;
|
||||
program.rule_tree = rule_tree;
|
||||
program.rego_v0 = rego_v0;
|
||||
program.needs_recompilation = needs_recompilation;
|
||||
|
||||
if !program.builtin_info_table.is_empty() {
|
||||
if let Err(_e) = program.initialize_resolved_builtins() {
|
||||
program.needs_recompilation = true;
|
||||
}
|
||||
}
|
||||
|
||||
if program.needs_recompilation {
|
||||
Ok(DeserializationResult::Partial(program))
|
||||
} else {
|
||||
Ok(DeserializationResult::Complete(program))
|
||||
}
|
||||
}
|
||||
2 | 3 => {
|
||||
if data.len() < 29 {
|
||||
return Err("Data too short for header".to_string());
|
||||
}
|
||||
|
||||
let entry_points_len =
|
||||
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
|
||||
let sources_len =
|
||||
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
|
||||
let literals_len =
|
||||
u32::from_le_bytes([data[16], data[17], data[18], data[19]]) as usize;
|
||||
let rule_tree_len =
|
||||
u32::from_le_bytes([data[20], data[21], data[22], data[23]]) as usize;
|
||||
let rego_v0 = data[24] != 0;
|
||||
|
||||
let entry_points_start = 25;
|
||||
let sources_start = entry_points_start + entry_points_len;
|
||||
let literals_start = sources_start + sources_len;
|
||||
let rule_tree_start = literals_start + literals_len;
|
||||
let binary_len_start = rule_tree_start + rule_tree_len;
|
||||
|
||||
if data.len() < binary_len_start + 4 {
|
||||
return Err("Data too short for binary length".to_string());
|
||||
}
|
||||
|
||||
let binary_len = u32::from_le_bytes([
|
||||
data[binary_len_start],
|
||||
data[binary_len_start + 1],
|
||||
data[binary_len_start + 2],
|
||||
data[binary_len_start + 3],
|
||||
]) as usize;
|
||||
|
||||
let binary_start = binary_len_start + 4;
|
||||
let binary_end = binary_start + binary_len;
|
||||
|
||||
if data.len() < binary_end {
|
||||
return Err("Data truncated".to_string());
|
||||
}
|
||||
|
||||
let entry_points =
|
||||
decode_from_slice(&data[entry_points_start..sources_start], standard())
|
||||
.map(|(value, _)| value)
|
||||
.map_err(|e| format!("Entry points deserialization failed: {}", e))?;
|
||||
|
||||
let sources = decode_from_slice(&data[sources_start..literals_start], standard())
|
||||
.map(|(value, _)| value)
|
||||
.map_err(|e| format!("Sources deserialization failed: {}", e))?;
|
||||
|
||||
let mut needs_recompilation = false;
|
||||
|
||||
let literals = match decode_from_slice::<Vec<BinaryValue>, _>(
|
||||
&data[literals_start..rule_tree_start],
|
||||
standard(),
|
||||
) {
|
||||
Ok((binary_literals, _)) => match binaries_to_values(binary_literals) {
|
||||
Ok(values) => values,
|
||||
Err(_e) => {
|
||||
needs_recompilation = true;
|
||||
Vec::new()
|
||||
}
|
||||
},
|
||||
Err(_e) => {
|
||||
needs_recompilation = true;
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
|
||||
let rule_tree = match decode_from_slice::<BinaryValue, _>(
|
||||
&data[rule_tree_start..binary_len_start],
|
||||
standard(),
|
||||
) {
|
||||
Ok((binary_tree, _)) => match binary_to_value(binary_tree) {
|
||||
Ok(value) => value,
|
||||
Err(_e) => {
|
||||
needs_recompilation = true;
|
||||
Value::new_object()
|
||||
}
|
||||
},
|
||||
Err(_e) => {
|
||||
needs_recompilation = true;
|
||||
Value::new_object()
|
||||
}
|
||||
};
|
||||
|
||||
let mut program = match decode_from_slice::<Program, _>(
|
||||
&data[binary_start..binary_end],
|
||||
standard(),
|
||||
) {
|
||||
Ok((prog, _)) => prog,
|
||||
Err(_e) => {
|
||||
needs_recompilation = true;
|
||||
Program::new()
|
||||
}
|
||||
};
|
||||
|
||||
program.entry_points = entry_points;
|
||||
program.sources = sources;
|
||||
program.literals = literals;
|
||||
program.rule_tree = rule_tree;
|
||||
program.rego_v0 = rego_v0;
|
||||
program.needs_recompilation = needs_recompilation;
|
||||
|
||||
if !program.builtin_info_table.is_empty() {
|
||||
if let Err(_e) = program.initialize_resolved_builtins() {
|
||||
program.needs_recompilation = true;
|
||||
}
|
||||
}
|
||||
|
||||
if program.needs_recompilation {
|
||||
Ok(DeserializationResult::Partial(program))
|
||||
} else {
|
||||
Ok(DeserializationResult::Complete(program))
|
||||
}
|
||||
}
|
||||
v => Err(format!("Unsupported version {}", v)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if data can be deserialized without actually deserializing
|
||||
pub fn can_deserialize(data: &[u8]) -> Result<bool, String> {
|
||||
if data.len() < 8 {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if data[0..4] != Self::MAGIC {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let version = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
|
||||
|
||||
match version {
|
||||
1..=3 => Ok(true),
|
||||
_ => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get file format information without deserializing
|
||||
pub fn get_file_info(data: &[u8]) -> Result<(u32, usize), String> {
|
||||
if data.len() < 9 {
|
||||
return Err("Data too short for header".to_string());
|
||||
}
|
||||
|
||||
if data[0..4] != Self::MAGIC {
|
||||
return Err("Invalid file format".to_string());
|
||||
}
|
||||
|
||||
let version = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
|
||||
|
||||
match version {
|
||||
1 => {
|
||||
if data.len() < 25 {
|
||||
return Err("Data too short for header".to_string());
|
||||
}
|
||||
|
||||
let entry_points_len =
|
||||
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
|
||||
let sources_len =
|
||||
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
|
||||
let binary_len_start = 17 + entry_points_len + sources_len;
|
||||
|
||||
if data.len() < binary_len_start + 4 {
|
||||
return Err("Data too short for binary length".to_string());
|
||||
}
|
||||
|
||||
let binary_len = u32::from_le_bytes([
|
||||
data[binary_len_start],
|
||||
data[binary_len_start + 1],
|
||||
data[binary_len_start + 2],
|
||||
data[binary_len_start + 3],
|
||||
]) as usize;
|
||||
|
||||
Ok((version, binary_len))
|
||||
}
|
||||
2 | 3 => {
|
||||
if data.len() < 29 {
|
||||
return Err("Data too short for header".to_string());
|
||||
}
|
||||
|
||||
let entry_points_len =
|
||||
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
|
||||
let sources_len =
|
||||
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
|
||||
let literals_len =
|
||||
u32::from_le_bytes([data[16], data[17], data[18], data[19]]) as usize;
|
||||
let rule_tree_len =
|
||||
u32::from_le_bytes([data[20], data[21], data[22], data[23]]) as usize;
|
||||
let binary_len_start =
|
||||
25 + entry_points_len + sources_len + literals_len + rule_tree_len;
|
||||
|
||||
if data.len() < binary_len_start + 4 {
|
||||
return Err("Data too short for binary length".to_string());
|
||||
}
|
||||
|
||||
let binary_len = u32::from_le_bytes([
|
||||
data[binary_len_start],
|
||||
data[binary_len_start + 1],
|
||||
data[binary_len_start + 2],
|
||||
data[binary_len_start + 3],
|
||||
]) as usize;
|
||||
|
||||
Ok((version, binary_len))
|
||||
}
|
||||
v => Err(format!("Unsupported version {}", v)),
|
||||
}
|
||||
}
|
||||
}
|
||||
196
src/rvm/program/serialization/json.rs
Normal file
196
src/rvm/program/serialization/json.rs
Normal file
@@ -0,0 +1,196 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use alloc::format;
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::super::types::SourceFile;
|
||||
use super::super::types::{BuiltinInfo, ProgramMetadata, RuleInfo, SpanInfo};
|
||||
use super::Program;
|
||||
use crate::rvm::instructions::InstructionData;
|
||||
use crate::rvm::Instruction;
|
||||
use crate::value::Value;
|
||||
use indexmap::IndexMap;
|
||||
|
||||
impl Program {
|
||||
/// Serialize to JSON format with complete program information and proper field names
|
||||
pub fn serialize_json(&self) -> Result<String, String> {
|
||||
let json_data = serde_json::json!({
|
||||
"metadata": {
|
||||
"compiler_version": self.metadata.compiler_version,
|
||||
"compiled_at": self.metadata.compiled_at,
|
||||
"source_info": self.metadata.source_info,
|
||||
"optimization_level": self.metadata.optimization_level,
|
||||
"rego_v0": self.rego_v0,
|
||||
"needs_runtime_recursion_check": self.needs_runtime_recursion_check,
|
||||
"needs_recompilation": self.needs_recompilation
|
||||
},
|
||||
"program_structure": {
|
||||
"main_entry_point": self.main_entry_point,
|
||||
"max_rule_window_size": self.max_rule_window_size,
|
||||
"dispatch_window_size": self.dispatch_window_size,
|
||||
},
|
||||
"instructions": self.instructions,
|
||||
"instruction_data": {
|
||||
"loop_params": self.instruction_data.loop_params,
|
||||
"builtin_call_params": self.instruction_data.builtin_call_params,
|
||||
"function_call_params": self.instruction_data.function_call_params,
|
||||
"object_create_params": self.instruction_data.object_create_params,
|
||||
"array_create_params": self.instruction_data.array_create_params,
|
||||
"set_create_params": self.instruction_data.set_create_params,
|
||||
"virtual_data_document_lookup_params": self.instruction_data.virtual_data_document_lookup_params,
|
||||
"chained_index_params": self.instruction_data.chained_index_params,
|
||||
"comprehension_begin_params": self.instruction_data.comprehension_begin_params
|
||||
},
|
||||
"literals": self.literals,
|
||||
"builtin_info_table": self.builtin_info_table,
|
||||
"entry_points": self.entry_points,
|
||||
"sources": self.sources,
|
||||
"rule_infos": self.rule_infos,
|
||||
"instruction_spans": self.instruction_spans,
|
||||
"rule_tree": self.rule_tree
|
||||
});
|
||||
|
||||
serde_json::to_string_pretty(&json_data)
|
||||
.map_err(|e| format!("JSON serialization failed: {}", e))
|
||||
}
|
||||
|
||||
/// Deserialize program from JSON format
|
||||
pub fn deserialize_json(data: &str) -> Result<Program, String> {
|
||||
let json_data: serde_json::Value =
|
||||
serde_json::from_str(data).map_err(|e| format!("JSON parsing failed: {}", e))?;
|
||||
|
||||
let metadata = json_data
|
||||
.get("metadata")
|
||||
.ok_or("Missing metadata section")?;
|
||||
let compiler_version = metadata
|
||||
.get("compiler_version")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
let compiled_at = metadata
|
||||
.get("compiled_at")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
let source_info = metadata
|
||||
.get("source_info")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
let optimization_level = metadata
|
||||
.get("optimization_level")
|
||||
.and_then(|v| v.as_u64())
|
||||
.unwrap_or(0) as u8;
|
||||
let rego_v0 = metadata
|
||||
.get("rego_v0")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false);
|
||||
let needs_runtime_recursion_check = metadata
|
||||
.get("needs_runtime_recursion_check")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false);
|
||||
let needs_recompilation = metadata
|
||||
.get("needs_recompilation")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false);
|
||||
|
||||
let program_structure = json_data
|
||||
.get("program_structure")
|
||||
.ok_or("Missing program_structure section")?;
|
||||
let main_entry_point = program_structure
|
||||
.get("main_entry_point")
|
||||
.and_then(|v| v.as_u64())
|
||||
.unwrap_or(0) as usize;
|
||||
let max_rule_window_size = program_structure
|
||||
.get("max_rule_window_size")
|
||||
.and_then(|v| v.as_u64())
|
||||
.unwrap_or(0) as usize;
|
||||
let dispatch_window_size = program_structure
|
||||
.get("dispatch_window_size")
|
||||
.and_then(|v| v.as_u64())
|
||||
.unwrap_or(0) as usize;
|
||||
|
||||
let instructions: Vec<Instruction> = serde_json::from_value(
|
||||
json_data
|
||||
.get("instructions")
|
||||
.ok_or("Missing instructions section")?
|
||||
.clone(),
|
||||
)
|
||||
.map_err(|e| format!("Failed to deserialize instructions: {}", e))?;
|
||||
|
||||
let instruction_data_json = json_data
|
||||
.get("instruction_data")
|
||||
.ok_or("Missing instruction_data section")?;
|
||||
let instruction_data: InstructionData =
|
||||
serde_json::from_value(instruction_data_json.clone())
|
||||
.map_err(|e| format!("Failed to deserialize instruction_data: {}", e))?;
|
||||
|
||||
let literals: Vec<Value> = json_data
|
||||
.get("literals")
|
||||
.map(|v| serde_json::from_value(v.clone()).unwrap_or_default())
|
||||
.unwrap_or_default();
|
||||
|
||||
let builtin_info_table: Vec<BuiltinInfo> = json_data
|
||||
.get("builtin_info_table")
|
||||
.map(|v| serde_json::from_value(v.clone()).unwrap_or_default())
|
||||
.unwrap_or_default();
|
||||
|
||||
let entry_points: IndexMap<String, usize> = json_data
|
||||
.get("entry_points")
|
||||
.map(|v| serde_json::from_value(v.clone()).unwrap_or_default())
|
||||
.unwrap_or_default();
|
||||
|
||||
let sources: Vec<SourceFile> = json_data
|
||||
.get("sources")
|
||||
.map(|v| serde_json::from_value(v.clone()).unwrap_or_default())
|
||||
.unwrap_or_default();
|
||||
|
||||
let rule_infos: Vec<RuleInfo> = json_data
|
||||
.get("rule_infos")
|
||||
.map(|v| serde_json::from_value(v.clone()).unwrap_or_default())
|
||||
.unwrap_or_default();
|
||||
|
||||
let instruction_spans: Vec<Option<SpanInfo>> = json_data
|
||||
.get("instruction_spans")
|
||||
.map(|v| serde_json::from_value(v.clone()).unwrap_or_default())
|
||||
.unwrap_or_default();
|
||||
|
||||
let rule_tree: Value = json_data
|
||||
.get("rule_tree")
|
||||
.map(|v| serde_json::from_value(v.clone()).unwrap_or_else(|_| Value::new_object()))
|
||||
.unwrap_or_else(Value::new_object);
|
||||
|
||||
let mut program = Program {
|
||||
instructions,
|
||||
literals,
|
||||
instruction_data,
|
||||
builtin_info_table,
|
||||
entry_points,
|
||||
sources,
|
||||
rule_infos,
|
||||
instruction_spans,
|
||||
main_entry_point,
|
||||
max_rule_window_size,
|
||||
dispatch_window_size,
|
||||
metadata: ProgramMetadata {
|
||||
compiler_version,
|
||||
compiled_at,
|
||||
source_info,
|
||||
optimization_level,
|
||||
},
|
||||
rule_tree,
|
||||
resolved_builtins: Vec::new(),
|
||||
needs_runtime_recursion_check,
|
||||
needs_recompilation,
|
||||
rego_v0,
|
||||
};
|
||||
|
||||
if !program.builtin_info_table.is_empty() {
|
||||
let _ = program.initialize_resolved_builtins();
|
||||
}
|
||||
|
||||
Ok(program)
|
||||
}
|
||||
}
|
||||
29
src/rvm/program/serialization/mod.rs
Normal file
29
src/rvm/program/serialization/mod.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
pub(crate) mod binary;
|
||||
mod json;
|
||||
pub(crate) mod value;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::Program;
|
||||
|
||||
/// Versioned program wrapper for serialization compatibility
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct VersionedProgram {
|
||||
/// Format version for compatibility checking
|
||||
pub version: u32,
|
||||
/// The actual program data
|
||||
pub program: Program,
|
||||
}
|
||||
|
||||
/// Result of program deserialization that explicitly indicates completeness
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DeserializationResult {
|
||||
/// Full deserialization was successful - program is fully functional
|
||||
Complete(Program),
|
||||
/// Only artifact section was deserialized - extensible sections failed
|
||||
/// The program contains entry_points and sources but requires recompilation
|
||||
Partial(Program),
|
||||
}
|
||||
294
src/rvm/program/serialization/value.rs
Normal file
294
src/rvm/program/serialization/value.rs
Normal file
@@ -0,0 +1,294 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use alloc::collections::{BTreeMap, BTreeSet};
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use core::fmt;
|
||||
use core::str::FromStr;
|
||||
use serde::de::{self, EnumAccess, VariantAccess, Visitor};
|
||||
use serde::ser::{SerializeSeq, SerializeTuple};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::number::Number;
|
||||
use crate::value::Value;
|
||||
|
||||
const VARIANT_NULL: u32 = 0;
|
||||
const VARIANT_BOOL: u32 = 1;
|
||||
const VARIANT_NUMBER_STRING: u32 = 2;
|
||||
const VARIANT_STRING: u32 = 3;
|
||||
const VARIANT_ARRAY: u32 = 4;
|
||||
const VARIANT_SET: u32 = 5;
|
||||
const VARIANT_OBJECT: u32 = 6;
|
||||
const VARIANT_UNDEFINED: u32 = 7;
|
||||
const VARIANT_NUMBER_I64: u32 = 8;
|
||||
const VARIANT_NUMBER_U64: u32 = 9;
|
||||
const VARIANT_NUMBER_F64: u32 = 10;
|
||||
|
||||
/// Wrapper type for zero-copy binary serialization of a `Value`.
|
||||
/// Keeps references into the original data so collections and strings are not cloned.
|
||||
pub(crate) struct BinaryValueRef<'a>(pub &'a Value);
|
||||
|
||||
impl<'a> Serialize for BinaryValueRef<'a> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match self.0 {
|
||||
Value::Null => serializer.serialize_unit_variant("BinaryValue", VARIANT_NULL, "Null"),
|
||||
Value::Bool(b) => {
|
||||
serializer.serialize_newtype_variant("BinaryValue", VARIANT_BOOL, "Bool", &b)
|
||||
}
|
||||
Value::Number(n) => {
|
||||
if let Some(value) = n.as_i64() {
|
||||
serializer.serialize_newtype_variant(
|
||||
"BinaryValue",
|
||||
VARIANT_NUMBER_I64,
|
||||
"NumberI64",
|
||||
&value,
|
||||
)
|
||||
} else if let Some(value) = n.as_u64() {
|
||||
serializer.serialize_newtype_variant(
|
||||
"BinaryValue",
|
||||
VARIANT_NUMBER_U64,
|
||||
"NumberU64",
|
||||
&value,
|
||||
)
|
||||
} else if let Some(value) = n.as_f64() {
|
||||
serializer.serialize_newtype_variant(
|
||||
"BinaryValue",
|
||||
VARIANT_NUMBER_F64,
|
||||
"NumberF64",
|
||||
&value,
|
||||
)
|
||||
} else {
|
||||
serializer.serialize_newtype_variant(
|
||||
"BinaryValue",
|
||||
VARIANT_NUMBER_STRING,
|
||||
"Number",
|
||||
&n.format_scientific(),
|
||||
)
|
||||
}
|
||||
}
|
||||
Value::String(s) => serializer.serialize_newtype_variant(
|
||||
"BinaryValue",
|
||||
VARIANT_STRING,
|
||||
"String",
|
||||
s.as_ref(),
|
||||
),
|
||||
Value::Array(items) => serializer.serialize_newtype_variant(
|
||||
"BinaryValue",
|
||||
VARIANT_ARRAY,
|
||||
"Array",
|
||||
&BinaryValueSlice(items.as_slice()),
|
||||
),
|
||||
Value::Set(items) => serializer.serialize_newtype_variant(
|
||||
"BinaryValue",
|
||||
VARIANT_SET,
|
||||
"Set",
|
||||
&BinarySetRef(items.as_ref()),
|
||||
),
|
||||
Value::Object(entries) => serializer.serialize_newtype_variant(
|
||||
"BinaryValue",
|
||||
VARIANT_OBJECT,
|
||||
"Object",
|
||||
&BinaryObjectRef(entries.as_ref()),
|
||||
),
|
||||
Value::Undefined => {
|
||||
serializer.serialize_unit_variant("BinaryValue", VARIANT_UNDEFINED, "Undefined")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Slice wrapper allowing zero-copy serialization of value collections.
|
||||
pub(crate) struct BinaryValueSlice<'a>(pub &'a [Value]);
|
||||
|
||||
impl<'a> Serialize for BinaryValueSlice<'a> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
|
||||
for value in self.0 {
|
||||
seq.serialize_element(&BinaryValueRef(value))?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
|
||||
struct BinarySetRef<'a>(&'a BTreeSet<Value>);
|
||||
|
||||
impl<'a> Serialize for BinarySetRef<'a> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
|
||||
for value in self.0.iter() {
|
||||
seq.serialize_element(&BinaryValueRef(value))?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
|
||||
struct BinaryObjectRef<'a>(&'a BTreeMap<Value, Value>);
|
||||
|
||||
impl<'a> Serialize for BinaryObjectRef<'a> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
|
||||
for (key, value) in self.0.iter() {
|
||||
seq.serialize_element(&BinaryEntryRef(key, value))?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
|
||||
struct BinaryEntryRef<'a>(&'a Value, &'a Value);
|
||||
|
||||
impl<'a> Serialize for BinaryEntryRef<'a> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut tuple = serializer.serialize_tuple(2)?;
|
||||
tuple.serialize_element(&BinaryValueRef(self.0))?;
|
||||
tuple.serialize_element(&BinaryValueRef(self.1))?;
|
||||
tuple.end()
|
||||
}
|
||||
}
|
||||
|
||||
/// Owned counterpart used during deserialization.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct BinaryValue(pub Value);
|
||||
|
||||
impl BinaryValue {
|
||||
fn into_value(self) -> Value {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
const BINARY_VARIANTS: &[&str] = &[
|
||||
"Null",
|
||||
"Bool",
|
||||
"Number",
|
||||
"String",
|
||||
"Array",
|
||||
"Set",
|
||||
"Object",
|
||||
"Undefined",
|
||||
"NumberI64",
|
||||
"NumberU64",
|
||||
"NumberF64",
|
||||
];
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
enum BinaryVariant {
|
||||
Null,
|
||||
Bool,
|
||||
Number,
|
||||
String,
|
||||
Array,
|
||||
Set,
|
||||
Object,
|
||||
Undefined,
|
||||
NumberI64,
|
||||
NumberU64,
|
||||
NumberF64,
|
||||
}
|
||||
|
||||
struct BinaryValueVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for BinaryValueVisitor {
|
||||
type Value = BinaryValue;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a BinaryValue enum")
|
||||
}
|
||||
|
||||
fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: EnumAccess<'de>,
|
||||
{
|
||||
match data.variant()? {
|
||||
(BinaryVariant::Null, variant) => {
|
||||
variant.unit_variant()?;
|
||||
Ok(BinaryValue(Value::Null))
|
||||
}
|
||||
(BinaryVariant::Bool, variant) => {
|
||||
let value = variant.newtype_variant::<bool>()?;
|
||||
Ok(BinaryValue(Value::from(value)))
|
||||
}
|
||||
(BinaryVariant::Number, variant) => {
|
||||
let numeric = variant.newtype_variant::<&'de str>()?;
|
||||
let number = Number::from_str(numeric).map_err(|_| {
|
||||
de::Error::custom(format!("Invalid numeric string '{numeric}'"))
|
||||
})?;
|
||||
Ok(BinaryValue(Value::from(number)))
|
||||
}
|
||||
(BinaryVariant::NumberI64, variant) => {
|
||||
let value = variant.newtype_variant::<i64>()?;
|
||||
Ok(BinaryValue(Value::from(value)))
|
||||
}
|
||||
(BinaryVariant::NumberU64, variant) => {
|
||||
let value = variant.newtype_variant::<u64>()?;
|
||||
Ok(BinaryValue(Value::from(value)))
|
||||
}
|
||||
(BinaryVariant::NumberF64, variant) => {
|
||||
let value = variant.newtype_variant::<f64>()?;
|
||||
Ok(BinaryValue(Value::from(value)))
|
||||
}
|
||||
(BinaryVariant::String, variant) => {
|
||||
let s = variant.newtype_variant::<&'de str>()?;
|
||||
Ok(BinaryValue(Value::from(s)))
|
||||
}
|
||||
(BinaryVariant::Array, variant) => {
|
||||
let items: Vec<BinaryValue> = variant.newtype_variant()?;
|
||||
let values: Vec<Value> = items.into_iter().map(BinaryValue::into_value).collect();
|
||||
Ok(BinaryValue(Value::from(values)))
|
||||
}
|
||||
(BinaryVariant::Set, variant) => {
|
||||
let items: Vec<BinaryValue> = variant.newtype_variant()?;
|
||||
let mut set = BTreeSet::new();
|
||||
for item in items {
|
||||
set.insert(item.into_value());
|
||||
}
|
||||
Ok(BinaryValue(Value::from(set)))
|
||||
}
|
||||
(BinaryVariant::Object, variant) => {
|
||||
let entries: Vec<(BinaryValue, BinaryValue)> = variant.newtype_variant()?;
|
||||
let mut map = BTreeMap::new();
|
||||
for (key, value) in entries {
|
||||
map.insert(key.into_value(), value.into_value());
|
||||
}
|
||||
Ok(BinaryValue(Value::from(map)))
|
||||
}
|
||||
(BinaryVariant::Undefined, variant) => {
|
||||
variant.unit_variant()?;
|
||||
Ok(BinaryValue(Value::Undefined))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for BinaryValue {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_enum("BinaryValue", BINARY_VARIANTS, BinaryValueVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn binaries_to_values(binaries: Vec<BinaryValue>) -> Result<Vec<Value>, String> {
|
||||
Ok(binaries.into_iter().map(BinaryValue::into_value).collect())
|
||||
}
|
||||
|
||||
pub(crate) fn binary_to_value(binary: BinaryValue) -> Result<Value, String> {
|
||||
Ok(binary.into_value())
|
||||
}
|
||||
182
src/rvm/program/types.rs
Normal file
182
src/rvm/program/types.rs
Normal file
@@ -0,0 +1,182 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Builtin function information stored in program's builtin info table
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BuiltinInfo {
|
||||
/// Builtin function name
|
||||
pub name: String,
|
||||
/// Exact number of arguments required
|
||||
pub num_args: u16,
|
||||
}
|
||||
|
||||
/// Span information for debugging and error reporting
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SpanInfo {
|
||||
/// Index into the source table
|
||||
pub source_index: usize,
|
||||
/// Line number (1-based)
|
||||
pub line: usize,
|
||||
/// Column number (1-based)
|
||||
pub column: usize,
|
||||
/// Length of the span
|
||||
pub length: usize,
|
||||
}
|
||||
|
||||
impl SpanInfo {
|
||||
pub fn new(source_index: usize, line: usize, column: usize, length: usize) -> Self {
|
||||
Self {
|
||||
source_index,
|
||||
line,
|
||||
column,
|
||||
length,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create SpanInfo from lexer Span with source table lookup
|
||||
pub fn from_lexer_span(span: &crate::lexer::Span, source_index: usize) -> Self {
|
||||
Self {
|
||||
source_index,
|
||||
line: span.line as usize,
|
||||
column: span.col as usize,
|
||||
length: span.text().len(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get source information using the program's source table
|
||||
pub fn get_source<'a>(&self, source_table: &'a [SourceFile]) -> Option<&'a str> {
|
||||
source_table
|
||||
.get(self.source_index)
|
||||
.map(|s| s.content.as_str())
|
||||
}
|
||||
|
||||
/// Get source name using the program's source table
|
||||
pub fn get_source_name<'a>(&self, source_table: &'a [SourceFile]) -> Option<&'a str> {
|
||||
source_table.get(self.source_index).map(|s| s.name.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// Rule type enumeration for different kinds of rules (complete, partial set, partial object)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord)]
|
||||
pub enum RuleType {
|
||||
Complete,
|
||||
PartialSet,
|
||||
PartialObject,
|
||||
}
|
||||
|
||||
/// Information about function rules
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FunctionInfo {
|
||||
/// Parameter names in order
|
||||
pub param_names: Vec<String>,
|
||||
/// Number of parameters
|
||||
pub num_params: u32,
|
||||
}
|
||||
|
||||
/// Rule metadata for debugging and introspection
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RuleInfo {
|
||||
/// Rule name (e.g., "data.package.rule_name")
|
||||
pub name: String,
|
||||
/// Rule type
|
||||
pub rule_type: RuleType,
|
||||
/// Definitions
|
||||
pub definitions: crate::Rc<Vec<Vec<u32>>>,
|
||||
/// Function-specific information (only present for function rules)
|
||||
pub function_info: Option<FunctionInfo>,
|
||||
/// Index into the program's literal table for default value (only for Complete rules)
|
||||
pub default_literal_index: Option<u16>,
|
||||
/// Register allocated for this rule's result accumulation
|
||||
pub result_reg: u8,
|
||||
/// Number of registers used by this rule (for register windowing)
|
||||
pub num_registers: u8,
|
||||
/// Optional destructuring block entry point per definition
|
||||
/// Index: definition_index → Some(entry_point) | None
|
||||
pub destructuring_blocks: Vec<Option<u32>>,
|
||||
}
|
||||
|
||||
impl RuleInfo {
|
||||
pub fn new(
|
||||
name: String,
|
||||
rule_type: RuleType,
|
||||
definitions: crate::Rc<Vec<Vec<u32>>>,
|
||||
result_reg: u8,
|
||||
num_registers: u8,
|
||||
) -> Self {
|
||||
let num_definitions = definitions.len();
|
||||
Self {
|
||||
name,
|
||||
rule_type,
|
||||
definitions,
|
||||
function_info: None,
|
||||
default_literal_index: None,
|
||||
result_reg,
|
||||
num_registers,
|
||||
destructuring_blocks: alloc::vec![None; num_definitions],
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new function rule with parameter information
|
||||
pub fn new_function(
|
||||
name: String,
|
||||
rule_type: RuleType,
|
||||
definitions: crate::Rc<Vec<Vec<u32>>>,
|
||||
param_names: Vec<String>,
|
||||
result_reg: u8,
|
||||
num_registers: u8,
|
||||
) -> Self {
|
||||
let num_params = param_names.len() as u32;
|
||||
let num_definitions = definitions.len();
|
||||
Self {
|
||||
name,
|
||||
rule_type,
|
||||
definitions,
|
||||
function_info: Some(FunctionInfo {
|
||||
param_names,
|
||||
num_params,
|
||||
}),
|
||||
default_literal_index: None,
|
||||
result_reg,
|
||||
num_registers,
|
||||
destructuring_blocks: alloc::vec![None; num_definitions],
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the default literal index for this rule
|
||||
pub fn set_default_literal_index(&mut self, default_literal_index: u16) {
|
||||
self.default_literal_index = Some(default_literal_index);
|
||||
}
|
||||
}
|
||||
|
||||
/// Source file information containing filename and contents
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SourceFile {
|
||||
/// Source file identifier/path
|
||||
pub name: String,
|
||||
/// The actual source code content
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
impl SourceFile {
|
||||
pub fn new(name: String, content: String) -> Self {
|
||||
Self { name, content }
|
||||
}
|
||||
}
|
||||
|
||||
/// Program compilation metadata
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ProgramMetadata {
|
||||
/// Compiler version that generated this program
|
||||
pub compiler_version: String,
|
||||
/// Compilation timestamp
|
||||
pub compiled_at: String,
|
||||
/// Source policy information
|
||||
pub source_info: String,
|
||||
/// Optimization level used
|
||||
pub optimization_level: u8,
|
||||
}
|
||||
648
src/rvm/tests/instruction_parser.rs
Normal file
648
src/rvm/tests/instruction_parser.rs
Normal file
@@ -0,0 +1,648 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::rvm::instructions::{Instruction, LoopMode};
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::vec::Vec;
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
|
||||
/// Parse a textual instruction like "Load { dest: 0, literal_idx: 1 }"
|
||||
pub fn parse_instruction(text: &str) -> Result<Instruction> {
|
||||
let text = text.trim();
|
||||
|
||||
// Find the instruction name and parameters
|
||||
if let Some(brace_start) = text.find('{') {
|
||||
let name = text[..brace_start].trim();
|
||||
let params_text = &text[brace_start..];
|
||||
|
||||
match name {
|
||||
"Load" => parse_load(params_text),
|
||||
"LoadTrue" => parse_load_true(params_text),
|
||||
"LoadFalse" => parse_load_false(params_text),
|
||||
"LoadNull" => parse_load_null(params_text),
|
||||
"LoadBool" => parse_load_bool(params_text),
|
||||
"LoadData" => parse_load_data(params_text),
|
||||
"LoadInput" => parse_load_input(params_text),
|
||||
"Move" => parse_move(params_text),
|
||||
"Add" => parse_add(params_text),
|
||||
"Sub" => parse_sub(params_text),
|
||||
"Mul" => parse_mul(params_text),
|
||||
"Div" => parse_div(params_text),
|
||||
"Mod" => parse_mod(params_text),
|
||||
"Eq" => parse_eq(params_text),
|
||||
"Ne" => parse_ne_instruction(params_text),
|
||||
"Lt" => parse_lt(params_text),
|
||||
"Le" => parse_le_instruction(params_text),
|
||||
"Gt" => parse_gt(params_text),
|
||||
"Ge" => parse_ge_instruction(params_text),
|
||||
"And" => parse_and(params_text),
|
||||
"Or" => parse_or(params_text),
|
||||
"Not" => parse_not(params_text),
|
||||
"Return" => parse_return(params_text),
|
||||
"RuleInit" => parse_rule_init(params_text),
|
||||
"RuleReturn" => parse_rule_return(params_text),
|
||||
"DestructuringSuccess" => parse_destructuring_success(params_text),
|
||||
"ObjectSet" => parse_object_set(params_text),
|
||||
"ObjectCreate" => parse_object_create(params_text),
|
||||
"Index" => parse_index(params_text),
|
||||
"IndexLiteral" => parse_index_literal(params_text),
|
||||
"ChainedIndex" => parse_chained_index(params_text),
|
||||
"ArrayNew" => parse_array_new(params_text),
|
||||
"ArrayCreate" => parse_array_create(params_text),
|
||||
"SetCreate" => parse_set_create(params_text),
|
||||
"ArrayPush" => parse_array_push(params_text),
|
||||
"SetNew" => parse_set_new(params_text),
|
||||
"SetAdd" => parse_set_add(params_text),
|
||||
"Contains" => parse_contains(params_text),
|
||||
"Count" => parse_count(params_text),
|
||||
"AssertCondition" => parse_assert_condition(params_text),
|
||||
"AssertNotUndefined" => parse_assert_not_undefined(params_text),
|
||||
"BuiltinCall" => parse_builtin_call(params_text),
|
||||
"FunctionCall" => parse_function_call(params_text),
|
||||
"CallRule" => parse_call_rule(params_text),
|
||||
"VirtualDataDocumentLookup" => parse_virtual_data_document_lookup(params_text),
|
||||
"HostAwait" => parse_host_await(params_text),
|
||||
"LoopStart" => parse_loop_start(params_text),
|
||||
"LoopNext" => parse_loop_next(params_text),
|
||||
"ComprehensionStart" => parse_comprehension_start(params_text),
|
||||
"ComprehensionAdd" => parse_comprehension_add(params_text),
|
||||
"ComprehensionBegin" => parse_comprehension_start(params_text),
|
||||
"ComprehensionYield" => parse_comprehension_add(params_text),
|
||||
_ => bail!("Unknown instruction: {}", name),
|
||||
}
|
||||
} else {
|
||||
// Handle instructions without parameters (no braces)
|
||||
let name = text.trim();
|
||||
match name {
|
||||
"Halt" => Ok(Instruction::Halt {}),
|
||||
"RuleReturn" => Ok(Instruction::RuleReturn {}),
|
||||
"DestructuringSuccess" => Ok(Instruction::DestructuringSuccess {}),
|
||||
"ComprehensionEnd" => Ok(Instruction::ComprehensionEnd {}),
|
||||
_ => bail!("Unknown instruction: {}", name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parameter parsing helpers
|
||||
fn parse_params(text: &str) -> Result<Vec<(String, String)>> {
|
||||
if !text.starts_with('{') || !text.ends_with('}') {
|
||||
bail!("Parameters must be enclosed in braces");
|
||||
}
|
||||
|
||||
let inner = &text[1..text.len() - 1];
|
||||
let mut params = Vec::new();
|
||||
let mut current = String::new();
|
||||
let in_value = false;
|
||||
let mut colon_pos = None;
|
||||
|
||||
for ch in inner.chars() {
|
||||
match ch {
|
||||
':' if !in_value => {
|
||||
colon_pos = Some(current.len());
|
||||
current.push(ch);
|
||||
}
|
||||
',' if !in_value => {
|
||||
if let Some(pos) = colon_pos {
|
||||
let key = current[..pos].trim().to_string();
|
||||
let value = current[pos + 1..].trim().to_string();
|
||||
params.push((key, value));
|
||||
current.clear();
|
||||
colon_pos = None;
|
||||
} else {
|
||||
bail!("Invalid parameter format");
|
||||
}
|
||||
}
|
||||
_ => current.push(ch),
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the last parameter
|
||||
if !current.trim().is_empty() {
|
||||
if let Some(pos) = colon_pos {
|
||||
let key = current[..pos].trim().to_string();
|
||||
let value = current[pos + 1..].trim().to_string();
|
||||
params.push((key, value));
|
||||
} else {
|
||||
bail!("Invalid parameter format");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn get_param_u16(params: &[(String, String)], name: &str) -> Result<u16> {
|
||||
for (key, value) in params {
|
||||
if key == name {
|
||||
return value
|
||||
.parse::<u16>()
|
||||
.map_err(|_| anyhow!("Invalid u16 value for {}: {}", name, value));
|
||||
}
|
||||
}
|
||||
bail!("Missing parameter: {}", name);
|
||||
}
|
||||
|
||||
fn get_param_bool(params: &[(String, String)], name: &str) -> Result<bool> {
|
||||
for (key, value) in params {
|
||||
if key == name {
|
||||
return value
|
||||
.parse::<bool>()
|
||||
.map_err(|_| anyhow!("Invalid bool value for {}: {}", name, value));
|
||||
}
|
||||
}
|
||||
bail!("Missing parameter: {}", name);
|
||||
}
|
||||
|
||||
pub fn parse_loop_mode(text: &str) -> Result<LoopMode> {
|
||||
match text {
|
||||
"Any" => Ok(LoopMode::Any),
|
||||
"Every" => Ok(LoopMode::Every),
|
||||
"ForEach" => Ok(LoopMode::ForEach),
|
||||
// Keep backwards compatibility for now
|
||||
"Existential" => Ok(LoopMode::Any),
|
||||
"Universal" => Ok(LoopMode::Every),
|
||||
"Collect" => Ok(LoopMode::ForEach),
|
||||
// Legacy comprehension modes now map to ForEach since we use dedicated comprehension instructions
|
||||
"ArrayComprehension" => Ok(LoopMode::ForEach),
|
||||
"SetComprehension" => Ok(LoopMode::ForEach),
|
||||
"ObjectComprehension" => Ok(LoopMode::ForEach),
|
||||
_ => bail!("Invalid loop mode: {}", text),
|
||||
}
|
||||
}
|
||||
|
||||
// Individual instruction parsers
|
||||
fn parse_load(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let literal_idx = get_param_u16(¶ms, "literal_idx")?;
|
||||
Ok(Instruction::Load {
|
||||
dest: dest.try_into().unwrap(),
|
||||
literal_idx,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_move(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let src = get_param_u16(¶ms, "src")?;
|
||||
Ok(Instruction::Move {
|
||||
dest: dest.try_into().unwrap(),
|
||||
src: src.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_add(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let left = get_param_u16(¶ms, "left")?;
|
||||
let right = get_param_u16(¶ms, "right")?;
|
||||
Ok(Instruction::Add {
|
||||
dest: dest.try_into().unwrap(),
|
||||
left: left.try_into().unwrap(),
|
||||
right: right.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_sub(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let left = get_param_u16(¶ms, "left")?;
|
||||
let right = get_param_u16(¶ms, "right")?;
|
||||
Ok(Instruction::Sub {
|
||||
dest: dest.try_into().unwrap(),
|
||||
left: left.try_into().unwrap(),
|
||||
right: right.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_mul(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let left = get_param_u16(¶ms, "left")?;
|
||||
let right = get_param_u16(¶ms, "right")?;
|
||||
Ok(Instruction::Mul {
|
||||
dest: dest.try_into().unwrap(),
|
||||
left: left.try_into().unwrap(),
|
||||
right: right.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_div(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let left = get_param_u16(¶ms, "left")?;
|
||||
let right = get_param_u16(¶ms, "right")?;
|
||||
Ok(Instruction::Div {
|
||||
dest: dest.try_into().unwrap(),
|
||||
left: left.try_into().unwrap(),
|
||||
right: right.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_eq(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let left = get_param_u16(¶ms, "left")?;
|
||||
let right = get_param_u16(¶ms, "right")?;
|
||||
Ok(Instruction::Eq {
|
||||
dest: dest.try_into().unwrap(),
|
||||
left: left.try_into().unwrap(),
|
||||
right: right.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_ne_instruction(content: &str) -> Result<Instruction> {
|
||||
let params = parse_params(content)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let left = get_param_u16(¶ms, "left")?;
|
||||
let right = get_param_u16(¶ms, "right")?;
|
||||
Ok(Instruction::Ne {
|
||||
dest: dest.try_into().unwrap(),
|
||||
left: left.try_into().unwrap(),
|
||||
right: right.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_lt(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let left = get_param_u16(¶ms, "left")?;
|
||||
let right = get_param_u16(¶ms, "right")?;
|
||||
Ok(Instruction::Lt {
|
||||
dest: dest.try_into().unwrap(),
|
||||
left: left.try_into().unwrap(),
|
||||
right: right.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_le_instruction(content: &str) -> Result<Instruction> {
|
||||
let params = parse_params(content)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let left = get_param_u16(¶ms, "left")?;
|
||||
let right = get_param_u16(¶ms, "right")?;
|
||||
Ok(Instruction::Le {
|
||||
dest: dest.try_into().unwrap(),
|
||||
left: left.try_into().unwrap(),
|
||||
right: right.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_gt(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let left = get_param_u16(¶ms, "left")?;
|
||||
let right = get_param_u16(¶ms, "right")?;
|
||||
Ok(Instruction::Gt {
|
||||
dest: dest.try_into().unwrap(),
|
||||
left: left.try_into().unwrap(),
|
||||
right: right.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_ge_instruction(content: &str) -> Result<Instruction> {
|
||||
let params = parse_params(content)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let left = get_param_u16(¶ms, "left")?;
|
||||
let right = get_param_u16(¶ms, "right")?;
|
||||
Ok(Instruction::Ge {
|
||||
dest: dest.try_into().unwrap(),
|
||||
left: left.try_into().unwrap(),
|
||||
right: right.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_return(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let value = get_param_u16(¶ms, "value")?;
|
||||
Ok(Instruction::Return {
|
||||
value: value.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_rule_init(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let result_reg = get_param_u16(¶ms, "result_reg")?;
|
||||
let rule_index = get_param_u16(¶ms, "rule_index")?;
|
||||
Ok(Instruction::RuleInit {
|
||||
result_reg: result_reg.try_into().unwrap(),
|
||||
rule_index,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_rule_return(params_text: &str) -> Result<Instruction> {
|
||||
let _params = parse_params(params_text)?;
|
||||
Ok(Instruction::RuleReturn {})
|
||||
}
|
||||
|
||||
fn parse_destructuring_success(params_text: &str) -> Result<Instruction> {
|
||||
let _params = parse_params(params_text)?;
|
||||
Ok(Instruction::DestructuringSuccess {})
|
||||
}
|
||||
|
||||
fn parse_object_set(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let obj = get_param_u16(¶ms, "obj")?;
|
||||
let key = get_param_u16(¶ms, "key")?;
|
||||
let value = get_param_u16(¶ms, "value")?;
|
||||
Ok(Instruction::ObjectSet {
|
||||
obj: obj.try_into().unwrap(),
|
||||
key: key.try_into().unwrap(),
|
||||
value: value.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_object_create(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let params_index = get_param_u16(¶ms, "params_index")?;
|
||||
Ok(Instruction::ObjectCreate { params_index })
|
||||
}
|
||||
|
||||
fn parse_index(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let container = get_param_u16(¶ms, "container")?;
|
||||
let key = get_param_u16(¶ms, "key")?;
|
||||
Ok(Instruction::Index {
|
||||
dest: dest.try_into().unwrap(),
|
||||
container: container.try_into().unwrap(),
|
||||
key: key.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_index_literal(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let container = get_param_u16(¶ms, "container")?;
|
||||
let literal_idx = get_param_u16(¶ms, "literal_idx")?;
|
||||
Ok(Instruction::IndexLiteral {
|
||||
dest: dest.try_into().unwrap(),
|
||||
container: container.try_into().unwrap(),
|
||||
literal_idx,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_chained_index(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let params_index = get_param_u16(¶ms, "params_index")?;
|
||||
Ok(Instruction::ChainedIndex { params_index })
|
||||
}
|
||||
|
||||
fn parse_array_new(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
Ok(Instruction::ArrayNew {
|
||||
dest: dest.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_array_push(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let arr = get_param_u16(¶ms, "arr")?;
|
||||
let value = get_param_u16(¶ms, "value")?;
|
||||
Ok(Instruction::ArrayPush {
|
||||
arr: arr.try_into().unwrap(),
|
||||
value: value.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_array_create(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let params_index = get_param_u16(¶ms, "params_index")?;
|
||||
Ok(Instruction::ArrayCreate { params_index })
|
||||
}
|
||||
|
||||
fn parse_set_create(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let params_index = get_param_u16(¶ms, "params_index")?;
|
||||
Ok(Instruction::SetCreate { params_index })
|
||||
}
|
||||
|
||||
fn parse_set_new(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
Ok(Instruction::SetNew {
|
||||
dest: dest.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_set_add(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let set = get_param_u16(¶ms, "set")?;
|
||||
let value = get_param_u16(¶ms, "value")?;
|
||||
Ok(Instruction::SetAdd {
|
||||
set: set.try_into().unwrap(),
|
||||
value: value.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_contains(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let collection = get_param_u16(¶ms, "collection")?;
|
||||
let value = get_param_u16(¶ms, "value")?;
|
||||
Ok(Instruction::Contains {
|
||||
dest: dest.try_into().unwrap(),
|
||||
collection: collection.try_into().unwrap(),
|
||||
value: value.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_count(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let collection = get_param_u16(¶ms, "collection")?;
|
||||
Ok(Instruction::Count {
|
||||
dest: dest.try_into().unwrap(),
|
||||
collection: collection.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_assert_condition(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let condition = get_param_u16(¶ms, "condition")?;
|
||||
Ok(Instruction::AssertCondition {
|
||||
condition: condition.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_assert_not_undefined(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let register = get_param_u16(¶ms, "register")?;
|
||||
Ok(Instruction::AssertNotUndefined {
|
||||
register: register.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_loop_start(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
|
||||
// Get params_index parameter - this should be specified in the test
|
||||
let params_index = get_param_u16(¶ms, "params_index")?;
|
||||
|
||||
Ok(Instruction::LoopStart { params_index })
|
||||
}
|
||||
|
||||
fn parse_loop_next(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let body_start = get_param_u16(¶ms, "body_start")?;
|
||||
let loop_end = get_param_u16(¶ms, "loop_end")?;
|
||||
Ok(Instruction::LoopNext {
|
||||
body_start,
|
||||
loop_end,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_load_true(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
Ok(Instruction::LoadTrue {
|
||||
dest: dest.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_load_false(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
Ok(Instruction::LoadFalse {
|
||||
dest: dest.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_load_null(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
Ok(Instruction::LoadNull {
|
||||
dest: dest.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_load_bool(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let value = get_param_bool(¶ms, "value")?;
|
||||
Ok(Instruction::LoadBool {
|
||||
dest: dest.try_into().unwrap(),
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_load_data(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
Ok(Instruction::LoadData {
|
||||
dest: dest.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_load_input(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
Ok(Instruction::LoadInput {
|
||||
dest: dest.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_mod(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let left = get_param_u16(¶ms, "left")?;
|
||||
let right = get_param_u16(¶ms, "right")?;
|
||||
Ok(Instruction::Mod {
|
||||
dest: dest.try_into().unwrap(),
|
||||
left: left.try_into().unwrap(),
|
||||
right: right.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_and(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let left = get_param_u16(¶ms, "left")?;
|
||||
let right = get_param_u16(¶ms, "right")?;
|
||||
Ok(Instruction::And {
|
||||
dest: dest.try_into().unwrap(),
|
||||
left: left.try_into().unwrap(),
|
||||
right: right.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_or(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let left = get_param_u16(¶ms, "left")?;
|
||||
let right = get_param_u16(¶ms, "right")?;
|
||||
Ok(Instruction::Or {
|
||||
dest: dest.try_into().unwrap(),
|
||||
left: left.try_into().unwrap(),
|
||||
right: right.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_not(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let operand = get_param_u16(¶ms, "operand")?;
|
||||
Ok(Instruction::Not {
|
||||
dest: dest.try_into().unwrap(),
|
||||
operand: operand.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_builtin_call(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let params_index = get_param_u16(¶ms, "params_index")?;
|
||||
Ok(Instruction::BuiltinCall { params_index })
|
||||
}
|
||||
|
||||
fn parse_function_call(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let params_index = get_param_u16(¶ms, "params_index")?;
|
||||
Ok(Instruction::FunctionCall { params_index })
|
||||
}
|
||||
|
||||
fn parse_call_rule(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let rule_index = get_param_u16(¶ms, "rule_index")?;
|
||||
Ok(Instruction::CallRule {
|
||||
dest: dest.try_into().unwrap(),
|
||||
rule_index,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_virtual_data_document_lookup(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let params_index = get_param_u16(¶ms, "params_index")?;
|
||||
Ok(Instruction::VirtualDataDocumentLookup { params_index })
|
||||
}
|
||||
|
||||
fn parse_host_await(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let dest = get_param_u16(¶ms, "dest")?;
|
||||
let arg = get_param_u16(¶ms, "arg")?;
|
||||
let id = get_param_u16(¶ms, "id")?;
|
||||
Ok(Instruction::HostAwait {
|
||||
dest: dest.try_into().unwrap(),
|
||||
arg: arg.try_into().unwrap(),
|
||||
id: id.try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_comprehension_start(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let params_index = get_param_u16(¶ms, "params_index")?;
|
||||
Ok(Instruction::ComprehensionBegin { params_index })
|
||||
}
|
||||
|
||||
fn parse_comprehension_add(params_text: &str) -> Result<Instruction> {
|
||||
let params = parse_params(params_text)?;
|
||||
let value_reg = get_param_u16(¶ms, "value_reg")?;
|
||||
let key_reg = if let Ok(key) = get_param_u16(¶ms, "key_reg") {
|
||||
Some(key.try_into().unwrap())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Instruction::ComprehensionYield {
|
||||
value_reg: value_reg.try_into().unwrap(),
|
||||
key_reg,
|
||||
})
|
||||
}
|
||||
12
src/rvm/tests/mod.rs
Normal file
12
src/rvm/tests/mod.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! RVM test modules
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod instruction_parser;
|
||||
|
||||
pub mod test_utils;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod vm;
|
||||
104
src/rvm/tests/test_utils.rs
Normal file
104
src/rvm/tests/test_utils.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! Test utility functions for RVM serialization
|
||||
|
||||
use crate::rvm::program::{binaries_to_values, BinaryValue, Program};
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use bincode::config::standard;
|
||||
use bincode::serde::decode_from_slice;
|
||||
|
||||
/// Test utility function for round-trip serialization
|
||||
/// Serializes program, deserializes it, and serializes again to check for consistency
|
||||
pub fn test_round_trip_serialization(program: &Program) -> Result<(), String> {
|
||||
// First serialization
|
||||
let serialized1 = program.serialize_binary()?;
|
||||
|
||||
// Basic validation: ensure literal section decodes cleanly under the current format.
|
||||
if serialized1.len() >= 8 && serialized1.starts_with(&Program::MAGIC) {
|
||||
let version = u32::from_le_bytes([
|
||||
serialized1[4],
|
||||
serialized1[5],
|
||||
serialized1[6],
|
||||
serialized1[7],
|
||||
]);
|
||||
|
||||
if version == 2 && serialized1.len() >= 25 {
|
||||
let entry_points_len = u32::from_le_bytes([
|
||||
serialized1[8],
|
||||
serialized1[9],
|
||||
serialized1[10],
|
||||
serialized1[11],
|
||||
]) as usize;
|
||||
let sources_len = u32::from_le_bytes([
|
||||
serialized1[12],
|
||||
serialized1[13],
|
||||
serialized1[14],
|
||||
serialized1[15],
|
||||
]) as usize;
|
||||
let literals_len = u32::from_le_bytes([
|
||||
serialized1[16],
|
||||
serialized1[17],
|
||||
serialized1[18],
|
||||
serialized1[19],
|
||||
]) as usize;
|
||||
let entry_points_start = 25;
|
||||
let sources_start = entry_points_start + entry_points_len;
|
||||
let literals_start = sources_start + sources_len;
|
||||
let rule_tree_start = literals_start + literals_len;
|
||||
|
||||
if literals_len > 0 && serialized1.len() >= rule_tree_start {
|
||||
match decode_from_slice::<Vec<BinaryValue>, _>(
|
||||
&serialized1[literals_start..rule_tree_start],
|
||||
standard(),
|
||||
) {
|
||||
Ok((decoded_literals, _)) => {
|
||||
if binaries_to_values(decoded_literals).is_err() {
|
||||
return Err(
|
||||
"Failed to convert literal table from binary representation".into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(format!(
|
||||
"Failed to decode literal table with bincode: {}",
|
||||
err
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deserialize
|
||||
let deserialized = match Program::deserialize_binary(&serialized1)? {
|
||||
crate::rvm::program::DeserializationResult::Complete(program) => program,
|
||||
crate::rvm::program::DeserializationResult::Partial(program) => {
|
||||
let info = format!(
|
||||
"Deserialization resulted in partial program during round-trip test \
|
||||
(instructions={}, literals={}, needs_recompilation={})",
|
||||
program.instructions.len(),
|
||||
program.literals.len(),
|
||||
program.needs_recompilation()
|
||||
);
|
||||
return Err(info);
|
||||
}
|
||||
};
|
||||
|
||||
// Second serialization
|
||||
let serialized2 = deserialized.serialize_binary()?;
|
||||
|
||||
// Compare the two serialized versions
|
||||
if serialized1 == serialized2 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!(
|
||||
"Round-trip serialization failed: serialized data differs. \
|
||||
First serialization: {} bytes, Second: {} bytes",
|
||||
serialized1.len(),
|
||||
serialized2.len()
|
||||
))
|
||||
}
|
||||
}
|
||||
995
src/rvm/tests/vm.rs
Normal file
995
src/rvm/tests/vm.rs
Normal file
@@ -0,0 +1,995 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::rvm::tests::instruction_parser::{parse_instruction, parse_loop_mode};
|
||||
use crate::rvm::tests::test_utils::test_round_trip_serialization;
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||
struct RuleInfoSpec {
|
||||
rule_type: String,
|
||||
definitions: Vec<Vec<u16>>,
|
||||
#[serde(default)]
|
||||
default_rule_index: Option<u16>,
|
||||
#[serde(default)]
|
||||
default_literal_index: Option<u16>,
|
||||
#[serde(default)]
|
||||
destructuring_blocks: Option<Vec<Option<u16>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
struct DefaultRuleSpec {
|
||||
rule_name: String,
|
||||
default_value: crate::Value,
|
||||
}
|
||||
|
||||
use crate::rvm::vm::{ExecutionMode, ExecutionState, RegoVM, SuspendReason, VmError};
|
||||
use crate::tests::interpreter::process_value;
|
||||
use crate::value::Value;
|
||||
use alloc::collections::{BTreeMap, VecDeque};
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use test_generator::test_resources;
|
||||
|
||||
extern crate alloc;
|
||||
extern crate std;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
struct HostAwaitResponseSpec {
|
||||
id: crate::Value,
|
||||
#[serde(default)]
|
||||
value: Option<crate::Value>,
|
||||
#[serde(default)]
|
||||
values: Vec<crate::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct VmTestCase {
|
||||
note: String,
|
||||
#[serde(default)]
|
||||
description: Option<String>,
|
||||
#[serde(default)]
|
||||
example_rego: Option<String>,
|
||||
#[serde(default)]
|
||||
data: Option<crate::Value>,
|
||||
#[serde(default)]
|
||||
input: Option<crate::Value>,
|
||||
literals: Vec<crate::Value>,
|
||||
#[serde(default)]
|
||||
rule_infos: Vec<RuleInfoSpec>,
|
||||
#[serde(default)]
|
||||
default_rules: Vec<DefaultRuleSpec>,
|
||||
#[serde(default)]
|
||||
rule_tree: Option<crate::Value>,
|
||||
#[serde(default)]
|
||||
instruction_params: Option<InstructionParamsSpec>,
|
||||
#[serde(default)]
|
||||
max_instructions: Option<usize>,
|
||||
#[serde(default)]
|
||||
host_await_responses: Option<Vec<HostAwaitResponseSpec>>,
|
||||
#[serde(default)]
|
||||
host_await_responses_run_to_completion: Option<Vec<HostAwaitResponseSpec>>,
|
||||
#[serde(default)]
|
||||
host_await_responses_suspendable: Option<Vec<HostAwaitResponseSpec>>,
|
||||
#[serde(default)]
|
||||
ignore_run_to_completion_hostawait_failure: bool,
|
||||
instructions: Vec<String>,
|
||||
#[serde(default, deserialize_with = "deserialize_optional_value")]
|
||||
want_result: Option<crate::Value>,
|
||||
#[serde(default)]
|
||||
want_error: Option<String>,
|
||||
#[serde(default, deserialize_with = "deserialize_optional_value")]
|
||||
want_result_strict: Option<crate::Value>,
|
||||
#[serde(default)]
|
||||
want_error_strict: Option<String>,
|
||||
}
|
||||
|
||||
fn deserialize_optional_value<'de, D>(deserializer: D) -> Result<Option<crate::Value>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
// If the field is present, always return Some, even if the value is null
|
||||
crate::Value::deserialize(deserializer).map(Some)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||
struct InstructionParamsSpec {
|
||||
#[serde(default)]
|
||||
loop_params: Vec<LoopStartParamsSpec>,
|
||||
#[serde(default)]
|
||||
call_params: Vec<CallParamsSpec>,
|
||||
#[serde(default)]
|
||||
builtin_call_params: Vec<BuiltinCallParamsSpec>,
|
||||
#[serde(default)]
|
||||
function_call_params: Vec<FunctionCallParamsSpec>,
|
||||
#[serde(default)]
|
||||
builtin_infos: Vec<BuiltinInfoSpec>,
|
||||
#[serde(default)]
|
||||
object_create_params: Vec<ObjectCreateParamsSpec>,
|
||||
#[serde(default)]
|
||||
array_create_params: Vec<ArrayCreateParamsSpec>,
|
||||
#[serde(default)]
|
||||
set_create_params: Vec<SetCreateParamsSpec>,
|
||||
#[serde(default)]
|
||||
virtual_data_document_lookup_params: Vec<VirtualDataDocumentLookupParamsSpec>,
|
||||
#[serde(default)]
|
||||
chained_index_params: Vec<ChainedIndexParamsSpec>,
|
||||
#[serde(default, alias = "comprehension_start_params")]
|
||||
comprehension_begin_params: Vec<ComprehensionBeginParamsSpec>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
struct LoopStartParamsSpec {
|
||||
mode: String,
|
||||
collection: u16,
|
||||
key_reg: u16,
|
||||
value_reg: u16,
|
||||
result_reg: u16,
|
||||
body_start: u16,
|
||||
loop_end: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
struct CallParamsSpec {
|
||||
dest: u16,
|
||||
func: u16,
|
||||
args_start: u16,
|
||||
args_count: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
struct BuiltinCallParamsSpec {
|
||||
dest: u16,
|
||||
builtin_index: u16,
|
||||
args: Vec<u16>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
struct FunctionCallParamsSpec {
|
||||
func: u16,
|
||||
dest: u16,
|
||||
args: Vec<u16>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
struct BuiltinInfoSpec {
|
||||
name: String,
|
||||
num_args: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
struct ObjectCreateParamsSpec {
|
||||
dest: u16,
|
||||
template_literal_idx: u16,
|
||||
literal_key_fields: Vec<(u16, u16)>,
|
||||
fields: Vec<(u16, u16)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
struct ArrayCreateParamsSpec {
|
||||
dest: u16,
|
||||
elements: Vec<u16>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
struct SetCreateParamsSpec {
|
||||
dest: u16,
|
||||
elements: Vec<u16>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
struct LiteralOrRegisterSpec {
|
||||
#[serde(default, alias = "literal")]
|
||||
literal_idx: Option<u16>,
|
||||
#[serde(default, alias = "reg")]
|
||||
register: Option<u16>,
|
||||
}
|
||||
|
||||
impl LiteralOrRegisterSpec {
|
||||
fn into_literal_or_register(self) -> crate::rvm::instructions::LiteralOrRegister {
|
||||
if let Some(literal_idx) = self.literal_idx {
|
||||
crate::rvm::instructions::LiteralOrRegister::Literal(literal_idx)
|
||||
} else if let Some(register) = self.register {
|
||||
crate::rvm::instructions::LiteralOrRegister::Register(register.try_into().unwrap())
|
||||
} else {
|
||||
panic!("LiteralOrRegisterSpec must specify either literal_idx or register");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
struct VirtualDataDocumentLookupParamsSpec {
|
||||
dest: u16,
|
||||
path_components: Vec<LiteralOrRegisterSpec>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
struct ChainedIndexParamsSpec {
|
||||
dest: u16,
|
||||
root: u16,
|
||||
path_components: Vec<LiteralOrRegisterSpec>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
struct ComprehensionBeginParamsSpec {
|
||||
mode: String,
|
||||
collection_reg: u16,
|
||||
#[serde(default)]
|
||||
result_reg: Option<u16>,
|
||||
key_reg: u16,
|
||||
value_reg: u16,
|
||||
body_start: u16,
|
||||
comprehension_end: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct VmTestSuite {
|
||||
cases: Vec<VmTestCase>,
|
||||
}
|
||||
|
||||
/// Execute VM instructions directly from parsed instructions and literals
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn execute_vm_instructions(
|
||||
instructions: Vec<crate::rvm::instructions::Instruction>,
|
||||
literals: Vec<Value>,
|
||||
rule_infos: Vec<RuleInfoSpec>,
|
||||
rule_tree: Option<Value>,
|
||||
instruction_params: Option<InstructionParamsSpec>,
|
||||
data: Option<Value>,
|
||||
input: Option<Value>,
|
||||
max_instructions: Option<usize>,
|
||||
host_await_responses: Option<Vec<HostAwaitResponseSpec>>,
|
||||
host_await_responses_run_to_completion: Option<Vec<HostAwaitResponseSpec>>,
|
||||
host_await_responses_suspendable: Option<Vec<HostAwaitResponseSpec>>,
|
||||
ignore_run_to_completion_hostawait_failure: bool,
|
||||
strict: bool,
|
||||
) -> Result<Value> {
|
||||
let processed_data = if let Some(ref data_value) = data {
|
||||
Some(process_value(data_value)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let processed_input = if let Some(ref input_value) = input {
|
||||
Some(process_value(input_value)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let processed_rule_tree = if let Some(ref tree_value) = rule_tree {
|
||||
Some(process_value(tree_value)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let process_responses =
|
||||
|responses: Vec<HostAwaitResponseSpec>| -> Result<BTreeMap<Value, Vec<Value>>> {
|
||||
let mut processed: BTreeMap<Value, Vec<Value>> = BTreeMap::new();
|
||||
|
||||
for spec in responses {
|
||||
let identifier = process_value(&spec.id)?;
|
||||
let mut values: Vec<Value> = Vec::new();
|
||||
|
||||
if let Some(single) = spec.value.as_ref() {
|
||||
values.push(process_value(single)?);
|
||||
}
|
||||
|
||||
for value in &spec.values {
|
||||
values.push(process_value(value)?);
|
||||
}
|
||||
|
||||
if values.is_empty() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"HostAwait response specification for id {:?} has no values",
|
||||
identifier
|
||||
));
|
||||
}
|
||||
|
||||
processed.entry(identifier).or_default().extend(values);
|
||||
}
|
||||
|
||||
Ok(processed)
|
||||
};
|
||||
|
||||
let processed_host_responses = if let Some(responses) = host_await_responses {
|
||||
Some(process_responses(responses)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let processed_host_responses_run_to_completion =
|
||||
if let Some(responses) = host_await_responses_run_to_completion {
|
||||
Some(process_responses(responses)?)
|
||||
} else {
|
||||
processed_host_responses.clone()
|
||||
};
|
||||
|
||||
let processed_host_responses_suspendable =
|
||||
if let Some(responses) = host_await_responses_suspendable {
|
||||
Some(process_responses(responses)?)
|
||||
} else {
|
||||
processed_host_responses.clone()
|
||||
};
|
||||
|
||||
// Create a Program from instructions and literals
|
||||
let mut program = crate::rvm::program::Program::new();
|
||||
program.instructions = instructions;
|
||||
|
||||
// Process literals through the value converter to handle special syntax like set!
|
||||
let mut processed_literals = Vec::new();
|
||||
for literal in &literals {
|
||||
processed_literals.push(process_value(literal)?);
|
||||
}
|
||||
program.literals = processed_literals;
|
||||
|
||||
if let Some(tree) = processed_rule_tree {
|
||||
program.rule_tree = tree;
|
||||
} else {
|
||||
program.rule_tree = Value::new_object();
|
||||
}
|
||||
|
||||
// Convert rule infos
|
||||
for rule_info_spec in rule_infos.iter() {
|
||||
use crate::rvm::program::{RuleInfo, RuleType};
|
||||
|
||||
let rule_type = match rule_info_spec.rule_type.as_str() {
|
||||
"Complete" => RuleType::Complete,
|
||||
"PartialSet" => RuleType::PartialSet,
|
||||
"PartialObject" => RuleType::PartialObject,
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Unknown rule type: {}",
|
||||
rule_info_spec.rule_type
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
// Convert Vec<Vec<u16>> to Vec<Vec<u32>>
|
||||
let definitions: Vec<Vec<u32>> = rule_info_spec
|
||||
.definitions
|
||||
.iter()
|
||||
.map(|def| def.iter().map(|&x| x as u32).collect())
|
||||
.collect();
|
||||
|
||||
let mut destructuring_blocks: Vec<Option<u32>> = rule_info_spec
|
||||
.destructuring_blocks
|
||||
.clone()
|
||||
.map(|blocks| {
|
||||
blocks
|
||||
.into_iter()
|
||||
.map(|entry| entry.map(|value| value as u32))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(|| alloc::vec![None; definitions.len()]);
|
||||
|
||||
if destructuring_blocks.len() != definitions.len() {
|
||||
destructuring_blocks.resize(definitions.len(), None);
|
||||
}
|
||||
|
||||
// For function calls, use result_reg 0; for other rules, use result_reg 1
|
||||
let result_reg = if instruction_params
|
||||
.as_ref()
|
||||
.is_some_and(|params| !params.function_call_params.is_empty())
|
||||
{
|
||||
0 // Function calls use register 0 as return register
|
||||
} else {
|
||||
1 // Regular rules use register 1
|
||||
};
|
||||
|
||||
let rule_info = RuleInfo {
|
||||
name: String::from("test_rule"),
|
||||
rule_type,
|
||||
definitions: crate::Rc::new(definitions.clone()),
|
||||
function_info: None,
|
||||
default_literal_index: rule_info_spec.default_literal_index,
|
||||
result_reg,
|
||||
num_registers: 50, // Increased to accommodate test cases with higher register indices
|
||||
destructuring_blocks,
|
||||
};
|
||||
|
||||
program.rule_infos.push(rule_info);
|
||||
}
|
||||
|
||||
// Build instruction data from params specification
|
||||
if let Some(params_spec) = instruction_params {
|
||||
// Convert loop params
|
||||
for loop_param_spec in params_spec.loop_params {
|
||||
let mode = parse_loop_mode(&loop_param_spec.mode)?;
|
||||
let loop_params = crate::rvm::instructions::LoopStartParams {
|
||||
mode,
|
||||
collection: loop_param_spec.collection.try_into().unwrap(),
|
||||
key_reg: loop_param_spec.key_reg.try_into().unwrap(),
|
||||
value_reg: loop_param_spec.value_reg.try_into().unwrap(),
|
||||
result_reg: loop_param_spec.result_reg.try_into().unwrap(),
|
||||
body_start: loop_param_spec.body_start,
|
||||
loop_end: loop_param_spec.loop_end,
|
||||
};
|
||||
program.add_loop_params(loop_params);
|
||||
}
|
||||
|
||||
// Convert call params
|
||||
// Legacy call_params support removed - use builtin_call_params or function_call_params instead
|
||||
if !params_spec.call_params.is_empty() {
|
||||
// Legacy call parameters are no longer supported
|
||||
// Convert to BuiltinCall or FunctionCall instructions instead
|
||||
panic!("Legacy call_params are no longer supported. Use builtin_call_params or function_call_params instead.");
|
||||
}
|
||||
|
||||
// Convert builtin info specs to program builtin info table
|
||||
for builtin_info_spec in params_spec.builtin_infos {
|
||||
let builtin_info = crate::rvm::program::BuiltinInfo {
|
||||
name: builtin_info_spec.name,
|
||||
num_args: builtin_info_spec.num_args,
|
||||
};
|
||||
program.add_builtin_info(builtin_info);
|
||||
}
|
||||
|
||||
// Convert builtin call params
|
||||
for builtin_call_spec in params_spec.builtin_call_params {
|
||||
use crate::rvm::instructions::BuiltinCallParams;
|
||||
|
||||
// Convert Vec<u16> to fixed array (unused slots are irrelevant due to num_args)
|
||||
let mut args_array = [0u8; 8];
|
||||
for (i, &arg) in builtin_call_spec.args.iter().enumerate() {
|
||||
if i < 8 {
|
||||
args_array[i] = arg.try_into().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let builtin_call_params = BuiltinCallParams {
|
||||
dest: builtin_call_spec.dest.try_into().unwrap(),
|
||||
builtin_index: builtin_call_spec.builtin_index,
|
||||
num_args: builtin_call_spec.args.len() as u8,
|
||||
args: args_array,
|
||||
};
|
||||
program.add_builtin_call_params(builtin_call_params);
|
||||
}
|
||||
|
||||
// Convert function call params
|
||||
for function_call_spec in params_spec.function_call_params {
|
||||
use crate::rvm::instructions::FunctionCallParams;
|
||||
|
||||
// Convert Vec<u16> to fixed array (unused slots are irrelevant due to num_args)
|
||||
let mut args_array = [0u8; 8];
|
||||
for (i, &arg) in function_call_spec.args.iter().enumerate() {
|
||||
if i < 8 {
|
||||
args_array[i] = arg.try_into().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let function_call_params = FunctionCallParams {
|
||||
func_rule_index: function_call_spec.func,
|
||||
dest: function_call_spec.dest.try_into().unwrap(),
|
||||
num_args: function_call_spec.args.len() as u8,
|
||||
args: args_array,
|
||||
};
|
||||
program.add_function_call_params(function_call_params);
|
||||
}
|
||||
|
||||
// Convert object create params
|
||||
for object_create_spec in params_spec.object_create_params {
|
||||
use crate::rvm::instructions::ObjectCreateParams;
|
||||
|
||||
let object_create_params = ObjectCreateParams {
|
||||
dest: object_create_spec.dest.try_into().unwrap(),
|
||||
template_literal_idx: object_create_spec.template_literal_idx,
|
||||
literal_key_fields: object_create_spec
|
||||
.literal_key_fields
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, v.try_into().unwrap()))
|
||||
.collect(),
|
||||
fields: object_create_spec
|
||||
.fields
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.try_into().unwrap(), v.try_into().unwrap()))
|
||||
.collect(),
|
||||
};
|
||||
program
|
||||
.instruction_data
|
||||
.add_object_create_params(object_create_params);
|
||||
}
|
||||
|
||||
// Convert array create params
|
||||
for array_create_spec in params_spec.array_create_params {
|
||||
use crate::rvm::instructions::ArrayCreateParams;
|
||||
|
||||
let array_create_params = ArrayCreateParams {
|
||||
dest: array_create_spec.dest.try_into().unwrap(),
|
||||
elements: array_create_spec
|
||||
.elements
|
||||
.into_iter()
|
||||
.map(|reg| reg.try_into().unwrap())
|
||||
.collect(),
|
||||
};
|
||||
program
|
||||
.instruction_data
|
||||
.add_array_create_params(array_create_params);
|
||||
}
|
||||
|
||||
// Convert set create params
|
||||
for set_create_spec in params_spec.set_create_params {
|
||||
use crate::rvm::instructions::SetCreateParams;
|
||||
|
||||
let set_create_params = SetCreateParams {
|
||||
dest: set_create_spec.dest.try_into().unwrap(),
|
||||
elements: set_create_spec
|
||||
.elements
|
||||
.into_iter()
|
||||
.map(|reg| reg.try_into().unwrap())
|
||||
.collect(),
|
||||
};
|
||||
program
|
||||
.instruction_data
|
||||
.add_set_create_params(set_create_params);
|
||||
}
|
||||
|
||||
// Convert virtual data document lookup params
|
||||
for virtual_spec in params_spec.virtual_data_document_lookup_params {
|
||||
use crate::rvm::instructions::{
|
||||
LiteralOrRegister, VirtualDataDocumentLookupParams,
|
||||
};
|
||||
|
||||
let path_components: Vec<LiteralOrRegister> = virtual_spec
|
||||
.path_components
|
||||
.into_iter()
|
||||
.map(|component| component.into_literal_or_register())
|
||||
.collect();
|
||||
|
||||
let params = VirtualDataDocumentLookupParams {
|
||||
dest: virtual_spec.dest.try_into().unwrap(),
|
||||
path_components,
|
||||
};
|
||||
|
||||
program
|
||||
.instruction_data
|
||||
.add_virtual_data_document_lookup_params(params);
|
||||
}
|
||||
|
||||
// Convert chained index params
|
||||
for chained_spec in params_spec.chained_index_params {
|
||||
use crate::rvm::instructions::{ChainedIndexParams, LiteralOrRegister};
|
||||
|
||||
let path_components: Vec<LiteralOrRegister> = chained_spec
|
||||
.path_components
|
||||
.into_iter()
|
||||
.map(|component| component.into_literal_or_register())
|
||||
.collect();
|
||||
|
||||
let params = ChainedIndexParams {
|
||||
dest: chained_spec.dest.try_into().unwrap(),
|
||||
root: chained_spec.root.try_into().unwrap(),
|
||||
path_components,
|
||||
};
|
||||
|
||||
program.instruction_data.add_chained_index_params(params);
|
||||
}
|
||||
|
||||
// Convert comprehension start params
|
||||
for comprehension_spec in params_spec.comprehension_begin_params {
|
||||
use crate::rvm::instructions::{ComprehensionBeginParams, ComprehensionMode};
|
||||
|
||||
let mode = match comprehension_spec.mode.as_str() {
|
||||
"Array" => ComprehensionMode::Array,
|
||||
"Set" => ComprehensionMode::Set,
|
||||
"Object" => ComprehensionMode::Object,
|
||||
_ => panic!("Invalid comprehension mode: {}", comprehension_spec.mode),
|
||||
};
|
||||
|
||||
let comprehension_params = ComprehensionBeginParams {
|
||||
mode,
|
||||
collection_reg: comprehension_spec.collection_reg.try_into().unwrap(),
|
||||
result_reg: comprehension_spec
|
||||
.result_reg
|
||||
.unwrap_or(comprehension_spec.collection_reg)
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
key_reg: comprehension_spec.key_reg.try_into().unwrap(),
|
||||
value_reg: comprehension_spec.value_reg.try_into().unwrap(),
|
||||
body_start: comprehension_spec.body_start,
|
||||
comprehension_end: comprehension_spec.comprehension_end,
|
||||
};
|
||||
program
|
||||
.instruction_data
|
||||
.add_comprehension_begin_params(comprehension_params);
|
||||
}
|
||||
}
|
||||
|
||||
program.main_entry_point = 0;
|
||||
|
||||
// Set a reasonable default for register window size in VM tests
|
||||
// Most tests use registers 0-10, so we'll allocate 256 registers to be safe
|
||||
program.max_rule_window_size = 256;
|
||||
program.dispatch_window_size = 50;
|
||||
|
||||
// Initialize resolved builtins if we have builtin info
|
||||
if !program.builtin_info_table.is_empty() {
|
||||
if let Err(e) = program.initialize_resolved_builtins() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Failed to initialize resolved builtins: {}",
|
||||
e
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure program artifacts survive binary round-tripping
|
||||
test_round_trip_serialization(&program)
|
||||
.map_err(|e| anyhow::anyhow!("Program serialization round-trip failed: {}", e))?;
|
||||
|
||||
let program = Arc::new(program);
|
||||
|
||||
let run_with_mode = |mode: ExecutionMode,
|
||||
use_step_mode: bool,
|
||||
host_responses_template: Option<BTreeMap<Value, Vec<Value>>>|
|
||||
-> Result<Result<Value>> {
|
||||
let mut vm = RegoVM::new();
|
||||
vm.set_execution_mode(mode);
|
||||
vm.set_step_mode(use_step_mode);
|
||||
vm.set_strict_builtin_errors(strict);
|
||||
|
||||
if let Some(data_value) = processed_data.clone() {
|
||||
vm.set_data(data_value)?;
|
||||
}
|
||||
if let Some(input_value) = processed_input.clone() {
|
||||
vm.set_input(input_value);
|
||||
}
|
||||
|
||||
if let Some(limit) = max_instructions {
|
||||
vm.set_max_instructions(limit);
|
||||
}
|
||||
|
||||
if matches!(mode, ExecutionMode::RunToCompletion) {
|
||||
if let Some(responses) = host_responses_template.clone() {
|
||||
vm.set_host_await_responses(responses);
|
||||
}
|
||||
}
|
||||
|
||||
vm.load_program(program.clone());
|
||||
|
||||
let mut response_map = host_responses_template.clone().map(|map| {
|
||||
map.into_iter()
|
||||
.map(|(identifier, values)| (identifier, VecDeque::from(values)))
|
||||
.collect::<BTreeMap<_, _>>()
|
||||
});
|
||||
|
||||
let mut last_result = vm.execute().map_err(|e| anyhow::anyhow!("{}", e));
|
||||
|
||||
loop {
|
||||
match vm.execution_state() {
|
||||
ExecutionState::Completed { result } => {
|
||||
return Ok(Ok(result.clone()));
|
||||
}
|
||||
ExecutionState::Error { error } => {
|
||||
return Ok(Err(anyhow::anyhow!("{}", error)));
|
||||
}
|
||||
ExecutionState::Suspended { reason, .. } => {
|
||||
if mode != ExecutionMode::Suspendable {
|
||||
return Ok(Err(anyhow::anyhow!(
|
||||
"Run-to-completion execution unexpectedly suspended: {:?}",
|
||||
reason
|
||||
)));
|
||||
}
|
||||
|
||||
match reason {
|
||||
SuspendReason::HostAwait {
|
||||
dest, identifier, ..
|
||||
} => {
|
||||
let dest = *dest;
|
||||
let identifier = identifier.clone();
|
||||
|
||||
let response = {
|
||||
let map = response_map.as_mut().ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"{}",
|
||||
VmError::HostAwaitResponseMissing {
|
||||
dest,
|
||||
identifier: identifier.clone(),
|
||||
}
|
||||
)
|
||||
})?;
|
||||
|
||||
let queue = map.get_mut(&identifier).ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"{}",
|
||||
VmError::HostAwaitResponseMissing {
|
||||
dest,
|
||||
identifier: identifier.clone(),
|
||||
}
|
||||
)
|
||||
})?;
|
||||
|
||||
let response = queue.pop_front().ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"{}",
|
||||
VmError::HostAwaitResponseMissing {
|
||||
dest,
|
||||
identifier: identifier.clone(),
|
||||
}
|
||||
)
|
||||
})?;
|
||||
|
||||
if queue.is_empty() {
|
||||
map.remove(&identifier);
|
||||
}
|
||||
|
||||
response
|
||||
};
|
||||
|
||||
last_result = vm
|
||||
.resume(Some(response))
|
||||
.map_err(|e| anyhow::anyhow!("{}", e));
|
||||
}
|
||||
SuspendReason::Step => {
|
||||
if !use_step_mode {
|
||||
return Ok(Err(anyhow::anyhow!(
|
||||
"Suspendable execution unexpectedly suspended: {:?}",
|
||||
reason
|
||||
)));
|
||||
}
|
||||
last_result = vm.resume(None).map_err(|e| anyhow::anyhow!("{}", e));
|
||||
}
|
||||
_ => {
|
||||
last_result = vm.resume(None).map_err(|e| anyhow::anyhow!("{}", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => match &last_result {
|
||||
Ok(value) => return Ok(Ok(value.clone())),
|
||||
Err(err) => return Ok(Err(anyhow::anyhow!("{}", err))),
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let compare_results = |baseline_name: &str,
|
||||
baseline: &Result<Value>,
|
||||
other_name: &str,
|
||||
other: &Result<Value>|
|
||||
-> Result<()> {
|
||||
match (baseline, other) {
|
||||
(Ok(expected), Ok(actual)) => {
|
||||
if expected != actual {
|
||||
return Err(anyhow::anyhow!(
|
||||
"{} execution result {:?} differed from {} {:?}",
|
||||
other_name,
|
||||
actual,
|
||||
baseline_name,
|
||||
expected
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
(Err(expected_err), Err(other_err)) => {
|
||||
let expected_msg = expected_err.to_string();
|
||||
let other_msg = other_err.to_string();
|
||||
if expected_msg != other_msg {
|
||||
return Err(anyhow::anyhow!(
|
||||
"{} execution error '{}' differed from {} '{}'",
|
||||
other_name,
|
||||
other_msg,
|
||||
baseline_name,
|
||||
expected_msg
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
(Ok(expected), Err(other_err)) => Err(anyhow::anyhow!(
|
||||
"{} execution failed with '{}' while {} succeeded with {:?}",
|
||||
other_name,
|
||||
other_err,
|
||||
baseline_name,
|
||||
expected
|
||||
)),
|
||||
(Err(expected_err), Ok(actual)) => Err(anyhow::anyhow!(
|
||||
"{} execution succeeded with {:?} while {} failed with '{}'",
|
||||
other_name,
|
||||
actual,
|
||||
baseline_name,
|
||||
expected_err
|
||||
)),
|
||||
}
|
||||
};
|
||||
|
||||
let run_to_completion = run_with_mode(
|
||||
ExecutionMode::RunToCompletion,
|
||||
false,
|
||||
processed_host_responses_run_to_completion.clone(),
|
||||
)?;
|
||||
let suspendable = run_with_mode(
|
||||
ExecutionMode::Suspendable,
|
||||
false,
|
||||
processed_host_responses_suspendable.clone(),
|
||||
)?;
|
||||
let stepwise = run_with_mode(
|
||||
ExecutionMode::Suspendable,
|
||||
true,
|
||||
processed_host_responses_suspendable.clone(),
|
||||
)?;
|
||||
|
||||
const HOST_AWAIT_RESPONSE_MISSING: &str = "HostAwait executed but no response provided";
|
||||
|
||||
let ignore_run_to_completion = ignore_run_to_completion_hostawait_failure
|
||||
&& matches!(
|
||||
&run_to_completion,
|
||||
Err(err) if err.to_string().contains(HOST_AWAIT_RESPONSE_MISSING)
|
||||
);
|
||||
|
||||
if ignore_run_to_completion {
|
||||
compare_results("suspendable", &suspendable, "step-by-step", &stepwise)?;
|
||||
return suspendable;
|
||||
}
|
||||
|
||||
compare_results(
|
||||
"run-to-completion",
|
||||
&run_to_completion,
|
||||
"suspendable",
|
||||
&suspendable,
|
||||
)?;
|
||||
compare_results(
|
||||
"run-to-completion",
|
||||
&run_to_completion,
|
||||
"step-by-step",
|
||||
&stepwise,
|
||||
)?;
|
||||
|
||||
run_to_completion
|
||||
}
|
||||
|
||||
fn run_vm_test_suite(file: &str) -> Result<()> {
|
||||
std::println!("Running VM test suite: {}", file);
|
||||
let yaml_content = fs::read_to_string(file)?;
|
||||
let test_suite: VmTestSuite = serde_yaml::from_str(&yaml_content)?;
|
||||
|
||||
for test_case in test_suite.cases {
|
||||
std::println!("Running VM test case: {}", test_case.note);
|
||||
|
||||
let instructions = test_case
|
||||
.instructions
|
||||
.iter()
|
||||
.map(|instruction_str| parse_instruction(instruction_str))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
let ignore_hostawait_failure = test_case.ignore_run_to_completion_hostawait_failure;
|
||||
|
||||
struct ModeExpectation<'a> {
|
||||
strict: bool,
|
||||
want_result: Option<&'a crate::Value>,
|
||||
want_error: Option<&'a String>,
|
||||
}
|
||||
|
||||
let mut expectations = Vec::new();
|
||||
|
||||
if test_case.want_result.is_some() || test_case.want_error.is_some() {
|
||||
expectations.push(ModeExpectation {
|
||||
strict: false,
|
||||
want_result: test_case.want_result.as_ref(),
|
||||
want_error: test_case.want_error.as_ref(),
|
||||
});
|
||||
}
|
||||
|
||||
if test_case.want_result_strict.is_some() || test_case.want_error_strict.is_some() {
|
||||
expectations.push(ModeExpectation {
|
||||
strict: true,
|
||||
want_result: test_case.want_result_strict.as_ref(),
|
||||
want_error: test_case.want_error_strict.as_ref(),
|
||||
});
|
||||
}
|
||||
|
||||
if expectations.is_empty() {
|
||||
panic!(
|
||||
"Test case '{}' must specify expectations for at least one mode",
|
||||
test_case.note
|
||||
);
|
||||
}
|
||||
|
||||
for expectation in expectations {
|
||||
let mode_label = if expectation.strict {
|
||||
"strict"
|
||||
} else {
|
||||
"non-strict"
|
||||
};
|
||||
std::println!(" Mode: {}", mode_label);
|
||||
|
||||
let execution_result = execute_vm_instructions(
|
||||
instructions.clone(),
|
||||
test_case.literals.clone(),
|
||||
test_case.rule_infos.clone(),
|
||||
test_case.rule_tree.clone(),
|
||||
test_case.instruction_params.clone(),
|
||||
test_case.data.clone(),
|
||||
test_case.input.clone(),
|
||||
test_case.max_instructions,
|
||||
test_case.host_await_responses.clone(),
|
||||
test_case.host_await_responses_run_to_completion.clone(),
|
||||
test_case.host_await_responses_suspendable.clone(),
|
||||
ignore_hostawait_failure,
|
||||
expectation.strict,
|
||||
);
|
||||
|
||||
if expectation.want_error.is_some() && expectation.want_result.is_some() {
|
||||
panic!(
|
||||
"Test case '{}' cannot specify both want_result and want_error for {} mode",
|
||||
test_case.note, mode_label
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(expected_error) = expectation.want_error {
|
||||
match execution_result {
|
||||
Err(e) => {
|
||||
let error_msg = std::format!("{}", e);
|
||||
if !error_msg.contains(expected_error) {
|
||||
std::println!(
|
||||
"Test case '{}' failed ({} mode):",
|
||||
test_case.note,
|
||||
mode_label
|
||||
);
|
||||
std::println!(" Expected error containing: '{}'", expected_error);
|
||||
std::println!(" Actual error: '{}'", error_msg);
|
||||
panic!("VM test case failed: {}", test_case.note);
|
||||
}
|
||||
}
|
||||
Ok(result) => {
|
||||
std::println!(
|
||||
"Test case '{}' failed ({} mode):",
|
||||
test_case.note,
|
||||
mode_label
|
||||
);
|
||||
std::println!(" Expected error containing: '{}'", expected_error);
|
||||
std::println!(" But got successful result: {:?}", result);
|
||||
panic!("VM test case failed: {}", test_case.note);
|
||||
}
|
||||
}
|
||||
} else if let Some(want_result) = expectation.want_result {
|
||||
let expected_result = process_value(want_result)?;
|
||||
|
||||
let actual_result = match execution_result {
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
if std::format!("{}", e).contains("Assertion failed") {
|
||||
Value::Undefined
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if actual_result != expected_result {
|
||||
std::println!(
|
||||
"Test case '{}' failed ({} mode):",
|
||||
test_case.note,
|
||||
mode_label
|
||||
);
|
||||
std::println!(" Expected: {:?}", expected_result);
|
||||
std::println!(" Actual: {:?}", actual_result);
|
||||
panic!("VM test case failed: {}", test_case.note);
|
||||
}
|
||||
} else {
|
||||
panic!(
|
||||
"Test case '{}' must specify either want_result or want_error for {} mode",
|
||||
test_case.note, mode_label
|
||||
);
|
||||
}
|
||||
|
||||
std::println!(" ✓ {} mode passed", mode_label);
|
||||
}
|
||||
|
||||
std::println!("✓ Test case '{}' passed", test_case.note);
|
||||
}
|
||||
std::println!("✓ Test suite '{}' completed successfully", file);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_resources("tests/rvm/vm/suites/*.yaml")]
|
||||
fn run_vm_test_file(file: &str) {
|
||||
run_vm_test_suite(file).unwrap()
|
||||
}
|
||||
|
||||
#[test_resources("tests/rvm/vm/suites/loops/*.yaml")]
|
||||
fn run_loop_test_file(file: &str) {
|
||||
run_vm_test_suite(file).unwrap()
|
||||
}
|
||||
}
|
||||
101
src/rvm/vm/arithmetic.rs
Normal file
101
src/rvm/vm/arithmetic.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::number::Number;
|
||||
use crate::value::Value;
|
||||
|
||||
use super::errors::{Result, VmError};
|
||||
use super::machine::RegoVM;
|
||||
|
||||
impl RegoVM {
|
||||
/// Add two values using interpreter's arithmetic logic
|
||||
pub(super) fn add_values(&self, a: &Value, b: &Value) -> Result<Value> {
|
||||
match (a, b) {
|
||||
(Value::Number(x), Value::Number(y)) => Ok(Value::from(x.add(y)?)),
|
||||
_ => Err(VmError::InvalidAddition {
|
||||
left: a.clone(),
|
||||
right: b.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Subtract two values using interpreter's arithmetic logic
|
||||
pub(super) fn sub_values(&self, a: &Value, b: &Value) -> Result<Value> {
|
||||
match (a, b) {
|
||||
(Value::Number(x), Value::Number(y)) => Ok(Value::from(x.sub(y)?)),
|
||||
_ => Err(VmError::InvalidSubtraction {
|
||||
left: a.clone(),
|
||||
right: b.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Multiply two values using interpreter's arithmetic logic
|
||||
pub(super) fn mul_values(&self, a: &Value, b: &Value) -> Result<Value> {
|
||||
match (a, b) {
|
||||
(Value::Number(x), Value::Number(y)) => Ok(Value::from(x.mul(y)?)),
|
||||
_ => Err(VmError::InvalidMultiplication {
|
||||
left: a.clone(),
|
||||
right: b.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Divide two values using interpreter's arithmetic logic
|
||||
pub(super) fn div_values(&self, a: &Value, b: &Value) -> Result<Value> {
|
||||
match (a, b) {
|
||||
(Value::Number(x), Value::Number(y)) => {
|
||||
if *y == Number::from(0u64) {
|
||||
if self.strict_builtin_errors {
|
||||
return Err(VmError::InvalidDivision {
|
||||
left: a.clone(),
|
||||
right: b.clone(),
|
||||
});
|
||||
}
|
||||
return Ok(Value::Undefined);
|
||||
}
|
||||
|
||||
Ok(Value::from(x.clone().divide(y)?))
|
||||
}
|
||||
_ => Err(VmError::InvalidDivision {
|
||||
left: a.clone(),
|
||||
right: b.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Modulo two values using interpreter's arithmetic logic
|
||||
pub(super) fn mod_values(&self, a: &Value, b: &Value) -> Result<Value> {
|
||||
match (a, b) {
|
||||
(Value::Number(x), Value::Number(y)) => {
|
||||
if *y == Number::from(0u64) {
|
||||
if self.strict_builtin_errors {
|
||||
return Err(VmError::InvalidModulo {
|
||||
left: a.clone(),
|
||||
right: b.clone(),
|
||||
});
|
||||
}
|
||||
return Ok(Value::Undefined);
|
||||
}
|
||||
|
||||
if !x.is_integer() || !y.is_integer() {
|
||||
return Err(VmError::ModuloOnFloat);
|
||||
}
|
||||
|
||||
Ok(Value::from(x.clone().modulo(y)?))
|
||||
}
|
||||
_ => Err(VmError::InvalidModulo {
|
||||
left: a.clone(),
|
||||
right: b.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn to_bool(&self, value: &Value) -> Option<bool> {
|
||||
match value {
|
||||
Value::Bool(b) => Some(*b),
|
||||
Value::Null if !self.strict_builtin_errors => Some(true),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
543
src/rvm/vm/comprehension.rs
Normal file
543
src/rvm/vm/comprehension.rs
Normal file
@@ -0,0 +1,543 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::rvm::instructions::{ComprehensionBeginParams, ComprehensionMode};
|
||||
use crate::value::Value;
|
||||
use crate::Rc;
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::format;
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::context::{ComprehensionContext, IterationState};
|
||||
use super::errors::{Result, VmError};
|
||||
use super::execution_model::{ExecutionFrame, ExecutionMode, FrameKind};
|
||||
use super::machine::RegoVM;
|
||||
|
||||
impl RegoVM {
|
||||
pub(super) fn execute_comprehension_begin(
|
||||
&mut self,
|
||||
params: &ComprehensionBeginParams,
|
||||
) -> Result<()> {
|
||||
match self.execution_mode {
|
||||
ExecutionMode::RunToCompletion => {
|
||||
self.execute_comprehension_begin_run_to_completion(params)
|
||||
}
|
||||
ExecutionMode::Suspendable => self.execute_comprehension_begin_suspendable(params),
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_comprehension_begin_run_to_completion(
|
||||
&mut self,
|
||||
params: &ComprehensionBeginParams,
|
||||
) -> Result<()> {
|
||||
let initial_result = match params.mode {
|
||||
ComprehensionMode::Set => Value::new_set(),
|
||||
ComprehensionMode::Array => Value::new_array(),
|
||||
ComprehensionMode::Object => Value::Object(Rc::new(BTreeMap::new())),
|
||||
};
|
||||
self.registers[params.result_reg as usize] = initial_result.clone();
|
||||
|
||||
let auto_iterate = params.collection_reg != params.result_reg;
|
||||
let iteration_state = if auto_iterate {
|
||||
let source_value = self.registers[params.collection_reg as usize].clone();
|
||||
match source_value {
|
||||
Value::Array(items) => {
|
||||
if items.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(IterationState::Array { items, index: 0 })
|
||||
}
|
||||
}
|
||||
Value::Object(obj) => {
|
||||
if obj.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(IterationState::Object {
|
||||
obj,
|
||||
current_key: None,
|
||||
first_iteration: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
Value::Set(set) => {
|
||||
if set.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(IterationState::Set {
|
||||
items: set,
|
||||
current_item: None,
|
||||
first_iteration: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
Value::Undefined => None,
|
||||
Value::Null => None,
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut has_iteration = false;
|
||||
if let Some(state) = iteration_state.as_ref() {
|
||||
has_iteration = self.setup_next_iteration(state, params.key_reg, params.value_reg)?;
|
||||
}
|
||||
|
||||
let resume_pc = if auto_iterate {
|
||||
params.comprehension_end as usize
|
||||
} else {
|
||||
params.comprehension_end.saturating_sub(1) as usize
|
||||
};
|
||||
|
||||
let mut comprehension_context = ComprehensionContext {
|
||||
mode: params.mode.clone(),
|
||||
result_reg: params.result_reg,
|
||||
key_reg: params.key_reg,
|
||||
value_reg: params.value_reg,
|
||||
body_start: params.body_start,
|
||||
comprehension_end: params.comprehension_end,
|
||||
iteration_state,
|
||||
resume_pc,
|
||||
};
|
||||
|
||||
if auto_iterate {
|
||||
if has_iteration {
|
||||
self.pc = params.body_start as usize - 1;
|
||||
} else {
|
||||
comprehension_context.iteration_state = None;
|
||||
self.pc = params.comprehension_end as usize - 1;
|
||||
}
|
||||
}
|
||||
|
||||
self.comprehension_stack.push(comprehension_context);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_comprehension_begin_suspendable(
|
||||
&mut self,
|
||||
params: &ComprehensionBeginParams,
|
||||
) -> Result<()> {
|
||||
let initial_result = match params.mode {
|
||||
ComprehensionMode::Set => Value::new_set(),
|
||||
ComprehensionMode::Array => Value::new_array(),
|
||||
ComprehensionMode::Object => Value::Object(Rc::new(BTreeMap::new())),
|
||||
};
|
||||
self.registers[params.result_reg as usize] = initial_result.clone();
|
||||
|
||||
let auto_iterate = params.collection_reg != params.result_reg;
|
||||
let iteration_state = if auto_iterate {
|
||||
let source_value = self.registers[params.collection_reg as usize].clone();
|
||||
match source_value {
|
||||
Value::Array(items) => {
|
||||
if items.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(IterationState::Array { items, index: 0 })
|
||||
}
|
||||
}
|
||||
Value::Object(obj) => {
|
||||
if obj.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(IterationState::Object {
|
||||
obj,
|
||||
current_key: None,
|
||||
first_iteration: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
Value::Set(set) => {
|
||||
if set.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(IterationState::Set {
|
||||
items: set,
|
||||
current_item: None,
|
||||
first_iteration: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
Value::Undefined => None,
|
||||
Value::Null => None,
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let has_iteration = if let Some(state) = iteration_state.as_ref() {
|
||||
self.setup_next_iteration(state, params.key_reg, params.value_reg)?
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let resume_pc = if auto_iterate {
|
||||
params.comprehension_end as usize
|
||||
} else {
|
||||
params.comprehension_end.saturating_sub(1) as usize
|
||||
};
|
||||
|
||||
let mut comprehension_context = ComprehensionContext {
|
||||
mode: params.mode.clone(),
|
||||
result_reg: params.result_reg,
|
||||
key_reg: params.key_reg,
|
||||
value_reg: params.value_reg,
|
||||
body_start: params.body_start,
|
||||
comprehension_end: params.comprehension_end,
|
||||
iteration_state,
|
||||
resume_pc,
|
||||
};
|
||||
|
||||
let next_pc = if auto_iterate {
|
||||
if has_iteration {
|
||||
params.body_start as usize
|
||||
} else {
|
||||
comprehension_context.iteration_state = None;
|
||||
params.comprehension_end as usize
|
||||
}
|
||||
} else {
|
||||
self.pc + 1
|
||||
};
|
||||
|
||||
let return_pc = comprehension_context.resume_pc;
|
||||
|
||||
let frame = ExecutionFrame::new(
|
||||
next_pc,
|
||||
FrameKind::Comprehension {
|
||||
return_pc,
|
||||
context: comprehension_context,
|
||||
},
|
||||
);
|
||||
self.execution_stack.push(frame);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn execute_comprehension_yield(
|
||||
&mut self,
|
||||
value_reg: u8,
|
||||
key_reg: Option<u8>,
|
||||
) -> Result<()> {
|
||||
match self.execution_mode {
|
||||
ExecutionMode::RunToCompletion => {
|
||||
self.execute_comprehension_yield_run_to_completion(value_reg, key_reg)
|
||||
}
|
||||
ExecutionMode::Suspendable => {
|
||||
self.execute_comprehension_yield_suspendable(value_reg, key_reg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_comprehension_yield_run_to_completion(
|
||||
&mut self,
|
||||
value_reg: u8,
|
||||
key_reg: Option<u8>,
|
||||
) -> Result<()> {
|
||||
let mut comprehension_context = if let Some(context) = self.comprehension_stack.pop() {
|
||||
context
|
||||
} else {
|
||||
return Err(VmError::InvalidIteration {
|
||||
value: Value::String(Arc::from("No active comprehension")),
|
||||
});
|
||||
};
|
||||
|
||||
let value_to_add = self.registers[value_reg as usize].clone();
|
||||
let key_value = if let Some(key_reg) = key_reg {
|
||||
Some(self.registers[key_reg as usize].clone())
|
||||
} else if matches!(comprehension_context.mode, ComprehensionMode::Object) {
|
||||
Some(self.registers[comprehension_context.key_reg as usize].clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let result_reg = comprehension_context.result_reg as usize;
|
||||
let current_result = self.registers[result_reg].clone();
|
||||
let mode = comprehension_context.mode.clone();
|
||||
|
||||
let updated_result = match (mode, current_result) {
|
||||
(ComprehensionMode::Set, Value::Set(set)) => {
|
||||
let mut new_set = set.as_ref().clone();
|
||||
new_set.insert(value_to_add);
|
||||
Value::Set(crate::Rc::new(new_set))
|
||||
}
|
||||
(ComprehensionMode::Array, Value::Array(arr)) => {
|
||||
let mut new_arr = arr.as_ref().to_vec();
|
||||
new_arr.push(value_to_add);
|
||||
Value::Array(crate::Rc::new(new_arr))
|
||||
}
|
||||
(ComprehensionMode::Object, Value::Object(obj)) => {
|
||||
if let Some(key) = key_value {
|
||||
let mut new_obj = obj.as_ref().clone();
|
||||
new_obj.insert(key, value_to_add);
|
||||
Value::Object(crate::Rc::new(new_obj))
|
||||
} else {
|
||||
self.comprehension_stack.push(comprehension_context);
|
||||
return Err(VmError::InvalidIteration {
|
||||
value: Value::String(Arc::from("Object comprehension requires key")),
|
||||
});
|
||||
}
|
||||
}
|
||||
(_mode, other) => {
|
||||
self.comprehension_stack.push(comprehension_context);
|
||||
return Err(VmError::InvalidIteration { value: other });
|
||||
}
|
||||
};
|
||||
|
||||
self.registers[result_reg] = updated_result;
|
||||
|
||||
if let Some(iter_state) = comprehension_context.iteration_state.as_mut() {
|
||||
match iter_state {
|
||||
IterationState::Object { current_key, .. } => {
|
||||
let tracked_key =
|
||||
if comprehension_context.key_reg != comprehension_context.value_reg {
|
||||
self.registers[comprehension_context.key_reg as usize].clone()
|
||||
} else {
|
||||
self.registers[comprehension_context.value_reg as usize].clone()
|
||||
};
|
||||
*current_key = Some(tracked_key);
|
||||
}
|
||||
IterationState::Set { current_item, .. } => {
|
||||
*current_item =
|
||||
Some(self.registers[comprehension_context.value_reg as usize].clone());
|
||||
}
|
||||
IterationState::Array { .. } => {}
|
||||
}
|
||||
|
||||
iter_state.advance();
|
||||
let has_next = self.setup_next_iteration(
|
||||
iter_state,
|
||||
comprehension_context.key_reg,
|
||||
comprehension_context.value_reg,
|
||||
)?;
|
||||
|
||||
if has_next {
|
||||
self.pc = comprehension_context.body_start as usize - 1;
|
||||
} else {
|
||||
comprehension_context.iteration_state = None;
|
||||
self.pc = comprehension_context.comprehension_end as usize - 1;
|
||||
}
|
||||
}
|
||||
|
||||
self.comprehension_stack.push(comprehension_context);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_comprehension_yield_suspendable(
|
||||
&mut self,
|
||||
value_reg: u8,
|
||||
key_reg: Option<u8>,
|
||||
) -> Result<()> {
|
||||
let comprehension_index = (0..self.execution_stack.len())
|
||||
.rev()
|
||||
.find(|&idx| {
|
||||
self.execution_stack
|
||||
.get(idx)
|
||||
.is_some_and(|frame| matches!(frame.kind, FrameKind::Comprehension { .. }))
|
||||
})
|
||||
.ok_or(VmError::InvalidIteration {
|
||||
value: Value::String(Arc::from("No active comprehension")),
|
||||
})?;
|
||||
|
||||
let (iteration_state_snapshot, key_reg_idx, value_reg_idx, body_start, comprehension_end) = {
|
||||
let frame = self.execution_stack.get_mut(comprehension_index).ok_or(
|
||||
VmError::InvalidIteration {
|
||||
value: Value::String(Arc::from("No active comprehension")),
|
||||
},
|
||||
)?;
|
||||
|
||||
match &mut frame.kind {
|
||||
FrameKind::Comprehension { context, .. } => {
|
||||
let value_to_add = self.registers[value_reg as usize].clone();
|
||||
let key_value = if let Some(key_reg) = key_reg {
|
||||
Some(self.registers[key_reg as usize].clone())
|
||||
} else if matches!(context.mode, ComprehensionMode::Object) {
|
||||
Some(self.registers[context.key_reg as usize].clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let result_reg_idx = context.result_reg as usize;
|
||||
let current_result = self.registers[result_reg_idx].clone();
|
||||
let mode = context.mode.clone();
|
||||
|
||||
let updated_result = match (mode, current_result) {
|
||||
(ComprehensionMode::Set, Value::Set(set)) => {
|
||||
let mut new_set = set.as_ref().clone();
|
||||
new_set.insert(value_to_add);
|
||||
Value::Set(crate::Rc::new(new_set))
|
||||
}
|
||||
(ComprehensionMode::Array, Value::Array(arr)) => {
|
||||
let mut new_arr = arr.as_ref().to_vec();
|
||||
new_arr.push(value_to_add);
|
||||
Value::Array(crate::Rc::new(new_arr))
|
||||
}
|
||||
(ComprehensionMode::Object, Value::Object(obj)) => {
|
||||
if let Some(key) = key_value {
|
||||
let mut new_obj = obj.as_ref().clone();
|
||||
new_obj.insert(key, value_to_add);
|
||||
Value::Object(crate::Rc::new(new_obj))
|
||||
} else {
|
||||
return Err(VmError::InvalidIteration {
|
||||
value: Value::String(Arc::from(
|
||||
"Object comprehension requires key",
|
||||
)),
|
||||
});
|
||||
}
|
||||
}
|
||||
(_mode, other) => {
|
||||
return Err(VmError::InvalidIteration { value: other });
|
||||
}
|
||||
};
|
||||
|
||||
self.registers[result_reg_idx] = updated_result;
|
||||
|
||||
if let Some(iter_state) = context.iteration_state.as_mut() {
|
||||
match iter_state {
|
||||
IterationState::Object { current_key, .. } => {
|
||||
let tracked_key = if context.key_reg != context.value_reg {
|
||||
self.registers[context.key_reg as usize].clone()
|
||||
} else {
|
||||
self.registers[context.value_reg as usize].clone()
|
||||
};
|
||||
*current_key = Some(tracked_key);
|
||||
}
|
||||
IterationState::Set { current_item, .. } => {
|
||||
*current_item =
|
||||
Some(self.registers[context.value_reg as usize].clone());
|
||||
}
|
||||
IterationState::Array { .. } => {}
|
||||
}
|
||||
|
||||
iter_state.advance();
|
||||
}
|
||||
|
||||
(
|
||||
context.iteration_state.clone(),
|
||||
context.key_reg,
|
||||
context.value_reg,
|
||||
context.body_start,
|
||||
context.comprehension_end,
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
return Err(VmError::InvalidIteration {
|
||||
value: Value::String(Arc::from("No active comprehension")),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(state) = iteration_state_snapshot.as_ref() {
|
||||
let has_next = self.setup_next_iteration(state, key_reg_idx, value_reg_idx)?;
|
||||
|
||||
if has_next {
|
||||
if let Some(frame) = self.execution_stack.get_mut(comprehension_index) {
|
||||
frame.pc = body_start as usize;
|
||||
self.frame_pc_overridden = true;
|
||||
}
|
||||
} else if let Some(frame) = self.execution_stack.get_mut(comprehension_index) {
|
||||
if let FrameKind::Comprehension { context, .. } = &mut frame.kind {
|
||||
context.iteration_state = None;
|
||||
}
|
||||
frame.pc = comprehension_end as usize;
|
||||
self.frame_pc_overridden = true;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn execute_comprehension_end(&mut self) -> Result<()> {
|
||||
match self.execution_mode {
|
||||
ExecutionMode::RunToCompletion => self.execute_comprehension_end_run_to_completion(),
|
||||
ExecutionMode::Suspendable => self.execute_comprehension_end_suspendable(),
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_comprehension_end_run_to_completion(&mut self) -> Result<()> {
|
||||
if let Some(_context) = self.comprehension_stack.pop() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(VmError::InvalidIteration {
|
||||
value: Value::String(Arc::from("No active comprehension context")),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_comprehension_end_suspendable(&mut self) -> Result<()> {
|
||||
let mut unwound_frames: Vec<ExecutionFrame> = Vec::new();
|
||||
|
||||
loop {
|
||||
let frame = match self.execution_stack.pop() {
|
||||
Some(frame) => frame,
|
||||
None => {
|
||||
// Restore any frames we already unwound before propagating the error.
|
||||
while let Some(restored) = unwound_frames.pop() {
|
||||
self.execution_stack.push(restored);
|
||||
}
|
||||
return Err(VmError::InvalidIteration {
|
||||
value: Value::String(Arc::from("No active comprehension context")),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let ExecutionFrame {
|
||||
pc: frame_pc,
|
||||
kind: frame_kind,
|
||||
} = frame;
|
||||
|
||||
match frame_kind {
|
||||
FrameKind::Comprehension {
|
||||
return_pc: _,
|
||||
context,
|
||||
} => {
|
||||
let raw_target = context.resume_pc;
|
||||
let resume_pc = if raw_target <= self.pc {
|
||||
self.pc.saturating_add(1)
|
||||
} else if raw_target == self.pc.saturating_add(1) {
|
||||
raw_target
|
||||
} else {
|
||||
raw_target.saturating_sub(1)
|
||||
};
|
||||
if let Some(parent) = self.execution_stack.last_mut() {
|
||||
parent.pc = resume_pc;
|
||||
}
|
||||
while let Some(restored) = unwound_frames.pop() {
|
||||
self.execution_stack.push(restored);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
FrameKind::Loop { return_pc, context } => {
|
||||
if let Some(parent) = self.execution_stack.last_mut() {
|
||||
parent.pc = return_pc;
|
||||
}
|
||||
// Keep the loop frame available so we can restore it if we discover a mismatch.
|
||||
unwound_frames.push(ExecutionFrame::new(
|
||||
frame_pc,
|
||||
FrameKind::Loop { return_pc, context },
|
||||
));
|
||||
}
|
||||
other_kind => {
|
||||
let message = format!(
|
||||
"Mismatched comprehension frame: frame={:?} stack_depth={} unwound_loops={}",
|
||||
&other_kind,
|
||||
self.execution_stack.len(),
|
||||
unwound_frames.len()
|
||||
);
|
||||
// Put the unexpected frame back on the stack along with any loops we unwound.
|
||||
self.execution_stack
|
||||
.push(ExecutionFrame::new(frame_pc, other_kind));
|
||||
while let Some(restored) = unwound_frames.pop() {
|
||||
self.execution_stack.push(restored);
|
||||
}
|
||||
return Err(VmError::InvalidIteration {
|
||||
value: Value::String(Arc::from(message.into_boxed_str())),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
97
src/rvm/vm/context.rs
Normal file
97
src/rvm/vm/context.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::rvm::instructions::{ComprehensionMode, LoopMode};
|
||||
use crate::value::Value;
|
||||
use crate::Rc;
|
||||
use alloc::collections::{BTreeMap, BTreeSet};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
/// Loop execution context for managing iteration state
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LoopContext {
|
||||
pub mode: LoopMode,
|
||||
pub iteration_state: IterationState,
|
||||
pub key_reg: u8,
|
||||
pub value_reg: u8,
|
||||
pub result_reg: u8,
|
||||
pub body_start: u16,
|
||||
pub loop_end: u16,
|
||||
pub loop_next_pc: u16, // PC of the LoopNext instruction to avoid searching
|
||||
pub body_resume_pc: usize,
|
||||
pub success_count: usize,
|
||||
pub total_iterations: usize,
|
||||
pub current_iteration_failed: bool, // Track if current iteration had condition failures
|
||||
}
|
||||
|
||||
/// Iterator state for different collection types
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum IterationState {
|
||||
Array {
|
||||
items: Rc<Vec<Value>>,
|
||||
index: usize,
|
||||
},
|
||||
Object {
|
||||
obj: Rc<BTreeMap<Value, Value>>,
|
||||
current_key: Option<Value>,
|
||||
first_iteration: bool,
|
||||
},
|
||||
Set {
|
||||
items: Rc<BTreeSet<Value>>,
|
||||
current_item: Option<Value>,
|
||||
first_iteration: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl IterationState {
|
||||
pub(super) fn advance(&mut self) {
|
||||
match self {
|
||||
IterationState::Array { index, .. } => {
|
||||
*index += 1;
|
||||
}
|
||||
IterationState::Object {
|
||||
first_iteration, ..
|
||||
} => {
|
||||
*first_iteration = false;
|
||||
}
|
||||
IterationState::Set {
|
||||
first_iteration, ..
|
||||
} => {
|
||||
*first_iteration = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CallRuleContext {
|
||||
pub return_pc: usize,
|
||||
pub dest_reg: u8,
|
||||
pub result_reg: u8,
|
||||
pub rule_index: u16,
|
||||
pub rule_type: crate::rvm::program::RuleType,
|
||||
pub current_definition_index: usize,
|
||||
pub current_body_index: usize,
|
||||
}
|
||||
|
||||
/// Context for tracking active comprehensions
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct ComprehensionContext {
|
||||
/// Type of comprehension (Array, Set, Object)
|
||||
pub(super) mode: ComprehensionMode,
|
||||
/// Register storing the comprehension result collection
|
||||
pub(super) result_reg: u8,
|
||||
/// Register holding the current iteration key
|
||||
pub(super) key_reg: u8,
|
||||
/// Register holding the current iteration value
|
||||
pub(super) value_reg: u8,
|
||||
/// Jump target for comprehension body start
|
||||
pub(super) body_start: u16,
|
||||
/// Jump target for comprehension end
|
||||
pub(super) comprehension_end: u16,
|
||||
/// Iteration state when comprehension manages iteration itself (None when driven by LoopStart/LoopNext)
|
||||
pub(super) iteration_state: Option<IterationState>,
|
||||
/// Resume location for the parent frame once this comprehension completes
|
||||
pub(super) resume_pc: usize,
|
||||
}
|
||||
771
src/rvm/vm/dispatch.rs
Normal file
771
src/rvm/vm/dispatch.rs
Normal file
@@ -0,0 +1,771 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::rvm::instructions::{Instruction, LiteralOrRegister};
|
||||
use crate::rvm::program::Program;
|
||||
use crate::value::Value;
|
||||
use alloc::collections::BTreeSet;
|
||||
use alloc::vec::Vec;
|
||||
use core::mem;
|
||||
|
||||
use super::errors::{Result, VmError};
|
||||
use super::execution_model::{ExecutionMode, SuspendReason};
|
||||
use super::loops::LoopParams;
|
||||
use super::machine::RegoVM;
|
||||
|
||||
pub(super) enum InstructionOutcome {
|
||||
Continue,
|
||||
Return(Value),
|
||||
Break,
|
||||
Suspend { reason: SuspendReason },
|
||||
}
|
||||
|
||||
impl RegoVM {
|
||||
pub(super) fn execute_instruction(
|
||||
&mut self,
|
||||
program: &Program,
|
||||
instruction: Instruction,
|
||||
) -> Result<InstructionOutcome> {
|
||||
self.execute_load_and_move(program, instruction)
|
||||
}
|
||||
|
||||
fn execute_load_and_move(
|
||||
&mut self,
|
||||
program: &Program,
|
||||
instruction: Instruction,
|
||||
) -> Result<InstructionOutcome> {
|
||||
use Instruction::*;
|
||||
match instruction {
|
||||
Load { dest, literal_idx } => {
|
||||
if let Some(value) = program.literals.get(literal_idx as usize) {
|
||||
self.registers[dest as usize] = value.clone();
|
||||
Ok(InstructionOutcome::Continue)
|
||||
} else {
|
||||
Err(VmError::LiteralIndexOutOfBounds {
|
||||
index: literal_idx as usize,
|
||||
})
|
||||
}
|
||||
}
|
||||
LoadTrue { dest } => {
|
||||
self.registers[dest as usize] = Value::Bool(true);
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
LoadFalse { dest } => {
|
||||
self.registers[dest as usize] = Value::Bool(false);
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
LoadNull { dest } => {
|
||||
self.registers[dest as usize] = Value::Null;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
LoadBool { dest, value } => {
|
||||
self.registers[dest as usize] = Value::Bool(value);
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
LoadData { dest } => {
|
||||
self.registers[dest as usize] = self.data.clone();
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
LoadInput { dest } => {
|
||||
self.registers[dest as usize] = self.input.clone();
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
Move { dest, src } => {
|
||||
self.registers[dest as usize] = self.registers[src as usize].clone();
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
other => self.execute_arithmetic_instruction(program, other),
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_arithmetic_instruction(
|
||||
&mut self,
|
||||
_program: &Program,
|
||||
instruction: Instruction,
|
||||
) -> Result<InstructionOutcome> {
|
||||
use Instruction::*;
|
||||
match instruction {
|
||||
Add { dest, left, right } => {
|
||||
let a = &self.registers[left as usize];
|
||||
let b = &self.registers[right as usize];
|
||||
|
||||
if a == &Value::Undefined || b == &Value::Undefined {
|
||||
self.registers[dest as usize] = Value::Undefined;
|
||||
return Ok(InstructionOutcome::Continue);
|
||||
}
|
||||
|
||||
let result = self.add_values(a, b)?;
|
||||
self.registers[dest as usize] = result;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
Sub { dest, left, right } => {
|
||||
let a = &self.registers[left as usize];
|
||||
let b = &self.registers[right as usize];
|
||||
|
||||
if a == &Value::Undefined || b == &Value::Undefined {
|
||||
self.registers[dest as usize] = Value::Undefined;
|
||||
return Ok(InstructionOutcome::Continue);
|
||||
}
|
||||
|
||||
let result = self.sub_values(a, b)?;
|
||||
self.registers[dest as usize] = result;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
Mul { dest, left, right } => {
|
||||
let a = &self.registers[left as usize];
|
||||
let b = &self.registers[right as usize];
|
||||
|
||||
if a == &Value::Undefined || b == &Value::Undefined {
|
||||
self.registers[dest as usize] = Value::Undefined;
|
||||
return Ok(InstructionOutcome::Continue);
|
||||
}
|
||||
|
||||
let result = self.mul_values(a, b)?;
|
||||
self.registers[dest as usize] = result;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
Div { dest, left, right } => {
|
||||
let a = &self.registers[left as usize];
|
||||
let b = &self.registers[right as usize];
|
||||
|
||||
if a == &Value::Undefined || b == &Value::Undefined {
|
||||
self.registers[dest as usize] = Value::Undefined;
|
||||
return Ok(InstructionOutcome::Continue);
|
||||
}
|
||||
|
||||
let result = self.div_values(a, b)?;
|
||||
self.registers[dest as usize] = result;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
Mod { dest, left, right } => {
|
||||
let a = &self.registers[left as usize];
|
||||
let b = &self.registers[right as usize];
|
||||
|
||||
if a == &Value::Undefined || b == &Value::Undefined {
|
||||
self.registers[dest as usize] = Value::Undefined;
|
||||
return Ok(InstructionOutcome::Continue);
|
||||
}
|
||||
|
||||
let result = self.mod_values(a, b)?;
|
||||
self.registers[dest as usize] = result;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
other => self.execute_comparison_instruction(_program, other),
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_comparison_instruction(
|
||||
&mut self,
|
||||
_program: &Program,
|
||||
instruction: Instruction,
|
||||
) -> Result<InstructionOutcome> {
|
||||
use Instruction::*;
|
||||
match instruction {
|
||||
Eq { dest, left, right } => {
|
||||
let a = &self.registers[left as usize];
|
||||
let b = &self.registers[right as usize];
|
||||
|
||||
if a == &Value::Undefined || b == &Value::Undefined {
|
||||
self.registers[dest as usize] = Value::Undefined;
|
||||
return Ok(InstructionOutcome::Continue);
|
||||
}
|
||||
|
||||
self.registers[dest as usize] = Value::Bool(a == b);
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
Ne { dest, left, right } => {
|
||||
let a = &self.registers[left as usize];
|
||||
let b = &self.registers[right as usize];
|
||||
|
||||
if a == &Value::Undefined || b == &Value::Undefined {
|
||||
self.registers[dest as usize] = Value::Undefined;
|
||||
return Ok(InstructionOutcome::Continue);
|
||||
}
|
||||
|
||||
self.registers[dest as usize] = Value::Bool(a != b);
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
Lt { dest, left, right } => {
|
||||
let a = &self.registers[left as usize];
|
||||
let b = &self.registers[right as usize];
|
||||
|
||||
if a == &Value::Undefined || b == &Value::Undefined {
|
||||
self.registers[dest as usize] = Value::Undefined;
|
||||
return Ok(InstructionOutcome::Continue);
|
||||
}
|
||||
|
||||
if self.strict_builtin_errors && mem::discriminant(a) != mem::discriminant(b) {
|
||||
return Err(VmError::ArithmeticError(alloc::format!(
|
||||
"#undefined: cannot compare values of different types (left={a:?}, right={b:?})"
|
||||
)));
|
||||
}
|
||||
|
||||
self.registers[dest as usize] = Value::Bool(a < b);
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
Le { dest, left, right } => {
|
||||
let a = &self.registers[left as usize];
|
||||
let b = &self.registers[right as usize];
|
||||
|
||||
if a == &Value::Undefined || b == &Value::Undefined {
|
||||
self.registers[dest as usize] = Value::Undefined;
|
||||
return Ok(InstructionOutcome::Continue);
|
||||
}
|
||||
|
||||
if self.strict_builtin_errors && mem::discriminant(a) != mem::discriminant(b) {
|
||||
return Err(VmError::ArithmeticError(alloc::format!(
|
||||
"#undefined: cannot compare values of different types (left={a:?}, right={b:?})"
|
||||
)));
|
||||
}
|
||||
|
||||
self.registers[dest as usize] = Value::Bool(a <= b);
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
Gt { dest, left, right } => {
|
||||
let a = &self.registers[left as usize];
|
||||
let b = &self.registers[right as usize];
|
||||
|
||||
if a == &Value::Undefined || b == &Value::Undefined {
|
||||
self.registers[dest as usize] = Value::Undefined;
|
||||
return Ok(InstructionOutcome::Continue);
|
||||
}
|
||||
|
||||
if self.strict_builtin_errors && mem::discriminant(a) != mem::discriminant(b) {
|
||||
return Err(VmError::ArithmeticError(alloc::format!(
|
||||
"#undefined: cannot compare values of different types (left={a:?}, right={b:?})"
|
||||
)));
|
||||
}
|
||||
|
||||
self.registers[dest as usize] = Value::Bool(a > b);
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
Ge { dest, left, right } => {
|
||||
let a = &self.registers[left as usize];
|
||||
let b = &self.registers[right as usize];
|
||||
|
||||
if a == &Value::Undefined || b == &Value::Undefined {
|
||||
self.registers[dest as usize] = Value::Undefined;
|
||||
return Ok(InstructionOutcome::Continue);
|
||||
}
|
||||
|
||||
if self.strict_builtin_errors && mem::discriminant(a) != mem::discriminant(b) {
|
||||
return Err(VmError::ArithmeticError(alloc::format!(
|
||||
"#undefined: cannot compare values of different types (left={a:?}, right={b:?})"
|
||||
)));
|
||||
}
|
||||
|
||||
self.registers[dest as usize] = Value::Bool(a >= b);
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
And { dest, left, right } => {
|
||||
let left_value = &self.registers[left as usize];
|
||||
let right_value = &self.registers[right as usize];
|
||||
|
||||
if left_value == &Value::Undefined || right_value == &Value::Undefined {
|
||||
self.registers[dest as usize] = Value::Undefined;
|
||||
return Ok(InstructionOutcome::Continue);
|
||||
}
|
||||
|
||||
match (self.to_bool(left_value), self.to_bool(right_value)) {
|
||||
(Some(a), Some(b)) => {
|
||||
self.registers[dest as usize] = Value::Bool(a && b);
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
_ => Err(VmError::ArithmeticError(alloc::format!(
|
||||
"#undefined: logical AND expects booleans (left={left_value:?}, right={right_value:?})"
|
||||
))),
|
||||
}
|
||||
}
|
||||
Or { dest, left, right } => {
|
||||
let left_value = &self.registers[left as usize];
|
||||
let right_value = &self.registers[right as usize];
|
||||
|
||||
if left_value == &Value::Undefined || right_value == &Value::Undefined {
|
||||
self.registers[dest as usize] = Value::Undefined;
|
||||
return Ok(InstructionOutcome::Continue);
|
||||
}
|
||||
|
||||
match (self.to_bool(left_value), self.to_bool(right_value)) {
|
||||
(Some(a), Some(b)) => {
|
||||
self.registers[dest as usize] = Value::Bool(a || b);
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
_ => Err(VmError::ArithmeticError(alloc::format!(
|
||||
"#undefined: logical OR expects booleans (left={left_value:?}, right={right_value:?})"
|
||||
))),
|
||||
}
|
||||
}
|
||||
Not { dest, operand } => {
|
||||
let operand_value = &self.registers[operand as usize];
|
||||
|
||||
if operand_value == &Value::Undefined {
|
||||
self.registers[dest as usize] = Value::Undefined;
|
||||
return Ok(InstructionOutcome::Continue);
|
||||
}
|
||||
|
||||
if let Some(value) = self.to_bool(operand_value) {
|
||||
self.registers[dest as usize] = Value::Bool(!value);
|
||||
Ok(InstructionOutcome::Continue)
|
||||
} else {
|
||||
Err(VmError::ArithmeticError(alloc::format!(
|
||||
"#undefined: logical NOT expects a boolean (operand={operand_value:?})"
|
||||
)))
|
||||
}
|
||||
}
|
||||
AssertCondition { condition } => {
|
||||
let value = &self.registers[condition as usize];
|
||||
|
||||
let condition_result = match value {
|
||||
Value::Bool(b) => *b,
|
||||
Value::Undefined => false,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
self.handle_condition(condition_result)?;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
AssertNotUndefined { register } => {
|
||||
let value = &self.registers[register as usize];
|
||||
|
||||
let is_undefined = matches!(value, Value::Undefined);
|
||||
self.handle_condition(!is_undefined)?;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
other => self.execute_call_instruction(_program, other),
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_call_instruction(
|
||||
&mut self,
|
||||
_program: &Program,
|
||||
instruction: Instruction,
|
||||
) -> Result<InstructionOutcome> {
|
||||
use Instruction::*;
|
||||
match instruction {
|
||||
BuiltinCall { params_index } => {
|
||||
self.execute_builtin_call(params_index)?;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
HostAwait { dest, arg, id } => {
|
||||
let argument = self.registers[arg as usize].clone();
|
||||
let identifier = self
|
||||
.registers
|
||||
.get(id as usize)
|
||||
.cloned()
|
||||
.unwrap_or(Value::Undefined);
|
||||
match self.execution_mode {
|
||||
ExecutionMode::RunToCompletion => {
|
||||
let response = self.next_host_await_response(&identifier, dest)?;
|
||||
if self.registers.len() <= dest as usize {
|
||||
self.registers.resize(dest as usize + 1, Value::Undefined);
|
||||
}
|
||||
self.registers[dest as usize] = response;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
ExecutionMode::Suspendable => Ok(InstructionOutcome::Suspend {
|
||||
reason: SuspendReason::HostAwait {
|
||||
dest,
|
||||
argument,
|
||||
identifier,
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
FunctionCall { params_index } => {
|
||||
self.execute_function_call(params_index)?;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
Return { value } => {
|
||||
let result = self.registers[value as usize].clone();
|
||||
Ok(InstructionOutcome::Return(result))
|
||||
}
|
||||
CallRule { dest, rule_index } => {
|
||||
self.execute_call_rule(dest, rule_index)?;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
RuleInit {
|
||||
result_reg,
|
||||
rule_index,
|
||||
} => {
|
||||
self.execute_rule_init(result_reg, rule_index)?;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
DestructuringSuccess {} => Ok(InstructionOutcome::Break),
|
||||
RuleReturn {} => {
|
||||
self.execute_rule_return()?;
|
||||
Ok(InstructionOutcome::Break)
|
||||
}
|
||||
other => self.execute_collection_instruction(_program, other),
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_collection_instruction(
|
||||
&mut self,
|
||||
program: &Program,
|
||||
instruction: Instruction,
|
||||
) -> Result<InstructionOutcome> {
|
||||
use Instruction::*;
|
||||
match instruction {
|
||||
ObjectSet { obj, key, value } => {
|
||||
let key_value = self.registers[key as usize].clone();
|
||||
let value_value = self.registers[value as usize].clone();
|
||||
|
||||
let mut obj_value = mem::replace(&mut self.registers[obj as usize], Value::Null);
|
||||
|
||||
if let Ok(obj_mut) = obj_value.as_object_mut() {
|
||||
obj_mut.insert(key_value, value_value);
|
||||
self.registers[obj as usize] = obj_value;
|
||||
} else {
|
||||
self.registers[obj as usize] = obj_value;
|
||||
return Err(VmError::RegisterNotObject { register: obj });
|
||||
}
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
ObjectCreate { params_index } => {
|
||||
let params = program
|
||||
.instruction_data
|
||||
.get_object_create_params(params_index)
|
||||
.ok_or(VmError::InvalidObjectCreateParams {
|
||||
index: params_index,
|
||||
})?;
|
||||
|
||||
let mut any_undefined = false;
|
||||
|
||||
for &(_, value_reg) in params.literal_key_field_pairs() {
|
||||
if matches!(self.registers[value_reg as usize], Value::Undefined) {
|
||||
any_undefined = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !any_undefined {
|
||||
for &(key_reg, value_reg) in params.field_pairs() {
|
||||
if matches!(self.registers[key_reg as usize], Value::Undefined)
|
||||
|| matches!(self.registers[value_reg as usize], Value::Undefined)
|
||||
{
|
||||
any_undefined = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if any_undefined {
|
||||
self.registers[params.dest as usize] = Value::Undefined;
|
||||
} else {
|
||||
let mut obj_value = program
|
||||
.literals
|
||||
.get(params.template_literal_idx as usize)
|
||||
.ok_or(VmError::InvalidTemplateLiteralIndex {
|
||||
index: params.template_literal_idx,
|
||||
})?
|
||||
.clone();
|
||||
|
||||
if let Ok(obj_mut) = obj_value.as_object_mut() {
|
||||
let mut literal_updates = params.literal_key_field_pairs().iter();
|
||||
let mut current_literal_update = literal_updates.next();
|
||||
|
||||
for (key, value) in obj_mut.iter_mut() {
|
||||
if let Some(&(literal_idx, value_reg)) = current_literal_update {
|
||||
if let Some(literal_key) =
|
||||
program.literals.get(literal_idx as usize)
|
||||
{
|
||||
if key == literal_key {
|
||||
*value = self.registers[value_reg as usize].clone();
|
||||
current_literal_update = literal_updates.next();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(&(literal_idx, value_reg)) = current_literal_update {
|
||||
if let Some(key_value) = program.literals.get(literal_idx as usize) {
|
||||
let value_value = self.registers[value_reg as usize].clone();
|
||||
obj_mut.insert(key_value.clone(), value_value);
|
||||
}
|
||||
current_literal_update = literal_updates.next();
|
||||
}
|
||||
|
||||
for &(key_reg, value_reg) in params.field_pairs() {
|
||||
let key_value = self.registers[key_reg as usize].clone();
|
||||
let value_value = self.registers[value_reg as usize].clone();
|
||||
obj_mut.insert(key_value, value_value);
|
||||
}
|
||||
} else {
|
||||
return Err(VmError::ObjectCreateInvalidTemplate);
|
||||
}
|
||||
|
||||
self.registers[params.dest as usize] = obj_value;
|
||||
}
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
Index {
|
||||
dest,
|
||||
container,
|
||||
key,
|
||||
} => {
|
||||
let key_value = &self.registers[key as usize];
|
||||
let container_value = &self.registers[container as usize];
|
||||
let result = container_value[key_value].clone();
|
||||
self.registers[dest as usize] = result;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
IndexLiteral {
|
||||
dest,
|
||||
container,
|
||||
literal_idx,
|
||||
} => {
|
||||
let container_value = &self.registers[container as usize];
|
||||
|
||||
if let Some(key_value) = program.literals.get(literal_idx as usize) {
|
||||
let result = container_value[key_value].clone();
|
||||
self.registers[dest as usize] = result;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
} else {
|
||||
Err(VmError::LiteralIndexOutOfBounds {
|
||||
index: literal_idx as usize,
|
||||
})
|
||||
}
|
||||
}
|
||||
ArrayNew { dest } => {
|
||||
let empty_array = Value::Array(crate::Rc::new(Vec::new()));
|
||||
self.registers[dest as usize] = empty_array;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
ArrayPush { arr, value } => {
|
||||
let value_to_push = self.registers[value as usize].clone();
|
||||
|
||||
let mut arr_value = mem::replace(&mut self.registers[arr as usize], Value::Null);
|
||||
|
||||
if let Ok(arr_mut) = arr_value.as_array_mut() {
|
||||
arr_mut.push(value_to_push);
|
||||
self.registers[arr as usize] = arr_value;
|
||||
} else {
|
||||
self.registers[arr as usize] = arr_value;
|
||||
return Err(VmError::RegisterNotArray { register: arr });
|
||||
}
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
ArrayCreate { params_index } => {
|
||||
if let Some(params) = program
|
||||
.instruction_data
|
||||
.get_array_create_params(params_index)
|
||||
{
|
||||
let mut any_undefined = false;
|
||||
for ® in params.element_registers() {
|
||||
if matches!(self.registers[reg as usize], Value::Undefined) {
|
||||
any_undefined = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if any_undefined {
|
||||
self.registers[params.dest as usize] = Value::Undefined;
|
||||
} else {
|
||||
let elements: Vec<Value> = params
|
||||
.element_registers()
|
||||
.iter()
|
||||
.map(|®| self.registers[reg as usize].clone())
|
||||
.collect();
|
||||
|
||||
let array_value = Value::Array(crate::Rc::new(elements));
|
||||
self.registers[params.dest as usize] = array_value;
|
||||
}
|
||||
Ok(InstructionOutcome::Continue)
|
||||
} else {
|
||||
Err(VmError::InvalidArrayCreateParams {
|
||||
index: params_index,
|
||||
})
|
||||
}
|
||||
}
|
||||
SetNew { dest } => {
|
||||
let empty_set = Value::Set(crate::Rc::new(BTreeSet::new()));
|
||||
self.registers[dest as usize] = empty_set;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
SetAdd { set, value } => {
|
||||
let value_to_add = self.registers[value as usize].clone();
|
||||
|
||||
let mut set_value = mem::replace(&mut self.registers[set as usize], Value::Null);
|
||||
|
||||
if let Ok(set_mut) = set_value.as_set_mut() {
|
||||
set_mut.insert(value_to_add);
|
||||
self.registers[set as usize] = set_value;
|
||||
} else {
|
||||
self.registers[set as usize] = set_value;
|
||||
return Err(VmError::RegisterNotSet { register: set });
|
||||
}
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
SetCreate { params_index } => {
|
||||
if let Some(params) = program.instruction_data.get_set_create_params(params_index) {
|
||||
let mut any_undefined = false;
|
||||
for ® in params.element_registers() {
|
||||
if matches!(self.registers[reg as usize], Value::Undefined) {
|
||||
any_undefined = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if any_undefined {
|
||||
self.registers[params.dest as usize] = Value::Undefined;
|
||||
} else {
|
||||
let mut set = BTreeSet::new();
|
||||
for ® in params.element_registers() {
|
||||
set.insert(self.registers[reg as usize].clone());
|
||||
}
|
||||
|
||||
let set_value = Value::Set(crate::Rc::new(set));
|
||||
self.registers[params.dest as usize] = set_value;
|
||||
}
|
||||
Ok(InstructionOutcome::Continue)
|
||||
} else {
|
||||
Err(VmError::InvalidSetCreateParams {
|
||||
index: params_index,
|
||||
})
|
||||
}
|
||||
}
|
||||
Contains {
|
||||
dest,
|
||||
collection,
|
||||
value,
|
||||
} => {
|
||||
let value_to_check = &self.registers[value as usize];
|
||||
let collection_value = &self.registers[collection as usize];
|
||||
|
||||
let result = match collection_value {
|
||||
Value::Set(set_elements) => Value::Bool(set_elements.contains(value_to_check)),
|
||||
Value::Array(array_items) => Value::Bool(array_items.contains(value_to_check)),
|
||||
Value::Object(object_fields) => Value::Bool(
|
||||
object_fields.contains_key(value_to_check)
|
||||
|| object_fields.values().any(|v| v == value_to_check),
|
||||
),
|
||||
_ => Value::Bool(false),
|
||||
};
|
||||
|
||||
self.registers[dest as usize] = result;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
Count { dest, collection } => {
|
||||
let collection_value = &self.registers[collection as usize];
|
||||
|
||||
let result = match collection_value {
|
||||
Value::Array(array_items) => Value::from(array_items.len()),
|
||||
Value::Object(object_fields) => Value::from(object_fields.len()),
|
||||
Value::Set(set_elements) => Value::from(set_elements.len()),
|
||||
_ => Value::Undefined,
|
||||
};
|
||||
|
||||
self.registers[dest as usize] = result;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
other => self.execute_loop_instruction(program, other),
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_loop_instruction(
|
||||
&mut self,
|
||||
program: &Program,
|
||||
instruction: Instruction,
|
||||
) -> Result<InstructionOutcome> {
|
||||
use Instruction::*;
|
||||
match instruction {
|
||||
LoopStart { params_index } => {
|
||||
let loop_params = &self.program.instruction_data.loop_params[params_index as usize];
|
||||
let mode = loop_params.mode.clone();
|
||||
let params = LoopParams {
|
||||
collection: loop_params.collection,
|
||||
key_reg: loop_params.key_reg,
|
||||
value_reg: loop_params.value_reg,
|
||||
result_reg: loop_params.result_reg,
|
||||
body_start: loop_params.body_start,
|
||||
loop_end: loop_params.loop_end,
|
||||
};
|
||||
self.execute_loop_start(&mode, params)?;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
LoopNext {
|
||||
body_start,
|
||||
loop_end,
|
||||
} => {
|
||||
self.execute_loop_next(body_start, loop_end)?;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
Halt {} => {
|
||||
let result = self.registers[0].clone();
|
||||
Ok(InstructionOutcome::Return(result))
|
||||
}
|
||||
other => self.execute_virtual_instruction(program, other),
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_virtual_instruction(
|
||||
&mut self,
|
||||
program: &Program,
|
||||
instruction: Instruction,
|
||||
) -> Result<InstructionOutcome> {
|
||||
use Instruction::*;
|
||||
match instruction {
|
||||
ChainedIndex { params_index } => {
|
||||
let params = program
|
||||
.instruction_data
|
||||
.get_chained_index_params(params_index)
|
||||
.ok_or(VmError::InvalidChainedIndexParams {
|
||||
index: params_index,
|
||||
})?;
|
||||
|
||||
let mut current_value = self.registers[params.root as usize].clone();
|
||||
|
||||
for component in ¶ms.path_components {
|
||||
let key_value = match component {
|
||||
LiteralOrRegister::Literal(idx) => program
|
||||
.literals
|
||||
.get(*idx as usize)
|
||||
.ok_or(VmError::LiteralIndexOutOfBounds {
|
||||
index: *idx as usize,
|
||||
})?
|
||||
.clone(),
|
||||
LiteralOrRegister::Register(reg) => self.registers[*reg as usize].clone(),
|
||||
};
|
||||
|
||||
current_value = current_value[&key_value].clone();
|
||||
|
||||
if current_value == Value::Undefined {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
self.registers[params.dest as usize] = current_value;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
VirtualDataDocumentLookup { params_index } => {
|
||||
self.execute_virtual_data_document_lookup(params_index)?;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
ComprehensionBegin { params_index } => {
|
||||
let params = program
|
||||
.instruction_data
|
||||
.get_comprehension_begin_params(params_index)
|
||||
.ok_or(VmError::InvalidComprehensionBeginParams {
|
||||
index: params_index,
|
||||
})?
|
||||
.clone();
|
||||
self.execute_comprehension_begin(¶ms)?;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
ComprehensionYield { value_reg, key_reg } => {
|
||||
self.execute_comprehension_yield(value_reg, key_reg)?;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
ComprehensionEnd {} => {
|
||||
self.execute_comprehension_end()?;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
unexpected => Err(VmError::Internal(alloc::format!(
|
||||
"Unhandled instruction variant: {:?}",
|
||||
unexpected
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
121
src/rvm/vm/errors.rs
Normal file
121
src/rvm/vm/errors.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::value::Value;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use thiserror::Error;
|
||||
|
||||
/// VM execution errors
|
||||
#[derive(Error, Debug, Clone, PartialEq)]
|
||||
pub enum VmError {
|
||||
#[error("Execution stopped: exceeded maximum instruction limit of {limit}")]
|
||||
InstructionLimitExceeded { limit: usize },
|
||||
|
||||
#[error("Literal index {index} out of bounds")]
|
||||
LiteralIndexOutOfBounds { index: usize },
|
||||
|
||||
#[error("Register {register} does not contain an object")]
|
||||
RegisterNotObject { register: u8 },
|
||||
|
||||
#[error("ObjectCreate: template is not an object")]
|
||||
ObjectCreateInvalidTemplate,
|
||||
|
||||
#[error("Register {register} does not contain an array")]
|
||||
RegisterNotArray { register: u8 },
|
||||
|
||||
#[error("Register {register} does not contain a set")]
|
||||
RegisterNotSet { register: u8 },
|
||||
|
||||
#[error("Rule index {index} out of bounds")]
|
||||
RuleIndexOutOfBounds { index: u16 },
|
||||
|
||||
#[error("Rule index {index} has no info")]
|
||||
RuleInfoMissing { index: u16 },
|
||||
|
||||
#[error("Invalid object create params index: {index}")]
|
||||
InvalidObjectCreateParams { index: u16 },
|
||||
|
||||
#[error("Invalid template literal index: {index}")]
|
||||
InvalidTemplateLiteralIndex { index: u16 },
|
||||
|
||||
#[error("Invalid chained index params index: {index}")]
|
||||
InvalidChainedIndexParams { index: u16 },
|
||||
|
||||
#[error("Invalid array create params index: {index}")]
|
||||
InvalidArrayCreateParams { index: u16 },
|
||||
|
||||
#[error("Invalid set create params index: {index}")]
|
||||
InvalidSetCreateParams { index: u16 },
|
||||
|
||||
#[error("Invalid virtual data document lookup params index: {index}")]
|
||||
InvalidVirtualDataDocumentLookupParams { index: u16 },
|
||||
|
||||
#[error("Invalid comprehension start params index: {index}")]
|
||||
InvalidComprehensionBeginParams { index: u16 },
|
||||
|
||||
#[error("Invalid rule index: {rule_index:?}")]
|
||||
InvalidRuleIndex { rule_index: Value },
|
||||
|
||||
#[error("Invalid rule tree entry: {value:?}")]
|
||||
InvalidRuleTreeEntry { value: Value },
|
||||
|
||||
#[error("Builtin function expects exactly {expected} arguments, got {actual}")]
|
||||
BuiltinArgumentMismatch { expected: u16, actual: usize },
|
||||
|
||||
#[error("Builtin function not resolved: {name}")]
|
||||
BuiltinNotResolved { name: String },
|
||||
|
||||
#[error("Cannot add {left:?} and {right:?}")]
|
||||
InvalidAddition { left: Value, right: Value },
|
||||
|
||||
#[error("Cannot subtract {left:?} and {right:?}")]
|
||||
InvalidSubtraction { left: Value, right: Value },
|
||||
|
||||
#[error("Cannot multiply {left:?} and {right:?}")]
|
||||
InvalidMultiplication { left: Value, right: Value },
|
||||
|
||||
#[error("Cannot divide {left:?} and {right:?}")]
|
||||
InvalidDivision { left: Value, right: Value },
|
||||
|
||||
#[error("modulo on floating-point number")]
|
||||
ModuloOnFloat,
|
||||
|
||||
#[error("Cannot modulo {left:?} and {right:?}")]
|
||||
InvalidModulo { left: Value, right: Value },
|
||||
|
||||
#[error("Cannot iterate over {value:?}")]
|
||||
InvalidIteration { value: Value },
|
||||
|
||||
#[error("HostAwait executed but no response provided for destination register {dest} (id: {identifier:?})")]
|
||||
HostAwaitResponseMissing { dest: u8, identifier: Value },
|
||||
|
||||
#[error("Assertion failed")]
|
||||
AssertionFailed,
|
||||
|
||||
#[error("Rule-data conflict: {0}")]
|
||||
RuleDataConflict(String),
|
||||
|
||||
#[error("Arithmetic error: {0}")]
|
||||
ArithmeticError(String),
|
||||
|
||||
#[error("Entry point index {index} out of bounds (max: {max_index})")]
|
||||
InvalidEntryPointIndex { index: usize, max_index: usize },
|
||||
|
||||
#[error("Entry point '{name}' not found. Available entry points: {available:?}")]
|
||||
EntryPointNotFound {
|
||||
name: String,
|
||||
available: Vec<String>,
|
||||
},
|
||||
|
||||
#[error("Internal VM error: {0}")]
|
||||
Internal(String),
|
||||
}
|
||||
|
||||
impl From<anyhow::Error> for VmError {
|
||||
fn from(err: anyhow::Error) -> Self {
|
||||
VmError::ArithmeticError(alloc::format!("{}", err))
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = core::result::Result<T, VmError>;
|
||||
591
src/rvm/vm/execution.rs
Normal file
591
src/rvm/vm/execution.rs
Normal file
@@ -0,0 +1,591 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
use crate::rvm::instructions::Instruction;
|
||||
use crate::rvm::program::Program;
|
||||
use crate::value::Value;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::dispatch::InstructionOutcome;
|
||||
use super::errors::{Result, VmError};
|
||||
use super::execution_model::{
|
||||
ExecutionFrame, ExecutionMode, ExecutionState, FrameKind, RuleFrameData, RuleFramePhase,
|
||||
SuspendReason,
|
||||
};
|
||||
use super::machine::RegoVM;
|
||||
|
||||
impl RegoVM {
|
||||
pub fn execute(&mut self) -> Result<Value> {
|
||||
match self.execution_mode {
|
||||
ExecutionMode::RunToCompletion => self.execute_run_to_completion(),
|
||||
ExecutionMode::Suspendable => self.execute_suspendable(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute_entry_point_by_index(&mut self, index: usize) -> Result<Value> {
|
||||
let entry_points: Vec<(String, usize)> = self
|
||||
.program
|
||||
.entry_points
|
||||
.iter()
|
||||
.map(|(name, pc)| (name.clone(), *pc))
|
||||
.collect();
|
||||
|
||||
if index >= entry_points.len() {
|
||||
return Err(VmError::InvalidEntryPointIndex {
|
||||
index,
|
||||
max_index: entry_points.len().saturating_sub(1),
|
||||
});
|
||||
}
|
||||
|
||||
let (_entry_point_name, entry_point_pc) = &entry_points[index];
|
||||
|
||||
if *entry_point_pc >= self.program.instructions.len() {
|
||||
return Err(VmError::Internal(alloc::format!(
|
||||
"Entry point PC {} >= instruction count {} for index {} | {}",
|
||||
entry_point_pc,
|
||||
self.program.instructions.len(),
|
||||
index,
|
||||
self.get_debug_state()
|
||||
)));
|
||||
}
|
||||
|
||||
match self.execution_mode {
|
||||
ExecutionMode::RunToCompletion => {
|
||||
self.reset_execution_state();
|
||||
|
||||
if let Err(e) = self.validate_vm_state() {
|
||||
return Err(VmError::Internal(alloc::format!(
|
||||
"VM state validation failed before entry point execution: {} | {}",
|
||||
e,
|
||||
self.get_debug_state()
|
||||
)));
|
||||
}
|
||||
|
||||
self.jump_to(*entry_point_pc)
|
||||
}
|
||||
ExecutionMode::Suspendable => {
|
||||
self.reset_execution_state();
|
||||
|
||||
if let Err(e) = self.validate_vm_state() {
|
||||
return Err(VmError::Internal(alloc::format!(
|
||||
"VM state validation failed before entry point execution: {} | {}",
|
||||
e,
|
||||
self.get_debug_state()
|
||||
)));
|
||||
}
|
||||
|
||||
self.execute_suspendable_entry(*entry_point_pc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute_entry_point_by_name(&mut self, name: &str) -> Result<Value> {
|
||||
let entry_point_pc =
|
||||
self.program
|
||||
.get_entry_point(name)
|
||||
.ok_or_else(|| VmError::EntryPointNotFound {
|
||||
name: String::from(name),
|
||||
available: self.program.entry_points.keys().cloned().collect(),
|
||||
})?;
|
||||
|
||||
if entry_point_pc >= self.program.instructions.len() {
|
||||
return Err(VmError::Internal(alloc::format!(
|
||||
"Entry point PC {} >= instruction count {} for '{}' | {}",
|
||||
entry_point_pc,
|
||||
self.program.instructions.len(),
|
||||
name,
|
||||
self.get_debug_state()
|
||||
)));
|
||||
}
|
||||
|
||||
match self.execution_mode {
|
||||
ExecutionMode::RunToCompletion => {
|
||||
self.reset_execution_state();
|
||||
|
||||
if let Err(e) = self.validate_vm_state() {
|
||||
return Err(VmError::Internal(alloc::format!(
|
||||
"VM state validation failed before entry point execution: {} | {}",
|
||||
e,
|
||||
self.get_debug_state()
|
||||
)));
|
||||
}
|
||||
|
||||
self.jump_to(entry_point_pc)
|
||||
}
|
||||
ExecutionMode::Suspendable => {
|
||||
self.reset_execution_state();
|
||||
|
||||
if let Err(e) = self.validate_vm_state() {
|
||||
return Err(VmError::Internal(alloc::format!(
|
||||
"VM state validation failed before entry point execution: {} | {}",
|
||||
e,
|
||||
self.get_debug_state()
|
||||
)));
|
||||
}
|
||||
|
||||
self.execute_suspendable_entry(entry_point_pc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn jump_to(&mut self, target: usize) -> Result<Value> {
|
||||
let program = self.program.clone();
|
||||
self.pc = target;
|
||||
while self.pc < program.instructions.len() {
|
||||
if self.executed_instructions >= self.max_instructions {
|
||||
return Err(VmError::InstructionLimitExceeded {
|
||||
limit: self.max_instructions,
|
||||
});
|
||||
}
|
||||
|
||||
self.executed_instructions += 1;
|
||||
let instruction = program.instructions[self.pc].clone();
|
||||
|
||||
match self.execute_instruction(&program, instruction)? {
|
||||
InstructionOutcome::Continue => {
|
||||
self.pc += 1;
|
||||
}
|
||||
InstructionOutcome::Return(value) => {
|
||||
return Ok(value);
|
||||
}
|
||||
InstructionOutcome::Break => {
|
||||
return Ok(self.registers[0].clone());
|
||||
}
|
||||
InstructionOutcome::Suspend { reason } => {
|
||||
return Err(VmError::Internal(alloc::format!(
|
||||
"Suspend instruction {:?} is not supported in run-to-completion execution",
|
||||
reason
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self.registers[0].clone())
|
||||
}
|
||||
|
||||
fn execute_run_to_completion(&mut self) -> Result<Value> {
|
||||
self.reset_execution_state();
|
||||
self.execution_state = ExecutionState::Running;
|
||||
match self.jump_to(0) {
|
||||
Ok(value) => {
|
||||
self.execution_state = ExecutionState::Completed {
|
||||
result: value.clone(),
|
||||
};
|
||||
Ok(value)
|
||||
}
|
||||
Err(err) => {
|
||||
self.execution_state = ExecutionState::Error { error: err.clone() };
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_suspendable(&mut self) -> Result<Value> {
|
||||
self.reset_execution_state();
|
||||
self.execution_state = ExecutionState::Running;
|
||||
match self.run_stackless_from(0) {
|
||||
Ok(result) => Ok(result),
|
||||
Err(err) => {
|
||||
self.execution_state = ExecutionState::Error { error: err.clone() };
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_suspendable_entry(&mut self, entry_point_pc: usize) -> Result<Value> {
|
||||
self.execution_state = ExecutionState::Running;
|
||||
match self.run_stackless_from(entry_point_pc) {
|
||||
Ok(result) => Ok(result),
|
||||
Err(err) => {
|
||||
self.execution_state = ExecutionState::Error { error: err.clone() };
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resume(&mut self, resume_value: Option<Value>) -> Result<Value> {
|
||||
let (reason, mut last_result) = match self.execution_state.clone() {
|
||||
ExecutionState::Suspended {
|
||||
reason,
|
||||
last_result,
|
||||
..
|
||||
} => (reason, last_result),
|
||||
current_state => {
|
||||
return Err(VmError::Internal(alloc::format!(
|
||||
"Cannot resume VM when execution state is {:?}",
|
||||
current_state
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
match reason.clone() {
|
||||
SuspendReason::HostAwait { dest, .. } => {
|
||||
let value = resume_value.ok_or_else(|| {
|
||||
VmError::Internal("HostAwait suspension requires a resume value".into())
|
||||
})?;
|
||||
|
||||
if self.registers.len() <= dest as usize {
|
||||
self.registers.resize(dest as usize + 1, Value::Undefined);
|
||||
}
|
||||
self.registers[dest as usize] = value;
|
||||
}
|
||||
other_reason => {
|
||||
if resume_value.is_some() {
|
||||
return Err(VmError::Internal(alloc::format!(
|
||||
"Unexpected resume value supplied for {:?}",
|
||||
other_reason
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.execution_state = ExecutionState::Running;
|
||||
|
||||
let program = self.program.clone();
|
||||
self.run_stackless_loop(&program, &mut last_result)?;
|
||||
|
||||
if matches!(self.execution_state, ExecutionState::Suspended { .. }) {
|
||||
Ok(last_result)
|
||||
} else {
|
||||
self.execution_stack.clear();
|
||||
self.execution_state = ExecutionState::Completed {
|
||||
result: last_result.clone(),
|
||||
};
|
||||
Ok(last_result)
|
||||
}
|
||||
}
|
||||
|
||||
fn run_stackless_from(&mut self, start_pc: usize) -> Result<Value> {
|
||||
let program = self.program.clone();
|
||||
|
||||
self.execution_stack.clear();
|
||||
self.execution_stack.push(ExecutionFrame::main(start_pc, 0));
|
||||
|
||||
let mut last_result = self.registers.first().cloned().unwrap_or(Value::Undefined);
|
||||
|
||||
self.run_stackless_loop(&program, &mut last_result)?;
|
||||
|
||||
if matches!(self.execution_state, ExecutionState::Suspended { .. }) {
|
||||
Ok(last_result)
|
||||
} else {
|
||||
self.execution_stack.clear();
|
||||
self.execution_state = ExecutionState::Completed {
|
||||
result: last_result.clone(),
|
||||
};
|
||||
Ok(last_result)
|
||||
}
|
||||
}
|
||||
|
||||
fn run_stackless_loop(&mut self, program: &Program, last_result: &mut Value) -> Result<()> {
|
||||
while !self.execution_stack.is_empty() {
|
||||
self.frame_pc_overridden = false;
|
||||
let should_finalize_rule = if let Some(frame) = self.execution_stack.last() {
|
||||
matches!(
|
||||
frame.kind,
|
||||
FrameKind::Rule(RuleFrameData {
|
||||
phase: RuleFramePhase::Finalizing,
|
||||
..
|
||||
})
|
||||
)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if should_finalize_rule {
|
||||
let frame = self.execution_stack.pop().expect("frame available");
|
||||
self.finalize_rule_execution_frame(frame, last_result)?;
|
||||
if self.execution_stack.is_empty() {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
let frame_pc = {
|
||||
let frame = self
|
||||
.execution_stack
|
||||
.last()
|
||||
.expect("stack checked to be non-empty");
|
||||
frame.pc
|
||||
};
|
||||
|
||||
if self.execution_mode == ExecutionMode::Suspendable
|
||||
&& self.breakpoints.contains(&frame_pc)
|
||||
{
|
||||
self.pc = frame_pc;
|
||||
let snapshot = (*last_result).clone();
|
||||
self.execution_state = ExecutionState::Suspended {
|
||||
reason: SuspendReason::Breakpoint { pc: frame_pc },
|
||||
pc: frame_pc,
|
||||
last_result: snapshot,
|
||||
};
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if frame_pc >= program.instructions.len() {
|
||||
let frame = self.execution_stack.pop().expect("frame exists");
|
||||
self.finalize_rule_execution_frame(frame, last_result)?;
|
||||
if self.execution_stack.is_empty() {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.executed_instructions >= self.max_instructions {
|
||||
self.execution_state = ExecutionState::Error {
|
||||
error: VmError::InstructionLimitExceeded {
|
||||
limit: self.max_instructions,
|
||||
},
|
||||
};
|
||||
return Err(VmError::InstructionLimitExceeded {
|
||||
limit: self.max_instructions,
|
||||
});
|
||||
}
|
||||
|
||||
self.pc = frame_pc;
|
||||
let instruction = program.instructions[self.pc].clone();
|
||||
if let Some(frame_info) = self.execution_stack.last() {
|
||||
if let FrameKind::Comprehension { context, .. } = &frame_info.kind {
|
||||
if context.iteration_state.is_none()
|
||||
&& frame_pc == context.comprehension_end as usize
|
||||
&& !matches!(instruction, Instruction::ComprehensionEnd { .. })
|
||||
{
|
||||
let resume_pc = frame_pc;
|
||||
let _completed = self.execution_stack.pop().expect("frame exists");
|
||||
if let Some(parent) = self.execution_stack.last_mut() {
|
||||
parent.pc = resume_pc;
|
||||
self.frame_pc_overridden = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.executed_instructions += 1;
|
||||
|
||||
let stack_depth_before = self.execution_stack.len();
|
||||
|
||||
match self.execute_instruction(program, instruction) {
|
||||
Ok(InstructionOutcome::Continue) => {
|
||||
let stack_depth_after = self.execution_stack.len();
|
||||
if stack_depth_after == stack_depth_before && !self.frame_pc_overridden {
|
||||
if let Some(frame) = self.execution_stack.last_mut() {
|
||||
frame.pc = self.pc + 1;
|
||||
}
|
||||
}
|
||||
if self.step_mode {
|
||||
self.handle_instruction_suspend(SuspendReason::Step, &*last_result);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Ok(InstructionOutcome::Return(value)) => {
|
||||
self.handle_instruction_return(value, last_result)?;
|
||||
if self.execution_stack.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(InstructionOutcome::Break) => {
|
||||
self.handle_instruction_break(last_result)?;
|
||||
if self.execution_stack.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(InstructionOutcome::Suspend { reason }) => {
|
||||
self.handle_instruction_suspend(reason, last_result);
|
||||
return Ok(());
|
||||
}
|
||||
Err(err) => {
|
||||
if self.handle_instruction_error(err.clone(), last_result)? {
|
||||
if self.execution_stack.is_empty() {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
self.execution_stack.clear();
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_instruction_suspend(&mut self, reason: SuspendReason, last_result: &Value) {
|
||||
if let Some(frame) = self.execution_stack.last_mut() {
|
||||
if !self.frame_pc_overridden && frame.pc <= self.pc {
|
||||
frame.pc = self.pc + 1;
|
||||
}
|
||||
}
|
||||
|
||||
self.execution_state = ExecutionState::Suspended {
|
||||
reason,
|
||||
pc: self.pc,
|
||||
last_result: last_result.clone(),
|
||||
};
|
||||
}
|
||||
|
||||
fn handle_completed_frame_kind(&mut self, kind: FrameKind, last_result: &mut Value) {
|
||||
match kind {
|
||||
FrameKind::Main {
|
||||
return_value_register,
|
||||
} => {
|
||||
*last_result = self
|
||||
.registers
|
||||
.get(return_value_register as usize)
|
||||
.cloned()
|
||||
.unwrap_or(Value::Undefined);
|
||||
}
|
||||
FrameKind::Loop { .. } | FrameKind::Comprehension { .. } => {
|
||||
*last_result = self.registers.first().cloned().unwrap_or(Value::Undefined);
|
||||
}
|
||||
FrameKind::Rule(_) => {
|
||||
*last_result = Value::Undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_instruction_return(&mut self, value: Value, last_result: &mut Value) -> Result<()> {
|
||||
loop {
|
||||
let frame = match self.execution_stack.pop() {
|
||||
Some(frame) => frame,
|
||||
None => {
|
||||
*last_result = value;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
match frame.kind {
|
||||
FrameKind::Rule(mut data) => {
|
||||
if self.registers.len() <= data.result_reg as usize {
|
||||
self.registers
|
||||
.resize(data.result_reg as usize + 1, Value::Undefined);
|
||||
}
|
||||
self.registers[data.result_reg as usize] = value.clone();
|
||||
data.accumulated_result = Some(value.clone());
|
||||
data.any_body_succeeded = true;
|
||||
|
||||
let result = self.finalize_rule_frame_data(data)?;
|
||||
*last_result = result.clone();
|
||||
|
||||
if let Some(parent_frame) = self.execution_stack.last_mut() {
|
||||
parent_frame.pc = self.pc + 1;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
FrameKind::Main {
|
||||
return_value_register,
|
||||
} => {
|
||||
if self.registers.len() <= return_value_register as usize {
|
||||
self.registers
|
||||
.resize(return_value_register as usize + 1, Value::Undefined);
|
||||
}
|
||||
self.registers[return_value_register as usize] = value.clone();
|
||||
*last_result = value;
|
||||
return Ok(());
|
||||
}
|
||||
FrameKind::Loop { return_pc, .. } | FrameKind::Comprehension { return_pc, .. } => {
|
||||
if let Some(parent_frame) = self.execution_stack.last_mut() {
|
||||
parent_frame.pc = return_pc;
|
||||
}
|
||||
// Propagate the return value outward until we reach the owning frame
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_instruction_break(&mut self, last_result: &mut Value) -> Result<()> {
|
||||
if let Some(frame) = self.execution_stack.pop() {
|
||||
match frame.kind {
|
||||
FrameKind::Rule(mut data) => {
|
||||
let current_pc = frame.pc;
|
||||
let next_pc = self.handle_rule_break_event(&mut data)?;
|
||||
if let Some(pc) = next_pc {
|
||||
self.execution_stack
|
||||
.push(ExecutionFrame::new(pc, FrameKind::Rule(data)));
|
||||
} else {
|
||||
self.finalize_rule_execution_frame(
|
||||
ExecutionFrame::new(current_pc, FrameKind::Rule(data)),
|
||||
last_result,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
other_kind => {
|
||||
self.handle_break_non_rule(
|
||||
ExecutionFrame::new(frame.pc, other_kind),
|
||||
last_result,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_instruction_error(&mut self, _err: VmError, last_result: &mut Value) -> Result<bool> {
|
||||
if let Some(frame) = self.execution_stack.pop() {
|
||||
match frame.kind {
|
||||
FrameKind::Rule(mut data) => {
|
||||
let current_pc = frame.pc;
|
||||
let next_pc = self.handle_rule_error_event(&mut data)?;
|
||||
if let Some(pc) = next_pc {
|
||||
self.execution_stack
|
||||
.push(ExecutionFrame::new(pc, FrameKind::Rule(data)));
|
||||
} else {
|
||||
self.finalize_rule_execution_frame(
|
||||
ExecutionFrame::new(current_pc, FrameKind::Rule(data)),
|
||||
last_result,
|
||||
)?;
|
||||
}
|
||||
return Ok(true);
|
||||
}
|
||||
other_kind => {
|
||||
self.execution_stack
|
||||
.push(ExecutionFrame::new(frame.pc, other_kind));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn handle_break_non_rule(&mut self, frame: ExecutionFrame, last_result: &mut Value) {
|
||||
match frame.kind {
|
||||
FrameKind::Main {
|
||||
return_value_register,
|
||||
} => {
|
||||
*last_result = self
|
||||
.registers
|
||||
.get(return_value_register as usize)
|
||||
.cloned()
|
||||
.unwrap_or(Value::Undefined);
|
||||
}
|
||||
FrameKind::Loop { return_pc, .. } | FrameKind::Comprehension { return_pc, .. } => {
|
||||
if let Some(parent_frame) = self.execution_stack.last_mut() {
|
||||
parent_frame.pc = return_pc;
|
||||
}
|
||||
*last_result = self.registers.first().cloned().unwrap_or(Value::Undefined);
|
||||
}
|
||||
FrameKind::Rule(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn finalize_rule_execution_frame(
|
||||
&mut self,
|
||||
frame: ExecutionFrame,
|
||||
last_result: &mut Value,
|
||||
) -> Result<()> {
|
||||
match frame.kind {
|
||||
FrameKind::Rule(data) => {
|
||||
let result = self.finalize_rule_frame_data(data)?;
|
||||
*last_result = result.clone();
|
||||
if let Some(parent_frame) = self.execution_stack.last_mut() {
|
||||
parent_frame.pc = self.pc + 1;
|
||||
}
|
||||
}
|
||||
other_kind => {
|
||||
self.handle_completed_frame_kind(other_kind, last_result);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
179
src/rvm/vm/execution_model.rs
Normal file
179
src/rvm/vm/execution_model.rs
Normal file
@@ -0,0 +1,179 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::rvm::program::RuleType;
|
||||
use crate::value::Value;
|
||||
use alloc::collections::BTreeSet;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::context::{ComprehensionContext, LoopContext};
|
||||
use super::errors::VmError;
|
||||
|
||||
/// Represents a single execution context (frame) in the VM
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct ExecutionFrame {
|
||||
/// Program counter for this frame
|
||||
pub(super) pc: usize,
|
||||
/// Frame-specific payload
|
||||
pub(super) kind: FrameKind,
|
||||
}
|
||||
|
||||
impl ExecutionFrame {
|
||||
pub(super) fn new(pc: usize, kind: FrameKind) -> Self {
|
||||
Self { pc, kind }
|
||||
}
|
||||
|
||||
pub(super) fn main(pc: usize, return_register: u8) -> Self {
|
||||
Self {
|
||||
pc,
|
||||
kind: FrameKind::Main {
|
||||
return_value_register: return_register,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Different categories of execution frames managed by the stackless engine
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) enum FrameKind {
|
||||
/// Main entry frame used when executing the program start point
|
||||
Main { return_value_register: u8 },
|
||||
/// Rule execution frame (replaces recursive jump_to calls)
|
||||
Rule(RuleFrameData),
|
||||
/// Loop iteration frame
|
||||
Loop {
|
||||
return_pc: usize,
|
||||
context: LoopContext,
|
||||
},
|
||||
/// Comprehension frame
|
||||
Comprehension {
|
||||
return_pc: usize,
|
||||
context: ComprehensionContext,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) enum RuleFramePhase {
|
||||
Initializing,
|
||||
ExecutingDestructuring,
|
||||
ExecutingBody,
|
||||
Finalizing,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct RuleFrameData {
|
||||
pub(super) return_pc: usize,
|
||||
pub(super) dest_reg: u8,
|
||||
pub(super) rule_index: u16,
|
||||
pub(super) current_definition_index: usize,
|
||||
pub(super) current_body_index: usize,
|
||||
pub(super) total_definitions: usize,
|
||||
pub(super) phase: RuleFramePhase,
|
||||
pub(super) accumulated_result: Option<Value>,
|
||||
pub(super) any_body_succeeded: bool,
|
||||
pub(super) rule_failed_due_to_inconsistency: bool,
|
||||
pub(super) rule_type: RuleType,
|
||||
pub(super) result_reg: u8,
|
||||
pub(super) is_function_rule: bool,
|
||||
pub(super) num_registers: usize,
|
||||
pub(super) num_retained_registers: usize,
|
||||
pub(super) saved_registers: Vec<Value>,
|
||||
pub(super) saved_loop_stack: Vec<LoopContext>,
|
||||
pub(super) saved_comprehension_stack: Vec<ComprehensionContext>,
|
||||
}
|
||||
|
||||
/// Explicit execution stack replacing the Rust call stack
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(super) struct ExecutionStack {
|
||||
frames: Vec<ExecutionFrame>,
|
||||
}
|
||||
|
||||
impl ExecutionStack {
|
||||
pub fn new() -> Self {
|
||||
Self { frames: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn push(&mut self, frame: ExecutionFrame) {
|
||||
self.frames.push(frame);
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> Option<ExecutionFrame> {
|
||||
self.frames.pop()
|
||||
}
|
||||
|
||||
pub fn last(&self) -> Option<&ExecutionFrame> {
|
||||
self.frames.last()
|
||||
}
|
||||
|
||||
pub fn last_mut(&mut self) -> Option<&mut ExecutionFrame> {
|
||||
self.frames.last_mut()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.frames.is_empty()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.frames.len()
|
||||
}
|
||||
|
||||
pub fn get(&self, index: usize) -> Option<&ExecutionFrame> {
|
||||
self.frames.get(index)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, index: usize) -> Option<&mut ExecutionFrame> {
|
||||
self.frames.get_mut(index)
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.frames.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the current execution state of the VM
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub enum ExecutionState {
|
||||
#[default]
|
||||
Ready,
|
||||
Running,
|
||||
Suspended {
|
||||
reason: SuspendReason,
|
||||
pc: usize,
|
||||
last_result: Value,
|
||||
},
|
||||
Completed {
|
||||
result: Value,
|
||||
},
|
||||
Error {
|
||||
error: VmError,
|
||||
},
|
||||
}
|
||||
|
||||
/// Reasons why execution may suspend
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum SuspendReason {
|
||||
SuspendInstruction,
|
||||
Breakpoint {
|
||||
pc: usize,
|
||||
},
|
||||
Step,
|
||||
InstructionLimit,
|
||||
External,
|
||||
HostAwait {
|
||||
dest: u8,
|
||||
argument: Value,
|
||||
identifier: Value,
|
||||
},
|
||||
}
|
||||
|
||||
/// Execution mode controls whether the VM runs straight through or supports suspension
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ExecutionMode {
|
||||
/// Execute in a single pass without exposing suspension points
|
||||
RunToCompletion,
|
||||
/// Execute cooperatively, allowing host-visible suspension and resume
|
||||
Suspendable,
|
||||
}
|
||||
|
||||
/// Set of breakpoints used by the suspendable engine
|
||||
pub(super) type BreakpointSet = BTreeSet<usize>;
|
||||
74
src/rvm/vm/functions.rs
Normal file
74
src/rvm/vm/functions.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
use crate::value::Value;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::errors::{Result, VmError};
|
||||
use super::execution_model::ExecutionMode;
|
||||
use super::machine::RegoVM;
|
||||
|
||||
impl RegoVM {
|
||||
pub(super) fn execute_function_call(&mut self, params_index: u16) -> Result<()> {
|
||||
let params =
|
||||
self.program.instruction_data.function_call_params[params_index as usize].clone();
|
||||
match self.execution_mode {
|
||||
ExecutionMode::RunToCompletion => {
|
||||
self.execute_call_rule_common(params.dest, params.func_rule_index, Some(¶ms))
|
||||
}
|
||||
ExecutionMode::Suspendable => self.execute_call_rule_suspendable(
|
||||
params.dest,
|
||||
params.func_rule_index,
|
||||
Some(¶ms),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn execute_builtin_call(&mut self, params_index: u16) -> Result<()> {
|
||||
let params = &self.program.instruction_data.builtin_call_params[params_index as usize];
|
||||
let builtin_info = &self.program.builtin_info_table[params.builtin_index as usize];
|
||||
|
||||
let mut args = Vec::new();
|
||||
for &arg_reg in params.arg_registers().iter() {
|
||||
let arg_value = self.registers[arg_reg as usize].clone();
|
||||
args.push(arg_value);
|
||||
}
|
||||
|
||||
if (args.len() as u16) != builtin_info.num_args {
|
||||
return Err(VmError::BuiltinArgumentMismatch {
|
||||
expected: builtin_info.num_args,
|
||||
actual: args.len(),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(builtin_fcn) = self.program.get_resolved_builtin(params.builtin_index) {
|
||||
let dummy_source = crate::lexer::Source::from_contents("arg".into(), String::new())?;
|
||||
let dummy_span = crate::lexer::Span {
|
||||
source: dummy_source,
|
||||
line: 1,
|
||||
col: 1,
|
||||
start: 0,
|
||||
end: 3,
|
||||
};
|
||||
|
||||
let mut dummy_exprs: Vec<crate::ast::Ref<crate::ast::Expr>> = Vec::new();
|
||||
for _ in 0..args.len() {
|
||||
let dummy_expr = crate::ast::Expr::Null {
|
||||
span: dummy_span.clone(),
|
||||
value: Value::Null,
|
||||
eidx: 0,
|
||||
};
|
||||
dummy_exprs.push(crate::ast::Ref::new(dummy_expr));
|
||||
}
|
||||
|
||||
let result = (builtin_fcn.0)(&dummy_span, &dummy_exprs, &args, true)?;
|
||||
self.registers[params.dest as usize] = result.clone();
|
||||
} else {
|
||||
return Err(VmError::BuiltinNotResolved {
|
||||
name: builtin_info.name.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
661
src/rvm/vm/loops.rs
Normal file
661
src/rvm/vm/loops.rs
Normal file
@@ -0,0 +1,661 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::rvm::instructions::LoopMode;
|
||||
use crate::value::Value;
|
||||
|
||||
use super::context::{IterationState, LoopContext};
|
||||
use super::errors::{Result, VmError};
|
||||
use super::execution_model::{ExecutionFrame, ExecutionMode, FrameKind};
|
||||
use super::machine::RegoVM;
|
||||
|
||||
fn compute_body_resume_pc(loop_start_pc: usize, body_start: u16) -> usize {
|
||||
if body_start == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let candidate = body_start.saturating_sub(1) as usize;
|
||||
if candidate == loop_start_pc {
|
||||
body_start as usize
|
||||
} else {
|
||||
candidate
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) struct LoopParams {
|
||||
pub(super) collection: u8,
|
||||
pub(super) key_reg: u8,
|
||||
pub(super) value_reg: u8,
|
||||
pub(super) result_reg: u8,
|
||||
pub(super) body_start: u16,
|
||||
pub(super) loop_end: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum LoopAction {
|
||||
ExitWithSuccess,
|
||||
ExitWithFailure,
|
||||
Continue,
|
||||
}
|
||||
|
||||
impl RegoVM {
|
||||
pub(super) fn execute_loop_start(&mut self, mode: &LoopMode, params: LoopParams) -> Result<()> {
|
||||
match self.execution_mode {
|
||||
ExecutionMode::RunToCompletion => {
|
||||
self.execute_loop_start_run_to_completion(mode, params)
|
||||
}
|
||||
ExecutionMode::Suspendable => self.execute_loop_start_suspendable(mode, params),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn execute_loop_next(&mut self, body_start: u16, loop_end: u16) -> Result<()> {
|
||||
match self.execution_mode {
|
||||
ExecutionMode::RunToCompletion => {
|
||||
self.execute_loop_next_run_to_completion(body_start, loop_end)
|
||||
}
|
||||
ExecutionMode::Suspendable => self.execute_loop_next_suspendable(body_start, loop_end),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn handle_condition(&mut self, condition_passed: bool) -> Result<()> {
|
||||
match self.execution_mode {
|
||||
ExecutionMode::RunToCompletion => {
|
||||
self.handle_condition_run_to_completion(condition_passed)
|
||||
}
|
||||
ExecutionMode::Suspendable => self.handle_condition_suspendable(condition_passed),
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_loop_start_run_to_completion(
|
||||
&mut self,
|
||||
mode: &LoopMode,
|
||||
params: LoopParams,
|
||||
) -> Result<()> {
|
||||
let initial_result = match mode {
|
||||
LoopMode::Any | LoopMode::Every | LoopMode::ForEach => Value::Bool(false),
|
||||
};
|
||||
self.registers[params.result_reg as usize] = initial_result.clone();
|
||||
|
||||
let collection_value = self.registers[params.collection as usize].clone();
|
||||
|
||||
let iteration_state = match &collection_value {
|
||||
Value::Array(items) => {
|
||||
if items.is_empty() {
|
||||
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
|
||||
return Ok(());
|
||||
}
|
||||
IterationState::Array {
|
||||
items: items.clone(),
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
Value::Object(obj) => {
|
||||
if obj.is_empty() {
|
||||
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
|
||||
return Ok(());
|
||||
}
|
||||
IterationState::Object {
|
||||
obj: obj.clone(),
|
||||
current_key: None,
|
||||
first_iteration: true,
|
||||
}
|
||||
}
|
||||
Value::Set(set) => {
|
||||
if set.is_empty() {
|
||||
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
|
||||
return Ok(());
|
||||
}
|
||||
IterationState::Set {
|
||||
items: set.clone(),
|
||||
current_item: None,
|
||||
first_iteration: true,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let has_next =
|
||||
self.setup_next_iteration(&iteration_state, params.key_reg, params.value_reg)?;
|
||||
if !has_next {
|
||||
self.pc = params.loop_end as usize;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let loop_next_pc = params.loop_end - 1;
|
||||
let body_resume_pc = compute_body_resume_pc(self.pc, params.body_start);
|
||||
|
||||
let loop_context = LoopContext {
|
||||
mode: mode.clone(),
|
||||
iteration_state,
|
||||
key_reg: params.key_reg,
|
||||
value_reg: params.value_reg,
|
||||
result_reg: params.result_reg,
|
||||
body_start: params.body_start,
|
||||
loop_end: params.loop_end,
|
||||
loop_next_pc,
|
||||
body_resume_pc,
|
||||
success_count: 0,
|
||||
total_iterations: 0,
|
||||
current_iteration_failed: false,
|
||||
};
|
||||
|
||||
self.loop_stack.push(loop_context);
|
||||
|
||||
self.pc = params.body_start as usize - 1;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_loop_next_run_to_completion(
|
||||
&mut self,
|
||||
_body_start: u16,
|
||||
loop_end: u16,
|
||||
) -> Result<()> {
|
||||
if let Some(mut loop_ctx) = self.loop_stack.pop() {
|
||||
let body_start = loop_ctx.body_start;
|
||||
let loop_end = loop_ctx.loop_end;
|
||||
|
||||
loop_ctx.total_iterations += 1;
|
||||
|
||||
let iteration_succeeded = self.check_iteration_success(&loop_ctx)?;
|
||||
|
||||
if iteration_succeeded {
|
||||
loop_ctx.success_count += 1;
|
||||
}
|
||||
|
||||
let action = self.determine_loop_action(&loop_ctx.mode, iteration_succeeded);
|
||||
|
||||
match action {
|
||||
LoopAction::ExitWithSuccess => {
|
||||
self.registers[loop_ctx.result_reg as usize] = Value::Bool(true);
|
||||
self.pc = loop_end as usize - 1;
|
||||
return Ok(());
|
||||
}
|
||||
LoopAction::ExitWithFailure => {
|
||||
self.registers[loop_ctx.result_reg as usize] = Value::Bool(false);
|
||||
self.pc = loop_end as usize - 1;
|
||||
return Ok(());
|
||||
}
|
||||
LoopAction::Continue => {}
|
||||
}
|
||||
|
||||
if let IterationState::Object {
|
||||
ref mut current_key,
|
||||
..
|
||||
} = &mut loop_ctx.iteration_state
|
||||
{
|
||||
if loop_ctx.key_reg != loop_ctx.value_reg {
|
||||
*current_key = Some(self.registers[loop_ctx.key_reg as usize].clone());
|
||||
}
|
||||
} else if let IterationState::Set {
|
||||
ref mut current_item,
|
||||
..
|
||||
} = &mut loop_ctx.iteration_state
|
||||
{
|
||||
*current_item = Some(self.registers[loop_ctx.value_reg as usize].clone());
|
||||
}
|
||||
|
||||
loop_ctx.iteration_state.advance();
|
||||
let has_next = self.setup_next_iteration(
|
||||
&loop_ctx.iteration_state,
|
||||
loop_ctx.key_reg,
|
||||
loop_ctx.value_reg,
|
||||
)?;
|
||||
|
||||
if has_next {
|
||||
loop_ctx.current_iteration_failed = false;
|
||||
|
||||
self.loop_stack.push(loop_ctx);
|
||||
self.pc = body_start as usize - 1;
|
||||
} else {
|
||||
let final_result = match loop_ctx.mode {
|
||||
LoopMode::Any => Value::Bool(loop_ctx.success_count > 0),
|
||||
LoopMode::Every => {
|
||||
Value::Bool(loop_ctx.success_count == loop_ctx.total_iterations)
|
||||
}
|
||||
LoopMode::ForEach => Value::Bool(loop_ctx.success_count > 0),
|
||||
};
|
||||
|
||||
self.registers[loop_ctx.result_reg as usize] = final_result;
|
||||
|
||||
self.pc = loop_end as usize - 1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
self.pc = loop_end as usize;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_loop_start_suspendable(
|
||||
&mut self,
|
||||
mode: &LoopMode,
|
||||
params: LoopParams,
|
||||
) -> Result<()> {
|
||||
let initial_result = match mode {
|
||||
LoopMode::Any | LoopMode::Every | LoopMode::ForEach => Value::Bool(false),
|
||||
};
|
||||
self.registers[params.result_reg as usize] = initial_result.clone();
|
||||
|
||||
let collection_value = self.registers[params.collection as usize].clone();
|
||||
|
||||
let iteration_state = match &collection_value {
|
||||
Value::Array(items) => {
|
||||
if items.is_empty() {
|
||||
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
|
||||
return Ok(());
|
||||
}
|
||||
IterationState::Array {
|
||||
items: items.clone(),
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
Value::Object(obj) => {
|
||||
if obj.is_empty() {
|
||||
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
|
||||
return Ok(());
|
||||
}
|
||||
IterationState::Object {
|
||||
obj: obj.clone(),
|
||||
current_key: None,
|
||||
first_iteration: true,
|
||||
}
|
||||
}
|
||||
Value::Set(set) => {
|
||||
if set.is_empty() {
|
||||
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
|
||||
return Ok(());
|
||||
}
|
||||
IterationState::Set {
|
||||
items: set.clone(),
|
||||
current_item: None,
|
||||
first_iteration: true,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let has_next =
|
||||
self.setup_next_iteration(&iteration_state, params.key_reg, params.value_reg)?;
|
||||
if !has_next {
|
||||
self.pc = params.loop_end as usize;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let loop_next_pc = params.loop_end - 1;
|
||||
let body_resume_pc = compute_body_resume_pc(self.pc, params.body_start);
|
||||
|
||||
let loop_context = LoopContext {
|
||||
mode: mode.clone(),
|
||||
iteration_state,
|
||||
key_reg: params.key_reg,
|
||||
value_reg: params.value_reg,
|
||||
result_reg: params.result_reg,
|
||||
body_start: params.body_start,
|
||||
loop_end: params.loop_end,
|
||||
loop_next_pc,
|
||||
body_resume_pc,
|
||||
success_count: 0,
|
||||
total_iterations: 0,
|
||||
current_iteration_failed: false,
|
||||
};
|
||||
|
||||
let frame = ExecutionFrame::new(
|
||||
params.body_start as usize,
|
||||
FrameKind::Loop {
|
||||
return_pc: params.loop_end as usize,
|
||||
context: loop_context,
|
||||
},
|
||||
);
|
||||
self.execution_stack.push(frame);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_loop_next_suspendable(&mut self, body_start: u16, loop_end: u16) -> Result<()> {
|
||||
if !matches!(
|
||||
self.execution_stack.last(),
|
||||
Some(ExecutionFrame {
|
||||
kind: FrameKind::Loop { .. },
|
||||
..
|
||||
})
|
||||
) {
|
||||
if let Some(frame) = self.execution_stack.last_mut() {
|
||||
// Advance past the offending instruction so we do not repeatedly
|
||||
// resume at the same LoopNext when the owning loop frame has
|
||||
// already been popped (for example after a manual comprehension
|
||||
// finalizes in suspendable mode).
|
||||
let mut target_pc = loop_end as usize;
|
||||
if target_pc <= self.pc {
|
||||
target_pc = self.pc.saturating_add(1);
|
||||
}
|
||||
frame.pc = target_pc;
|
||||
self.frame_pc_overridden = true;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (resume_pc, result_reg, loop_mode, iteration_succeeded) = {
|
||||
let frame = self
|
||||
.execution_stack
|
||||
.last_mut()
|
||||
.ok_or(VmError::AssertionFailed)?;
|
||||
match &mut frame.kind {
|
||||
FrameKind::Loop { return_pc, context } => {
|
||||
context.total_iterations += 1;
|
||||
let succeeded = !context.current_iteration_failed;
|
||||
if succeeded {
|
||||
context.success_count += 1;
|
||||
}
|
||||
|
||||
(
|
||||
*return_pc,
|
||||
context.result_reg,
|
||||
context.mode.clone(),
|
||||
succeeded,
|
||||
)
|
||||
}
|
||||
_ => return Err(VmError::AssertionFailed),
|
||||
}
|
||||
};
|
||||
|
||||
let action = self.determine_loop_action(&loop_mode, iteration_succeeded);
|
||||
|
||||
match action {
|
||||
LoopAction::ExitWithSuccess => {
|
||||
self.registers[result_reg as usize] = Value::Bool(true);
|
||||
let completed_frame = self.execution_stack.pop().expect("loop frame exists");
|
||||
if let Some(parent) = self.execution_stack.last_mut() {
|
||||
parent.pc = resume_pc;
|
||||
self.frame_pc_overridden = true;
|
||||
}
|
||||
drop(completed_frame);
|
||||
Ok(())
|
||||
}
|
||||
LoopAction::ExitWithFailure => {
|
||||
self.registers[result_reg as usize] = Value::Bool(false);
|
||||
let completed_frame = self.execution_stack.pop().expect("loop frame exists");
|
||||
if let Some(parent) = self.execution_stack.last_mut() {
|
||||
parent.pc = resume_pc;
|
||||
self.frame_pc_overridden = true;
|
||||
}
|
||||
drop(completed_frame);
|
||||
Ok(())
|
||||
}
|
||||
LoopAction::Continue => {
|
||||
let (mode, success_count, total_iterations, key_reg, value_reg, iteration_state) = {
|
||||
let frame = self
|
||||
.execution_stack
|
||||
.last_mut()
|
||||
.ok_or(VmError::AssertionFailed)?;
|
||||
match &mut frame.kind {
|
||||
FrameKind::Loop { context, .. } => {
|
||||
if let IterationState::Object {
|
||||
ref mut current_key,
|
||||
..
|
||||
} = &mut context.iteration_state
|
||||
{
|
||||
if context.key_reg != context.value_reg {
|
||||
*current_key =
|
||||
Some(self.registers[context.key_reg as usize].clone());
|
||||
}
|
||||
} else if let IterationState::Set {
|
||||
ref mut current_item,
|
||||
..
|
||||
} = &mut context.iteration_state
|
||||
{
|
||||
*current_item =
|
||||
Some(self.registers[context.value_reg as usize].clone());
|
||||
}
|
||||
|
||||
context.iteration_state.advance();
|
||||
context.current_iteration_failed = false;
|
||||
|
||||
(
|
||||
context.mode.clone(),
|
||||
context.success_count,
|
||||
context.total_iterations,
|
||||
context.key_reg,
|
||||
context.value_reg,
|
||||
context.iteration_state.clone(),
|
||||
)
|
||||
}
|
||||
_ => return Err(VmError::AssertionFailed),
|
||||
}
|
||||
};
|
||||
|
||||
let has_next = self.setup_next_iteration(&iteration_state, key_reg, value_reg)?;
|
||||
|
||||
if has_next {
|
||||
if let Some(frame) = self.execution_stack.last_mut() {
|
||||
if let FrameKind::Loop { context, .. } = &frame.kind {
|
||||
frame.pc = context.body_resume_pc;
|
||||
} else {
|
||||
frame.pc = body_start as usize;
|
||||
}
|
||||
self.frame_pc_overridden = true;
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
let final_result = match mode {
|
||||
LoopMode::Any => Value::Bool(success_count > 0),
|
||||
LoopMode::Every => Value::Bool(success_count == total_iterations),
|
||||
LoopMode::ForEach => Value::Bool(success_count > 0),
|
||||
};
|
||||
|
||||
self.registers[result_reg as usize] = final_result;
|
||||
|
||||
let completed_frame = self.execution_stack.pop().expect("loop frame exists");
|
||||
if let Some(parent) = self.execution_stack.last_mut() {
|
||||
parent.pc = resume_pc;
|
||||
self.frame_pc_overridden = true;
|
||||
}
|
||||
drop(completed_frame);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_empty_collection(
|
||||
&mut self,
|
||||
mode: &LoopMode,
|
||||
result_reg: u8,
|
||||
loop_end: u16,
|
||||
) -> Result<()> {
|
||||
let result = match mode {
|
||||
LoopMode::Any => Value::Bool(false),
|
||||
LoopMode::Every => Value::Bool(true),
|
||||
LoopMode::ForEach => Value::Bool(false),
|
||||
};
|
||||
|
||||
self.registers[result_reg as usize] = result;
|
||||
self.pc = (loop_end as usize).saturating_sub(1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn setup_next_iteration(
|
||||
&mut self,
|
||||
state: &IterationState,
|
||||
key_reg: u8,
|
||||
value_reg: u8,
|
||||
) -> Result<bool> {
|
||||
match state {
|
||||
IterationState::Array { items, index } => {
|
||||
if *index < items.len() {
|
||||
if key_reg != value_reg {
|
||||
let key_value = Value::from(*index as f64);
|
||||
self.registers[key_reg as usize] = key_value;
|
||||
}
|
||||
let item_value = items[*index].clone();
|
||||
self.registers[value_reg as usize] = item_value;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
IterationState::Object {
|
||||
obj,
|
||||
current_key,
|
||||
first_iteration,
|
||||
} => {
|
||||
if *first_iteration {
|
||||
if let Some((key, value)) = obj.iter().next() {
|
||||
if key_reg != value_reg {
|
||||
self.registers[key_reg as usize] = key.clone();
|
||||
}
|
||||
self.registers[value_reg as usize] = value.clone();
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
} else if let Some(ref current) = current_key {
|
||||
let mut range_iter = obj.range((
|
||||
core::ops::Bound::Excluded(current),
|
||||
core::ops::Bound::Unbounded,
|
||||
));
|
||||
if let Some((key, value)) = range_iter.next() {
|
||||
if key_reg != value_reg {
|
||||
self.registers[key_reg as usize] = key.clone();
|
||||
}
|
||||
self.registers[value_reg as usize] = value.clone();
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
IterationState::Set {
|
||||
items,
|
||||
current_item,
|
||||
first_iteration,
|
||||
} => {
|
||||
if *first_iteration {
|
||||
if let Some(item) = items.iter().next() {
|
||||
if key_reg != value_reg {
|
||||
self.registers[key_reg as usize] = item.clone();
|
||||
}
|
||||
self.registers[value_reg as usize] = item.clone();
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
} else if let Some(ref current) = current_item {
|
||||
let mut range_iter = items.range((
|
||||
core::ops::Bound::Excluded(current),
|
||||
core::ops::Bound::Unbounded,
|
||||
));
|
||||
if let Some(item) = range_iter.next() {
|
||||
if key_reg != value_reg {
|
||||
self.registers[key_reg as usize] = item.clone();
|
||||
}
|
||||
self.registers[value_reg as usize] = item.clone();
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_iteration_success(&self, loop_ctx: &LoopContext) -> Result<bool> {
|
||||
Ok(!loop_ctx.current_iteration_failed)
|
||||
}
|
||||
|
||||
fn determine_loop_action(&self, mode: &LoopMode, success: bool) -> LoopAction {
|
||||
match (mode, success) {
|
||||
(LoopMode::Any, true) => LoopAction::ExitWithSuccess,
|
||||
(LoopMode::Every, false) => LoopAction::ExitWithFailure,
|
||||
(LoopMode::ForEach, _) => LoopAction::Continue,
|
||||
_ => LoopAction::Continue,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_condition_run_to_completion(&mut self, condition_passed: bool) -> Result<()> {
|
||||
if condition_passed {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !self.loop_stack.is_empty() {
|
||||
let (loop_mode, loop_next_pc, loop_end, result_reg) = {
|
||||
let loop_ctx = self.loop_stack.last().unwrap();
|
||||
(
|
||||
loop_ctx.mode.clone(),
|
||||
loop_ctx.loop_next_pc,
|
||||
loop_ctx.loop_end,
|
||||
loop_ctx.result_reg,
|
||||
)
|
||||
};
|
||||
|
||||
match loop_mode {
|
||||
LoopMode::Any => {
|
||||
if let Some(loop_ctx_mut) = self.loop_stack.last_mut() {
|
||||
loop_ctx_mut.current_iteration_failed = true;
|
||||
}
|
||||
|
||||
self.pc = loop_next_pc as usize - 1;
|
||||
}
|
||||
LoopMode::Every => {
|
||||
self.loop_stack.pop();
|
||||
self.pc = loop_end as usize - 1;
|
||||
self.registers[result_reg as usize] = Value::Bool(false);
|
||||
}
|
||||
_ => {
|
||||
if let Some(loop_ctx_mut) = self.loop_stack.last_mut() {
|
||||
loop_ctx_mut.current_iteration_failed = true;
|
||||
}
|
||||
self.pc = loop_next_pc as usize - 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(VmError::AssertionFailed);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_condition_suspendable(&mut self, condition_passed: bool) -> Result<()> {
|
||||
if condition_passed {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (resume_pc, loop_ctx) = match self.execution_stack.last_mut() {
|
||||
Some(ExecutionFrame {
|
||||
kind: FrameKind::Loop { return_pc, context },
|
||||
..
|
||||
}) => (*return_pc, context),
|
||||
_ => return Err(VmError::AssertionFailed),
|
||||
};
|
||||
|
||||
match loop_ctx.mode {
|
||||
LoopMode::Any | LoopMode::ForEach => {
|
||||
loop_ctx.current_iteration_failed = true;
|
||||
self.pc = loop_ctx.loop_next_pc as usize - 1;
|
||||
}
|
||||
LoopMode::Every => {
|
||||
self.registers[loop_ctx.result_reg as usize] = Value::Bool(false);
|
||||
let completed_frame = self.execution_stack.pop().expect("loop frame exists");
|
||||
if let Some(parent) = self.execution_stack.last_mut() {
|
||||
parent.pc = resume_pc;
|
||||
}
|
||||
drop(completed_frame);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
325
src/rvm/vm/machine.rs
Normal file
325
src/rvm/vm/machine.rs
Normal file
@@ -0,0 +1,325 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::rvm::program::Program;
|
||||
use crate::value::Value;
|
||||
use crate::CompiledPolicy;
|
||||
use alloc::collections::{btree_map::Entry, BTreeMap, VecDeque};
|
||||
use alloc::string::String;
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::context::{CallRuleContext, ComprehensionContext, LoopContext};
|
||||
use super::errors::{Result, VmError};
|
||||
use super::execution_model::{
|
||||
BreakpointSet, ExecutionMode, ExecutionStack, ExecutionState, SuspendReason,
|
||||
};
|
||||
|
||||
/// The Rego Virtual Machine
|
||||
pub struct RegoVM {
|
||||
/// Registers for storing values during execution
|
||||
pub(super) registers: Vec<Value>,
|
||||
|
||||
/// Program counter
|
||||
pub(super) pc: usize,
|
||||
|
||||
/// The compiled program containing instructions, literals, and metadata
|
||||
pub(super) program: Arc<Program>,
|
||||
|
||||
/// Reference to the compiled policy for default rule access
|
||||
pub(super) compiled_policy: Option<CompiledPolicy>,
|
||||
|
||||
/// Rule execution cache: rule_index -> (computed: bool, result: Value)
|
||||
pub(super) rule_cache: Vec<(bool, Value)>,
|
||||
|
||||
/// Global data object
|
||||
pub(super) data: Value,
|
||||
|
||||
/// Global input object
|
||||
pub(super) input: Value,
|
||||
|
||||
/// Loop execution stack
|
||||
/// Note: Loops are either at the outermost level (rule body) or within the topmost comprehension.
|
||||
/// Loops never contain comprehensions - it's always the other way around.
|
||||
pub(super) loop_stack: Vec<LoopContext>,
|
||||
|
||||
/// Call rule execution stack for managing nested rule calls
|
||||
pub(super) call_rule_stack: Vec<CallRuleContext>,
|
||||
|
||||
/// Register stack for isolated register spaces during rule calls
|
||||
pub(super) register_stack: Vec<Vec<Value>>,
|
||||
|
||||
/// Comprehension execution stack for tracking active comprehensions
|
||||
/// Note: Comprehensions can be nested within each other, forming a proper nesting hierarchy.
|
||||
/// Any loops within a comprehension belong to the topmost (current) comprehension context.
|
||||
pub(super) comprehension_stack: Vec<ComprehensionContext>,
|
||||
|
||||
/// Base register window size for the main execution context
|
||||
pub(super) base_register_count: usize,
|
||||
|
||||
/// Object pools for performance optimization
|
||||
/// Pool of register windows for reuse during rule calls
|
||||
pub(super) register_window_pool: Vec<Vec<Value>>,
|
||||
|
||||
/// Maximum number of instructions to execute (default: 25000)
|
||||
pub(super) max_instructions: usize,
|
||||
|
||||
/// Current count of executed instructions
|
||||
pub(super) executed_instructions: usize,
|
||||
|
||||
/// Cache for evaluated paths in virtual data document lookup
|
||||
/// Structure: evaluated[path_component1][path_component2]...[Undefined] = result_value
|
||||
pub(super) evaluated: Value,
|
||||
|
||||
/// Counter for cache hits during virtual data document lookup evaluation
|
||||
pub(super) cache_hits: usize,
|
||||
|
||||
/// Explicit execution stack used when running in suspendable mode
|
||||
pub(super) execution_stack: ExecutionStack,
|
||||
|
||||
/// Current execution state of the VM
|
||||
pub(super) execution_state: ExecutionState,
|
||||
|
||||
/// Active breakpoints for the suspendable engine
|
||||
pub(super) breakpoints: BreakpointSet,
|
||||
|
||||
/// Flag indicating whether single-step mode is active
|
||||
pub(super) step_mode: bool,
|
||||
|
||||
/// Preloaded responses for HostAwait in run-to-completion execution keyed by identifier
|
||||
pub(super) host_await_responses: BTreeMap<Value, VecDeque<Value>>,
|
||||
|
||||
/// Current execution mode (run-to-completion vs suspendable)
|
||||
pub(super) execution_mode: ExecutionMode,
|
||||
|
||||
/// Tracks whether the current top-of-stack frame PC was explicitly set by an instruction
|
||||
pub(super) frame_pc_overridden: bool,
|
||||
|
||||
/// Whether builtins should raise errors strictly or return undefined on failure
|
||||
pub(super) strict_builtin_errors: bool,
|
||||
}
|
||||
|
||||
impl Default for RegoVM {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl RegoVM {
|
||||
/// Create a new virtual machine
|
||||
pub fn new() -> Self {
|
||||
RegoVM {
|
||||
registers: Vec::new(), // Start with no registers - will be resized when program is loaded
|
||||
pc: 0,
|
||||
program: Arc::new(Program::default()),
|
||||
compiled_policy: None,
|
||||
rule_cache: Vec::new(),
|
||||
data: Value::Null,
|
||||
input: Value::Null,
|
||||
loop_stack: Vec::new(),
|
||||
call_rule_stack: Vec::new(),
|
||||
register_stack: Vec::new(),
|
||||
comprehension_stack: Vec::new(),
|
||||
base_register_count: 2, // Default to 2 registers for basic operations
|
||||
register_window_pool: Vec::new(), // Initialize register window pool
|
||||
max_instructions: 25000, // Default maximum instruction limit
|
||||
executed_instructions: 0,
|
||||
evaluated: Value::new_object(), // Initialize evaluation cache
|
||||
cache_hits: 0, // Initialize cache hit counter
|
||||
execution_stack: ExecutionStack::new(),
|
||||
execution_state: ExecutionState::Ready,
|
||||
breakpoints: BreakpointSet::new(),
|
||||
step_mode: false,
|
||||
host_await_responses: BTreeMap::new(),
|
||||
execution_mode: ExecutionMode::RunToCompletion,
|
||||
frame_pc_overridden: false,
|
||||
strict_builtin_errors: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new virtual machine with compiled policy for default rule support
|
||||
pub fn new_with_policy(compiled_policy: CompiledPolicy) -> Self {
|
||||
let mut vm = Self::new();
|
||||
vm.compiled_policy = Some(compiled_policy);
|
||||
vm
|
||||
}
|
||||
|
||||
/// Load a complete program for execution
|
||||
pub fn load_program(&mut self, program: Arc<Program>) {
|
||||
self.program = program.clone();
|
||||
|
||||
// Use the dispatch window size from the program for initial register allocation
|
||||
let dispatch_size = program.dispatch_window_size.max(2); // Ensure at least 2 registers
|
||||
self.base_register_count = dispatch_size;
|
||||
|
||||
// Resize registers to match program requirements
|
||||
self.registers.clear();
|
||||
self.registers.resize(dispatch_size, Value::Undefined);
|
||||
|
||||
// Initialize rule cache
|
||||
self.rule_cache = vec![(false, Value::Undefined); program.rule_infos.len()];
|
||||
|
||||
// Set PC to main entry point
|
||||
self.pc = program.main_entry_point;
|
||||
self.executed_instructions = 0; // Reset instruction counter
|
||||
}
|
||||
|
||||
/// Set the compiled policy for default rule evaluation
|
||||
pub fn set_compiled_policy(&mut self, compiled_policy: CompiledPolicy) {
|
||||
self.compiled_policy = Some(compiled_policy);
|
||||
}
|
||||
|
||||
/// Set the maximum number of instructions that can be executed
|
||||
pub fn set_max_instructions(&mut self, max: usize) {
|
||||
self.max_instructions = max;
|
||||
}
|
||||
|
||||
/// Set the base register count for the main execution context
|
||||
/// This determines how many registers are available in the root register window
|
||||
pub fn set_base_register_count(&mut self, count: usize) {
|
||||
self.base_register_count = count.max(1); // Ensure at least 1 register
|
||||
if !self.registers.is_empty() {
|
||||
self.registers
|
||||
.resize(self.base_register_count, Value::Undefined);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the global data object
|
||||
pub fn set_data(&mut self, data: Value) -> Result<()> {
|
||||
// Check for conflicts between rule tree and data
|
||||
self.program.check_rule_data_conflicts(&data)?;
|
||||
|
||||
self.data = data;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the global input object
|
||||
pub fn set_input(&mut self, input: Value) {
|
||||
self.input = input;
|
||||
}
|
||||
|
||||
/// Get the number of entry points available
|
||||
pub fn get_entry_point_count(&self) -> usize {
|
||||
self.program.entry_points.len()
|
||||
}
|
||||
|
||||
/// Get all entry point names
|
||||
pub fn get_entry_point_names(&self) -> Vec<String> {
|
||||
self.program.entry_points.keys().cloned().collect()
|
||||
}
|
||||
|
||||
// Public getters for visualization
|
||||
pub fn get_pc(&self) -> usize {
|
||||
self.pc
|
||||
}
|
||||
|
||||
pub fn get_registers(&self) -> &Vec<Value> {
|
||||
&self.registers
|
||||
}
|
||||
|
||||
pub fn get_program(&self) -> &Arc<Program> {
|
||||
&self.program
|
||||
}
|
||||
|
||||
pub fn get_call_stack(&self) -> &Vec<CallRuleContext> {
|
||||
&self.call_rule_stack
|
||||
}
|
||||
|
||||
pub fn get_loop_stack(&self) -> &Vec<LoopContext> {
|
||||
&self.loop_stack
|
||||
}
|
||||
|
||||
pub fn get_cache_hits(&self) -> usize {
|
||||
self.cache_hits
|
||||
}
|
||||
|
||||
/// Set the execution mode for the VM
|
||||
pub fn set_execution_mode(&mut self, mode: ExecutionMode) {
|
||||
self.execution_mode = mode;
|
||||
}
|
||||
|
||||
/// Configure whether builtin operations should raise errors strictly
|
||||
pub fn set_strict_builtin_errors(&mut self, strict: bool) {
|
||||
self.strict_builtin_errors = strict;
|
||||
}
|
||||
|
||||
/// Returns whether builtin operations raise errors strictly
|
||||
pub fn strict_builtin_errors(&self) -> bool {
|
||||
self.strict_builtin_errors
|
||||
}
|
||||
|
||||
/// Enable or disable single-step execution for suspendable runs
|
||||
pub fn set_step_mode(&mut self, enabled: bool) {
|
||||
self.step_mode = enabled;
|
||||
}
|
||||
|
||||
/// Configure the sequence of HostAwait responses for run-to-completion execution
|
||||
pub fn set_host_await_responses<I, J>(&mut self, responses: I)
|
||||
where
|
||||
I: IntoIterator<Item = (Value, J)>,
|
||||
J: IntoIterator<Item = Value>,
|
||||
{
|
||||
self.host_await_responses.clear();
|
||||
|
||||
for (identifier, values) in responses {
|
||||
let mut queue = VecDeque::new();
|
||||
queue.extend(values);
|
||||
|
||||
match self.host_await_responses.entry(identifier) {
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(queue);
|
||||
}
|
||||
Entry::Occupied(mut entry) => {
|
||||
entry.get_mut().extend(queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn next_host_await_response(
|
||||
&mut self,
|
||||
identifier: &Value,
|
||||
dest: u8,
|
||||
) -> Result<Value> {
|
||||
let missing_error = || VmError::HostAwaitResponseMissing {
|
||||
dest,
|
||||
identifier: identifier.clone(),
|
||||
};
|
||||
|
||||
let (response, should_remove) = {
|
||||
let queue = self
|
||||
.host_await_responses
|
||||
.get_mut(identifier)
|
||||
.ok_or_else(missing_error)?;
|
||||
|
||||
let response = queue.pop_front().ok_or_else(missing_error)?;
|
||||
let should_remove = queue.is_empty();
|
||||
(response, should_remove)
|
||||
};
|
||||
|
||||
if should_remove {
|
||||
self.host_await_responses.remove(identifier);
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// Get the current execution mode
|
||||
pub fn get_execution_mode(&self) -> ExecutionMode {
|
||||
self.execution_mode
|
||||
}
|
||||
|
||||
/// Get the current execution state of the VM
|
||||
pub fn execution_state(&self) -> &ExecutionState {
|
||||
&self.execution_state
|
||||
}
|
||||
|
||||
/// Get the suspend reason if the VM is currently suspended
|
||||
pub fn suspend_reason(&self) -> Option<&SuspendReason> {
|
||||
match &self.execution_state {
|
||||
ExecutionState::Suspended { reason, .. } => Some(reason),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/rvm/vm/mod.rs
Normal file
23
src/rvm/vm/mod.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
mod arithmetic;
|
||||
mod comprehension;
|
||||
mod context;
|
||||
mod dispatch;
|
||||
mod errors;
|
||||
mod execution;
|
||||
mod execution_model;
|
||||
mod functions;
|
||||
mod loops;
|
||||
mod machine;
|
||||
mod rules;
|
||||
mod state;
|
||||
mod virtual_data;
|
||||
|
||||
pub use context::{CallRuleContext, IterationState, LoopContext};
|
||||
pub use errors::{Result, VmError};
|
||||
pub use execution_model::{ExecutionMode, ExecutionState, SuspendReason};
|
||||
pub use machine::RegoVM;
|
||||
627
src/rvm/vm/rules.rs
Normal file
627
src/rvm/vm/rules.rs
Normal file
@@ -0,0 +1,627 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::rvm::instructions::FunctionCallParams;
|
||||
use crate::rvm::program::{RuleInfo, RuleType};
|
||||
use crate::value::Value;
|
||||
use alloc::vec::Vec;
|
||||
use core::mem;
|
||||
|
||||
use super::context::CallRuleContext;
|
||||
use super::errors::{Result, VmError};
|
||||
use super::execution_model::{
|
||||
ExecutionFrame, ExecutionMode, FrameKind, RuleFrameData, RuleFramePhase,
|
||||
};
|
||||
use super::machine::RegoVM;
|
||||
|
||||
impl RegoVM {
|
||||
pub(super) fn execute_rule_definitions_common(
|
||||
&mut self,
|
||||
rule_definitions: &[Vec<u32>],
|
||||
rule_info: &RuleInfo,
|
||||
function_call_params: Option<&FunctionCallParams>,
|
||||
) -> Result<(Value, bool)> {
|
||||
let mut first_successful_result: Option<Value> = None;
|
||||
let mut rule_failed_due_to_inconsistency = false;
|
||||
let is_function_call = rule_info.function_info.is_some();
|
||||
let result_reg = rule_info.result_reg as usize;
|
||||
|
||||
let num_registers = rule_info.num_registers as usize;
|
||||
let mut register_window = self.new_register_window();
|
||||
register_window.clear();
|
||||
register_window.reserve(num_registers);
|
||||
|
||||
register_window.push(Value::Undefined);
|
||||
|
||||
let num_retained_registers = match function_call_params {
|
||||
Some(params) => {
|
||||
for arg in params.args[0..params.num_args as usize].iter() {
|
||||
register_window.push(self.registers[*arg as usize].clone());
|
||||
}
|
||||
params.num_args as usize + 1
|
||||
}
|
||||
_ => match rule_info.rule_type {
|
||||
RuleType::PartialSet | RuleType::PartialObject => 1,
|
||||
RuleType::Complete => 0,
|
||||
},
|
||||
};
|
||||
|
||||
let mut old_registers = Vec::default();
|
||||
mem::swap(&mut old_registers, &mut self.registers);
|
||||
|
||||
let mut old_loop_stack = Vec::default();
|
||||
mem::swap(&mut old_loop_stack, &mut self.loop_stack);
|
||||
|
||||
let mut old_comprehension_stack = Vec::default();
|
||||
mem::swap(&mut old_comprehension_stack, &mut self.comprehension_stack);
|
||||
|
||||
self.register_stack.push(old_registers);
|
||||
self.registers = register_window;
|
||||
|
||||
'outer: for (def_idx, definition_bodies) in rule_definitions.iter().enumerate() {
|
||||
for (body_entry_point_idx, body_entry_point) in definition_bodies.iter().enumerate() {
|
||||
if let Some(ctx) = self.call_rule_stack.last_mut() {
|
||||
ctx.current_body_index = body_entry_point_idx;
|
||||
ctx.current_definition_index = def_idx;
|
||||
}
|
||||
|
||||
self.registers
|
||||
.resize(num_retained_registers, Value::Undefined);
|
||||
self.registers.resize(num_registers, Value::Undefined);
|
||||
|
||||
if let Some(destructuring_entry_point) =
|
||||
rule_info.destructuring_blocks.get(def_idx).and_then(|x| *x)
|
||||
{
|
||||
match self.jump_to(destructuring_entry_point as usize) {
|
||||
Ok(_result) => {}
|
||||
Err(_e) => {
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match self.jump_to(*body_entry_point as usize) {
|
||||
Ok(_) => {
|
||||
if matches!(rule_info.rule_type, RuleType::Complete) || is_function_call {
|
||||
let current_result = self.registers[result_reg].clone();
|
||||
if current_result != Value::Undefined {
|
||||
if let Some(ref expected) = first_successful_result {
|
||||
if *expected != current_result {
|
||||
rule_failed_due_to_inconsistency = true;
|
||||
self.registers[result_reg] = Value::Undefined;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
first_successful_result = Some(current_result.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_e) => {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if rule_failed_due_to_inconsistency {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let final_result = if rule_failed_due_to_inconsistency {
|
||||
Value::Undefined
|
||||
} else if let Some(successful_result) = first_successful_result {
|
||||
successful_result
|
||||
} else {
|
||||
self.registers[result_reg].clone()
|
||||
};
|
||||
|
||||
if let Some(old_registers) = self.register_stack.pop() {
|
||||
let mut current_register_window = Vec::default();
|
||||
mem::swap(&mut current_register_window, &mut self.registers);
|
||||
self.return_register_window(current_register_window);
|
||||
|
||||
self.registers = old_registers;
|
||||
}
|
||||
|
||||
self.loop_stack = old_loop_stack;
|
||||
self.comprehension_stack = old_comprehension_stack;
|
||||
|
||||
Ok((final_result, rule_failed_due_to_inconsistency))
|
||||
}
|
||||
|
||||
pub(super) fn execute_call_rule_common(
|
||||
&mut self,
|
||||
dest: u8,
|
||||
rule_index: u16,
|
||||
function_call_params: Option<&FunctionCallParams>,
|
||||
) -> Result<()> {
|
||||
let rule_idx = rule_index as usize;
|
||||
|
||||
if rule_idx >= self.rule_cache.len() {
|
||||
return Err(VmError::RuleIndexOutOfBounds { index: rule_index });
|
||||
}
|
||||
|
||||
let rule_info = self
|
||||
.program
|
||||
.rule_infos
|
||||
.get(rule_idx)
|
||||
.ok_or(VmError::RuleInfoMissing { index: rule_index })?
|
||||
.clone();
|
||||
|
||||
let is_function_rule = rule_info.function_info.is_some();
|
||||
|
||||
if !is_function_rule {
|
||||
let (computed, cached_result) = &self.rule_cache[rule_idx];
|
||||
if *computed {
|
||||
self.registers[dest as usize] = cached_result.clone();
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let rule_type = rule_info.rule_type.clone();
|
||||
let rule_definitions = rule_info.definitions.clone();
|
||||
|
||||
if rule_definitions.is_empty() {
|
||||
let result = Value::Undefined;
|
||||
if !is_function_rule {
|
||||
self.rule_cache[rule_idx] = (true, result.clone());
|
||||
}
|
||||
self.registers[dest as usize] = result;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.call_rule_stack.push(CallRuleContext {
|
||||
return_pc: self.pc,
|
||||
dest_reg: dest,
|
||||
result_reg: rule_info.result_reg,
|
||||
rule_index,
|
||||
rule_type: rule_type.clone(),
|
||||
current_definition_index: 0,
|
||||
current_body_index: 0,
|
||||
});
|
||||
|
||||
let (final_result, rule_failed_due_to_inconsistency) = self
|
||||
.execute_rule_definitions_common(&rule_definitions, &rule_info, function_call_params)?;
|
||||
|
||||
self.registers[dest as usize] = Value::Undefined;
|
||||
|
||||
let call_context = self.call_rule_stack.pop().expect("Call stack underflow");
|
||||
self.pc = call_context.return_pc;
|
||||
|
||||
let result_from_rule = if !rule_failed_due_to_inconsistency {
|
||||
final_result
|
||||
} else {
|
||||
Value::Undefined
|
||||
};
|
||||
|
||||
self.registers[dest as usize] = result_from_rule.clone();
|
||||
|
||||
if self.registers[dest as usize] == Value::Undefined && !rule_failed_due_to_inconsistency {
|
||||
match call_context.rule_type {
|
||||
RuleType::PartialSet => {
|
||||
self.registers[dest as usize] = Value::new_set();
|
||||
}
|
||||
RuleType::PartialObject => {
|
||||
self.registers[dest as usize] = Value::new_object();
|
||||
}
|
||||
RuleType::Complete => {
|
||||
if let Some(rule_info) = self
|
||||
.program
|
||||
.rule_infos
|
||||
.get(call_context.rule_index as usize)
|
||||
{
|
||||
if let Some(default_literal_index) = rule_info.default_literal_index {
|
||||
if let Some(default_value) =
|
||||
self.program.literals.get(default_literal_index as usize)
|
||||
{
|
||||
self.registers[dest as usize] = default_value.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let final_result = self.registers[dest as usize].clone();
|
||||
if !is_function_rule {
|
||||
self.rule_cache[rule_idx] = (true, final_result);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn execute_call_rule(&mut self, dest: u8, rule_index: u16) -> Result<()> {
|
||||
match self.execution_mode {
|
||||
ExecutionMode::RunToCompletion => self.execute_call_rule_common(dest, rule_index, None),
|
||||
ExecutionMode::Suspendable => {
|
||||
self.execute_call_rule_suspendable(dest, rule_index, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn execute_call_rule_suspendable(
|
||||
&mut self,
|
||||
dest: u8,
|
||||
rule_index: u16,
|
||||
function_call_params: Option<&FunctionCallParams>,
|
||||
) -> Result<()> {
|
||||
let rule_idx = rule_index as usize;
|
||||
|
||||
if rule_idx >= self.rule_cache.len() {
|
||||
return Err(VmError::RuleIndexOutOfBounds { index: rule_index });
|
||||
}
|
||||
|
||||
let rule_info = self
|
||||
.program
|
||||
.rule_infos
|
||||
.get(rule_idx)
|
||||
.ok_or(VmError::RuleInfoMissing { index: rule_index })?
|
||||
.clone();
|
||||
|
||||
let is_function_rule = rule_info.function_info.is_some();
|
||||
|
||||
if !is_function_rule {
|
||||
let (computed, cached_result) = &self.rule_cache[rule_idx];
|
||||
if *computed {
|
||||
self.registers[dest as usize] = cached_result.clone();
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if rule_info.definitions.is_empty() {
|
||||
let result = Value::Undefined;
|
||||
if !is_function_rule {
|
||||
self.rule_cache[rule_idx] = (true, result.clone());
|
||||
}
|
||||
if self.registers.len() <= dest as usize {
|
||||
self.registers.resize(dest as usize + 1, Value::Undefined);
|
||||
}
|
||||
self.registers[dest as usize] = result;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let num_registers = rule_info.num_registers as usize;
|
||||
|
||||
let num_retained_registers = match function_call_params {
|
||||
Some(params) => params.arg_count() + 1,
|
||||
None => match rule_info.rule_type {
|
||||
RuleType::PartialSet | RuleType::PartialObject => 1,
|
||||
RuleType::Complete => 0,
|
||||
},
|
||||
};
|
||||
|
||||
let mut register_window = self.new_register_window();
|
||||
register_window.clear();
|
||||
register_window.reserve(num_registers);
|
||||
register_window.push(Value::Undefined);
|
||||
|
||||
if let Some(params) = function_call_params {
|
||||
for &arg in params.arg_registers() {
|
||||
register_window.push(self.registers[arg as usize].clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut saved_registers = Vec::default();
|
||||
mem::swap(&mut saved_registers, &mut self.registers);
|
||||
self.registers = register_window;
|
||||
|
||||
let mut saved_loop_stack = Vec::default();
|
||||
mem::swap(&mut saved_loop_stack, &mut self.loop_stack);
|
||||
|
||||
let mut saved_comprehension_stack = Vec::default();
|
||||
mem::swap(
|
||||
&mut saved_comprehension_stack,
|
||||
&mut self.comprehension_stack,
|
||||
);
|
||||
|
||||
self.loop_stack.clear();
|
||||
self.comprehension_stack.clear();
|
||||
|
||||
self.call_rule_stack.push(CallRuleContext {
|
||||
return_pc: self.pc,
|
||||
dest_reg: dest,
|
||||
result_reg: rule_info.result_reg,
|
||||
rule_index,
|
||||
rule_type: rule_info.rule_type.clone(),
|
||||
current_definition_index: 0,
|
||||
current_body_index: 0,
|
||||
});
|
||||
|
||||
let mut frame_data = RuleFrameData {
|
||||
return_pc: self.pc,
|
||||
dest_reg: dest,
|
||||
rule_index,
|
||||
current_definition_index: 0,
|
||||
current_body_index: 0,
|
||||
total_definitions: rule_info.definitions.len(),
|
||||
phase: RuleFramePhase::Initializing,
|
||||
accumulated_result: None,
|
||||
any_body_succeeded: false,
|
||||
rule_failed_due_to_inconsistency: false,
|
||||
rule_type: rule_info.rule_type.clone(),
|
||||
result_reg: rule_info.result_reg,
|
||||
is_function_rule,
|
||||
num_registers,
|
||||
num_retained_registers,
|
||||
saved_registers,
|
||||
saved_loop_stack,
|
||||
saved_comprehension_stack,
|
||||
};
|
||||
|
||||
let initial_pc = self
|
||||
.prepare_rule_frame_initial_pc(&mut frame_data, &rule_info)?
|
||||
.ok_or_else(|| VmError::Internal("Rule frame has no initial PC".into()))?;
|
||||
|
||||
let frame = ExecutionFrame::new(initial_pc, FrameKind::Rule(frame_data));
|
||||
self.execution_stack.push(frame);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn execute_rule_init(&mut self, result_reg: u8, _rule_index: u16) -> Result<()> {
|
||||
let current_ctx = self
|
||||
.call_rule_stack
|
||||
.last_mut()
|
||||
.expect("Call stack underflow");
|
||||
current_ctx.result_reg = result_reg;
|
||||
match current_ctx.rule_type {
|
||||
RuleType::Complete => {
|
||||
self.registers[result_reg as usize] = Value::Undefined;
|
||||
}
|
||||
RuleType::PartialSet => {
|
||||
if current_ctx.current_definition_index == 0 && current_ctx.current_body_index == 0
|
||||
{
|
||||
self.registers[result_reg as usize] = Value::new_set();
|
||||
}
|
||||
}
|
||||
RuleType::PartialObject => {
|
||||
if current_ctx.current_definition_index == 0 && current_ctx.current_body_index == 0
|
||||
{
|
||||
self.registers[result_reg as usize] = Value::new_object();
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn execute_rule_return(&mut self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prepare_rule_frame_initial_pc(
|
||||
&mut self,
|
||||
frame_data: &mut RuleFrameData,
|
||||
rule_info: &RuleInfo,
|
||||
) -> Result<Option<usize>> {
|
||||
frame_data.current_definition_index = 0;
|
||||
frame_data.current_body_index = 0;
|
||||
frame_data.phase = RuleFramePhase::Initializing;
|
||||
self.rule_frame_schedule_segment(frame_data, rule_info)
|
||||
}
|
||||
|
||||
fn rule_frame_schedule_segment(
|
||||
&mut self,
|
||||
frame_data: &mut RuleFrameData,
|
||||
rule_info: &RuleInfo,
|
||||
) -> Result<Option<usize>> {
|
||||
if frame_data.rule_failed_due_to_inconsistency {
|
||||
frame_data.phase = RuleFramePhase::Finalizing;
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
while frame_data.current_definition_index < frame_data.total_definitions {
|
||||
let definition_bodies = &rule_info.definitions[frame_data.current_definition_index];
|
||||
|
||||
if frame_data.current_body_index < definition_bodies.len() {
|
||||
if let Some(ctx) = self.call_rule_stack.last_mut() {
|
||||
ctx.current_definition_index = frame_data.current_definition_index;
|
||||
ctx.current_body_index = frame_data.current_body_index;
|
||||
}
|
||||
|
||||
self.registers
|
||||
.resize(frame_data.num_retained_registers, Value::Undefined);
|
||||
self.registers
|
||||
.resize(frame_data.num_registers, Value::Undefined);
|
||||
|
||||
if let Some(destructuring_entry_point) = rule_info
|
||||
.destructuring_blocks
|
||||
.get(frame_data.current_definition_index)
|
||||
.and_then(|opt| *opt)
|
||||
{
|
||||
frame_data.phase = RuleFramePhase::ExecutingDestructuring;
|
||||
return Ok(Some(destructuring_entry_point as usize));
|
||||
} else {
|
||||
frame_data.phase = RuleFramePhase::ExecutingBody;
|
||||
return Ok(Some(
|
||||
definition_bodies[frame_data.current_body_index] as usize,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
frame_data.current_definition_index += 1;
|
||||
frame_data.current_body_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
frame_data.phase = RuleFramePhase::Finalizing;
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn rule_frame_after_destructuring_success(
|
||||
&mut self,
|
||||
frame_data: &mut RuleFrameData,
|
||||
rule_info: &RuleInfo,
|
||||
) -> Result<Option<usize>> {
|
||||
frame_data.phase = RuleFramePhase::ExecutingBody;
|
||||
let definition_bodies = &rule_info.definitions[frame_data.current_definition_index];
|
||||
if frame_data.current_body_index >= definition_bodies.len() {
|
||||
frame_data.current_body_index += 1;
|
||||
return self.rule_frame_schedule_segment(frame_data, rule_info);
|
||||
}
|
||||
|
||||
Ok(Some(
|
||||
definition_bodies[frame_data.current_body_index] as usize,
|
||||
))
|
||||
}
|
||||
|
||||
fn rule_frame_after_failure(
|
||||
&mut self,
|
||||
frame_data: &mut RuleFrameData,
|
||||
rule_info: &RuleInfo,
|
||||
) -> Result<Option<usize>> {
|
||||
frame_data.current_body_index += 1;
|
||||
self.rule_frame_schedule_segment(frame_data, rule_info)
|
||||
}
|
||||
|
||||
fn rule_frame_after_success(
|
||||
&mut self,
|
||||
frame_data: &mut RuleFrameData,
|
||||
rule_info: &RuleInfo,
|
||||
) -> Result<Option<usize>> {
|
||||
frame_data.any_body_succeeded = true;
|
||||
|
||||
if matches!(frame_data.rule_type, RuleType::Complete) || frame_data.is_function_rule {
|
||||
let current_result = self
|
||||
.registers
|
||||
.get(frame_data.result_reg as usize)
|
||||
.cloned()
|
||||
.unwrap_or(Value::Undefined);
|
||||
|
||||
if current_result != Value::Undefined {
|
||||
if let Some(expected) = &frame_data.accumulated_result {
|
||||
if *expected != current_result {
|
||||
frame_data.rule_failed_due_to_inconsistency = true;
|
||||
if let Some(result_slot) =
|
||||
self.registers.get_mut(frame_data.result_reg as usize)
|
||||
{
|
||||
*result_slot = Value::Undefined;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
frame_data.accumulated_result = Some(current_result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frame_data.current_body_index += 1;
|
||||
self.rule_frame_schedule_segment(frame_data, rule_info)
|
||||
}
|
||||
|
||||
pub(super) fn finalize_rule_frame_data(&mut self, frame_data: RuleFrameData) -> Result<Value> {
|
||||
let RuleFrameData {
|
||||
return_pc,
|
||||
dest_reg,
|
||||
rule_index,
|
||||
accumulated_result,
|
||||
rule_failed_due_to_inconsistency,
|
||||
rule_type,
|
||||
result_reg,
|
||||
is_function_rule,
|
||||
saved_registers,
|
||||
saved_loop_stack,
|
||||
saved_comprehension_stack,
|
||||
..
|
||||
} = frame_data;
|
||||
|
||||
let rule_idx = rule_index as usize;
|
||||
let rule_info = self
|
||||
.program
|
||||
.rule_infos
|
||||
.get(rule_idx)
|
||||
.ok_or(VmError::RuleInfoMissing { index: rule_index })?
|
||||
.clone();
|
||||
|
||||
let result_from_rule = if rule_failed_due_to_inconsistency {
|
||||
Value::Undefined
|
||||
} else if let Some(value) = accumulated_result {
|
||||
value
|
||||
} else {
|
||||
self.registers
|
||||
.get(result_reg as usize)
|
||||
.cloned()
|
||||
.unwrap_or(Value::Undefined)
|
||||
};
|
||||
|
||||
let mut current_window = Vec::default();
|
||||
mem::swap(&mut current_window, &mut self.registers);
|
||||
self.return_register_window(current_window);
|
||||
|
||||
self.loop_stack = saved_loop_stack;
|
||||
self.comprehension_stack = saved_comprehension_stack;
|
||||
|
||||
let mut parent_registers = saved_registers;
|
||||
if parent_registers.len() <= dest_reg as usize {
|
||||
parent_registers.resize(dest_reg as usize + 1, Value::Undefined);
|
||||
}
|
||||
parent_registers[dest_reg as usize] = result_from_rule.clone();
|
||||
|
||||
if parent_registers[dest_reg as usize] == Value::Undefined
|
||||
&& !rule_failed_due_to_inconsistency
|
||||
{
|
||||
match rule_type {
|
||||
RuleType::PartialSet => parent_registers[dest_reg as usize] = Value::new_set(),
|
||||
RuleType::PartialObject => {
|
||||
parent_registers[dest_reg as usize] = Value::new_object()
|
||||
}
|
||||
RuleType::Complete => {
|
||||
if let Some(default_literal_index) = rule_info.default_literal_index {
|
||||
if let Some(default_value) =
|
||||
self.program.literals.get(default_literal_index as usize)
|
||||
{
|
||||
parent_registers[dest_reg as usize] = default_value.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let final_value = parent_registers[dest_reg as usize].clone();
|
||||
|
||||
if !is_function_rule {
|
||||
self.rule_cache[rule_idx] = (true, final_value.clone());
|
||||
}
|
||||
|
||||
self.registers = parent_registers;
|
||||
|
||||
if self.call_rule_stack.pop().is_none() {
|
||||
return Err(VmError::Internal(alloc::format!(
|
||||
"Call rule stack underflow during rule finalization | {}",
|
||||
self.get_debug_state()
|
||||
)));
|
||||
}
|
||||
|
||||
self.pc = return_pc;
|
||||
|
||||
Ok(final_value)
|
||||
}
|
||||
|
||||
pub(super) fn handle_rule_break_event(
|
||||
&mut self,
|
||||
frame_data: &mut RuleFrameData,
|
||||
) -> Result<Option<usize>> {
|
||||
let rule_info = self.get_rule_info(frame_data.rule_index)?;
|
||||
match frame_data.phase {
|
||||
RuleFramePhase::ExecutingDestructuring => {
|
||||
self.rule_frame_after_destructuring_success(frame_data, &rule_info)
|
||||
}
|
||||
RuleFramePhase::ExecutingBody => self.rule_frame_after_success(frame_data, &rule_info),
|
||||
RuleFramePhase::Initializing | RuleFramePhase::Finalizing => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn handle_rule_error_event(
|
||||
&mut self,
|
||||
frame_data: &mut RuleFrameData,
|
||||
) -> Result<Option<usize>> {
|
||||
let rule_info = self.get_rule_info(frame_data.rule_index)?;
|
||||
self.rule_frame_after_failure(frame_data, &rule_info)
|
||||
}
|
||||
|
||||
fn get_rule_info(&self, rule_index: u16) -> Result<RuleInfo> {
|
||||
let idx = rule_index as usize;
|
||||
self.program
|
||||
.rule_infos
|
||||
.get(idx)
|
||||
.cloned()
|
||||
.ok_or(VmError::RuleInfoMissing { index: rule_index })
|
||||
}
|
||||
}
|
||||
107
src/rvm/vm/state.rs
Normal file
107
src/rvm/vm/state.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::value::Value;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::errors::{Result, VmError};
|
||||
use super::execution_model::ExecutionState;
|
||||
use super::machine::RegoVM;
|
||||
|
||||
impl RegoVM {
|
||||
/// Reset all execution state and return objects to pools for reuse
|
||||
pub(super) fn reset_execution_state(&mut self) {
|
||||
// Reset basic execution state
|
||||
self.executed_instructions = 0;
|
||||
self.pc = 0;
|
||||
self.evaluated = Value::new_object();
|
||||
self.cache_hits = 0;
|
||||
|
||||
// Reset suspendable execution state
|
||||
self.execution_stack.clear();
|
||||
self.execution_state = ExecutionState::Ready;
|
||||
|
||||
// Return objects to pools and clear stacks
|
||||
self.return_to_pools();
|
||||
|
||||
// Reset rule cache
|
||||
self.rule_cache = alloc::vec![(false, Value::Undefined); self.program.rule_infos.len()];
|
||||
|
||||
// Reset registers to clean state
|
||||
self.registers.clear();
|
||||
self.registers
|
||||
.resize(self.base_register_count, Value::Undefined);
|
||||
}
|
||||
|
||||
/// Return all active objects to their respective pools for reuse
|
||||
pub(super) fn return_to_pools(&mut self) {
|
||||
// Clear stacks - these are small structs that don't need pooling
|
||||
self.loop_stack.clear();
|
||||
self.call_rule_stack.clear();
|
||||
self.comprehension_stack.clear();
|
||||
|
||||
// Return register windows to pool for reuse
|
||||
while let Some(registers) = self.register_stack.pop() {
|
||||
self.return_register_window(registers);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a register window from the pool or create a new one
|
||||
pub(super) fn new_register_window(&mut self) -> Vec<Value> {
|
||||
self.register_window_pool.pop().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Return a register window to the pool for reuse
|
||||
pub(super) fn return_register_window(&mut self, mut window: Vec<Value>) {
|
||||
window.clear(); // Clear contents for reuse
|
||||
self.register_window_pool.push(window);
|
||||
}
|
||||
|
||||
/// Validate VM state consistency for debugging
|
||||
pub(super) fn validate_vm_state(&self) -> Result<()> {
|
||||
// Check register bounds
|
||||
if self.registers.len() < self.base_register_count {
|
||||
return Err(VmError::Internal(alloc::format!(
|
||||
"Register count {} < base count {}",
|
||||
self.registers.len(),
|
||||
self.base_register_count
|
||||
)));
|
||||
}
|
||||
|
||||
// Check PC bounds
|
||||
if self.pc >= self.program.instructions.len() {
|
||||
return Err(VmError::Internal(alloc::format!(
|
||||
"PC {} >= instruction count {}",
|
||||
self.pc,
|
||||
self.program.instructions.len()
|
||||
)));
|
||||
}
|
||||
|
||||
// Check rule cache bounds
|
||||
if self.rule_cache.len() != self.program.rule_infos.len() {
|
||||
return Err(VmError::Internal(alloc::format!(
|
||||
"Rule cache size {} != rule info count {}",
|
||||
self.rule_cache.len(),
|
||||
self.program.rule_infos.len()
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get current VM state for debugging
|
||||
pub(super) fn get_debug_state(&self) -> String {
|
||||
alloc::format!(
|
||||
"VM State: PC={}, registers={}, executed={}/{}, stacks: loop={}, call={}, register={}, comprehension={}",
|
||||
self.pc,
|
||||
self.registers.len(),
|
||||
self.executed_instructions,
|
||||
self.max_instructions,
|
||||
self.loop_stack.len(),
|
||||
self.call_rule_stack.len(),
|
||||
self.register_stack.len(),
|
||||
self.comprehension_stack.len()
|
||||
)
|
||||
}
|
||||
}
|
||||
293
src/rvm/vm/virtual_data.rs
Normal file
293
src/rvm/vm/virtual_data.rs
Normal file
@@ -0,0 +1,293 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::rvm::instructions::LiteralOrRegister;
|
||||
use crate::value::Value;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::errors::{Result, VmError};
|
||||
use super::machine::RegoVM;
|
||||
|
||||
impl RegoVM {
|
||||
pub(super) fn execute_virtual_data_document_lookup_subobject(
|
||||
&mut self,
|
||||
path_components: &[LiteralOrRegister],
|
||||
rule_tree_subobject: &Value,
|
||||
) -> Result<Value> {
|
||||
let mut root_path = Vec::new();
|
||||
for component in path_components {
|
||||
let key_value = match component {
|
||||
LiteralOrRegister::Literal(idx) => self
|
||||
.program
|
||||
.literals
|
||||
.get(*idx as usize)
|
||||
.ok_or(VmError::LiteralIndexOutOfBounds {
|
||||
index: *idx as usize,
|
||||
})?
|
||||
.clone(),
|
||||
LiteralOrRegister::Register(reg) => self.registers[*reg as usize].clone(),
|
||||
};
|
||||
root_path.push(key_value);
|
||||
}
|
||||
|
||||
let mut data_subobject = self.data.clone();
|
||||
for path_component in &root_path {
|
||||
data_subobject = data_subobject[path_component].clone();
|
||||
}
|
||||
|
||||
let mut result_subobject = match data_subobject {
|
||||
Value::Undefined => Value::new_object(),
|
||||
_ => data_subobject,
|
||||
};
|
||||
|
||||
self.traverse_rule_tree_subobject(rule_tree_subobject, &mut result_subobject, &root_path)?;
|
||||
|
||||
Ok(result_subobject)
|
||||
}
|
||||
|
||||
fn set_nested_value(&self, target: &mut Value, path: &[Value], value: Value) -> Result<()> {
|
||||
Self::set_nested_value_static(target, path, value)
|
||||
}
|
||||
|
||||
fn set_nested_value_static(target: &mut Value, path: &[Value], value: Value) -> Result<()> {
|
||||
if path.is_empty() {
|
||||
*target = value;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if *target == Value::Undefined {
|
||||
*target = Value::new_object();
|
||||
}
|
||||
|
||||
if let Value::Object(ref mut map) = target {
|
||||
let key = &path[0];
|
||||
|
||||
if !map.contains_key(key) {
|
||||
crate::Rc::make_mut(map).insert(key.clone(), Value::Undefined);
|
||||
}
|
||||
|
||||
if let Some(next_target) = crate::Rc::make_mut(map).get_mut(key) {
|
||||
Self::set_nested_value_static(next_target, &path[1..], value)?;
|
||||
}
|
||||
} else {
|
||||
return Err(VmError::InvalidRuleTreeEntry {
|
||||
value: target.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn traverse_rule_tree_subobject(
|
||||
&mut self,
|
||||
rule_tree_node: &Value,
|
||||
result_subobject: &mut Value,
|
||||
root_path: &[Value],
|
||||
) -> Result<()> {
|
||||
self.traverse_rule_tree_subobject_with_path(
|
||||
rule_tree_node,
|
||||
result_subobject,
|
||||
root_path,
|
||||
&[],
|
||||
)
|
||||
}
|
||||
|
||||
fn traverse_rule_tree_subobject_with_path(
|
||||
&mut self,
|
||||
rule_tree_node: &Value,
|
||||
result_subobject: &mut Value,
|
||||
root_path: &[Value],
|
||||
relative_path: &[Value],
|
||||
) -> Result<()> {
|
||||
match rule_tree_node {
|
||||
Value::Number(rule_idx) => {
|
||||
if let Some(rule_index) = rule_idx.as_u64() {
|
||||
let mut full_cache_path = root_path.to_vec();
|
||||
full_cache_path.extend_from_slice(relative_path);
|
||||
|
||||
let cached_result = {
|
||||
let mut cache_lookup = &self.evaluated;
|
||||
let mut path_exists = true;
|
||||
|
||||
for path_component in &full_cache_path {
|
||||
if let Value::Object(ref map) = cache_lookup {
|
||||
if let Some(next_value) = map.get(path_component) {
|
||||
cache_lookup = next_value;
|
||||
} else {
|
||||
path_exists = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
path_exists = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if path_exists {
|
||||
if let Value::Object(ref map) = cache_lookup {
|
||||
map.get(&Value::Undefined).cloned()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let rule_result = if let Some(cached) = cached_result {
|
||||
self.cache_hits += 1;
|
||||
cached
|
||||
} else {
|
||||
let temp_reg = self.registers.len() as u8;
|
||||
self.registers.push(Value::Undefined);
|
||||
self.execute_call_rule_common(temp_reg, rule_index as u16, None)?;
|
||||
let result = self.registers.pop().unwrap();
|
||||
|
||||
let mut cache_path = full_cache_path.clone();
|
||||
cache_path.push(Value::Undefined);
|
||||
Self::set_nested_value_static(
|
||||
&mut self.evaluated,
|
||||
&cache_path,
|
||||
result.clone(),
|
||||
)?;
|
||||
|
||||
result
|
||||
};
|
||||
|
||||
self.set_nested_value(result_subobject, relative_path, rule_result)?;
|
||||
} else {
|
||||
return Err(VmError::InvalidRuleIndex {
|
||||
rule_index: Value::Number(rule_idx.clone()),
|
||||
});
|
||||
}
|
||||
}
|
||||
Value::Object(obj) => {
|
||||
for (key, value) in obj.iter() {
|
||||
let mut new_relative_path = relative_path.to_vec();
|
||||
new_relative_path.push(key.clone());
|
||||
self.traverse_rule_tree_subobject_with_path(
|
||||
value,
|
||||
result_subobject,
|
||||
root_path,
|
||||
&new_relative_path,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn execute_virtual_data_document_lookup(&mut self, params_index: u16) -> Result<()> {
|
||||
let params = self
|
||||
.program
|
||||
.instruction_data
|
||||
.get_virtual_data_document_lookup_params(params_index)
|
||||
.ok_or(VmError::InvalidVirtualDataDocumentLookupParams {
|
||||
index: params_index,
|
||||
})?
|
||||
.clone();
|
||||
|
||||
let mut current_node = &self.program.rule_tree["data"];
|
||||
let mut components_consumed = 0;
|
||||
|
||||
for (i, component) in params.path_components.iter().enumerate() {
|
||||
let key_value = match component {
|
||||
LiteralOrRegister::Literal(idx) => self
|
||||
.program
|
||||
.literals
|
||||
.get(*idx as usize)
|
||||
.ok_or(VmError::LiteralIndexOutOfBounds {
|
||||
index: *idx as usize,
|
||||
})?
|
||||
.clone(),
|
||||
LiteralOrRegister::Register(reg) => self.registers[*reg as usize].clone(),
|
||||
};
|
||||
|
||||
current_node = ¤t_node[&key_value];
|
||||
components_consumed = i + 1;
|
||||
|
||||
match current_node {
|
||||
Value::Undefined | Value::Number(_) => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
match current_node {
|
||||
Value::Number(rule_index_value) => {
|
||||
if let Some(rule_index) = rule_index_value.as_u64() {
|
||||
let rule_index = rule_index as u16;
|
||||
|
||||
self.execute_call_rule_common(params.dest, rule_index, None)?;
|
||||
|
||||
if components_consumed < params.path_components.len() {
|
||||
let mut rule_result = self.registers[params.dest as usize].clone();
|
||||
|
||||
for component in ¶ms.path_components[components_consumed..] {
|
||||
let key_value = match component {
|
||||
LiteralOrRegister::Literal(idx) => self
|
||||
.program
|
||||
.literals
|
||||
.get(*idx as usize)
|
||||
.ok_or(VmError::LiteralIndexOutOfBounds {
|
||||
index: *idx as usize,
|
||||
})?
|
||||
.clone(),
|
||||
LiteralOrRegister::Register(reg) => {
|
||||
self.registers[*reg as usize].clone()
|
||||
}
|
||||
};
|
||||
|
||||
rule_result = rule_result[&key_value].clone();
|
||||
}
|
||||
|
||||
self.registers[params.dest as usize] = rule_result;
|
||||
}
|
||||
} else {
|
||||
return Err(VmError::InvalidRuleIndex {
|
||||
rule_index: Value::Number(rule_index_value.clone()),
|
||||
});
|
||||
}
|
||||
}
|
||||
Value::Undefined | Value::Object(_)
|
||||
if components_consumed != params.path_components.len() =>
|
||||
{
|
||||
let mut result = self.data.clone();
|
||||
|
||||
for component in ¶ms.path_components {
|
||||
let key_value = match component {
|
||||
LiteralOrRegister::Literal(idx) => self
|
||||
.program
|
||||
.literals
|
||||
.get(*idx as usize)
|
||||
.ok_or(VmError::LiteralIndexOutOfBounds {
|
||||
index: *idx as usize,
|
||||
})?
|
||||
.clone(),
|
||||
LiteralOrRegister::Register(reg) => self.registers[*reg as usize].clone(),
|
||||
};
|
||||
|
||||
result = result[&key_value].clone();
|
||||
}
|
||||
|
||||
self.registers[params.dest as usize] = result;
|
||||
}
|
||||
Value::Object(_) => {
|
||||
let rule_tree_subobject = current_node.clone();
|
||||
|
||||
let result = self.execute_virtual_data_document_lookup_subobject(
|
||||
¶ms.path_components,
|
||||
&rule_tree_subobject,
|
||||
)?;
|
||||
self.registers[params.dest as usize] = result;
|
||||
}
|
||||
_ => {
|
||||
return Err(VmError::InvalidRuleTreeEntry {
|
||||
value: current_node.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
mod interpreter;
|
||||
pub mod interpreter;
|
||||
mod scheduler;
|
||||
|
||||
17
src/value.rs
17
src/value.rs
@@ -4,6 +4,7 @@
|
||||
use crate::number::Number;
|
||||
|
||||
use alloc::collections::{BTreeMap, BTreeSet};
|
||||
use alloc::vec::Vec;
|
||||
use core::fmt;
|
||||
use core::ops;
|
||||
|
||||
@@ -67,7 +68,7 @@ impl Serialize for Value {
|
||||
{
|
||||
use serde::ser::Error;
|
||||
match self {
|
||||
Value::Null => serializer.serialize_none(),
|
||||
Value::Null => serializer.serialize_unit(),
|
||||
Value::Bool(b) => serializer.serialize_bool(*b),
|
||||
Value::String(s) => serializer.serialize_str(s.as_ref()),
|
||||
Value::Number(n) => n.serialize(serializer),
|
||||
@@ -118,6 +119,20 @@ impl<'de> Visitor<'de> for ValueVisitor {
|
||||
Ok(Value::Bool(v))
|
||||
}
|
||||
|
||||
fn visit_none<E>(self) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(Value::Null)
|
||||
}
|
||||
|
||||
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Value::deserialize(deserializer)
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
|
||||
Reference in New Issue
Block a user