1 #![cfg_attr(
2     all(unix, not(miri), not(asan)),
3     expect(dead_code, reason = "not used, but typechecked")
4 )]
5 
6 use crate::PoolConcurrencyLimitError;
7 use crate::prelude::*;
8 use crate::runtime::vm::PoolingInstanceAllocatorConfig;
9 use std::sync::atomic::{AtomicU64, Ordering};
10 
11 /// A generic implementation of a stack pool.
12 ///
13 /// This implementation technically doesn't actually pool anything at this time.
14 /// Originally this was the implementation for non-Unix (e.g. Windows and
15 /// MIRI), but nowadays this is also used for fuzzing. For more documentation
16 /// for why this is used on fuzzing see the `asan` module in the
17 /// `wasmtime-fiber` crate.
18 ///
19 /// Currently the only purpose of `StackPool` is to limit the total number of
20 /// concurrent stacks while otherwise leveraging `wasmtime_fiber::FiberStack`
21 /// natively.
22 #[derive(Debug)]
23 pub struct StackPool {
24     stack_size: usize,
25     stack_zeroing: bool,
26     live_stacks: AtomicU64,
27     stack_limit: u64,
28 }
29 
30 impl StackPool {
31     #[cfg(test)]
enabled() -> bool32     pub fn enabled() -> bool {
33         false
34     }
35 
new(config: &PoolingInstanceAllocatorConfig) -> Result<Self>36     pub fn new(config: &PoolingInstanceAllocatorConfig) -> Result<Self> {
37         Ok(StackPool {
38             stack_size: config.stack_size,
39             stack_zeroing: config.async_stack_zeroing,
40             live_stacks: AtomicU64::new(0),
41             stack_limit: config.limits.total_stacks.into(),
42         })
43     }
44 
is_empty(&self) -> bool45     pub fn is_empty(&self) -> bool {
46         self.live_stacks.load(Ordering::Acquire) == 0
47     }
48 
allocate(&self) -> Result<wasmtime_fiber::FiberStack>49     pub fn allocate(&self) -> Result<wasmtime_fiber::FiberStack> {
50         if self.stack_size == 0 {
51             bail!("fiber stack allocation not supported")
52         }
53 
54         let old_count = self.live_stacks.fetch_add(1, Ordering::AcqRel);
55         if old_count >= self.stack_limit {
56             self.live_stacks.fetch_sub(1, Ordering::AcqRel);
57             return Err(PoolConcurrencyLimitError::new(
58                 usize::try_from(self.stack_limit).unwrap(),
59                 "fibers",
60             )
61             .into());
62         }
63 
64         match wasmtime_fiber::FiberStack::new(self.stack_size, self.stack_zeroing) {
65             Ok(stack) => Ok(stack),
66             Err(e) => {
67                 self.live_stacks.fetch_sub(1, Ordering::AcqRel);
68 
69                 #[allow(
70                     clippy::useless_conversion,
71                     reason = "some `cfg`s have `wasmtime::Error` as the error type, others don't"
72                 )]
73                 let e = crate::Error::from(e);
74 
75                 Err(e)
76             }
77         }
78     }
79 
zero_stack( &self, _stack: &mut wasmtime_fiber::FiberStack, _decommit: impl FnMut(*mut u8, usize), ) -> usize80     pub unsafe fn zero_stack(
81         &self,
82         _stack: &mut wasmtime_fiber::FiberStack,
83         _decommit: impl FnMut(*mut u8, usize),
84     ) -> usize {
85         // No need to actually zero the stack, since the stack won't ever be
86         // reused on non-unix systems.
87         0
88     }
89 
90     /// Safety: see the unix implementation.
deallocate(&self, stack: wasmtime_fiber::FiberStack, _bytes_resident: usize)91     pub unsafe fn deallocate(&self, stack: wasmtime_fiber::FiberStack, _bytes_resident: usize) {
92         self.live_stacks.fetch_sub(1, Ordering::AcqRel);
93         // A no-op as we don't actually own the fiber stack on Windows.
94         let _ = stack;
95     }
96 
unused_warm_slots(&self) -> u3297     pub fn unused_warm_slots(&self) -> u32 {
98         0
99     }
100 
unused_bytes_resident(&self) -> Option<usize>101     pub fn unused_bytes_resident(&self) -> Option<usize> {
102         None
103     }
104 }
105