1mod decommit_queue;
22mod index_allocator;
23mod memory_pool;
24mod metrics;
25mod table_pool;
26
27#[cfg(feature = "gc")]
28mod gc_heap_pool;
29
30#[cfg(all(feature = "async"))]
31mod generic_stack_pool;
32#[cfg(all(feature = "async", unix, not(miri)))]
33mod unix_stack_pool;
34
35#[cfg(all(feature = "async"))]
36cfg_select! {
37 all(unix, not(miri), not(asan)) => {
38 use unix_stack_pool as stack_pool;
39 }
40 _ => {
41 use generic_stack_pool as stack_pool;
42 }
43}
44
45use self::decommit_queue::DecommitQueue;
46use self::memory_pool::MemoryPool;
47pub use self::metrics::PoolingAllocatorMetrics;
48use self::table_pool::TablePool;
49use super::{
50 InstanceAllocationRequest, InstanceAllocator, MemoryAllocationIndex, TableAllocationIndex,
51};
52use crate::Enabled;
53use crate::config::PoolingAllocationConfig;
54use crate::prelude::*;
55use crate::runtime::vm::{
56 CompiledModuleId, Memory, Table,
57 instance::Instance,
58 mpk::{self, ProtectionKey, ProtectionMask},
59 sys::vm::PageMap,
60};
61use core::future::Future;
62use core::pin::Pin;
63use core::sync::atomic::AtomicUsize;
64use std::borrow::Cow;
65use std::fmt::Display;
66use std::sync::{Mutex, MutexGuard};
67use std::{
68 mem,
69 sync::atomic::{AtomicU64, Ordering},
70};
71use wasmtime_environ::{
72 DefinedMemoryIndex, DefinedTableIndex, HostPtr, MemoryKind, Module, Tunables, VMOffsets,
73};
74
75#[cfg(feature = "gc")]
76use super::GcHeapAllocationIndex;
77#[cfg(feature = "gc")]
78use crate::runtime::vm::{GcHeap, GcRuntime};
79#[cfg(feature = "gc")]
80use gc_heap_pool::GcHeapPool;
81
82#[repr(align(128))]
85#[derive(Debug)]
86struct CachePadded<T>(T);
87
88#[derive(Copy, Clone, Debug, PartialEq, Eq)]
91pub(crate) struct ShardId(u32);
92
93impl ShardId {
94 pub(crate) fn from_index(index: usize) -> ShardId {
95 ShardId(u32::try_from(index).unwrap())
96 }
97
98 pub(crate) fn index(self) -> usize {
99 usize::try_from(self.0).unwrap()
100 }
101}
102
103pub(crate) fn default_shard_count() -> u32 {
111 let n = std::thread::available_parallelism()
112 .map(|n| n.get())
113 .unwrap_or(1)
114 .min(16);
115 u32::try_from(n).unwrap()
116}
117
118pub(crate) fn thread_shard(nshards: usize) -> ShardId {
122 static NEXT_SHARD: AtomicUsize = AtomicUsize::new(0);
123 std::thread_local! {
124 static SHARD: usize = NEXT_SHARD.fetch_add(1, Ordering::Relaxed);
125 }
126 ShardId::from_index(SHARD.with(|s| *s) % nshards)
127}
128
129pub(crate) fn shard_ids_from_home(nshards: usize) -> impl Iterator<Item = ShardId> {
132 let home = thread_shard(nshards).index();
133 (0..nshards).map(move |i| ShardId::from_index((home + i) % nshards))
134}
135
136#[cfg(feature = "async")]
137use stack_pool::StackPool;
138
139#[cfg(feature = "component-model")]
140use wasmtime_environ::{
141 StaticModuleIndex,
142 component::{Component, VMComponentOffsets},
143};
144
145fn round_up_to_pow2(n: usize, to: usize) -> usize {
146 debug_assert!(to > 0);
147 debug_assert!(to.is_power_of_two());
148 (n + to - 1) & !(to - 1)
149}
150
151impl PoolingAllocationConfig {
152 pub fn is_pagemap_scan_available() -> bool {
155 PageMap::new().is_some()
156 }
157}
158
159#[derive(Debug)]
163pub struct PoolConcurrencyLimitError {
164 limit: usize,
165 kind: Cow<'static, str>,
166}
167
168impl core::error::Error for PoolConcurrencyLimitError {}
169
170impl Display for PoolConcurrencyLimitError {
171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 let limit = self.limit;
173 let kind = &self.kind;
174 write!(f, "maximum concurrent limit of {limit} for {kind} reached")
175 }
176}
177
178impl PoolConcurrencyLimitError {
179 fn new(limit: usize, kind: impl Into<Cow<'static, str>>) -> Self {
180 Self {
181 limit,
182 kind: kind.into(),
183 }
184 }
185}
186
187#[derive(Debug)]
195pub struct PoolingInstanceAllocator {
196 live_core_instances: AtomicU64,
204 live_component_instances: AtomicU64,
205
206 decommit_queues: Box<[CachePadded<Mutex<DecommitQueue>>]>,
212
213 memories: MemoryPool,
214 live_memories: AtomicUsize,
215
216 tables: TablePool,
217 live_tables: AtomicUsize,
218
219 #[cfg(feature = "gc")]
220 gc_heaps: Option<GcHeapPool>,
221 #[cfg(feature = "gc")]
222 live_gc_heaps: AtomicUsize,
223
224 #[cfg(feature = "async")]
225 stacks: StackPool,
226 #[cfg(feature = "async")]
227 live_stacks: AtomicUsize,
228
229 pagemap: Option<PageMap>,
230 config: PoolingAllocationConfig,
231}
232
233impl Drop for PoolingInstanceAllocator {
234 fn drop(&mut self) {
235 if !cfg!(debug_assertions) {
236 return;
237 }
238
239 self.flush_all_decommit_queues();
247
248 debug_assert_eq!(self.live_component_instances.load(Ordering::Acquire), 0);
249 debug_assert_eq!(self.live_core_instances.load(Ordering::Acquire), 0);
250 debug_assert_eq!(self.live_memories.load(Ordering::Acquire), 0);
251 debug_assert_eq!(self.live_tables.load(Ordering::Acquire), 0);
252
253 debug_assert!(self.memories.is_empty());
254 debug_assert!(self.tables.is_empty());
255
256 #[cfg(feature = "gc")]
257 if let Some(gc_heaps) = &self.gc_heaps {
258 debug_assert!(gc_heaps.is_empty());
259 debug_assert_eq!(self.live_gc_heaps.load(Ordering::Acquire), 0);
260 }
261
262 #[cfg(feature = "async")]
263 {
264 debug_assert!(self.stacks.is_empty());
265 debug_assert_eq!(self.live_stacks.load(Ordering::Acquire), 0);
266 }
267 }
268}
269
270impl PoolingInstanceAllocator {
271 pub fn new(config: &PoolingAllocationConfig, tunables: &Tunables) -> Result<Self> {
273 Ok(Self {
274 live_component_instances: AtomicU64::new(0),
275 live_core_instances: AtomicU64::new(0),
276 decommit_queues: (0..default_shard_count())
277 .map(|_| CachePadded(Mutex::new(DecommitQueue::default())))
278 .try_collect::<Box<[_]>, OutOfMemory>()?,
279 memories: MemoryPool::new(config, tunables)?,
280 live_memories: AtomicUsize::new(0),
281 tables: TablePool::new(config)?,
282 live_tables: AtomicUsize::new(0),
283 #[cfg(feature = "gc")]
284 gc_heaps: if tunables.collector.is_some() {
285 Some(GcHeapPool::new(config, tunables)?)
286 } else {
287 None
288 },
289 #[cfg(feature = "gc")]
290 live_gc_heaps: AtomicUsize::new(0),
291 #[cfg(feature = "async")]
292 stacks: StackPool::new(config)?,
293 #[cfg(feature = "async")]
294 live_stacks: AtomicUsize::new(0),
295 pagemap: match config.pagemap_scan {
296 Enabled::Auto => PageMap::new(),
297 Enabled::Yes => Some(PageMap::new().ok_or_else(|| {
298 format_err!(
299 "required to enable PAGEMAP_SCAN but this system \
300 does not support it"
301 )
302 })?),
303 Enabled::No => None,
304 },
305 config: config.clone(),
306 })
307 }
308
309 fn core_instance_size(&self) -> usize {
310 round_up_to_pow2(
311 self.config.limits.core_instance_size,
312 mem::align_of::<Instance>(),
313 )
314 }
315
316 fn validate_table_plans(&self, module: &Module) -> Result<()> {
317 self.tables.validate(module)
318 }
319
320 fn validate_memory_plans(&self, module: &Module) -> Result<()> {
321 self.memories.validate_memories(module)
322 }
323
324 fn validate_core_instance_size(&self, offsets: &VMOffsets<HostPtr>) -> Result<()> {
325 let layout = Instance::alloc_layout(offsets);
326 if layout.size() <= self.core_instance_size() {
327 return Ok(());
328 }
329
330 let mut message = format!(
338 "instance allocation for this module \
339 requires {} bytes which exceeds the configured maximum \
340 of {} bytes; breakdown of allocation requirement:\n\n",
341 layout.size(),
342 self.core_instance_size(),
343 );
344
345 let mut remaining = layout.size();
346 let mut push = |name: &str, bytes: usize| {
347 assert!(remaining >= bytes);
348 remaining -= bytes;
349
350 if bytes > layout.size() / 20 {
357 message.push_str(&format!(
358 " * {:.02}% - {} bytes - {}\n",
359 ((bytes as f32) / (layout.size() as f32)) * 100.0,
360 bytes,
361 name,
362 ));
363 }
364 };
365
366 push("instance state management", mem::size_of::<Instance>());
368
369 for (desc, size) in offsets.region_sizes() {
372 push(desc, size as usize);
373 }
374
375 assert_eq!(remaining, 0);
377
378 bail!("{message}")
379 }
380
381 #[cfg(feature = "component-model")]
382 fn validate_component_instance_size(
383 &self,
384 offsets: &VMComponentOffsets<HostPtr>,
385 core_instances_aggregate_size: usize,
386 ) -> Result<()> {
387 let vmcomponentctx_size = usize::try_from(offsets.size_of_vmctx()).unwrap();
388 let total_instance_size = core_instances_aggregate_size.saturating_add(vmcomponentctx_size);
389 if total_instance_size <= self.config.limits.component_instance_size {
390 return Ok(());
391 }
392
393 bail!(
396 "instance allocation for this component requires {total_instance_size} bytes of `VMComponentContext` \
397 and aggregated core instance runtime space which exceeds the configured maximum of {} bytes. \
398 `VMComponentContext` used {vmcomponentctx_size} bytes, `core module instances` used \
399 {core_instances_aggregate_size} bytes.",
400 self.config.limits.component_instance_size
401 )
402 }
403
404 fn decommit_queue(&self, shard: ShardId) -> &Mutex<DecommitQueue> {
406 &self.decommit_queues[shard.index()].0
407 }
408
409 fn decommit_shard_ids(&self) -> impl Iterator<Item = ShardId> {
412 shard_ids_from_home(self.decommit_queues.len())
413 }
414
415 fn flush_decommit_queue(&self, mut locked_queue: MutexGuard<'_, DecommitQueue>) -> bool {
416 let queue = mem::take(&mut *locked_queue);
419 drop(locked_queue);
420 queue.flush(self)
421 }
422
423 fn flush_all_decommit_queues(&self) -> bool {
426 let mut any = false;
427 for shard in self.decommit_shard_ids() {
428 let queue = self.decommit_queue(shard).lock().unwrap();
429 any |= self.flush_decommit_queue(queue);
430 }
431 any
432 }
433
434 #[cfg(feature = "async")]
449 fn with_flush_and_retry<T>(&self, mut f: impl FnMut() -> Result<T>) -> Result<T> {
450 let mut result = f();
451 for shard in self.decommit_shard_ids() {
452 match &result {
453 Err(e) if e.is::<PoolConcurrencyLimitError>() => {}
454 _ => break,
455 }
456 let queue = self.decommit_queue(shard).lock().unwrap();
457 if self.flush_decommit_queue(queue) {
458 result = f();
459 }
460 }
461 result
462 }
463
464 fn merge_or_flush(&self, mut local_queue: DecommitQueue) {
465 match local_queue.raw_len() {
466 0 => {
471 local_queue.flush(self);
472 }
473
474 n if n >= self.config.decommit_batch_size => {
478 local_queue.flush(self);
479 }
480
481 n => {
485 debug_assert!(n < self.config.decommit_batch_size);
486 let shard = thread_shard(self.decommit_queues.len());
487 let mut shared_queue = self.decommit_queue(shard).lock().unwrap();
488 shared_queue.append(&mut local_queue);
489 if shared_queue.raw_len() >= self.config.decommit_batch_size {
492 self.flush_decommit_queue(shared_queue);
493 }
494 }
495 }
496 }
497
498 pub fn config(&self) -> &PoolingAllocationConfig {
499 &self.config
500 }
501}
502
503unsafe impl InstanceAllocator for PoolingInstanceAllocator {
504 #[cfg(feature = "component-model")]
505 fn validate_component<'a>(
506 &self,
507 component: &Component,
508 offsets: &VMComponentOffsets<HostPtr>,
509 get_module: &'a dyn Fn(StaticModuleIndex) -> &'a Module,
510 ) -> Result<()> {
511 let mut num_core_instances = 0;
512 let mut num_memories = 0;
513 let mut num_tables = 0;
514 let mut core_instances_aggregate_size: usize = 0;
515 for init in &component.initializers {
516 use wasmtime_environ::component::GlobalInitializer::*;
517 use wasmtime_environ::component::InstantiateModule;
518 match init {
519 InstantiateModule(InstantiateModule::Import(_, _), _) => {
520 num_core_instances += 1;
521 }
524 InstantiateModule(InstantiateModule::Static(static_module_index, _), _) => {
525 let module = get_module(*static_module_index);
526 let offsets = VMOffsets::new(HostPtr, &module);
527 let layout = Instance::alloc_layout(&offsets);
528 self.validate_module(module, &offsets)?;
529 num_core_instances += 1;
530 num_memories += module.num_defined_memories();
531 num_tables += module.num_defined_tables();
532 core_instances_aggregate_size += layout.size();
533 }
534 LowerImport { .. }
535 | ExtractMemory(_)
536 | ExtractTable(_)
537 | ExtractRealloc(_)
538 | ExtractCallback(_)
539 | ExtractPostReturn(_)
540 | Resource(_) => {}
541 }
542 }
543
544 if num_core_instances
545 > usize::try_from(self.config.limits.max_core_instances_per_component).unwrap()
546 {
547 bail!(
548 "The component transitively contains {num_core_instances} core module instances, \
549 which exceeds the configured maximum of {} in the pooling allocator",
550 self.config.limits.max_core_instances_per_component
551 );
552 }
553
554 if num_memories > usize::try_from(self.config.limits.max_memories_per_component).unwrap() {
555 bail!(
556 "The component transitively contains {num_memories} Wasm linear memories, which \
557 exceeds the configured maximum of {} in the pooling allocator",
558 self.config.limits.max_memories_per_component
559 );
560 }
561
562 if num_tables > usize::try_from(self.config.limits.max_tables_per_component).unwrap() {
563 bail!(
564 "The component transitively contains {num_tables} tables, which exceeds the \
565 configured maximum of {} in the pooling allocator",
566 self.config.limits.max_tables_per_component
567 );
568 }
569
570 self.validate_component_instance_size(offsets, core_instances_aggregate_size)
571 .context("component instance size does not fit in pooling allocator requirements")?;
572
573 Ok(())
574 }
575
576 fn validate_module(&self, module: &Module, offsets: &VMOffsets<HostPtr>) -> Result<()> {
577 self.validate_memory_plans(module)
578 .context("module memory does not fit in pooling allocator requirements")?;
579 self.validate_table_plans(module)
580 .context("module table does not fit in pooling allocator requirements")?;
581 self.validate_core_instance_size(offsets)
582 .context("module instance size does not fit in pooling allocator requirements")?;
583 Ok(())
584 }
585
586 #[cfg(feature = "gc")]
587 fn validate_memory(&self, memory: &wasmtime_environ::Memory) -> Result<()> {
588 self.memories.validate_memory(memory)
589 }
590
591 #[cfg(feature = "component-model")]
592 fn increment_component_instance_count(&self) -> Result<()> {
593 let old_count = self.live_component_instances.fetch_add(1, Ordering::AcqRel);
594 if old_count >= u64::from(self.config.limits.total_component_instances) {
595 self.decrement_component_instance_count();
596 return Err(PoolConcurrencyLimitError::new(
597 usize::try_from(self.config.limits.total_component_instances).unwrap(),
598 "component instances",
599 )
600 .into());
601 }
602 Ok(())
603 }
604
605 #[cfg(feature = "component-model")]
606 fn decrement_component_instance_count(&self) {
607 self.live_component_instances.fetch_sub(1, Ordering::AcqRel);
608 }
609
610 fn increment_core_instance_count(&self) -> Result<()> {
611 let old_count = self.live_core_instances.fetch_add(1, Ordering::AcqRel);
612 if old_count >= u64::from(self.config.limits.total_core_instances) {
613 self.decrement_core_instance_count();
614 return Err(PoolConcurrencyLimitError::new(
615 usize::try_from(self.config.limits.total_core_instances).unwrap(),
616 "core instances",
617 )
618 .into());
619 }
620 Ok(())
621 }
622
623 fn decrement_core_instance_count(&self) {
624 self.live_core_instances.fetch_sub(1, Ordering::AcqRel);
625 }
626
627 fn allocate_memory<'a, 'b: 'a, 'c: 'a>(
628 &'a self,
629 request: &'a mut InstanceAllocationRequest<'b, 'c>,
630 ty: &'a wasmtime_environ::Memory,
631 memory_index: Option<DefinedMemoryIndex>,
632 _memory_kind: MemoryKind,
633 ) -> Pin<Box<dyn Future<Output = Result<(MemoryAllocationIndex, Memory)>> + Send + 'a>> {
634 crate::runtime::box_future(async move {
635 async {
636 let mut e = match self.memories.allocate(request, ty, memory_index).await {
641 Ok(result) => return Ok(result),
642 Err(e) => e,
643 };
644
645 for shard in self.decommit_shard_ids() {
646 if !e.is::<PoolConcurrencyLimitError>() {
647 break;
648 }
649 let queue = self.decommit_queue(shard).lock().unwrap();
650 if self.flush_decommit_queue(queue) {
651 match self.memories.allocate(request, ty, memory_index).await {
652 Ok(result) => return Ok(result),
653 Err(err) => e = err,
654 }
655 }
656 }
657
658 Err(e)
659 }
660 .await
661 .inspect(|_| {
662 self.live_memories.fetch_add(1, Ordering::Relaxed);
663 })
664 })
665 }
666
667 unsafe fn deallocate_memory(
668 &self,
669 _memory_index: Option<DefinedMemoryIndex>,
670 allocation_index: MemoryAllocationIndex,
671 memory: Memory,
672 ) {
673 let prev = self.live_memories.fetch_sub(1, Ordering::Relaxed);
674 debug_assert!(prev > 0);
675
676 let mut image = memory.unwrap_static_image();
681 let mut queue = DecommitQueue::default();
682 let bytes_resident = image.clear_and_remain_ready(
683 self.pagemap.as_ref(),
684 self.memories.keep_resident,
685 |ptr, len| {
686 unsafe {
690 queue.push_raw(ptr, len);
691 }
692 },
693 );
694
695 match bytes_resident {
696 Ok(bytes_resident) => {
697 unsafe {
700 queue.push_memory(allocation_index, image, bytes_resident);
701 }
702 self.merge_or_flush(queue);
703 }
704 Err(e) => {
705 log::warn!("ignoring clear_and_remain_ready error {e}");
706 unsafe {
718 self.memories.deallocate(allocation_index, None, 0);
719 }
720 }
721 }
722 }
723
724 fn allocate_table<'a, 'b: 'a, 'c: 'a>(
725 &'a self,
726 request: &'a mut InstanceAllocationRequest<'b, 'c>,
727 ty: &'a wasmtime_environ::Table,
728 _table_index: DefinedTableIndex,
729 ) -> Pin<Box<dyn Future<Output = Result<(super::TableAllocationIndex, Table)>> + Send + 'a>>
730 {
731 crate::runtime::box_future(async move {
732 async {
733 let mut e = match self.tables.allocate(request, ty).await {
736 Ok(result) => return Ok(result),
737 Err(e) => e,
738 };
739
740 for shard in self.decommit_shard_ids() {
741 if !e.is::<PoolConcurrencyLimitError>() {
742 break;
743 }
744 let queue = self.decommit_queue(shard).lock().unwrap();
745 if self.flush_decommit_queue(queue) {
746 match self.tables.allocate(request, ty).await {
747 Ok(result) => return Ok(result),
748 Err(err) => e = err,
749 }
750 }
751 }
752
753 Err(e)
754 }
755 .await
756 .inspect(|_| {
757 self.live_tables.fetch_add(1, Ordering::Relaxed);
758 })
759 })
760 }
761
762 unsafe fn deallocate_table(
763 &self,
764 _table_index: DefinedTableIndex,
765 allocation_index: TableAllocationIndex,
766 mut table: Table,
767 ) {
768 let prev = self.live_tables.fetch_sub(1, Ordering::Relaxed);
769 debug_assert!(prev > 0);
770
771 let mut queue = DecommitQueue::default();
772 let bytes_resident = unsafe {
777 self.tables.reset_table_pages_to_zero(
778 self.pagemap.as_ref(),
779 allocation_index,
780 &mut table,
781 |ptr, len| {
782 queue.push_raw(ptr, len);
783 },
784 )
785 };
786
787 unsafe {
789 queue.push_table(allocation_index, table, bytes_resident);
790 }
791 self.merge_or_flush(queue);
792 }
793
794 #[cfg(feature = "async")]
795 fn allocate_fiber_stack(&self) -> Result<wasmtime_fiber::FiberStack> {
796 let ret = self.with_flush_and_retry(|| self.stacks.allocate())?;
797 self.live_stacks.fetch_add(1, Ordering::Relaxed);
798 Ok(ret)
799 }
800
801 #[cfg(feature = "async")]
802 unsafe fn deallocate_fiber_stack(&self, mut stack: wasmtime_fiber::FiberStack) {
803 self.live_stacks.fetch_sub(1, Ordering::Relaxed);
804 let mut queue = DecommitQueue::default();
805 let bytes_resident = unsafe {
809 self.stacks
810 .zero_stack(&mut stack, |ptr, len| queue.push_raw(ptr, len))
811 };
812 unsafe {
814 queue.push_stack(stack, bytes_resident);
815 }
816 self.merge_or_flush(queue);
817 }
818
819 fn purge_module(&self, module: CompiledModuleId) {
820 self.memories.purge_module(module);
821 }
822
823 fn next_available_pkey(&self) -> Option<ProtectionKey> {
824 self.memories.next_available_pkey()
825 }
826
827 fn restrict_to_pkey(&self, pkey: ProtectionKey) {
828 mpk::allow(ProtectionMask::zero().or(pkey));
829 }
830
831 fn allow_all_pkeys(&self) {
832 mpk::allow(ProtectionMask::all());
833 }
834
835 #[cfg(feature = "gc")]
836 fn allocate_gc_heap(
837 &self,
838 engine: &crate::Engine,
839 gc_runtime: &dyn GcRuntime,
840 memory_alloc_index: MemoryAllocationIndex,
841 ) -> Result<(GcHeapAllocationIndex, Box<dyn GcHeap>)> {
842 let ret =
843 self.gc_heaps
844 .as_ref()
845 .unwrap()
846 .allocate(engine, gc_runtime, memory_alloc_index)?;
847 self.live_gc_heaps.fetch_add(1, Ordering::Relaxed);
848 Ok(ret)
849 }
850
851 #[cfg(feature = "gc")]
852 fn deallocate_gc_heap(
853 &self,
854 allocation_index: GcHeapAllocationIndex,
855 gc_heap: Box<dyn GcHeap>,
856 ) -> MemoryAllocationIndex {
857 let gc_heaps = self.gc_heaps.as_ref().unwrap();
858 self.live_gc_heaps.fetch_sub(1, Ordering::Relaxed);
859 gc_heaps.deallocate(allocation_index, gc_heap)
860 }
861
862 fn as_pooling(&self) -> Option<&PoolingInstanceAllocator> {
863 Some(self)
864 }
865}
866
867#[cfg(test)]
868#[cfg(target_pointer_width = "64")]
869mod test {
870 use super::*;
871 use crate::config::InstanceLimits;
872
873 #[test]
874 fn test_pooling_allocator_with_memory_pages_exceeded() {
875 let config = PoolingAllocationConfig {
876 limits: InstanceLimits {
877 total_memories: 1,
878 max_memory_size: 0x100010000,
879 ..Default::default()
880 },
881 ..PoolingAllocationConfig::default()
882 };
883 assert_eq!(
884 PoolingInstanceAllocator::new(
885 &config,
886 &Tunables {
887 memory_reservation: 0x10000,
888 ..Tunables::default_host()
889 },
890 )
891 .map_err(|e| e.to_string())
892 .expect_err("expected a failure constructing instance allocator"),
893 "maximum memory size of 0x100010000 bytes exceeds the configured \
894 memory reservation of 0x10000 bytes"
895 );
896 }
897
898 #[cfg(all(
899 unix,
900 target_pointer_width = "64",
901 feature = "async",
902 not(miri),
903 not(asan)
904 ))]
905 #[test]
906 fn test_stack_zeroed() -> Result<()> {
907 let config = PoolingAllocationConfig {
908 max_unused_warm_slots: 0,
909 limits: InstanceLimits {
910 total_stacks: 1,
911 total_memories: 0,
912 total_tables: 0,
913 ..Default::default()
914 },
915 stack_size: 128,
916 async_stack_zeroing: true,
917 ..PoolingAllocationConfig::default()
918 };
919 let allocator = PoolingInstanceAllocator::new(&config, &Tunables::default_host())?;
920
921 unsafe {
922 for _ in 0..255 {
923 let stack = allocator.allocate_fiber_stack()?;
924
925 let addr = stack.top().unwrap().sub(1);
927
928 assert_eq!(*addr, 0);
929 *addr = 1;
930
931 allocator.deallocate_fiber_stack(stack);
932 }
933 }
934
935 Ok(())
936 }
937
938 #[cfg(all(
939 unix,
940 target_pointer_width = "64",
941 feature = "async",
942 not(miri),
943 not(asan)
944 ))]
945 #[test]
946 fn test_stack_unzeroed() -> Result<()> {
947 let config = PoolingAllocationConfig {
948 max_unused_warm_slots: 0,
949 limits: InstanceLimits {
950 total_stacks: 1,
951 total_memories: 0,
952 total_tables: 0,
953 ..Default::default()
954 },
955 stack_size: 128,
956 async_stack_zeroing: false,
957 ..PoolingAllocationConfig::default()
958 };
959 let allocator = PoolingInstanceAllocator::new(&config, &Tunables::default_host())?;
960
961 unsafe {
962 for i in 0..255 {
963 let stack = allocator.allocate_fiber_stack()?;
964
965 let addr = stack.top().unwrap().sub(1);
967
968 assert_eq!(*addr, i);
969 *addr = i + 1;
970
971 allocator.deallocate_fiber_stack(stack);
972 }
973 }
974
975 Ok(())
976 }
977}