fizzyx 0.1.1

Safe, ergonomic Rust bindings for the Fizzy WebAssembly interpreter.
Documentation
//! Access to an instance's linear memory.

use crate::error::{Error, Result};
use crate::instance::Instance;
use fizzyx_sys as sys;

/// The size of a WebAssembly linear memory page, in bytes (64 KiB).
pub const PAGE_SIZE: usize = 65536;

/// A handle to a linear memory exported by an [`Instance`].
///
/// Obtained via [`Instance::get_memory`]. Because WebAssembly 1.0 permits at most
/// one memory per module, all methods operate on that single memory of the
/// instance passed in.
///
/// # Note
///
/// The underlying data pointer can change when memory grows, so each access
/// re-queries it; never cache a slice across a call into Wasm.
#[derive(Debug, Copy, Clone)]
pub struct Memory {
    _private: (),
}

impl Memory {
    pub(crate) fn new() -> Self {
        Self { _private: () }
    }

    /// Returns the current size of the memory in [pages](PAGE_SIZE) (64 KiB each).
    pub fn size(&self, instance: &Instance) -> u64 {
        (self.data_size(instance) / PAGE_SIZE) as u64
    }

    /// Returns the current size of the memory in bytes.
    pub fn data_size(&self, instance: &Instance) -> usize {
        // SAFETY: `instance` is a valid instance pointer.
        unsafe { sys::fizzy_get_instance_memory_size(instance.as_ptr()) }
    }

    /// Returns a shared view of the memory's current contents.
    pub fn data<'a>(&self, instance: &'a Instance) -> &'a [u8] {
        // SAFETY: the pointer/size pair describes the instance's live memory and
        // is borrowed for no longer than `instance`.
        unsafe {
            let ptr = sys::fizzy_get_instance_memory_data(instance.as_ptr());
            let size = sys::fizzy_get_instance_memory_size(instance.as_ptr());
            if ptr.is_null() || size == 0 {
                &[]
            } else {
                core::slice::from_raw_parts(ptr, size)
            }
        }
    }

    /// Returns an exclusive view of the memory's current contents.
    pub fn data_mut<'a>(&self, instance: &'a mut Instance) -> &'a mut [u8] {
        // SAFETY: exclusive borrow of `instance` guarantees no aliasing; the
        // pointer/size pair describes its live memory.
        unsafe {
            let ptr = sys::fizzy_get_instance_memory_data(instance.as_ptr());
            let size = sys::fizzy_get_instance_memory_size(instance.as_ptr());
            if ptr.is_null() || size == 0 {
                &mut []
            } else {
                core::slice::from_raw_parts_mut(ptr, size)
            }
        }
    }

    /// Reads `buf.len()` bytes starting at `offset` into `buf`.
    ///
    /// # Errors
    ///
    /// Returns [`Error::MemoryOutOfBounds`] if the range exceeds the memory size.
    pub fn read(&self, instance: &Instance, offset: u32, buf: &mut [u8]) -> Result<()> {
        let data = self.data(instance);
        let start = offset as usize;
        let end = start
            .checked_add(buf.len())
            .filter(|&end| end <= data.len())
            .ok_or(Error::MemoryOutOfBounds {
                offset: start,
                length: buf.len(),
                size: data.len(),
            })?;
        buf.copy_from_slice(&data[start..end]);
        Ok(())
    }

    /// Writes `buf` into the memory starting at `offset`.
    ///
    /// # Errors
    ///
    /// Returns [`Error::MemoryOutOfBounds`] if the range exceeds the memory size.
    pub fn write(&self, instance: &mut Instance, offset: u32, buf: &[u8]) -> Result<()> {
        let data = self.data_mut(instance);
        let start = offset as usize;
        let end = start
            .checked_add(buf.len())
            .filter(|&end| end <= data.len())
            .ok_or(Error::MemoryOutOfBounds {
                offset: start,
                length: buf.len(),
                size: data.len(),
            })?;
        data[start..end].copy_from_slice(buf);
        Ok(())
    }
}