Rvm optimizations (#620)

* perf(rvm): fix O(n²) comprehension yield by mutating in-place

Instead of cloning the entire accumulator collection on every yield
iteration, use take_register + Rc::make_mut to get exclusive ownership
and mutate in-place. This reduces comprehension yield from O(n²) to O(n)
for both run-to-completion and suspendable execution modes.

- Add RegoVM::take_register() helper that swaps register with Undefined
- Comprehension yield now takes the accumulator, mutates via Rc::make_mut,
  and writes back — avoiding deep clones when refcount == 1

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* perf(rvm): use take_register for ObjectSet, ArrayPush, SetAdd

These instructions were cloning the container register (bumping Rc to 2),
then calling as_object_mut/as_array_mut/as_set_mut which invokes
Rc::make_mut — deep-cloning the entire collection since refcount > 1.

Use take_register instead so the Rc refcount stays at 1, making
Rc::make_mut a no-op and allowing in-place mutation.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* perf(rvm): remove unnecessary clones in rule caching

- execute_call_rule_common: move final_value into cache instead of
  cloning, since it is not used afterwards
- finalize_rule_frame_data: add comment clarifying the clone is needed
  because the value is both cached and returned
- Remove unnecessary .clone() on result_from_rule when setting register

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* rvm: avoid RuleInfo clone per rule call

Replace RuleInfo.clone() (which heap-allocates name, destructuring_blocks, and
potentially function_info) with a cheap Arc<Program> clone (atomic refcount
bump) followed by borrowing &RuleInfo from the local Arc. This eliminates
per-rule-call heap allocations.

Sites changed:
- execute_call_rule_common: Arc clone + borrow
- execute_call_rule_suspendable: Arc clone + borrow
- finalize_rule_frame_data: Arc clone + borrow
- handle_rule_break_event: inline Arc clone + borrow (was get_rule_info)
- handle_rule_error_event: inline Arc clone + borrow (was get_rule_info)
- Removed now-unused get_rule_info method

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* rvm: replace bincode with postcard for serialization

Remove unlinked bincode dependency. Use postcard (already a dep for rvm feature)
for all binary serialization/deserialization in program serialization and tests.

Also adds rvm_benchmark benchmark.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* perf(rvm): cache dummy Span/Expr for builtin calls

Every builtin call was allocating a Source (via from_contents), a Span, and
N Ref<Expr> wrappers just to satisfy the builtin function signature. These
dummy values are only used for error reporting context.

Cache the dummy Span and Vec<Ref<Expr>> on the RegoVM struct. The Source and
Span are created once on first builtin call; dummy Expr entries grow as
needed and are reused across calls via mem::take/put-back pattern.

This eliminates per-builtin-call heap allocations for Source (Rc + String +
Vec<lines>), Span clones, and Rc<Expr> wrappers.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* perf(rvm): round 2 allocation reduction in builtins, entry points, virtual data

- Cache builtin args Vec on RegoVM (mem::take/clear/put-back pattern)
- Restructure builtins_cache as two-level map for clone-free lookup
- Use IndexMap::get_index() in execute_entry_point_by_index
- Use mutable Vec path stack in traverse_rule_tree_subobject (push/pop)
- Walk data tree and rule-result paths by reference, clone only leaf
- Use mem::replace in resume() instead of cloning ExecutionState

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* fix(rvm): address PR review feedback

- Restore cached_builtin_args on all error/early-return paths in
  execute_builtin_call to preserve allocation reuse
- Use 1-based line/col and \"<builtin>\" filename in dummy span for
  clearer diagnostics
- Restore result register before returning errors in comprehension
  mode-mismatch branches (both run-to-completion and suspendable)
- Avoid clone in resume() invalid-state error path by formatting
  debug string before moving state back

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2026-03-11 21:39:58 -05:00
committed by GitHub
parent ee3dff9a3d
commit 50c0215fdb
9 changed files with 960 additions and 179 deletions
+33 -37
View File
@@ -255,26 +255,22 @@ impl RegoVM {
};
let result_reg = comprehension_context.result_reg;
let current_result = self.get_register(result_reg)?.clone();
let mode = comprehension_context.mode.clone();
// Take ownership of the result register so Rc refcount stays at 1,
// allowing Rc::make_mut to mutate in-place instead of deep-cloning.
let mut current_result = self.take_register(result_reg)?;
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))
match (&comprehension_context.mode, &mut current_result) {
(&ComprehensionMode::Set, &mut Value::Set(ref mut set)) => {
crate::Rc::make_mut(set).insert(value_to_add);
}
(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::Array, &mut Value::Array(ref mut arr)) => {
crate::Rc::make_mut(arr).push(value_to_add);
}
(ComprehensionMode::Object, Value::Object(obj)) => {
(&ComprehensionMode::Object, &mut Value::Object(ref mut 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))
crate::Rc::make_mut(obj).insert(key, value_to_add);
} else {
self.set_register(result_reg, current_result)?;
self.comprehension_stack.push(comprehension_context);
return Err(VmError::InvalidIteration {
value: Value::String(Arc::from("Object comprehension requires key")),
@@ -283,15 +279,17 @@ impl RegoVM {
}
}
(_mode, other) => {
let offending = core::mem::replace(other, Value::Undefined);
self.set_register(result_reg, current_result)?;
self.comprehension_stack.push(comprehension_context);
return Err(VmError::InvalidIteration {
value: other,
value: offending,
pc: self.pc,
});
}
};
}
self.set_register(result_reg, updated_result)?;
self.set_register(result_reg, current_result)?;
if let Some(iter_state) = comprehension_context.iteration_state.as_mut() {
match *iter_state {
@@ -357,7 +355,6 @@ impl RegoVM {
let (
value_to_add,
key_value,
current_result,
mode,
result_reg_idx,
key_reg_idx,
@@ -384,7 +381,6 @@ impl RegoVM {
};
let result_reg_idx = context.result_reg;
let current_result = self.get_register(result_reg_idx)?.clone();
let mode = context.mode.clone();
let iteration_key = self.get_register(context.key_reg)?.clone();
let iteration_value = self.get_register(context.value_reg)?.clone();
@@ -392,7 +388,6 @@ impl RegoVM {
(
value_to_add,
key_value,
current_result,
mode,
result_reg_idx,
context.key_reg,
@@ -408,23 +403,22 @@ impl RegoVM {
}
};
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))
// Take ownership of the result register so Rc refcount stays at 1,
// allowing Rc::make_mut to mutate in-place instead of deep-cloning.
let mut current_result = self.take_register(result_reg_idx)?;
match (&mode, &mut current_result) {
(&ComprehensionMode::Set, &mut Value::Set(ref mut set)) => {
crate::Rc::make_mut(set).insert(value_to_add);
}
(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::Array, &mut Value::Array(ref mut arr)) => {
crate::Rc::make_mut(arr).push(value_to_add);
}
(ComprehensionMode::Object, Value::Object(obj)) => {
(&ComprehensionMode::Object, &mut Value::Object(ref mut 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))
crate::Rc::make_mut(obj).insert(key, value_to_add);
} else {
self.set_register(result_reg_idx, current_result)?;
return Err(VmError::InvalidIteration {
value: Value::String(Arc::from("Object comprehension requires key")),
pc: self.pc,
@@ -432,12 +426,14 @@ impl RegoVM {
}
}
(_mode, other) => {
let offending = core::mem::replace(other, Value::Undefined);
self.set_register(result_reg_idx, current_result)?;
return Err(VmError::InvalidIteration {
value: other,
value: offending,
pc: self.pc,
});
}
};
}
let (iteration_state_snapshot, body_start, comprehension_end) = {
let frame = self.execution_stack.get_mut(comprehension_index).ok_or(
@@ -489,7 +485,7 @@ impl RegoVM {
}
};
self.set_register(result_reg_idx, updated_result)?;
self.set_register(result_reg_idx, current_result)?;
if let Some(state) = iteration_state_snapshot.as_ref() {
let has_next = self.setup_next_iteration(state, key_reg_idx, value_reg_idx)?;
+6 -3
View File
@@ -434,7 +434,8 @@ impl RegoVM {
let key_value = self.get_register(key)?.clone();
let value_value = self.get_register(value)?.clone();
let mut obj_value = self.get_register(obj)?.clone();
// Take ownership so Rc refcount stays at 1 and make_mut is a no-op.
let mut obj_value = self.take_register(obj)?;
if let Ok(obj_mut) = obj_value.as_object_mut() {
obj_mut.insert(key_value, value_value);
@@ -574,7 +575,8 @@ impl RegoVM {
ArrayPush { arr, value } => {
let value_to_push = self.get_register(value)?.clone();
let mut arr_value = self.get_register(arr)?.clone();
// Take ownership so Rc refcount stays at 1 and make_mut is a no-op.
let mut arr_value = self.take_register(arr)?;
if let Ok(arr_mut) = arr_value.as_array_mut() {
arr_mut.push(value_to_push);
@@ -632,7 +634,8 @@ impl RegoVM {
SetAdd { set, value } => {
let value_to_add = self.get_register(value)?.clone();
let mut set_value = self.get_register(set)?.clone();
// Take ownership so Rc refcount stays at 1 and make_mut is a no-op.
let mut set_value = self.take_register(set)?;
if let Ok(set_mut) = set_value.as_set_mut() {
set_mut.insert(value_to_add);
+14 -25
View File
@@ -4,7 +4,6 @@ use crate::rvm::instructions::Instruction;
use crate::rvm::program::Program;
use crate::value::Value;
use alloc::string::String;
use alloc::vec::Vec;
use core::convert::TryFrom as _;
use super::dispatch::InstructionOutcome;
@@ -24,35 +23,22 @@ impl RegoVM {
}
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),
pc: self.pc,
});
}
let &(ref entry_point_name, entry_point_pc) =
entry_points
.get(index)
.ok_or(VmError::InvalidEntryPointIndex {
let (entry_point_name, entry_point_pc) = {
let (name, &pc) = self.program.entry_points.get_index(index).ok_or(
VmError::InvalidEntryPointIndex {
index,
max_index: entry_points.len().saturating_sub(1),
max_index: self.program.entry_points.len().saturating_sub(1),
pc: self.pc,
})?;
},
)?;
(name.clone(), pc)
};
if entry_point_pc >= self.program.instructions.len() {
return Err(VmError::EntryPointPcOutOfBounds {
pc: entry_point_pc,
instruction_count: self.program.instructions.len(),
entry_point: entry_point_name.clone(),
entry_point: entry_point_name,
});
}
@@ -215,15 +201,18 @@ impl RegoVM {
}
pub fn resume(&mut self, resume_value: Option<Value>) -> Result<Value> {
let (reason, mut last_result) = match self.execution_state.clone() {
let old_state = core::mem::replace(&mut self.execution_state, ExecutionState::Running);
let (reason, mut last_result) = match old_state {
ExecutionState::Suspended {
reason,
last_result,
..
} => (reason, last_result),
current_state => {
let desc = alloc::format!("{:?}", current_state);
self.execution_state = current_state;
return Err(VmError::InvalidResumeState {
state: alloc::format!("{:?}", current_state),
state: desc,
pc: self.pc,
});
}
+77 -57
View File
@@ -2,8 +2,6 @@
// Licensed under the MIT License.
use crate::builtins;
use crate::value::Value;
use alloc::string::String;
use alloc::vec::Vec;
use super::errors::{Result, VmError};
use super::execution_model::ExecutionMode;
@@ -39,24 +37,26 @@ impl RegoVM {
}
pub(super) fn execute_builtin_call(&mut self, params_index: u16) -> Result<()> {
let params = self
.program
let program = self.program.clone();
let params = program
.instruction_data
.get_builtin_call_params(params_index)
.ok_or(VmError::InvalidBuiltinCallParamsIndex {
index: params_index,
pc: self.pc,
available: self.program.instruction_data.builtin_call_params.len(),
})?;
let builtin_info = self.program.get_builtin_info(params.builtin_index).ok_or(
available: program.instruction_data.builtin_call_params.len(),
})?
.clone();
let builtin_info = program.get_builtin_info(params.builtin_index).ok_or(
VmError::InvalidBuiltinInfoIndex {
index: params.builtin_index,
pc: self.pc,
available: self.program.builtin_info_table.len(),
available: program.builtin_info_table.len(),
},
)?;
let mut args = Vec::new();
let mut args = core::mem::take(&mut self.cached_builtin_args);
args.clear();
for &arg_reg in params.arg_registers().iter() {
let arg_value = self.get_register(arg_reg)?.clone();
args.push(arg_value);
@@ -65,6 +65,7 @@ impl RegoVM {
let expected_args = builtin_info.num_args;
let actual_args = args.len();
if u16::try_from(actual_args).unwrap_or(u16::MAX) != expected_args {
self.cached_builtin_args = args;
return Err(VmError::BuiltinArgumentMismatch {
expected: expected_args,
actual: actual_args,
@@ -73,65 +74,84 @@ impl RegoVM {
}
if args.iter().any(|a| a == &Value::Undefined) {
self.cached_builtin_args = args;
self.set_register(params.dest, Value::Undefined)?;
self.memory_check()?;
return Ok(());
}
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));
// Extract everything we need from program before releasing the borrow.
let builtin_fn = match program.get_resolved_builtin(params.builtin_index) {
Some(fcn) => fcn.0,
None => {
self.cached_builtin_args = args;
return Err(VmError::BuiltinNotResolved {
name: builtin_info.name.clone(),
pc: self.pc,
});
}
};
let cache_name = builtins::must_cache(builtin_info.name.as_str());
drop(program);
let cache_name = builtins::must_cache(builtin_info.name.as_str());
if let Some(name) = cache_name {
if let Some(value) = self.builtins_cache.get(&(name, args.clone())) {
self.set_register(params.dest, value.clone())?;
return Ok(());
self.ensure_dummy_exprs(args.len())?;
let dummy_span = self.get_dummy_span()?.clone();
// Take the dummy_exprs vec out of self so we can pass it to the builtin
// while still calling &mut self methods afterwards.
let dummy_exprs = core::mem::take(&mut self.dummy_exprs);
if let Some(name) = cache_name {
if let Some(entries) = self.builtins_cache.get(name) {
for entry in entries {
if entry.0.as_slice() == args.as_slice() {
let cached = entry.1.clone();
self.dummy_exprs = dummy_exprs;
self.cached_builtin_args = args;
self.set_register(params.dest, cached)?;
self.memory_check()?;
return Ok(());
}
}
}
let result =
match (builtin_fcn.0)(&dummy_span, &dummy_exprs, &args, self.strict_builtin_errors)
{
Ok(value) => value,
Err(_) if !self.strict_builtin_errors => Value::Undefined,
Err(err) => return Err(err.into()),
};
if result == Value::Undefined {
self.set_register(params.dest, Value::Undefined)?;
} else {
self.set_register(params.dest, result.clone())?;
}
if let Some(name) = cache_name {
self.builtins_cache.insert((name, args), result);
}
self.memory_check()?;
} else {
return Err(VmError::BuiltinNotResolved {
name: builtin_info.name.clone(),
pc: self.pc,
});
}
let result = match builtin_fn(
&dummy_span,
dummy_exprs.get(..args.len()).unwrap_or(&[]),
&args,
self.strict_builtin_errors,
) {
Ok(value) => value,
Err(_) if !self.strict_builtin_errors => Value::Undefined,
Err(err) => {
self.dummy_exprs = dummy_exprs;
self.cached_builtin_args = args;
return Err(err.into());
}
};
// Put dummy_exprs back for reuse.
self.dummy_exprs = dummy_exprs;
if let Some(name) = cache_name {
// Move args into the cache. The now-empty (zero-capacity) Vec is
// stored back in cached_builtin_args; the next call will re-allocate.
// This is acceptable because cache inserts are rare (once per unique
// argument set) while the hot path (cache hit above) reuses the Vec.
let cache_args = core::mem::take(&mut args);
self.cached_builtin_args = args;
self.builtins_cache
.entry(name)
.or_default()
.push((cache_args, result.clone()));
self.set_register(params.dest, result)?;
} else {
self.cached_builtin_args = args;
self.set_register(params.dest, result)?;
}
self.memory_check()?;
Ok(())
}
}
+79 -2
View File
@@ -109,8 +109,15 @@ pub struct RegoVM {
/// Whether builtins should raise errors strictly or return undefined on failure
pub(super) strict_builtin_errors: bool,
/// Cache for builtin calls that must stay deterministic across a single evaluation
pub(super) builtins_cache: BTreeMap<(&'static str, Vec<Value>), Value>,
/// Cache for builtin calls that must stay deterministic across a single evaluation.
///
/// Two-level structure: outer BTreeMap keyed by builtin name, inner Vec of
/// (args, result) pairs scanned linearly. This avoids allocating a composite
/// key on every lookup (which a single-level BTreeMap<(name, Vec<Value>), Value>
/// would require). Linear scan is fast for the small number of entries per
/// builtin (typically <10). Can be revisited with a HashMap if `Value` gains
/// a `Hash` implementation.
pub(super) builtins_cache: BTreeMap<&'static str, Vec<(Vec<Value>, Value)>>,
/// Optional override for the execution timer configuration
pub(super) execution_timer_config: Option<ExecutionTimerConfig>,
@@ -120,6 +127,15 @@ pub struct RegoVM {
/// Elapsed wall-clock time recorded when the VM entered a suspended state
pub(super) execution_timer_elapsed_at_suspend: Option<Duration>,
/// Cached dummy span for builtin calls (avoids Source::from_contents per call)
pub(super) dummy_span: Option<crate::lexer::Span>,
/// Cached dummy expressions for builtin calls (avoids Rc<Expr> allocs per call)
pub(super) dummy_exprs: Vec<crate::ast::Ref<crate::ast::Expr>>,
/// Cached args Vec for builtin calls (avoids Vec allocation per call)
pub(super) cached_builtin_args: Vec<Value>,
}
impl Default for RegoVM {
@@ -163,6 +179,9 @@ impl RegoVM {
execution_timer_config: None,
execution_timer: ExecutionTimer::new(fallback_timer),
execution_timer_elapsed_at_suspend: None,
dummy_span: None,
dummy_exprs: Vec::new(),
cached_builtin_args: Vec::new(),
}
}
@@ -451,6 +470,24 @@ impl RegoVM {
})
}
/// Take ownership of a register value, replacing it with `Value::Undefined`.
/// This avoids bumping the Rc refcount that a clone would cause, keeping the
/// refcount at 1 so that subsequent `Rc::make_mut` calls can mutate in place.
#[inline]
#[allow(dead_code)]
pub(super) fn take_register(&mut self, index: u8) -> Result<Value> {
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,
},
)?;
Ok(core::mem::replace(slot, Value::Undefined))
}
#[inline]
#[allow(dead_code)]
pub(super) fn set_register(&mut self, index: u8, value: Value) -> Result<()> {
@@ -486,4 +523,44 @@ impl RegoVM {
pub(super) fn memory_check(&mut self) -> Result<()> {
Ok(())
}
/// Get or create the cached dummy span for builtin calls.
pub(super) fn get_dummy_span(&mut self) -> Result<&crate::lexer::Span> {
if self.dummy_span.is_none() {
let source = crate::lexer::Source::from_contents("<builtin>".into(), String::new())
.map_err(|e| VmError::Internal {
message: alloc::format!("failed to create dummy source: {e}"),
pc: self.pc,
})?;
self.dummy_span = Some(crate::lexer::Span {
source,
line: 1,
col: 1,
start: 0,
end: 0,
});
}
// SAFETY: we just ensured it's Some above
self.dummy_span.as_ref().ok_or(VmError::Internal {
message: String::from("dummy span not initialized"),
pc: self.pc,
})
}
/// Ensure the cached dummy_exprs vec has at least `count` elements.
pub(super) fn ensure_dummy_exprs(&mut self, count: usize) -> Result<()> {
if self.dummy_exprs.len() >= count {
return Ok(());
}
let span = self.get_dummy_span()?.clone();
while self.dummy_exprs.len() < count {
self.dummy_exprs
.push(crate::ast::Ref::new(crate::ast::Expr::Null {
span: span.clone(),
value: Value::Null,
eidx: 0,
}));
}
Ok(())
}
}
+37 -34
View File
@@ -153,16 +153,17 @@ impl RegoVM {
});
}
let rule_info = self
.program
// Clone the Arc (cheap atomic increment) so we can borrow &RuleInfo
// without holding an immutable borrow on self.
let program = self.program.clone();
let rule_info = program
.rule_infos
.get(rule_idx)
.ok_or(VmError::RuleInfoMissing {
index: rule_index,
pc: self.pc,
available: self.program.rule_infos.len(),
})?
.clone();
available: program.rule_infos.len(),
})?;
let is_function_rule = rule_info.function_info.is_some();
@@ -214,7 +215,7 @@ impl RegoVM {
});
let (final_result, rule_failed_due_to_inconsistency) = self
.execute_rule_definitions_common(&rule_definitions, &rule_info, function_call_params)?;
.execute_rule_definitions_common(&rule_definitions, rule_info, function_call_params)?;
self.set_register(dest, Value::Undefined)?;
@@ -230,7 +231,7 @@ impl RegoVM {
Value::Undefined
};
self.set_register(dest, result_from_rule.clone())?;
self.set_register(dest, result_from_rule)?;
if self.get_register(dest)? == &Value::Undefined && !rule_failed_due_to_inconsistency {
match call_context.rule_type {
@@ -272,7 +273,7 @@ impl RegoVM {
pc: self.pc,
available,
})?;
*entry = (true, final_value.clone());
*entry = (true, final_value);
}
Ok(())
}
@@ -301,16 +302,15 @@ impl RegoVM {
});
}
let rule_info = self
.program
let program = self.program.clone();
let rule_info = program
.rule_infos
.get(rule_idx)
.ok_or(VmError::RuleInfoMissing {
index: rule_index,
pc: self.pc,
available: self.program.rule_infos.len(),
})?
.clone();
available: program.rule_infos.len(),
})?;
let is_function_rule = rule_info.function_info.is_some();
@@ -425,7 +425,7 @@ impl RegoVM {
};
let initial_pc = self
.prepare_rule_frame_initial_pc(&mut frame_data, &rule_info)?
.prepare_rule_frame_initial_pc(&mut frame_data, rule_info)?
.ok_or(VmError::RuleFrameMissingInitialPc { pc: self.pc })?;
let frame = ExecutionFrame::new(initial_pc, FrameKind::Rule(frame_data));
@@ -639,16 +639,15 @@ impl RegoVM {
} = frame_data;
let rule_idx = usize::from(rule_index);
let rule_info = self
.program
let program = self.program.clone();
let rule_info = program
.rule_infos
.get(rule_idx)
.ok_or(VmError::RuleInfoMissing {
index: rule_index,
pc: self.pc,
available: self.program.rule_infos.len(),
})?
.clone();
available: program.rule_infos.len(),
})?;
let result_from_rule = if rule_failed_due_to_inconsistency {
Value::Undefined
@@ -760,6 +759,7 @@ impl RegoVM {
pc: self.pc,
available,
})?;
// Clone into cache; return the original below.
*entry = (true, final_value.clone());
}
@@ -778,12 +778,20 @@ impl RegoVM {
&mut self,
frame_data: &mut RuleFrameData,
) -> Result<Option<usize>> {
let rule_info = self.get_rule_info(frame_data.rule_index)?;
let program = self.program.clone();
let rule_info = program
.rule_infos
.get(usize::from(frame_data.rule_index))
.ok_or(VmError::RuleInfoMissing {
index: frame_data.rule_index,
pc: self.pc,
available: program.rule_infos.len(),
})?;
match frame_data.phase {
RuleFramePhase::ExecutingDestructuring => {
self.rule_frame_after_destructuring_success(frame_data, &rule_info)
self.rule_frame_after_destructuring_success(frame_data, rule_info)
}
RuleFramePhase::ExecutingBody => self.rule_frame_after_success(frame_data, &rule_info),
RuleFramePhase::ExecutingBody => self.rule_frame_after_success(frame_data, rule_info),
RuleFramePhase::Initializing | RuleFramePhase::Finalizing => Ok(None),
}
}
@@ -792,21 +800,16 @@ impl RegoVM {
&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 = usize::from(rule_index);
self.program
let program = self.program.clone();
let rule_info = program
.rule_infos
.get(idx)
.cloned()
.get(usize::from(frame_data.rule_index))
.ok_or(VmError::RuleInfoMissing {
index: rule_index,
index: frame_data.rule_index,
pc: self.pc,
available: self.program.rule_infos.len(),
})
available: program.rule_infos.len(),
})?;
self.rule_frame_after_failure(frame_data, rule_info)
}
pub(super) fn checked_add_one(&self, value: usize, context: &'static str) -> Result<usize> {
+32 -21
View File
@@ -21,14 +21,15 @@ impl RegoVM {
root_path.push(key_value);
}
let mut data_subobject = self.data.clone();
// Walk data tree by reference, only clone the leaf.
let mut data_ref = &self.data;
for path_component in &root_path {
data_subobject = data_subobject[path_component].clone();
data_ref = &data_ref[path_component];
}
let mut result_subobject = match data_subobject {
let mut result_subobject = match *data_ref {
Value::Undefined => Value::new_object(),
_ => data_subobject,
_ => data_ref.clone(),
};
self.traverse_rule_tree_subobject(rule_tree_subobject, &mut result_subobject, &root_path)?;
@@ -74,11 +75,12 @@ impl RegoVM {
result_subobject: &mut Value,
root_path: &[Value],
) -> Result<()> {
let mut relative_path = Vec::new();
self.traverse_rule_tree_subobject_with_path(
rule_tree_node,
result_subobject,
root_path,
&[],
&mut relative_path,
)
}
@@ -87,19 +89,18 @@ impl RegoVM {
rule_tree_node: &Value,
result_subobject: &mut Value,
root_path: &[Value],
relative_path: &[Value],
relative_path: &mut Vec<Value>,
) -> Result<()> {
match *rule_tree_node {
Value::Number(ref 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);
// Walk the evaluated cache using root_path then relative_path,
// without allocating a combined full_cache_path Vec.
let cached_result = {
let mut cache_lookup = &self.evaluated;
let mut path_exists = true;
for path_component in &full_cache_path {
for path_component in root_path.iter().chain(relative_path.iter()) {
if let Value::Object(ref map) = *cache_lookup {
if let Some(next_value) = map.get(path_component) {
cache_lookup = next_value;
@@ -153,7 +154,15 @@ impl RegoVM {
register_count,
})?;
let mut cache_path = full_cache_path.clone();
// Build cache_path from root_path + relative_path + Undefined sentinel.
let mut cache_path: Vec<Value> = Vec::with_capacity(
root_path
.len()
.saturating_add(relative_path.len())
.saturating_add(1),
);
cache_path.extend_from_slice(root_path);
cache_path.extend_from_slice(relative_path);
cache_path.push(Value::Undefined);
Self::set_nested_value_static(
&mut self.evaluated,
@@ -174,14 +183,14 @@ impl RegoVM {
}
Value::Object(ref obj) => {
for (key, value) in obj.iter() {
let mut new_relative_path = relative_path.to_vec();
new_relative_path.push(key.clone());
relative_path.push(key.clone());
self.traverse_rule_tree_subobject_with_path(
value,
result_subobject,
root_path,
&new_relative_path,
relative_path,
)?;
relative_path.pop();
}
}
_ => {}
@@ -232,15 +241,16 @@ impl RegoVM {
self.execute_call_rule_common(params.dest, rule_index, None)?;
if components_consumed < params.path_components.len() {
let mut rule_result = self.get_register(params.dest)?.clone();
// Walk remaining path by reference, clone only the leaf.
let mut ref_val = self.get_register(params.dest)?;
for component in params.path_components.iter().skip(components_consumed) {
let key_value = self.literal_or_register_value(component)?;
rule_result = rule_result[&key_value].clone();
ref_val = &ref_val[&key_value];
}
self.set_register(params.dest, rule_result)?;
let leaf = ref_val.clone();
self.set_register(params.dest, leaf)?;
}
} else {
return Err(VmError::InvalidRuleIndex {
@@ -252,14 +262,15 @@ impl RegoVM {
Value::Undefined | Value::Object(_)
if components_consumed != params.path_components.len() =>
{
let mut result = self.data.clone();
// Walk data tree by reference, clone only the leaf.
let mut data_ref = &self.data;
for component in &params.path_components {
let key_value = self.literal_or_register_value(component)?;
result = result[&key_value].clone();
data_ref = &data_ref[&key_value];
}
let result = data_ref.clone();
self.set_register(params.dest, result)?;
}
Value::Object(_) => {