wasmi/src/common/stack.rs

491 lines
14 KiB
Rust

use core::marker::PhantomData;
use core::ptr;
use core::usize;
#[allow(unused_imports)]
use alloc::prelude::*;
#[derive(Copy, Clone, Debug)]
pub struct StackOverflow;
/// Pre-allocated, growable stack with upper bound on size.
///
/// StackWithLimit is guaranteed never to grow larger than a set limit.
/// When limit is reached attempting to push to the stack will return
/// `Err(StackOverflow)`.
///
/// Both limit and initial stack size are configurable.
/// `StackWithLimit` will start out with initial size, but grow when necessary.
#[derive(Debug)]
pub struct StackWithLimit<T> {
stack: Vec<T>,
limit: usize,
}
impl<T> StackWithLimit<T> {
/// Create a StackWithLimit with `limit` max size and `initial_size` of pre-allocated
/// memory.
///
/// ```
/// # extern crate wasmi;
/// # use wasmi::{StackWithLimit, StackSize, FuncRef};
/// StackWithLimit::<FuncRef>::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::<RuntimeValue>::new(
/// StackSize::from_element_count(1024).into_initial(),
/// StackSize::unlimited(),
/// );
/// ```
///
/// # Panics
///
/// In debug mode, panics if `initial_size` is larger than `limit`.
pub fn new(initial_size: StackSizeInitial<T>, limit: StackSizeLimit<T>) -> StackWithLimit<T> {
let initial_size_elements = initial_size.0.element_count();
let limit_elements = limit.0.element_count();
debug_assert!(
limit_elements >= initial_size_elements,
"initial_size should not be larger than StackWithLimit limit"
);
StackWithLimit {
stack: Vec::with_capacity(initial_size_elements.min(limit_elements)),
limit: limit_elements,
}
}
/// Create an new StackWithLimit with `limit` max size and `limit` elements pre-allocated
///
/// ```
/// # extern crate wasmi;
/// # use wasmi::{StackWithLimit, StackSize};
/// let bstack = StackWithLimit::<i32>::with_size(StackSize::from_element_count(2));
/// ```
pub fn with_size(size: StackSize<T>) -> StackWithLimit<T> {
StackWithLimit::new(StackSizeInitial(size), StackSizeLimit(size))
}
/// Attempt to push value onto stack.
///
/// # Errors
///
/// Returns Err(StackOverflow) if stack is already full.
#[inline]
pub fn push(&mut self, value: T) -> Result<(), StackOverflow> {
debug_assert!(self.stack.capacity() <= self.limit);
if self.stack.len() == self.stack.capacity() {
if self.stack.len() == self.limit {
return Err(StackOverflow);
}
// grows exponentially, just like Vec normally does
// https://doc.rust-lang.org/1.26.0/src/alloc/raw_vec.rs.html#462
let desired_len = self.stack.len().saturating_mul(2).min(self.limit).max(1);
let additional_len = desired_len - self.stack.len();
self.stack.reserve_exact(additional_len);
}
debug_assert!(self.stack.len() < self.limit);
debug_assert!(self.stack.len() < self.stack.capacity());
let len = self.stack.len();
unsafe {
ptr::write(self.stack.get_unchecked_mut(len), value);
self.stack.set_len(len + 1);
}
Ok(())
}
/// 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::<i32>::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<T> {
self.stack.pop()
}
/// Remove and Return top element. Does not check for emptyness.
/// 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]
pub unsafe fn pop_unchecked(&mut self) -> T {
debug_assert!(self.stack.len() > 0);
let len = self.stack.len();
self.stack.set_len(len - 1);
ptr::read(self.stack.get_unchecked(self.stack.len()))
}
/// Return optional reference to item `depth` distance away from top
///
/// `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::<i32>::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
// depth can legally be 2^32. On a 32 bit system, adding may overflow
// overflow isn't an issue unless T is zero size
// In debug builds, underflow panics, but in release mode, underflow is not checked.
let index = self.stack.len().checked_sub(1)?.checked_sub(depth)?;
debug_assert!(self.stack.len() > index, "guaranteed by previous line");
Some(&self.stack[index])
}
/// Return mutable reference to item `depth` distance away from top
///
/// Does not check whether depth is in range.
pub fn nth_from_top_mut_unchecked(&mut self, depth: usize) -> &mut T {
let offset = self.stack.len() - 1 - depth;
&mut self.stack[offset]
}
/// Swaps two elements in the stack.
///
/// # Arguments
///
/// * a - The index of the first element
/// * b - The index of the second element
///
/// # Panics
///
/// Panics if `a` or `b` are out of bound.
///
/// ```
/// # extern crate wasmi;
/// # use wasmi::{StackWithLimit, StackSize};
/// let mut bstack = StackWithLimit::<i32>::with_size(StackSize::from_element_count(2));
/// bstack.push(1);
/// bstack.push(2);
/// assert_eq!(bstack.top(), Some(&2));
/// stack.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::<i32>::with_size(StackSize::from_element_count(2));
/// bstack.push(1);
/// bstack.push(2);
/// assert_eq!(bstack.top(), Some(&2));
/// assert_eq!(stack.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::<i32>::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()
}
/// Get a mutable reference to the top of the stack, or `None` if the stack is empty.
pub fn top_mut(&mut self) -> Option<&mut T> {
self.stack.last_mut()
}
/// Shorten the stack, keeping the bottom len elements and dropping the rest.
///
/// If new_size is greater then the current stack size, this has no effect.
pub fn truncate(&mut self, new_size: usize) {
self.stack.truncate(new_size)
}
/// Get number of items in a stack.
///
/// ```
/// # extern crate wasmi;
/// # use wasmi::{StackWithLimit, StackSize};
/// let mut bstack = StackWithLimit::<i32>::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::<i32>::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<T> { ... }
// ```
//
// into:
//
// ```
// pub fn new(initial_size: StackSizeInitial<T>, limit: StackSizeLimit<T>) -> StackWithLimit<T> { ... }
// ```
//
// 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)]
pub struct StackSize<T> {
num_elements: usize,
// PhantomData is a zero-sized type for keeping track of T without rustc complaining.
// *const T or &'a T is recommended when T is not actually owned.
// https://doc.rust-lang.org/std/marker/struct.PhantomData.html#ownership-and-the-drop-check
phantom: PhantomData<*const T>,
}
impl<T> Clone for StackSize<T> {
fn clone(&self) -> Self {
StackSize {
num_elements: self.num_elements,
phantom: PhantomData,
}
}
}
impl<T> Copy for StackSize<T> {}
impl<T> StackSize<T> {
/// 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<T> {
StackSize {
num_elements,
phantom: PhantomData,
}
}
/// 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<T> {
// This debug_assert should catch logical errors.
debug_assert!(::core::mem::size_of::<T>() != 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::<T>() != 0 {
num_bytes / ::core::mem::size_of::<T>()
} 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<RuntimeValue> = StackSize::from_element_count(1024).into_limit();
/// ```
pub fn into_limit(self) -> StackSizeLimit<T> {
StackSizeLimit(self)
}
/// Create StackSizeLimit out of self
pub fn into_initial(self) -> StackSizeInitial<T> {
StackSizeInitial(self)
}
/// Create StackSizeLimit with no upper bound.
pub fn unlimited() -> StackSizeLimit<T> {
StackSize::from_element_count(usize::MAX).into_limit()
}
}
/// Max size a stack may become.
///
/// Constructed by [`StackSize::into_limit`](into_limit) or [`StackSize::unlimited`](unlimited)
///
/// [into_limit]: type.StackSize.into_limit.html
/// [unlimited]: type.StackSize.unlimited.html
pub struct StackSizeLimit<T>(StackSize<T>);
/// Number of pre-allocated elements.
///
/// Constructed by [`StackSize::into_initial`]
///
/// [into_initial]: type.StackSize.into_initial.html
pub struct StackSizeInitial<T>(StackSize<T>);
#[cfg(test)]
mod test {
use super::{StackSize, StackSizeInitial, StackSizeLimit, StackWithLimit};
use core::usize;
#[test]
fn nth_from_top() {
let mut bstack = StackWithLimit::<i32>::with_size(StackSize::from_element_count(2));
assert_eq!(bstack.nth_from_top(0), None);
bstack.push(1).unwrap();
bstack.push(2).unwrap();
bstack.push(3).unwrap_err();
assert_eq!(bstack.nth_from_top(0), Some(&2));
assert_eq!(bstack.nth_from_top(1), Some(&1));
assert_eq!(bstack.nth_from_top(2), None);
assert_eq!(bstack.nth_from_top(3), None);
}
fn exersize(mut bstack: StackWithLimit<i32>) {
assert!(bstack.is_empty());
assert_eq!(bstack.len(), 0);
assert_eq!(bstack.top(), None);
assert_eq!(bstack.top_mut(), None);
assert_eq!(bstack.pop(), None);
bstack.push(0).unwrap();
assert!(!bstack.is_empty());
assert_eq!(bstack.len(), 1);
assert_eq!(bstack.top(), Some(&0));
assert_eq!(bstack.top_mut(), Some(&mut 0));
assert_eq!(bstack.pop(), Some(0));
bstack.push(0).unwrap();
bstack.push(0).unwrap();
bstack.push(0).unwrap();
bstack.push(0).unwrap();
assert_eq!(bstack.len(), 4);
bstack.truncate(8);
assert_eq!(bstack.len(), 4);
bstack.truncate(4);
assert_eq!(bstack.len(), 4);
bstack.truncate(2);
assert_eq!(bstack.len(), 2);
bstack.truncate(0);
assert_eq!(bstack.len(), 0);
}
#[test]
fn stack_with_limit() {
let bstack = StackWithLimit::<i32>::with_size(StackSize::from_element_count(20));
exersize(bstack);
}
// Check for integer overflow bugs
#[test]
fn practically_unlimited_stack() {
let bstack = StackWithLimit::<i32>::new(
StackSizeInitial(StackSize::from_element_count(0)),
StackSizeLimit(StackSize::from_element_count(usize::MAX)),
);
exersize(bstack);
}
// Make sure the stack resizes properly.
#[test]
fn must_resize() {
let mut bstack = StackWithLimit::<i32>::new(
StackSizeInitial(StackSize::from_element_count(2)),
StackSizeLimit(StackSize::from_element_count(4)),
);
bstack.push(2).unwrap();
bstack.push(4).unwrap();
bstack.push(8).unwrap();
bstack.push(16).unwrap();
bstack.push(16).unwrap_err();
assert_eq!(bstack.pop(), Some(16));
assert_eq!(bstack.pop(), Some(8));
assert_eq!(bstack.pop(), Some(4));
assert_eq!(bstack.pop(), Some(2));
}
#[test]
fn pop_unchecked() {
let mut bstack = StackWithLimit::<i32>::with_size(StackSize::from_element_count(20));
bstack.push(8).unwrap();
bstack.push(0).unwrap();
assert_eq!(unsafe { bstack.pop_unchecked() }, 0);
assert_eq!(unsafe { bstack.pop_unchecked() }, 8);
}
}