Skip to main content

wasmer_sys_utils/memory/fd_memory/
memories.rs

1//! Memory management for linear memories.
2//!
3//! `Memory` is to WebAssembly linear memories what `Table` is to WebAssembly tables.
4// This file contains code from external sources.
5// Attributions: https://github.com/wasmerio/wasmer/blob/main/docs/ATTRIBUTIONS.md
6
7use std::{
8    cell::UnsafeCell,
9    convert::TryInto,
10    ptr::NonNull,
11    sync::{Arc, RwLock},
12};
13
14use wasmer::{Bytes, MemoryError, MemoryType, Pages};
15use wasmer_types::{MemoryStyle, WASM_PAGE_SIZE};
16use wasmer_vm::{
17    LinearMemory, MaybeInstanceOwned, ThreadConditions, Trap, VMMemoryDefinition, WaiterError,
18};
19
20use super::fd_mmap::FdMmap;
21
22// use crate::trap::Trap;
23// use crate::{mmap::Mmap, store::MaybeInstanceOwned, vmcontext::VMMemoryDefinition};
24// use more_asserts::assert_ge;
25// use std::cell::UnsafeCell;
26// use std::convert::TryInto;
27// use std::ptr::NonNull;
28// use std::slice;
29// use std::sync::{Arc, RwLock};
30// use wasmer_types::{Bytes, MemoryError, MemoryStyle, MemoryType, Pages};
31
32// The memory mapped area
33#[derive(Debug)]
34struct WasmMmap {
35    // Our OS allocation of mmap'd memory.
36    alloc: FdMmap,
37    // The current logical size in wasm pages of this linear memory.
38    size: Pages,
39    /// The owned memory definition used by the generated code
40    vm_memory_definition: MaybeInstanceOwned<VMMemoryDefinition>,
41}
42
43/// # SAFETY: Not safe by rust standards, since guest code may do weird things
44/// with its memory. However, this is still safe to send across threads as
45/// far as the WASM spec is concerned.
46unsafe impl Send for WasmMmap {}
47/// # SAFETY: see above.
48unsafe impl Sync for WasmMmap {}
49
50impl WasmMmap {
51    fn get_vm_memory_definition(&self) -> NonNull<VMMemoryDefinition> {
52        self.vm_memory_definition.as_ptr()
53    }
54
55    fn size(&self) -> Pages {
56        unsafe {
57            let md_ptr = self.get_vm_memory_definition();
58            let md = md_ptr.as_ref();
59            Bytes::from(md.current_length).try_into().unwrap()
60        }
61    }
62
63    fn grow(&mut self, delta: Pages, conf: VMMemoryConfig) -> Result<Pages, MemoryError> {
64        // Optimization of memory.grow 0 calls.
65        if delta.0 == 0 {
66            return Ok(self.size);
67        }
68
69        let new_pages = self
70            .size
71            .checked_add(delta)
72            .ok_or(MemoryError::CouldNotGrow {
73                current: self.size,
74                attempted_delta: delta,
75            })?;
76        let prev_pages = self.size;
77
78        if let Some(maximum) = conf.maximum
79            && new_pages > maximum
80        {
81            return Err(MemoryError::CouldNotGrow {
82                current: self.size,
83                attempted_delta: delta,
84            });
85        }
86
87        // Wasm linear memories are never allowed to grow beyond what is
88        // indexable. If the memory has no maximum, enforce the greatest
89        // limit here.
90        if new_pages >= Pages::max_value() {
91            // Linear memory size would exceed the index range.
92            return Err(MemoryError::CouldNotGrow {
93                current: self.size,
94                attempted_delta: delta,
95            });
96        }
97
98        let delta_bytes = delta.bytes().0;
99        let prev_bytes = prev_pages.bytes().0;
100        let new_bytes = new_pages.bytes().0;
101
102        if new_bytes > self.alloc.len() - conf.offset_guard_size {
103            // If the new size is within the declared maximum, but needs more memory than we
104            // have on hand, it's a dynamic heap and it can move.
105            let guard_bytes = conf.offset_guard_size;
106            let request_bytes =
107                new_bytes
108                    .checked_add(guard_bytes)
109                    .ok_or_else(|| MemoryError::CouldNotGrow {
110                        current: new_pages,
111                        attempted_delta: Bytes(guard_bytes).try_into().unwrap(),
112                    })?;
113
114            let mut new_mmap = FdMmap::accessible_reserved(new_bytes, request_bytes)
115                .map_err(MemoryError::Region)?;
116
117            let copy_len = self.alloc.len() - conf.offset_guard_size;
118            new_mmap.as_mut_slice()[..copy_len].copy_from_slice(&self.alloc.as_slice()[..copy_len]);
119
120            self.alloc = new_mmap;
121        } else if delta_bytes > 0 {
122            // Make the newly allocated pages accessible.
123            self.alloc
124                .make_accessible(prev_bytes, delta_bytes)
125                .map_err(MemoryError::Region)?;
126        }
127
128        self.size = new_pages;
129
130        // update memory definition
131        unsafe {
132            let mut md_ptr = self.vm_memory_definition.as_ptr();
133            let md = md_ptr.as_mut();
134            md.current_length = new_pages.bytes().0;
135            md.base = self.alloc.as_mut_ptr() as _;
136        }
137
138        Ok(prev_pages)
139    }
140
141    /// Grows the memory to at least a minimum size. If the memory is already big enough
142    /// for the min size then this function does nothing
143    fn grow_at_least(&mut self, min_size: u64, conf: VMMemoryConfig) -> Result<(), MemoryError> {
144        let cur_size = self.size.bytes().0 as u64;
145        if cur_size < min_size {
146            let growth = min_size - cur_size;
147            let growth_pages = ((growth - 1) / WASM_PAGE_SIZE as u64) + 1;
148            self.grow(Pages(growth_pages as u32), conf)?;
149        }
150
151        Ok(())
152    }
153
154    fn reset(&mut self) -> Result<(), MemoryError> {
155        self.size.0 = 0;
156        Ok(())
157    }
158
159    /// Copies the memory
160    /// (in this case it performs a copy-on-write to save memory)
161    pub fn copy(&self) -> Result<Self, MemoryError> {
162        let mem_length = self.size.bytes().0;
163        let mut alloc = self
164            .alloc
165            .duplicate(Some(mem_length))
166            .map_err(MemoryError::Generic)?;
167        let base_ptr = alloc.as_mut_ptr();
168        Ok(Self {
169            vm_memory_definition: MaybeInstanceOwned::Host(Box::new(UnsafeCell::new(
170                VMMemoryDefinition {
171                    base: base_ptr,
172                    current_length: mem_length,
173                },
174            ))),
175            alloc,
176            size: self.size,
177        })
178    }
179}
180
181/// A linear memory instance.
182#[derive(Debug, Clone)]
183struct VMMemoryConfig {
184    // The optional maximum size in wasm pages of this linear memory.
185    maximum: Option<Pages>,
186    /// The WebAssembly linear memory description.
187    memory: MemoryType,
188    /// Our chosen implementation style.
189    style: MemoryStyle,
190    // Size in bytes of extra guard pages after the end to optimize loads and stores with
191    // constant offsets.
192    offset_guard_size: usize,
193}
194
195impl VMMemoryConfig {
196    fn ty(&self, minimum: Pages) -> MemoryType {
197        let mut out = self.memory;
198        out.minimum = minimum;
199
200        out
201    }
202
203    fn style(&self) -> MemoryStyle {
204        self.style
205    }
206}
207
208/// A linear memory instance.
209#[derive(Debug)]
210pub struct VMOwnedMemory {
211    // The underlying allocation.
212    mmap: WasmMmap,
213    // Configuration of this memory
214    config: VMMemoryConfig,
215}
216
217unsafe impl Send for VMOwnedMemory {}
218unsafe impl Sync for VMOwnedMemory {}
219
220impl VMOwnedMemory {
221    /// Create a new linear memory instance with specified minimum and maximum number of wasm pages.
222    ///
223    /// This creates a `Memory` with owned metadata: this can be used to create a memory
224    /// that will be imported into Wasm modules.
225    pub fn new(memory: &MemoryType, style: &MemoryStyle) -> Result<Self, MemoryError> {
226        unsafe { Self::new_internal(memory, style, None) }
227    }
228
229    /// Create a new linear memory instance with specified minimum and maximum number of wasm pages.
230    ///
231    /// This creates a `Memory` with metadata owned by a VM, pointed to by
232    /// `vm_memory_location`: this can be used to create a local memory.
233    ///
234    /// # Safety
235    /// - `vm_memory_location` must point to a valid location in VM memory.
236    pub unsafe fn from_definition(
237        memory: &MemoryType,
238        style: &MemoryStyle,
239        vm_memory_location: NonNull<VMMemoryDefinition>,
240    ) -> Result<Self, MemoryError> {
241        unsafe { Self::new_internal(memory, style, Some(vm_memory_location)) }
242    }
243
244    /// Build a `Memory` with either self-owned or VM owned metadata.
245    unsafe fn new_internal(
246        memory: &MemoryType,
247        style: &MemoryStyle,
248        vm_memory_location: Option<NonNull<VMMemoryDefinition>>,
249    ) -> Result<Self, MemoryError> {
250        if memory.minimum > Pages::max_value() {
251            return Err(MemoryError::MinimumMemoryTooLarge {
252                min_requested: memory.minimum,
253                max_allowed: Pages::max_value(),
254            });
255        }
256        // `maximum` cannot be set to more than `65536` pages.
257        if let Some(max) = memory.maximum {
258            if max > Pages::max_value() {
259                return Err(MemoryError::MaximumMemoryTooLarge {
260                    max_requested: max,
261                    max_allowed: Pages::max_value(),
262                });
263            }
264            if max < memory.minimum {
265                return Err(MemoryError::InvalidMemory {
266                    reason: format!(
267                        "the maximum ({} pages) is less than the minimum ({} pages)",
268                        max.0, memory.minimum.0
269                    ),
270                });
271            }
272        }
273
274        let offset_guard_bytes = usize::try_from(style.offset_guard_size())
275            .map_err(|e| MemoryError::Generic(format!("cannot install memory guard page: {e}")))?;
276
277        let minimum_pages = match style {
278            MemoryStyle::Dynamic { .. } => memory.minimum,
279            MemoryStyle::Static => {
280                let bound = MemoryStyle::static_bound();
281                assert!(bound >= memory.minimum);
282                bound
283            }
284        };
285        let minimum_bytes = minimum_pages.bytes().0;
286        let request_bytes = minimum_bytes.checked_add(offset_guard_bytes).unwrap();
287        let mapped_pages = memory.minimum;
288        let mapped_bytes = mapped_pages.bytes();
289
290        let mut alloc = FdMmap::accessible_reserved(mapped_bytes.0, request_bytes)
291            .map_err(MemoryError::Region)?;
292        let base_ptr = alloc.as_mut_ptr();
293        let mem_length = memory.minimum.bytes().0;
294        let mmap = WasmMmap {
295            vm_memory_definition: if let Some(mem_loc) = vm_memory_location {
296                {
297                    let mut ptr = mem_loc;
298                    let md = unsafe { ptr.as_mut() };
299                    md.base = base_ptr;
300                    md.current_length = mem_length;
301                }
302                MaybeInstanceOwned::Instance(mem_loc)
303            } else {
304                MaybeInstanceOwned::Host(Box::new(UnsafeCell::new(VMMemoryDefinition {
305                    base: base_ptr,
306                    current_length: mem_length,
307                })))
308            },
309            alloc,
310            size: memory.minimum,
311        };
312
313        Ok(Self {
314            mmap,
315            config: VMMemoryConfig {
316                maximum: memory.maximum,
317                offset_guard_size: offset_guard_bytes,
318                memory: *memory,
319                style: *style,
320            },
321        })
322    }
323
324    /// Converts this owned memory into shared memory
325    pub fn to_shared(self) -> VMSharedMemory {
326        VMSharedMemory {
327            mmap: Arc::new(RwLock::new(self.mmap)),
328            config: self.config,
329            conditions: ThreadConditions::new(),
330        }
331    }
332
333    /// Copies this memory to a new memory
334    pub fn copy(&self) -> Result<Self, MemoryError> {
335        Ok(Self {
336            mmap: self.mmap.copy()?,
337            config: self.config.clone(),
338        })
339    }
340}
341
342impl LinearMemory for VMOwnedMemory {
343    /// Returns the type for this memory.
344    fn ty(&self) -> MemoryType {
345        let minimum = self.mmap.size();
346        self.config.ty(minimum)
347    }
348
349    /// Returns the size of the memory in pages
350    fn size(&self) -> Pages {
351        self.mmap.size()
352    }
353
354    /// Returns the memory style for this memory.
355    fn style(&self) -> MemoryStyle {
356        self.config.style()
357    }
358
359    /// Grow memory by the specified amount of wasm pages.
360    ///
361    /// Returns `None` if memory can't be grown by the specified amount
362    /// of wasm pages.
363    fn grow(&mut self, delta: Pages) -> Result<Pages, MemoryError> {
364        self.mmap.grow(delta, self.config.clone())
365    }
366
367    /// Grows the memory to at least a minimum size. If the memory is already big enough
368    /// for the min size then this function does nothing
369    fn grow_at_least(&mut self, min_size: u64) -> Result<(), MemoryError> {
370        self.mmap.grow_at_least(min_size, self.config.clone())
371    }
372
373    fn reset(&mut self) -> Result<(), MemoryError> {
374        self.mmap.reset()?;
375        Ok(())
376    }
377
378    /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code.
379    fn vmmemory(&self) -> NonNull<VMMemoryDefinition> {
380        self.mmap.vm_memory_definition.as_ptr()
381    }
382
383    /// Owned memory can not be cloned (this will always return None)
384    fn try_clone(&self) -> Result<Box<dyn LinearMemory + Send + Sync + 'static>, MemoryError> {
385        Err(MemoryError::MemoryNotShared)
386    }
387
388    /// Copies this memory to a new memory
389    fn copy(&self) -> Result<Box<dyn LinearMemory + Send + Sync + 'static>, MemoryError> {
390        let forked = Self::copy(self)?;
391        Ok(Box::new(forked))
392    }
393}
394
395/// A shared linear memory instance.
396#[derive(Debug, Clone)]
397pub struct VMSharedMemory {
398    // The underlying allocation.
399    mmap: Arc<RwLock<WasmMmap>>,
400    // Configuration of this memory
401    config: VMMemoryConfig,
402    conditions: ThreadConditions,
403}
404
405impl VMSharedMemory {
406    /// Create a new linear memory instance with specified minimum and maximum number of wasm pages.
407    ///
408    /// This creates a `Memory` with owned metadata: this can be used to create a memory
409    /// that will be imported into Wasm modules.
410    pub fn new(memory: &MemoryType, style: &MemoryStyle) -> Result<Self, MemoryError> {
411        Ok(VMOwnedMemory::new(memory, style)?.to_shared())
412    }
413
414    /// Create a new linear memory instance with specified minimum and maximum number of wasm pages.
415    ///
416    /// This creates a `Memory` with metadata owned by a VM, pointed to by
417    /// `vm_memory_location`: this can be used to create a local memory.
418    ///
419    /// # Safety
420    /// - `vm_memory_location` must point to a valid location in VM memory.
421    pub unsafe fn from_definition(
422        memory: &MemoryType,
423        style: &MemoryStyle,
424        vm_memory_location: NonNull<VMMemoryDefinition>,
425    ) -> Result<Self, MemoryError> {
426        let owned = unsafe { VMOwnedMemory::from_definition(memory, style, vm_memory_location)? };
427        Ok(owned.to_shared())
428    }
429
430    /// Copies this memory to a new memory
431    pub fn copy(&self) -> Result<Self, MemoryError> {
432        let guard = self.mmap.read().unwrap();
433        Ok(Self {
434            mmap: Arc::new(RwLock::new(guard.copy()?)),
435            config: self.config.clone(),
436            conditions: ThreadConditions::new(),
437        })
438    }
439}
440
441impl LinearMemory for VMSharedMemory {
442    /// Returns the type for this memory.
443    fn ty(&self) -> MemoryType {
444        let minimum = {
445            let guard = self.mmap.read().unwrap();
446            guard.size()
447        };
448        self.config.ty(minimum)
449    }
450
451    /// Returns the size of the memory in pages
452    fn size(&self) -> Pages {
453        let guard = self.mmap.read().unwrap();
454        guard.size()
455    }
456
457    /// Resets the memory back down to zero size
458    fn reset(&mut self) -> Result<(), MemoryError> {
459        let mut guard = self.mmap.write().unwrap();
460        guard.reset()?;
461        Ok(())
462    }
463
464    /// Returns the memory style for this memory.
465    fn style(&self) -> MemoryStyle {
466        self.config.style()
467    }
468
469    /// Grow memory by the specified amount of wasm pages.
470    ///
471    /// Returns `None` if memory can't be grown by the specified amount
472    /// of wasm pages.
473    fn grow(&mut self, delta: Pages) -> Result<Pages, MemoryError> {
474        let mut guard = self.mmap.write().unwrap();
475        guard.grow(delta, self.config.clone())
476    }
477
478    /// Grows the memory to at least a minimum size. If the memory is already big enough
479    /// for the min size then this function does nothing
480    fn grow_at_least(&mut self, min_size: u64) -> Result<(), MemoryError> {
481        let mut guard = self.mmap.write().unwrap();
482        guard.grow_at_least(min_size, self.config.clone())
483    }
484
485    /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code.
486    fn vmmemory(&self) -> NonNull<VMMemoryDefinition> {
487        let guard = self.mmap.read().unwrap();
488        guard.vm_memory_definition.as_ptr()
489    }
490
491    /// Shared memory can always be cloned
492    fn try_clone(&self) -> Result<Box<dyn LinearMemory + Send + Sync + 'static>, MemoryError> {
493        Ok(Box::new(self.clone()))
494    }
495
496    /// Copies this memory to a new memory
497    fn copy(&self) -> Result<Box<dyn LinearMemory + Send + Sync + 'static>, MemoryError> {
498        let forked = Self::copy(self)?;
499        Ok(Box::new(forked))
500    }
501
502    unsafe fn do_wait(
503        &mut self,
504        dst: u32,
505        expected: wasmer_vm::ExpectedValue,
506        timeout: Option<std::time::Duration>,
507    ) -> Result<u32, WaiterError> {
508        unsafe {
509            let dst = wasmer_vm::NotifyLocation {
510                address: dst,
511                memory_base: self
512                    .mmap
513                    .read()
514                    .unwrap()
515                    .vm_memory_definition
516                    .as_ptr()
517                    .as_ref()
518                    .base,
519            };
520            self.conditions.do_wait(dst, expected, timeout)
521        }
522    }
523
524    fn do_notify(&mut self, dst: u32, count: u32) -> u32 {
525        self.conditions.do_notify(dst, count)
526    }
527}
528
529impl From<VMOwnedMemory> for VMMemory {
530    fn from(mem: VMOwnedMemory) -> Self {
531        Self(Box::new(mem))
532    }
533}
534
535impl From<VMSharedMemory> for VMMemory {
536    fn from(mem: VMSharedMemory) -> Self {
537        Self(Box::new(mem))
538    }
539}
540
541/// Represents linear memory that can be either owned or shared
542#[derive(Debug)]
543pub struct VMMemory(pub Box<dyn LinearMemory + 'static>);
544
545impl From<Box<dyn LinearMemory + 'static>> for VMMemory {
546    fn from(mem: Box<dyn LinearMemory + 'static>) -> Self {
547        Self(mem)
548    }
549}
550
551impl LinearMemory for VMMemory {
552    /// Returns the type for this memory.
553    fn ty(&self) -> MemoryType {
554        self.0.ty()
555    }
556
557    /// Returns the size of the memory in pages
558    fn size(&self) -> Pages {
559        self.0.size()
560    }
561
562    /// Grow memory by the specified amount of wasm pages.
563    ///
564    /// Returns `None` if memory can't be grown by the specified amount
565    /// of wasm pages.
566    fn grow(&mut self, delta: Pages) -> Result<Pages, MemoryError> {
567        self.0.grow(delta)
568    }
569
570    /// Grows the memory to at least a minimum size. If the memory is already big enough
571    /// for the min size then this function does nothing
572    fn grow_at_least(&mut self, min_size: u64) -> Result<(), MemoryError> {
573        self.0.grow_at_least(min_size)
574    }
575
576    /// Resets the memory down to a zero size
577    fn reset(&mut self) -> Result<(), MemoryError> {
578        self.0.reset()?;
579        Ok(())
580    }
581
582    /// Returns the memory style for this memory.
583    fn style(&self) -> MemoryStyle {
584        self.0.style()
585    }
586
587    /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code.
588    fn vmmemory(&self) -> NonNull<VMMemoryDefinition> {
589        self.0.vmmemory()
590    }
591
592    /// Attempts to clone this memory (if its cloneable)
593    fn try_clone(&self) -> Result<Box<dyn LinearMemory + Send + Sync + 'static>, MemoryError> {
594        self.0.try_clone()
595    }
596
597    /// Initialize memory with data
598    unsafe fn initialize_with_data(&self, start: usize, data: &[u8]) -> Result<(), Trap> {
599        unsafe { self.0.initialize_with_data(start, data) }
600    }
601
602    /// Copies this memory to a new memory
603    fn copy(&self) -> Result<Box<dyn LinearMemory + Send + Sync + 'static>, MemoryError> {
604        self.0.copy()
605    }
606}
607
608impl VMMemory {
609    /// Creates a new linear memory instance of the correct type with specified
610    /// minimum and maximum number of wasm pages.
611    ///
612    /// This creates a `Memory` with owned metadata: this can be used to create a memory
613    /// that will be imported into Wasm modules.
614    pub fn new(memory: &MemoryType, style: &MemoryStyle) -> Result<Self, MemoryError> {
615        Ok(if memory.shared {
616            Self(Box::new(VMSharedMemory::new(memory, style)?))
617        } else {
618            Self(Box::new(VMOwnedMemory::new(memory, style)?))
619        })
620    }
621
622    /// Returns the number of pages in the allocated memory block
623    pub fn get_runtime_size(&self) -> u32 {
624        self.0.size().0
625    }
626
627    /// Create a new linear memory instance with specified minimum and maximum number of wasm pages.
628    ///
629    /// This creates a `Memory` with metadata owned by a VM, pointed to by
630    /// `vm_memory_location`: this can be used to create a local memory.
631    ///
632    /// # Safety
633    /// - `vm_memory_location` must point to a valid location in VM memory.
634    pub unsafe fn from_definition(
635        memory: &MemoryType,
636        style: &MemoryStyle,
637        vm_memory_location: NonNull<VMMemoryDefinition>,
638    ) -> Result<Self, MemoryError> {
639        Ok(if memory.shared {
640            let shared =
641                unsafe { VMSharedMemory::from_definition(memory, style, vm_memory_location)? };
642            Self(Box::new(shared))
643        } else {
644            let owned =
645                unsafe { VMOwnedMemory::from_definition(memory, style, vm_memory_location)? };
646            Self(Box::new(owned))
647        })
648    }
649
650    /// Creates VMMemory from a custom implementation - the following into implementations
651    /// are natively supported
652    /// - VMOwnedMemory -> VMMemory
653    /// - Box<dyn LinearMemory + 'static> -> VMMemory
654    pub fn from_custom<IntoVMMemory>(memory: IntoVMMemory) -> Self
655    where
656        IntoVMMemory: Into<Self>,
657    {
658        memory.into()
659    }
660
661    /// Copies this memory to a new memory
662    pub fn copy(&self) -> Result<Box<dyn LinearMemory + Send + Sync + 'static>, MemoryError> {
663        LinearMemory::copy(self)
664    }
665}
666
667#[doc(hidden)]
668/// Default implementation to initialize memory with data
669pub unsafe fn initialize_memory_with_data(
670    memory: &VMMemoryDefinition,
671    start: usize,
672    data: &[u8],
673) -> Result<(), Trap> {
674    let mem_slice = unsafe { std::slice::from_raw_parts_mut(memory.base, memory.current_length) };
675    let end = start + data.len();
676    let to_init = &mut mem_slice[start..end];
677    to_init.copy_from_slice(data);
678
679    Ok(())
680}