use crate::rvm::program::Program;
#[cfg(feature = "allocator-memory-limits")]
use crate::utils::limits;
use crate::utils::limits::{
fallback_execution_timer_config, monotonic_now, ExecutionTimer, ExecutionTimerConfig,
LimitError,
};
use crate::value::Value;
use crate::CompiledPolicy;
use alloc::collections::{btree_map::Entry, BTreeMap, VecDeque};
#[cfg(feature = "allocator-memory-limits")]
use alloc::format;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec;
use alloc::vec::Vec;
use core::time::Duration;
use super::context::{CallRuleContext, ComprehensionContext, LoopContext};
use super::errors::{Result, VmError};
use super::execution_model::{
BreakpointSet, ExecutionMode, ExecutionStack, ExecutionState, SuspendReason,
};
#[derive(Debug)]
pub struct RegoVM {
pub(super) registers: Vec<Value>,
pub(super) pc: usize,
pub(super) program: Arc<Program>,
pub(super) compiled_policy: Option<CompiledPolicy>,
pub(super) rule_cache: Vec<(bool, Value)>,
pub(super) data: Value,
pub(super) input: Value,
pub(super) loop_stack: Vec<LoopContext>,
pub(super) call_rule_stack: Vec<CallRuleContext>,
pub(super) register_stack: Vec<Vec<Value>>,
pub(super) comprehension_stack: Vec<ComprehensionContext>,
pub(super) base_register_count: usize,
pub(super) register_window_pool: Vec<Vec<Value>>,
pub(super) max_instructions: usize,
pub(super) executed_instructions: usize,
pub(super) evaluated: Value,
pub(super) cache_hits: usize,
pub(super) execution_stack: ExecutionStack,
pub(super) execution_state: ExecutionState,
pub(super) breakpoints: BreakpointSet,
pub(super) step_mode: bool,
pub(super) host_await_responses: BTreeMap<Value, VecDeque<Value>>,
pub(super) execution_mode: ExecutionMode,
pub(super) frame_pc_overridden: bool,
pub(super) strict_builtin_errors: bool,
pub(super) builtins_cache: BTreeMap<(&'static str, Vec<Value>), Value>,
pub(super) execution_timer_config: Option<ExecutionTimerConfig>,
pub(super) execution_timer: ExecutionTimer,
pub(super) execution_timer_elapsed_at_suspend: Option<Duration>,
}
impl Default for RegoVM {
fn default() -> Self {
Self::new()
}
}
impl RegoVM {
pub fn new() -> Self {
let fallback_timer = fallback_execution_timer_config();
RegoVM {
registers: Vec::new(), 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, register_window_pool: Vec::new(), max_instructions: 25000, executed_instructions: 0,
evaluated: Value::new_object(), cache_hits: 0, 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,
builtins_cache: BTreeMap::new(),
execution_timer_config: None,
execution_timer: ExecutionTimer::new(fallback_timer),
execution_timer_elapsed_at_suspend: None,
}
}
pub fn new_with_policy(compiled_policy: CompiledPolicy) -> Self {
let mut vm = Self::new();
vm.compiled_policy = Some(compiled_policy);
vm
}
pub fn load_program(&mut self, program: Arc<Program>) {
self.program = program.clone();
let dispatch_size = usize::from(program.dispatch_window_size).max(2); self.base_register_count = dispatch_size;
self.registers.clear();
self.registers.resize(dispatch_size, Value::Undefined);
self.rule_cache = vec![(false, Value::Undefined); program.rule_infos.len()];
self.pc = usize::try_from(program.main_entry_point).unwrap_or(0);
self.executed_instructions = 0; }
pub fn set_compiled_policy(&mut self, compiled_policy: CompiledPolicy) {
self.compiled_policy = Some(compiled_policy);
}
pub const fn set_max_instructions(&mut self, max: usize) {
self.max_instructions = max;
}
pub fn set_base_register_count(&mut self, count: usize) {
self.base_register_count = count.max(1); if !self.registers.is_empty() {
self.registers
.resize(self.base_register_count, Value::Undefined);
}
}
pub fn set_data(&mut self, data: Value) -> Result<()> {
self.program.check_rule_data_conflicts(&data)?;
self.data = data;
Ok(())
}
pub fn set_input(&mut self, input: Value) {
self.input = input;
}
pub fn get_entry_point_count(&self) -> usize {
self.program.entry_points.len()
}
pub fn get_entry_point_names(&self) -> Vec<String> {
self.program.entry_points.keys().cloned().collect()
}
pub const fn get_pc(&self) -> usize {
self.pc
}
pub const fn get_registers(&self) -> &Vec<Value> {
&self.registers
}
pub const fn get_program(&self) -> &Arc<Program> {
&self.program
}
pub const fn get_call_stack(&self) -> &Vec<CallRuleContext> {
&self.call_rule_stack
}
pub const fn get_loop_stack(&self) -> &Vec<LoopContext> {
&self.loop_stack
}
pub const fn get_cache_hits(&self) -> usize {
self.cache_hits
}
pub const fn set_execution_mode(&mut self, mode: ExecutionMode) {
self.execution_mode = mode;
}
pub const fn set_strict_builtin_errors(&mut self, strict: bool) {
self.strict_builtin_errors = strict;
}
pub const fn strict_builtin_errors(&self) -> bool {
self.strict_builtin_errors
}
pub const fn set_step_mode(&mut self, enabled: bool) {
self.step_mode = enabled;
}
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(),
pc: self.pc,
};
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)
}
pub const fn get_execution_mode(&self) -> ExecutionMode {
self.execution_mode
}
pub fn set_execution_timer_config(&mut self, config: Option<ExecutionTimerConfig>) {
self.execution_timer_config = config;
self.reset_execution_timer_state();
}
pub const fn execution_timer_config(&self) -> Option<ExecutionTimerConfig> {
self.execution_timer_config
}
pub(super) fn reset_execution_timer_state(&mut self) {
let config = self.effective_execution_timer_config();
self.execution_timer = ExecutionTimer::new(config);
self.execution_timer_elapsed_at_suspend = None;
if config.is_none() {
return;
}
if let Some(now) = monotonic_now() {
self.execution_timer.start(now);
}
}
fn effective_execution_timer_config(&self) -> Option<ExecutionTimerConfig> {
self.execution_timer_config
.or_else(fallback_execution_timer_config)
}
pub(super) fn execution_timer_tick(&mut self, work_units: u32) -> Result<()> {
if self.execution_timer.limit().is_none() {
return Ok(());
}
let Some(now) = monotonic_now() else {
return Ok(());
};
self.execution_timer
.tick(work_units, now)
.map_err(|err| match err {
LimitError::TimeLimitExceeded { elapsed, limit } => VmError::TimeLimitExceeded {
elapsed,
limit,
pc: self.pc,
},
LimitError::MemoryLimitExceeded { usage, limit } => VmError::MemoryLimitExceeded {
usage,
limit,
pc: self.pc,
},
})
}
pub(super) fn snapshot_execution_timer_on_suspend(&mut self) {
if self.execution_timer.config().is_none() {
self.execution_timer_elapsed_at_suspend = None;
return;
}
let Some(now) = monotonic_now() else {
self.execution_timer_elapsed_at_suspend = None;
return;
};
self.execution_timer_elapsed_at_suspend = self.execution_timer.elapsed(now);
}
pub(super) fn restore_execution_timer_after_resume(&mut self) {
if self.execution_timer.config().is_none() {
self.execution_timer_elapsed_at_suspend = None;
return;
}
let Some(elapsed) = self.execution_timer_elapsed_at_suspend.take() else {
return;
};
let Some(now) = monotonic_now() else {
return;
};
self.execution_timer.resume_from_elapsed(now, elapsed);
}
pub const fn execution_state(&self) -> &ExecutionState {
&self.execution_state
}
pub const fn suspend_reason(&self) -> Option<&SuspendReason> {
match self.execution_state {
ExecutionState::Suspended { ref reason, .. } => Some(reason),
_ => None,
}
}
#[inline]
#[allow(dead_code)]
pub(super) fn get_register(&self, index: u8) -> Result<&Value> {
self.registers
.get(usize::from(index))
.ok_or(VmError::RegisterIndexOutOfBounds {
index,
pc: self.pc,
register_count: self.registers.len(),
})
}
#[inline]
#[allow(dead_code)]
pub(super) fn set_register(&mut self, index: u8, value: Value) -> Result<()> {
let register_count = self.registers.len();
let slot = self.registers.get_mut(usize::from(index)).ok_or(
VmError::RegisterIndexOutOfBounds {
index,
pc: self.pc,
register_count,
},
)?;
*slot = value;
Ok(())
}
#[cfg(feature = "allocator-memory-limits")]
pub(super) fn memory_check(&mut self) -> Result<()> {
limits::check_memory_limit_if_needed().map_err(|err| match err {
LimitError::MemoryLimitExceeded { usage, limit } => VmError::MemoryLimitExceeded {
usage,
limit,
pc: self.pc,
},
other => VmError::Internal {
message: format!("unexpected limit error: {other}"),
pc: self.pc,
},
})
}
#[cfg(not(feature = "allocator-memory-limits"))]
pub(super) fn memory_check(&mut self) -> Result<()> {
Ok(())
}
}