//! `Pool` as a global singleton use core::{ any::TypeId, cmp, fmt, hash::{Hash, Hasher}, marker::PhantomData, mem::{self, MaybeUninit}, ops::{Deref, DerefMut}, ptr, }; use as_slice::{AsMutSlice, AsSlice}; use super::{Init, Node, Uninit}; /// Instantiates a pool as a global singleton #[cfg(any(armv7m, armv7r, armv8m_main, test))] #[macro_export] macro_rules! pool { ($(#[$($attr:tt)*])* $ident:ident: $ty:ty) => { pub struct $ident; impl $crate::pool::singleton::Pool for $ident { type Data = $ty; fn ptr() -> &'static $crate::pool::Pool<$ty> { $(#[$($attr)*])* static $ident: $crate::pool::Pool<$ty> = $crate::pool::Pool::new(); &$ident } } }; } /// A global singleton memory pool pub trait Pool { /// The type of data that can be allocated on this pool type Data: 'static; #[doc(hidden)] fn ptr() -> &'static super::Pool; /// Claims a memory block from the pool /// /// Returns `None` when the pool is observed as exhausted /// /// *NOTE:* This method does *not* have bounded execution time; i.e. it contains a CAS loop fn alloc() -> Option> where Self: Sized, { Self::ptr().alloc().map(|inner| Box { _pool: PhantomData, inner, }) } /// Increases the capacity of the pool /// /// This method might *not* fully utilize the given memory block due to alignment requirements /// /// This method returns the number of *new* blocks that can be allocated. fn grow(memory: &'static mut [u8]) -> usize { Self::ptr().grow(memory) } /// Increases the capacity of the pool /// /// Unlike [`Pool.grow`](trait.Pool.html#method.grow_exact) this method fully utilizes the given /// memory block fn grow_exact(memory: &'static mut MaybeUninit) -> usize where A: AsMutSlice>, { Self::ptr().grow_exact(memory) } } /// A memory block that belongs to the global memory pool, `POOL` pub struct Box where POOL: Pool, STATE: 'static, { _pool: PhantomData, inner: super::Box, } impl

Box where P: Pool, { /// Initializes this memory block pub fn init(self, val: P::Data) -> Box { let node = self.inner.node; mem::forget(self); unsafe { ptr::write(node.as_ref().data.get(), val); } Box { inner: super::Box { node, _state: PhantomData, }, _pool: PhantomData, } } } impl

Box where P: Pool, P::Data: AsSlice, { /// Freezes the contents of this memory block /// /// See [rust-lang/rust#58363](https://github.com/rust-lang/rust/pull/58363) for details. pub fn freeze(self) -> Box { let node = self.inner.node; mem::forget(self); // it seems we can get away with not calling `ptr::freeze` here and not run into UB // because we are dealing with static memory and using fences // let p: *const u8 = (*node.as_ref().data.get()).as_slice().as_ptr(); // ptr::freeze(p as *mut u8); Box { inner: super::Box { node, _state: PhantomData, }, _pool: PhantomData, } } } impl

Deref for Box

where P: Pool, { type Target = P::Data; fn deref(&self) -> &P::Data { self.inner.deref() } } impl

DerefMut for Box

where P: Pool, { fn deref_mut(&mut self) -> &mut P::Data { self.inner.deref_mut() } } impl

fmt::Debug for Box

where P: Pool, P::Data: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ::fmt(self, f) } } impl

fmt::Display for Box

where P: Pool, P::Data: fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ::fmt(self, f) } } impl Drop for Box where P: Pool, S: 'static, { fn drop(&mut self) { if TypeId::of::() == TypeId::of::() { unsafe { ptr::drop_in_place(self.inner.node.as_ref().data.get()); } } P::ptr().push(self.inner.node) } } unsafe impl Send for Box where P: Pool, P::Data: Send, { } unsafe impl Sync for Box where P: Pool, P::Data: Sync, { } impl AsSlice for Box

where P: Pool, P::Data: AsSlice, { type Element = T; fn as_slice(&self) -> &[T] { self.deref().as_slice() } } impl AsMutSlice for Box

where P: Pool, P::Data: AsMutSlice, { fn as_mut_slice(&mut self) -> &mut [T] { self.deref_mut().as_mut_slice() } } impl

PartialEq for Box

where P: Pool, P::Data: PartialEq, { fn eq(&self, rhs: &Box

) -> bool { ::eq(self, rhs) } } impl

Eq for Box

where P: Pool, P::Data: Eq, { } impl

PartialOrd for Box

where P: Pool, P::Data: PartialOrd, { fn partial_cmp(&self, rhs: &Box

) -> Option { ::partial_cmp(self, rhs) } } impl

Ord for Box

where P: Pool, P::Data: Ord, { fn cmp(&self, rhs: &Box

) -> cmp::Ordering { ::cmp(self, rhs) } } impl

Hash for Box

where P: Pool, P::Data: Hash, { fn hash(&self, state: &mut H) where H: Hasher, { ::hash(self, state) } } #[cfg(test)] mod tests { use core::{ mem, sync::atomic::{AtomicUsize, Ordering}, }; use super::Pool; #[test] fn sanity() { static mut MEMORY: [u8; 31] = [0; 31]; pool!(A: u8); // empty pool assert!(A::alloc().is_none()); A::grow(unsafe { &mut MEMORY }); let x = A::alloc().unwrap().init(0); assert_eq!(*x, 0); // pool exhausted assert!(A::alloc().is_none()); drop(x); // should be possible to allocate again assert_eq!(*A::alloc().unwrap().init(1), 1); } #[test] fn destructors() { static COUNT: AtomicUsize = AtomicUsize::new(0); pub struct X; impl X { fn new() -> X { COUNT.fetch_add(1, Ordering::Relaxed); X } } impl Drop for X { fn drop(&mut self) { COUNT.fetch_sub(1, Ordering::Relaxed); } } pool!(A: X); static mut MEMORY: [u8; 23] = [0; 23]; A::grow(unsafe { &mut MEMORY }); let x = A::alloc().unwrap().init(X::new()); let y = A::alloc().unwrap().init(X::new()); assert_eq!(COUNT.load(Ordering::Relaxed), 2); // this runs `X`'s destructor drop(x); assert_eq!(COUNT.load(Ordering::Relaxed), 1); // this leaks memory mem::forget(y); assert_eq!(COUNT.load(Ordering::Relaxed), 1); } }