wasmer_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 wasmer_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 {
91                offset_guard_size, ..
92            } => *offset_guard_size,
93        }
94    }
95}
96
97/// Trait for implementing Wasm Memory used by Wasmer.
98pub trait Memory: fmt::Debug + Send + Sync {
99    /// Returns the memory type for this memory.
100    fn ty(&self) -> MemoryType;
101
102    /// Returns the memory style for this memory.
103    fn style(&self) -> &MemoryStyle;
104
105    /// Returns the number of allocated wasm pages.
106    fn size(&self) -> Pages;
107
108    /// Grow memory by the specified amount of wasm pages.
109    fn grow(&self, delta: Pages) -> Result<Pages, MemoryError>;
110
111    /// Return a [`VMMemoryDefinition`] for exposing the memory to compiled wasm code.
112    ///
113    /// The pointer returned in [`VMMemoryDefinition`] must be valid for the lifetime of this memory.
114    fn vmmemory(&self) -> NonNull<VMMemoryDefinition>;
115}
116
117/// A linear memory instance.
118#[derive(Debug)]
119pub struct LinearMemory {
120    // The underlying allocation.
121    mmap: Mutex<WasmMmap>,
122
123    // The optional maximum size in wasm pages of this linear memory.
124    maximum: Option<Pages>,
125
126    /// The WebAssembly linear memory description.
127    memory: MemoryType,
128
129    /// Our chosen implementation style.
130    style: MemoryStyle,
131
132    // Size in bytes of extra guard pages after the end to optimize loads and stores with
133    // constant offsets.
134    offset_guard_size: usize,
135
136    /// The owned memory definition used by the generated code
137    vm_memory_definition: VMMemoryDefinitionOwnership,
138}
139
140/// A type to help manage who is responsible for the backing memory of them
141/// `VMMemoryDefinition`.
142#[derive(Debug)]
143enum VMMemoryDefinitionOwnership {
144    /// The `VMMemoryDefinition` is owned by the `Instance` and we should use
145    /// its memory. This is how a local memory that's exported should be stored.
146    VMOwned(NonNull<VMMemoryDefinition>),
147    /// The `VMMemoryDefinition` is owned by the host and we should manage its
148    /// memory. This is how an imported memory that doesn't come from another
149    /// Wasm module should be stored.
150    HostOwned(Box<UnsafeCell<VMMemoryDefinition>>),
151}
152
153/// We must implement this because of `VMMemoryDefinitionOwnership::VMOwned`.
154/// This is correct because synchronization of memory accesses is controlled
155/// by the VM.
156// REVIEW: I don't believe ^; this probably shouldn't be `Send`...
157// mutations from other threads into this data could be a problem, but we probably
158// don't want to use atomics for this in the generated code.
159// TODO:
160unsafe impl Send for LinearMemory {}
161
162/// This is correct because all internal mutability is protected by a mutex.
163unsafe impl Sync for LinearMemory {}
164
165#[derive(Debug)]
166struct WasmMmap {
167    // Our OS allocation of mmap'd memory.
168    alloc: Mmap,
169    // The current logical size in wasm pages of this linear memory.
170    size: Pages,
171}
172
173impl LinearMemory {
174    /// Create a new linear memory instance with specified minimum and maximum number of wasm pages.
175    ///
176    /// This creates a `LinearMemory` with owned metadata: this can be used to create a memory
177    /// that will be imported into Wasm modules.
178    pub fn new(memory: &MemoryType, style: &MemoryStyle) -> Result<Self, MemoryError> {
179        unsafe { Self::new_internal(memory, style, None) }
180    }
181
182    /// Create a new linear memory instance with specified minimum and maximum number of wasm pages.
183    ///
184    /// This creates a `LinearMemory` with metadata owned by a VM, pointed to by
185    /// `vm_memory_location`: this can be used to create a local memory.
186    ///
187    /// # Safety
188    /// - `vm_memory_location` must point to a valid location in VM memory.
189    pub unsafe fn from_definition(
190        memory: &MemoryType,
191        style: &MemoryStyle,
192        vm_memory_location: NonNull<VMMemoryDefinition>,
193    ) -> Result<Self, MemoryError> {
194        Self::new_internal(memory, style, Some(vm_memory_location))
195    }
196
197    /// Build a `LinearMemory` with either self-owned or VM owned metadata.
198    unsafe fn new_internal(
199        memory: &MemoryType,
200        style: &MemoryStyle,
201        vm_memory_location: Option<NonNull<VMMemoryDefinition>>,
202    ) -> Result<Self, MemoryError> {
203        if memory.minimum > Pages::max_value() {
204            return Err(MemoryError::MinimumMemoryTooLarge {
205                min_requested: memory.minimum,
206                max_allowed: Pages::max_value(),
207            });
208        }
209        // `maximum` cannot be set to more than `65536` pages.
210        if let Some(max) = memory.maximum {
211            if max > Pages::max_value() {
212                return Err(MemoryError::MaximumMemoryTooLarge {
213                    max_requested: max,
214                    max_allowed: Pages::max_value(),
215                });
216            }
217            if max < memory.minimum {
218                return Err(MemoryError::InvalidMemory {
219                    reason: format!(
220                        "the maximum ({} pages) is less than the minimum ({} pages)",
221                        max.0, memory.minimum.0
222                    ),
223                });
224            }
225        }
226
227        let offset_guard_bytes = style.offset_guard_size() as usize;
228
229        let minimum_pages = match style {
230            MemoryStyle::Dynamic { .. } => memory.minimum,
231            MemoryStyle::Static { bound, .. } => {
232                assert_ge!(*bound, memory.minimum);
233                *bound
234            }
235        };
236        let minimum_bytes = minimum_pages.bytes().0;
237        let request_bytes = minimum_bytes.checked_add(offset_guard_bytes).unwrap();
238        let mapped_pages = memory.minimum;
239        let mapped_bytes = mapped_pages.bytes();
240
241        let mut mmap = WasmMmap {
242            alloc: Mmap::accessible_reserved(mapped_bytes.0, request_bytes)
243                .map_err(MemoryError::Region)?,
244            size: memory.minimum,
245        };
246
247        let base_ptr = mmap.alloc.as_mut_ptr();
248        let mem_length = memory.minimum.bytes().0;
249        Ok(Self {
250            mmap: Mutex::new(mmap),
251            maximum: memory.maximum,
252            offset_guard_size: offset_guard_bytes,
253            vm_memory_definition: if let Some(mem_loc) = vm_memory_location {
254                {
255                    let mut ptr = mem_loc;
256                    let md = ptr.as_mut();
257                    md.base = base_ptr;
258                    md.current_length = mem_length;
259                }
260                VMMemoryDefinitionOwnership::VMOwned(mem_loc)
261            } else {
262                VMMemoryDefinitionOwnership::HostOwned(Box::new(UnsafeCell::new(
263                    VMMemoryDefinition {
264                        base: base_ptr,
265                        current_length: mem_length,
266                    },
267                )))
268            },
269            memory: *memory,
270            style: style.clone(),
271        })
272    }
273
274    /// Get the `VMMemoryDefinition`.
275    ///
276    /// # Safety
277    /// - You must ensure that you have mutually exclusive access before calling
278    ///   this function. You can get this by locking the `mmap` mutex.
279    unsafe fn get_vm_memory_definition(&self) -> NonNull<VMMemoryDefinition> {
280        match &self.vm_memory_definition {
281            VMMemoryDefinitionOwnership::VMOwned(ptr) => *ptr,
282            VMMemoryDefinitionOwnership::HostOwned(boxed_ptr) => {
283                NonNull::new_unchecked(boxed_ptr.get())
284            }
285        }
286    }
287}
288
289impl Memory for LinearMemory {
290    /// Returns the type for this memory.
291    fn ty(&self) -> MemoryType {
292        let minimum = self.size();
293        let mut out = self.memory;
294        out.minimum = minimum;
295
296        out
297    }
298
299    /// Returns the memory style for this memory.
300    fn style(&self) -> &MemoryStyle {
301        &self.style
302    }
303
304    /// Returns the number of allocated wasm pages.
305    fn size(&self) -> Pages {
306        // TODO: investigate this function for race conditions
307        unsafe {
308            let md_ptr = self.get_vm_memory_definition();
309            let md = md_ptr.as_ref();
310            Bytes::from(md.current_length).try_into().unwrap()
311        }
312    }
313
314    /// Grow memory by the specified amount of wasm pages.
315    ///
316    /// Returns `None` if memory can't be grown by the specified amount
317    /// of wasm pages.
318    fn grow(&self, delta: Pages) -> Result<Pages, MemoryError> {
319        let mut mmap_guard = self.mmap.lock().unwrap();
320        let mmap = mmap_guard.borrow_mut();
321        // Optimization of memory.grow 0 calls.
322        if delta.0 == 0 {
323            return Ok(mmap.size);
324        }
325
326        let new_pages = mmap
327            .size
328            .checked_add(delta)
329            .ok_or(MemoryError::CouldNotGrow {
330                current: mmap.size,
331                attempted_delta: delta,
332            })?;
333        let prev_pages = mmap.size;
334
335        if let Some(maximum) = self.maximum {
336            if new_pages > maximum {
337                return Err(MemoryError::CouldNotGrow {
338                    current: mmap.size,
339                    attempted_delta: delta,
340                });
341            }
342        }
343
344        // Wasm linear memories are never allowed to grow beyond what is
345        // indexable. If the memory has no maximum, enforce the greatest
346        // limit here.
347        if new_pages >= Pages::max_value() {
348            // Linear memory size would exceed the index range.
349            return Err(MemoryError::CouldNotGrow {
350                current: mmap.size,
351                attempted_delta: delta,
352            });
353        }
354
355        let delta_bytes = delta.bytes().0;
356        let prev_bytes = prev_pages.bytes().0;
357        let new_bytes = new_pages.bytes().0;
358
359        if new_bytes > mmap.alloc.len() - self.offset_guard_size {
360            // If the new size is within the declared maximum, but needs more memory than we
361            // have on hand, it's a dynamic heap and it can move.
362            let guard_bytes = self.offset_guard_size;
363            let request_bytes =
364                new_bytes
365                    .checked_add(guard_bytes)
366                    .ok_or_else(|| MemoryError::CouldNotGrow {
367                        current: new_pages,
368                        attempted_delta: Bytes(guard_bytes).try_into().unwrap(),
369                    })?;
370
371            let mut new_mmap =
372                Mmap::accessible_reserved(new_bytes, request_bytes).map_err(MemoryError::Region)?;
373
374            let copy_len = mmap.alloc.len() - self.offset_guard_size;
375            new_mmap.as_mut_slice()[..copy_len].copy_from_slice(&mmap.alloc.as_slice()[..copy_len]);
376
377            mmap.alloc = new_mmap;
378        } else if delta_bytes > 0 {
379            // Make the newly allocated pages accessible.
380            mmap.alloc
381                .make_accessible(prev_bytes, delta_bytes)
382                .map_err(MemoryError::Region)?;
383        }
384
385        mmap.size = new_pages;
386
387        // update memory definition
388        unsafe {
389            let mut md_ptr = self.get_vm_memory_definition();
390            let md = md_ptr.as_mut();
391            md.current_length = new_pages.bytes().0;
392            md.base = mmap.alloc.as_mut_ptr() as _;
393        }
394
395        Ok(prev_pages)
396    }
397
398    /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code.
399    fn vmmemory(&self) -> NonNull<VMMemoryDefinition> {
400        let _mmap_guard = self.mmap.lock().unwrap();
401        unsafe { self.get_vm_memory_definition() }
402    }
403}