Skip to main content

substrate_wasmtime_runtime/
memory.rs

1//! Memory management for linear memories.
2//!
3//! `RuntimeLinearMemory` is to WebAssembly linear memories what `Table` is to WebAssembly tables.
4
5use crate::mmap::Mmap;
6use crate::vmcontext::VMMemoryDefinition;
7use more_asserts::{assert_ge, assert_le};
8use std::cell::RefCell;
9use std::convert::TryFrom;
10use wasmtime_environ::{MemoryPlan, MemoryStyle, WASM_MAX_PAGES, WASM_PAGE_SIZE};
11
12/// A memory allocator
13pub trait RuntimeMemoryCreator: Send + Sync {
14    /// Create new RuntimeLinearMemory
15    fn new_memory(&self, plan: &MemoryPlan) -> Result<Box<dyn RuntimeLinearMemory>, String>;
16}
17
18/// A default memory allocator used by Wasmtime
19pub struct DefaultMemoryCreator;
20
21impl RuntimeMemoryCreator for DefaultMemoryCreator {
22    /// Create new MmapMemory
23    fn new_memory(&self, plan: &MemoryPlan) -> Result<Box<dyn RuntimeLinearMemory>, String> {
24        Ok(Box::new(MmapMemory::new(plan)?) as Box<dyn RuntimeLinearMemory>)
25    }
26}
27
28/// A linear memory
29pub trait RuntimeLinearMemory {
30    /// Returns the number of allocated wasm pages.
31    fn size(&self) -> u32;
32
33    /// Grow memory by the specified amount of wasm pages.
34    ///
35    /// Returns `None` if memory can't be grown by the specified amount
36    /// of wasm pages.
37    fn grow(&self, delta: u32) -> Option<u32>;
38
39    /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code.
40    fn vmmemory(&self) -> VMMemoryDefinition;
41}
42
43/// A linear memory instance.
44#[derive(Debug)]
45pub struct MmapMemory {
46    // The underlying allocation.
47    mmap: RefCell<WasmMmap>,
48
49    // The optional maximum size in wasm pages of this linear memory.
50    maximum: Option<u32>,
51
52    // Size in bytes of extra guard pages after the end to optimize loads and stores with
53    // constant offsets.
54    offset_guard_size: usize,
55
56    // Records whether we're using a bounds-checking strategy which requires
57    // handlers to catch trapping accesses.
58    pub(crate) needs_signal_handlers: bool,
59}
60
61#[derive(Debug)]
62struct WasmMmap {
63    // Our OS allocation of mmap'd memory.
64    alloc: Mmap,
65    // The current logical size in wasm pages of this linear memory.
66    size: u32,
67}
68
69impl MmapMemory {
70    /// Create a new linear memory instance with specified minimum and maximum number of wasm pages.
71    pub fn new(plan: &MemoryPlan) -> Result<Self, String> {
72        // `maximum` cannot be set to more than `65536` pages.
73        assert_le!(plan.memory.minimum, WASM_MAX_PAGES);
74        assert!(plan.memory.maximum.is_none() || plan.memory.maximum.unwrap() <= WASM_MAX_PAGES);
75
76        let offset_guard_bytes = plan.offset_guard_size as usize;
77
78        // If we have an offset guard, or if we're doing the static memory
79        // allocation strategy, we need signal handlers to catch out of bounds
80        // acceses.
81        let needs_signal_handlers = offset_guard_bytes > 0
82            || match plan.style {
83                MemoryStyle::Dynamic => false,
84                MemoryStyle::Static { .. } => true,
85            };
86
87        let minimum_pages = match plan.style {
88            MemoryStyle::Dynamic => plan.memory.minimum,
89            MemoryStyle::Static { bound } => {
90                assert_ge!(bound, plan.memory.minimum);
91                bound
92            }
93        } as usize;
94        let minimum_bytes = minimum_pages.checked_mul(WASM_PAGE_SIZE as usize).unwrap();
95        let request_bytes = minimum_bytes.checked_add(offset_guard_bytes).unwrap();
96        let mapped_pages = plan.memory.minimum as usize;
97        let mapped_bytes = mapped_pages * WASM_PAGE_SIZE as usize;
98
99        let mmap = WasmMmap {
100            alloc: Mmap::accessible_reserved(mapped_bytes, request_bytes)?,
101            size: plan.memory.minimum,
102        };
103
104        Ok(Self {
105            mmap: mmap.into(),
106            maximum: plan.memory.maximum,
107            offset_guard_size: offset_guard_bytes,
108            needs_signal_handlers,
109        })
110    }
111}
112
113impl RuntimeLinearMemory for MmapMemory {
114    /// Returns the number of allocated wasm pages.
115    fn size(&self) -> u32 {
116        self.mmap.borrow().size
117    }
118
119    /// Grow memory by the specified amount of wasm pages.
120    ///
121    /// Returns `None` if memory can't be grown by the specified amount
122    /// of wasm pages.
123    fn grow(&self, delta: u32) -> Option<u32> {
124        // Optimization of memory.grow 0 calls.
125        let mut mmap = self.mmap.borrow_mut();
126        if delta == 0 {
127            return Some(mmap.size);
128        }
129
130        let new_pages = match mmap.size.checked_add(delta) {
131            Some(new_pages) => new_pages,
132            // Linear memory size overflow.
133            None => return None,
134        };
135        let prev_pages = mmap.size;
136
137        if let Some(maximum) = self.maximum {
138            if new_pages > maximum {
139                // Linear memory size would exceed the declared maximum.
140                return None;
141            }
142        }
143
144        // Wasm linear memories are never allowed to grow beyond what is
145        // indexable. If the memory has no maximum, enforce the greatest
146        // limit here.
147        if new_pages >= WASM_MAX_PAGES {
148            // Linear memory size would exceed the index range.
149            return None;
150        }
151
152        let delta_bytes = usize::try_from(delta).unwrap() * WASM_PAGE_SIZE as usize;
153        let prev_bytes = usize::try_from(prev_pages).unwrap() * WASM_PAGE_SIZE as usize;
154        let new_bytes = usize::try_from(new_pages).unwrap() * WASM_PAGE_SIZE as usize;
155
156        if new_bytes > mmap.alloc.len() - self.offset_guard_size {
157            // If the new size is within the declared maximum, but needs more memory than we
158            // have on hand, it's a dynamic heap and it can move.
159            let guard_bytes = self.offset_guard_size;
160            let request_bytes = new_bytes.checked_add(guard_bytes)?;
161
162            let mut new_mmap = Mmap::accessible_reserved(new_bytes, request_bytes).ok()?;
163
164            let copy_len = mmap.alloc.len() - self.offset_guard_size;
165            new_mmap.as_mut_slice()[..copy_len].copy_from_slice(&mmap.alloc.as_slice()[..copy_len]);
166
167            mmap.alloc = new_mmap;
168        } else if delta_bytes > 0 {
169            // Make the newly allocated pages accessible.
170            mmap.alloc.make_accessible(prev_bytes, delta_bytes).ok()?;
171        }
172
173        mmap.size = new_pages;
174
175        Some(prev_pages)
176    }
177
178    /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code.
179    fn vmmemory(&self) -> VMMemoryDefinition {
180        let mut mmap = self.mmap.borrow_mut();
181        VMMemoryDefinition {
182            base: mmap.alloc.as_mut_ptr(),
183            current_length: mmap.size as usize * WASM_PAGE_SIZE as usize,
184        }
185    }
186}