Skip to main content

wasmtime/runtime/vm/
memory.rs

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//! │    LinearMemory     ├─────────────┬───────────────┐
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//! * `LinearMemory` - this is the public trait which
56//!   `LocalMemory` delegates to. This is exposed all the
57//!   way through `wasmtime::Config` so embedders can provide arbitrary
58//!   implementations, and Wasmtime's own memories implement it as well.
59//!
60//! * `MmapMemory` - this is an implementation of `LinearMemory` in terms of
61//!   the platform's mmap primitive.
62//!
63//! * `StaticMemory` - this is an implementation of `LinearMemory` for the
64//!   pooling allocator where the base pointer is already allocated and
65//!   contents are managed through `MemoryImageSlot`.
66//!
67//! Other important types for memories are `MemoryImage` and `MemoryImageSlot`
68//! which manage CoW state for memories. Initializing a memory from a
69//! `MemoryImage` is delegated to the `LinearMemory` itself through
70//! `LinearMemory::init_from_image`, and the pooling allocator's
71//! `StaticMemory` owns a `MemoryImageSlot` which is reused across instances.
72
73use crate::prelude::*;
74use crate::runtime::store::StoreResourceLimiter;
75use crate::runtime::vm::MemoryImage;
76use crate::runtime::vm::vmcontext::VMMemoryDefinition;
77#[cfg(feature = "pooling-allocator")]
78use crate::runtime::vm::{MemoryImageSlot, MmapOffset};
79use crate::{Engine, LinearMemory, MemoryCreator, MemoryRequest};
80use alloc::sync::Arc;
81use core::{ops::Range, ptr::NonNull};
82use wasmtime_environ::{MemoryKind, MemoryTunables};
83
84#[cfg(feature = "threads")]
85use wasmtime_environ::Trap;
86
87#[cfg(has_virtual_memory)]
88mod mmap;
89#[cfg(has_virtual_memory)]
90pub use self::mmap::MmapMemory;
91
92mod malloc;
93pub use self::malloc::MallocMemory;
94
95#[cfg(feature = "pooling-allocator")]
96mod static_;
97#[cfg(feature = "pooling-allocator")]
98use self::static_::StaticMemory;
99
100#[cfg(feature = "threads")]
101mod shared_memory;
102#[cfg(feature = "threads")]
103pub use shared_memory::SharedMemory;
104
105#[cfg(not(feature = "threads"))]
106mod shared_memory_disabled;
107#[cfg(not(feature = "threads"))]
108pub use shared_memory_disabled::SharedMemory;
109
110/// Default memory creator used by Wasmtime when no custom [`MemoryCreator`] implementation is
111/// configured.
112///
113/// It chooses [`MmapMemory`] if available, falling back to [`MallocMemory`] otherwise.
114pub struct DefaultMemoryCreator;
115
116// SAFETY: `MmapMemory` and `MallocMemory` own their allocations and we do not hold any references
117// to them. Memory is zeroed automatically for mmap, and during growth in MallocMemory implementation.
118unsafe impl MemoryCreator for DefaultMemoryCreator {
119    fn new_memory(&self, request: &MemoryRequest<'_>) -> Result<Box<dyn LinearMemory>> {
120        let ty = request.wasmtime_memory();
121        let memory_tunables = request.tunables();
122        let minimum = request.minimum();
123        let maximum = request.maximum();
124
125        #[cfg(has_virtual_memory)]
126        if memory_tunables.tunables().signals_based_traps
127            || memory_tunables.guard_size() > 0
128            || memory_tunables.reservation() > 0
129            || memory_tunables.tunables().memory_init_cow
130        {
131            return Ok(
132                try_new::<Box<_>>(MmapMemory::new(ty, memory_tunables, minimum, maximum)?)?
133                    as Box<dyn LinearMemory>,
134            );
135        }
136
137        // MallocMemory does not have a maximum. This path is only possible when no initial
138        // reservation was requested, in which case memory cannot move, or if virtual memory is
139        // unavailable, in which case none of the usual optimizations are possible and this value
140        // can be ignored.
141        let _ = maximum;
142        Ok(
143            try_new::<Box<_>>(MallocMemory::new(ty, memory_tunables, minimum)?)?
144                as Box<dyn LinearMemory>,
145        )
146    }
147}
148
149/// Representation of a runtime wasm linear memory.
150pub enum Memory {
151    Local(LocalMemory),
152    Shared(SharedMemory),
153}
154
155impl Memory {
156    /// Create a new dynamic (movable) memory instance for the specified plan.
157    pub async fn new_dynamic(
158        ty: &wasmtime_environ::Memory,
159        engine: &Engine,
160        creator: &dyn MemoryCreator,
161        memory_image: Option<&Arc<MemoryImage>>,
162        limiter: Option<&mut StoreResourceLimiter<'_>>,
163        kind: MemoryKind,
164    ) -> Result<Self> {
165        let (minimum, maximum) = Self::limit_new(ty, limiter).await?;
166        let tunables = engine.tunables();
167        let memory_tunables = MemoryTunables::new(tunables, kind);
168        let request = MemoryRequest::new(ty, &memory_tunables, minimum, maximum);
169        let allocation = creator.new_memory(&request)?;
170
171        let memory = LocalMemory::new(ty, &memory_tunables, allocation, memory_image)?;
172        Ok(if ty.shared {
173            Memory::Shared(SharedMemory::wrap(engine, ty, memory)?)
174        } else {
175            Memory::Local(memory)
176        })
177    }
178
179    /// Create a new static (immovable) memory instance for the specified plan.
180    ///
181    /// The `memory_image_slot` must have been already instantiated by the pooling allocator.
182    #[cfg(feature = "pooling-allocator")]
183    pub async fn new_static(
184        ty: &wasmtime_environ::Memory,
185        memory_tunables: &MemoryTunables<'_>,
186        base: MmapOffset,
187        base_capacity: usize,
188        memory_image_slot: MemoryImageSlot,
189        limiter: Option<&mut StoreResourceLimiter<'_>>,
190    ) -> Result<Self> {
191        let (minimum, maximum) = Self::limit_new(ty, limiter).await?;
192
193        // Assert the memory image was instantiated.
194        debug_assert!(memory_image_slot.is_dirty());
195        // The pooling allocator has already instantiated the image, so it only needs
196        // initialization if the slot holds no image.
197        let needs_init = !memory_image_slot.has_image();
198        let pooled_memory =
199            StaticMemory::new(base, base_capacity, minimum, maximum, memory_image_slot)?;
200        let allocation = try_new::<Box<_>>(pooled_memory)?;
201
202        // Configure some defaults a bit differently for this memory within the
203        // `LocalMemory` structure created:
204        // - record whether the data segments still need to be copied in;
205        // - regardless of configuration settings this memory can't move its base pointer since it's a fixed allocation.
206        let mut memory = LocalMemory::new(ty, memory_tunables, allocation, None)?;
207        memory.needs_init = needs_init;
208        memory.memory_may_move = false;
209
210        Ok(if ty.shared {
211            // FIXME(#4244): not supported with the pooling allocator (which
212            // `new_static` is always used with), see `MemoryPool::validate` as
213            // well).
214            todo!("using shared memory with the pooling allocator is a work in progress");
215        } else {
216            Memory::Local(memory)
217        })
218    }
219
220    /// Calls the `store`'s limiter to optionally prevent a memory from being allocated.
221    ///
222    /// Returns a tuple of the minimum size, optional maximum size, and log(page
223    /// size) of the memory, all in bytes.
224    pub(crate) async fn limit_new(
225        ty: &wasmtime_environ::Memory,
226        limiter: Option<&mut StoreResourceLimiter<'_>>,
227    ) -> Result<(usize, Option<usize>)> {
228        let page_size = usize::try_from(ty.page_size()).unwrap();
229
230        // This is the absolute possible maximum that the module can try to
231        // allocate, which is our entire address space minus a wasm page. That
232        // shouldn't ever actually work in terms of an allocation because
233        // presumably the kernel wants *something* for itself, but this is used
234        // to pass to the `store`'s limiter for a requested size
235        // to approximate the scale of the request that the wasm module is
236        // making. This is necessary because the limiter works on `usize` bytes
237        // whereas we're working with possibly-overflowing `u64` calculations
238        // here. To actually faithfully represent the byte requests of modules
239        // we'd have to represent things as `u128`, but that's kinda
240        // overkill for this purpose.
241        let absolute_max = 0usize.wrapping_sub(page_size);
242
243        // If the minimum memory size overflows the size of our own address
244        // space, then we can't satisfy this request, but defer the error to
245        // later so the `store` can be informed that an effective oom is
246        // happening.
247        let minimum = ty
248            .minimum_byte_size()
249            .ok()
250            .and_then(|m| usize::try_from(m).ok());
251
252        // The plan stores the maximum size in units of wasm pages, but we
253        // use units of bytes. Unlike for the `minimum` size we silently clamp
254        // the effective maximum size to the limits of what we can track. If the
255        // maximum size exceeds `usize` or `u64` then there's no need to further
256        // keep track of it as some sort of runtime limit will kick in long
257        // before we reach the statically declared maximum size.
258        let maximum = ty
259            .maximum_byte_size()
260            .ok()
261            .and_then(|m| usize::try_from(m).ok());
262
263        // Inform the store's limiter what's about to happen. This will let the
264        // limiter reject anything if necessary, and this also guarantees that
265        // we should call the limiter for all requested memories, even if our
266        // `minimum` calculation overflowed. This means that the `minimum` we're
267        // informing the limiter is lossy and may not be 100% accurate, but for
268        // now the expected uses of limiter means that's ok.
269        if let Some(limiter) = limiter {
270            if !limiter
271                .memory_growing(0, minimum.unwrap_or(absolute_max), maximum)
272                .await?
273            {
274                bail!(
275                    "memory minimum size of {} pages exceeds memory limits",
276                    ty.limits.min
277                );
278            }
279        }
280
281        // At this point we need to actually handle overflows, so bail out with
282        // an error if we made it this far.
283        let minimum = minimum.ok_or_else(|| {
284            format_err!(
285                "memory minimum size of {} pages exceeds memory limits",
286                ty.limits.min
287            )
288        })?;
289
290        if !ty.allow_growth_to(minimum) {
291            bail!(
292                "memory minimum size of {} pages exceeds memory limits",
293                ty.limits.min
294            );
295        }
296
297        Ok((minimum, maximum))
298    }
299
300    /// Returns this memory's page size, in bytes.
301    pub fn page_size(&self) -> u64 {
302        self.ty().page_size()
303    }
304
305    /// Returns the size of this memory, in bytes.
306    pub fn byte_size(&self) -> usize {
307        match self {
308            Memory::Local(mem) => mem.byte_size(),
309            Memory::Shared(mem) => mem.byte_size(),
310        }
311    }
312
313    /// Returns whether or not this memory needs initialization. It
314    /// may not if it already has initial content thanks to a CoW
315    /// mechanism.
316    pub(crate) fn needs_init(&self) -> bool {
317        match self {
318            Memory::Local(mem) => mem.needs_init(),
319            Memory::Shared(mem) => mem.needs_init(),
320        }
321    }
322
323    /// Grow memory by the specified amount of wasm pages.
324    ///
325    /// Returns `None` if memory can't be grown by the specified amount
326    /// of wasm pages. Returns `Some` with the old size of memory, in bytes, on
327    /// successful growth.
328    ///
329    /// # Safety
330    ///
331    /// Resizing the memory can reallocate the memory buffer for dynamic memories.
332    /// An instance's `VMContext` may have pointers to the memory's base and will
333    /// need to be fixed up after growing the memory.
334    ///
335    /// Generally, prefer using `InstanceHandle::memory_grow`, which encapsulates
336    /// this unsafety.
337    ///
338    /// Ensure that the provided Store is not used to get access any Memory
339    /// which lives inside it.
340    pub async unsafe fn grow(
341        &mut self,
342        delta_pages: u64,
343        limiter: Option<&mut StoreResourceLimiter<'_>>,
344    ) -> Result<Option<usize>, Error> {
345        let result = match self {
346            Memory::Local(mem) => mem.grow(delta_pages, limiter).await?,
347            Memory::Shared(mem) => mem.grow(delta_pages)?,
348        };
349        match result {
350            Some((old, _new)) => Ok(Some(old)),
351            None => Ok(None),
352        }
353    }
354
355    /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code.
356    pub fn vmmemory(&self) -> VMMemoryDefinition {
357        match self {
358            Memory::Local(mem) => mem.vmmemory(),
359            // `vmmemory()` is used for writing the `VMMemoryDefinition` of a
360            // memory into its `VMContext`; this should never be possible for a
361            // shared memory because the only `VMMemoryDefinition` for it should
362            // be stored in its own `def` field.
363            Memory::Shared(_) => unreachable!(),
364        }
365    }
366
367    /// Consume the memory, returning its [`MemoryImageSlot`] if any is present.
368    /// The image should only be present for a subset of memories created with
369    /// [`Memory::new_static()`].
370    #[cfg(feature = "pooling-allocator")]
371    pub fn unwrap_static_image(self) -> MemoryImageSlot {
372        match self {
373            Memory::Local(mem) => mem.unwrap_static_image(),
374            Memory::Shared(_) => panic!("expected a local memory"),
375        }
376    }
377
378    /// Is this a shared memory?
379    pub fn is_shared_memory(&self) -> bool {
380        matches!(self, Memory::Shared(_))
381    }
382
383    /// If the [Memory] is a [SharedMemory], unwrap it and return a clone to
384    /// that shared memory.
385    pub fn as_shared_memory(&self) -> Option<&SharedMemory> {
386        match self {
387            Memory::Local(_) => None,
388            Memory::Shared(mem) => Some(mem),
389        }
390    }
391
392    /// Implementation of `memory.atomic.notify` for all memories.
393    #[cfg(feature = "threads")]
394    pub fn atomic_notify(&mut self, addr: u64, count: u32) -> Result<u32, Trap> {
395        match self.as_shared_memory() {
396            Some(m) => m.atomic_notify(addr, count),
397            None => {
398                validate_atomic_addr(&self.vmmemory(), addr, 4, 4)?;
399                Ok(0)
400            }
401        }
402    }
403
404    /// Implementation of `memory.atomic.wait32` for all memories.
405    #[cfg(feature = "threads")]
406    pub fn atomic_wait32(
407        &mut self,
408        addr: u64,
409        expected: u32,
410        timeout: Option<core::time::Duration>,
411    ) -> Result<crate::WaitResult, Trap> {
412        match self.as_shared_memory() {
413            Some(m) => m.atomic_wait32(addr, expected, timeout),
414            None => {
415                validate_atomic_addr(&self.vmmemory(), addr, 4, 4)?;
416                Err(Trap::AtomicWaitNonSharedMemory)
417            }
418        }
419    }
420
421    /// Implementation of `memory.atomic.wait64` for all memories.
422    #[cfg(feature = "threads")]
423    pub fn atomic_wait64(
424        &mut self,
425        addr: u64,
426        expected: u64,
427        timeout: Option<core::time::Duration>,
428    ) -> Result<crate::WaitResult, Trap> {
429        match self.as_shared_memory() {
430            Some(m) => m.atomic_wait64(addr, expected, timeout),
431            None => {
432                validate_atomic_addr(&self.vmmemory(), addr, 8, 8)?;
433                Err(Trap::AtomicWaitNonSharedMemory)
434            }
435        }
436    }
437
438    /// Returns the range of bytes that WebAssembly should be able to address in
439    /// this linear memory. Note that this includes guard pages which wasm can
440    /// hit.
441    pub fn wasm_accessible(&self) -> Range<usize> {
442        match self {
443            Memory::Local(mem) => mem.wasm_accessible(),
444            Memory::Shared(mem) => mem.wasm_accessible(),
445        }
446    }
447
448    fn ty(&self) -> &wasmtime_environ::Memory {
449        match self {
450            Memory::Local(mem) => mem.ty(),
451            Memory::Shared(mem) => mem.ty(),
452        }
453    }
454}
455
456/// An owned allocation of a wasm linear memory.
457///
458/// This might be part of a `Memory` via `Memory::Local` but it might also be
459/// the implementation basis for a `SharedMemory` behind an `RwLock` for
460/// example.
461pub struct LocalMemory {
462    alloc: Box<dyn LinearMemory>,
463    ty: wasmtime_environ::Memory,
464    /// True only if this memory may move its base pointer.
465    memory_may_move: bool,
466    memory_guard_size: usize,
467    memory_reservation: usize,
468
469    /// Whether the data segments of this memory still need to be copied into it.
470    /// The memory might be already initialized from a pooled allocator or by the particular
471    /// allocation's implementation, in which case this is false.
472    needs_init: bool,
473}
474
475impl LocalMemory {
476    pub fn new(
477        ty: &wasmtime_environ::Memory,
478        memory_tunables: &MemoryTunables<'_>,
479        mut alloc: Box<dyn LinearMemory>,
480        memory_image: Option<&Arc<MemoryImage>>,
481    ) -> Result<LocalMemory> {
482        // If a memory image was specified, give the allocation a chance to
483        // initialize itself from it. If it declines then the data segments
484        // are copied in as usual.
485        let initialized = match memory_image {
486            Some(image) => alloc.init_from_image(&crate::MemoryImage::new(image.clone()))?,
487            None => false,
488        };
489        Ok(LocalMemory {
490            ty: *ty,
491            alloc,
492            memory_may_move: ty.memory_may_move(memory_tunables),
493            needs_init: !initialized,
494            memory_guard_size: memory_tunables.guard_size().try_into().unwrap(),
495            memory_reservation: memory_tunables.reservation().try_into().unwrap(),
496        })
497    }
498
499    pub fn ty(&self) -> &wasmtime_environ::Memory {
500        &self.ty
501    }
502
503    /// Grows a memory by `delta_pages`.
504    ///
505    /// This performs the necessary checks on the growth before delegating to
506    /// the underlying `grow_to` implementation.
507    ///
508    /// The `store` is used only for error reporting.
509    pub async fn grow(
510        &mut self,
511        delta_pages: u64,
512        mut limiter: Option<&mut StoreResourceLimiter<'_>>,
513    ) -> Result<Option<(usize, usize)>, Error> {
514        let old_byte_size = self.alloc.byte_size();
515
516        // Wasm spec: when growing by 0 pages, always return the current size.
517        if delta_pages == 0 {
518            return Ok(Some((old_byte_size, old_byte_size)));
519        }
520
521        let page_size = usize::try_from(self.ty().page_size()).unwrap();
522
523        // The largest wasm-page-aligned region of memory is possible to
524        // represent in a `usize`. This will be impossible for the system to
525        // actually allocate.
526        let absolute_max = 0usize.wrapping_sub(page_size);
527
528        // Calculate the byte size of the new allocation. Let it overflow up to
529        // `usize::MAX`, then clamp it down to `absolute_max`.
530        let new_byte_size = usize::try_from(delta_pages)
531            .unwrap_or(usize::MAX)
532            .saturating_mul(page_size)
533            .saturating_add(old_byte_size)
534            .min(absolute_max);
535
536        let maximum = self
537            .ty
538            .maximum_byte_size()
539            .ok()
540            .and_then(|n| usize::try_from(n).ok());
541
542        // Disallow growth to a value which this linear memory's type
543        // cannot represent. For example 1-byte-page memories cannot be
544        // 4GiB in size.
545        if !self.ty().allow_growth_to(new_byte_size) {
546            if let Some(limiter) = limiter {
547                let err = crate::format_err!("memory growth exceeds memory type's limits");
548                limiter.memory_grow_failed(err)?;
549            }
550            return Ok(None);
551        }
552
553        // Store limiter gets first chance to reject memory_growing.
554        if let Some(limiter) = &mut limiter {
555            if !limiter
556                .memory_growing(old_byte_size, new_byte_size, maximum)
557                .await?
558            {
559                return Ok(None);
560            }
561        }
562
563        // Save the original base pointer to assert the invariant that growth up
564        // to the byte capacity never relocates the base pointer.
565        let base_ptr_before = self.alloc.as_ptr();
566        let required_to_not_move_memory = new_byte_size <= self.alloc.byte_capacity();
567
568        let result = (|| -> Result<()> {
569            // Never exceed maximum, even if limiter permitted it.
570            if let Some(max) = maximum {
571                if new_byte_size > max {
572                    bail!("Memory maximum size exceeded");
573                }
574            }
575
576            // If memory isn't allowed to move then don't let growth happen
577            // beyond the initial capacity
578            if !self.memory_may_move && new_byte_size > self.alloc.byte_capacity() {
579                bail!("Memory maximum size exceeded");
580            }
581
582            self.alloc.grow_to(new_byte_size)
583        })();
584
585        match result {
586            Ok(()) => {
587                // On successful growth double-check that the base pointer
588                // didn't move if it shouldn't have.
589                if required_to_not_move_memory {
590                    assert_eq!(
591                        base_ptr_before,
592                        self.alloc.as_ptr(),
593                        "linear memory moved on grow when it must not have"
594                    );
595                }
596
597                Ok(Some((old_byte_size, new_byte_size)))
598            }
599            Err(e) => {
600                // FIXME: shared memories may not have an associated store to
601                // report the growth failure to but the error should not be
602                // dropped
603                // (https://github.com/bytecodealliance/wasmtime/issues/4240).
604                if let Some(limiter) = limiter {
605                    limiter.memory_grow_failed(e)?;
606                }
607                Ok(None)
608            }
609        }
610    }
611
612    /// Return the bounds of the memory (base, byte_size) for consumption by WebAssembly code in a
613    /// repr(C) struct.
614    pub fn vmmemory(&self) -> VMMemoryDefinition {
615        let base = NonNull::new(self.alloc.as_ptr()).expect("linear memory base is non-null");
616        VMMemoryDefinition {
617            base: base.into(),
618            current_length: self.alloc.byte_size().into(),
619        }
620    }
621
622    pub fn byte_size(&self) -> usize {
623        self.alloc.byte_size()
624    }
625
626    pub fn needs_init(&self) -> bool {
627        self.needs_init
628    }
629
630    pub fn wasm_accessible(&self) -> Range<usize> {
631        let base = self.alloc.as_ptr() as usize;
632        // From the base add:
633        //
634        // * max(capacity, reservation) -- all memory is guaranteed to have at
635        //   least `memory_reservation`, but capacity may go beyond that.
636        // * memory_guard_size - wasm is allowed to hit the guard page for
637        //   sigsegv for example.
638        //
639        // and this computes the range that wasm is allowed to load from and
640        // deterministically trap or succeed.
641        let end =
642            base + self.alloc.byte_capacity().max(self.memory_reservation) + self.memory_guard_size;
643        base..end
644    }
645
646    /// Assume the inner allocation is a [`StaticMemory`] created for a pooling allocator and unwrap
647    /// the inner [`MemoryImageSlot`].
648    /// This function panics if this memory was not created from a [`StaticMemory`] allocation.
649    #[cfg(feature = "pooling-allocator")]
650    pub fn unwrap_static_image(self) -> MemoryImageSlot {
651        let alloc: Box<dyn core::any::Any> = self.alloc;
652        alloc
653            .downcast::<StaticMemory>()
654            .unwrap_or_else(|_| panic!("expected a pooling allocator memory"))
655            .into_image_slot()
656    }
657}
658
659/// In the configurations where bounds checks were elided in JIT code (because
660/// we are using static memories with virtual memory guard pages) this manual
661/// check is here so we don't segfault from Rust. For other configurations,
662/// these checks are required anyways.
663#[cfg(feature = "threads")]
664pub fn validate_atomic_addr(
665    def: &VMMemoryDefinition,
666    addr: u64,
667    access_size: u64,
668    access_alignment: u64,
669) -> Result<*mut u8, Trap> {
670    debug_assert!(access_alignment.is_power_of_two());
671    if !(addr % access_alignment == 0) {
672        return Err(Trap::HeapMisaligned);
673    }
674
675    let length = u64::try_from(def.current_length()).unwrap();
676    if !(addr.saturating_add(access_size) <= length) {
677        return Err(Trap::MemoryOutOfBounds);
678    }
679
680    let addr = usize::try_from(addr).unwrap();
681    Ok(def.base.as_ptr().wrapping_add(addr))
682}