Encapsulate functions resolution cache into State API.

This commit is contained in:
Stephen Chung 2021-02-07 17:56:29 +08:00
parent a76bed2f46
commit 58528db45c
3 changed files with 72 additions and 66 deletions

View File

@ -519,8 +519,8 @@ pub struct State {
/// Embedded module resolver.
#[cfg(not(feature = "no_module"))]
pub resolver: Option<Shared<crate::module::resolvers::StaticModuleResolver>>,
/// Cached lookup values for function hashes.
pub functions_caches: StaticVec<
/// Functions resolution cache.
fn_resolution_caches: StaticVec<
HashMap<
NonZeroU64,
Option<(CallableFunction, Option<ImmutableString>)>,
@ -535,6 +535,40 @@ impl State {
pub fn is_global(&self) -> bool {
self.scope_level == 0
}
/// Get the current functions resolution cache.
pub fn fn_resolution_cache(
&self,
) -> Option<
&HashMap<
NonZeroU64,
Option<(CallableFunction, Option<ImmutableString>)>,
StraightHasherBuilder,
>,
> {
self.fn_resolution_caches.last()
}
/// Get a mutable reference to the current functions resolution cache.
pub fn fn_resolution_cache_mut(
&mut self,
) -> &mut HashMap<
NonZeroU64,
Option<(CallableFunction, Option<ImmutableString>)>,
StraightHasherBuilder,
> {
if self.fn_resolution_caches.is_empty() {
self.fn_resolution_caches
.push(HashMap::with_capacity_and_hasher(16, StraightHasherBuilder));
}
self.fn_resolution_caches.last_mut().unwrap()
}
/// Push an empty functions resolution cache onto the stack and make it current.
pub fn push_fn_resolution_cache(&mut self) {
self.fn_resolution_caches.push(Default::default());
}
/// Remove the current functions resolution cache and make the last one current.
pub fn pop_fn_resolution_cache(&mut self) {
self.fn_resolution_caches.pop();
}
}
/// _(INTERNALS)_ A type containing all the limits imposed by the [`Engine`].
@ -1874,9 +1908,11 @@ impl Engine {
Stmt::Import(_, _, _) => {
// When imports list is modified, clear the functions lookup cache
if _has_imports {
state.functions_caches.last_mut().map(|c| c.clear());
state.fn_resolution_caches.last_mut().map(|c| c.clear());
} else if restore {
state.functions_caches.push(Default::default());
state
.fn_resolution_caches
.push(HashMap::with_capacity_and_hasher(16, StraightHasherBuilder));
_has_imports = true;
}
}
@ -1890,7 +1926,7 @@ impl Engine {
scope.rewind(prev_scope_len);
if _has_imports {
// If imports list is modified, pop the functions lookup cache
state.functions_caches.pop();
state.fn_resolution_caches.pop();
}
mods.truncate(prev_mods_len);
state.scope_level -= 1;

View File

@ -1527,15 +1527,16 @@ impl Engine {
ast: &'a AST,
level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> {
let state = &mut State {
source: ast.clone_source(),
let mut state: State = Default::default();
state.source = ast.clone_source();
#[cfg(not(feature = "no_module"))]
resolver: ast.resolver(),
..Default::default()
};
{
state.resolver = ast.resolver();
}
let statements = ast.statements();
let lib = &[ast.lib()];
self.eval_global_statements(scope, mods, state, statements, lib, level)
self.eval_global_statements(scope, mods, &mut state, statements, lib, level)
}
/// Evaluate a file, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors.
@ -1598,15 +1599,15 @@ impl Engine {
ast: &AST,
) -> Result<(), Box<EvalAltResult>> {
let mods = &mut (&self.global_sub_modules).into();
let state = &mut State {
source: ast.clone_source(),
let mut state: State = Default::default();
state.source = ast.clone_source();
#[cfg(not(feature = "no_module"))]
resolver: ast.resolver(),
..Default::default()
};
{
state.resolver = ast.resolver();
}
let statements = ast.statements();
let lib = &[ast.lib()];
self.eval_global_statements(scope, mods, state, statements, lib, 0)?;
self.eval_global_statements(scope, mods, &mut state, statements, lib, 0)?;
Ok(())
}
/// Call a script function defined in an [`AST`] with multiple arguments.

View File

@ -175,15 +175,11 @@ impl Engine {
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
self.inc_operations(state, pos)?;
// Check if function access already in the cache
if state.functions_caches.is_empty() {
state.functions_caches.push(Default::default());
}
let source = state.source.clone();
// Check if function access already in the cache
let func = &*state
.functions_caches
.last_mut()
.unwrap()
.fn_resolution_cache_mut()
.entry(hash_fn)
.or_insert_with(|| {
// Search for the native function
@ -208,7 +204,7 @@ impl Engine {
})
});
if let Some((func, source)) = func {
if let Some((func, src)) = func {
assert!(func.is_native());
// Calling pure function but the first argument is a reference?
@ -216,12 +212,7 @@ impl Engine {
backup.change_first_arg_to_copy(is_ref && func.is_pure(), args);
// Run external function
let source = if source.is_none() {
state.source.as_ref()
} else {
source.as_ref()
}
.map(|s| s.as_str());
let source = src.as_ref().or_else(|| source.as_ref()).map(|s| s.as_str());
let result = if func.is_plugin_fn() {
func.get_plugin_fn()
.call((self, fn_name, source, mods, lib).into(), args)
@ -405,7 +396,7 @@ impl Engine {
let unified_lib = if let Some(ref env_lib) = fn_def.lib {
unified = true;
state.functions_caches.push(Default::default());
state.push_fn_resolution_cache();
lib_merged = Default::default();
lib_merged.push(env_lib.as_ref());
lib_merged.extend(lib.iter().cloned());
@ -477,7 +468,7 @@ impl Engine {
state.scope_level = orig_scope_level;
if unified {
state.functions_caches.pop();
state.pop_fn_resolution_cache();
}
result
@ -515,13 +506,13 @@ impl Engine {
// Check if it is already in the cache
if let Some(state) = state.as_mut() {
if let Some(hash) = hash_script {
match state.functions_caches.last().map_or(None, |c| c.get(&hash)) {
match state.fn_resolution_cache().map_or(None, |c| c.get(&hash)) {
Some(v) => return v.is_some(),
None => (),
}
}
if let Some(hash) = hash_fn {
match state.functions_caches.last().map_or(None, |c| c.get(&hash)) {
match state.fn_resolution_cache().map_or(None, |c| c.get(&hash)) {
Some(v) => return v.is_some(),
None => (),
}
@ -545,24 +536,10 @@ impl Engine {
if !r {
if let Some(state) = state.as_mut() {
if let Some(hash) = hash_script {
if state.functions_caches.is_empty() {
state.functions_caches.push(Default::default());
}
state
.functions_caches
.last_mut()
.unwrap()
.insert(hash, None);
state.fn_resolution_cache_mut().insert(hash, None);
}
if let Some(hash) = hash_fn {
if state.functions_caches.is_empty() {
state.functions_caches.push(Default::default());
}
state
.functions_caches
.last_mut()
.unwrap()
.insert(hash, None);
state.fn_resolution_cache_mut().insert(hash, None);
}
}
}
@ -653,14 +630,8 @@ impl Engine {
let hash_script = hash_script.unwrap();
// Check if function access already in the cache
if state.functions_caches.is_empty() {
state.functions_caches.push(Default::default());
}
let (func, source) = state
.functions_caches
.last_mut()
.unwrap()
.fn_resolution_cache_mut()
.entry(hash_script)
.or_insert_with(|| {
lib.iter()
@ -822,14 +793,12 @@ impl Engine {
}
// Evaluate the AST
let new_state = &mut State {
source: state.source.clone(),
operations: state.operations,
..Default::default()
};
let mut new_state: State = Default::default();
new_state.source = state.source.clone();
new_state.operations = state.operations;
let result =
self.eval_global_statements(scope, mods, new_state, ast.statements(), lib, level);
self.eval_global_statements(scope, mods, &mut new_state, ast.statements(), lib, level);
state.operations = new_state.operations;
result