Optimize Scope.

This commit is contained in:
Stephen Chung 2020-11-01 22:46:46 +08:00
parent 3485f9b00b
commit c55fc5a9a5
5 changed files with 109 additions and 146 deletions

View File

@ -2070,7 +2070,7 @@ impl Engine {
} else { } else {
unsafe_cast_var_name_to_lifetime(&var_def.name).into() 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()) Ok(Default::default())
} }

View File

@ -36,10 +36,6 @@ use crate::engine::{Map, Target, FN_GET, FN_SET};
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
use crate::engine::KEYWORD_IS_SHARED; 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::{ use crate::stdlib::{
any::{type_name, TypeId}, any::{type_name, TypeId},
boxed::Box, boxed::Box,
@ -553,22 +549,14 @@ impl Engine {
if let Some(captured) = _capture_scope { if let Some(captured) = _capture_scope {
captured captured
.into_iter() .into_iter()
.filter(|ScopeEntry { name, .. }| { .filter(|(name, _, _, _)| func.externals.contains(name.as_ref()))
func.externals.contains(name.as_ref()) .for_each(|(name, typ, value, _)| {
})
.for_each(
|ScopeEntry {
name, typ, value, ..
}| {
// Consume the scope values. // Consume the scope values.
match typ { match typ {
ScopeEntryType::Normal => scope.push(name, value), ScopeEntryType::Normal => scope.push(name, value),
ScopeEntryType::Constant => { ScopeEntryType::Constant => scope.push_constant(name, value),
scope.push_constant(name, value)
}
}; };
}, });
);
} }
let result = if _is_method { let result = if _is_method {

View File

@ -16,7 +16,7 @@ use crate::{ast::ScriptFnDef, fn_native::Shared};
use crate::{ use crate::{
ast::AST, ast::AST,
engine::{Engine, Imports}, engine::{Engine, Imports},
scope::{Entry as ScopeEntry, Scope}, scope::Scope,
}; };
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -1348,12 +1348,10 @@ impl Module {
// Create new module // Create new module
let mut module = Module::new(); let mut module = Module::new();
scope scope.into_iter().for_each(|(_, _, value, alias)| {
.into_iter()
.for_each(|ScopeEntry { value, alias, .. }| {
// Variables with an alias left in the scope become module variables // Variables with an alias left in the scope become module variables
if let Some(alias) = alias { if let Some(alias) = alias {
module.variables.insert(*alias, value); module.variables.insert(alias, value);
} }
}); });

View File

@ -9,7 +9,7 @@ use crate::engine::{
use crate::fn_call::run_builtin_binary_op; use crate::fn_call::run_builtin_binary_op;
use crate::module::Module; use crate::module::Module;
use crate::parser::map_dynamic_to_expr; 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::token::{is_valid_identifier, Position};
use crate::{calc_native_fn_hash, StaticVec}; use crate::{calc_native_fn_hash, StaticVec};
@ -721,24 +721,15 @@ fn optimize(
// Set up the state // Set up the state
let mut state = State::new(engine, lib, level); 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 scope
.to_iter() .iter()
// Get all the constants that can be made into a constant literal. .filter(|(_, typ, _)| *typ)
.filter(|ScopeEntry { typ, .. }| typ.is_constant()) .for_each(|(name, _, value)| {
.for_each( if let Some(val) = map_dynamic_to_expr(value.clone(), Position::none()) {
|ScopeEntry { state.push_constant(name, val);
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);
} }
}, });
);
let orig_constants_len = state.constants.len(); let orig_constants_len = state.constants.len();

View File

@ -1,11 +1,8 @@
//! Module that defines the `Scope` type representing a function call-stack scope. //! Module that defines the `Scope` type representing a function call-stack scope.
use crate::ast::Expr;
use crate::dynamic::{Dynamic, Variant}; 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. /// Type of an entry in the Scope.
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] #[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<Box<String>>,
/// A constant expression if the initial value matches one of the recognized types.
pub expr: Option<Box<Expr>>,
}
/// Type containing information about the current scope. /// Type containing information about the current scope.
/// Useful for keeping state between `Engine` evaluation runs. /// 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, /// When searching for entries, newly-added entries are found before similarly-named but older entries,
/// allowing for automatic _shadowing_. /// 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<str>` 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)] #[derive(Debug, Clone, Default)]
pub struct Scope<'a>(Vec<Entry<'a>>); pub struct Scope<'a> {
/// (Name, alias) of the entry.
names: Vec<(Cow<'a, str>, Option<String>)>,
/// Type of the entry.
types: Vec<EntryType>,
/// Current value of the entry.
values: Vec<Dynamic>,
}
impl<'a> Scope<'a> { impl<'a> Scope<'a> {
/// Create a new Scope. /// Create a new Scope.
@ -114,7 +113,9 @@ impl<'a> Scope<'a> {
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn clear(&mut self) -> &mut Self { pub fn clear(&mut self) -> &mut Self {
self.0.clear(); self.names.clear();
self.types.clear();
self.values.clear();
self self
} }
@ -133,7 +134,7 @@ impl<'a> Scope<'a> {
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.0.len() self.values.len()
} }
/// Is the Scope empty? /// Is the Scope empty?
@ -151,7 +152,7 @@ impl<'a> Scope<'a> {
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.0.len() == 0 self.values.len() == 0
} }
/// Add (push) a new entry to the Scope. /// Add (push) a new entry to the Scope.
@ -172,7 +173,7 @@ impl<'a> Scope<'a> {
name: K, name: K,
value: T, value: T,
) -> &mut Self { ) -> &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. /// Add (push) a new `Dynamic` entry to the Scope.
@ -189,7 +190,7 @@ impl<'a> Scope<'a> {
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn push_dynamic<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Dynamic) -> &mut Self { pub fn push_dynamic<K: Into<Cow<'a, str>>>(&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. /// Add (push) a new constant to the Scope.
@ -216,7 +217,7 @@ impl<'a> Scope<'a> {
name: K, name: K,
value: T, value: T,
) -> &mut Self { ) -> &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. /// Add (push) a new constant with a `Dynamic` value to the Scope.
@ -244,7 +245,7 @@ impl<'a> Scope<'a> {
name: K, name: K,
value: Dynamic, value: Dynamic,
) -> &mut Self { ) -> &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. /// Add (push) a new entry with a `Dynamic` value to the Scope.
@ -254,22 +255,10 @@ impl<'a> Scope<'a> {
name: K, name: K,
entry_type: EntryType, entry_type: EntryType,
value: Dynamic, value: Dynamic,
map_expr: bool,
) -> &mut Self { ) -> &mut Self {
let expr = if map_expr { self.names.push((name.into(), None));
map_dynamic_to_expr(value.clone(), Position::none()).map(Box::new) self.types.push(entry_type);
} else { self.values.push(value.into());
None
};
self.0.push(Entry {
name: name.into(),
typ: entry_type,
alias: None,
value: value.into(),
expr,
});
self self
} }
@ -301,7 +290,9 @@ impl<'a> Scope<'a> {
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn rewind(&mut self, size: usize) -> &mut Self { 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 self
} }
@ -320,37 +311,28 @@ impl<'a> Scope<'a> {
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn contains(&self, name: &str) -> bool { pub fn contains(&self, name: &str) -> bool {
self.0 self.names
.iter() .iter()
.rev() // Always search a Scope in reverse order .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. /// Find an entry in the Scope, starting from the last.
#[inline(always)] #[inline(always)]
pub(crate) fn get_index(&self, name: &str) -> Option<(usize, EntryType)> { pub(crate) fn get_index(&self, name: &str) -> Option<(usize, EntryType)> {
self.0 self.names
.iter() .iter()
.enumerate() .enumerate()
.rev() // Always search a Scope in reverse order .rev() // Always search a Scope in reverse order
.find_map(|(index, Entry { name: key, typ, .. })| { .find_map(|(index, (key, _))| {
if name == key { if name == key.as_ref() {
Some((index, *typ)) Some((index, self.types[index]))
} else { } else {
None 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. /// Get the value of an entry in the Scope, starting from the last.
/// ///
/// # Example /// # Example
@ -365,8 +347,12 @@ impl<'a> Scope<'a> {
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn get_value<T: Variant + Clone>(&self, name: &str) -> Option<T> { pub fn get_value<T: Variant + Clone>(&self, name: &str) -> Option<T> {
self.get_entry(name) self.names
.and_then(|Entry { value, .. }| value.flatten_clone().try_cast()) .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. /// Update the value of the named entry.
@ -398,7 +384,7 @@ impl<'a> Scope<'a> {
} }
Some((_, EntryType::Constant)) => panic!("variable {} is constant", name), Some((_, EntryType::Constant)) => panic!("variable {} is constant", name),
Some((index, EntryType::Normal)) => { Some((index, EntryType::Normal)) => {
self.0.get_mut(index).unwrap().value = Dynamic::from(value); *self.values.get_mut(index).unwrap() = Dynamic::from(value);
} }
} }
self self
@ -407,16 +393,18 @@ impl<'a> Scope<'a> {
/// Get a mutable reference to an entry in the Scope. /// Get a mutable reference to an entry in the Scope.
#[inline(always)] #[inline(always)]
pub(crate) fn get_mut(&mut self, index: usize) -> (&mut Dynamic, EntryType) { 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. /// Update the access type of an entry in the Scope.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
pub(crate) fn set_entry_alias(&mut self, index: usize, alias: String) -> &mut Self { 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"); let entry = self.names.get_mut(index).expect("invalid index in Scope");
entry.alias = Some(Box::new(alias)); entry.1 = Some(alias);
self self
} }
@ -424,34 +412,36 @@ impl<'a> Scope<'a> {
/// Shadowed variables are omitted in the copy. /// Shadowed variables are omitted in the copy.
#[inline] #[inline]
pub(crate) fn clone_visible(&self) -> Self { pub(crate) fn clone_visible(&self) -> Self {
let mut entries: Vec<Entry> = Default::default(); let mut entries: Self = Default::default();
self.0.iter().rev().for_each(|entry| { self.names
if entries
.iter() .iter()
.find(|Entry { name, .. }| &entry.name == name) .enumerate()
.is_none() .rev()
{ .for_each(|(index, (name, alias))| {
entries.push(entry.clone()); 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. /// Get an iterator to entries in the Scope.
#[inline(always)] #[inline(always)]
pub(crate) fn into_iter(self) -> impl Iterator<Item = Entry<'a>> { pub(crate) fn into_iter(
self.0.into_iter() self,
} ) -> impl Iterator<Item = (Cow<'a, str>, EntryType, Dynamic, Option<String>)> {
self.names
/// Get an iterator to entries in the Scope in reverse order. .into_iter()
#[inline(always)] .zip(self.types.into_iter().zip(self.values.into_iter()))
pub(crate) fn to_iter(&self) -> impl Iterator<Item = &Entry> { .map(|((name, alias), (typ, value))| (name, typ, value, alias))
self.0.iter().rev() // Always search a Scope in reverse order
} }
/// Get an iterator to entries in the Scope. /// Get an iterator to entries in the Scope.
/// Shared values are flatten-cloned.
/// ///
/// # Example /// # Example
/// ///
@ -484,25 +474,21 @@ impl<'a> Scope<'a> {
/// Get an iterator to entries in the Scope. /// Get an iterator to entries in the Scope.
/// Shared values are not expanded. /// Shared values are not expanded.
#[inline(always)] #[inline(always)]
pub fn iter_raw(&self) -> impl Iterator<Item = (&str, bool, &Dynamic)> { pub fn iter_raw<'x: 'a>(&'x self) -> impl Iterator<Item = (&'a str, bool, &'x Dynamic)> + 'x {
self.0.iter().map( self.names
|Entry { .iter()
name, typ, value, .. .zip(self.types.iter().zip(self.values.iter()))
}| { (name.as_ref(), typ.is_constant(), value) }, .map(|((name, _), (typ, value))| (name.as_ref(), typ.is_constant(), value))
)
} }
} }
impl<'a, K: Into<Cow<'a, str>>> iter::Extend<(K, EntryType, Dynamic)> for Scope<'a> { impl<'a, K: Into<Cow<'a, str>>> iter::Extend<(K, EntryType, Dynamic)> for Scope<'a> {
#[inline(always)] #[inline(always)]
fn extend<T: IntoIterator<Item = (K, EntryType, Dynamic)>>(&mut self, iter: T) { fn extend<T: IntoIterator<Item = (K, EntryType, Dynamic)>>(&mut self, iter: T) {
self.0 iter.into_iter().for_each(|(name, typ, value)| {
.extend(iter.into_iter().map(|(name, typ, value)| Entry { self.names.push((name.into(), None));
name: name.into(), self.types.push(typ);
typ, self.values.push(value);
alias: None, });
value: value.into(),
expr: None,
}));
} }
} }