Skip to main content

tinywasm/store/memory/
mod.rs

1use alloc::{boxed::Box, format, sync::Arc};
2use alloc::{vec, vec::Vec};
3use core::cmp::min;
4use core::hint::cold_path;
5use core::ops::{Deref, DerefMut};
6
7use tinywasm_types::MemoryType;
8
9use crate::interpreter::Value128;
10use crate::{Error, Result};
11
12mod instance;
13mod lazy;
14
15mod paged;
16#[path = "vec.rs"]
17mod vec_memory;
18
19pub(crate) use instance::MemoryInstance;
20pub use {lazy::LazyLinearMemory, paged::PagedMemory, vec_memory::VecMemory};
21
22/// Backend storage for a linear memory
23///
24/// This is a low-level trait that abstracts over the actual storage mechanism for linear memory.
25/// This will probably change in the future to allow more efficient implementations.
26/// See [`MemoryBackend`] for a higher-level interface to configuring memory storage.
27pub trait LinearMemory {
28    /// Returns the current memory length in bytes.
29    fn len(&self) -> usize;
30
31    /// Returns true if the memory is empty.
32    fn is_empty(&self) -> bool {
33        self.len() == 0
34    }
35
36    /// Grows the memory to `new_len` bytes.
37    ///
38    /// The runtime only calls this with lengths that are exact multiples of the Wasm page size for
39    /// the owning memory.
40    fn grow_to(&mut self, new_len: usize) -> core::result::Result<(), crate::Trap>;
41
42    /// Reads up to `dst.len()` bytes starting at `addr` and returns the number of bytes read.
43    ///
44    /// Backends may return fewer bytes than requested even when more data is available. This lets
45    /// non-contiguous backends stop at a natural boundary such as the end of a chunk.
46    fn read(&self, addr: usize, dst: &mut [u8]) -> usize;
47
48    /// Writes up to `src.len()` bytes starting at `addr` and returns the number of bytes written.
49    ///
50    /// Backends may return fewer bytes than requested even when more space is available. This lets
51    /// non-contiguous backends stop at a natural boundary such as the end of a chunk.
52    fn write(&mut self, addr: usize, src: &[u8]) -> usize;
53
54    /// Writes all bytes in `src` starting at `addr`, or returns `None` if any byte could not be written.
55    fn write_all(&mut self, addr: usize, src: &[u8]) -> Option<()> {
56        let end = addr.checked_add(src.len())?;
57        if end > self.len() {
58            return None;
59        }
60
61        let mut offset = 0;
62        while offset < src.len() {
63            let written = self.write(addr + offset, &src[offset..]);
64            if written == 0 {
65                return None;
66            }
67            offset += written;
68        }
69
70        Some(())
71    }
72
73    /// Fills the range `[addr, addr + len)` with `val`.
74    fn fill(&mut self, addr: usize, len: usize, val: u8) -> Option<()> {
75        let end = addr.checked_add(len)?;
76        if end > self.len() {
77            return None;
78        }
79
80        let mut offset = 0;
81        while offset < len {
82            let chunk_len = min(len - offset, 1024);
83            let chunk = vec![val; chunk_len];
84            self.write_all(addr + offset, &chunk)?;
85            offset += chunk_len;
86        }
87
88        Some(())
89    }
90
91    /// Copies `len` bytes from `src` to `dst` within the same memory.
92    fn copy_within(&mut self, dst: usize, src: usize, len: usize) -> Option<()> {
93        let src_end = src.checked_add(len)?;
94        let dst_end = dst.checked_add(len)?;
95        if src_end > self.len() || dst_end > self.len() {
96            return None;
97        }
98
99        if len == 0 || dst == src {
100            return Some(());
101        }
102
103        // If the source and destination ranges are disjoint, we can copy forward without a temporary buffer.
104        if dst < src || dst >= src_end {
105            let mut offset = 0;
106            while offset < len {
107                let chunk_len = min(len - offset, 1024);
108                let mut chunk = vec![0; chunk_len];
109                self.read_exact(src + offset, &mut chunk)?;
110                self.write_all(dst + offset, &chunk)?;
111                offset += chunk_len;
112            }
113        } else {
114            // Otherwise, we need to copy backward to avoid overwriting the source data before it's read.
115            let mut offset = len;
116            while offset > 0 {
117                let chunk_len = min(offset, 1024);
118                offset -= chunk_len;
119                let mut chunk = vec![0; chunk_len];
120                self.read_exact(src + offset, &mut chunk)?;
121                self.write_all(dst + offset, &chunk)?;
122            }
123        }
124
125        Some(())
126    }
127
128    /// Reads exactly `dst.len()` bytes starting at `addr`.
129    fn read_exact(&self, addr: usize, dst: &mut [u8]) -> Option<()> {
130        let end = addr.checked_add(dst.len())?;
131        if end > self.len() {
132            return None;
133        }
134
135        let mut offset = 0;
136        while offset < dst.len() {
137            let read = self.read(addr + offset, &mut dst[offset..]);
138            if read == 0 {
139                return None;
140            }
141            offset += read;
142        }
143
144        Some(())
145    }
146
147    /// Reads `len` bytes starting at `addr` into a newly allocated buffer.
148    fn read_vec(&self, addr: usize, len: usize) -> Option<Vec<u8>> {
149        let end = addr.checked_add(len)?;
150        if end > self.len() {
151            return None;
152        }
153
154        let mut data = vec![0; len];
155        self.read_exact(addr, &mut data)?;
156        Some(data)
157    }
158
159    /// Reads exactly 1 byte at the effective address `base + offset`.
160    fn read_8(&self, base: u64, offset: u64) -> core::result::Result<u8, crate::Trap> {
161        let addr = checked_effective_addr::<1>(self.len(), base, offset)?;
162        let mut bytes = [0; 1];
163        self.read_exact(addr, &mut bytes).ok_or_else(|| {
164            cold_path();
165            memory_oob(addr, 1, self.len())
166        })?;
167        Ok(bytes[0])
168    }
169
170    /// Reads exactly 2 bytes at the effective address `base + offset`.
171    fn read_16(&self, base: u64, offset: u64) -> core::result::Result<[u8; 2], crate::Trap> {
172        let addr = checked_effective_addr::<2>(self.len(), base, offset)?;
173        let mut bytes = [0; 2];
174        self.read_exact(addr, &mut bytes).ok_or_else(|| {
175            cold_path();
176            memory_oob(addr, 2, self.len())
177        })?;
178        Ok(bytes)
179    }
180
181    /// Reads exactly 4 bytes at the effective address `base + offset`.
182    fn read_32(&self, base: u64, offset: u64) -> core::result::Result<[u8; 4], crate::Trap> {
183        let addr = checked_effective_addr::<4>(self.len(), base, offset)?;
184        let mut bytes = [0; 4];
185        self.read_exact(addr, &mut bytes).ok_or_else(|| {
186            cold_path();
187            memory_oob(addr, 4, self.len())
188        })?;
189        Ok(bytes)
190    }
191
192    /// Reads exactly 8 bytes at the effective address `base + offset`.
193    fn read_64(&self, base: u64, offset: u64) -> core::result::Result<[u8; 8], crate::Trap> {
194        let addr = checked_effective_addr::<8>(self.len(), base, offset)?;
195        let mut bytes = [0; 8];
196        self.read_exact(addr, &mut bytes).ok_or_else(|| {
197            cold_path();
198            memory_oob(addr, 8, self.len())
199        })?;
200        Ok(bytes)
201    }
202
203    /// Reads exactly 16 bytes at the effective address `base + offset`.
204    fn read_128(&self, base: u64, offset: u64) -> core::result::Result<[u8; 16], crate::Trap> {
205        let addr = checked_effective_addr::<16>(self.len(), base, offset)?;
206        let mut bytes = [0; 16];
207        self.read_exact(addr, &mut bytes).ok_or_else(|| {
208            cold_path();
209            memory_oob(addr, 16, self.len())
210        })?;
211        Ok(bytes)
212    }
213
214    /// Writes exactly 1 byte at the effective address `base + offset`.
215    fn write_8(&mut self, base: u64, offset: u64, byte: u8) -> core::result::Result<(), crate::Trap> {
216        let addr = checked_effective_addr::<1>(self.len(), base, offset)?;
217        self.write(addr, &[byte]);
218        Ok(())
219    }
220
221    /// Writes exactly 2 bytes at the effective address `base + offset`.
222    fn write_16(&mut self, base: u64, offset: u64, bytes: [u8; 2]) -> core::result::Result<(), crate::Trap> {
223        let addr = checked_effective_addr::<2>(self.len(), base, offset)?;
224        self.write_all(addr, &bytes).ok_or_else(|| {
225            cold_path();
226            memory_oob(addr, 2, self.len())
227        })
228    }
229
230    /// Writes exactly 4 bytes at the effective address `base + offset`.
231    fn write_32(&mut self, base: u64, offset: u64, bytes: [u8; 4]) -> core::result::Result<(), crate::Trap> {
232        let addr = checked_effective_addr::<4>(self.len(), base, offset)?;
233        self.write_all(addr, &bytes).ok_or_else(|| {
234            cold_path();
235            memory_oob(addr, 4, self.len())
236        })
237    }
238
239    /// Writes exactly 8 bytes at the effective address `base + offset`.
240    fn write_64(&mut self, base: u64, offset: u64, bytes: [u8; 8]) -> core::result::Result<(), crate::Trap> {
241        let addr = checked_effective_addr::<8>(self.len(), base, offset)?;
242        self.write_all(addr, &bytes).ok_or_else(|| {
243            cold_path();
244            memory_oob(addr, 8, self.len())
245        })
246    }
247
248    /// Writes exactly 16 bytes at the effective address `base + offset`.
249    fn write_128(&mut self, base: u64, offset: u64, bytes: [u8; 16]) -> core::result::Result<(), crate::Trap> {
250        let addr = checked_effective_addr::<16>(self.len(), base, offset)?;
251        self.write_all(addr, &bytes).ok_or_else(|| {
252            cold_path();
253            memory_oob(addr, 16, self.len())
254        })
255    }
256}
257
258type MemoryFactory = dyn Fn(MemoryType) -> Result<Box<dyn LinearMemory>> + Send + Sync;
259
260/// Configures how runtime memory instances are created.
261#[derive(Clone, Default)]
262pub struct MemoryBackend {
263    kind: MemoryBackendKind,
264}
265
266#[derive(Clone, Default)]
267enum MemoryBackendKind {
268    #[default]
269    Vec,
270    Paged {
271        chunk_size: usize,
272    },
273    Custom(Arc<MemoryFactory>),
274}
275
276impl MemoryBackend {
277    /// Uses a contiguous [`VecMemory`] for each memory instance.
278    ///
279    /// This is usually the fastest option for reads and writes, but large grows can be expensive
280    /// because they may reallocate and copy the entire buffer.
281    pub const fn vec() -> Self {
282        Self { kind: MemoryBackendKind::Vec }
283    }
284
285    /// Uses sparse chunked storage for each memory instance.
286    ///
287    /// `chunk_size` is the backend chunk size in bytes. It is independent from the Wasm page size.
288    ///
289    /// This generally makes growth cheaper than [`Self::vec`], but read and write operations do a
290    /// little more work and may be slightly slower.
291    pub fn paged(chunk_size: usize) -> Self {
292        assert!(chunk_size != 0, "chunk_size must be greater than zero");
293        Self { kind: MemoryBackendKind::Paged { chunk_size } }
294    }
295
296    /// Uses a custom factory to create memory instances.
297    pub fn custom<F, M>(factory: F) -> Self
298    where
299        F: Fn(MemoryType) -> Result<M> + Send + Sync + 'static,
300        M: LinearMemory + 'static,
301    {
302        Self {
303            kind: MemoryBackendKind::Custom(Arc::new(move |ty| {
304                let memory = factory(ty)?;
305                Ok(Box::new(memory) as Box<dyn LinearMemory>)
306            })),
307        }
308    }
309
310    pub(crate) fn create(&self, ty: MemoryType, initial_len: usize) -> Result<MemoryStorage> {
311        let storage = match &self.kind {
312            MemoryBackendKind::Vec => {
313                Box::new(VecMemory::try_new(initial_len).map_err(Error::Trap)?) as Box<dyn LinearMemory>
314            }
315            MemoryBackendKind::Paged { chunk_size } => {
316                Box::new(PagedMemory::try_new(initial_len, *chunk_size).map_err(Error::Trap)?) as Box<dyn LinearMemory>
317            }
318            MemoryBackendKind::Custom(factory) => factory(ty)?,
319        };
320
321        if storage.len() < initial_len {
322            return Err(Error::Other(format!(
323                "memory backend returned {} bytes for a memory that requires at least {initial_len}",
324                storage.len()
325            )));
326        }
327
328        Ok(MemoryStorage(storage))
329    }
330
331    pub(crate) fn create_lazy(&self, ty: MemoryType, initial_len: usize) -> Result<MemoryStorage> {
332        Ok(MemoryStorage(Box::new(LazyLinearMemory::new_with_initial_len(ty, initial_len, self.clone()))))
333    }
334}
335
336#[cfg(feature = "debug")]
337impl core::fmt::Debug for MemoryBackend {
338    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
339        match &self.kind {
340            MemoryBackendKind::Vec => f.debug_tuple("MemoryBackend::Vec").finish(),
341            MemoryBackendKind::Paged { chunk_size } => {
342                f.debug_struct("MemoryBackend::Paged").field("chunk_size", chunk_size).finish()
343            }
344            MemoryBackendKind::Custom(_) => f.debug_tuple("MemoryBackend::Custom").finish(),
345        }
346    }
347}
348
349pub(crate) struct MemoryStorage(Box<dyn LinearMemory>);
350
351impl Deref for MemoryStorage {
352    type Target = dyn LinearMemory;
353
354    #[inline(always)]
355    fn deref(&self) -> &Self::Target {
356        &*self.0
357    }
358}
359
360impl DerefMut for MemoryStorage {
361    #[inline(always)]
362    fn deref_mut(&mut self) -> &mut Self::Target {
363        &mut *self.0
364    }
365}
366
367#[cfg(feature = "debug")]
368impl core::fmt::Debug for MemoryStorage {
369    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
370        f.debug_tuple("MemoryStorage").field(&format!("{} bytes", self.len())).finish()
371    }
372}
373
374/// A trait for types that can be converted to and from static byte arrays
375pub(crate) trait MemValue<const N: usize>: Copy + Default {
376    /// Store a value in memory
377    fn to_mem_bytes(self) -> [u8; N];
378
379    /// Load a value from memory
380    fn from_mem_bytes(bytes: [u8; N]) -> Self;
381
382    fn load(mem: &dyn LinearMemory, base: u64, offset: u64) -> core::result::Result<Self, crate::Trap>;
383}
384
385macro_rules! impl_mem_traits {
386    ($($ty:ty, $size:expr, $read:ident),* $(,)?) => {
387        $(
388            impl MemValue<$size> for $ty {
389                #[inline(always)]
390                fn from_mem_bytes(bytes: [u8; $size]) -> Self {
391                    <$ty>::from_le_bytes(bytes)
392                }
393
394                #[inline(always)]
395                fn to_mem_bytes(self) -> [u8; $size] {
396                    self.to_le_bytes()
397                }
398
399                #[inline(always)]
400                fn load(
401                    mem: &dyn LinearMemory,
402                    base: u64,
403                    offset: u64,
404                ) -> core::result::Result<Self, crate::Trap> {
405                    Ok(Self::from_mem_bytes(mem.$read(base, offset)?))
406                }
407            }
408        )*
409    };
410}
411
412impl MemValue<1> for u8 {
413    #[inline(always)]
414    fn from_mem_bytes(bytes: [u8; 1]) -> Self {
415        bytes[0]
416    }
417
418    #[inline(always)]
419    fn to_mem_bytes(self) -> [u8; 1] {
420        [self]
421    }
422
423    #[inline(always)]
424    fn load(mem: &dyn LinearMemory, base: u64, offset: u64) -> core::result::Result<Self, crate::Trap> {
425        mem.read_8(base, offset)
426    }
427}
428
429impl MemValue<1> for i8 {
430    #[inline(always)]
431    fn from_mem_bytes(bytes: [u8; 1]) -> Self {
432        i8::from_le_bytes(bytes)
433    }
434
435    #[inline(always)]
436    fn to_mem_bytes(self) -> [u8; 1] {
437        self.to_le_bytes()
438    }
439
440    #[inline(always)]
441    fn load(mem: &dyn LinearMemory, base: u64, offset: u64) -> core::result::Result<Self, crate::Trap> {
442        Ok(mem.read_8(base, offset)? as i8)
443    }
444}
445
446impl_mem_traits!(
447    u16, 2, read_16, i16, 2, read_16, u32, 4, read_32, i32, 4, read_32, f32, 4, read_32, u64, 8, read_64, i64, 8,
448    read_64, f64, 8, read_64, Value128, 16, read_128
449);
450
451fn memory_oob(offset: usize, len: usize, max: usize) -> crate::Trap {
452    crate::Trap::MemoryOutOfBounds { offset, len, max }
453}
454
455fn checked_effective_addr<const LEN: usize>(
456    max: usize,
457    base: u64,
458    offset: u64,
459) -> core::result::Result<usize, crate::Trap> {
460    let Some(max_addr) = max.checked_sub(LEN).map(|max_addr| max_addr as u64) else {
461        cold_path();
462        return Err(memory_oob(usize::try_from(base).unwrap_or(usize::MAX), LEN, max));
463    };
464
465    let addr = base.wrapping_add(offset);
466    if addr < base || addr > max_addr {
467        cold_path();
468        return Err(memory_oob(usize::try_from(addr).unwrap_or(usize::MAX), LEN, max));
469    }
470
471    Ok(addr as usize)
472}
473
474#[cfg(test)]
475mod tests {
476    use super::*;
477    use tinywasm_types::MemoryArch;
478
479    fn create_test_memory(kind: MemoryType, backend: MemoryBackend) -> MemoryInstance {
480        MemoryInstance::new(kind, &backend).unwrap()
481    }
482
483    fn test_backends() -> [MemoryBackend; 2] {
484        [MemoryBackend::vec(), MemoryBackend::paged(4)]
485    }
486
487    #[test]
488    fn memory_copy_from_slice_and_read_vec_work() {
489        for backend in test_backends() {
490            let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None);
491            let mut memory = create_test_memory(kind, backend);
492            let data = [1, 2, 3, 4];
493            assert!(memory.inner.write_all(0, &data).is_some());
494            assert_eq!(memory.inner.read_vec(0, data.len()).unwrap(), data);
495        }
496    }
497
498    #[test]
499    fn memory_read_returns_partial_count() {
500        for backend in test_backends() {
501            let kind = MemoryType::new(MemoryArch::I32, 1, Some(1), Some(4));
502            let memory = create_test_memory(kind, backend);
503            let mut dst = [9; 8];
504            assert_eq!(memory.inner.read(2, &mut dst), 2);
505            assert_eq!(&dst[..2], &[0, 0]);
506            assert_eq!(&dst[2..], &[9; 6]);
507        }
508    }
509
510    #[test]
511    fn memory_copy_from_slice_out_of_bounds_fails() {
512        for backend in test_backends() {
513            let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None);
514            let mut memory = create_test_memory(kind, backend);
515            let data = [1, 2, 3, 4];
516            let len = memory.inner.len();
517            assert!(memory.inner.write_all(len, &data).is_none());
518        }
519    }
520
521    #[test]
522    fn memory_fill_works() {
523        for backend in test_backends() {
524            let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None);
525            let mut memory = create_test_memory(kind, backend);
526            assert!(memory.inner.fill(0, 10, 42).is_some());
527            assert_eq!(memory.inner.read_vec(0, 10).unwrap(), vec![42; 10]);
528        }
529    }
530
531    #[test]
532    fn memory_fill_out_of_bounds_fails() {
533        for backend in test_backends() {
534            let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None);
535            let mut memory = create_test_memory(kind, backend);
536            let len = memory.inner.len();
537            assert!(memory.inner.fill(len, 10, 42).is_none());
538        }
539    }
540
541    #[test]
542    fn memory_copy_within_works() {
543        for backend in test_backends() {
544            let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None);
545            let mut memory = create_test_memory(kind, backend);
546            memory.inner.fill(0, 10, 1).unwrap();
547            assert!(memory.copy_within(10, 0, 10).is_ok());
548            assert_eq!(memory.inner.read_vec(10, 10).unwrap(), vec![1; 10]);
549        }
550    }
551
552    #[test]
553    fn memory_copy_within_out_of_bounds_fails() {
554        for backend in test_backends() {
555            let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None);
556            let mut memory = create_test_memory(kind, backend);
557            assert!(memory.copy_within(memory.inner.len(), 0, 10).is_err());
558        }
559    }
560
561    #[test]
562    fn memory_grow_works() {
563        for backend in test_backends() {
564            let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None);
565            let mut memory = create_test_memory(kind, backend);
566            let original_pages = memory.page_count;
567            assert_eq!(memory.grow(1, false).unwrap(), Some(original_pages as i64));
568            assert_eq!(memory.page_count, original_pages + 1);
569        }
570    }
571
572    #[test]
573    fn memory_grow_out_of_bounds_fails() {
574        for backend in test_backends() {
575            let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None);
576            let mut memory = create_test_memory(kind, backend);
577            assert_eq!(memory.grow(memory.kind.max_size() as i64 + 1, false).unwrap(), None);
578        }
579    }
580
581    #[test]
582    fn memory_grow_respects_max_pages() {
583        for backend in test_backends() {
584            let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None);
585            let mut memory = create_test_memory(kind, backend);
586            assert_eq!(memory.grow(1, false).unwrap(), Some(1));
587            assert_eq!(memory.grow(1, false).unwrap(), None);
588        }
589    }
590
591    #[test]
592    fn memory_grow_negative_delta_fails() {
593        for backend in test_backends() {
594            let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None);
595            let mut memory = create_test_memory(kind, backend);
596            let original_pages = memory.page_count;
597            assert_eq!(memory.grow(-1, false).unwrap(), None);
598            assert_eq!(memory.page_count, original_pages);
599        }
600    }
601
602    #[test]
603    fn memory_custom_page_size_out_of_bounds_fails() {
604        for backend in test_backends() {
605            let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), Some(1));
606            let mut memory = create_test_memory(kind, backend);
607            let data = [1, 2];
608            assert!(memory.inner.write_all(0, &data).is_none());
609        }
610    }
611
612    #[test]
613    fn memory_custom_page_size_grow_works() {
614        for backend in test_backends() {
615            let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), Some(1));
616            let mut memory = create_test_memory(kind, backend);
617            assert_eq!(memory.grow(1, false).unwrap(), Some(1));
618            let data = [1, 2];
619            assert!(memory.inner.write_all(0, &data).is_some());
620            assert_eq!(memory.inner.read_vec(0, data.len()).unwrap(), data);
621        }
622    }
623}