1 //! Memory management for linear memories. 2 //! 3 //! `RuntimeLinearMemory` is to WebAssembly linear memories what `Table` is to WebAssembly tables. 4 5 use crate::runtime::vm::mmap::Mmap; 6 use crate::runtime::vm::vmcontext::VMMemoryDefinition; 7 use crate::runtime::vm::{ 8 MemoryImage, MemoryImageSlot, SendSyncPtr, SharedMemory, Store, WaitResult, 9 }; 10 use anyhow::Error; 11 use anyhow::{bail, format_err, Result}; 12 use std::ops::Range; 13 use std::ptr::NonNull; 14 use std::sync::Arc; 15 use std::time::Instant; 16 use wasmtime_environ::{MemoryPlan, MemoryStyle, Trap, WASM32_MAX_PAGES, WASM64_MAX_PAGES}; 17 18 const WASM_PAGE_SIZE: usize = wasmtime_environ::WASM_PAGE_SIZE as usize; 19 const WASM_PAGE_SIZE_U64: u64 = wasmtime_environ::WASM_PAGE_SIZE as u64; 20 21 /// A memory allocator 22 pub trait RuntimeMemoryCreator: Send + Sync { 23 /// Create new RuntimeLinearMemory 24 fn new_memory( 25 &self, 26 plan: &MemoryPlan, 27 minimum: usize, 28 maximum: Option<usize>, 29 // Optionally, a memory image for CoW backing. 30 memory_image: Option<&Arc<MemoryImage>>, 31 ) -> Result<Box<dyn RuntimeLinearMemory>>; 32 } 33 34 /// A default memory allocator used by Wasmtime 35 pub struct DefaultMemoryCreator; 36 37 impl RuntimeMemoryCreator for DefaultMemoryCreator { 38 /// Create new MmapMemory 39 fn new_memory( 40 &self, 41 plan: &MemoryPlan, 42 minimum: usize, 43 maximum: Option<usize>, 44 memory_image: Option<&Arc<MemoryImage>>, 45 ) -> Result<Box<dyn RuntimeLinearMemory>> { 46 Ok(Box::new(MmapMemory::new( 47 plan, 48 minimum, 49 maximum, 50 memory_image, 51 )?)) 52 } 53 } 54 55 /// A linear memory 56 pub trait RuntimeLinearMemory: Send + Sync { 57 /// Returns the number of allocated bytes. 58 fn byte_size(&self) -> usize; 59 60 /// Returns the maximum number of bytes the memory can grow to. 61 /// Returns `None` if the memory is unbounded. 62 fn maximum_byte_size(&self) -> Option<usize>; 63 64 /// Grows a memory by `delta_pages`. 65 /// 66 /// This performs the necessary checks on the growth before delegating to 67 /// the underlying `grow_to` implementation. A default implementation of 68 /// this memory is provided here since this is assumed to be the same for 69 /// most kinds of memory; one exception is shared memory, which must perform 70 /// all the steps of the default implementation *plus* the required locking. 71 /// 72 /// The `store` is used only for error reporting. 73 fn grow( 74 &mut self, 75 delta_pages: u64, 76 mut store: Option<&mut dyn Store>, 77 ) -> Result<Option<(usize, usize)>, Error> { 78 let old_byte_size = self.byte_size(); 79 80 // Wasm spec: when growing by 0 pages, always return the current size. 81 if delta_pages == 0 { 82 return Ok(Some((old_byte_size, old_byte_size))); 83 } 84 85 // The largest wasm-page-aligned region of memory is possible to 86 // represent in a `usize`. This will be impossible for the system to 87 // actually allocate. 88 let absolute_max = 0usize.wrapping_sub(WASM_PAGE_SIZE); 89 90 // Calculate the byte size of the new allocation. Let it overflow up to 91 // `usize::MAX`, then clamp it down to `absolute_max`. 92 let new_byte_size = usize::try_from(delta_pages) 93 .unwrap_or(usize::MAX) 94 .saturating_mul(WASM_PAGE_SIZE) 95 .saturating_add(old_byte_size); 96 let new_byte_size = if new_byte_size > absolute_max { 97 absolute_max 98 } else { 99 new_byte_size 100 }; 101 102 let maximum = self.maximum_byte_size(); 103 // Store limiter gets first chance to reject memory_growing. 104 if let Some(store) = &mut store { 105 if !store.memory_growing(old_byte_size, new_byte_size, maximum)? { 106 return Ok(None); 107 } 108 } 109 110 // Never exceed maximum, even if limiter permitted it. 111 if let Some(max) = maximum { 112 if new_byte_size > max { 113 if let Some(store) = store { 114 // FIXME: shared memories may not have an associated store 115 // to report the growth failure to but the error should not 116 // be dropped 117 // (https://github.com/bytecodealliance/wasmtime/issues/4240). 118 store.memory_grow_failed(format_err!("Memory maximum size exceeded"))?; 119 } 120 return Ok(None); 121 } 122 } 123 124 match self.grow_to(new_byte_size) { 125 Ok(_) => Ok(Some((old_byte_size, new_byte_size))), 126 Err(e) => { 127 // FIXME: shared memories may not have an associated store to 128 // report the growth failure to but the error should not be 129 // dropped 130 // (https://github.com/bytecodealliance/wasmtime/issues/4240). 131 if let Some(store) = store { 132 store.memory_grow_failed(e)?; 133 } 134 Ok(None) 135 } 136 } 137 } 138 139 /// Grow memory to the specified amount of bytes. 140 /// 141 /// Returns an error if memory can't be grown by the specified amount 142 /// of bytes. 143 fn grow_to(&mut self, size: usize) -> Result<()>; 144 145 /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm 146 /// code. 147 fn vmmemory(&mut self) -> VMMemoryDefinition; 148 149 /// Does this memory need initialization? It may not if it already 150 /// has initial contents courtesy of the `MemoryImage` passed to 151 /// `RuntimeMemoryCreator::new_memory()`. 152 fn needs_init(&self) -> bool; 153 154 /// Used for optional dynamic downcasting. 155 fn as_any_mut(&mut self) -> &mut dyn std::any::Any; 156 157 /// Returns the range of addresses that may be reached by WebAssembly. 158 /// 159 /// This starts at the base of linear memory and ends at the end of the 160 /// guard pages, if any. 161 fn wasm_accessible(&self) -> Range<usize>; 162 } 163 164 /// A linear memory instance. 165 #[derive(Debug)] 166 pub struct MmapMemory { 167 // The underlying allocation. 168 mmap: Mmap, 169 170 // The number of bytes that are accessible in `mmap` and available for 171 // reading and writing. 172 // 173 // This region starts at `pre_guard_size` offset from the base of `mmap`. 174 accessible: usize, 175 176 // The optional maximum accessible size, in bytes, for this linear memory. 177 // 178 // Note that this maximum does not factor in guard pages, so this isn't the 179 // maximum size of the linear address space reservation for this memory. 180 maximum: Option<usize>, 181 182 // The amount of extra bytes to reserve whenever memory grows. This is 183 // specified so that the cost of repeated growth is amortized. 184 extra_to_reserve_on_growth: usize, 185 186 // Size in bytes of extra guard pages before the start and after the end to 187 // optimize loads and stores with constant offsets. 188 pre_guard_size: usize, 189 offset_guard_size: usize, 190 191 // An optional CoW mapping that provides the initial content of this 192 // MmapMemory, if mapped. 193 memory_image: Option<MemoryImageSlot>, 194 } 195 196 impl MmapMemory { 197 /// Create a new linear memory instance with specified minimum and maximum 198 /// number of wasm pages. 199 pub fn new( 200 plan: &MemoryPlan, 201 minimum: usize, 202 mut maximum: Option<usize>, 203 memory_image: Option<&Arc<MemoryImage>>, 204 ) -> Result<Self> { 205 // It's a programmer error for these two configuration values to exceed 206 // the host available address space, so panic if such a configuration is 207 // found (mostly an issue for hypothetical 32-bit hosts). 208 let offset_guard_bytes = usize::try_from(plan.offset_guard_size).unwrap(); 209 let pre_guard_bytes = usize::try_from(plan.pre_guard_size).unwrap(); 210 211 let (alloc_bytes, extra_to_reserve_on_growth) = match plan.style { 212 // Dynamic memories start with the minimum size plus the `reserve` 213 // amount specified to grow into. 214 MemoryStyle::Dynamic { reserve } => (minimum, usize::try_from(reserve).unwrap()), 215 216 // Static memories will never move in memory and consequently get 217 // their entire allocation up-front with no extra room to grow into. 218 // Note that the `maximum` is adjusted here to whatever the smaller 219 // of the two is, the `maximum` given or the `bound` specified for 220 // this memory. 221 MemoryStyle::Static { bound } => { 222 assert!(bound >= plan.memory.minimum); 223 let bound_bytes = 224 usize::try_from(bound.checked_mul(WASM_PAGE_SIZE_U64).unwrap()).unwrap(); 225 maximum = Some(bound_bytes.min(maximum.unwrap_or(usize::MAX))); 226 (bound_bytes, 0) 227 } 228 }; 229 230 let request_bytes = pre_guard_bytes 231 .checked_add(alloc_bytes) 232 .and_then(|i| i.checked_add(extra_to_reserve_on_growth)) 233 .and_then(|i| i.checked_add(offset_guard_bytes)) 234 .ok_or_else(|| format_err!("cannot allocate {} with guard regions", minimum))?; 235 let mut mmap = Mmap::accessible_reserved(0, request_bytes)?; 236 237 if minimum > 0 { 238 mmap.make_accessible(pre_guard_bytes, minimum)?; 239 } 240 241 // If a memory image was specified, try to create the MemoryImageSlot on 242 // top of our mmap. 243 let memory_image = match memory_image { 244 Some(image) => { 245 let base = unsafe { mmap.as_mut_ptr().add(pre_guard_bytes) }; 246 let mut slot = MemoryImageSlot::create( 247 base.cast(), 248 minimum, 249 alloc_bytes + extra_to_reserve_on_growth, 250 ); 251 slot.instantiate(minimum, Some(image), &plan)?; 252 // On drop, we will unmap our mmap'd range that this slot was 253 // mapped on top of, so there is no need for the slot to wipe 254 // it with an anonymous mapping first. 255 slot.no_clear_on_drop(); 256 Some(slot) 257 } 258 None => None, 259 }; 260 261 Ok(Self { 262 mmap, 263 accessible: minimum, 264 maximum, 265 pre_guard_size: pre_guard_bytes, 266 offset_guard_size: offset_guard_bytes, 267 extra_to_reserve_on_growth, 268 memory_image, 269 }) 270 } 271 } 272 273 impl RuntimeLinearMemory for MmapMemory { 274 fn byte_size(&self) -> usize { 275 self.accessible 276 } 277 278 fn maximum_byte_size(&self) -> Option<usize> { 279 self.maximum 280 } 281 282 fn grow_to(&mut self, new_size: usize) -> Result<()> { 283 if new_size > self.mmap.len() - self.offset_guard_size - self.pre_guard_size { 284 // If the new size of this heap exceeds the current size of the 285 // allocation we have, then this must be a dynamic heap. Use 286 // `new_size` to calculate a new size of an allocation, allocate it, 287 // and then copy over the memory from before. 288 let request_bytes = self 289 .pre_guard_size 290 .checked_add(new_size) 291 .and_then(|s| s.checked_add(self.extra_to_reserve_on_growth)) 292 .and_then(|s| s.checked_add(self.offset_guard_size)) 293 .ok_or_else(|| format_err!("overflow calculating size of memory allocation"))?; 294 295 let mut new_mmap = Mmap::accessible_reserved(0, request_bytes)?; 296 new_mmap.make_accessible(self.pre_guard_size, new_size)?; 297 298 // This method has an exclusive reference to `self.mmap` and just 299 // created `new_mmap` so it should be safe to acquire references 300 // into both of them and copy between them. 301 unsafe { 302 let range = self.pre_guard_size..self.pre_guard_size + self.accessible; 303 let src = self.mmap.slice(range.clone()); 304 let dst = new_mmap.slice_mut(range); 305 dst.copy_from_slice(src); 306 } 307 308 // Now drop the MemoryImageSlot, if any. We've lost the CoW 309 // advantages by explicitly copying all data, but we have 310 // preserved all of its content; so we no longer need the 311 // mapping. We need to do this before we (implicitly) drop the 312 // `mmap` field by overwriting it below. 313 drop(self.memory_image.take()); 314 315 self.mmap = new_mmap; 316 } else if let Some(image) = self.memory_image.as_mut() { 317 // MemoryImageSlot has its own growth mechanisms; defer to its 318 // implementation. 319 image.set_heap_limit(new_size)?; 320 } else { 321 // If the new size of this heap fits within the existing allocation 322 // then all we need to do is to make the new pages accessible. This 323 // can happen either for "static" heaps which always hit this case, 324 // or "dynamic" heaps which have some space reserved after the 325 // initial allocation to grow into before the heap is moved in 326 // memory. 327 assert!(new_size > self.accessible); 328 self.mmap.make_accessible( 329 self.pre_guard_size + self.accessible, 330 new_size - self.accessible, 331 )?; 332 } 333 334 self.accessible = new_size; 335 336 Ok(()) 337 } 338 339 fn vmmemory(&mut self) -> VMMemoryDefinition { 340 VMMemoryDefinition { 341 base: unsafe { self.mmap.as_mut_ptr().add(self.pre_guard_size) }, 342 current_length: self.accessible.into(), 343 } 344 } 345 346 fn needs_init(&self) -> bool { 347 // If we're using a CoW mapping, then no initialization 348 // is needed. 349 self.memory_image.is_none() 350 } 351 352 fn as_any_mut(&mut self) -> &mut dyn std::any::Any { 353 self 354 } 355 356 fn wasm_accessible(&self) -> Range<usize> { 357 let base = self.mmap.as_ptr() as usize + self.pre_guard_size; 358 let end = base + (self.mmap.len() - self.pre_guard_size); 359 base..end 360 } 361 } 362 363 /// A "static" memory where the lifetime of the backing memory is managed 364 /// elsewhere. Currently used with the pooling allocator. 365 struct StaticMemory { 366 /// The base pointer of this static memory, wrapped up in a send/sync 367 /// wrapper. 368 base: SendSyncPtr<u8>, 369 370 /// The byte capacity of the `base` pointer. 371 capacity: usize, 372 373 /// The current size, in bytes, of this memory. 374 size: usize, 375 376 /// The size, in bytes, of the virtual address allocation starting at `base` 377 /// and going to the end of the guard pages at the end of the linear memory. 378 memory_and_guard_size: usize, 379 380 /// The image management, if any, for this memory. Owned here and 381 /// returned to the pooling allocator when termination occurs. 382 memory_image: MemoryImageSlot, 383 } 384 385 impl StaticMemory { 386 fn new( 387 base_ptr: *mut u8, 388 base_capacity: usize, 389 initial_size: usize, 390 maximum_size: Option<usize>, 391 memory_image: MemoryImageSlot, 392 memory_and_guard_size: usize, 393 ) -> Result<Self> { 394 if base_capacity < initial_size { 395 bail!( 396 "initial memory size of {} exceeds the pooling allocator's \ 397 configured maximum memory size of {} bytes", 398 initial_size, 399 base_capacity, 400 ); 401 } 402 403 // Only use the part of the slice that is necessary. 404 let base_capacity = match maximum_size { 405 Some(max) if max < base_capacity => max, 406 _ => base_capacity, 407 }; 408 409 Ok(Self { 410 base: SendSyncPtr::new(NonNull::new(base_ptr).unwrap()), 411 capacity: base_capacity, 412 size: initial_size, 413 memory_image, 414 memory_and_guard_size, 415 }) 416 } 417 } 418 419 impl RuntimeLinearMemory for StaticMemory { 420 fn byte_size(&self) -> usize { 421 self.size 422 } 423 424 fn maximum_byte_size(&self) -> Option<usize> { 425 Some(self.capacity) 426 } 427 428 fn grow_to(&mut self, new_byte_size: usize) -> Result<()> { 429 // Never exceed the static memory size; this check should have been made 430 // prior to arriving here. 431 assert!(new_byte_size <= self.capacity); 432 433 self.memory_image.set_heap_limit(new_byte_size)?; 434 435 // Update our accounting of the available size. 436 self.size = new_byte_size; 437 Ok(()) 438 } 439 440 fn vmmemory(&mut self) -> VMMemoryDefinition { 441 VMMemoryDefinition { 442 base: self.base.as_ptr(), 443 current_length: self.size.into(), 444 } 445 } 446 447 fn needs_init(&self) -> bool { 448 !self.memory_image.has_image() 449 } 450 451 fn as_any_mut(&mut self) -> &mut dyn std::any::Any { 452 self 453 } 454 455 fn wasm_accessible(&self) -> Range<usize> { 456 let base = self.base.as_ptr() as usize; 457 let end = base + self.memory_and_guard_size; 458 base..end 459 } 460 } 461 462 /// Representation of a runtime wasm linear memory. 463 pub struct Memory(pub(crate) Box<dyn RuntimeLinearMemory>); 464 465 impl Memory { 466 /// Create a new dynamic (movable) memory instance for the specified plan. 467 pub fn new_dynamic( 468 plan: &MemoryPlan, 469 creator: &dyn RuntimeMemoryCreator, 470 store: &mut dyn Store, 471 memory_image: Option<&Arc<MemoryImage>>, 472 ) -> Result<Self> { 473 let (minimum, maximum) = Self::limit_new(plan, Some(store))?; 474 let allocation = creator.new_memory(plan, minimum, maximum, memory_image)?; 475 let allocation = if plan.memory.shared { 476 Box::new(SharedMemory::wrap(plan, allocation, plan.memory)?) 477 } else { 478 allocation 479 }; 480 Ok(Memory(allocation)) 481 } 482 483 /// Create a new static (immovable) memory instance for the specified plan. 484 pub fn new_static( 485 plan: &MemoryPlan, 486 base_ptr: *mut u8, 487 base_capacity: usize, 488 memory_image: MemoryImageSlot, 489 memory_and_guard_size: usize, 490 store: &mut dyn Store, 491 ) -> Result<Self> { 492 let (minimum, maximum) = Self::limit_new(plan, Some(store))?; 493 let pooled_memory = StaticMemory::new( 494 base_ptr, 495 base_capacity, 496 minimum, 497 maximum, 498 memory_image, 499 memory_and_guard_size, 500 )?; 501 let allocation = Box::new(pooled_memory); 502 let allocation: Box<dyn RuntimeLinearMemory> = if plan.memory.shared { 503 // FIXME: since the pooling allocator owns the memory allocation 504 // (which is torn down with the instance), the current shared memory 505 // implementation will cause problems; see 506 // https://github.com/bytecodealliance/wasmtime/issues/4244. 507 todo!("using shared memory with the pooling allocator is a work in progress"); 508 } else { 509 allocation 510 }; 511 Ok(Memory(allocation)) 512 } 513 514 /// Calls the `store`'s limiter to optionally prevent a memory from being allocated. 515 /// 516 /// Returns the minimum size and optional maximum size of the memory, in 517 /// bytes. 518 pub(crate) fn limit_new( 519 plan: &MemoryPlan, 520 store: Option<&mut dyn Store>, 521 ) -> Result<(usize, Option<usize>)> { 522 // Sanity-check what should already be true from wasm module validation. 523 let absolute_max = if plan.memory.memory64 { 524 WASM64_MAX_PAGES 525 } else { 526 WASM32_MAX_PAGES 527 }; 528 assert!(plan.memory.minimum <= absolute_max); 529 assert!(plan.memory.maximum.is_none() || plan.memory.maximum.unwrap() <= absolute_max); 530 531 // This is the absolute possible maximum that the module can try to 532 // allocate, which is our entire address space minus a wasm page. That 533 // shouldn't ever actually work in terms of an allocation because 534 // presumably the kernel wants *something* for itself, but this is used 535 // to pass to the `store`'s limiter for a requested size 536 // to approximate the scale of the request that the wasm module is 537 // making. This is necessary because the limiter works on `usize` bytes 538 // whereas we're working with possibly-overflowing `u64` calculations 539 // here. To actually faithfully represent the byte requests of modules 540 // we'd have to represent things as `u128`, but that's kinda 541 // overkill for this purpose. 542 let absolute_max = 0usize.wrapping_sub(WASM_PAGE_SIZE); 543 544 // If the minimum memory size overflows the size of our own address 545 // space, then we can't satisfy this request, but defer the error to 546 // later so the `store` can be informed that an effective oom is 547 // happening. 548 let minimum = plan 549 .memory 550 .minimum 551 .checked_mul(WASM_PAGE_SIZE_U64) 552 .and_then(|m| usize::try_from(m).ok()); 553 554 // The plan stores the maximum size in units of wasm pages, but we 555 // use units of bytes. Unlike for the `minimum` size we silently clamp 556 // the effective maximum size to `absolute_max` above if the maximum is 557 // too large. This should be ok since as a wasm runtime we get to 558 // arbitrarily decide the actual maximum size of memory, regardless of 559 // what's actually listed on the memory itself. 560 let mut maximum = plan.memory.maximum.map(|max| { 561 usize::try_from(max) 562 .ok() 563 .and_then(|m| m.checked_mul(WASM_PAGE_SIZE)) 564 .unwrap_or(absolute_max) 565 }); 566 567 // If this is a 32-bit memory and no maximum is otherwise listed then we 568 // need to still specify a maximum size of 4GB. If the host platform is 569 // 32-bit then there's no need to limit the maximum this way since no 570 // allocation of 4GB can succeed, but for 64-bit platforms this is 571 // required to limit memories to 4GB. 572 if !plan.memory.memory64 && maximum.is_none() { 573 maximum = usize::try_from(1u64 << 32).ok(); 574 } 575 576 // Inform the store's limiter what's about to happen. This will let the 577 // limiter reject anything if necessary, and this also guarantees that 578 // we should call the limiter for all requested memories, even if our 579 // `minimum` calculation overflowed. This means that the `minimum` we're 580 // informing the limiter is lossy and may not be 100% accurate, but for 581 // now the expected uses of limiter means that's ok. 582 if let Some(store) = store { 583 // We ignore the store limits for shared memories since they are 584 // technically not created within a store (though, trickily, they 585 // may be associated with one in order to get a `vmctx`). 586 if !plan.memory.shared { 587 if !store.memory_growing(0, minimum.unwrap_or(absolute_max), maximum)? { 588 bail!( 589 "memory minimum size of {} pages exceeds memory limits", 590 plan.memory.minimum 591 ); 592 } 593 } 594 } 595 596 // At this point we need to actually handle overflows, so bail out with 597 // an error if we made it this far. 598 let minimum = minimum.ok_or_else(|| { 599 format_err!( 600 "memory minimum size of {} pages exceeds memory limits", 601 plan.memory.minimum 602 ) 603 })?; 604 Ok((minimum, maximum)) 605 } 606 607 /// Returns the number of allocated wasm pages. 608 pub fn byte_size(&self) -> usize { 609 self.0.byte_size() 610 } 611 612 /// Returns whether or not this memory needs initialization. It 613 /// may not if it already has initial content thanks to a CoW 614 /// mechanism. 615 pub(crate) fn needs_init(&self) -> bool { 616 self.0.needs_init() 617 } 618 619 /// Grow memory by the specified amount of wasm pages. 620 /// 621 /// Returns `None` if memory can't be grown by the specified amount 622 /// of wasm pages. Returns `Some` with the old size of memory, in bytes, on 623 /// successful growth. 624 /// 625 /// # Safety 626 /// 627 /// Resizing the memory can reallocate the memory buffer for dynamic memories. 628 /// An instance's `VMContext` may have pointers to the memory's base and will 629 /// need to be fixed up after growing the memory. 630 /// 631 /// Generally, prefer using `InstanceHandle::memory_grow`, which encapsulates 632 /// this unsafety. 633 /// 634 /// Ensure that the provided Store is not used to get access any Memory 635 /// which lives inside it. 636 pub unsafe fn grow( 637 &mut self, 638 delta_pages: u64, 639 store: Option<&mut dyn Store>, 640 ) -> Result<Option<usize>, Error> { 641 self.0 642 .grow(delta_pages, store) 643 .map(|opt| opt.map(|(old, _new)| old)) 644 } 645 646 /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code. 647 pub fn vmmemory(&mut self) -> VMMemoryDefinition { 648 self.0.vmmemory() 649 } 650 651 /// Consume the memory, returning its [`MemoryImageSlot`] if any is present. 652 /// The image should only be present for a subset of memories created with 653 /// [`Memory::new_static()`]. 654 #[cfg(feature = "pooling-allocator")] 655 pub fn unwrap_static_image(mut self) -> MemoryImageSlot { 656 let mem = self.0.as_any_mut().downcast_mut::<StaticMemory>().unwrap(); 657 std::mem::replace(&mut mem.memory_image, MemoryImageSlot::dummy()) 658 } 659 660 /// If the [Memory] is a [SharedMemory], unwrap it and return a clone to 661 /// that shared memory. 662 pub fn as_shared_memory(&mut self) -> Option<&mut SharedMemory> { 663 let as_any = self.0.as_any_mut(); 664 if let Some(m) = as_any.downcast_mut::<SharedMemory>() { 665 Some(m) 666 } else { 667 None 668 } 669 } 670 671 /// Implementation of `memory.atomic.notify` for all memories. 672 pub fn atomic_notify(&mut self, addr: u64, count: u32) -> Result<u32, Trap> { 673 match self.0.as_any_mut().downcast_mut::<SharedMemory>() { 674 Some(m) => m.atomic_notify(addr, count), 675 None => { 676 validate_atomic_addr(&self.vmmemory(), addr, 4, 4)?; 677 Ok(0) 678 } 679 } 680 } 681 682 /// Implementation of `memory.atomic.wait32` for all memories. 683 pub fn atomic_wait32( 684 &mut self, 685 addr: u64, 686 expected: u32, 687 deadline: Option<Instant>, 688 ) -> Result<WaitResult, Trap> { 689 match self.0.as_any_mut().downcast_mut::<SharedMemory>() { 690 Some(m) => m.atomic_wait32(addr, expected, deadline), 691 None => { 692 validate_atomic_addr(&self.vmmemory(), addr, 4, 4)?; 693 Err(Trap::AtomicWaitNonSharedMemory) 694 } 695 } 696 } 697 698 /// Implementation of `memory.atomic.wait64` for all memories. 699 pub fn atomic_wait64( 700 &mut self, 701 addr: u64, 702 expected: u64, 703 deadline: Option<Instant>, 704 ) -> Result<WaitResult, Trap> { 705 match self.0.as_any_mut().downcast_mut::<SharedMemory>() { 706 Some(m) => m.atomic_wait64(addr, expected, deadline), 707 None => { 708 validate_atomic_addr(&self.vmmemory(), addr, 8, 8)?; 709 Err(Trap::AtomicWaitNonSharedMemory) 710 } 711 } 712 } 713 714 /// Returns the range of bytes that WebAssembly should be able to address in 715 /// this linear memory. Note that this includes guard pages which wasm can 716 /// hit. 717 pub fn wasm_accessible(&self) -> Range<usize> { 718 self.0.wasm_accessible() 719 } 720 } 721 722 /// In the configurations where bounds checks were elided in JIT code (because 723 /// we are using static memories with virtual memory guard pages) this manual 724 /// check is here so we don't segfault from Rust. For other configurations, 725 /// these checks are required anyways. 726 pub fn validate_atomic_addr( 727 def: &VMMemoryDefinition, 728 addr: u64, 729 access_size: u64, 730 access_alignment: u64, 731 ) -> Result<*mut u8, Trap> { 732 debug_assert!(access_alignment.is_power_of_two()); 733 if !(addr % access_alignment == 0) { 734 return Err(Trap::HeapMisaligned); 735 } 736 737 let length = u64::try_from(def.current_length()).unwrap(); 738 if !(addr.saturating_add(access_size) < length) { 739 return Err(Trap::MemoryOutOfBounds); 740 } 741 742 Ok(def.base.wrapping_add(addr as usize)) 743 } 744