wasm3x 0.1.0

Safe, Wasmi/Wasmtime-shaped Rust bindings for the Wasm3 interpreter.
Documentation
//! Access to a store's linear [`Memory`].

use wasm3x_sys as ffi;

use crate::error::{Error, Result};
use crate::store::{AsContext, AsContextMut, Store, StoreId, assert_same_store};

/// A handle to the linear memory of a [`Store`].
///
/// Wasm3 may relocate the memory buffer when it grows, so a [`Memory`] never
/// hands out a slice that outlives the store borrow used to obtain it.
#[derive(Debug, Clone, Copy)]
pub struct Memory {
    store_id: StoreId,
    index: u32,
}

impl Memory {
    pub(crate) fn new(store_id: StoreId, index: u32) -> Self {
        Self { store_id, index }
    }

    /// Returns the current base pointer and size (in bytes) of the memory.
    fn raw_parts(runtime: ffi::IM3Runtime, index: u32) -> (*mut u8, usize) {
        let mut size = 0u32;
        // SAFETY: valid runtime; `size` receives the byte length.
        let ptr = unsafe { ffi::m3_GetMemory(runtime, &mut size, index) };
        (ptr, size as usize)
    }

    /// Returns the size of the memory in bytes.
    pub fn data_size(&self, store: impl AsContext) -> usize {
        let ctx = store.as_context();
        assert_same_store(ctx.store_id(), self.store_id);
        Self::raw_parts(ctx.runtime(), self.index).1
    }

    /// Returns the size of the memory in 64 KiB Wasm pages.
    pub fn size(&self, store: impl AsContext) -> usize {
        self.data_size(store) / (64 * 1024)
    }

    /// Returns a shared view of the memory contents.
    ///
    /// Unlike the other accessors this takes a `&Store<T>` directly: the
    /// returned slice borrows the store, a relationship the by-value context
    /// traits cannot express.
    pub fn data<'a, T>(&self, store: &'a Store<T>) -> &'a [u8] {
        store.ensure_owns(self.store_id);
        let (ptr, size) = Self::raw_parts(store.raw(), self.index);
        if ptr.is_null() || size == 0 {
            return &[];
        }
        // SAFETY: `ptr`/`size` describe a valid buffer for as long as `store`
        // is borrowed; the memory cannot grow (which needs `&mut Store`) while
        // this shared borrow is alive.
        unsafe { core::slice::from_raw_parts(ptr, size) }
    }

    /// Returns an exclusive view of the memory contents.
    ///
    /// Takes a `&mut Store<T>` directly for the same reason as [`Memory::data`].
    pub fn data_mut<'a, T>(&self, store: &'a mut Store<T>) -> &'a mut [u8] {
        store.ensure_owns(self.store_id);
        let (ptr, size) = Self::raw_parts(store.raw(), self.index);
        if ptr.is_null() || size == 0 {
            return &mut [];
        }
        // SAFETY: exclusive `store` borrow guarantees unique access; the buffer
        // is valid for the borrow's duration.
        unsafe { core::slice::from_raw_parts_mut(ptr, size) }
    }

    /// Reads `buffer.len()` bytes starting at `offset`.
    pub fn read(&self, store: impl AsContext, offset: usize, buffer: &mut [u8]) -> Result<()> {
        let ctx = store.as_context();
        assert_same_store(ctx.store_id(), self.store_id);
        let (ptr, size) = Self::raw_parts(ctx.runtime(), self.index);
        // SAFETY: `ptr`/`size` describe the live memory; the shared context
        // borrow keeps it from growing for the duration of this read.
        let data: &[u8] = if ptr.is_null() || size == 0 {
            &[]
        } else {
            unsafe { core::slice::from_raw_parts(ptr, size) }
        };
        let end = offset
            .checked_add(buffer.len())
            .filter(|end| *end <= data.len())
            .ok_or_else(|| Error::new("out-of-bounds memory read"))?;
        buffer.copy_from_slice(&data[offset..end]);
        Ok(())
    }

    /// Writes `buffer` into memory starting at `offset`.
    pub fn write(&self, mut store: impl AsContextMut, offset: usize, buffer: &[u8]) -> Result<()> {
        let ctx = store.as_context_mut();
        assert_same_store(ctx.store_id(), self.store_id);
        let (ptr, size) = Self::raw_parts(ctx.runtime(), self.index);
        // SAFETY: the exclusive context borrow guarantees unique access to the
        // live memory for the duration of this write.
        let data: &mut [u8] = if ptr.is_null() || size == 0 {
            &mut []
        } else {
            unsafe { core::slice::from_raw_parts_mut(ptr, size) }
        };
        let end = offset
            .checked_add(buffer.len())
            .filter(|end| *end <= data.len())
            .ok_or_else(|| Error::new("out-of-bounds memory write"))?;
        data[offset..end].copy_from_slice(buffer);
        Ok(())
    }
}