unc_vm_vm/
memory.rs

1// This file contains code from external sources.
2// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md
3
4//! Memory management for linear memories.
5//!
6//! `LinearMemory` is to WebAssembly linear memories what `Table` is to WebAssembly tables.
7
8use crate::mmap::Mmap;
9use crate::vmcontext::VMMemoryDefinition;
10use more_asserts::assert_ge;
11use std::borrow::BorrowMut;
12use std::cell::UnsafeCell;
13use std::convert::TryInto;
14use std::fmt;
15use std::ptr::NonNull;
16use std::sync::Mutex;
17use thiserror::Error;
18use unc_vm_types::{Bytes, MemoryType, Pages};
19
20/// Error type describing things that can go wrong when operating on Wasm Memories.
21#[derive(Error, Debug, Clone, PartialEq, Hash)]
22pub enum MemoryError {
23    /// Low level error with mmap.
24    #[error("Error when allocating memory: {0}")]
25    Region(String),
26    /// The operation would cause the size of the memory to exceed the maximum or would cause
27    /// an overflow leading to unindexable memory.
28    #[error("The memory could not grow: current size {} pages, requested increase: {} pages", current.0, attempted_delta.0)]
29    CouldNotGrow {
30        /// The current size in pages.
31        current: Pages,
32        /// The attempted amount to grow by in pages.
33        attempted_delta: Pages,
34    },
35    /// The operation would cause the size of the memory size exceed the maximum.
36    #[error("The memory is invalid because {}", reason)]
37    InvalidMemory {
38        /// The reason why the provided memory is invalid.
39        reason: String,
40    },
41    /// Caller asked for more minimum memory than we can give them.
42    #[error("The minimum requested ({} pages) memory is greater than the maximum allowed memory ({} pages)", min_requested.0, max_allowed.0)]
43    MinimumMemoryTooLarge {
44        /// The number of pages requested as the minimum amount of memory.
45        min_requested: Pages,
46        /// The maximum amount of memory we can allocate.
47        max_allowed: Pages,
48    },
49    /// Caller asked for a maximum memory greater than we can give them.
50    #[error("The maximum requested memory ({} pages) is greater than the maximum allowed memory ({} pages)", max_requested.0, max_allowed.0)]
51    MaximumMemoryTooLarge {
52        /// The number of pages requested as the maximum amount of memory.
53        max_requested: Pages,
54        /// The number of pages requested as the maximum amount of memory.
55        max_allowed: Pages,
56    },
57    /// A user defined error value, used for error cases not listed above.
58    #[error("A user-defined error occurred: {0}")]
59    Generic(String),
60}
61
62/// Implementation styles for WebAssembly linear memory.
63#[derive(Debug, Clone, PartialEq, Eq, Hash, rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)]
64pub enum MemoryStyle {
65    /// The actual memory can be resized and moved.
66    Dynamic {
67        /// Our chosen offset-guard size.
68        ///
69        /// It represents the size in bytes of extra guard pages after the end
70        /// to optimize loads and stores with constant offsets.
71        offset_guard_size: u64,
72    },
73    /// Address space is allocated up front.
74    Static {
75        /// The number of mapped and unmapped pages.
76        bound: Pages,
77        /// Our chosen offset-guard size.
78        ///
79        /// It represents the size in bytes of extra guard pages after the end
80        /// to optimize loads and stores with constant offsets.
81        offset_guard_size: u64,
82    },
83}
84
85impl MemoryStyle {
86    /// Returns the offset-guard size
87    pub fn offset_guard_size(&self) -> u64 {
88        match self {
89            Self::Dynamic { offset_guard_size } => *offset_guard_size,
90            Self::Static { offset_guard_size, .. } => *offset_guard_size,
91        }
92    }
93}
94
95/// Trait for implementing Wasm Memory used by Wasmer.
96pub trait Memory: fmt::Debug + Send + Sync {
97    /// Returns the memory type for this memory.
98    fn ty(&self) -> MemoryType;
99
100    /// Returns the memory style for this memory.
101    fn style(&self) -> &MemoryStyle;
102
103    /// Returns the number of allocated wasm pages.
104    fn size(&self) -> Pages;
105
106    /// Grow memory by the specified amount of wasm pages.
107    fn grow(&self, delta: Pages) -> Result<Pages, MemoryError>;
108
109    /// Return a [`VMMemoryDefinition`] for exposing the memory to compiled wasm code.
110    ///
111    /// The pointer returned in [`VMMemoryDefinition`] must be valid for the lifetime of this memory.
112    fn vmmemory(&self) -> NonNull<VMMemoryDefinition>;
113}
114
115/// A linear memory instance.
116#[derive(Debug)]
117pub struct LinearMemory {
118    // The underlying allocation.
119    mmap: Mutex<WasmMmap>,
120
121    // The optional maximum size in wasm pages of this linear memory.
122    maximum: Option<Pages>,
123
124    /// The WebAssembly linear memory description.
125    memory: MemoryType,
126
127    /// Our chosen implementation style.
128    style: MemoryStyle,
129
130    // Size in bytes of extra guard pages after the end to optimize loads and stores with
131    // constant offsets.
132    offset_guard_size: usize,
133
134    /// The owned memory definition used by the generated code
135    vm_memory_definition: VMMemoryDefinitionOwnership,
136}
137
138/// A type to help manage who is responsible for the backing memory of them
139/// `VMMemoryDefinition`.
140#[derive(Debug)]
141enum VMMemoryDefinitionOwnership {
142    /// The `VMMemoryDefinition` is owned by the `Instance` and we should use
143    /// its memory. This is how a local memory that's exported should be stored.
144    VMOwned(NonNull<VMMemoryDefinition>),
145    /// The `VMMemoryDefinition` is owned by the host and we should manage its
146    /// memory. This is how an imported memory that doesn't come from another
147    /// Wasm module should be stored.
148    HostOwned(Box<UnsafeCell<VMMemoryDefinition>>),
149}
150
151/// We must implement this because of `VMMemoryDefinitionOwnership::VMOwned`.
152/// This is correct because synchronization of memory accesses is controlled
153/// by the VM.
154// REVIEW: I don't believe ^; this probably shouldn't be `Send`...
155// mutations from other threads into this data could be a problem, but we probably
156// don't want to use atomics for this in the generated code.
157// TODO:
158unsafe impl Send for LinearMemory {}
159
160/// This is correct because all internal mutability is protected by a mutex.
161unsafe impl Sync for LinearMemory {}
162
163#[derive(Debug)]
164struct WasmMmap {
165    // Our OS allocation of mmap'd memory.
166    alloc: Mmap,
167    // The current logical size in wasm pages of this linear memory.
168    size: Pages,
169}
170
171impl LinearMemory {
172    /// Create a new linear memory instance with specified minimum and maximum number of wasm pages.
173    ///
174    /// This creates a `LinearMemory` with owned metadata: this can be used to create a memory
175    /// that will be imported into Wasm modules.
176    pub fn new(memory: &MemoryType, style: &MemoryStyle) -> Result<Self, MemoryError> {
177        unsafe { Self::new_internal(memory, style, None) }
178    }
179
180    /// Create a new linear memory instance with specified minimum and maximum number of wasm pages.
181    ///
182    /// This creates a `LinearMemory` with metadata owned by a VM, pointed to by
183    /// `vm_memory_location`: this can be used to create a local memory.
184    ///
185    /// # Safety
186    /// - `vm_memory_location` must point to a valid location in VM memory.
187    pub unsafe fn from_definition(
188        memory: &MemoryType,
189        style: &MemoryStyle,
190        vm_memory_location: NonNull<VMMemoryDefinition>,
191    ) -> Result<Self, MemoryError> {
192        Self::new_internal(memory, style, Some(vm_memory_location))
193    }
194
195    /// Build a `LinearMemory` with either self-owned or VM owned metadata.
196    unsafe fn new_internal(
197        memory: &MemoryType,
198        style: &MemoryStyle,
199        vm_memory_location: Option<NonNull<VMMemoryDefinition>>,
200    ) -> Result<Self, MemoryError> {
201        if memory.minimum > Pages::max_value() {
202            return Err(MemoryError::MinimumMemoryTooLarge {
203                min_requested: memory.minimum,
204                max_allowed: Pages::max_value(),
205            });
206        }
207        // `maximum` cannot be set to more than `65536` pages.
208        if let Some(max) = memory.maximum {
209            if max > Pages::max_value() {
210                return Err(MemoryError::MaximumMemoryTooLarge {
211                    max_requested: max,
212                    max_allowed: Pages::max_value(),
213                });
214            }
215            if max < memory.minimum {
216                return Err(MemoryError::InvalidMemory {
217                    reason: format!(
218                        "the maximum ({} pages) is less than the minimum ({} pages)",
219                        max.0, memory.minimum.0
220                    ),
221                });
222            }
223        }
224
225        let offset_guard_bytes = style.offset_guard_size() as usize;
226
227        let minimum_pages = match style {
228            MemoryStyle::Dynamic { .. } => memory.minimum,
229            MemoryStyle::Static { bound, .. } => {
230                assert_ge!(*bound, memory.minimum);
231                *bound
232            }
233        };
234        let minimum_bytes = minimum_pages.bytes().0;
235        let request_bytes = minimum_bytes.checked_add(offset_guard_bytes).unwrap();
236        let mapped_pages = memory.minimum;
237        let mapped_bytes = mapped_pages.bytes();
238
239        let mut mmap = WasmMmap {
240            alloc: Mmap::accessible_reserved(mapped_bytes.0, request_bytes)
241                .map_err(MemoryError::Region)?,
242            size: memory.minimum,
243        };
244
245        let base_ptr = mmap.alloc.as_mut_ptr();
246        let mem_length = memory.minimum.bytes().0;
247        Ok(Self {
248            mmap: Mutex::new(mmap),
249            maximum: memory.maximum,
250            offset_guard_size: offset_guard_bytes,
251            vm_memory_definition: if let Some(mem_loc) = vm_memory_location {
252                {
253                    let mut ptr = mem_loc;
254                    let md = ptr.as_mut();
255                    md.base = base_ptr;
256                    md.current_length = mem_length;
257                }
258                VMMemoryDefinitionOwnership::VMOwned(mem_loc)
259            } else {
260                VMMemoryDefinitionOwnership::HostOwned(Box::new(UnsafeCell::new(
261                    VMMemoryDefinition { base: base_ptr, current_length: mem_length },
262                )))
263            },
264            memory: *memory,
265            style: style.clone(),
266        })
267    }
268
269    /// Get the `VMMemoryDefinition`.
270    ///
271    /// # Safety
272    /// - You must ensure that you have mutually exclusive access before calling
273    ///   this function. You can get this by locking the `mmap` mutex.
274    unsafe fn get_vm_memory_definition(&self) -> NonNull<VMMemoryDefinition> {
275        match &self.vm_memory_definition {
276            VMMemoryDefinitionOwnership::VMOwned(ptr) => *ptr,
277            VMMemoryDefinitionOwnership::HostOwned(boxed_ptr) => {
278                NonNull::new_unchecked(boxed_ptr.get())
279            }
280        }
281    }
282}
283
284impl Memory for LinearMemory {
285    /// Returns the type for this memory.
286    fn ty(&self) -> MemoryType {
287        let minimum = self.size();
288        let mut out = self.memory;
289        out.minimum = minimum;
290
291        out
292    }
293
294    /// Returns the memory style for this memory.
295    fn style(&self) -> &MemoryStyle {
296        &self.style
297    }
298
299    /// Returns the number of allocated wasm pages.
300    fn size(&self) -> Pages {
301        // TODO: investigate this function for race conditions
302        unsafe {
303            let md_ptr = self.get_vm_memory_definition();
304            let md = md_ptr.as_ref();
305            Bytes::from(md.current_length).try_into().unwrap()
306        }
307    }
308
309    /// Grow memory by the specified amount of wasm pages.
310    ///
311    /// Returns `None` if memory can't be grown by the specified amount
312    /// of wasm pages.
313    fn grow(&self, delta: Pages) -> Result<Pages, MemoryError> {
314        let mut mmap_guard = self.mmap.lock().unwrap();
315        let mmap = mmap_guard.borrow_mut();
316        // Optimization of memory.grow 0 calls.
317        if delta.0 == 0 {
318            return Ok(mmap.size);
319        }
320
321        let new_pages = mmap
322            .size
323            .checked_add(delta)
324            .ok_or(MemoryError::CouldNotGrow { current: mmap.size, attempted_delta: delta })?;
325        let prev_pages = mmap.size;
326
327        if let Some(maximum) = self.maximum {
328            if new_pages > maximum {
329                return Err(MemoryError::CouldNotGrow {
330                    current: mmap.size,
331                    attempted_delta: delta,
332                });
333            }
334        }
335
336        // Wasm linear memories are never allowed to grow beyond what is
337        // indexable. If the memory has no maximum, enforce the greatest
338        // limit here.
339        if new_pages >= Pages::max_value() {
340            // Linear memory size would exceed the index range.
341            return Err(MemoryError::CouldNotGrow { current: mmap.size, attempted_delta: delta });
342        }
343
344        let delta_bytes = delta.bytes().0;
345        let prev_bytes = prev_pages.bytes().0;
346        let new_bytes = new_pages.bytes().0;
347
348        if new_bytes > mmap.alloc.len() - self.offset_guard_size {
349            // If the new size is within the declared maximum, but needs more memory than we
350            // have on hand, it's a dynamic heap and it can move.
351            let guard_bytes = self.offset_guard_size;
352            let request_bytes =
353                new_bytes.checked_add(guard_bytes).ok_or_else(|| MemoryError::CouldNotGrow {
354                    current: new_pages,
355                    attempted_delta: Bytes(guard_bytes).try_into().unwrap(),
356                })?;
357
358            let mut new_mmap =
359                Mmap::accessible_reserved(new_bytes, request_bytes).map_err(MemoryError::Region)?;
360
361            let copy_len = mmap.alloc.len() - self.offset_guard_size;
362            new_mmap.as_mut_slice()[..copy_len].copy_from_slice(&mmap.alloc.as_slice()[..copy_len]);
363
364            mmap.alloc = new_mmap;
365        } else if delta_bytes > 0 {
366            // Make the newly allocated pages accessible.
367            mmap.alloc.make_accessible(prev_bytes, delta_bytes).map_err(MemoryError::Region)?;
368        }
369
370        mmap.size = new_pages;
371
372        // update memory definition
373        unsafe {
374            let mut md_ptr = self.get_vm_memory_definition();
375            let md = md_ptr.as_mut();
376            md.current_length = new_pages.bytes().0;
377            md.base = mmap.alloc.as_mut_ptr() as _;
378        }
379
380        Ok(prev_pages)
381    }
382
383    /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code.
384    fn vmmemory(&self) -> NonNull<VMMemoryDefinition> {
385        let _mmap_guard = self.mmap.lock().unwrap();
386        unsafe { self.get_vm_memory_definition() }
387    }
388}