Struct wasmtime::Memory

source ·
#[repr(transparent)]
pub struct Memory(_);
Expand description

A WebAssembly linear memory.

WebAssembly memories represent a contiguous array of bytes that have a size that is always a multiple of the WebAssembly page size, currently 64 kilobytes.

WebAssembly memory is used for global data (not to be confused with wasm global items), statics in C/C++/Rust, shadow stack memory, etc. Accessing wasm memory is generally quite fast.

Memories, like other wasm items, are owned by a Store.

Memory and Safety

Linear memory is a lynchpin of safety for WebAssembly. In Wasmtime there are safe methods of interacting with a Memory:

Note that all of these consider the entire store context as borrowed for the duration of the call or the duration of the returned slice. This largely means that while the function is running you’ll be unable to borrow anything else from the store. This includes getting access to the T on Store<T>, but it also means that you can’t recursively call into WebAssembly for instance.

If you’d like to dip your toes into handling Memory in a more raw fashion (e.g. by using raw pointers or raw slices), then there’s a few important points to consider when doing so:

  • Any recursive calls into WebAssembly can possibly modify any byte of the entire memory. This means that whenever wasm is called Rust can’t have any long-lived borrows live across the wasm function call. Slices like &mut [u8] will be violated because they’re not actually exclusive at that point, and slices like &[u8] are also violated because their contents may be mutated.

  • WebAssembly memories can grow, and growth may change the base pointer. This means that even holding a raw pointer to memory over a wasm function call is also incorrect. Anywhere in the function call the base address of memory may change. Note that growth can also be requested from the embedding API as well.

As a general rule of thumb it’s recommended to stick to the safe methods of Memory if you can. It’s not advised to use raw pointers or unsafe operations because of how easy it is to accidentally get things wrong.

Some examples of safely interacting with memory are:

use wasmtime::{Memory, Store, MemoryAccessError};

// Memory can be read and written safely with the `Memory::read` and
// `Memory::write` methods.
// An error is returned if the copy did not succeed.
fn safe_examples(mem: Memory, store: &mut Store<()>) -> Result<(), MemoryAccessError> {
    let offset = 5;
    mem.write(&mut *store, offset, b"hello")?;
    let mut buffer = [0u8; 5];
    mem.read(&store, offset, &mut buffer)?;
    assert_eq!(b"hello", &buffer);

    // Note that while this is safe care must be taken because the indexing
    // here may panic if the memory isn't large enough.
    assert_eq!(&mem.data(&store)[offset..offset + 5], b"hello");
    mem.data_mut(&mut *store)[offset..offset + 5].copy_from_slice(b"bye!!");

    Ok(())
}

It’s worth also, however, covering some examples of incorrect, unsafe usages of Memory. Do not do these things!

use wasmtime::{Memory, Store};

// NOTE: All code in this function is not safe to execute and may cause
// segfaults/undefined behavior at runtime. Do not copy/paste these examples
// into production code!
unsafe fn unsafe_examples(mem: Memory, store: &mut Store<()>) -> Result<()> {
    // First and foremost, any borrow can be invalidated at any time via the
    // `Memory::grow` function. This can relocate memory which causes any
    // previous pointer to be possibly invalid now.
    let pointer: &u8 = &*mem.data_ptr(&store);
    mem.grow(&mut *store, 1)?; // invalidates `pointer`!
    // println!("{}", *pointer); // FATAL: use-after-free

    // Note that the use-after-free also applies to slices, whether they're
    // slices of bytes or strings.
    let mem_slice = std::slice::from_raw_parts(
        mem.data_ptr(&store),
        mem.data_size(&store),
    );
    let slice: &[u8] = &mem_slice[0x100..0x102];
    mem.grow(&mut *store, 1)?; // invalidates `slice`!
    // println!("{:?}", slice); // FATAL: use-after-free

    // The `Memory` type may be stored in other locations, so if you hand
    // off access to the `Store` then those locations may also call
    // `Memory::grow` or similar, so it's not enough to just audit code for
    // calls to `Memory::grow`.
    let pointer: &u8 = &*mem.data_ptr(&store);
    some_other_function(store); // may invalidate `pointer` through use of `store`
    // println!("{:?}", pointer); // FATAL: maybe a use-after-free

    // An especially subtle aspect of accessing a wasm instance's memory is
    // that you need to be extremely careful about aliasing. Anyone at any
    // time can call `data_unchecked()` or `data_unchecked_mut()`, which
    // means you can easily have aliasing mutable references:
    let ref1: &u8 = &*mem.data_ptr(&store).add(0x100);
    let ref2: &mut u8 = &mut *mem.data_ptr(&store).add(0x100);
    // *ref2 = *ref1; // FATAL: violates Rust's aliasing rules

    Ok(())
}

