diff --git a/src/engine.rs b/src/engine.rs index bcc06b83..c9910ab5 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -488,6 +488,18 @@ impl> From for Target<'_> { } } +/// An entry in a function resolution cache. +#[derive(Debug, Clone)] +pub struct FnResolutionCacheEntry { + /// Function. + pub func: CallableFunction, + /// Optional source. + pub source: Option, +} + +/// A function resolution cache. +pub type FnResolutionCache = HashMap, StraightHasherBuilder>; + /// _(INTERNALS)_ A type that holds all the current states of the [`Engine`]. /// Exported under the `internals` feature only. /// @@ -512,10 +524,10 @@ pub struct State { /// Embedded module resolver. #[cfg(not(feature = "no_module"))] pub resolver: Option>, - /// Functions resolution cache. - fn_resolution_caches: StaticVec< - HashMap)>, StraightHasherBuilder>, - >, + /// function resolution cache. + fn_resolution_caches: StaticVec, + /// Free resolution caches. + fn_resolution_caches_free_list: Vec, } impl State { @@ -524,25 +536,32 @@ impl State { pub fn is_global(&self) -> bool { self.scope_level == 0 } - /// Get a mutable reference to the current functions resolution cache. - pub fn fn_resolution_cache_mut( - &mut self, - ) -> &mut HashMap)>, StraightHasherBuilder> - { + /// Get a mutable reference to the current function resolution cache. + pub fn fn_resolution_cache_mut(&mut self) -> &mut FnResolutionCache { 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. + /// Push an empty function resolution cache onto the stack and make it current. #[allow(dead_code)] pub fn push_fn_resolution_cache(&mut self) { - self.fn_resolution_caches.push(Default::default()); + self.fn_resolution_caches.push( + self.fn_resolution_caches_free_list + .pop() + .unwrap_or_default(), + ); } - /// Remove the current functions resolution cache and make the last one current. + /// Remove the current function resolution cache from the stack and make the last one current. + /// + /// # Panics + /// + /// Panics if there are no more function resolution cache in the stack. pub fn pop_fn_resolution_cache(&mut self) { - self.fn_resolution_caches.pop(); + let mut cache = self.fn_resolution_caches.pop().unwrap(); + cache.clear(); + self.fn_resolution_caches_free_list.push(cache); } } diff --git a/src/fn_call.rs b/src/fn_call.rs index f8a0d38f..c2b594d3 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -2,8 +2,8 @@ use crate::ast::FnHash; use crate::engine::{ - Imports, State, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, - KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF, + FnResolutionCacheEntry, Imports, State, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, + KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF, MAX_DYNAMIC_PARAMETERS, }; use crate::fn_builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn}; @@ -186,7 +186,7 @@ impl Engine { args: Option<&mut FnCallArgs>, allow_dynamic: bool, is_op_assignment: bool, - ) -> &'s Option<(CallableFunction, Option)> { + ) -> &'s Option { let mut hash = if let Some(ref args) = args { let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id())); combine_hashes(hash_script, hash_params) @@ -211,28 +211,43 @@ impl Engine { .iter() .find_map(|m| { m.get_fn(hash, false) - .map(|f| (f.clone(), m.id_raw().cloned())) + .cloned() + .map(|func| FnResolutionCacheEntry { + func, + source: m.id_raw().cloned(), + }) }) .or_else(|| { self.global_namespace .get_fn(hash, false) .cloned() - .map(|f| (f, None)) + .map(|func| FnResolutionCacheEntry { func, source: None }) }) .or_else(|| { self.global_modules.iter().find_map(|m| { m.get_fn(hash, false) - .map(|f| (f.clone(), m.id_raw().cloned())) + .cloned() + .map(|func| FnResolutionCacheEntry { + func, + source: m.id_raw().cloned(), + }) }) }) .or_else(|| { mods.get_fn(hash) - .map(|(f, source)| (f.clone(), source.cloned())) + .map(|(func, source)| FnResolutionCacheEntry { + func: func.clone(), + source: source.cloned(), + }) }) .or_else(|| { self.global_sub_modules.values().find_map(|m| { - m.get_qualified_fn(hash) - .map(|f| (f.clone(), m.id_raw().cloned())) + m.get_qualified_fn(hash).cloned().map(|func| { + FnResolutionCacheEntry { + func, + source: m.id_raw().cloned(), + } + }) }) }); @@ -249,10 +264,12 @@ impl Engine { if let Some(f) = get_builtin_binary_op_fn(fn_name, &args[0], &args[1]) { - Some(( - CallableFunction::from_method(Box::new(f) as Box), - None, - )) + Some(FnResolutionCacheEntry { + func: CallableFunction::from_method( + Box::new(f) as Box + ), + source: None, + }) } else { None } @@ -262,10 +279,12 @@ impl Engine { if let Some(f) = get_builtin_op_assignment_fn(fn_name, *first, second[0]) { - Some(( - CallableFunction::from_method(Box::new(f) as Box), - None, - )) + Some(FnResolutionCacheEntry { + func: CallableFunction::from_method( + Box::new(f) as Box + ), + source: None, + }) } else { None } @@ -318,7 +337,7 @@ impl Engine { ) -> Result<(Dynamic, bool), Box> { self.inc_operations(state, pos)?; - let source = state.source.clone(); + let state_source = state.source.clone(); // Check if function access already in the cache let func = self.resolve_function( @@ -332,7 +351,7 @@ impl Engine { is_op_assignment, ); - if let Some((func, src)) = func { + if let Some(FnResolutionCacheEntry { func, source }) = func { assert!(func.is_native()); // Calling pure function but the first argument is a reference? @@ -343,7 +362,10 @@ impl Engine { } // Run external function - let source = src.as_ref().or_else(|| source.as_ref()).map(|s| s.as_str()); + let source = source + .as_ref() + .or_else(|| state_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) @@ -719,10 +741,9 @@ impl Engine { }; #[cfg(not(feature = "no_function"))] - if let Some((func, source)) = hash_script.and_then(|hash| { + if let Some(FnResolutionCacheEntry { func, source }) = hash_script.and_then(|hash| { self.resolve_function(mods, state, lib, fn_name, hash, None, false, false) - .as_ref() - .map(|(f, s)| (f.clone(), s.clone())) + .clone() }) { // Script function call assert!(func.is_script());