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 thatn
is smaller than or equal tolinear_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 u8
s
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>
impl<const PAGE_SIZE: usize> LinearMemory<PAGE_SIZE>
sourceconst PAGE_SIZE: usize = PAGE_SIZE
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
sourcepub fn new() -> Self
pub fn new() -> Self
Create a new, empty LinearMemory
sourcepub fn new_with_initial_pages(pages: u16) -> Self
pub fn new_with_initial_pages(pages: u16) -> Self
Create a new, empty LinearMemory
sourcepub fn grow(&self, pages_to_add: u16)
pub fn grow(&self, pages_to_add: u16)
Grow the LinearMemory
by a number of pages
sourcepub fn pages(&self) -> u16
pub fn pages(&self) -> u16
Get the number of pages currently allocated to this LinearMemory
sourcepub fn len(&self) -> usize
pub fn len(&self) -> usize
Get the length in bytes currently allocated to this LinearMemory
sourcepub fn store<const N: usize, T: LittleEndianBytes<N>>(
&self,
index: usize,
value: T,
) -> Result<(), RuntimeError>
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
sourcepub fn load<const N: usize, T: LittleEndianBytes<N>>(
&self,
index: usize,
) -> Result<T, RuntimeError>
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
sourcepub fn fill(
&self,
index: usize,
data_byte: u8,
count: usize,
) -> Result<(), RuntimeError>
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
.
sourcepub fn copy(
&self,
destination_index: usize,
source_mem: &Self,
source_index: usize,
count: usize,
) -> Result<(), RuntimeError>
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 fromsource_index
, overwriting thecount
bytes starting fromdestination_index