Overall there’s some general rules of thumb when unsafely working with Memory and getting raw pointers inside of it:

  • If you never have a “long lived” pointer into memory, you’re likely in the clear. Care still needs to be taken in threaded scenarios or when/where data is read, but you’ll be shielded from many classes of issues.
  • Long-lived pointers must always respect Rust’a aliasing rules. It’s ok for shared borrows to overlap with each other, but mutable borrows must overlap with nothing.
  • Long-lived pointers are only valid if they’re not invalidated for their lifetime. This means that Store isn’t used to reenter wasm or the memory itself is never grown or otherwise modified/aliased.

At this point it’s worth reiterating again that unsafely working with Memory is pretty tricky and not recommended! It’s highly recommended to use the safe methods to interact with Memory whenever possible.

Memory Safety and Threads

Currently the wasmtime crate does not implement the wasm threads proposal, but it is planned to do so. It may be interesting to readers to see how this affects memory safety and what was previously just discussed as well.

Once threads are added into the mix, all of the above rules still apply. There’s an additional consideration that all reads and writes can happen concurrently, though. This effectively means that any borrow into wasm memory are virtually never safe to have.

Mutable pointers are fundamentally unsafe to have in a concurrent scenario in the face of arbitrary wasm code. Only if you dynamically know for sure that wasm won’t access a region would it be safe to construct a mutable pointer. Additionally even shared pointers are largely unsafe because their underlying contents may change, so unless UnsafeCell in one form or another is used everywhere there’s no safety.

One important point about concurrency is that while Memory::grow can happen concurrently it will never relocate the base pointer. Shared memories must always have a maximum size and they will be preallocated such that growth will never relocate the base pointer. The current size of the memory may still change over time though.

Overall the general rule of thumb for shared memories is that you must atomically read and write everything. Nothing can be borrowed and everything must be eagerly copied out. This means that Memory::data and Memory::data_mut won’t work in the future (they’ll probably return an error) for shared memories when they’re implemented. When possible it’s recommended to use Memory::read and Memory::write which will still be provided.

Implementations§

Creates a new WebAssembly memory given the configuration of ty.

The store argument will be the owner of the returned Memory. All WebAssembly memory is initialized to zero.

Panics

This function will panic if the Store has a ResourceLimiterAsync (see also: Store::limiter_async). When using an async resource limiter, use Memory::new_async instead.

Examples
let engine = Engine::default();
let mut store = Store::new(&engine, ());

let memory_ty = MemoryType::new(1, None);
let memory = Memory::new(&mut store, memory_ty)?;

let module = Module::new(&engine, "(module (memory (import \"\" \"\") 1))")?;
let instance = Instance::new(&mut store, &module, &[memory.into()])?;
// ...
Available on crate feature async only.

Async variant of Memory::new. You must use this variant with Stores which have a ResourceLimiterAsync.

Panics

This function will panic when used with a non-async Store.

Returns the underlying type of this memory.

Panics

Panics if this memory doesn’t belong to store.

