1 //! Memory management for linear memories.
2 //!
3 //! This module implements the runtime data structures that manage linear
4 //! memories for WebAssembly. There's a number of types here each with various
5 //! purposes, and this is the high level relationships between types where an
6 //! arrow here means "builds on top of".
7 //!
8 //! ```text
9 //! ┌─────────────────────┐
10 //! │ │
11 //! │ Memory ├─────────────┐
12 //! │ │ │
13 //! └──────────┬──────────┘ │
14 //! │ │
15 //! │ │
16 //! ▼ ▼
17 //! ┌─────────────────────┐ ┌──────────────┐
18 //! │ │ │ │
19 //! │ LocalMemory │◄────┤ SharedMemory │
20 //! │ │ │ │
21 //! └──────────┬──────────┘ └──────────────┘
22 //! │
23 //! │
24 //! ▼
25 //! ┌─────────────────────┐
26 //! │ │
27 //! │ RuntimeLinearMemory ├─────────────┬───────────────┐
28 //! │ │ │ │
29 //! └──────────┬──────────┘ │ │
30 //! │ │ │
31 //! │ │ │
32 //! ▼ ▼ ▼
33 //! ┌─────────────────────┐ ┌──────────────┐ ┌─────┐
34 //! │ │ │ │ │ │
35 //! │ MmapMemory │ │ StaticMemory │ │ ... │
36 //! │ │ │ │ │ │
37 //! └─────────────────────┘ └──────────────┘ └─────┘
38 //! ```
39 //!
40 //! In more detail:
41 //!
42 //! * `Memory` - the root of what's actually stored in a wasm instance. This
43 //! implements the high-level embedder APIs one would expect from a wasm
44 //! linear memory.
45 //!
46 //! * `SharedMemory` - this is one of the variants of a local memory. A shared
47 //! memory contains `RwLock<LocalMemory>` where all the real bits happen
48 //! within the lock.
49 //!
50 //! * `LocalMemory` - this is an owned allocation of a linear memory which
51 //! maintains low-level state that's shared between `SharedMemory` and the
52 //! instance-local state of `Memory`. One example is that `LocalMemory::grow`
53 //! has most of the logic around memory growth.
54 //!
55 //! * `RuntimeLinearMemory` - this is a trait which `LocalMemory` delegates to.
56 //! This trait is intentionally relatively simple to be exposed in Wasmtime's
57 //! embedder API. This is exposed all the way through `wasmtime::Config` so
58 //! embedders can provide arbitrary implementations.
59 //!
60 //! * `MmapMemory` - this is an implementation of `RuntimeLinearMemory` in terms
61 //! of the platform's mmap primitive.
62 //!
63 //! * `StaticMemory` - this is an implementation of `RuntimeLinearMemory`
64 //! for the pooling allocator where the base pointer is already allocated
65 //! and contents are managed through `MemoryImageSlot`.
66 //!
67 //! Other important types for memories are `MemoryImage` and `MemoryImageSlot`
68 //! which manage CoW state for memories. This is implemented at the
69 //! `LocalMemory` layer.
70 //!
71 //! FIXME: don't have both RuntimeLinearMemory and wasmtime::LinearMemory, they
72 //! should be merged together.
73 //!
74 //! FIXME: don't have both RuntimeMemoryCreator and wasmtime::MemoryCreator,
75 //! they should be merged together.
76
77 use crate::Engine;
78 use crate::prelude::*;
79 use crate::runtime::store::StoreResourceLimiter;
80 use crate::runtime::vm::vmcontext::VMMemoryDefinition;
81 #[cfg(has_virtual_memory)]
82 use crate::runtime::vm::{HostAlignedByteCount, MmapOffset};
83 use crate::runtime::vm::{MemoryImage, MemoryImageSlot, SendSyncPtr};
84 use alloc::sync::Arc;
85 use core::{ops::Range, ptr::NonNull};
86 use wasmtime_environ::Tunables;
87
88 #[cfg(feature = "threads")]
89 use wasmtime_environ::Trap;
90
91 #[cfg(has_virtual_memory)]
92 mod mmap;
93 #[cfg(has_virtual_memory)]
94 pub use self::mmap::MmapMemory;
95
96 mod malloc;
97 pub use self::malloc::MallocMemory;
98
99 #[cfg(feature = "pooling-allocator")]
100 mod static_;
101 #[cfg(feature = "pooling-allocator")]
102 use self::static_::StaticMemory;
103
104 #[cfg(feature = "threads")]
105 mod shared_memory;
106 #[cfg(feature = "threads")]
107 pub use shared_memory::SharedMemory;
108
109 #[cfg(not(feature = "threads"))]
110 mod shared_memory_disabled;
111 #[cfg(not(feature = "threads"))]
112 pub use shared_memory_disabled::SharedMemory;
113
114 /// A memory allocator
115 pub trait RuntimeMemoryCreator: Send + Sync {
116 /// Create new RuntimeLinearMemory
new_memory( &self, ty: &wasmtime_environ::Memory, tunables: &Tunables, minimum: usize, maximum: Option<usize>, ) -> Result<Box<dyn RuntimeLinearMemory>>117 fn new_memory(
118 &self,
119 ty: &wasmtime_environ::Memory,
120 tunables: &Tunables,
121 minimum: usize,
122 maximum: Option<usize>,
123 ) -> Result<Box<dyn RuntimeLinearMemory>>;
124 }
125
126 /// A default memory allocator used by Wasmtime
127 pub struct DefaultMemoryCreator;
128
129 impl RuntimeMemoryCreator for DefaultMemoryCreator {
130 /// Create new MmapMemory
new_memory( &self, ty: &wasmtime_environ::Memory, tunables: &Tunables, minimum: usize, maximum: Option<usize>, ) -> Result<Box<dyn RuntimeLinearMemory>>131 fn new_memory(
132 &self,
133 ty: &wasmtime_environ::Memory,
134 tunables: &Tunables,
135 minimum: usize,
136 maximum: Option<usize>,
137 ) -> Result<Box<dyn RuntimeLinearMemory>> {
138 #[cfg(has_virtual_memory)]
139 if tunables.signals_based_traps
140 || tunables.memory_guard_size > 0
141 || tunables.memory_reservation > 0
142 || tunables.memory_init_cow
143 {
144 return Ok(
145 try_new::<Box<_>>(MmapMemory::new(ty, tunables, minimum, maximum)?)?
146 as Box<dyn RuntimeLinearMemory>,
147 );
148 }
149
150 let _ = maximum;
151 Ok(
152 try_new::<Box<_>>(MallocMemory::new(ty, tunables, minimum)?)?
153 as Box<dyn RuntimeLinearMemory>,
154 )
155 }
156 }
157
158 /// A linear memory and its backing storage.
159 pub trait RuntimeLinearMemory: Send + Sync {
160 /// Returns the number bytes that this linear memory can access.
byte_size(&self) -> usize161 fn byte_size(&self) -> usize;
162
163 /// Returns the maximal number of bytes the current allocation can access.
164 ///
165 /// Growth up to this value should not relocate the base pointer.
byte_capacity(&self) -> usize166 fn byte_capacity(&self) -> usize;
167
168 /// Grow memory to the specified amount of bytes.
169 ///
170 /// Returns an error if memory can't be grown by the specified amount
171 /// of bytes.
grow_to(&mut self, size: usize) -> Result<()>172 fn grow_to(&mut self, size: usize) -> Result<()>;
173
174 /// Returns a pointer to the base of this linear memory allocation.
175 ///
176 /// This is either a raw pointer, or a reference to an mmap along with an
177 /// offset within it.
base(&self) -> MemoryBase178 fn base(&self) -> MemoryBase;
179
180 /// Get a `VMMemoryDefinition` for this linear memory.
vmmemory(&self) -> VMMemoryDefinition181 fn vmmemory(&self) -> VMMemoryDefinition;
182
183 /// Internal method for Wasmtime when used in conjunction with CoW images.
184 /// This is used to inform the underlying memory that the size of memory has
185 /// changed.
186 ///
187 /// Note that this is hidden and panics by default as embedders using custom
188 /// memory without CoW images shouldn't have to worry about this.
189 #[doc(hidden)]
set_byte_size(&mut self, len: usize)190 fn set_byte_size(&mut self, len: usize) {
191 let _ = len;
192 panic!("CoW images used with this memory and it doesn't support it");
193 }
194 }
195
196 /// The base pointer of a memory allocation.
197 #[derive(Clone, Debug)]
198 pub enum MemoryBase {
199 /// A raw pointer into memory.
200 ///
201 /// This may or may not be host-page-aligned.
202 Raw(SendSyncPtr<u8>),
203
204 /// An mmap along with an offset into it.
205 #[cfg(has_virtual_memory)]
206 Mmap(MmapOffset),
207 }
208
209 impl MemoryBase {
210 /// Creates a new `MemoryBase` from a raw pointer.
211 ///
212 /// The pointer must be non-null, and it must be logically `Send + Sync`.
new_raw(ptr: *mut u8) -> Self213 pub fn new_raw(ptr: *mut u8) -> Self {
214 Self::Raw(NonNull::new(ptr).expect("pointer is non-null").into())
215 }
216
217 /// Returns the actual memory address in memory that is represented by this
218 /// base.
as_non_null(&self) -> NonNull<u8>219 pub fn as_non_null(&self) -> NonNull<u8> {
220 match self {
221 Self::Raw(ptr) => ptr.as_non_null(),
222 #[cfg(has_virtual_memory)]
223 Self::Mmap(mmap_offset) => mmap_offset.as_non_null(),
224 }
225 }
226
227 /// Same as `as_non_null`, but different return type.
as_mut_ptr(&self) -> *mut u8228 pub fn as_mut_ptr(&self) -> *mut u8 {
229 self.as_non_null().as_ptr()
230 }
231 }
232
233 /// Representation of a runtime wasm linear memory.
234 pub enum Memory {
235 Local(LocalMemory),
236 Shared(SharedMemory),
237 }
238
239 impl Memory {
240 /// Create a new dynamic (movable) memory instance for the specified plan.
new_dynamic( ty: &wasmtime_environ::Memory, engine: &Engine, creator: &dyn RuntimeMemoryCreator, memory_image: Option<&Arc<MemoryImage>>, limiter: Option<&mut StoreResourceLimiter<'_>>, ) -> Result<Self>241 pub async fn new_dynamic(
242 ty: &wasmtime_environ::Memory,
243 engine: &Engine,
244 creator: &dyn RuntimeMemoryCreator,
245 memory_image: Option<&Arc<MemoryImage>>,
246 limiter: Option<&mut StoreResourceLimiter<'_>>,
247 ) -> Result<Self> {
248 let (minimum, maximum) = Self::limit_new(ty, limiter).await?;
249 let tunables = engine.tunables();
250 let allocation = creator.new_memory(ty, tunables, minimum, maximum)?;
251
252 let memory = LocalMemory::new(ty, tunables, allocation, memory_image)?;
253 Ok(if ty.shared {
254 Memory::Shared(SharedMemory::wrap(engine, ty, memory)?)
255 } else {
256 Memory::Local(memory)
257 })
258 }
259
260 /// Create a new static (immovable) memory instance for the specified plan.
261 #[cfg(feature = "pooling-allocator")]
new_static( ty: &wasmtime_environ::Memory, tunables: &Tunables, base: MemoryBase, base_capacity: usize, memory_image: MemoryImageSlot, limiter: Option<&mut StoreResourceLimiter<'_>>, ) -> Result<Self>262 pub async fn new_static(
263 ty: &wasmtime_environ::Memory,
264 tunables: &Tunables,
265 base: MemoryBase,
266 base_capacity: usize,
267 memory_image: MemoryImageSlot,
268 limiter: Option<&mut StoreResourceLimiter<'_>>,
269 ) -> Result<Self> {
270 let (minimum, maximum) = Self::limit_new(ty, limiter).await?;
271 let pooled_memory = StaticMemory::new(base, base_capacity, minimum, maximum)?;
272 let allocation = Box::new(pooled_memory);
273
274 // Configure some defaults a bit differently for this memory within the
275 // `LocalMemory` structure created, notably we already have
276 // `memory_image` and regardless of configuration settings this memory
277 // can't move its base pointer since it's a fixed allocation.
278 let mut memory = LocalMemory::new(ty, tunables, allocation, None)?;
279 assert!(memory.memory_image.is_none());
280 memory.memory_image = Some(memory_image);
281 memory.memory_may_move = false;
282
283 Ok(if ty.shared {
284 // FIXME(#4244): not supported with the pooling allocator (which
285 // `new_static` is always used with), see `MemoryPool::validate` as
286 // well).
287 todo!("using shared memory with the pooling allocator is a work in progress");
288 } else {
289 Memory::Local(memory)
290 })
291 }
292
293 /// Calls the `store`'s limiter to optionally prevent a memory from being allocated.
294 ///
295 /// Returns a tuple of the minimum size, optional maximum size, and log(page
296 /// size) of the memory, all in bytes.
limit_new( ty: &wasmtime_environ::Memory, limiter: Option<&mut StoreResourceLimiter<'_>>, ) -> Result<(usize, Option<usize>)>297 pub(crate) async fn limit_new(
298 ty: &wasmtime_environ::Memory,
299 limiter: Option<&mut StoreResourceLimiter<'_>>,
300 ) -> Result<(usize, Option<usize>)> {
301 let page_size = usize::try_from(ty.page_size()).unwrap();
302
303 // This is the absolute possible maximum that the module can try to
304 // allocate, which is our entire address space minus a wasm page. That
305 // shouldn't ever actually work in terms of an allocation because
306 // presumably the kernel wants *something* for itself, but this is used
307 // to pass to the `store`'s limiter for a requested size
308 // to approximate the scale of the request that the wasm module is
309 // making. This is necessary because the limiter works on `usize` bytes
310 // whereas we're working with possibly-overflowing `u64` calculations
311 // here. To actually faithfully represent the byte requests of modules
312 // we'd have to represent things as `u128`, but that's kinda
313 // overkill for this purpose.
314 let absolute_max = 0usize.wrapping_sub(page_size);
315
316 // If the minimum memory size overflows the size of our own address
317 // space, then we can't satisfy this request, but defer the error to
318 // later so the `store` can be informed that an effective oom is
319 // happening.
320 let minimum = ty
321 .minimum_byte_size()
322 .ok()
323 .and_then(|m| usize::try_from(m).ok());
324
325 // The plan stores the maximum size in units of wasm pages, but we
326 // use units of bytes. Unlike for the `minimum` size we silently clamp
327 // the effective maximum size to the limits of what we can track. If the
328 // maximum size exceeds `usize` or `u64` then there's no need to further
329 // keep track of it as some sort of runtime limit will kick in long
330 // before we reach the statically declared maximum size.
331 let maximum = ty
332 .maximum_byte_size()
333 .ok()
334 .and_then(|m| usize::try_from(m).ok());
335
336 // Inform the store's limiter what's about to happen. This will let the
337 // limiter reject anything if necessary, and this also guarantees that
338 // we should call the limiter for all requested memories, even if our
339 // `minimum` calculation overflowed. This means that the `minimum` we're
340 // informing the limiter is lossy and may not be 100% accurate, but for
341 // now the expected uses of limiter means that's ok.
342 if let Some(limiter) = limiter {
343 if !limiter
344 .memory_growing(0, minimum.unwrap_or(absolute_max), maximum)
345 .await?
346 {
347 bail!(
348 "memory minimum size of {} pages exceeds memory limits",
349 ty.limits.min
350 );
351 }
352 }
353
354 // At this point we need to actually handle overflows, so bail out with
355 // an error if we made it this far.
356 let minimum = minimum.ok_or_else(|| {
357 format_err!(
358 "memory minimum size of {} pages exceeds memory limits",
359 ty.limits.min
360 )
361 })?;
362
363 if !ty.allow_growth_to(minimum) {
364 bail!(
365 "memory minimum size of {} pages exceeds memory limits",
366 ty.limits.min
367 );
368 }
369
370 Ok((minimum, maximum))
371 }
372
373 /// Returns this memory's page size, in bytes.
page_size(&self) -> u64374 pub fn page_size(&self) -> u64 {
375 self.ty().page_size()
376 }
377
378 /// Returns the size of this memory, in bytes.
byte_size(&self) -> usize379 pub fn byte_size(&self) -> usize {
380 match self {
381 Memory::Local(mem) => mem.byte_size(),
382 Memory::Shared(mem) => mem.byte_size(),
383 }
384 }
385
386 /// Returns whether or not this memory needs initialization. It
387 /// may not if it already has initial content thanks to a CoW
388 /// mechanism.
needs_init(&self) -> bool389 pub(crate) fn needs_init(&self) -> bool {
390 match self {
391 Memory::Local(mem) => mem.needs_init(),
392 Memory::Shared(mem) => mem.needs_init(),
393 }
394 }
395
396 /// Grow memory by the specified amount of wasm pages.
397 ///
398 /// Returns `None` if memory can't be grown by the specified amount
399 /// of wasm pages. Returns `Some` with the old size of memory, in bytes, on
400 /// successful growth.
401 ///
402 /// # Safety
403 ///
404 /// Resizing the memory can reallocate the memory buffer for dynamic memories.
405 /// An instance's `VMContext` may have pointers to the memory's base and will
406 /// need to be fixed up after growing the memory.
407 ///
408 /// Generally, prefer using `InstanceHandle::memory_grow`, which encapsulates
409 /// this unsafety.
410 ///
411 /// Ensure that the provided Store is not used to get access any Memory
412 /// which lives inside it.
grow( &mut self, delta_pages: u64, limiter: Option<&mut StoreResourceLimiter<'_>>, ) -> Result<Option<usize>, Error>413 pub async unsafe fn grow(
414 &mut self,
415 delta_pages: u64,
416 limiter: Option<&mut StoreResourceLimiter<'_>>,
417 ) -> Result<Option<usize>, Error> {
418 let new_size = delta_pages
419 .checked_mul(self.page_size())
420 .and_then(|new_bytes| {
421 let new_bytes = usize::try_from(new_bytes).ok()?;
422 self.byte_size().checked_add(new_bytes)
423 });
424 match new_size {
425 Some(new_size) => {
426 // FIXME(WebAssembly/custom-page-sizes#45) - what should the
427 // behavior here be exactly? A trap? Return -1? A smaller limit?
428 // Unclear!
429 //
430 // Trap for now to make this as noisy/conservative as possible.
431 if !self.ty().allow_growth_to(new_size) {
432 bail!(
433 "disallowing growth to {new_size:#x} bytes based on \
434 page size"
435 )
436 }
437 }
438
439 // If the new size in memory isn't representable in a `usize` then
440 // there's no need to actually try to grow it to that size. It's
441 // impossible to succeed so just fail it early.
442 None => {
443 if let Some(limiter) = limiter {
444 let err = crate::format_err!("memory growth exceeds address space");
445 limiter.memory_grow_failed(err)?;
446 }
447 return Ok(None);
448 }
449 }
450
451 let result = match self {
452 Memory::Local(mem) => mem.grow(delta_pages, limiter).await?,
453 Memory::Shared(mem) => mem.grow(delta_pages)?,
454 };
455 match result {
456 Some((old, _new)) => Ok(Some(old)),
457 None => Ok(None),
458 }
459 }
460
461 /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code.
vmmemory(&self) -> VMMemoryDefinition462 pub fn vmmemory(&self) -> VMMemoryDefinition {
463 match self {
464 Memory::Local(mem) => mem.vmmemory(),
465 // `vmmemory()` is used for writing the `VMMemoryDefinition` of a
466 // memory into its `VMContext`; this should never be possible for a
467 // shared memory because the only `VMMemoryDefinition` for it should
468 // be stored in its own `def` field.
469 Memory::Shared(_) => unreachable!(),
470 }
471 }
472
473 /// Consume the memory, returning its [`MemoryImageSlot`] if any is present.
474 /// The image should only be present for a subset of memories created with
475 /// [`Memory::new_static()`].
476 #[cfg(feature = "pooling-allocator")]
unwrap_static_image(self) -> MemoryImageSlot477 pub fn unwrap_static_image(self) -> MemoryImageSlot {
478 match self {
479 Memory::Local(mem) => mem.unwrap_static_image(),
480 Memory::Shared(_) => panic!("expected a local memory"),
481 }
482 }
483
484 /// Is this a shared memory?
is_shared_memory(&self) -> bool485 pub fn is_shared_memory(&self) -> bool {
486 matches!(self, Memory::Shared(_))
487 }
488
489 /// If the [Memory] is a [SharedMemory], unwrap it and return a clone to
490 /// that shared memory.
as_shared_memory(&self) -> Option<&SharedMemory>491 pub fn as_shared_memory(&self) -> Option<&SharedMemory> {
492 match self {
493 Memory::Local(_) => None,
494 Memory::Shared(mem) => Some(mem),
495 }
496 }
497
498 /// Implementation of `memory.atomic.notify` for all memories.
499 #[cfg(feature = "threads")]
atomic_notify(&mut self, addr: u64, count: u32) -> Result<u32, Trap>500 pub fn atomic_notify(&mut self, addr: u64, count: u32) -> Result<u32, Trap> {
501 match self.as_shared_memory() {
502 Some(m) => m.atomic_notify(addr, count),
503 None => {
504 validate_atomic_addr(&self.vmmemory(), addr, 4, 4)?;
505 Ok(0)
506 }
507 }
508 }
509
510 /// Implementation of `memory.atomic.wait32` for all memories.
511 #[cfg(feature = "threads")]
atomic_wait32( &mut self, addr: u64, expected: u32, timeout: Option<core::time::Duration>, ) -> Result<crate::WaitResult, Trap>512 pub fn atomic_wait32(
513 &mut self,
514 addr: u64,
515 expected: u32,
516 timeout: Option<core::time::Duration>,
517 ) -> Result<crate::WaitResult, Trap> {
518 match self.as_shared_memory() {
519 Some(m) => m.atomic_wait32(addr, expected, timeout),
520 None => {
521 validate_atomic_addr(&self.vmmemory(), addr, 4, 4)?;
522 Err(Trap::AtomicWaitNonSharedMemory)
523 }
524 }
525 }
526
527 /// Implementation of `memory.atomic.wait64` for all memories.
528 #[cfg(feature = "threads")]
atomic_wait64( &mut self, addr: u64, expected: u64, timeout: Option<core::time::Duration>, ) -> Result<crate::WaitResult, Trap>529 pub fn atomic_wait64(
530 &mut self,
531 addr: u64,
532 expected: u64,
533 timeout: Option<core::time::Duration>,
534 ) -> Result<crate::WaitResult, Trap> {
535 match self.as_shared_memory() {
536 Some(m) => m.atomic_wait64(addr, expected, timeout),
537 None => {
538 validate_atomic_addr(&self.vmmemory(), addr, 8, 8)?;
539 Err(Trap::AtomicWaitNonSharedMemory)
540 }
541 }
542 }
543
544 /// Returns the range of bytes that WebAssembly should be able to address in
545 /// this linear memory. Note that this includes guard pages which wasm can
546 /// hit.
wasm_accessible(&self) -> Range<usize>547 pub fn wasm_accessible(&self) -> Range<usize> {
548 match self {
549 Memory::Local(mem) => mem.wasm_accessible(),
550 Memory::Shared(mem) => mem.wasm_accessible(),
551 }
552 }
553
ty(&self) -> &wasmtime_environ::Memory554 fn ty(&self) -> &wasmtime_environ::Memory {
555 match self {
556 Memory::Local(mem) => mem.ty(),
557 Memory::Shared(mem) => mem.ty(),
558 }
559 }
560 }
561
562 /// An owned allocation of a wasm linear memory.
563 ///
564 /// This might be part of a `Memory` via `Memory::Local` but it might also be
565 /// the implementation basis for a `SharedMemory` behind an `RwLock` for
566 /// example.
567 pub struct LocalMemory {
568 alloc: Box<dyn RuntimeLinearMemory>,
569 ty: wasmtime_environ::Memory,
570 memory_may_move: bool,
571 memory_guard_size: usize,
572 memory_reservation: usize,
573
574 /// An optional CoW mapping that provides the initial content of this
575 /// memory.
576 memory_image: Option<MemoryImageSlot>,
577 }
578
579 impl LocalMemory {
new( ty: &wasmtime_environ::Memory, tunables: &Tunables, alloc: Box<dyn RuntimeLinearMemory>, memory_image: Option<&Arc<MemoryImage>>, ) -> Result<LocalMemory>580 pub fn new(
581 ty: &wasmtime_environ::Memory,
582 tunables: &Tunables,
583 alloc: Box<dyn RuntimeLinearMemory>,
584 memory_image: Option<&Arc<MemoryImage>>,
585 ) -> Result<LocalMemory> {
586 // If a memory image was specified, try to create the MemoryImageSlot on
587 // top of our mmap.
588 let memory_image = match memory_image {
589 #[cfg(has_virtual_memory)]
590 Some(image) => {
591 // We currently don't support memory_image if
592 // `RuntimeLinearMemory::byte_size` is not a multiple of the host page
593 // size. See https://github.com/bytecodealliance/wasmtime/issues/9660.
594 if let Ok(byte_size) = HostAlignedByteCount::new(alloc.byte_size()) {
595 // memory_image is CoW-based so it is expected to be backed
596 // by an mmap.
597 let mmap_base = match alloc.base() {
598 MemoryBase::Mmap(offset) => offset,
599 MemoryBase::Raw { .. } => {
600 unreachable!("memory_image is Some only for mmap-based memories")
601 }
602 };
603
604 let mut slot =
605 MemoryImageSlot::create(mmap_base, byte_size, alloc.byte_capacity());
606 slot.instantiate(alloc.byte_size(), Some(image), ty, tunables)?;
607 Some(slot)
608 } else {
609 None
610 }
611 }
612 #[cfg(not(has_virtual_memory))]
613 Some(_) => unreachable!(),
614 None => None,
615 };
616 Ok(LocalMemory {
617 ty: *ty,
618 alloc,
619 memory_may_move: ty.memory_may_move(tunables),
620 memory_image,
621 memory_guard_size: tunables.memory_guard_size.try_into().unwrap(),
622 memory_reservation: tunables.memory_reservation.try_into().unwrap(),
623 })
624 }
625
ty(&self) -> &wasmtime_environ::Memory626 pub fn ty(&self) -> &wasmtime_environ::Memory {
627 &self.ty
628 }
629
630 /// Grows a memory by `delta_pages`.
631 ///
632 /// This performs the necessary checks on the growth before delegating to
633 /// the underlying `grow_to` implementation.
634 ///
635 /// The `store` is used only for error reporting.
grow( &mut self, delta_pages: u64, mut limiter: Option<&mut StoreResourceLimiter<'_>>, ) -> Result<Option<(usize, usize)>, Error>636 pub async fn grow(
637 &mut self,
638 delta_pages: u64,
639 mut limiter: Option<&mut StoreResourceLimiter<'_>>,
640 ) -> Result<Option<(usize, usize)>, Error> {
641 let old_byte_size = self.alloc.byte_size();
642
643 // Wasm spec: when growing by 0 pages, always return the current size.
644 if delta_pages == 0 {
645 return Ok(Some((old_byte_size, old_byte_size)));
646 }
647
648 let page_size = usize::try_from(self.ty().page_size()).unwrap();
649
650 // The largest wasm-page-aligned region of memory is possible to
651 // represent in a `usize`. This will be impossible for the system to
652 // actually allocate.
653 let absolute_max = 0usize.wrapping_sub(page_size);
654
655 // Calculate the byte size of the new allocation. Let it overflow up to
656 // `usize::MAX`, then clamp it down to `absolute_max`.
657 let new_byte_size = usize::try_from(delta_pages)
658 .unwrap_or(usize::MAX)
659 .saturating_mul(page_size)
660 .saturating_add(old_byte_size)
661 .min(absolute_max);
662
663 let maximum = self
664 .ty
665 .maximum_byte_size()
666 .ok()
667 .and_then(|n| usize::try_from(n).ok());
668
669 // Store limiter gets first chance to reject memory_growing.
670 if let Some(limiter) = &mut limiter {
671 if !limiter
672 .memory_growing(old_byte_size, new_byte_size, maximum)
673 .await?
674 {
675 return Ok(None);
676 }
677 }
678
679 // Save the original base pointer to assert the invariant that growth up
680 // to the byte capacity never relocates the base pointer.
681 let base_ptr_before = self.alloc.base().as_mut_ptr();
682 let required_to_not_move_memory = new_byte_size <= self.alloc.byte_capacity();
683
684 let result = (|| -> Result<()> {
685 // Never exceed maximum, even if limiter permitted it.
686 if let Some(max) = maximum {
687 if new_byte_size > max {
688 bail!("Memory maximum size exceeded");
689 }
690 }
691
692 // If memory isn't allowed to move then don't let growth happen
693 // beyond the initial capacity
694 if !self.memory_may_move && new_byte_size > self.alloc.byte_capacity() {
695 bail!("Memory maximum size exceeded");
696 }
697
698 // If we have a CoW image overlay then let it manage accessible
699 // bytes. Once the heap limit is modified inform the underlying
700 // allocation that the size has changed.
701 //
702 // If the growth is going beyond the size of the heap image then
703 // discard it. This should only happen for `MmapMemory` where
704 // `no_clear_on_drop` is set so the destructor doesn't do anything.
705 // For now be maximally sure about this by asserting that memory can
706 // indeed move and that we're on unix. If this wants to run
707 // somewhere else like Windows or with other allocations this may
708 // need adjusting.
709 if let Some(image) = &mut self.memory_image {
710 if new_byte_size <= self.alloc.byte_capacity() {
711 image.set_heap_limit(new_byte_size)?;
712 self.alloc.set_byte_size(new_byte_size);
713 return Ok(());
714 }
715 assert!(cfg!(unix));
716 assert!(self.memory_may_move);
717 self.memory_image = None;
718 }
719
720 // And failing all that fall back to the underlying allocation to
721 // grow it.
722 self.alloc.grow_to(new_byte_size)
723 })();
724
725 match result {
726 Ok(()) => {
727 // On successful growth double-check that the base pointer
728 // didn't move if it shouldn't have.
729 if required_to_not_move_memory {
730 assert_eq!(base_ptr_before, self.alloc.base().as_mut_ptr());
731 }
732
733 Ok(Some((old_byte_size, new_byte_size)))
734 }
735 Err(e) => {
736 // FIXME: shared memories may not have an associated store to
737 // report the growth failure to but the error should not be
738 // dropped
739 // (https://github.com/bytecodealliance/wasmtime/issues/4240).
740 if let Some(limiter) = limiter {
741 limiter.memory_grow_failed(e)?;
742 }
743 Ok(None)
744 }
745 }
746 }
747
vmmemory(&self) -> VMMemoryDefinition748 pub fn vmmemory(&self) -> VMMemoryDefinition {
749 self.alloc.vmmemory()
750 }
751
byte_size(&self) -> usize752 pub fn byte_size(&self) -> usize {
753 self.alloc.byte_size()
754 }
755
needs_init(&self) -> bool756 pub fn needs_init(&self) -> bool {
757 match &self.memory_image {
758 Some(image) => !image.has_image(),
759 None => true,
760 }
761 }
762
wasm_accessible(&self) -> Range<usize>763 pub fn wasm_accessible(&self) -> Range<usize> {
764 let base = self.alloc.base().as_mut_ptr() as usize;
765 // From the base add:
766 //
767 // * max(capacity, reservation) -- all memory is guaranteed to have at
768 // least `memory_reservation`, but capacity may go beyond that.
769 // * memory_guard_size - wasm is allowed to hit the guard page for
770 // sigsegv for example.
771 //
772 // and this computes the range that wasm is allowed to load from and
773 // deterministically trap or succeed.
774 let end =
775 base + self.alloc.byte_capacity().max(self.memory_reservation) + self.memory_guard_size;
776 base..end
777 }
778
779 #[cfg(feature = "pooling-allocator")]
unwrap_static_image(self) -> MemoryImageSlot780 pub fn unwrap_static_image(self) -> MemoryImageSlot {
781 self.memory_image.unwrap()
782 }
783 }
784
785 /// In the configurations where bounds checks were elided in JIT code (because
786 /// we are using static memories with virtual memory guard pages) this manual
787 /// check is here so we don't segfault from Rust. For other configurations,
788 /// these checks are required anyways.
789 #[cfg(feature = "threads")]
validate_atomic_addr( def: &VMMemoryDefinition, addr: u64, access_size: u64, access_alignment: u64, ) -> Result<*mut u8, Trap>790 pub fn validate_atomic_addr(
791 def: &VMMemoryDefinition,
792 addr: u64,
793 access_size: u64,
794 access_alignment: u64,
795 ) -> Result<*mut u8, Trap> {
796 debug_assert!(access_alignment.is_power_of_two());
797 if !(addr % access_alignment == 0) {
798 return Err(Trap::HeapMisaligned);
799 }
800
801 let length = u64::try_from(def.current_length()).unwrap();
802 if !(addr.saturating_add(access_size) <= length) {
803 return Err(Trap::MemoryOutOfBounds);
804 }
805
806 let addr = usize::try_from(addr).unwrap();
807 Ok(def.base.as_ptr().wrapping_add(addr))
808 }
809