diff --git a/src/engine.rs b/src/engine.rs index ee635ed8..e7544d56 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -2070,7 +2070,7 @@ impl Engine { } else { unsafe_cast_var_name_to_lifetime(&var_def.name).into() }; - scope.push_dynamic_value(var_name, entry_type, val, false); + scope.push_dynamic_value(var_name, entry_type, val); Ok(Default::default()) } diff --git a/src/fn_call.rs b/src/fn_call.rs index 0ae48900..f5b0a190 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -36,10 +36,6 @@ use crate::engine::{Map, Target, FN_GET, FN_SET}; #[cfg(not(feature = "no_closure"))] use crate::engine::KEYWORD_IS_SHARED; -#[cfg(not(feature = "no_closure"))] -#[cfg(not(feature = "no_function"))] -use crate::scope::Entry as ScopeEntry; - use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, @@ -553,22 +549,14 @@ impl Engine { if let Some(captured) = _capture_scope { captured .into_iter() - .filter(|ScopeEntry { name, .. }| { - func.externals.contains(name.as_ref()) - }) - .for_each( - |ScopeEntry { - name, typ, value, .. - }| { - // Consume the scope values. - match typ { - ScopeEntryType::Normal => scope.push(name, value), - ScopeEntryType::Constant => { - scope.push_constant(name, value) - } - }; - }, - ); + .filter(|(name, _, _, _)| func.externals.contains(name.as_ref())) + .for_each(|(name, typ, value, _)| { + // Consume the scope values. + match typ { + ScopeEntryType::Normal => scope.push(name, value), + ScopeEntryType::Constant => scope.push_constant(name, value), + }; + }); } let result = if _is_method { diff --git a/src/module/mod.rs b/src/module/mod.rs index 79dbe625..4766ebce 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -16,7 +16,7 @@ use crate::{ast::ScriptFnDef, fn_native::Shared}; use crate::{ ast::AST, engine::{Engine, Imports}, - scope::{Entry as ScopeEntry, Scope}, + scope::Scope, }; #[cfg(not(feature = "no_index"))] @@ -1348,14 +1348,12 @@ impl Module { // Create new module let mut module = Module::new(); - scope - .into_iter() - .for_each(|ScopeEntry { value, alias, .. }| { - // Variables with an alias left in the scope become module variables - if let Some(alias) = alias { - module.variables.insert(*alias, value); - } - }); + scope.into_iter().for_each(|(_, _, value, alias)| { + // Variables with an alias left in the scope become module variables + if let Some(alias) = alias { + module.variables.insert(alias, value); + } + }); // Modules left in the scope become sub-modules mods.into_iter().for_each(|(alias, m)| { diff --git a/src/optimize.rs b/src/optimize.rs index e482c805..1e6d1eaf 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -9,7 +9,7 @@ use crate::engine::{ use crate::fn_call::run_builtin_binary_op; use crate::module::Module; use crate::parser::map_dynamic_to_expr; -use crate::scope::{Entry as ScopeEntry, Scope}; +use crate::scope::Scope; use crate::token::{is_valid_identifier, Position}; use crate::{calc_native_fn_hash, StaticVec}; @@ -721,24 +721,15 @@ fn optimize( // Set up the state let mut state = State::new(engine, lib, level); - // Add constants from the scope into the state + // Add constants from the scope that can be made into a literal into the state scope - .to_iter() - // Get all the constants that can be made into a constant literal. - .filter(|ScopeEntry { typ, .. }| typ.is_constant()) - .for_each( - |ScopeEntry { - name, expr, value, .. - }| { - if let Some(val) = expr - .as_ref() - .map(|expr| expr.as_ref().clone()) - .or_else(|| map_dynamic_to_expr(value.clone(), Position::none())) - { - state.push_constant(name.as_ref(), val); - } - }, - ); + .iter() + .filter(|(_, typ, _)| *typ) + .for_each(|(name, _, value)| { + if let Some(val) = map_dynamic_to_expr(value.clone(), Position::none()) { + state.push_constant(name, val); + } + }); let orig_constants_len = state.constants.len(); diff --git a/src/scope.rs b/src/scope.rs index 13442a3b..77e45a87 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,11 +1,8 @@ //! Module that defines the `Scope` type representing a function call-stack scope. -use crate::ast::Expr; use crate::dynamic::{Dynamic, Variant}; -use crate::parser::map_dynamic_to_expr; -use crate::token::Position; -use crate::stdlib::{borrow::Cow, boxed::Box, iter, string::String, vec::Vec}; +use crate::stdlib::{borrow::Cow, iter, string::String, vec::Vec}; /// Type of an entry in the Scope. #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] @@ -27,21 +24,6 @@ impl EntryType { } } -/// An entry in the Scope. -#[derive(Debug, Clone)] -pub struct Entry<'a> { - /// Name of the entry. - pub name: Cow<'a, str>, - /// Type of the entry. - pub typ: EntryType, - /// Current value of the entry. - pub value: Dynamic, - /// Alias of the entry. - pub alias: Option>, - /// A constant expression if the initial value matches one of the recognized types. - pub expr: Option>, -} - /// Type containing information about the current scope. /// Useful for keeping state between `Engine` evaluation runs. /// @@ -72,8 +54,25 @@ pub struct Entry<'a> { /// /// When searching for entries, newly-added entries are found before similarly-named but older entries, /// allowing for automatic _shadowing_. +// +// # Implementation Notes +// +// `Scope` is implemented as three `Vec`'s of exactly the same length. Variables data (name, type, etc.) +// is manually split into three equal-length arrays. That's because variable names take up the most space, +// with `Cow` being four words long, but in the vast majority of cases the name is NOT used to look up +// a variable's value. Variable lookup is usually via direct index, by-passing the name altogether. +// +// Since `Dynamic` is reasonably small, packing it tightly improves cache locality when variables are accessed. +// The variable type is packed separately into another array because it is even smaller. #[derive(Debug, Clone, Default)] -pub struct Scope<'a>(Vec>); +pub struct Scope<'a> { + /// (Name, alias) of the entry. + names: Vec<(Cow<'a, str>, Option)>, + /// Type of the entry. + types: Vec, + /// Current value of the entry. + values: Vec, +} impl<'a> Scope<'a> { /// Create a new Scope. @@ -114,7 +113,9 @@ impl<'a> Scope<'a> { /// ``` #[inline(always)] pub fn clear(&mut self) -> &mut Self { - self.0.clear(); + self.names.clear(); + self.types.clear(); + self.values.clear(); self } @@ -133,7 +134,7 @@ impl<'a> Scope<'a> { /// ``` #[inline(always)] pub fn len(&self) -> usize { - self.0.len() + self.values.len() } /// Is the Scope empty? @@ -151,7 +152,7 @@ impl<'a> Scope<'a> { /// ``` #[inline(always)] pub fn is_empty(&self) -> bool { - self.0.len() == 0 + self.values.len() == 0 } /// Add (push) a new entry to the Scope. @@ -172,7 +173,7 @@ impl<'a> Scope<'a> { name: K, value: T, ) -> &mut Self { - self.push_dynamic_value(name, EntryType::Normal, Dynamic::from(value), false) + self.push_dynamic_value(name, EntryType::Normal, Dynamic::from(value)) } /// Add (push) a new `Dynamic` entry to the Scope. @@ -189,7 +190,7 @@ impl<'a> Scope<'a> { /// ``` #[inline(always)] pub fn push_dynamic>>(&mut self, name: K, value: Dynamic) -> &mut Self { - self.push_dynamic_value(name, EntryType::Normal, value, false) + self.push_dynamic_value(name, EntryType::Normal, value) } /// Add (push) a new constant to the Scope. @@ -216,7 +217,7 @@ impl<'a> Scope<'a> { name: K, value: T, ) -> &mut Self { - self.push_dynamic_value(name, EntryType::Constant, Dynamic::from(value), true) + self.push_dynamic_value(name, EntryType::Constant, Dynamic::from(value)) } /// Add (push) a new constant with a `Dynamic` value to the Scope. @@ -244,7 +245,7 @@ impl<'a> Scope<'a> { name: K, value: Dynamic, ) -> &mut Self { - self.push_dynamic_value(name, EntryType::Constant, value, true) + self.push_dynamic_value(name, EntryType::Constant, value) } /// Add (push) a new entry with a `Dynamic` value to the Scope. @@ -254,22 +255,10 @@ impl<'a> Scope<'a> { name: K, entry_type: EntryType, value: Dynamic, - map_expr: bool, ) -> &mut Self { - let expr = if map_expr { - map_dynamic_to_expr(value.clone(), Position::none()).map(Box::new) - } else { - None - }; - - self.0.push(Entry { - name: name.into(), - typ: entry_type, - alias: None, - value: value.into(), - expr, - }); - + self.names.push((name.into(), None)); + self.types.push(entry_type); + self.values.push(value.into()); self } @@ -301,7 +290,9 @@ impl<'a> Scope<'a> { /// ``` #[inline(always)] pub fn rewind(&mut self, size: usize) -> &mut Self { - self.0.truncate(size); + self.names.truncate(size); + self.types.truncate(size); + self.values.truncate(size); self } @@ -320,37 +311,28 @@ impl<'a> Scope<'a> { /// ``` #[inline(always)] pub fn contains(&self, name: &str) -> bool { - self.0 + self.names .iter() .rev() // Always search a Scope in reverse order - .any(|Entry { name: key, .. }| name == key) + .any(|(key, _)| name == key.as_ref()) } /// Find an entry in the Scope, starting from the last. #[inline(always)] pub(crate) fn get_index(&self, name: &str) -> Option<(usize, EntryType)> { - self.0 + self.names .iter() .enumerate() .rev() // Always search a Scope in reverse order - .find_map(|(index, Entry { name: key, typ, .. })| { - if name == key { - Some((index, *typ)) + .find_map(|(index, (key, _))| { + if name == key.as_ref() { + Some((index, self.types[index])) } else { None } }) } - /// Get an entry in the Scope, starting from the last. - #[inline(always)] - pub(crate) fn get_entry(&self, name: &str) -> Option<&Entry> { - self.0 - .iter() - .rev() - .find(|Entry { name: key, .. }| name == key) - } - /// Get the value of an entry in the Scope, starting from the last. /// /// # Example @@ -365,8 +347,12 @@ impl<'a> Scope<'a> { /// ``` #[inline(always)] pub fn get_value(&self, name: &str) -> Option { - self.get_entry(name) - .and_then(|Entry { value, .. }| value.flatten_clone().try_cast()) + self.names + .iter() + .enumerate() + .rev() + .find(|(_, (key, _))| name == key.as_ref()) + .and_then(|(index, _)| self.values[index].flatten_clone().try_cast()) } /// Update the value of the named entry. @@ -398,7 +384,7 @@ impl<'a> Scope<'a> { } Some((_, EntryType::Constant)) => panic!("variable {} is constant", name), Some((index, EntryType::Normal)) => { - self.0.get_mut(index).unwrap().value = Dynamic::from(value); + *self.values.get_mut(index).unwrap() = Dynamic::from(value); } } self @@ -407,16 +393,18 @@ impl<'a> Scope<'a> { /// Get a mutable reference to an entry in the Scope. #[inline(always)] pub(crate) fn get_mut(&mut self, index: usize) -> (&mut Dynamic, EntryType) { - let entry = self.0.get_mut(index).expect("invalid index in Scope"); - (&mut entry.value, entry.typ) + ( + self.values.get_mut(index).expect("invalid index in Scope"), + self.types[index], + ) } /// Update the access type of an entry in the Scope. #[cfg(not(feature = "no_module"))] #[inline(always)] pub(crate) fn set_entry_alias(&mut self, index: usize, alias: String) -> &mut Self { - let entry = self.0.get_mut(index).expect("invalid index in Scope"); - entry.alias = Some(Box::new(alias)); + let entry = self.names.get_mut(index).expect("invalid index in Scope"); + entry.1 = Some(alias); self } @@ -424,34 +412,36 @@ impl<'a> Scope<'a> { /// Shadowed variables are omitted in the copy. #[inline] pub(crate) fn clone_visible(&self) -> Self { - let mut entries: Vec = Default::default(); + let mut entries: Self = Default::default(); - self.0.iter().rev().for_each(|entry| { - if entries - .iter() - .find(|Entry { name, .. }| &entry.name == name) - .is_none() - { - entries.push(entry.clone()); - } - }); + self.names + .iter() + .enumerate() + .rev() + .for_each(|(index, (name, alias))| { + if !entries.names.iter().any(|(key, _)| key == name) { + entries.names.push((name.clone(), alias.clone())); + entries.types.push(self.types[index]); + entries.values.push(self.values[index].clone()); + } + }); - Self(entries) + entries } /// Get an iterator to entries in the Scope. #[inline(always)] - pub(crate) fn into_iter(self) -> impl Iterator> { - self.0.into_iter() - } - - /// Get an iterator to entries in the Scope in reverse order. - #[inline(always)] - pub(crate) fn to_iter(&self) -> impl Iterator { - self.0.iter().rev() // Always search a Scope in reverse order + pub(crate) fn into_iter( + self, + ) -> impl Iterator, EntryType, Dynamic, Option)> { + self.names + .into_iter() + .zip(self.types.into_iter().zip(self.values.into_iter())) + .map(|((name, alias), (typ, value))| (name, typ, value, alias)) } /// Get an iterator to entries in the Scope. + /// Shared values are flatten-cloned. /// /// # Example /// @@ -484,25 +474,21 @@ impl<'a> Scope<'a> { /// Get an iterator to entries in the Scope. /// Shared values are not expanded. #[inline(always)] - pub fn iter_raw(&self) -> impl Iterator { - self.0.iter().map( - |Entry { - name, typ, value, .. - }| { (name.as_ref(), typ.is_constant(), value) }, - ) + pub fn iter_raw<'x: 'a>(&'x self) -> impl Iterator + 'x { + self.names + .iter() + .zip(self.types.iter().zip(self.values.iter())) + .map(|((name, _), (typ, value))| (name.as_ref(), typ.is_constant(), value)) } } impl<'a, K: Into>> iter::Extend<(K, EntryType, Dynamic)> for Scope<'a> { #[inline(always)] fn extend>(&mut self, iter: T) { - self.0 - .extend(iter.into_iter().map(|(name, typ, value)| Entry { - name: name.into(), - typ, - alias: None, - value: value.into(), - expr: None, - })); + iter.into_iter().for_each(|(name, typ, value)| { + self.names.push((name.into(), None)); + self.types.push(typ); + self.values.push(value); + }); } }