Examples
let engine = Engine::default();
let mut store = Store::new(&engine, ());
let module = Module::new(&engine, "(module (memory (export \"mem\") 1))")?;
let instance = Instance::new(&mut store, &module, &[])?;
let memory = instance.get_memory(&mut store, "mem").unwrap();
let ty = memory.ty(&store);
assert_eq!(ty.minimum(), 1);
Examples found in repository?
src/memory.rs (line 362)
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
    pub fn data<'a, T: 'a>(&self, store: impl Into<StoreContext<'a, T>>) -> &'a [u8] {
        unsafe {
            let store = store.into();
            let definition = &*store[self.0].definition;
            debug_assert!(!self.ty(store).is_shared());
            slice::from_raw_parts(definition.base, definition.current_length())
        }
    }

    /// Returns this memory as a native Rust mutable slice.
    ///
    /// Note that this method will consider the entire store context provided as
    /// borrowed for the duration of the lifetime of the returned slice.
    ///
    /// # Panics
    ///
    /// Panics if this memory doesn't belong to `store`.
    pub fn data_mut<'a, T: 'a>(&self, store: impl Into<StoreContextMut<'a, T>>) -> &'a mut [u8] {
        unsafe {
            let store = store.into();
            let definition = &*store[self.0].definition;
            debug_assert!(!self.ty(store).is_shared());
            slice::from_raw_parts_mut(definition.base, definition.current_length())
        }
    }
More examples
Hide additional examples
src/externals.rs (line 101)
97
98
99
100
101
102
103
104
105
106
    pub fn ty(&self, store: impl AsContext) -> ExternType {
        let store = store.as_context();
        match self {
            Extern::Func(ft) => ExternType::Func(ft.ty(store)),
            Extern::Memory(ft) => ExternType::Memory(ft.ty(store)),
            Extern::SharedMemory(ft) => ExternType::Memory(ft.ty()),
            Extern::Table(tt) => ExternType::Table(tt.ty(store)),
            Extern::Global(gt) => ExternType::Global(gt.ty(store)),
        }
    }

Safely reads memory contents at the given offset into a buffer.

The entire buffer will be filled.

If offset + buffer.len() exceed the current memory capacity, then the buffer is left untouched and a MemoryAccessError is returned.

Panics

Panics if this memory doesn’t belong to store.

Safely writes contents of a buffer to this memory at the given offset.

If the offset + buffer.len() exceeds the current memory capacity, then none of the buffer is written to memory and a MemoryAccessError is returned.

Panics

Panics if this memory doesn’t belong to store.

Returns this memory as a native Rust slice.

Note that this method will consider the entire store context provided as borrowed for the duration of the lifetime of the returned slice.

Panics

Panics if this memory doesn’t belong to store.

Examples found in repository?
src/memory.rs (line 318)
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
    pub fn read(
        &self,
        store: impl AsContext,
        offset: usize,
        buffer: &mut [u8],
    ) -> Result<(), MemoryAccessError> {
        let store = store.as_context();
        let slice = self
            .data(&store)
            .get(offset..)
            .and_then(|s| s.get(..buffer.len()))
            .ok_or(MemoryAccessError { _private: () })?;
        buffer.copy_from_slice(slice);
        Ok(())
    }

Returns this memory as a native Rust mutable slice.

Note that this method will consider the entire store context provided as borrowed for the duration of the lifetime of the returned slice.

Panics

Panics if this memory doesn’t belong to store.

Examples found in repository?
src/memory.rs (line 342)
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
    pub fn write(
        &self,
        mut store: impl AsContextMut,
        offset: usize,
        buffer: &[u8],
    ) -> Result<(), MemoryAccessError> {
        let mut context = store.as_context_mut();
        self.data_mut(&mut context)
            .get_mut(offset..)
            .and_then(|s| s.get_mut(..buffer.len()))
            .ok_or(MemoryAccessError { _private: () })?
            .copy_from_slice(buffer);
        Ok(())
    }

    /// Returns this memory as a native Rust slice.
    ///
    /// Note that this method will consider the entire store context provided as
    /// borrowed for the duration of the lifetime of the returned slice.
    ///
    /// # Panics
    ///
    /// Panics if this memory doesn't belong to `store`.
    pub fn data<'a, T: 'a>(&self, store: impl Into<StoreContext<'a, T>>) -> &'a [u8] {
        unsafe {
            let store = store.into();
            let definition = &*store[self.0].definition;
            debug_assert!(!self.ty(store).is_shared());
            slice::from_raw_parts(definition.base, definition.current_length())
        }
    }

    /// Returns this memory as a native Rust mutable slice.
    ///
    /// Note that this method will consider the entire store context provided as
    /// borrowed for the duration of the lifetime of the returned slice.
    ///
    /// # Panics
    ///
    /// Panics if this memory doesn't belong to `store`.
    pub fn data_mut<'a, T: 'a>(&self, store: impl Into<StoreContextMut<'a, T>>) -> &'a mut [u8] {
        unsafe {
            let store = store.into();
            let definition = &*store[self.0].definition;
            debug_assert!(!self.ty(store).is_shared());
            slice::from_raw_parts_mut(definition.base, definition.current_length())
        }
    }

    /// Same as [`Memory::data_mut`], but also returns the `T` from the
    /// [`StoreContextMut`].
    ///
    /// This method can be used when you want to simultaneously work with the
    /// `T` in the store as well as the memory behind this [`Memory`]. Using
    /// [`Memory::data_mut`] would consider the entire store borrowed, whereas
    /// this method allows the Rust compiler to see that the borrow of this
    /// memory and the borrow of `T` are disjoint.
    ///
    /// # Panics
    ///
    /// Panics if this memory doesn't belong to `store`.
    pub fn data_and_store_mut<'a, T: 'a>(
        &self,
        store: impl Into<StoreContextMut<'a, T>>,
    ) -> (&'a mut [u8], &'a mut T) {
        // Note the unsafety here. Our goal is to simultaneously borrow the
        // memory and custom data from `store`, and the store it's connected
        // to. Rust will not let us do that, however, because we must call two
        // separate methods (both of which borrow the whole `store`) and one of
        // our borrows is mutable (the custom data).
        //
        // This operation, however, is safe because these borrows do not overlap
        // and in the process of borrowing them mutability doesn't actually
        // touch anything. This is akin to mutably borrowing two indices in an
        // array, which is safe so long as the indices are separate.
        unsafe {
            let mut store = store.into();
            let data = &mut *(store.data_mut() as *mut T);
            (self.data_mut(store), data)
        }
    }

Same as Memory::data_mut, but also returns the T from the StoreContextMut.

This method can be used when you want to simultaneously work with the T in the store as well as the memory behind this Memory. Using Memory::data_mut would consider the entire store borrowed, whereas this method allows the Rust compiler to see that the borrow of this memory and the borrow of T are disjoint.

Panics

Panics if this memory doesn’t belong to store.

Returns the base pointer, in the host’s address space, that the memory is located at.

For more information and examples see the documentation on the Memory type.

Panics

Panics if this memory doesn’t belong to store.

Returns the byte length of this memory.

The returned value will be a multiple of the wasm page size, 64k.

For more information and examples see the documentation on the Memory type.

Panics

Panics if this memory doesn’t belong to store.

Returns the size, in WebAssembly pages, of this wasm memory.

Panics

Panics if this memory doesn’t belong to store.

Grows this WebAssembly memory by delta pages.

This will attempt to add delta more pages of memory on to the end of this Memory instance. If successful this may relocate the memory and cause Memory::data_ptr to return a new value. Additionally any unsafely constructed slices into this memory may no longer be valid.

On success returns the number of pages this memory previously had before the growth succeeded.

Errors

Returns an error if memory could not be grown, for example if it exceeds the maximum limits of this memory. A ResourceLimiter is another example of preventing a memory to grow.

Panics

Panics if this memory doesn’t belong to store.

This function will panic if the Store has a ResourceLimiterAsync (see also: Store::limiter_async. When using an async resource limiter, use Memory::grow_async instead.

Examples
let engine = Engine::default();
let mut store = Store::new(&engine, ());
let module = Module::new(&engine, "(module (memory (export \"mem\") 1 2))")?;
let instance = Instance::new(&mut store, &module, &[])?;
let memory = instance.get_memory(&mut store, "mem").unwrap();

assert_eq!(memory.size(&store), 1);
assert_eq!(memory.grow(&mut store, 1)?, 1);
assert_eq!(memory.size(&store), 2);
assert!(memory.grow(&mut store, 1).is_err());
assert_eq!(memory.size(&store), 2);
assert_eq!(memory.grow(&mut store, 0)?, 2);
Examples found in repository?
src/memory.rs (line 544)
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
    pub async fn grow_async<T>(
        &self,
        mut store: impl AsContextMut<Data = T>,
        delta: u64,
    ) -> Result<u64>
    where
        T: Send,
    {
        let mut store = store.as_context_mut();
        assert!(
            store.0.async_support(),
            "cannot use `grow_async` without enabling async support on the config"
        );
        store.on_fiber(|store| self.grow(store, delta)).await?
    }
Available on crate feature async only.

Async variant of Memory::grow. Required when using a ResourceLimiterAsync.

Panics

This function will panic when used with a non-async Store.

Trait Implementations§

Returns a copy of the value. Read more
Performs copy-assignment from source. Read more
Formats the value using the given formatter. Read more
Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

Calls U::from(self).

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

The alignment of pointer.
The type for initializers.
Initializes a with the given initializer. Read more
Dereferences the given pointer. Read more
Mutably dereferences the given pointer. Read more
Drops the object pointed to by the given pointer. Read more
Should always be Self
The resulting type after obtaining ownership.
Creates owned data from borrowed data, usually by cloning. Read more
Uses borrowed data to replace owned data, usually by cloning. Read more
The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.