diff --git a/src/common/stack.rs b/src/common/stack.rs index 65fcf3a..7482d5a 100644 --- a/src/common/stack.rs +++ b/src/common/stack.rs @@ -26,26 +26,6 @@ impl StackWithLimit { /// Create a StackWithLimit with `limit` max size and `initial_size` of pre-allocated /// memory. /// - /// ``` - /// # extern crate wasmi; - /// # use wasmi::{StackWithLimit, StackSize, FuncRef}; - /// StackWithLimit::::new( - /// StackSize::from_element_count(1024).into_initial(), - /// StackSize::from_element_count(2048).into_limit(), - /// ); - /// ``` - /// - /// Unlimited - /// - /// ``` - /// # extern crate wasmi; - /// # use wasmi::{StackWithLimit, StackSize, RuntimeValue}; - /// StackWithLimit::::new( - /// StackSize::from_element_count(1024).into_initial(), - /// StackSize::unlimited(), - /// ); - /// ``` - /// /// # Panics /// /// In debug mode, panics if `initial_size` is larger than `limit`. @@ -63,12 +43,6 @@ impl StackWithLimit { } /// Create an new StackWithLimit with `limit` max size and `limit` elements pre-allocated - /// - /// ``` - /// # extern crate wasmi; - /// # use wasmi::{StackWithLimit, StackSize}; - /// let bstack = StackWithLimit::::with_size(StackSize::from_element_count(2)); - /// ``` pub fn with_size(size: StackSize) -> StackWithLimit { StackWithLimit::new(StackSizeInitial(size), StackSizeLimit(size)) } @@ -102,21 +76,11 @@ impl StackWithLimit { } /// Remove item from the top of a stack and return it, or `None` if the stack is empty. - /// - /// ``` - /// # extern crate wasmi; - /// # use wasmi::{StackWithLimit, StackSize}; - /// let mut bstack = StackWithLimit::::with_size(StackSize::from_element_count(2)); - /// bstack.push(2); - /// assert_eq!(bstack.pop(), Some(2)); - /// assert_eq!(bstack.len(), 0); - /// assert_eq!(bstack.pop(), None); - /// ``` pub fn pop(&mut self) -> Option { self.stack.pop() } - /// Remove and Return top element. Does not check for emptyness. + /// Remove and return top element. Does not check for emptiness. /// If this is called on a zero length stack, bad things will happen. /// Do not call this method unless you can prove the stack has length. #[inline] @@ -132,15 +96,6 @@ impl StackWithLimit { /// `bstack.nth_from_top(0)` gets the top of the stack /// /// `bstack.nth_from_top(1)` gets the item just below the stack - /// - /// ``` - /// # extern crate wasmi; - /// # use wasmi::{StackWithLimit, StackSize}; - /// let mut bstack = StackWithLimit::::with_size(StackSize::from_element_count(2)); - /// bstack.push(4); - /// assert_eq!(bstack.nth_from_top(0), Some(&4)); - /// assert_eq!(bstack.nth_from_top(1), None); - /// ``` pub fn nth_from_top(&self, depth: usize) -> Option<&T> { // Be cognizant of integer underflow and overflow here. Both are possible in this situation. // len() is unsigned, so if len() == 0, subtraction is a problem @@ -176,54 +131,12 @@ impl StackWithLimit { /// # Panics /// /// Panics if `a` or `b` are out of bound. - /// - /// ``` - /// # extern crate wasmi; - /// # use wasmi::{StackWithLimit, StackSize}; - /// let mut bstack = StackWithLimit::::with_size(StackSize::from_element_count(2)); - /// bstack.push(1); - /// bstack.push(2); - /// assert_eq!(bstack.top(), Some(&2)); - /// bstack.swap(0, 1); - /// assert_eq!(bstack.top(), Some(&1)); - /// ``` #[inline] pub fn swap(&mut self, a: usize, b: usize) { self.stack.swap(a, b) } - /// Removes an element from the stack and returns it. - /// - /// The removed element is replaced by the element at the top of the stack. - /// - /// # Panics - /// - /// Panics if `index` is out of bounds. - /// - /// ``` - /// # extern crate wasmi; - /// # use wasmi::{StackWithLimit, StackSize}; - /// let mut bstack = StackWithLimit::::with_size(StackSize::from_element_count(2)); - /// bstack.push(1); - /// bstack.push(2); - /// assert_eq!(bstack.top(), Some(&2)); - /// assert_eq!(bstack.swap_remove(0), 1); - /// assert_eq!(bstack.top(), Some(&2)); - /// ``` - pub fn swap_remove(&mut self, index: usize) -> T { - self.stack.swap_remove(index) - } - /// Get a reference to the top of the stack, or `None` if the stack is empty. - /// - /// ``` - /// # extern crate wasmi; - /// # use wasmi::{StackWithLimit, StackSize}; - /// let mut bstack = StackWithLimit::::with_size(StackSize::from_element_count(2)); - /// assert_eq!(bstack.top(), None); - /// bstack.push(2); - /// assert_eq!(bstack.top(), Some(&2)); - /// ``` pub fn top(&self) -> Option<&T> { self.stack.last() } @@ -241,55 +154,17 @@ impl StackWithLimit { } /// Get number of items in a stack. - /// - /// ``` - /// # extern crate wasmi; - /// # use wasmi::{StackWithLimit, StackSize}; - /// let mut bstack = StackWithLimit::::with_size(StackSize::from_element_count(2)); - /// assert_eq!(bstack.len(), 0); - /// bstack.push(1); - /// bstack.push(2); - /// assert_eq!(bstack.len(), 2); - /// ``` #[inline] pub fn len(&self) -> usize { self.stack.len() } /// Check whether the stack is empty. - /// - /// ``` - /// # extern crate wasmi; - /// # use wasmi::{StackWithLimit, StackSize}; - /// let mut bstack = StackWithLimit::::with_size(StackSize::from_element_count(2)); - /// assert_eq!(bstack.is_empty(), true); - /// bstack.push(1); - /// assert_eq!(bstack.is_empty(), false); - /// ``` pub fn is_empty(&self) -> bool { self.stack.is_empty() } } -// Why introduce the extra complexity of StackSizeLimit, StackSizeInitial, and StackSize? -// We want to make the user to do the correct thing using the type checker. -// Check out the excellent Rust API Guidelines (linked below) for suggestions -// about type safety in rust. -// -// By introducing the new typed arguments, we turn: -// -// ``` -// pub fn new(initial_size: usize, limit: usize) -> StackWithLimit { ... } -// ``` -// -// into: -// -// ``` -// pub fn new(initial_size: StackSizeInitial, limit: StackSizeLimit) -> StackWithLimit { ... } -// ``` -// -// https://rust-lang-nursery.github.io/api-guidelines/type-safety.html#c-custom-type - /// Type for communicating the size of some contigous container. /// Used for constructing both [`StackSizeLimit`] and [`StackSizeInitial`]. #[derive(Eq, PartialEq, Hash, Debug)] @@ -314,13 +189,6 @@ impl Copy for StackSize {} impl StackSize { /// Create StackSize based on number of elements. - /// - /// ``` - /// # extern crate wasmi; - /// # use wasmi::StackSize; - /// let ss = StackSize::<(u8, u8)>::from_element_count(10); - /// assert_eq!(ss.element_count(), 10); - /// ``` pub fn from_element_count(num_elements: usize) -> StackSize { StackSize { num_elements, @@ -328,51 +196,12 @@ impl StackSize { } } - /// Compute StackSize based on allowable memory. - /// - /// ``` - /// # extern crate wasmi; - /// # use wasmi::StackSize; - /// let ss = StackSize::<(u8, u8)>::from_byte_count(10); - /// assert_eq!(ss.element_count(), 10 / 2); - /// ``` - /// - /// # Errors - /// - /// In debug mode, panics if size of `T` is 0. - pub fn from_byte_count(num_bytes: usize) -> StackSize { - // This debug_assert should catch logical errors. - debug_assert!(::core::mem::size_of::() != 0, "That doesn't make sense."); - - // In case a zero sized T still makes it into prod. We assume unlimited stack - // size instead of panicking. - let element_count = if ::core::mem::size_of::() != 0 { - num_bytes / ::core::mem::size_of::() - } else { - usize::MAX // Semi-relevant fun fact: Vec::<()>::new().capacity() == usize::MAX - }; - - StackSize::from_element_count(element_count) - } - /// Return number the of elements this StackSize indicates. - /// - /// ``` - /// # use wasmi::StackSize; - /// let ss = StackSize::<(u8, u8)>::from_element_count(10); - /// assert_eq!(ss.element_count(), 10); - /// ``` - /// pub fn element_count(&self) -> usize { self.num_elements } /// Create StackSizeLimit out of self - /// - /// ``` - /// # use wasmi::{StackSize, StackSizeLimit, RuntimeValue}; - /// let values_limit: StackSizeLimit = StackSize::from_element_count(1024).into_limit(); - /// ``` pub fn into_limit(self) -> StackSizeLimit { StackSizeLimit(self) } @@ -381,11 +210,6 @@ impl StackSize { pub fn into_initial(self) -> StackSizeInitial { StackSizeInitial(self) } - - /// Create StackSizeLimit with no upper bound. - pub fn unlimited() -> StackSizeLimit { - StackSize::from_element_count(usize::MAX).into_limit() - } } /// Max size a stack may become. @@ -492,4 +316,61 @@ mod test { assert_eq!(unsafe { bstack.pop_unchecked() }, 0); assert_eq!(unsafe { bstack.pop_unchecked() }, 8); } + + #[test] + fn misc() { + // These used to be doctests. They were moved here because StackWithLimit no longer + // public. Doctests can't test internal APIs. + + StackWithLimit::::new( + StackSize::from_element_count(1024).into_initial(), + StackSize::from_element_count(2048).into_limit(), + ); + + let bstack = StackWithLimit::::with_size(StackSize::from_element_count(2)); + + let mut bstack = StackWithLimit::::with_size(StackSize::from_element_count(2)); + bstack.push(2).unwrap(); + assert_eq!(bstack.pop(), Some(2)); + assert_eq!(bstack.len(), 0); + assert_eq!(bstack.pop(), None); + + let mut bstack = StackWithLimit::::with_size(StackSize::from_element_count(2)); + bstack.push(4).unwrap(); + assert_eq!(bstack.nth_from_top(0), Some(&4)); + assert_eq!(bstack.nth_from_top(1), None); + + let mut bstack = StackWithLimit::::with_size(StackSize::from_element_count(2)); + bstack.push(1).unwrap(); + bstack.push(2).unwrap(); + assert_eq!(bstack.top(), Some(&2)); + bstack.swap(0, 1); + assert_eq!(bstack.top(), Some(&1)); + + let mut bstack = StackWithLimit::::with_size(StackSize::from_element_count(2)); + bstack.push(1).unwrap(); + bstack.push(2).unwrap(); + assert_eq!(bstack.top(), Some(&2)); + bstack.swap(0, 1); + assert_eq!(bstack.top(), Some(&1)); + + let mut bstack = StackWithLimit::::with_size(StackSize::from_element_count(2)); + assert_eq!(bstack.len(), 0); + bstack.push(1).unwrap(); + bstack.push(2).unwrap(); + assert_eq!(bstack.len(), 2); + + let mut bstack = StackWithLimit::::with_size(StackSize::from_element_count(2)); + assert_eq!(bstack.is_empty(), true); + bstack.push(1).unwrap(); + assert_eq!(bstack.is_empty(), false); + + let ss = StackSize::<(u8, u8)>::from_element_count(10); + assert_eq!(ss.element_count(), 10); + + let ss = StackSize::<(u8, u8)>::from_element_count(10); + assert_eq!(ss.element_count(), 10); + + let values_limit: StackSizeLimit = StackSize::from_element_count(1024).into_limit(); + } } diff --git a/src/lib.rs b/src/lib.rs index c6b75dc..a8dd422 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -404,8 +404,7 @@ pub use self::module::{ModuleInstance, ModuleRef, ExternVal, NotStartedModuleRef pub use self::global::{GlobalInstance, GlobalRef}; pub use self::func::{FuncInstance, FuncRef, FuncInvocation, ResumableError}; pub use self::types::{Signature, ValueType, GlobalDescriptor, TableDescriptor, MemoryDescriptor}; -pub use self::common::stack::{StackWithLimit, StackSize, StackSizeLimit, StackSizeInitial}; -pub use self::runner::Interpreter; +pub use self::runner::{Interpreter, InterpreterStackConfig}; /// WebAssembly-specific sizes and units. pub mod memory_units { diff --git a/src/runner.rs b/src/runner.rs index edead57..5edfb45 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -19,11 +19,8 @@ use value::{ }; use {Signature, Trap, TrapKind, ValueType}; -/// Maximum number of entries in value stack. -pub const DEFAULT_VALUE_STACK_LIMIT: usize = (1024 * 1024) / ::core::mem::size_of::(); - -// TODO: Make these parameters changeble. -pub const DEFAULT_CALL_STACK_LIMIT: usize = 64 * 1024; +const DEFAULT_VALUE_STACK_LIMIT: usize = (1024 * 1024) / 8; +const DEFAULT_CALL_STACK_LIMIT: usize = 64 * 1024; /// This is a wrapper around u64 to allow us to treat runtime values as a tag-free `u64` /// (where if the runtime value is <64 bits the upper bits are 0). This is safe, since @@ -37,7 +34,7 @@ pub const DEFAULT_CALL_STACK_LIMIT: usize = 64 * 1024; /// at these boundaries. #[derive(Copy, Clone, Debug, PartialEq, Default)] #[repr(transparent)] -pub struct RuntimeValueInternal(pub u64); +struct RuntimeValueInternal(pub u64); impl RuntimeValueInternal { pub fn with_type(self, ty: ValueType) -> RuntimeValue { @@ -164,6 +161,54 @@ enum RunResult { NestedCall(FuncRef), } +/// Stack pre-allocation and size limit settings for [`Interpreter`]. +/// +/// Sizes indicate number of elements, not number of bytes. +/// +/// When an interpreter exceeds the size limit on either stack, [`Trapkind::StackOverflow`] is returned. +/// +/// The following example initializes an Interpreter with space pre-allocated for 1024 +/// values and 1024 function calls. The value stack has a maximum size of +/// 2048 and the call stack has unlimited size (bounded only by available addressable memory). +/// +/// ``` +/// # extern crate wasmi; +/// # use wasmi::{Interpreter, InterpreterStackConfig}; +/// use std::usize; +/// let config = InterpreterStackConfig { +/// value_stack_size_initial: 1024, +/// value_stack_size_limit: 2048, +/// call_stack_size_initial: 1024, +/// call_stack_size_limit: usize::MAX, +/// }; +/// let interpreter = Interpreter::with_stacks(config); +/// ``` +/// +/// [`Interpreter`]: struct.Interpreter.html +/// [`Trapkind::StackOverflow`]: enum.TrapKind.html +#[derive(Clone, PartialEq, PartialOrd, Debug)] +pub struct InterpreterStackConfig { + /// Number of spots to be pre-allocated for the value stack. + pub value_stack_size_initial: usize, + /// Maximum nuber of elements allowed in the value stack. + pub value_stack_size_limit: usize, + /// Number of spots to be pre-allocated for the call stack. + pub call_stack_size_initial: usize, + /// Maximum nuber of elements allowed in the value stack. + pub call_stack_size_limit: usize, +} + +impl Default for InterpreterStackConfig { + fn default() -> Self { + InterpreterStackConfig { + value_stack_size_initial: DEFAULT_VALUE_STACK_LIMIT, + value_stack_size_limit: DEFAULT_VALUE_STACK_LIMIT, + call_stack_size_initial: DEFAULT_CALL_STACK_LIMIT, + call_stack_size_limit: DEFAULT_CALL_STACK_LIMIT, + } + } +} + /// Function interpreter. pub struct Interpreter { value_stack: ValueStack, @@ -172,36 +217,46 @@ pub struct Interpreter { } impl Interpreter { - /// Initialize an interpreter with defaults stack sizes. + /// Initialize an interpreter with default stack sizes. pub fn new() -> Interpreter { - Interpreter::with_stacks( - StackWithLimit::with_size(StackSize::from_element_count(DEFAULT_VALUE_STACK_LIMIT)), - StackWithLimit::with_size(StackSize::from_element_count(DEFAULT_CALL_STACK_LIMIT)), - ) + Interpreter::with_stacks(InterpreterStackConfig::default()) } - /// Initialize an interpreter that will use `value_stack` and `call_stack`. + /// Initialize an interpreter with value stack and call stack configured according to + /// `stack_sizes`. /// - /// `value_stack` `call_stack` determine the allowed stack size during later executions. + /// # Panics + /// + /// In debug mode, panics if `stack_sizes.value_stack_size_initial` is larger than + /// `stack_sizes.value_stack_size_limit` or if `stack_sizes.frame_stack_size_initial` is larger + /// than `stack_sizes.frame_stack_size_limit`. /// /// ``` /// # extern crate wasmi; - /// use wasmi::{Interpreter, StackWithLimit, StackSize}; - /// let interpreter = Interpreter::with_stacks( - /// StackWithLimit::with_size(StackSize::from_byte_count(8192)), - /// StackWithLimit::with_size(StackSize::from_element_count(2048)), - /// ); - /// # let value_stack_size = StackSize::from_byte_count(8192); - /// # let value_stack = StackWithLimit::with_size(value_stack_size); - /// # let interpreter = Interpreter::with_stacks( - /// # value_stack, - /// # StackWithLimit::with_size(StackSize::from_element_count(2048)), - /// # ); + /// use wasmi::{Interpreter, InterpreterStackConfig}; + /// let config = InterpreterStackConfig { + /// value_stack_size_initial: 8192, + /// value_stack_size_limit: 8192, + /// call_stack_size_initial: 2048, + /// call_stack_size_limit: 2048, + /// }; + /// let interpreter = Interpreter::with_stacks(config); /// ``` - pub fn with_stacks( - value_stack: StackWithLimit, - call_stack: StackWithLimit, - ) -> Interpreter { + pub fn with_stacks(stack_sizes: InterpreterStackConfig) -> Interpreter { + let InterpreterStackConfig { + value_stack_size_initial, + value_stack_size_limit, + call_stack_size_initial, + call_stack_size_limit, + } = stack_sizes; + let value_stack = StackWithLimit::new( + StackSize::from_element_count(value_stack_size_initial).into_initial(), + StackSize::from_element_count(value_stack_size_limit).into_limit(), + ); // debug panic may occur here + let call_stack = StackWithLimit::new( + StackSize::from_element_count(call_stack_size_initial).into_initial(), + StackSize::from_element_count(call_stack_size_limit).into_limit(), + ); // debug panic may occur here Interpreter { value_stack: ValueStack(value_stack), call_stack, @@ -1217,7 +1272,7 @@ impl Interpreter { } /// Function execution context. -pub struct FunctionContext { +struct FunctionContext { /// Is context initialized. pub is_initialized: bool, /// Internal function reference.