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