tinywasm 0.10.0

A tiny WebAssembly interpreter
Documentation
use alloc::vec::Vec;
use core::hint::cold_path;

use super::{LinearMemory, memory_oob};

/// A contiguous `Vec<u8>`-backed linear memory.
///
/// This is the simplest backend and typically gives the best read and write throughput because
/// the whole memory lives in one contiguous allocation.
///
/// The tradeoff is growth cost: large grows may need to reallocate and copy the full buffer,
/// which can get expensive for large memories.
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct VecMemory {
    data: Vec<u8>,
}

impl VecMemory {
    /// Tries to create a new memory with `len` zero-initialized bytes.
    ///
    /// Prefer this backend when contiguous access is more important than grow performance.
    pub fn try_new(len: usize) -> Result<Self, crate::Trap> {
        let mut data = Vec::new();
        match data.try_reserve_exact(len) {
            Ok(()) => {}
            Err(_) => {
                cold_path();
                return Err(crate::Trap::OutOfMemory);
            }
        }
        data.resize(len, 0);
        Ok(Self { data })
    }

    #[inline(always)]
    fn read_fixed<const N: usize>(&self, addr: usize) -> Result<[u8; N], crate::Trap> {
        self.check_fixed_addr::<N>(addr)?;
        Ok(self.data[addr..addr + N].try_into().unwrap_or_else(|_| unreachable!("slice length should be {N}")))
    }

    #[inline(always)]
    fn check_fixed_addr<const N: usize>(&self, addr: usize) -> Result<(), crate::Trap> {
        if N > self.data.len() || addr > self.data.len() - N {
            cold_path();
            return Err(memory_oob(addr, N, self.data.len()));
        }
        Ok(())
    }
}

impl LinearMemory for VecMemory {
    #[inline(always)]
    fn len(&self) -> usize {
        self.data.len()
    }

    #[inline(always)]
    fn grow_to(&mut self, new_len: usize) -> Result<(), crate::Trap> {
        if new_len < self.data.len() {
            return Err(crate::Trap::MemoryOutOfBounds { offset: new_len, len: 0, max: self.data.len() });
        }
        match self.data.try_reserve_exact(new_len.saturating_sub(self.data.len())) {
            Ok(()) => {}
            Err(_) => {
                cold_path();
                return Err(crate::Trap::OutOfMemory);
            }
        }
        self.data.resize(new_len, 0);
        Ok(())
    }

    #[inline(always)]
    fn read(&self, addr: usize, dst: &mut [u8]) -> usize {
        if addr >= self.data.len() {
            return 0;
        }
        let read_len = dst.len().min(self.data.len() - addr);
        dst[..read_len].copy_from_slice(&self.data[addr..addr + read_len]);
        read_len
    }

    #[inline(always)]
    fn read_exact(&self, addr: usize, dst: &mut [u8]) -> Option<()> {
        dst.copy_from_slice(self.data.get(addr..addr.checked_add(dst.len())?)?);
        Some(())
    }

    #[inline(always)]
    fn read_vec(&self, addr: usize, len: usize) -> Option<Vec<u8>> {
        Some(self.data.get(addr..addr.checked_add(len)?)?.to_vec())
    }

    #[inline(always)]
    fn write(&mut self, addr: usize, src: &[u8]) -> usize {
        if addr >= self.data.len() {
            return 0;
        }

        let write_len = src.len().min(self.data.len() - addr);
        self.data[addr..addr + write_len].copy_from_slice(&src[..write_len]);
        write_len
    }

    #[inline(always)]
    fn write_all(&mut self, addr: usize, src: &[u8]) -> Option<()> {
        let dst = self.data.get_mut(addr..addr.checked_add(src.len())?)?;
        dst.copy_from_slice(src);
        Some(())
    }

    #[inline(always)]
    fn fill(&mut self, addr: usize, len: usize, val: u8) -> Option<()> {
        self.data.get_mut(addr..addr.checked_add(len)?)?.fill(val);
        Some(())
    }

    #[inline(always)]
    fn copy_within(&mut self, dst: usize, src: usize, len: usize) -> Option<()> {
        let src_end = src.checked_add(len)?;
        let dst_end = dst.checked_add(len)?;
        if src_end > self.data.len() || dst_end > self.data.len() {
            return None;
        }

        self.data.copy_within(src..src_end, dst);
        Some(())
    }

    #[inline(always)]
    fn read_8(&self, addr: usize) -> core::result::Result<[u8; 1], crate::Trap> {
        self.check_fixed_addr::<1>(addr)?;
        Ok([self.data[addr]])
    }

    #[inline(always)]
    fn read_16(&self, addr: usize) -> core::result::Result<[u8; 2], crate::Trap> {
        self.read_fixed::<2>(addr)
    }

    #[inline(always)]
    fn read_32(&self, addr: usize) -> core::result::Result<[u8; 4], crate::Trap> {
        self.read_fixed::<4>(addr)
    }

    #[inline(always)]
    fn read_64(&self, addr: usize) -> core::result::Result<[u8; 8], crate::Trap> {
        self.read_fixed::<8>(addr)
    }

    #[inline(always)]
    fn read_128(&self, addr: usize) -> core::result::Result<[u8; 16], crate::Trap> {
        self.read_fixed::<16>(addr)
    }

    #[inline(always)]
    fn write_8(&mut self, addr: usize, bytes: &[u8]) -> core::result::Result<(), crate::Trap> {
        self.check_fixed_addr::<1>(addr)?;
        self.data[addr] = bytes[0];
        Ok(())
    }

    #[inline(always)]
    fn write_16(&mut self, addr: usize, bytes: &[u8]) -> core::result::Result<(), crate::Trap> {
        self.check_fixed_addr::<2>(addr)?;
        self.data[addr..addr + 2].copy_from_slice(bytes);
        Ok(())
    }

    #[inline(always)]
    fn write_32(&mut self, addr: usize, bytes: &[u8]) -> core::result::Result<(), crate::Trap> {
        self.check_fixed_addr::<4>(addr)?;
        self.data[addr..addr + 4].copy_from_slice(bytes);
        Ok(())
    }

    #[inline(always)]
    fn write_64(&mut self, addr: usize, bytes: &[u8]) -> core::result::Result<(), crate::Trap> {
        self.check_fixed_addr::<8>(addr)?;
        self.data[addr..addr + 8].copy_from_slice(bytes);
        Ok(())
    }

    #[inline(always)]
    fn write_128(&mut self, addr: usize, bytes: &[u8]) -> core::result::Result<(), crate::Trap> {
        self.check_fixed_addr::<16>(addr)?;
        self.data[addr..addr + 16].copy_from_slice(bytes);
        Ok(())
    }
}