1 //! Implements the pooling instance allocator. 2 //! 3 //! The pooling instance allocator maps memory in advance and allocates 4 //! instances, memories, tables, and stacks from a pool of available resources. 5 //! Using the pooling instance allocator can speed up module instantiation when 6 //! modules can be constrained based on configurable limits 7 //! ([`InstanceLimits`]). Each new instance is stored in a "slot"; as instances 8 //! are allocated and freed, these slots are either filled or emptied: 9 //! 10 //! ```text 11 //! ┌──────┬──────┬──────┬──────┬──────┐ 12 //! │Slot 0│Slot 1│Slot 2│Slot 3│......│ 13 //! └──────┴──────┴──────┴──────┴──────┘ 14 //! ``` 15 //! 16 //! Each slot has a "slot ID"--an index into the pool. Slot IDs are handed out 17 //! by the [`index_allocator`] module. Note that each kind of pool-allocated 18 //! item is stored in its own separate pool: [`memory_pool`], [`table_pool`], 19 //! [`stack_pool`]. See those modules for more details. 20 21 mod decommit_queue; 22 mod index_allocator; 23 mod memory_pool; 24 mod table_pool; 25 26 #[cfg(feature = "gc")] 27 mod gc_heap_pool; 28 29 #[cfg(all(feature = "async"))] 30 mod generic_stack_pool; 31 #[cfg(all(feature = "async", unix, not(miri)))] 32 mod unix_stack_pool; 33 34 #[cfg(all(feature = "async"))] 35 cfg_if::cfg_if! { 36 if #[cfg(all(unix, not(miri), not(asan)))] { 37 use unix_stack_pool as stack_pool; 38 } else { 39 use generic_stack_pool as stack_pool; 40 } 41 } 42 43 use self::decommit_queue::DecommitQueue; 44 use self::memory_pool::MemoryPool; 45 use self::table_pool::TablePool; 46 use super::{ 47 InstanceAllocationRequest, InstanceAllocator, MemoryAllocationIndex, TableAllocationIndex, 48 }; 49 use crate::Enabled; 50 use crate::prelude::*; 51 use crate::runtime::vm::{ 52 CompiledModuleId, Memory, Table, 53 instance::Instance, 54 mpk::{self, ProtectionKey, ProtectionMask}, 55 sys::vm::PageMap, 56 }; 57 use std::borrow::Cow; 58 use std::fmt::Display; 59 use std::sync::{Mutex, MutexGuard}; 60 use std::{ 61 mem, 62 sync::atomic::{AtomicU64, Ordering}, 63 }; 64 use wasmtime_environ::{ 65 DefinedMemoryIndex, DefinedTableIndex, HostPtr, Module, Tunables, VMOffsets, 66 }; 67 68 #[cfg(feature = "gc")] 69 use super::GcHeapAllocationIndex; 70 #[cfg(feature = "gc")] 71 use crate::runtime::vm::{GcHeap, GcRuntime}; 72 #[cfg(feature = "gc")] 73 use gc_heap_pool::GcHeapPool; 74 75 #[cfg(feature = "async")] 76 use stack_pool::StackPool; 77 78 #[cfg(feature = "component-model")] 79 use wasmtime_environ::{ 80 StaticModuleIndex, 81 component::{Component, VMComponentOffsets}, 82 }; 83 84 fn round_up_to_pow2(n: usize, to: usize) -> usize { 85 debug_assert!(to > 0); 86 debug_assert!(to.is_power_of_two()); 87 (n + to - 1) & !(to - 1) 88 } 89 90 /// Instance-related limit configuration for pooling. 91 /// 92 /// More docs on this can be found at `wasmtime::PoolingAllocationConfig`. 93 #[derive(Debug, Copy, Clone)] 94 pub struct InstanceLimits { 95 /// The maximum number of component instances that may be allocated 96 /// concurrently. 97 pub total_component_instances: u32, 98 99 /// The maximum size of a component's `VMComponentContext`, not including 100 /// any of its inner core modules' `VMContext` sizes. 101 pub component_instance_size: usize, 102 103 /// The maximum number of core module instances that may be allocated 104 /// concurrently. 105 pub total_core_instances: u32, 106 107 /// The maximum number of core module instances that a single component may 108 /// transitively contain. 109 pub max_core_instances_per_component: u32, 110 111 /// The maximum number of Wasm linear memories that a component may 112 /// transitively contain. 113 pub max_memories_per_component: u32, 114 115 /// The maximum number of tables that a component may transitively contain. 116 pub max_tables_per_component: u32, 117 118 /// The total number of linear memories in the pool, across all instances. 119 pub total_memories: u32, 120 121 /// The total number of tables in the pool, across all instances. 122 pub total_tables: u32, 123 124 /// The total number of async stacks in the pool, across all instances. 125 #[cfg(feature = "async")] 126 pub total_stacks: u32, 127 128 /// Maximum size of a core instance's `VMContext`. 129 pub core_instance_size: usize, 130 131 /// Maximum number of tables per instance. 132 pub max_tables_per_module: u32, 133 134 /// Maximum number of word-size elements per table. 135 /// 136 /// Note that tables for element types such as continuations 137 /// that use more than one word of storage may store fewer 138 /// elements. 139 pub table_elements: usize, 140 141 /// Maximum number of linear memories per instance. 142 pub max_memories_per_module: u32, 143 144 /// Maximum byte size of a linear memory, must be smaller than 145 /// `memory_reservation` in `Tunables`. 146 pub max_memory_size: usize, 147 148 /// The total number of GC heaps in the pool, across all instances. 149 #[cfg(feature = "gc")] 150 pub total_gc_heaps: u32, 151 } 152 153 impl Default for InstanceLimits { 154 fn default() -> Self { 155 let total = if cfg!(target_pointer_width = "32") { 156 100 157 } else { 158 1000 159 }; 160 // See doc comments for `wasmtime::PoolingAllocationConfig` for these 161 // default values 162 Self { 163 total_component_instances: total, 164 component_instance_size: 1 << 20, // 1 MiB 165 total_core_instances: total, 166 max_core_instances_per_component: u32::MAX, 167 max_memories_per_component: u32::MAX, 168 max_tables_per_component: u32::MAX, 169 total_memories: total, 170 total_tables: total, 171 #[cfg(feature = "async")] 172 total_stacks: total, 173 core_instance_size: 1 << 20, // 1 MiB 174 max_tables_per_module: 1, 175 // NB: in #8504 it was seen that a C# module in debug module can 176 // have 10k+ elements. 177 table_elements: 20_000, 178 max_memories_per_module: 1, 179 #[cfg(target_pointer_width = "64")] 180 max_memory_size: 1 << 32, // 4G, 181 #[cfg(target_pointer_width = "32")] 182 max_memory_size: 10 << 20, // 10 MiB 183 #[cfg(feature = "gc")] 184 total_gc_heaps: total, 185 } 186 } 187 } 188 189 /// Configuration options for the pooling instance allocator supplied at 190 /// construction. 191 #[derive(Copy, Clone, Debug)] 192 pub struct PoolingInstanceAllocatorConfig { 193 /// See `PoolingAllocatorConfig::max_unused_warm_slots` in `wasmtime` 194 pub max_unused_warm_slots: u32, 195 /// The target number of decommits to do per batch. This is not precise, as 196 /// we can queue up decommits at times when we aren't prepared to 197 /// immediately flush them, and so we may go over this target size 198 /// occasionally. 199 pub decommit_batch_size: usize, 200 /// The size, in bytes, of async stacks to allocate (not including the guard 201 /// page). 202 pub stack_size: usize, 203 /// The limits to apply to instances allocated within this allocator. 204 pub limits: InstanceLimits, 205 /// Whether or not async stacks are zeroed after use. 206 pub async_stack_zeroing: bool, 207 /// If async stack zeroing is enabled and the host platform is Linux this is 208 /// how much memory to zero out with `memset`. 209 /// 210 /// The rest of memory will be zeroed out with `madvise`. 211 #[cfg(feature = "async")] 212 pub async_stack_keep_resident: usize, 213 /// How much linear memory, in bytes, to keep resident after resetting for 214 /// use with the next instance. This much memory will be `memset` to zero 215 /// when a linear memory is deallocated. 216 /// 217 /// Memory exceeding this amount in the wasm linear memory will be released 218 /// with `madvise` back to the kernel. 219 /// 220 /// Only applicable on Linux. 221 pub linear_memory_keep_resident: usize, 222 /// Same as `linear_memory_keep_resident` but for tables. 223 pub table_keep_resident: usize, 224 /// Whether to enable memory protection keys. 225 pub memory_protection_keys: Enabled, 226 /// How many memory protection keys to allocate. 227 pub max_memory_protection_keys: usize, 228 /// Whether to enable PAGEMAP_SCAN on Linux. 229 pub pagemap_scan: Enabled, 230 } 231 232 impl Default for PoolingInstanceAllocatorConfig { 233 fn default() -> PoolingInstanceAllocatorConfig { 234 PoolingInstanceAllocatorConfig { 235 max_unused_warm_slots: 100, 236 decommit_batch_size: 1, 237 stack_size: 2 << 20, 238 limits: InstanceLimits::default(), 239 async_stack_zeroing: false, 240 #[cfg(feature = "async")] 241 async_stack_keep_resident: 0, 242 linear_memory_keep_resident: 0, 243 table_keep_resident: 0, 244 memory_protection_keys: Enabled::No, 245 max_memory_protection_keys: 16, 246 pagemap_scan: Enabled::No, 247 } 248 } 249 } 250 251 impl PoolingInstanceAllocatorConfig { 252 pub fn is_pagemap_scan_available() -> bool { 253 PageMap::new().is_some() 254 } 255 } 256 257 /// An error returned when the pooling allocator cannot allocate a table, 258 /// memory, etc... because the maximum number of concurrent allocations for that 259 /// entity has been reached. 260 #[derive(Debug)] 261 pub struct PoolConcurrencyLimitError { 262 limit: usize, 263 kind: Cow<'static, str>, 264 } 265 266 impl core::error::Error for PoolConcurrencyLimitError {} 267 268 impl Display for PoolConcurrencyLimitError { 269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 270 let limit = self.limit; 271 let kind = &self.kind; 272 write!(f, "maximum concurrent limit of {limit} for {kind} reached") 273 } 274 } 275 276 impl PoolConcurrencyLimitError { 277 fn new(limit: usize, kind: impl Into<Cow<'static, str>>) -> Self { 278 Self { 279 limit, 280 kind: kind.into(), 281 } 282 } 283 } 284 285 /// Implements the pooling instance allocator. 286 /// 287 /// This allocator internally maintains pools of instances, memories, tables, 288 /// and stacks. 289 /// 290 /// Note: the resource pools are manually dropped so that the fault handler 291 /// terminates correctly. 292 #[derive(Debug)] 293 pub struct PoolingInstanceAllocator { 294 decommit_batch_size: usize, 295 limits: InstanceLimits, 296 297 // The number of live core module and component instances at any given 298 // time. Note that this can temporarily go over the configured limit. This 299 // doesn't mean we have actually overshot, but that we attempted to allocate 300 // a new instance and incremented the counter, we've seen (or are about to 301 // see) that the counter is beyond the configured threshold, and are going 302 // to decrement the counter and return an error but haven't done so yet. See 303 // the increment trait methods for more details. 304 live_core_instances: AtomicU64, 305 live_component_instances: AtomicU64, 306 307 decommit_queue: Mutex<DecommitQueue>, 308 memories: MemoryPool, 309 tables: TablePool, 310 311 #[cfg(feature = "gc")] 312 gc_heaps: GcHeapPool, 313 314 #[cfg(feature = "async")] 315 stacks: StackPool, 316 317 pagemap: Option<PageMap>, 318 } 319 320 impl Drop for PoolingInstanceAllocator { 321 fn drop(&mut self) { 322 if !cfg!(debug_assertions) { 323 return; 324 } 325 326 // NB: when cfg(not(debug_assertions)) it is okay that we don't flush 327 // the queue, as the sub-pools will unmap those ranges anyways, so 328 // there's no point in decommitting them. But we do need to flush the 329 // queue when debug assertions are enabled to make sure that all 330 // entities get returned to their associated sub-pools and we can 331 // differentiate between a leaking slot and an enqueued-for-decommit 332 // slot. 333 let queue = self.decommit_queue.lock().unwrap(); 334 self.flush_decommit_queue(queue); 335 336 debug_assert_eq!(self.live_component_instances.load(Ordering::Acquire), 0); 337 debug_assert_eq!(self.live_core_instances.load(Ordering::Acquire), 0); 338 339 debug_assert!(self.memories.is_empty()); 340 debug_assert!(self.tables.is_empty()); 341 342 #[cfg(feature = "gc")] 343 debug_assert!(self.gc_heaps.is_empty()); 344 345 #[cfg(feature = "async")] 346 debug_assert!(self.stacks.is_empty()); 347 } 348 } 349 350 impl PoolingInstanceAllocator { 351 /// Creates a new pooling instance allocator with the given strategy and limits. 352 pub fn new(config: &PoolingInstanceAllocatorConfig, tunables: &Tunables) -> Result<Self> { 353 Ok(Self { 354 decommit_batch_size: config.decommit_batch_size, 355 limits: config.limits, 356 live_component_instances: AtomicU64::new(0), 357 live_core_instances: AtomicU64::new(0), 358 decommit_queue: Mutex::new(DecommitQueue::default()), 359 memories: MemoryPool::new(config, tunables)?, 360 tables: TablePool::new(config)?, 361 #[cfg(feature = "gc")] 362 gc_heaps: GcHeapPool::new(config)?, 363 #[cfg(feature = "async")] 364 stacks: StackPool::new(config)?, 365 pagemap: match config.pagemap_scan { 366 Enabled::Auto => PageMap::new(), 367 Enabled::Yes => Some(PageMap::new().ok_or_else(|| { 368 anyhow!( 369 "required to enable PAGEMAP_SCAN but this system \ 370 does not support it" 371 ) 372 })?), 373 Enabled::No => None, 374 }, 375 }) 376 } 377 378 fn core_instance_size(&self) -> usize { 379 round_up_to_pow2(self.limits.core_instance_size, mem::align_of::<Instance>()) 380 } 381 382 fn validate_table_plans(&self, module: &Module) -> Result<()> { 383 self.tables.validate(module) 384 } 385 386 fn validate_memory_plans(&self, module: &Module) -> Result<()> { 387 self.memories.validate_memories(module) 388 } 389 390 fn validate_core_instance_size(&self, offsets: &VMOffsets<HostPtr>) -> Result<()> { 391 let layout = Instance::alloc_layout(offsets); 392 if layout.size() <= self.core_instance_size() { 393 return Ok(()); 394 } 395 396 // If this `module` exceeds the allocation size allotted to it then an 397 // error will be reported here. The error of "required N bytes but 398 // cannot allocate that" is pretty opaque, however, because it's not 399 // clear what the breakdown of the N bytes are and what to optimize 400 // next. To help provide a better error message here some fancy-ish 401 // logic is done here to report the breakdown of the byte request into 402 // the largest portions and where it's coming from. 403 let mut message = format!( 404 "instance allocation for this module \ 405 requires {} bytes which exceeds the configured maximum \ 406 of {} bytes; breakdown of allocation requirement:\n\n", 407 layout.size(), 408 self.core_instance_size(), 409 ); 410 411 let mut remaining = layout.size(); 412 let mut push = |name: &str, bytes: usize| { 413 assert!(remaining >= bytes); 414 remaining -= bytes; 415 416 // If the `name` region is more than 5% of the allocation request 417 // then report it here, otherwise ignore it. We have less than 20 418 // fields so we're guaranteed that something should be reported, and 419 // otherwise it's not particularly interesting to learn about 5 420 // different fields that are all 8 or 0 bytes. Only try to report 421 // the "major" sources of bytes here. 422 if bytes > layout.size() / 20 { 423 message.push_str(&format!( 424 " * {:.02}% - {} bytes - {}\n", 425 ((bytes as f32) / (layout.size() as f32)) * 100.0, 426 bytes, 427 name, 428 )); 429 } 430 }; 431 432 // The `Instance` itself requires some size allocated to it. 433 push("instance state management", mem::size_of::<Instance>()); 434 435 // Afterwards the `VMContext`'s regions are why we're requesting bytes, 436 // so ask it for descriptions on each region's byte size. 437 for (desc, size) in offsets.region_sizes() { 438 push(desc, size as usize); 439 } 440 441 // double-check we accounted for all the bytes 442 assert_eq!(remaining, 0); 443 444 bail!("{}", message) 445 } 446 447 #[cfg(feature = "component-model")] 448 fn validate_component_instance_size( 449 &self, 450 offsets: &VMComponentOffsets<HostPtr>, 451 ) -> Result<()> { 452 if usize::try_from(offsets.size_of_vmctx()).unwrap() <= self.limits.component_instance_size 453 { 454 return Ok(()); 455 } 456 457 // TODO: Add context with detailed accounting of what makes up all the 458 // `VMComponentContext`'s space like we do for module instances. 459 bail!( 460 "instance allocation for this component requires {} bytes of `VMComponentContext` \ 461 space which exceeds the configured maximum of {} bytes", 462 offsets.size_of_vmctx(), 463 self.limits.component_instance_size 464 ) 465 } 466 467 fn flush_decommit_queue(&self, mut locked_queue: MutexGuard<'_, DecommitQueue>) -> bool { 468 // Take the queue out of the mutex and drop the lock, to minimize 469 // contention. 470 let queue = mem::take(&mut *locked_queue); 471 drop(locked_queue); 472 queue.flush(self) 473 } 474 475 /// Execute `f` and if it returns `Err(PoolConcurrencyLimitError)`, then try 476 /// flushing the decommit queue. If flushing the queue freed up slots, then 477 /// try running `f` again. 478 #[cfg(feature = "async")] 479 fn with_flush_and_retry<T>(&self, mut f: impl FnMut() -> Result<T>) -> Result<T> { 480 f().or_else(|e| { 481 if e.is::<PoolConcurrencyLimitError>() { 482 let queue = self.decommit_queue.lock().unwrap(); 483 if self.flush_decommit_queue(queue) { 484 return f(); 485 } 486 } 487 488 Err(e) 489 }) 490 } 491 492 fn merge_or_flush(&self, mut local_queue: DecommitQueue) { 493 match local_queue.raw_len() { 494 // If we didn't enqueue any regions for decommit, then we must have 495 // either memset the whole entity or eagerly remapped it to zero 496 // because we don't have linux's `madvise(DONTNEED)` semantics. In 497 // either case, the entity slot is ready for reuse immediately. 498 0 => { 499 local_queue.flush(self); 500 } 501 502 // We enqueued at least our batch size of regions for decommit, so 503 // flush the local queue immediately. Don't bother inspecting (or 504 // locking!) the shared queue. 505 n if n >= self.decommit_batch_size => { 506 local_queue.flush(self); 507 } 508 509 // If we enqueued some regions for decommit, but did not reach our 510 // batch size, so we don't want to flush it yet, then merge the 511 // local queue into the shared queue. 512 n => { 513 debug_assert!(n < self.decommit_batch_size); 514 let mut shared_queue = self.decommit_queue.lock().unwrap(); 515 shared_queue.append(&mut local_queue); 516 // And if the shared queue now has at least as many regions 517 // enqueued for decommit as our batch size, then we can flush 518 // it. 519 if shared_queue.raw_len() >= self.decommit_batch_size { 520 self.flush_decommit_queue(shared_queue); 521 } 522 } 523 } 524 } 525 } 526 527 #[async_trait::async_trait] 528 unsafe impl InstanceAllocator for PoolingInstanceAllocator { 529 #[cfg(feature = "component-model")] 530 fn validate_component<'a>( 531 &self, 532 component: &Component, 533 offsets: &VMComponentOffsets<HostPtr>, 534 get_module: &'a dyn Fn(StaticModuleIndex) -> &'a Module, 535 ) -> Result<()> { 536 self.validate_component_instance_size(offsets) 537 .context("component instance size does not fit in pooling allocator requirements")?; 538 539 let mut num_core_instances = 0; 540 let mut num_memories = 0; 541 let mut num_tables = 0; 542 for init in &component.initializers { 543 use wasmtime_environ::component::GlobalInitializer::*; 544 use wasmtime_environ::component::InstantiateModule; 545 match init { 546 InstantiateModule(InstantiateModule::Import(_, _)) => { 547 num_core_instances += 1; 548 // Can't statically account for the total vmctx size, number 549 // of memories, and number of tables in this component. 550 } 551 InstantiateModule(InstantiateModule::Static(static_module_index, _)) => { 552 let module = get_module(*static_module_index); 553 let offsets = VMOffsets::new(HostPtr, &module); 554 self.validate_module(module, &offsets)?; 555 num_core_instances += 1; 556 num_memories += module.num_defined_memories(); 557 num_tables += module.num_defined_tables(); 558 } 559 LowerImport { .. } 560 | ExtractMemory(_) 561 | ExtractTable(_) 562 | ExtractRealloc(_) 563 | ExtractCallback(_) 564 | ExtractPostReturn(_) 565 | Resource(_) => {} 566 } 567 } 568 569 if num_core_instances 570 > usize::try_from(self.limits.max_core_instances_per_component).unwrap() 571 { 572 bail!( 573 "The component transitively contains {num_core_instances} core module instances, \ 574 which exceeds the configured maximum of {} in the pooling allocator", 575 self.limits.max_core_instances_per_component 576 ); 577 } 578 579 if num_memories > usize::try_from(self.limits.max_memories_per_component).unwrap() { 580 bail!( 581 "The component transitively contains {num_memories} Wasm linear memories, which \ 582 exceeds the configured maximum of {} in the pooling allocator", 583 self.limits.max_memories_per_component 584 ); 585 } 586 587 if num_tables > usize::try_from(self.limits.max_tables_per_component).unwrap() { 588 bail!( 589 "The component transitively contains {num_tables} tables, which exceeds the \ 590 configured maximum of {} in the pooling allocator", 591 self.limits.max_tables_per_component 592 ); 593 } 594 595 Ok(()) 596 } 597 598 fn validate_module(&self, module: &Module, offsets: &VMOffsets<HostPtr>) -> Result<()> { 599 self.validate_memory_plans(module) 600 .context("module memory does not fit in pooling allocator requirements")?; 601 self.validate_table_plans(module) 602 .context("module table does not fit in pooling allocator requirements")?; 603 self.validate_core_instance_size(offsets) 604 .context("module instance size does not fit in pooling allocator requirements")?; 605 Ok(()) 606 } 607 608 #[cfg(feature = "gc")] 609 fn validate_memory(&self, memory: &wasmtime_environ::Memory) -> Result<()> { 610 self.memories.validate_memory(memory) 611 } 612 613 #[cfg(feature = "component-model")] 614 fn increment_component_instance_count(&self) -> Result<()> { 615 let old_count = self.live_component_instances.fetch_add(1, Ordering::AcqRel); 616 if old_count >= u64::from(self.limits.total_component_instances) { 617 self.decrement_component_instance_count(); 618 return Err(PoolConcurrencyLimitError::new( 619 usize::try_from(self.limits.total_component_instances).unwrap(), 620 "component instances", 621 ) 622 .into()); 623 } 624 Ok(()) 625 } 626 627 #[cfg(feature = "component-model")] 628 fn decrement_component_instance_count(&self) { 629 self.live_component_instances.fetch_sub(1, Ordering::AcqRel); 630 } 631 632 fn increment_core_instance_count(&self) -> Result<()> { 633 let old_count = self.live_core_instances.fetch_add(1, Ordering::AcqRel); 634 if old_count >= u64::from(self.limits.total_core_instances) { 635 self.decrement_core_instance_count(); 636 return Err(PoolConcurrencyLimitError::new( 637 usize::try_from(self.limits.total_core_instances).unwrap(), 638 "core instances", 639 ) 640 .into()); 641 } 642 Ok(()) 643 } 644 645 fn decrement_core_instance_count(&self) { 646 self.live_core_instances.fetch_sub(1, Ordering::AcqRel); 647 } 648 649 async fn allocate_memory( 650 &self, 651 request: &mut InstanceAllocationRequest<'_, '_>, 652 ty: &wasmtime_environ::Memory, 653 memory_index: Option<DefinedMemoryIndex>, 654 ) -> Result<(MemoryAllocationIndex, Memory)> { 655 // FIXME(rust-lang/rust#145127) this should ideally use a version of 656 // `with_flush_and_retry` but adapted for async closures instead of only 657 // sync closures. Right now that won't compile though so this is the 658 // manually expanded version of the method. 659 let e = match self.memories.allocate(request, ty, memory_index).await { 660 Ok(result) => return Ok(result), 661 Err(e) => e, 662 }; 663 664 if e.is::<PoolConcurrencyLimitError>() { 665 let queue = self.decommit_queue.lock().unwrap(); 666 if self.flush_decommit_queue(queue) { 667 return self.memories.allocate(request, ty, memory_index).await; 668 } 669 } 670 671 Err(e) 672 } 673 674 unsafe fn deallocate_memory( 675 &self, 676 _memory_index: Option<DefinedMemoryIndex>, 677 allocation_index: MemoryAllocationIndex, 678 memory: Memory, 679 ) { 680 // Reset the image slot. If there is any error clearing the 681 // image, just drop it here, and let the drop handler for the 682 // slot unmap in a way that retains the address space 683 // reservation. 684 let mut image = memory.unwrap_static_image(); 685 let mut queue = DecommitQueue::default(); 686 image 687 .clear_and_remain_ready( 688 self.pagemap.as_ref(), 689 self.memories.keep_resident, 690 |ptr, len| { 691 // SAFETY: the memory in `image` won't be used until this 692 // decommit queue is flushed, and by definition the memory is 693 // not in use when calling this function. 694 unsafe { 695 queue.push_raw(ptr, len); 696 } 697 }, 698 ) 699 .expect("failed to reset memory image"); 700 701 // SAFETY: this image is not in use and its memory regions were enqueued 702 // with `push_raw` above. 703 unsafe { 704 queue.push_memory(allocation_index, image); 705 } 706 self.merge_or_flush(queue); 707 } 708 709 async fn allocate_table( 710 &self, 711 request: &mut InstanceAllocationRequest<'_, '_>, 712 ty: &wasmtime_environ::Table, 713 _table_index: DefinedTableIndex, 714 ) -> Result<(super::TableAllocationIndex, Table)> { 715 // FIXME: see `allocate_memory` above for comments about duplication 716 // with `with_flush_and_retry`. 717 let e = match self.tables.allocate(request, ty).await { 718 Ok(result) => return Ok(result), 719 Err(e) => e, 720 }; 721 722 if e.is::<PoolConcurrencyLimitError>() { 723 let queue = self.decommit_queue.lock().unwrap(); 724 if self.flush_decommit_queue(queue) { 725 return self.tables.allocate(request, ty).await; 726 } 727 } 728 729 Err(e) 730 } 731 732 unsafe fn deallocate_table( 733 &self, 734 _table_index: DefinedTableIndex, 735 allocation_index: TableAllocationIndex, 736 mut table: Table, 737 ) { 738 let mut queue = DecommitQueue::default(); 739 // SAFETY: This table is no longer in use by the allocator when this 740 // method is called and additionally all image ranges are pushed with 741 // the understanding that the memory won't get used until the whole 742 // queue is flushed. 743 unsafe { 744 self.tables.reset_table_pages_to_zero( 745 self.pagemap.as_ref(), 746 allocation_index, 747 &mut table, 748 |ptr, len| { 749 queue.push_raw(ptr, len); 750 }, 751 ); 752 } 753 754 // SAFETY: the table has had all its memory regions enqueued above. 755 unsafe { 756 queue.push_table(allocation_index, table); 757 } 758 self.merge_or_flush(queue); 759 } 760 761 #[cfg(feature = "async")] 762 fn allocate_fiber_stack(&self) -> Result<wasmtime_fiber::FiberStack> { 763 self.with_flush_and_retry(|| self.stacks.allocate()) 764 } 765 766 #[cfg(feature = "async")] 767 unsafe fn deallocate_fiber_stack(&self, mut stack: wasmtime_fiber::FiberStack) { 768 let mut queue = DecommitQueue::default(); 769 // SAFETY: the stack is no longer in use by definition when this 770 // function is called and memory ranges pushed here are otherwise no 771 // longer in use. 772 unsafe { 773 self.stacks 774 .zero_stack(&mut stack, |ptr, len| queue.push_raw(ptr, len)); 775 } 776 // SAFETY: this stack's memory regions were enqueued above. 777 unsafe { 778 queue.push_stack(stack); 779 } 780 self.merge_or_flush(queue); 781 } 782 783 fn purge_module(&self, module: CompiledModuleId) { 784 self.memories.purge_module(module); 785 } 786 787 fn next_available_pkey(&self) -> Option<ProtectionKey> { 788 self.memories.next_available_pkey() 789 } 790 791 fn restrict_to_pkey(&self, pkey: ProtectionKey) { 792 mpk::allow(ProtectionMask::zero().or(pkey)); 793 } 794 795 fn allow_all_pkeys(&self) { 796 mpk::allow(ProtectionMask::all()); 797 } 798 799 #[cfg(feature = "gc")] 800 fn allocate_gc_heap( 801 &self, 802 engine: &crate::Engine, 803 gc_runtime: &dyn GcRuntime, 804 memory_alloc_index: MemoryAllocationIndex, 805 memory: Memory, 806 ) -> Result<(GcHeapAllocationIndex, Box<dyn GcHeap>)> { 807 self.gc_heaps 808 .allocate(engine, gc_runtime, memory_alloc_index, memory) 809 } 810 811 #[cfg(feature = "gc")] 812 fn deallocate_gc_heap( 813 &self, 814 allocation_index: GcHeapAllocationIndex, 815 gc_heap: Box<dyn GcHeap>, 816 ) -> (MemoryAllocationIndex, Memory) { 817 self.gc_heaps.deallocate(allocation_index, gc_heap) 818 } 819 } 820 821 #[cfg(test)] 822 #[cfg(target_pointer_width = "64")] 823 mod test { 824 use super::*; 825 826 #[test] 827 fn test_pooling_allocator_with_memory_pages_exceeded() { 828 let config = PoolingInstanceAllocatorConfig { 829 limits: InstanceLimits { 830 total_memories: 1, 831 max_memory_size: 0x100010000, 832 ..Default::default() 833 }, 834 ..PoolingInstanceAllocatorConfig::default() 835 }; 836 assert_eq!( 837 PoolingInstanceAllocator::new( 838 &config, 839 &Tunables { 840 memory_reservation: 0x10000, 841 ..Tunables::default_host() 842 }, 843 ) 844 .map_err(|e| e.to_string()) 845 .expect_err("expected a failure constructing instance allocator"), 846 "maximum memory size of 0x100010000 bytes exceeds the configured \ 847 memory reservation of 0x10000 bytes" 848 ); 849 } 850 851 #[cfg(all( 852 unix, 853 target_pointer_width = "64", 854 feature = "async", 855 not(miri), 856 not(asan) 857 ))] 858 #[test] 859 fn test_stack_zeroed() -> Result<()> { 860 let config = PoolingInstanceAllocatorConfig { 861 max_unused_warm_slots: 0, 862 limits: InstanceLimits { 863 total_stacks: 1, 864 total_memories: 0, 865 total_tables: 0, 866 ..Default::default() 867 }, 868 stack_size: 128, 869 async_stack_zeroing: true, 870 ..PoolingInstanceAllocatorConfig::default() 871 }; 872 let allocator = PoolingInstanceAllocator::new(&config, &Tunables::default_host())?; 873 874 unsafe { 875 for _ in 0..255 { 876 let stack = allocator.allocate_fiber_stack()?; 877 878 // The stack pointer is at the top, so decrement it first 879 let addr = stack.top().unwrap().sub(1); 880 881 assert_eq!(*addr, 0); 882 *addr = 1; 883 884 allocator.deallocate_fiber_stack(stack); 885 } 886 } 887 888 Ok(()) 889 } 890 891 #[cfg(all( 892 unix, 893 target_pointer_width = "64", 894 feature = "async", 895 not(miri), 896 not(asan) 897 ))] 898 #[test] 899 fn test_stack_unzeroed() -> Result<()> { 900 let config = PoolingInstanceAllocatorConfig { 901 max_unused_warm_slots: 0, 902 limits: InstanceLimits { 903 total_stacks: 1, 904 total_memories: 0, 905 total_tables: 0, 906 ..Default::default() 907 }, 908 stack_size: 128, 909 async_stack_zeroing: false, 910 ..PoolingInstanceAllocatorConfig::default() 911 }; 912 let allocator = PoolingInstanceAllocator::new(&config, &Tunables::default_host())?; 913 914 unsafe { 915 for i in 0..255 { 916 let stack = allocator.allocate_fiber_stack()?; 917 918 // The stack pointer is at the top, so decrement it first 919 let addr = stack.top().unwrap().sub(1); 920 921 assert_eq!(*addr, i); 922 *addr = i + 1; 923 924 allocator.deallocate_fiber_stack(stack); 925 } 926 } 927 928 Ok(()) 929 } 930 } 931