LinearMemory

Struct LinearMemory 

Source
pub struct LinearMemory<const PAGE_SIZE: usize = { crate::Limits::MEM_PAGE_SIZE as usize }> {
    inner_data: RwSpinLock<Vec<AtomicU8>>,
}
Expand description

Implementation of the linear memory suitable for concurrent access

Implements the base for the instructions described in https://webassembly.github.io/spec/core/exec/instructions.html#memory-instructions.

This linear memory implementation internally relies on a Vec<AtomicU8>. Thus, the atomic unit of information for it is a byte (u8). All access to the linear memory internally occur through AtomicU8::load and AtomicU8::store, avoiding the creation of shared and mut refs to the internal data completely. This avoids undefined behavior. Racy multibyte writes to the same data however may tear (e.g. for any number of concurrent writes to a given byte, only one is effectively written). Because of this, the LinearMemory::store function does not require &mut self&self suffices.

The implementation of atomic stores to multibyte values requires a global write lock. Rust’s memory model considers partially overlapping atomic operations involving a write as undefined behavior. As there is no way to predict if an atomic multibyte store operation might overlap with another store or load operation, only a lock at runtime can avoid this cause of undefined behavior.

§Notes on overflowing

All operations that rely on accessing n bytes starting at index in the linear memory have to perform bounds checking. Thus, they always have to ensure that n + index < linear_memory.len() holds true (e.g. n + index - 1 must be a valid index into linear_memory). However, writing that check as is bears the danger of an overflow, assuming that n, index and linear_memory.len() are the same given integer type, n + index can overflow, resulting in the check passing despite the access being out of bounds!

To avoid this, the bounds checks are carefully ordered to avoid any overflows:

  • First we check, that n <= linear_memory.len() holds true, ensuring that the amount of bytes to be accessed is indeed smaller than or equal to the linear memory’s size. If this does not hold true, continuation of the operation will yield out of bounds access in any case.
  • Then, as a second check, we verify that index <= linear_memory.len() - n. This way we avoid the overflow, as there is no addition. The subtraction in the left hand can not underflow, due to the previous check (which asserts that n is smaller than or equal to linear_memory.len()).

Combined in the given order, these two checks enable bounds checking without risking any overflow or underflow, provided that n, index and linear_memory.len() are of the same integer type.

In addition, the Wasm specification requires a certain order of checks. For example, when a copy instruction is emitted with a count of zero (i.e. no bytes to be copied), an out of bounds index still has to cause a trap. To control the order of checks manually, use of slice indexing is avoided altogether.

§Notes on locking

The internal data vector of the LinearMemory is wrapped in a RwSpinLock. Despite the name, writes to the linear memory do not require an acquisition of a write lock. Non-atomic or atomic single-byte writes are implemented through a shared ref to the internal vector, with AtomicU8 to achieve interior mutability without undefined behavior.

However, linear memory can grow. As the linear memory is implemented via a Vec, a grow can result in the vector’s internal data buffer to be copied over to a bigger, fresh allocation. The old buffer is then freed. Combined with concurrent access, this can cause use-after-free. To avoid this, a grow operation of the linear memory acquires a write lock, blocking all read/write to the linear memory in between.

§Unsafe Note

As the manual index checking assures all indices to be valid, there is no need to re-check. Therefore slice::get_unchecked is used access the internal AtomicU8 in the vector backing a LinearMemory, implicating the use of unsafe.

To gain some confidence in the correctness of the unsafe code in this module, run miri:

cargo miri test --test memory # quick
cargo miri test # thorough

Fields§

§inner_data: RwSpinLock<Vec<AtomicU8>>

Implementations§

Source§

impl<const PAGE_SIZE: usize> LinearMemory<PAGE_SIZE>

Source

const PAGE_SIZE: usize = PAGE_SIZE

Size of a page in the linear memory, measured in bytes

The WASM specification demands a page size of 64 KiB, that is 65536 bytes: https://webassembly.github.io/spec/core/exec/runtime.html?highlight=page#memory-instances

Source

pub fn new() -> Self

Create a new, empty LinearMemory

Source

pub fn new_with_initial_pages(pages: u16) -> Self

Create a new, empty LinearMemory

Source

pub fn grow(&self, pages_to_add: u16)

Grow the LinearMemory by a number of pages

Source

pub fn pages(&self) -> u16

Get the number of pages currently allocated to this LinearMemory

Source

pub fn len(&self) -> usize

Get the length in bytes currently allocated to this LinearMemory

Source

pub fn store<const N: usize, T: LittleEndianBytes<N>>( &self, index: usize, value: T, ) -> Result<(), RuntimeError>

At a given index, store a datum in the LinearMemory

Source

pub fn store_bytes<const N: usize>( &self, index: usize, bytes: [u8; N], ) -> Result<(), RuntimeError>

At a given index, store a number of bytes N in the LinearMemory

Source

pub fn load<const N: usize, T: LittleEndianBytes<N>>( &self, index: usize, ) -> Result<T, RuntimeError>

From a given index, load a datum from the LinearMemory

Source

pub fn load_bytes<const N: usize>( &self, index: usize, ) -> Result<[u8; N], RuntimeError>

From a given index, load a number of bytes N from the LinearMemory

Source

pub fn fill( &self, index: usize, data_byte: u8, count: usize, ) -> Result<(), RuntimeError>

Source

pub fn copy( &self, destination_index: usize, source_mem: &Self, source_index: usize, count: usize, ) -> Result<(), RuntimeError>

Copy count bytes from one region in the linear memory to another region in the same or a different linear memory

  • Both regions may overlap
  • Copies the count bytes starting from source_index, overwriting the count bytes starting from destination_index

https://webassembly.github.io/spec/core/exec/instructions.html#xref-syntax-instructions-syntax-instr-memory-mathsf-memory-copy

Source

pub fn init( &self, destination_index: usize, source_data: &[u8], source_index: usize, count: usize, ) -> Result<(), RuntimeError>

Source

pub fn access_mut_slice<R>(&self, accessor: impl FnOnce(&mut [u8]) -> R) -> R

Allows a given closure to temporarily access the entire memory as a &mut [u8].

§Note on locking

This operation exclusively locks the entire linear memory for the duration of this function call. To acquire the lock, this function may also block until the lock is available.

Trait Implementations§

Source§

impl<const PAGE_SIZE: usize> Debug for LinearMemory<PAGE_SIZE>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<const PAGE_SIZE: usize> Default for LinearMemory<PAGE_SIZE>

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl<const PAGE_SIZE: usize = { crate::Limits::MEM_PAGE_SIZE as usize }> !Freeze for LinearMemory<PAGE_SIZE>

§

impl<const PAGE_SIZE: usize = { crate::Limits::MEM_PAGE_SIZE as usize }> !RefUnwindSafe for LinearMemory<PAGE_SIZE>

§

impl<const PAGE_SIZE: usize> Send for LinearMemory<PAGE_SIZE>

§

impl<const PAGE_SIZE: usize> Sync for LinearMemory<PAGE_SIZE>

§

impl<const PAGE_SIZE: usize> Unpin for LinearMemory<PAGE_SIZE>

§

impl<const PAGE_SIZE: usize> UnwindSafe for LinearMemory<PAGE_SIZE>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.