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}