From 4e5d009386341b928895a4026029cbd7b4dab6d0 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 12 Mar 2021 13:26:47 +0800 Subject: [PATCH 01/10] Inline scope entries. --- Cargo.toml | 60 +++++++++++++++++++++++----------------------------- src/scope.rs | 26 +++++++++++++++-------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9ce632c5..f1759877 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,5 @@ [workspace] -members = [ - ".", - "codegen" -] +members = [".", "codegen"] [package] name = "rhai" @@ -14,14 +11,9 @@ homepage = "https://rhai.rs" repository = "https://github.com/rhaiscript" readme = "README.md" license = "MIT OR Apache-2.0" -include = [ - "**/*.rs", - "scripts/*.rhai", - "**/*.md", - "Cargo.toml" -] -keywords = [ "scripting", "scripting-engine", "scripting-language", "embedded" ] -categories = [ "no-std", "embedded", "wasm", "parser-implementations" ] +include = ["**/*.rs", "scripts/*.rhai", "**/*.md", "Cargo.toml"] +keywords = ["scripting", "scripting-engine", "scripting-language", "embedded"] +categories = ["no-std", "embedded", "wasm", "parser-implementations"] [dependencies] smallvec = { version = "1.6", default-features = false, features = ["union"] } @@ -31,29 +23,29 @@ rhai_codegen = { version = "0.3.3", path = "codegen" } [features] default = [] -unchecked = [] # unchecked arithmetic -sync = [] # restrict to only types that implement Send + Sync -no_optimize = [] # no script optimizer -no_float = [] # no floating-point -f32_float = [] # set FLOAT=f32 -only_i32 = [] # set INT=i32 (useful for 32-bit systems) -only_i64 = [] # set INT=i64 (default) and disable support for all other integer types -decimal = [ "rust_decimal" ] # add the Decimal number type -no_index = [] # no arrays and indexing -no_object = [] # no custom objects -no_function = [ "no_closure" ] # no script-defined functions (meaning no closures) -no_closure = [] # no automatic sharing and capture of anonymous functions to external variables -no_module = [] # no modules -internals = [] # expose internal data structures -unicode-xid-ident = [ "unicode-xid" ] # allow Unicode Standard Annex #31 for identifiers. -metadata = [ "serde", "serde_json" ] # enables exporting functions metadata to JSON +unchecked = [] # unchecked arithmetic +sync = [] # restrict to only types that implement Send + Sync +no_optimize = [] # no script optimizer +no_float = [] # no floating-point +f32_float = [] # set FLOAT=f32 +only_i32 = [] # set INT=i32 (useful for 32-bit systems) +only_i64 = [] # set INT=i64 (default) and disable support for all other integer types +decimal = ["rust_decimal"] # add the Decimal number type +no_index = [] # no arrays and indexing +no_object = [] # no custom objects +no_function = ["no_closure"] # no script-defined functions (meaning no closures) +no_closure = [] # no automatic sharing and capture of anonymous functions to external variables +no_module = [] # no modules +internals = [] # expose internal data structures +unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. +metadata = ["serde", "serde_json"] # enables exporting functions metadata to JSON # compiling for no-std -no_std = [ "smallvec/union", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash/compile-time-rng" ] +no_std = ["smallvec/union", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash/compile-time-rng"] # compiling for WASM -wasm-bindgen = [ "instant/wasm-bindgen" ] -stdweb = [ "instant/stdweb" ] +wasm-bindgen = ["instant/wasm-bindgen"] +stdweb = ["instant/stdweb"] [profile.release] lto = "fat" @@ -101,10 +93,10 @@ default_features = false optional = true [target.'cfg(target_arch = "wasm32")'.dependencies] -instant= { version = "0.1" } # WASM implementation of std::time::Instant +instant = { version = "0.1" } # WASM implementation of std::time::Instant [target.'cfg(target_arch = "wasm64")'.dependencies] -instant= { version = "0.1" } # WASM implementation of std::time::Instant +instant = { version = "0.1" } # WASM implementation of std::time::Instant [package.metadata.docs.rs] -features = [ "metadata", "internals", "decimal" ] +features = ["metadata", "internals", "decimal"] diff --git a/src/scope.rs b/src/scope.rs index 2071a75d..2fa8f235 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -4,6 +4,9 @@ use crate::dynamic::{AccessMode, Variant}; use crate::stdlib::{borrow::Cow, boxed::Box, iter, vec::Vec}; use crate::{Dynamic, ImmutableString, StaticVec}; +/// Keep a number of entries inline (since [`Dynamic`] is usually small enough). +const SCOPE_SIZE: usize = 16; + /// Type containing information about the current scope. /// Useful for keeping state between [`Engine`][crate::Engine] evaluation runs. /// @@ -49,17 +52,17 @@ use crate::{Dynamic, ImmutableString, StaticVec}; #[derive(Debug, Clone, Hash)] pub struct Scope<'a> { /// Current value of the entry. - values: Vec, + values: smallvec::SmallVec<[Dynamic; SCOPE_SIZE]>, /// (Name, aliases) of the entry. - names: Vec<(Cow<'a, str>, Box>)>, + names: Vec<(Cow<'a, str>, Option>>)>, } impl Default for Scope<'_> { #[inline(always)] fn default() -> Self { Self { - values: Vec::with_capacity(16), - names: Vec::with_capacity(16), + values: Default::default(), + names: Vec::with_capacity(SCOPE_SIZE), } } } @@ -244,7 +247,7 @@ impl<'a> Scope<'a> { access: AccessMode, mut value: Dynamic, ) -> &mut Self { - self.names.push((name.into(), Box::new(Default::default()))); + self.names.push((name.into(), None)); value.set_access_mode(access); self.values.push(value.into()); self @@ -413,8 +416,11 @@ impl<'a> Scope<'a> { alias: impl Into + PartialEq, ) -> &mut Self { let entry = self.names.get_mut(index).expect("invalid index in Scope"); - if !entry.1.iter().any(|a| &alias == a) { - entry.1.push(alias.into()); + if entry.1.is_none() { + entry.1 = Some(Default::default()); + } + if !entry.1.as_ref().unwrap().iter().any(|a| &alias == a) { + entry.1.as_mut().unwrap().push(alias.into()); } self } @@ -446,7 +452,9 @@ impl<'a> Scope<'a> { self.names .into_iter() .zip(self.values.into_iter()) - .map(|((name, alias), value)| (name, value, alias.to_vec())) + .map(|((name, alias), value)| { + (name, value, alias.map(|a| a.to_vec()).unwrap_or_default()) + }) } /// Get an iterator to entries in the [`Scope`]. /// Shared values are flatten-cloned. @@ -493,7 +501,7 @@ impl<'a, K: Into>> iter::Extend<(K, Dynamic)> for Scope<'a> { #[inline(always)] fn extend>(&mut self, iter: T) { iter.into_iter().for_each(|(name, value)| { - self.names.push((name.into(), Box::new(Default::default()))); + self.names.push((name.into(), None)); self.values.push(value); }); } From 85fcb74be9409f3e8f0b3c2c1ebf52cf3c6062f2 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 12 Mar 2021 14:11:08 +0800 Subject: [PATCH 02/10] Furtuer optimize data structure sizes. --- src/ast.rs | 4 +- src/fn_native.rs | 12 +-- src/module/mod.rs | 228 +++++++++++++++++++--------------------------- src/optimize.rs | 6 +- src/parser.rs | 10 +- 5 files changed, 109 insertions(+), 151 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 9ce4c8bf..1e42993c 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -658,7 +658,7 @@ impl AST { pub(crate) fn iter_fn_def(&self) -> impl Iterator { self.functions .iter_script_fn() - .map(|(_, _, _, _, fn_def)| fn_def) + .map(|(_, _, _, _, fn_def)| fn_def.as_ref()) } /// Iterate through all function definitions. /// @@ -668,7 +668,7 @@ impl AST { pub fn iter_functions<'a>(&'a self) -> impl Iterator + 'a { self.functions .iter_script_fn() - .map(|(_, _, _, _, fn_def)| fn_def.into()) + .map(|(_, _, _, _, fn_def)| fn_def.as_ref().into()) } /// Clear all function definitions in the [`AST`]. /// diff --git a/src/fn_native.rs b/src/fn_native.rs index ace60b56..fdb4ba9b 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -570,9 +570,9 @@ impl CallableFunction { /// Panics if the [`CallableFunction`] is not [`Pure`][CallableFunction::Pure] or /// [`Method`][CallableFunction::Method]. #[inline(always)] - pub fn get_native_fn(&self) -> &FnAny { + pub fn get_native_fn(&self) -> &Shared { match self { - Self::Pure(f) | Self::Method(f) => f.as_ref(), + Self::Pure(f) | Self::Method(f) => f, Self::Iterator(_) | Self::Plugin(_) => panic!("function should be native"), #[cfg(not(feature = "no_function"))] @@ -586,12 +586,12 @@ impl CallableFunction { /// Panics if the [`CallableFunction`] is not [`Script`][CallableFunction::Script]. #[cfg(not(feature = "no_function"))] #[inline(always)] - pub fn get_fn_def(&self) -> &crate::ast::ScriptFnDef { + pub fn get_fn_def(&self) -> &Shared { match self { Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => { panic!("function should be scripted") } - Self::Script(f) => f.as_ref(), + Self::Script(f) => f, } } /// Get a reference to an iterator function. @@ -617,9 +617,9 @@ impl CallableFunction { /// /// Panics if the [`CallableFunction`] is not [`Plugin`][CallableFunction::Plugin]. #[inline(always)] - pub fn get_plugin_fn<'s>(&'s self) -> &FnPlugin { + pub fn get_plugin_fn<'s>(&'s self) -> &Shared { match self { - Self::Plugin(f) => f.as_ref(), + Self::Plugin(f) => f, Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => { panic!("function should a plugin") } diff --git a/src/module/mod.rs b/src/module/mod.rs index 2a0c2edc..d9dbd69b 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -136,7 +136,7 @@ pub struct Module { /// Flattened collection of all [`Module`] variables, including those in sub-modules. all_variables: HashMap, /// External Rust functions. - functions: HashMap, + functions: HashMap, StraightHasherBuilder>, /// Flattened collection of all external Rust functions, native or scripted. /// including those in sub-modules. all_functions: HashMap, @@ -207,7 +207,7 @@ impl fmt::Debug for Module { " functions: {}\n", self.functions .values() - .map(|FuncInfo { func, .. }| func.to_string()) + .map(|f| f.func.to_string()) .collect::>() .join(", ") ) @@ -384,11 +384,11 @@ impl Module { pub fn gen_fn_signatures(&self) -> impl Iterator + '_ { self.functions .values() - .filter(|FuncInfo { access, .. }| match access { + .filter(|f| match f.access { FnAccess::Public => true, FnAccess::Private => false, }) - .map(FuncInfo::gen_signature) + .map(|f| f.gen_signature()) } /// Does a variable exist in the [`Module`]? @@ -490,7 +490,7 @@ impl Module { param_names.push("Dynamic".into()); self.functions.insert( hash_script, - FuncInfo { + Box::new(FuncInfo { name: fn_def.name.to_string(), namespace: FnNamespace::Internal, access: fn_def.access, @@ -498,14 +498,15 @@ impl Module { param_types: Default::default(), param_names, func: fn_def.into(), - }, + }), ); self.indexed = false; self.contains_indexed_global_functions = false; hash_script } - /// Get a script-defined function in the [`Module`] based on name and number of parameters. + /// Get a shared reference to the script-defined function in the [`Module`] based on name + /// and number of parameters. #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn get_script_fn( @@ -513,22 +514,15 @@ impl Module { name: &str, num_params: usize, public_only: bool, - ) -> Option<&crate::ast::ScriptFnDef> { + ) -> Option<&Shared> { self.functions .values() - .find( - |FuncInfo { - name: fn_name, - access, - params, - .. - }| { - (!public_only || *access == FnAccess::Public) - && *params == num_params - && fn_name == name - }, - ) - .map(|FuncInfo { func, .. }| func.get_fn_def()) + .find(|f| { + (!public_only || f.access == FnAccess::Public) + && f.params == num_params + && f.name == name + }) + .map(|f| f.func.get_fn_def()) } /// Get a mutable reference to the underlying [`HashMap`] of sub-modules. @@ -629,7 +623,7 @@ impl Module { if public_only { self.functions .get(&hash_fn) - .map_or(false, |FuncInfo { access, .. }| match access { + .map_or(false, |f| match f.access { FnAccess::Public => true, FnAccess::Private => false, }) @@ -717,7 +711,7 @@ impl Module { self.functions.insert( hash_fn, - FuncInfo { + Box::new(FuncInfo { name, namespace, access, @@ -729,7 +723,7 @@ impl Module { Default::default() }, func: func.into(), - }, + }), ); self.indexed = false; @@ -1478,13 +1472,11 @@ impl Module { /// The [`u64`] hash is returned by the `set_fn_XXX` calls. #[inline(always)] pub(crate) fn get_fn(&self, hash_fn: u64, public_only: bool) -> Option<&CallableFunction> { - self.functions - .get(&hash_fn) - .and_then(|FuncInfo { access, func, .. }| match access { - _ if !public_only => Some(func), - FnAccess::Public => Some(func), - FnAccess::Private => None, - }) + self.functions.get(&hash_fn).and_then(|f| match f.access { + _ if !public_only => Some(&f.func), + FnAccess::Public => Some(&f.func), + FnAccess::Private => None, + }) } /// Does the particular namespace-qualified function exist in the [`Module`]? @@ -1594,27 +1586,15 @@ impl Module { other .functions .iter() - .filter( - |( - _, - FuncInfo { - namespace, - access, - name, - params, - func, - .. - }, - )| { - _filter( - *namespace, - *access, - func.is_script(), - name.as_str(), - *params, - ) - }, - ) + .filter(|(_, f)| { + _filter( + f.namespace, + f.access, + f.func.is_script(), + f.name.as_str(), + f.params, + ) + }) .map(|(&k, v)| (k, v.clone())), ); @@ -1634,23 +1614,13 @@ impl Module { &mut self, filter: impl Fn(FnNamespace, FnAccess, &str, usize) -> bool, ) -> &mut Self { - self.functions.retain( - |_, - FuncInfo { - namespace, - access, - name, - params, - func, - .. - }| { - if func.is_script() { - filter(*namespace, *access, name.as_str(), *params) - } else { - false - } - }, - ); + self.functions.retain(|_, f| { + if f.func.is_script() { + filter(f.namespace, f.access, f.name.as_str(), f.params) + } else { + false + } + }); self.all_functions.clear(); self.all_variables.clear(); @@ -1686,7 +1656,7 @@ impl Module { #[inline(always)] #[allow(dead_code)] pub(crate) fn iter_fn(&self) -> impl Iterator { - self.functions.values() + self.functions.values().map(Box::as_ref) } /// Get an iterator over all script-defined functions in the [`Module`]. @@ -1701,26 +1671,27 @@ impl Module { #[inline(always)] pub(crate) fn iter_script_fn( &self, - ) -> impl Iterator + '_ - { - self.functions.values().filter(|f| f.func.is_script()).map( - |FuncInfo { - namespace, - access, - name, - params, - func, - .. - }| { + ) -> impl Iterator< + Item = ( + FnNamespace, + FnAccess, + &str, + usize, + &Shared, + ), + > + '_ { + self.functions + .values() + .filter(|f| f.func.is_script()) + .map(|f| { ( - *namespace, - *access, - name.as_str(), - *params, - func.get_fn_def(), + f.namespace, + f.access, + f.name.as_str(), + f.params, + f.func.get_fn_def(), ) - }, - ) + }) } /// Get an iterator over all script-defined functions in the [`Module`]. @@ -1736,15 +1707,10 @@ impl Module { pub fn iter_script_fn_info( &self, ) -> impl Iterator { - self.functions.values().filter(|f| f.func.is_script()).map( - |FuncInfo { - name, - namespace, - access, - params, - .. - }| (*namespace, *access, name.as_str(), *params), - ) + self.functions + .values() + .filter(|f| f.func.is_script()) + .map(|f| (f.namespace, f.access, f.name.as_str(), f.params)) } /// _(INTERNALS)_ Get an iterator over all script-defined functions in the [`Module`]. @@ -1825,14 +1791,14 @@ impl Module { ast.lib() .functions .values() - .filter(|FuncInfo { access, .. }| match access { + .filter(|f| match f.access { FnAccess::Public => true, FnAccess::Private => false, }) - .filter(|FuncInfo { func, .. }| func.is_script()) - .for_each(|FuncInfo { func, .. }| { + .filter(|f| f.func.is_script()) + .for_each(|f| { // Encapsulate AST environment - let mut func = func.get_fn_def().clone(); + let mut func = crate::fn_native::shared_take_or_clone(f.func.get_fn_def().clone()); func.lib = Some(ast.shared_lib()); func.mods = func_mods.clone(); module.set_script_fn(func); @@ -1891,43 +1857,33 @@ impl Module { }); // Index all Rust functions - module.functions.iter().for_each( - |( - &hash, - FuncInfo { - name, - namespace, - access, - params, - param_types, - func, - .. - }, - )| { - match namespace { - FnNamespace::Global => { - // Flatten all functions with global namespace - functions.insert(hash, func.clone()); - contains_indexed_global_functions = true; - } - FnNamespace::Internal => (), - } - match access { - FnAccess::Public => (), - FnAccess::Private => return, // Do not index private functions + module.functions.iter().for_each(|(&hash, f)| { + match f.namespace { + FnNamespace::Global => { + // Flatten all functions with global namespace + functions.insert(hash, f.func.clone()); + contains_indexed_global_functions = true; } + FnNamespace::Internal => (), + } + match f.access { + FnAccess::Public => (), + FnAccess::Private => return, // Do not index private functions + } - if !func.is_script() { - let hash_qualified_fn = - calc_native_fn_hash(qualifiers.iter().cloned(), name, param_types); - functions.insert(hash_qualified_fn, func.clone()); - } else if cfg!(not(feature = "no_function")) { - let hash_qualified_script = - crate::calc_fn_hash(qualifiers.iter().cloned(), name, *params); - functions.insert(hash_qualified_script, func.clone()); - } - }, - ); + if !f.func.is_script() { + let hash_qualified_fn = calc_native_fn_hash( + qualifiers.iter().cloned(), + f.name.as_str(), + &f.param_types, + ); + functions.insert(hash_qualified_fn, f.func.clone()); + } else if cfg!(not(feature = "no_function")) { + let hash_qualified_script = + crate::calc_fn_hash(qualifiers.iter().cloned(), f.name.as_str(), f.params); + functions.insert(hash_qualified_script, f.func.clone()); + } + }); contains_indexed_global_functions } diff --git a/src/optimize.rs b/src/optimize.rs index 06a06eaf..d087f584 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -849,7 +849,7 @@ pub fn optimize_into_ast( engine: &Engine, scope: &Scope, mut statements: Vec, - _functions: Vec, + _functions: Vec>, optimization_level: OptimizationLevel, ) -> AST { let level = if cfg!(feature = "no_optimize") { @@ -888,7 +888,9 @@ pub fn optimize_into_ast( _functions .into_iter() - .map(|mut fn_def| { + .map(|fn_def| { + let mut fn_def = crate::fn_native::shared_take_or_clone(fn_def); + let pos = fn_def.body.pos; let mut body = fn_def.body.statements.into_vec(); diff --git a/src/parser.rs b/src/parser.rs index db15c06a..171976e3 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -26,7 +26,7 @@ use crate::token::{is_keyword_function, is_valid_identifier, Token, TokenStream} use crate::utils::{get_hasher, StraightHasherBuilder}; use crate::{ calc_fn_hash, Dynamic, Engine, ImmutableString, LexError, ParseError, ParseErrorType, Position, - Scope, StaticVec, AST, + Scope, Shared, StaticVec, AST, }; #[cfg(not(feature = "no_float"))] @@ -37,7 +37,7 @@ use crate::FnAccess; type PERR = ParseErrorType; -type FunctionsLib = HashMap; +type FunctionsLib = HashMap, StraightHasherBuilder>; /// A type that encapsulates the current state of the parser. #[derive(Debug)] @@ -1008,7 +1008,7 @@ fn parse_primary( }); let hash_script = calc_fn_hash(empty(), &func.name, func.params.len()); - lib.insert(hash_script, func); + lib.insert(hash_script, func.into()); expr } @@ -2530,7 +2530,7 @@ fn parse_stmt( .into_err(pos)); } - lib.insert(hash, func); + lib.insert(hash, func.into()); Ok(Stmt::Noop(pos)) } @@ -2969,7 +2969,7 @@ impl Engine { fn parse_global_level( &self, input: &mut TokenStream, - ) -> Result<(Vec, Vec), ParseError> { + ) -> Result<(Vec, Vec>), ParseError> { let mut statements = Vec::with_capacity(16); let mut functions = HashMap::with_capacity_and_hasher(16, StraightHasherBuilder); let mut state = ParseState::new( From b74d2ff2b4a3e8cf2b8f6b50265ddc4620dec123 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 12 Mar 2021 14:13:19 +0800 Subject: [PATCH 03/10] Fix test. --- src/ast.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ast.rs b/src/ast.rs index 1e42993c..8bb895df 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1847,7 +1847,7 @@ mod tests { assert_eq!(size_of::(), 40); assert_eq!(size_of::>(), 40); assert_eq!(size_of::(), 32); - assert_eq!(size_of::(), 48); + assert_eq!(size_of::(), 288); assert_eq!(size_of::(), 56); assert_eq!(size_of::(), 16); assert_eq!(size_of::(), 72); From 81ca86a8d2fb56e90e170240cc40b989a02485b6 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 12 Mar 2021 19:33:22 +0800 Subject: [PATCH 04/10] Fix internals build. --- src/module/mod.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/module/mod.rs b/src/module/mod.rs index d9dbd69b..7ff74104 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1727,7 +1727,15 @@ impl Module { #[inline(always)] pub fn iter_script_fn_info( &self, - ) -> impl Iterator { + ) -> impl Iterator< + Item = ( + FnNamespace, + FnAccess, + &str, + usize, + &Shared, + ), + > { self.iter_script_fn() } From 61b0c7b2b3e2a88e638d0ef5ea09679802770fcb Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 12 Mar 2021 22:30:08 +0800 Subject: [PATCH 05/10] Optimize imports layout. --- src/engine.rs | 36 +++++++++++++++--------------------- src/fn_call.rs | 5 ++++- src/module/mod.rs | 25 +++++++++++-------------- src/token.rs | 12 ++++++------ 4 files changed, 36 insertions(+), 42 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 973cf56b..bcc06b83 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -53,7 +53,7 @@ pub const TYPICAL_MAP_SIZE: usize = 8; // Small maps are typical // the module name will live beyond the AST of the eval script text. // The best we can do is a shared reference. #[derive(Debug, Clone, Default)] -pub struct Imports(StaticVec<(ImmutableString, Shared)>); +pub struct Imports(StaticVec, StaticVec>); impl Imports { /// Get the length of this stack of imported [modules][Module]. @@ -69,7 +69,7 @@ impl Imports { /// Get the imported [modules][Module] at a particular index. #[inline(always)] pub fn get(&self, index: usize) -> Option> { - self.0.get(index).map(|(_, m)| m).cloned() + self.1.get(index).cloned() } /// Get the index of an imported [modules][Module] by name. #[inline(always)] @@ -78,18 +78,19 @@ impl Imports { .iter() .enumerate() .rev() - .find(|(_, (key, _))| key.as_str() == name) - .map(|(index, _)| index) + .find_map(|(i, key)| if key.as_str() == name { Some(i) } else { None }) } /// Push an imported [modules][Module] onto the stack. #[inline(always)] pub fn push(&mut self, name: impl Into, module: impl Into>) { - self.0.push((name.into(), module.into())); + self.0.push(name.into()); + self.1.push(module.into()); } /// Truncate the stack of imported [modules][Module] to a particular length. #[inline(always)] pub fn truncate(&mut self, size: usize) { self.0.truncate(size); + self.1.truncate(size); } /// Get an iterator to this stack of imported [modules][Module] in reverse order. #[allow(dead_code)] @@ -97,6 +98,7 @@ impl Imports { pub fn iter(&self) -> impl Iterator { self.0 .iter() + .zip(self.1.iter()) .rev() .map(|(name, module)| (name.as_str(), module.as_ref())) } @@ -104,52 +106,44 @@ impl Imports { #[allow(dead_code)] #[inline(always)] pub(crate) fn iter_raw(&self) -> impl Iterator)> { - self.0.iter().rev().map(|(n, m)| (n, m)) + self.0.iter().rev().zip(self.1.iter().rev()) } /// Get an iterator to this stack of imported [modules][Module] in forward order. #[allow(dead_code)] #[inline(always)] pub(crate) fn scan_raw(&self) -> impl Iterator)> { - self.0.iter().map(|(n, m)| (n, m)) + self.0.iter().zip(self.1.iter()) } /// Get a consuming iterator to this stack of imported [modules][Module] in reverse order. #[inline(always)] pub fn into_iter(self) -> impl Iterator)> { - self.0.into_iter().rev() - } - /// Add a stream of imported [modules][Module]. - #[inline(always)] - pub fn extend(&mut self, stream: impl Iterator)>) { - self.0.extend(stream) + self.0.into_iter().rev().zip(self.1.into_iter().rev()) } /// Does the specified function hash key exist in this stack of imported [modules][Module]? #[allow(dead_code)] #[inline(always)] pub fn contains_fn(&self, hash: u64) -> bool { - self.0.iter().any(|(_, m)| m.contains_qualified_fn(hash)) + self.1.iter().any(|m| m.contains_qualified_fn(hash)) } /// Get specified function via its hash key. #[inline(always)] pub fn get_fn(&self, hash: u64) -> Option<(&CallableFunction, Option<&ImmutableString>)> { - self.0 + self.1 .iter() .rev() - .find_map(|(_, m)| m.get_qualified_fn(hash).map(|f| (f, m.id_raw()))) + .find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id_raw()))) } /// Does the specified [`TypeId`][std::any::TypeId] iterator exist in this stack of /// imported [modules][Module]? #[allow(dead_code)] #[inline(always)] pub fn contains_iter(&self, id: TypeId) -> bool { - self.0.iter().any(|(_, m)| m.contains_qualified_iter(id)) + self.1.iter().any(|m| m.contains_qualified_iter(id)) } /// Get the specified [`TypeId`][std::any::TypeId] iterator. #[inline(always)] pub fn get_iter(&self, id: TypeId) -> Option { - self.0 - .iter() - .rev() - .find_map(|(_, m)| m.get_qualified_iter(id)) + self.1.iter().rev().find_map(|m| m.get_qualified_iter(id)) } } diff --git a/src/fn_call.rs b/src/fn_call.rs index 8a008ee1..f8a0d38f 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -539,7 +539,10 @@ impl Engine { #[cfg(not(feature = "no_module"))] if !fn_def.mods.is_empty() { - mods.extend(fn_def.mods.iter_raw().map(|(n, m)| (n.clone(), m.clone()))); + fn_def + .mods + .iter_raw() + .for_each(|(n, m)| mods.push(n.clone(), m.clone())); } // Evaluate the function diff --git a/src/module/mod.rs b/src/module/mod.rs index 7ff74104..18c7f3f1 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1836,7 +1836,7 @@ impl Module { // Collect a particular module. fn index_module<'a>( module: &'a Module, - qualifiers: &mut Vec<&'a str>, + path: &mut Vec<&'a str>, variables: &mut HashMap, functions: &mut HashMap, type_iterators: &mut HashMap, @@ -1845,16 +1845,16 @@ impl Module { module.modules.iter().for_each(|(name, m)| { // Index all the sub-modules first. - qualifiers.push(name); - if index_module(m, qualifiers, variables, functions, type_iterators) { + path.push(name); + if index_module(m, path, variables, functions, type_iterators) { contains_indexed_global_functions = true; } - qualifiers.pop(); + path.pop(); }); // Index all variables module.variables.iter().for_each(|(var_name, value)| { - let hash_var = crate::calc_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0); + let hash_var = crate::calc_fn_hash(path.iter().map(|&v| v), var_name, 0); variables.insert(hash_var, value.clone()); }); @@ -1880,15 +1880,12 @@ impl Module { } if !f.func.is_script() { - let hash_qualified_fn = calc_native_fn_hash( - qualifiers.iter().cloned(), - f.name.as_str(), - &f.param_types, - ); + let hash_qualified_fn = + calc_native_fn_hash(path.iter().cloned(), f.name.as_str(), &f.param_types); functions.insert(hash_qualified_fn, f.func.clone()); } else if cfg!(not(feature = "no_function")) { let hash_qualified_script = - crate::calc_fn_hash(qualifiers.iter().cloned(), f.name.as_str(), f.params); + crate::calc_fn_hash(path.iter().cloned(), f.name.as_str(), f.params); functions.insert(hash_qualified_script, f.func.clone()); } }); @@ -1897,16 +1894,16 @@ impl Module { } if !self.indexed { - let mut qualifiers = Vec::with_capacity(4); + let mut path = Vec::with_capacity(4); let mut variables = HashMap::with_capacity_and_hasher(16, StraightHasherBuilder); let mut functions = HashMap::with_capacity_and_hasher(256, StraightHasherBuilder); let mut type_iterators = HashMap::with_capacity(16); - qualifiers.push("root"); + path.push("root"); self.contains_indexed_global_functions = index_module( self, - &mut qualifiers, + &mut path, &mut variables, &mut functions, &mut type_iterators, diff --git a/src/token.rs b/src/token.rs index d0cdcf9e..0d68c460 100644 --- a/src/token.rs +++ b/src/token.rs @@ -840,8 +840,8 @@ pub fn parse_string_literal( pos: &mut Position, enclosing_char: char, ) -> Result { - let mut result: StaticVec = Default::default(); - let mut escape: StaticVec = Default::default(); + let mut result: smallvec::SmallVec<[char; 16]> = Default::default(); + let mut escape: smallvec::SmallVec<[char; 12]> = Default::default(); let start = *pos; @@ -1109,7 +1109,7 @@ fn get_next_token_inner( // digit ... ('0'..='9', _) => { - let mut result: StaticVec = Default::default(); + let mut result: smallvec::SmallVec<[char; 16]> = Default::default(); let mut radix_base: Option = None; let mut valid: fn(char) -> bool = is_numeric_digit; result.push(c); @@ -1596,7 +1596,7 @@ fn get_identifier( start_pos: Position, first_char: char, ) -> Option<(Token, Position)> { - let mut result: StaticVec<_> = Default::default(); + let mut result: smallvec::SmallVec<[char; 8]> = Default::default(); result.push(first_char); while let Some(next_char) = stream.peek_next() { @@ -1691,10 +1691,10 @@ pub fn is_id_continue(x: char) -> bool { pub struct MultiInputsStream<'a> { /// Buffered character, if any. buf: Option, - /// The input character streams. - streams: StaticVec>>, /// The current stream index. index: usize, + /// The input character streams. + streams: StaticVec>>, } impl InputStream for MultiInputsStream<'_> { From 558ffeaf3e2adb0ed58d1f74ca4452803728790f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 13 Mar 2021 18:46:08 +0800 Subject: [PATCH 06/10] Add FnResolutionCacheEntry. --- src/engine.rs | 45 +++++++++++++++++++++++---------- src/fn_call.rs | 67 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 76 insertions(+), 36 deletions(-) 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()); From 58d28fb468bf71b537b73f6e63e8b8103d2611d3 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 13 Mar 2021 19:08:49 +0800 Subject: [PATCH 07/10] Fix no-std build. --- src/engine.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine.rs b/src/engine.rs index c9910ab5..07b18e51 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -20,6 +20,7 @@ use crate::stdlib::{ num::{NonZeroU64, NonZeroU8, NonZeroUsize}, ops::DerefMut, string::{String, ToString}, + vec::Vec, }; use crate::syntax::CustomSyntax; use crate::utils::{get_hasher, StraightHasherBuilder}; From 4e3fdf6dab01a08e06e831b26675394b48144731 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 13 Mar 2021 23:43:05 +0800 Subject: [PATCH 08/10] Revamp statement block optimization. --- src/optimize.rs | 307 ++++++++++++++++++++++----------------------- tests/optimizer.rs | 8 +- 2 files changed, 158 insertions(+), 157 deletions(-) diff --git a/src/optimize.rs b/src/optimize.rs index d087f584..7c863747 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -88,11 +88,6 @@ impl<'a> State<'a> { optimization_level, } } - /// Reset the state from dirty to clean. - #[inline(always)] - pub fn reset(&mut self) { - self.changed = false; - } /// Set the [`AST`] state to be dirty (i.e. changed). #[inline(always)] pub fn set_dirty(&mut self) { @@ -179,6 +174,8 @@ fn optimize_stmt_block( mut statements: Vec, state: &mut State, preserve_result: bool, + is_internal: bool, + reduce_return: bool, ) -> Vec { if statements.is_empty() { return statements; @@ -186,6 +183,12 @@ fn optimize_stmt_block( let mut is_dirty = state.is_dirty(); + let is_pure = if is_internal { + Stmt::is_internally_pure + } else { + Stmt::is_pure + }; + loop { state.clear_dirty(); @@ -228,18 +231,89 @@ fn optimize_stmt_block( } }); + // Remove all pure statements except the last one + let mut index = 0; + let mut first_non_constant = statements + .iter() + .rev() + .enumerate() + .find_map(|(i, stmt)| match stmt { + stmt if !is_pure(stmt) => Some(i), + + Stmt::Noop(_) | Stmt::Return(_, None, _) | Stmt::Export(_, _) | Stmt::Share(_) => { + None + } + + Stmt::Let(e, _, _, _) + | Stmt::Const(e, _, _, _) + | Stmt::Expr(e) + | Stmt::Return(_, Some(e), _) + | Stmt::Import(e, _, _) + if e.is_constant() => + { + None + } + + _ => Some(i), + }) + .map_or(0, |n| statements.len() - n); + + while index < statements.len() { + if preserve_result && index >= statements.len() - 1 { + break; + } else { + match &statements[index] { + stmt if is_pure(stmt) && index >= first_non_constant => { + state.set_dirty(); + statements.remove(index); + } + stmt if stmt.is_pure() => { + state.set_dirty(); + if index < first_non_constant { + first_non_constant -= 1; + } + statements.remove(index); + } + _ => index += 1, + } + } + } + // Remove all pure statements that do not return values at the end of a block. // We cannot remove anything for non-pure statements due to potential side-effects. if preserve_result { loop { - match &statements[..] { - [stmt] if !stmt.returns_value() && stmt.is_internally_pure() => { + match &mut statements[..] { + // { return; } -> {} + [Stmt::Return(crate::ast::ReturnType::Return, None, _)] if reduce_return => { state.set_dirty(); statements.clear(); } + [stmt] if !stmt.returns_value() && is_pure(stmt) => { + state.set_dirty(); + statements.clear(); + } + // { ...; return; } -> { ... } + [.., last_stmt, Stmt::Return(crate::ast::ReturnType::Return, None, _)] + if reduce_return && !last_stmt.returns_value() => + { + state.set_dirty(); + statements.pop().unwrap(); + } + // { ...; return val; } -> { ...; val } + [.., Stmt::Return(crate::ast::ReturnType::Return, expr, pos)] + if reduce_return => + { + state.set_dirty(); + *statements.last_mut().unwrap() = if let Some(expr) = expr { + Stmt::Expr(mem::take(expr)) + } else { + Stmt::Noop(*pos) + }; + } [.., second_last_stmt, Stmt::Noop(_)] if second_last_stmt.returns_value() => {} [.., second_last_stmt, last_stmt] - if !last_stmt.returns_value() && last_stmt.is_internally_pure() => + if !last_stmt.returns_value() && is_pure(last_stmt) => { state.set_dirty(); if second_last_stmt.returns_value() { @@ -254,11 +328,25 @@ fn optimize_stmt_block( } else { loop { match &statements[..] { - [stmt] if stmt.is_internally_pure() => { + [stmt] if is_pure(stmt) => { state.set_dirty(); statements.clear(); } - [.., last_stmt] if last_stmt.is_internally_pure() => { + // { ...; return; } -> { ... } + [.., Stmt::Return(crate::ast::ReturnType::Return, None, _)] + if reduce_return => + { + state.set_dirty(); + statements.pop().unwrap(); + } + // { ...; return pure_val; } -> { ... } + [.., Stmt::Return(crate::ast::ReturnType::Return, Some(expr), _)] + if reduce_return && expr.is_pure() => + { + state.set_dirty(); + statements.pop().unwrap(); + } + [.., last_stmt] if is_pure(last_stmt) => { state.set_dirty(); statements.pop().unwrap(); } @@ -282,6 +370,9 @@ fn optimize_stmt_block( state.set_dirty(); } + println!("{:?}", statements); + + statements.shrink_to_fit(); statements } @@ -321,11 +412,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { // if false { if_block } else { else_block } -> else_block Stmt::If(Expr::BoolConstant(false, _), x, _) => { state.set_dirty(); - *stmt = match optimize_stmt_block( - mem::take(&mut x.1.statements).into_vec(), - state, - preserve_result, - ) { + let else_block = mem::take(&mut x.1.statements).into_vec(); + *stmt = match optimize_stmt_block(else_block, state, preserve_result, true, false) { statements if statements.is_empty() => Stmt::Noop(x.1.pos), statements => Stmt::Block(statements, x.1.pos), } @@ -333,11 +421,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { // if true { if_block } else { else_block } -> if_block Stmt::If(Expr::BoolConstant(true, _), x, _) => { state.set_dirty(); - *stmt = match optimize_stmt_block( - mem::take(&mut x.0.statements).into_vec(), - state, - preserve_result, - ) { + let if_block = mem::take(&mut x.0.statements).into_vec(); + *stmt = match optimize_stmt_block(if_block, state, preserve_result, true, false) { statements if statements.is_empty() => Stmt::Noop(x.0.pos), statements => Stmt::Block(statements, x.0.pos), } @@ -345,18 +430,12 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { // if expr { if_block } else { else_block } Stmt::If(condition, x, _) => { optimize_expr(condition, state); - x.0.statements = optimize_stmt_block( - mem::take(&mut x.0.statements).into_vec(), - state, - preserve_result, - ) - .into(); - x.1.statements = optimize_stmt_block( - mem::take(&mut x.1.statements).into_vec(), - state, - preserve_result, - ) - .into(); + let if_block = mem::take(&mut x.0.statements).into_vec(); + x.0.statements = + optimize_stmt_block(if_block, state, preserve_result, true, false).into(); + let else_block = mem::take(&mut x.1.statements).into_vec(); + x.1.statements = + optimize_stmt_block(else_block, state, preserve_result, true, false).into(); } // switch const { ... } @@ -371,15 +450,15 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { let table = &mut x.0; let (statements, new_pos) = if let Some(block) = table.get_mut(&hash) { + let match_block = mem::take(&mut block.statements).into_vec(); ( - optimize_stmt_block(mem::take(&mut block.statements).into_vec(), state, true) - .into(), + optimize_stmt_block(match_block, state, true, true, false).into(), block.pos, ) } else { + let def_block = mem::take(&mut x.1.statements).into_vec(); ( - optimize_stmt_block(mem::take(&mut x.1.statements).into_vec(), state, true) - .into(), + optimize_stmt_block(def_block, state, true, true, false).into(), if x.1.pos.is_none() { *pos } else { x.1.pos }, ) }; @@ -393,19 +472,13 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { Stmt::Switch(expr, x, _) => { optimize_expr(expr, state); x.0.values_mut().for_each(|block| { - block.statements = optimize_stmt_block( - mem::take(&mut block.statements).into_vec(), - state, - preserve_result, - ) - .into() + let match_block = mem::take(&mut block.statements).into_vec(); + block.statements = + optimize_stmt_block(match_block, state, preserve_result, true, false).into() }); - x.1.statements = optimize_stmt_block( - mem::take(&mut x.1.statements).into_vec(), - state, - preserve_result, - ) - .into() + let def_block = mem::take(&mut x.1.statements).into_vec(); + x.1.statements = + optimize_stmt_block(def_block, state, preserve_result, true, false).into() } // while false { block } -> Noop @@ -414,15 +487,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { *stmt = Stmt::Noop(*pos) } // while expr { block } - Stmt::While(condition, block, _) => { + Stmt::While(condition, body, _) => { optimize_expr(condition, state); - block.statements = - optimize_stmt_block(mem::take(&mut block.statements).into_vec(), state, false) - .into(); + let block = mem::take(&mut body.statements).into_vec(); + body.statements = optimize_stmt_block(block, state, false, true, false).into(); - if block.len() == 1 { - match block.statements[0] { + if body.len() == 1 { + match body.statements[0] { // while expr { break; } -> { expr; } Stmt::Break(pos) => { // Only a single break statement - turn into running the guard expression once @@ -442,26 +514,26 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { } } // do { block } while false | do { block } until true -> { block } - Stmt::Do(block, Expr::BoolConstant(true, _), false, _) - | Stmt::Do(block, Expr::BoolConstant(false, _), true, _) => { + Stmt::Do(body, Expr::BoolConstant(true, _), false, _) + | Stmt::Do(body, Expr::BoolConstant(false, _), true, _) => { state.set_dirty(); + let block = mem::take(&mut body.statements).into_vec(); *stmt = Stmt::Block( - optimize_stmt_block(mem::take(&mut block.statements).into_vec(), state, false), - block.pos, + optimize_stmt_block(block, state, false, true, false), + body.pos, ); } // do { block } while|until expr - Stmt::Do(block, condition, _, _) => { + Stmt::Do(body, condition, _, _) => { optimize_expr(condition, state); - block.statements = - optimize_stmt_block(mem::take(&mut block.statements).into_vec(), state, false) - .into(); + let block = mem::take(&mut body.statements).into_vec(); + body.statements = optimize_stmt_block(block, state, false, true, false).into(); } // for id in expr { block } Stmt::For(iterable, x, _) => { optimize_expr(iterable, state); - x.1.statements = - optimize_stmt_block(mem::take(&mut x.1.statements).into_vec(), state, false).into(); + let body = mem::take(&mut x.1.statements).into_vec(); + x.1.statements = optimize_stmt_block(body, state, false, true, false).into(); } // let id = expr; Stmt::Let(expr, _, _, _) => optimize_expr(expr, state), @@ -470,7 +542,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { Stmt::Import(expr, _, _) => optimize_expr(expr, state), // { block } Stmt::Block(statements, pos) => { - *stmt = match optimize_stmt_block(mem::take(statements), state, preserve_result) { + let block = mem::take(statements); + *stmt = match optimize_stmt_block(block, state, preserve_result, true, false) { statements if statements.is_empty() => { state.set_dirty(); Stmt::Noop(*pos) @@ -483,21 +556,22 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { statements => Stmt::Block(statements, *pos), }; } - // try { pure block } catch ( var ) { block } + // try { pure try_block } catch ( var ) { catch_block } -> try_block Stmt::TryCatch(x, _, _) if x.0.statements.iter().all(Stmt::is_pure) => { // If try block is pure, there will never be any exceptions state.set_dirty(); + let try_block = mem::take(&mut x.0.statements).into_vec(); *stmt = Stmt::Block( - optimize_stmt_block(mem::take(&mut x.0.statements).into_vec(), state, false), + optimize_stmt_block(try_block, state, false, true, false), x.0.pos, ); } - // try { block } catch ( var ) { block } + // try { try_block } catch ( var ) { catch_block } Stmt::TryCatch(x, _, _) => { - x.0.statements = - optimize_stmt_block(mem::take(&mut x.0.statements).into_vec(), state, false).into(); - x.2.statements = - optimize_stmt_block(mem::take(&mut x.2.statements).into_vec(), state, false).into(); + let try_block = mem::take(&mut x.0.statements).into_vec(); + x.0.statements = optimize_stmt_block(try_block, state, false, true, false).into(); + let catch_block = mem::take(&mut x.2.statements).into_vec(); + x.2.statements = optimize_stmt_block(catch_block, state, false, true, false).into(); } // {} Stmt::Expr(Expr::Stmt(x)) if x.statements.is_empty() => { @@ -532,7 +606,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { // {} Expr::Stmt(x) if x.statements.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.pos) } // { stmt; ... } - do not count promotion as dirty because it gets turned back into an array - Expr::Stmt(x) => x.statements = optimize_stmt_block(mem::take(&mut x.statements).into_vec(), state, true).into(), + Expr::Stmt(x) => x.statements = optimize_stmt_block(mem::take(&mut x.statements).into_vec(), state, true, true, false).into(), // lhs.rhs #[cfg(not(feature = "no_object"))] Expr::Dot(x, _) => match (&mut x.lhs, &mut x.rhs) { @@ -785,62 +859,7 @@ fn optimize_top_level( } }); - let orig_constants_len = state.variables.len(); - - // Optimization loop - loop { - state.reset(); - state.restore_var(orig_constants_len); - - let num_statements = statements.len(); - - statements.iter_mut().enumerate().for_each(|(i, stmt)| { - match stmt { - Stmt::Const(value_expr, Ident { name, .. }, _, _) => { - // Load constants - optimize_expr(value_expr, &mut state); - - if value_expr.is_constant() { - state.push_var(name, AccessMode::ReadOnly, value_expr.clone()); - } - } - Stmt::Let(value_expr, Ident { name, pos, .. }, _, _) => { - optimize_expr(value_expr, &mut state); - state.push_var(name, AccessMode::ReadWrite, Expr::Unit(*pos)); - } - _ => { - // Keep all variable declarations at this level - // and always keep the last return value - let keep = match stmt { - Stmt::Let(_, _, _, _) | Stmt::Const(_, _, _, _) => true, - #[cfg(not(feature = "no_module"))] - Stmt::Import(_, _, _) => true, - _ => i >= num_statements - 1, - }; - optimize_stmt(stmt, &mut state, keep); - } - } - }); - - if !state.is_dirty() { - break; - } - } - - // Eliminate code that is pure but always keep the last statement - let last_stmt = statements.pop(); - - // Remove all pure statements at global level - statements.retain(|stmt| !stmt.is_pure()); - - // Add back the last statement unless it is a lone No-op - if let Some(stmt) = last_stmt { - if !statements.is_empty() || !stmt.is_noop() { - statements.push(stmt); - } - } - - statements.shrink_to_fit(); + statements = optimize_stmt_block(statements, &mut state, true, false, true); statements } @@ -895,34 +914,10 @@ pub fn optimize_into_ast( let mut body = fn_def.body.statements.into_vec(); - loop { - // Optimize the function body - let state = &mut State::new(engine, lib2, level); + // Optimize the function body + let state = &mut State::new(engine, lib2, level); - body = optimize_stmt_block(body, state, true); - - match &mut body[..] { - // { return; } -> {} - [Stmt::Return(crate::ast::ReturnType::Return, None, _)] => { - body.clear(); - } - // { ...; return; } -> { ... } - [.., last_stmt, Stmt::Return(crate::ast::ReturnType::Return, None, _)] - if !last_stmt.returns_value() => - { - body.pop().unwrap(); - } - // { ...; return val; } -> { ...; val } - [.., Stmt::Return(crate::ast::ReturnType::Return, expr, pos)] => { - *body.last_mut().unwrap() = if let Some(expr) = expr { - Stmt::Expr(mem::take(expr)) - } else { - Stmt::Noop(*pos) - }; - } - _ => break, - } - } + body = optimize_stmt_block(body, state, true, true, true); fn_def.body = StmtBlock { statements: body.into(), diff --git a/tests/optimizer.rs b/tests/optimizer.rs index 4c7cfb49..7f9d2bd5 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -55,7 +55,13 @@ fn test_optimizer_parse() -> Result<(), Box> { let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?; - assert!(format!("{:?}", ast).starts_with(r#"AST { source: None, body: [Block([Const(BoolConstant(false, 1:20), Ident("DECISION" @ 1:9), false, 1:3), Expr(IntegerConstant(123, 1:53))], 1:1)], functions: Module("#)); + assert!(format!("{:?}", ast).starts_with( + r#"AST { source: None, body: [Expr(IntegerConstant(123, 1:53))], functions: Module("# + )); + + let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?; + + assert!(format!("{:?}", ast).starts_with(r#"AST { source: None, body: [Const(BoolConstant(false, 1:18), Ident("DECISION" @ 1:7), false, 1:1), Expr(IntegerConstant(123, 1:51))], functions: Module("#)); let ast = engine.compile("if 1 == 2 { 42 }")?; From 008ef0a41be3e62b08bbc62bc5ad741f8995d2d1 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 14 Mar 2021 10:47:21 +0800 Subject: [PATCH 09/10] Fix feature builds. --- src/fn_call.rs | 2 ++ src/optimize.rs | 16 ++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/fn_call.rs b/src/fn_call.rs index c2b594d3..f4c3c0e6 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -607,6 +607,7 @@ impl Engine { } // Does a scripted function exist? + #[cfg(not(feature = "no_function"))] #[inline(always)] pub(crate) fn has_script_fn( &self, @@ -734,6 +735,7 @@ impl Engine { } // Scripted function call? + #[cfg(not(feature = "no_function"))] let hash_script = if hash.is_native_only() { None } else { diff --git a/src/optimize.rs b/src/optimize.rs index 7c863747..3cf81224 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -240,20 +240,26 @@ fn optimize_stmt_block( .find_map(|(i, stmt)| match stmt { stmt if !is_pure(stmt) => Some(i), - Stmt::Noop(_) | Stmt::Return(_, None, _) | Stmt::Export(_, _) | Stmt::Share(_) => { - None - } + Stmt::Noop(_) | Stmt::Return(_, None, _) => None, Stmt::Let(e, _, _, _) | Stmt::Const(e, _, _, _) | Stmt::Expr(e) | Stmt::Return(_, Some(e), _) - | Stmt::Import(e, _, _) if e.is_constant() => { None } + #[cfg(not(feature = "no_module"))] + Stmt::Import(e, _, _) if e.is_constant() => None, + + #[cfg(not(feature = "no_module"))] + Stmt::Export(_, _) => None, + + #[cfg(not(feature = "no_closure"))] + Stmt::Share(_) => None, + _ => Some(i), }) .map_or(0, |n| statements.len() - n); @@ -370,8 +376,6 @@ fn optimize_stmt_block( state.set_dirty(); } - println!("{:?}", statements); - statements.shrink_to_fit(); statements } From d9df6aabc85d503f4584b7504c20e8908dcfd49b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 14 Mar 2021 10:47:29 +0800 Subject: [PATCH 10/10] Change precedence to own type. --- src/engine.rs | 8 ++++--- src/engine_settings.rs | 5 +++-- src/parser.rs | 49 +++++++++++++++++++----------------------- src/token.rs | 10 ++++----- 4 files changed, 35 insertions(+), 37 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 07b18e51..334a7c00 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -17,7 +17,7 @@ use crate::stdlib::{ collections::{HashMap, HashSet}, fmt, format, hash::{Hash, Hasher}, - num::{NonZeroU64, NonZeroU8, NonZeroUsize}, + num::{NonZeroU8, NonZeroUsize}, ops::DerefMut, string::{String, ToString}, vec::Vec, @@ -41,6 +41,8 @@ use crate::Map; #[cfg(not(feature = "no_object"))] pub const TYPICAL_MAP_SIZE: usize = 8; // Small maps are typical +pub type Precedence = NonZeroU8; + /// _(INTERNALS)_ A stack of imported [modules][Module]. /// Exported under the `internals` feature only. /// @@ -590,7 +592,7 @@ pub struct Limits { #[cfg(not(feature = "no_function"))] pub max_function_expr_depth: Option, /// Maximum number of operations allowed to run. - pub max_operations: Option, + pub max_operations: Option, /// Maximum number of [modules][Module] allowed to load. /// /// Set to zero to effectively disable loading any [module][Module]. @@ -724,7 +726,7 @@ pub struct Engine { /// A hashset containing symbols to disable. pub(crate) disabled_symbols: HashSet, /// A hashmap containing custom keywords and precedence to recognize. - pub(crate) custom_keywords: HashMap>, + pub(crate) custom_keywords: HashMap>, /// Custom syntax. pub(crate) custom_syntax: HashMap, /// Callback closure for resolving variable access. diff --git a/src/engine_settings.rs b/src/engine_settings.rs index 166f41f0..7ba0f2cc 100644 --- a/src/engine_settings.rs +++ b/src/engine_settings.rs @@ -1,6 +1,7 @@ //! Configuration settings for [`Engine`]. -use crate::stdlib::{format, num::NonZeroU8, string::String}; +use crate::engine::Precedence; +use crate::stdlib::{format, string::String}; use crate::token::Token; use crate::Engine; @@ -272,7 +273,7 @@ impl Engine { keyword: &str, precedence: u8, ) -> Result<&mut Self, String> { - let precedence = NonZeroU8::new(precedence); + let precedence = Precedence::new(precedence); if precedence.is_none() { return Err("precedence cannot be zero".into()); diff --git a/src/parser.rs b/src/parser.rs index 171976e3..12a714a8 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -5,7 +5,7 @@ use crate::ast::{ Stmt, StmtBlock, }; use crate::dynamic::{AccessMode, Union}; -use crate::engine::{KEYWORD_THIS, OP_CONTAINS}; +use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS}; use crate::module::NamespaceRef; use crate::optimize::optimize_into_ast; use crate::optimize::OptimizationLevel; @@ -1611,7 +1611,7 @@ fn parse_binary_op( input: &mut TokenStream, state: &mut ParseState, lib: &mut FunctionsLib, - parent_precedence: u8, + parent_precedence: Option, lhs: Expr, mut settings: ParseSettings, ) -> Result { @@ -1625,18 +1625,12 @@ fn parse_binary_op( loop { let (current_op, current_pos) = input.peek().unwrap(); let precedence = match current_op { - Token::Custom(c) => { - if state - .engine - .custom_keywords - .get(c) - .map_or(false, Option::is_some) - { - state.engine.custom_keywords.get(c).unwrap().unwrap().get() - } else { - return Err(PERR::Reserved(c.clone()).into_err(*current_pos)); - } - } + Token::Custom(c) => state + .engine + .custom_keywords + .get(c) + .cloned() + .ok_or_else(|| PERR::Reserved(c.clone()).into_err(*current_pos))?, Token::Reserved(c) if !is_valid_identifier(c.chars()) => { return Err(PERR::UnknownOperator(c.into()).into_err(*current_pos)) } @@ -1656,18 +1650,12 @@ fn parse_binary_op( let (next_op, next_pos) = input.peek().unwrap(); let next_precedence = match next_op { - Token::Custom(c) => { - if state - .engine - .custom_keywords - .get(c) - .map_or(false, Option::is_some) - { - state.engine.custom_keywords.get(c).unwrap().unwrap().get() - } else { - return Err(PERR::Reserved(c.clone()).into_err(*next_pos)); - } - } + Token::Custom(c) => state + .engine + .custom_keywords + .get(c) + .cloned() + .ok_or_else(|| PERR::Reserved(c.clone()).into_err(*next_pos))?, Token::Reserved(c) if !is_valid_identifier(c.chars()) => { return Err(PERR::UnknownOperator(c.into()).into_err(*next_pos)) } @@ -1937,7 +1925,14 @@ fn parse_expr( // Parse expression normally. let lhs = parse_unary(input, state, lib, settings.level_up())?; - parse_binary_op(input, state, lib, 1, lhs, settings.level_up()) + parse_binary_op( + input, + state, + lib, + Precedence::new(1), + lhs, + settings.level_up(), + ) } /// Make sure that the expression is not a statement expression (i.e. wrapped in `{}`). diff --git a/src/token.rs b/src/token.rs index 0d68c460..909442c3 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1,8 +1,8 @@ //! Main module defining the lexer and parser. use crate::engine::{ - KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, - KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, + Precedence, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, + KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, }; use crate::stdlib::{ borrow::Cow, @@ -666,10 +666,10 @@ impl Token { } /// Get the precedence number of the token. - pub fn precedence(&self) -> u8 { + pub fn precedence(&self) -> Option { use Token::*; - match self { + Precedence::new(match self { // Assignments are not considered expressions - set to zero Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | PowerOfAssign | LeftShiftAssign | RightShiftAssign | AndAssign | OrAssign | XOrAssign @@ -696,7 +696,7 @@ impl Token { Period => 240, _ => 0, - } + }) } /// Does an expression bind to the right (instead of left)?