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}