Struct wasm::execution::linear_memory::LinearMemory

source ·
pub struct LinearMemory<const PAGE_SIZE: usize = { crate::Limits::MEM_PAGE_SIZE as usize }> {
    inner_data: RwSpinLock<Vec<UnsafeCell<u8>>>,
}
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<UnsafeCell<u8>>. Thus, the atomic unit of information for it is a byte (u8). All access to the linear memory internally occurs through pointers, avoiding the creation of shared and mut refs to the internal data completely. This avoids undefined behavior, except for the race-condition inherent to concurrent writes. Because of this, the LinearMemory::store function does not require &mut self&self suffices.

§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.

§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. Writes are implemented through a shared ref to the internal vector, with an UnsafeCell to achieve interior mutability.

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 mutable 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 inbetween.

§Unsafe Note

Raw pointer access it required, because concurent mutation of the linear memory might happen (consider the threading proposal for WASM, where mutliple WASM threads access the same linear memory at the same time). The inherent race condition results in UB w/r/t the state of the u8s in the inner data. However, this is tolerable, e.g. avoiding race conditions on the state of the linear memory can not be the task of the interpreter, but has to be fulfilled by the interpreted bytecode itself.

Fields§

§inner_data: RwSpinLock<Vec<UnsafeCell<u8>>>

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 load<const N: usize, T: LittleEndianBytes<N>>( &self, index: usize, ) -> Result<T, RuntimeError>

From a given index, load a datum in the LinearMemory

source

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

Implementation of the behavior described in https://webassembly.github.io/spec/core/exec/instructions.html#xref-syntax-instructions-syntax-instr-memory-mathsf-memory-fill. Note, that the WASM spec defines the behavior by recursion, while our implementation uses the memset like core::ptr::write_bytes.

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
source

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

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 = { crate::Limits::MEM_PAGE_SIZE as 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.