diff --git a/src/any.rs b/src/any.rs index 156e8cba..a96bcca2 100644 --- a/src/any.rs +++ b/src/any.rs @@ -145,6 +145,8 @@ impl dyn Variant { pub struct Dynamic(pub(crate) Union); /// Internal `Dynamic` representation. +/// +/// Most variants are boxed to reduce the size. pub enum Union { Unit(()), Bool(bool), @@ -330,6 +332,12 @@ fn cast_box(item: Box) -> Result, Box> { impl Dynamic { /// Create a `Dynamic` from any type. A `Dynamic` value is simply returned as is. /// + /// # Safety + /// + /// This type uses some unsafe code, mainly for type casting. + /// + /// # Notes + /// /// Beware that you need to pass in an `Array` type for it to be recognized as an `Array`. /// A `Vec` does not get automatically converted to an `Array`, but will be a generic /// restricted trait object instead, because `Vec` is not a supported standard type. diff --git a/src/api.rs b/src/api.rs index db4b00c8..1918b487 100644 --- a/src/api.rs +++ b/src/api.rs @@ -10,6 +10,7 @@ use crate::parser::{parse, parse_global_expr, AST}; use crate::result::EvalAltResult; use crate::scope::Scope; use crate::token::{lex, Position}; +use crate::utils::StaticVec; #[cfg(not(feature = "no_object"))] use crate::engine::Map; @@ -20,7 +21,6 @@ use crate::stdlib::{ collections::HashMap, mem, string::{String, ToString}, - vec::Vec, }; #[cfg(not(feature = "no_std"))] use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf}; @@ -994,7 +994,7 @@ impl Engine { args: A, ) -> Result> { let mut arg_values = args.into_vec(); - let mut args: Vec<_> = arg_values.iter_mut().collect(); + let mut args: StaticVec<_> = arg_values.iter_mut().collect(); let fn_lib = ast.fn_lib(); let pos = Position::none(); @@ -1004,7 +1004,7 @@ impl Engine { let state = State::new(fn_lib); - let result = self.call_script_fn(Some(scope), &state, fn_def, &mut args, pos, 0)?; + let result = self.call_script_fn(Some(scope), &state, fn_def, args.as_mut(), pos, 0)?; let return_type = self.map_type_name(result.type_name()); diff --git a/src/engine.rs b/src/engine.rs index 7b29129c..8428de09 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -109,12 +109,12 @@ impl Target<'_> { .as_char() .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; - let mut chars: Vec = s.chars().collect(); - let ch = chars[x.1]; + let mut chars: StaticVec = s.chars().collect(); + let ch = *chars.get_ref(x.1); // See if changed - if so, update the String if ch != new_ch { - chars[x.1] = new_ch; + *chars.get_mut(x.1) = new_ch; s.clear(); chars.iter().for_each(|&ch| s.push(ch)); } @@ -194,7 +194,7 @@ pub struct FunctionsLib(HashMap); impl FunctionsLib { /// Create a new `FunctionsLib` from a collection of `FnDef`. - pub fn from_vec(vec: Vec) -> Self { + pub fn from_iter(vec: impl IntoIterator) -> Self { FunctionsLib( vec.into_iter() .map(|fn_def| { @@ -446,7 +446,7 @@ fn search_scope<'a>( #[cfg(not(feature = "no_module"))] { if let Some((modules, hash)) = modules { - let (id, root_pos) = modules.get(0); + let (id, root_pos) = modules.get_ref(0); let module = if let Some(index) = modules.index() { scope @@ -666,7 +666,7 @@ impl Engine { /// Function call arguments may be _consumed_ when the function requires them to be passed by value. /// All function arguments not in the first position are always passed by value and thus consumed. /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! - pub(crate) fn call_script_fn( + pub(crate) fn call_script_fn<'a>( &self, scope: Option<&mut Scope>, state: &State, @@ -890,9 +890,9 @@ impl Engine { match rhs { // xxx.fn_name(arg_expr_list) Expr::FnCall(x) if x.1.is_none() => { - let ((name, pos), modules, hash, args, def_val) = x.as_ref(); + let ((name, pos), _, hash, _, def_val) = x.as_ref(); - let mut args: Vec<_> = once(obj) + let mut args: StaticVec<_> = once(obj) .chain( idx_val .downcast_mut::>() @@ -902,7 +902,7 @@ impl Engine { .collect(); // A function call is assumed to have side effects, so the value is changed // TODO - Remove assumption of side effects by checking whether the first parameter is &mut - self.exec_fn_call(state, name, *hash, &mut args, def_val.as_ref(), *pos, 0) + self.exec_fn_call(state, name, *hash, args.as_mut(), def_val.as_ref(), *pos, 0) .map(|v| (v, true)) } // xxx.module::fn_name(...) - syntax error @@ -1394,9 +1394,9 @@ impl Engine { let mut arg_values = args_expr .iter() .map(|expr| self.eval_expr(scope, state, expr, level)) - .collect::, _>>()?; + .collect::, _>>()?; - let mut args: Vec<_> = arg_values.iter_mut().collect(); + let mut args: StaticVec<_> = arg_values.iter_mut().collect(); let hash_fn_spec = calc_fn_hash(empty(), KEYWORD_EVAL, once(TypeId::of::())); @@ -1410,7 +1410,7 @@ impl Engine { // Evaluate the text string as a script let result = - self.eval_script_expr(scope, state, args[0], args_expr[0].position()); + self.eval_script_expr(scope, state, args.pop(), args_expr[0].position()); if scope.len() != prev_len { // IMPORTANT! If the eval defines new variables in the current scope, @@ -1425,7 +1425,7 @@ impl Engine { state, name, *hash_fn_def, - &mut args, + args.as_mut(), def_val.as_ref(), *pos, level, @@ -1442,11 +1442,11 @@ impl Engine { let mut arg_values = args_expr .iter() .map(|expr| self.eval_expr(scope, state, expr, level)) - .collect::, _>>()?; + .collect::, _>>()?; - let mut args: Vec<_> = arg_values.iter_mut().collect(); + let mut args: StaticVec<_> = arg_values.iter_mut().collect(); - let (id, root_pos) = modules.get(0); // First module + let (id, root_pos) = modules.get_ref(0); // First module let module = if let Some(index) = modules.index() { scope @@ -1462,7 +1462,7 @@ impl Engine { // First search in script-defined functions (can override built-in) if let Some(fn_def) = module.get_qualified_scripted_fn(*hash_fn_def) { - self.call_script_fn(None, state, fn_def, &mut args, *pos, level) + self.call_script_fn(None, state, fn_def, args.as_mut(), *pos, level) } else { // Then search in Rust functions @@ -1476,7 +1476,7 @@ impl Engine { let hash = *hash_fn_def ^ hash_fn_args; match module.get_qualified_fn(name, hash, *pos) { - Ok(func) => func(&mut args, *pos), + Ok(func) => func(args.as_mut(), *pos), Err(_) if def_val.is_some() => Ok(def_val.clone().unwrap()), Err(err) => Err(err), } diff --git a/src/fn_call.rs b/src/fn_call.rs index c64ba035..e1da767b 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -3,14 +3,14 @@ #![allow(non_snake_case)] use crate::any::{Dynamic, Variant}; -use crate::stdlib::vec::Vec; +use crate::utils::StaticVec; /// Trait that represents arguments to a function call. /// Any data type that can be converted into a `Vec` can be used /// as arguments to a function call. pub trait FuncArgs { /// Convert to a `Vec` of the function call arguments. - fn into_vec(self) -> Vec; + fn into_vec(self) -> StaticVec; } /// Macro to implement `FuncArgs` for tuples of standard types (each can be @@ -19,11 +19,11 @@ macro_rules! impl_args { ($($p:ident),*) => { impl<$($p: Variant + Clone),*> FuncArgs for ($($p,)*) { - fn into_vec(self) -> Vec { + fn into_vec(self) -> StaticVec { let ($($p,)*) = self; #[allow(unused_mut)] - let mut v = Vec::new(); + let mut v = StaticVec::new(); $(v.push($p.into_dynamic());)* v diff --git a/src/optimize.rs b/src/optimize.rs index 09f294c6..ca18d1ce 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -704,40 +704,29 @@ pub fn optimize_into_ast( const fn_lib: &[(&str, usize)] = &[]; #[cfg(not(feature = "no_function"))] - let lib = FunctionsLib::from_vec( - functions - .iter() - .cloned() - .map(|mut fn_def| { - if !level.is_none() { - let pos = fn_def.body.position(); + let lib = FunctionsLib::from_iter(functions.iter().cloned().map(|mut fn_def| { + if !level.is_none() { + let pos = fn_def.body.position(); - // Optimize the function body - let mut body = - optimize(vec![fn_def.body], engine, &Scope::new(), &fn_lib, level); + // Optimize the function body + let mut body = optimize(vec![fn_def.body], engine, &Scope::new(), &fn_lib, level); - // {} -> Noop - fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) { - // { return val; } -> val - Stmt::ReturnWithVal(x) - if x.1.is_some() && (x.0).0 == ReturnType::Return => - { - Stmt::Expr(Box::new(x.1.unwrap())) - } - // { return; } -> () - Stmt::ReturnWithVal(x) - if x.1.is_none() && (x.0).0 == ReturnType::Return => - { - Stmt::Expr(Box::new(Expr::Unit((x.0).1))) - } - // All others - stmt => stmt, - }; + // {} -> Noop + fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) { + // { return val; } -> val + Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => { + Stmt::Expr(Box::new(x.1.unwrap())) } - fn_def - }) - .collect(), - ); + // { return; } -> () + Stmt::ReturnWithVal(x) if x.1.is_none() && (x.0).0 == ReturnType::Return => { + Stmt::Expr(Box::new(Expr::Unit((x.0).1))) + } + // All others + stmt => stmt, + }; + } + fn_def + })); #[cfg(feature = "no_function")] let lib: FunctionsLib = Default::default(); diff --git a/src/parser.rs b/src/parser.rs index 8041dcef..aebcf037 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -267,6 +267,9 @@ impl DerefMut for Stack { } /// A statement. +/// +/// Each variant is at most one pointer in size (for speed), +/// with everything being allocated together in one single tuple. #[derive(Debug, Clone)] pub enum Stmt { /// No-op. @@ -368,6 +371,9 @@ type MRef = Option>; type MRef = Option; /// An expression. +/// +/// Each variant is at most one pointer in size (for speed), +/// with everything being allocated together in one single tuple. #[derive(Debug, Clone)] pub enum Expr { /// Integer constant. @@ -718,7 +724,7 @@ fn parse_call_expr<'a>( #[cfg(not(feature = "no_module"))] let hash_fn_def = { if let Some(modules) = modules.as_mut() { - modules.set_index(stack.find_module(&modules.get(0).0)); + modules.set_index(stack.find_module(&modules.get_ref(0).0)); // Rust functions are indexed in two steps: // 1) Calculate a hash in a similar manner to script-defined functions, @@ -758,7 +764,7 @@ fn parse_call_expr<'a>( #[cfg(not(feature = "no_module"))] let hash_fn_def = { if let Some(modules) = modules.as_mut() { - modules.set_index(stack.find_module(&modules.get(0).0)); + modules.set_index(stack.find_module(&modules.get_ref(0).0)); // Rust functions are indexed in two steps: // 1) Calculate a hash in a similar manner to script-defined functions, @@ -1232,7 +1238,7 @@ fn parse_primary<'a>( // Qualifiers + variable name *hash = calc_fn_hash(modules.iter().map(|(v, _)| v.as_str()), name, empty()); - modules.set_index(stack.find_module(&modules.get(0).0)); + modules.set_index(stack.find_module(&modules.get_ref(0).0)); } _ => (), } @@ -1440,7 +1446,7 @@ fn make_dot_expr( #[cfg(feature = "no_module")] unreachable!(); #[cfg(not(feature = "no_module"))] - return Err(PERR::PropertyExpected.into_err(x.1.unwrap().get(0).1)); + return Err(PERR::PropertyExpected.into_err(x.1.unwrap().get_ref(0).1)); } // lhs.dot_lhs.dot_rhs (lhs, Expr::Dot(x)) => { diff --git a/src/scope.rs b/src/scope.rs index 542bb01b..9f47b446 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -7,7 +7,7 @@ use crate::token::Position; #[cfg(not(feature = "no_module"))] use crate::module::Module; -use crate::stdlib::{borrow::Cow, boxed::Box, iter, vec::Vec}; +use crate::stdlib::{borrow::Cow, boxed::Box, iter, string::String, vec::Vec}; /// Type of an entry in the Scope. #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] diff --git a/src/utils.rs b/src/utils.rs index 2d0ae237..7abe88ab 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,9 +1,12 @@ //! Module containing various utility types and functions. +// +// TODO - remove unsafe code use crate::stdlib::{ any::TypeId, fmt, hash::{Hash, Hasher}, + iter::FromIterator, mem, vec::Vec, }; @@ -23,7 +26,7 @@ pub fn EMPTY_TYPE_ID() -> TypeId { /// Module names are passed in via `&str` references from an iterator. /// Parameter types are passed in via `TypeId` values from an iterator. /// -/// ### Note +/// # Note /// /// The first module name is skipped. Hashing starts from the _second_ module in the chain. pub fn calc_fn_spec<'a>( @@ -47,8 +50,12 @@ pub fn calc_fn_spec<'a>( /// /// This is essentially a knock-off of the [`staticvec`](https://crates.io/crates/staticvec) crate. /// This simplified implementation here is to avoid pulling in another crate. -#[derive(Clone, Hash, Default)] -pub struct StaticVec { +/// +/// # Safety +/// +/// This type uses some unsafe code (mainly to zero out unused array slots) for efficiency. +#[derive(Clone, Hash)] +pub struct StaticVec { /// Total number of values held. len: usize, /// Static storage. 4 slots should be enough for most cases - i.e. four levels of indirection. @@ -57,14 +64,44 @@ pub struct StaticVec { more: Vec, } -impl StaticVec { +impl Default for StaticVec { + fn default() -> Self { + Self { + len: 0, + list: unsafe { mem::MaybeUninit::zeroed().assume_init() }, + more: Vec::new(), + } + } +} + +impl FromIterator for StaticVec { + fn from_iter>(iter: X) -> Self { + let mut vec = StaticVec::new(); + + for x in iter { + vec.push(x); + } + + vec + } +} + +impl StaticVec { /// Create a new `StaticVec`. pub fn new() -> Self { Default::default() } /// Push a new value to the end of this `StaticVec`. pub fn push>(&mut self, value: X) { - if self.len >= self.list.len() { + if self.len == self.list.len() { + // Move the fixed list to the Vec + for x in 0..self.list.len() { + let def_val: T = unsafe { mem::MaybeUninit::zeroed().assume_init() }; + self.more + .push(mem::replace(self.list.get_mut(x).unwrap(), def_val)); + } + self.more.push(value.into()); + } else if self.len > self.list.len() { self.more.push(value.into()); } else { self.list[self.len] = value.into(); @@ -80,9 +117,19 @@ impl StaticVec { let result = if self.len <= 0 { panic!("nothing to pop!") } else if self.len <= self.list.len() { - mem::take(self.list.get_mut(self.len - 1).unwrap()) + let def_val: T = unsafe { mem::MaybeUninit::zeroed().assume_init() }; + mem::replace(self.list.get_mut(self.len - 1).unwrap(), def_val) } else { - self.more.pop().unwrap() + let r = self.more.pop().unwrap(); + + // Move back to the fixed list + if self.more.len() == self.list.len() { + for x in 0..self.list.len() { + self.list[self.list.len() - 1 - x] = self.more.pop().unwrap(); + } + } + + r }; self.len -= 1; @@ -93,48 +140,99 @@ impl StaticVec { pub fn len(&self) -> usize { self.len } - /// Get an item at a particular index. + /// Get a reference to the item at a particular index. /// /// # Panics /// /// Panics if the index is out of bounds. - pub fn get(&self, index: usize) -> &T { + pub fn get_ref(&self, index: usize) -> &T { if index >= self.len { panic!("index OOB in StaticVec"); } - if index < self.list.len() { + if self.len < self.list.len() { self.list.get(index).unwrap() } else { - self.more.get(index - self.list.len()).unwrap() + self.more.get(index).unwrap() + } + } + /// Get a mutable reference to the item at a particular index. + /// + /// # Panics + /// + /// Panics if the index is out of bounds. + pub fn get_mut(&mut self, index: usize) -> &mut T { + if index >= self.len { + panic!("index OOB in StaticVec"); + } + + if self.len < self.list.len() { + self.list.get_mut(index).unwrap() + } else { + self.more.get_mut(index).unwrap() } } /// Get an iterator to entries in the `StaticVec`. pub fn iter(&self) -> impl Iterator { - let num = if self.len >= self.list.len() { - self.list.len() + if self.len > self.list.len() { + self.more.iter() } else { - self.len - }; - - self.list[..num].iter().chain(self.more.iter()) + self.list[..self.len].iter() + } } /// Get a mutable iterator to entries in the `StaticVec`. pub fn iter_mut(&mut self) -> impl Iterator { - let num = if self.len >= self.list.len() { - self.list.len() + if self.len > self.list.len() { + self.more.iter_mut() } else { - self.len - }; - - self.list[..num].iter_mut().chain(self.more.iter_mut()) + self.list[..self.len].iter_mut() + } } } -impl fmt::Debug for StaticVec { +impl StaticVec { + /// Get the item at a particular index. + /// + /// # Panics + /// + /// Panics if the index is out of bounds. + pub fn get(&self, index: usize) -> T { + if index >= self.len { + panic!("index OOB in StaticVec"); + } + + if self.len < self.list.len() { + *self.list.get(index).unwrap() + } else { + *self.more.get(index).unwrap() + } + } +} + +impl fmt::Debug for StaticVec { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "[ ")?; self.iter().try_for_each(|v| write!(f, "{:?}, ", v))?; write!(f, "]") } } + +impl AsRef<[T]> for StaticVec { + fn as_ref(&self) -> &[T] { + if self.len > self.list.len() { + &self.more[..] + } else { + &self.list[..self.len] + } + } +} + +impl AsMut<[T]> for StaticVec { + fn as_mut(&mut self) -> &mut [T] { + if self.len > self.list.len() { + &mut self.more[..] + } else { + &mut self.list[..self.len] + } + } +}