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 = style.offset_guard_size() as usize;
275
276        let minimum_pages = match style {
277            MemoryStyle::Dynamic { .. } => memory.minimum,
278            MemoryStyle::Static { bound, .. } => {
279                assert!(*bound >= memory.minimum);
280                *bound
281            }
282        };
283        let minimum_bytes = minimum_pages.bytes().0;
284        let request_bytes = minimum_bytes.checked_add(offset_guard_bytes).unwrap();
285        let mapped_pages = memory.minimum;
286        let mapped_bytes = mapped_pages.bytes();
287
288        let mut alloc = FdMmap::accessible_reserved(mapped_bytes.0, request_bytes)
289            .map_err(MemoryError::Region)?;
290        let base_ptr = alloc.as_mut_ptr();
291        let mem_length = memory.minimum.bytes().0;
292        let mmap = WasmMmap {
293            vm_memory_definition: if let Some(mem_loc) = vm_memory_location {
294                {
295                    let mut ptr = mem_loc;
296                    let md = unsafe { ptr.as_mut() };
297                    md.base = base_ptr;
298                    md.current_length = mem_length;
299                }
300                MaybeInstanceOwned::Instance(mem_loc)
301            } else {
302                MaybeInstanceOwned::Host(Box::new(UnsafeCell::new(VMMemoryDefinition {
303                    base: base_ptr,
304                    current_length: mem_length,
305                })))
306            },
307            alloc,
308            size: memory.minimum,
309        };
310
311        Ok(Self {
312            mmap,
313            config: VMMemoryConfig {
314                maximum: memory.maximum,
315                offset_guard_size: offset_guard_bytes,
316                memory: *memory,
317                style: *style,
318            },
319        })
320    }
321
322    /// Converts this owned memory into shared memory
323    pub fn to_shared(self) -> VMSharedMemory {
324        VMSharedMemory {
325            mmap: Arc::new(RwLock::new(self.mmap)),
326            config: self.config,
327            conditions: ThreadConditions::new(),
328        }
329    }
330
331    /// Copies this memory to a new memory
332    pub fn copy(&self) -> Result<Self, MemoryError> {
333        Ok(Self {
334            mmap: self.mmap.copy()?,
335            config: self.config.clone(),
336        })
337    }
338}
339
340impl LinearMemory for VMOwnedMemory {
341    /// Returns the type for this memory.
342    fn ty(&self) -> MemoryType {
343        let minimum = self.mmap.size();
344        self.config.ty(minimum)
345    }
346
347    /// Returns the size of the memory in pages
348    fn size(&self) -> Pages {
349        self.mmap.size()
350    }
351
352    /// Returns the memory style for this memory.
353    fn style(&self) -> MemoryStyle {
354        self.config.style()
355    }
356
357    /// Grow memory by the specified amount of wasm pages.
358    ///
359    /// Returns `None` if memory can't be grown by the specified amount
360    /// of wasm pages.
361    fn grow(&mut self, delta: Pages) -> Result<Pages, MemoryError> {
362        self.mmap.grow(delta, self.config.clone())
363    }
364
365    /// Grows the memory to at least a minimum size. If the memory is already big enough
366    /// for the min size then this function does nothing
367    fn grow_at_least(&mut self, min_size: u64) -> Result<(), MemoryError> {
368        self.mmap.grow_at_least(min_size, self.config.clone())
369    }
370
371    fn reset(&mut self) -> Result<(), MemoryError> {
372        self.mmap.reset()?;
373        Ok(())
374    }
375
376    /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code.
377    fn vmmemory(&self) -> NonNull<VMMemoryDefinition> {
378        self.mmap.vm_memory_definition.as_ptr()
379    }
380
381    /// Owned memory can not be cloned (this will always return None)
382    fn try_clone(&self) -> Result<Box<dyn LinearMemory + Send + Sync + 'static>, MemoryError> {
383        Err(MemoryError::MemoryNotShared)
384    }
385
386    /// Copies this memory to a new memory
387    fn copy(&self) -> Result<Box<dyn LinearMemory + Send + Sync + 'static>, MemoryError> {
388        let forked = Self::copy(self)?;
389        Ok(Box::new(forked))
390    }
391}
392
393/// A shared linear memory instance.
394#[derive(Debug, Clone)]
395pub struct VMSharedMemory {
396    // The underlying allocation.
397    mmap: Arc<RwLock<WasmMmap>>,
398    // Configuration of this memory
399    config: VMMemoryConfig,
400    conditions: ThreadConditions,
401}
402
403impl VMSharedMemory {
404    /// Create a new linear memory instance with specified minimum and maximum number of wasm pages.
405    ///
406    /// This creates a `Memory` with owned metadata: this can be used to create a memory
407    /// that will be imported into Wasm modules.
408    pub fn new(memory: &MemoryType, style: &MemoryStyle) -> Result<Self, MemoryError> {
409        Ok(VMOwnedMemory::new(memory, style)?.to_shared())
410    }
411
412    /// Create a new linear memory instance with specified minimum and maximum number of wasm pages.
413    ///
414    /// This creates a `Memory` with metadata owned by a VM, pointed to by
415    /// `vm_memory_location`: this can be used to create a local memory.
416    ///
417    /// # Safety
418    /// - `vm_memory_location` must point to a valid location in VM memory.
419    pub unsafe fn from_definition(
420        memory: &MemoryType,
421        style: &MemoryStyle,
422        vm_memory_location: NonNull<VMMemoryDefinition>,
423    ) -> Result<Self, MemoryError> {
424        let owned = unsafe { VMOwnedMemory::from_definition(memory, style, vm_memory_location)? };
425        Ok(owned.to_shared())
426    }
427
428    /// Copies this memory to a new memory
429    pub fn copy(&self) -> Result<Self, MemoryError> {
430        let guard = self.mmap.read().unwrap();
431        Ok(Self {
432            mmap: Arc::new(RwLock::new(guard.copy()?)),
433            config: self.config.clone(),
434            conditions: ThreadConditions::new(),
435        })
436    }
437}
438
439impl LinearMemory for VMSharedMemory {
440    /// Returns the type for this memory.
441    fn ty(&self) -> MemoryType {
442        let minimum = {
443            let guard = self.mmap.read().unwrap();
444            guard.size()
445        };
446        self.config.ty(minimum)
447    }
448
449    /// Returns the size of the memory in pages
450    fn size(&self) -> Pages {
451        let guard = self.mmap.read().unwrap();
452        guard.size()
453    }
454
455    /// Resets the memory back down to zero size
456    fn reset(&mut self) -> Result<(), MemoryError> {
457        let mut guard = self.mmap.write().unwrap();
458        guard.reset()?;
459        Ok(())
460    }
461
462    /// Returns the memory style for this memory.
463    fn style(&self) -> MemoryStyle {
464        self.config.style()
465    }
466
467    /// Grow memory by the specified amount of wasm pages.
468    ///
469    /// Returns `None` if memory can't be grown by the specified amount
470    /// of wasm pages.
471    fn grow(&mut self, delta: Pages) -> Result<Pages, MemoryError> {
472        let mut guard = self.mmap.write().unwrap();
473        guard.grow(delta, self.config.clone())
474    }
475
476    /// Grows the memory to at least a minimum size. If the memory is already big enough
477    /// for the min size then this function does nothing
478    fn grow_at_least(&mut self, min_size: u64) -> Result<(), MemoryError> {
479        let mut guard = self.mmap.write().unwrap();
480        guard.grow_at_least(min_size, self.config.clone())
481    }
482
483    /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code.
484    fn vmmemory(&self) -> NonNull<VMMemoryDefinition> {
485        let guard = self.mmap.read().unwrap();
486        guard.vm_memory_definition.as_ptr()
487    }
488
489    /// Shared memory can always be cloned
490    fn try_clone(&self) -> Result<Box<dyn LinearMemory + Send + Sync + 'static>, MemoryError> {
491        Ok(Box::new(self.clone()))
492    }
493
494    /// Copies this memory to a new memory
495    fn copy(&self) -> Result<Box<dyn LinearMemory + Send + Sync + 'static>, MemoryError> {
496        let forked = Self::copy(self)?;
497        Ok(Box::new(forked))
498    }
499
500    unsafe fn do_wait(
501        &mut self,
502        dst: u32,
503        expected: wasmer_vm::ExpectedValue,
504        timeout: Option<std::time::Duration>,
505    ) -> Result<u32, WaiterError> {
506        unsafe {
507            let dst = wasmer_vm::NotifyLocation {
508                address: dst,
509                memory_base: self
510                    .mmap
511                    .read()
512                    .unwrap()
513                    .vm_memory_definition
514                    .as_ptr()
515                    .as_ref()
516                    .base,
517            };
518            self.conditions.do_wait(dst, expected, timeout)
519        }
520    }
521
522    fn do_notify(&mut self, dst: u32, count: u32) -> u32 {
523        self.conditions.do_notify(dst, count)
524    }
525}
526
527impl From<VMOwnedMemory> for VMMemory {
528    fn from(mem: VMOwnedMemory) -> Self {
529        Self(Box::new(mem))
530    }
531}
532
533impl From<VMSharedMemory> for VMMemory {
534    fn from(mem: VMSharedMemory) -> Self {
535        Self(Box::new(mem))
536    }
537}
538
539/// Represents linear memory that can be either owned or shared
540#[derive(Debug)]
541pub struct VMMemory(pub Box<dyn LinearMemory + 'static>);
542
543impl From<Box<dyn LinearMemory + 'static>> for VMMemory {
544    fn from(mem: Box<dyn LinearMemory + 'static>) -> Self {
545        Self(mem)
546    }
547}
548
549impl LinearMemory for VMMemory {
550    /// Returns the type for this memory.
551    fn ty(&self) -> MemoryType {
552        self.0.ty()
553    }
554
555    /// Returns the size of the memory in pages
556    fn size(&self) -> Pages {
557        self.0.size()
558    }
559
560    /// Grow memory by the specified amount of wasm pages.
561    ///
562    /// Returns `None` if memory can't be grown by the specified amount
563    /// of wasm pages.
564    fn grow(&mut self, delta: Pages) -> Result<Pages, MemoryError> {
565        self.0.grow(delta)
566    }
567
568    /// Grows the memory to at least a minimum size. If the memory is already big enough
569    /// for the min size then this function does nothing
570    fn grow_at_least(&mut self, min_size: u64) -> Result<(), MemoryError> {
571        self.0.grow_at_least(min_size)
572    }
573
574    /// Resets the memory down to a zero size
575    fn reset(&mut self) -> Result<(), MemoryError> {
576        self.0.reset()?;
577        Ok(())
578    }
579
580    /// Returns the memory style for this memory.
581    fn style(&self) -> MemoryStyle {
582        self.0.style()
583    }
584
585    /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code.
586    fn vmmemory(&self) -> NonNull<VMMemoryDefinition> {
587        self.0.vmmemory()
588    }
589
590    /// Attempts to clone this memory (if its cloneable)
591    fn try_clone(&self) -> Result<Box<dyn LinearMemory + Send + Sync + 'static>, MemoryError> {
592        self.0.try_clone()
593    }
594
595    /// Initialize memory with data
596    unsafe fn initialize_with_data(&self, start: usize, data: &[u8]) -> Result<(), Trap> {
597        unsafe { self.0.initialize_with_data(start, data) }
598    }
599
600    /// Copies this memory to a new memory
601    fn copy(&self) -> Result<Box<dyn LinearMemory + Send + Sync + 'static>, MemoryError> {
602        self.0.copy()
603    }
604}
605
606impl VMMemory {
607    /// Creates a new linear memory instance of the correct type with specified
608    /// minimum and maximum number of wasm pages.
609    ///
610    /// This creates a `Memory` with owned metadata: this can be used to create a memory
611    /// that will be imported into Wasm modules.
612    pub fn new(memory: &MemoryType, style: &MemoryStyle) -> Result<Self, MemoryError> {
613        Ok(if memory.shared {
614            Self(Box::new(VMSharedMemory::new(memory, style)?))
615        } else {
616            Self(Box::new(VMOwnedMemory::new(memory, style)?))
617        })
618    }
619
620    /// Returns the number of pages in the allocated memory block
621    pub fn get_runtime_size(&self) -> u32 {
622        self.0.size().0
623    }
624
625    /// Create a new linear memory instance with specified minimum and maximum number of wasm pages.
626    ///
627    /// This creates a `Memory` with metadata owned by a VM, pointed to by
628    /// `vm_memory_location`: this can be used to create a local memory.
629    ///
630    /// # Safety
631    /// - `vm_memory_location` must point to a valid location in VM memory.
632    pub unsafe fn from_definition(
633        memory: &MemoryType,
634        style: &MemoryStyle,
635        vm_memory_location: NonNull<VMMemoryDefinition>,
636    ) -> Result<Self, MemoryError> {
637        Ok(if memory.shared {
638            let shared =
639                unsafe { VMSharedMemory::from_definition(memory, style, vm_memory_location)? };
640            Self(Box::new(shared))
641        } else {
642            let owned =
643                unsafe { VMOwnedMemory::from_definition(memory, style, vm_memory_location)? };
644            Self(Box::new(owned))
645        })
646    }
647
648    /// Creates VMMemory from a custom implementation - the following into implementations
649    /// are natively supported
650    /// - VMOwnedMemory -> VMMemory
651    /// - Box<dyn LinearMemory + 'static> -> VMMemory
652    pub fn from_custom<IntoVMMemory>(memory: IntoVMMemory) -> Self
653    where
654        IntoVMMemory: Into<Self>,
655    {
656        memory.into()
657    }
658
659    /// Copies this memory to a new memory
660    pub fn copy(&self) -> Result<Box<dyn LinearMemory + Send + Sync + 'static>, MemoryError> {
661        LinearMemory::copy(self)
662    }
663}
664
665#[doc(hidden)]
666/// Default implementation to initialize memory with data
667pub unsafe fn initialize_memory_with_data(
668    memory: &VMMemoryDefinition,
669    start: usize,
670    data: &[u8],
671) -> Result<(), Trap> {
672    let mem_slice = unsafe { std::slice::from_raw_parts_mut(memory.base, memory.current_length) };
673    let end = start + data.len();
674    let to_init = &mut mem_slice[start..end];
675    to_init.copy_from_slice(data);
676
677    Ok(())
678}