From 424a6f6b1af89d435bb97327c3871d028d755855 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 17 May 2018 23:23:51 +0200 Subject: [PATCH] WIP ObjectPool --- Cargo.toml | 4 + src/__core.rs | 4 + src/lib.rs | 25 ++- src/object_pool.rs | 520 +++++++++++++++++++++++++++++++++++++++++++++ src/vec.rs | 20 +- 5 files changed, 550 insertions(+), 23 deletions(-) create mode 100644 src/object_pool.rs diff --git a/Cargo.toml b/Cargo.toml index b3a4e142..6df5e808 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,7 @@ scoped_threadpool = "0.1.8" [dependencies] generic-array = "0.11.0" hash32 = "0.1.0" + +[dependencies.stable_deref_trait] +default-features = false +version = "1.0.0" diff --git a/src/__core.rs b/src/__core.rs index 671255f4..ef3c0a7c 100644 --- a/src/__core.rs +++ b/src/__core.rs @@ -43,3 +43,7 @@ pub mod mem { U { none: () }.some } } + +pub mod ops { + pub use core::ops::{Deref, DerefMut}; +} diff --git a/src/lib.rs b/src/lib.rs index feb14abe..25016bf5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,13 +39,13 @@ //! //! List of currently implemented data structures: //! -//! - [`BinaryHeap`](binary_heap/struct.BinaryHeap.html) -- priority queue -//! - [`IndexMap`](struct.IndexMap.html) -- hash table -//! - [`IndexSet`](struct.IndexSet.html) -- hash set -//! - [`LinearMap`](struct.LinearMap.html) -//! - [`RingBuffer`](ring_buffer/struct.RingBuffer.html) -- single producer single consumer lockless -//! queue -//! - [`String`](struct.String.html) +//! - [`BinaryHeap`] -- priority queue +//! - [`IndexMap`] -- hash table +//! - [`IndexSet`] -- hash set +//! - [`LinearMap`] +//! - [`ObjectPool`] -- an object / memory pool +//! - [`RingBuffer`] -- single producer single consumer lockless queue +//! - [`String`] //! - [`Vec`](struct.Vec.html) #![allow(warnings)] @@ -59,16 +59,20 @@ extern crate generic_array; extern crate hash32; +extern crate stable_deref_trait; #[cfg(test)] extern crate std; pub use binary_heap::BinaryHeap; pub use generic_array::typenum::consts; -pub use generic_array::ArrayLength; +pub use generic_array::{ArrayLength, GenericArray}; pub use indexmap::{FnvIndexMap, IndexMap}; pub use indexset::{FnvIndexSet, IndexSet}; pub use linear_map::LinearMap; +pub use object_pool::ObjectPool; pub use ring_buffer::RingBuffer; +#[doc(hidden)] +pub use stable_deref_trait::StableDeref; pub use string::String; pub use vec::Vec; @@ -79,7 +83,8 @@ mod linear_map; mod string; mod vec; +#[doc(hidden)] +pub mod __core; pub mod binary_heap; +pub mod object_pool; pub mod ring_buffer; - -mod __core; diff --git a/src/object_pool.rs b/src/object_pool.rs new file mode 100644 index 00000000..3687e3be --- /dev/null +++ b/src/object_pool.rs @@ -0,0 +1,520 @@ +//! Object / memory pool + +use core::any::TypeId; +use core::marker::PhantomData; +use core::{mem, ops, ptr}; + +use generic_array::{ArrayLength, GenericArray}; +use stable_deref_trait::StableDeref; + +/// Creates a singleton type that acts as a proxy for an uninitialized `static mut` variable +/// +/// This type will provide an unsafe `new` constructor that can be used to create an instance of the +/// type. The caller must ensure that this constructor is called only *once* during the whole +/// lifetime of the program to avoid mutable aliasing. +/// +/// # Example +/// +/// ``` +/// #[macro_use(singleton)] +/// extern crate heapless; +/// +/// fn main() { +/// singleton!(A: i32); +/// +/// let mut a = unsafe { A::new() }.init(1); +/// +/// assert_eq!(*a, 1); +/// +/// *a = 2; +/// +/// let b: &'static mut i32 = a.into(); +/// +/// assert_eq!(*b, 2); +/// } +/// ``` +#[macro_export] +macro_rules! singleton { + ($Name:ident : $Type:ty) => { + pub struct $Name { + _0: $crate::object_pool::Private, + } + + unsafe impl $crate::object_pool::Singleton for $Name { + type Data = $Type; + + unsafe fn _var() -> &'static mut Self::Data { + static mut VAR: $Type = unsafe { $crate::__core::mem::uninitialized() }; + + &mut VAR + } + } + + impl $Name { + unsafe fn new() -> $crate::object_pool::Uninit { + $crate::object_pool::Uninit::new($Name { + _0: $crate::object_pool::Private::new(), + }) + } + } + + impl AsRef<$Type> for $Name { + fn as_ref(&self) -> &$Type { + use $crate::object_pool::Singleton; + + unsafe { $Name::_var() } + } + } + + impl AsMut<$Type> for $Name { + fn as_mut(&mut self) -> &mut $Type { + use $crate::object_pool::Singleton; + + unsafe { $Name::_var() } + } + } + + impl $crate::__core::ops::Deref for $Name { + type Target = $Type; + + fn deref(&self) -> &$Type { + self.as_ref() + } + } + + unsafe impl $crate::StableDeref for $Name {} + + impl $crate::__core::ops::DerefMut for $Name { + fn deref_mut(&mut self) -> &mut $Type { + self.as_mut() + } + } + + impl Into<&'static mut $Type> for $Name { + fn into(self) -> &'static mut $Type { + use $crate::object_pool::Singleton; + + unsafe { $Name::_var() } + } + } + }; +} + +#[doc(hidden)] +pub struct Private { + _0: (), +} + +impl Private { + #[doc(hidden)] + pub unsafe fn new() -> Self { + Private { _0: () } + } +} + +/// Uninitialized newtype +pub struct Uninit { + data: S, +} + +impl Uninit { + /// Wraps `data` in `Uninit` to indicate that it's uninitialized + pub unsafe fn new(data: S) -> Self { + Uninit { data } + } + + /// Initializes the data to the given `value` + pub fn init(self, value: S::Data) -> S + where + S: Singleton, + { + unsafe { + ptr::write(S::_var(), value); + self.data + } + } + + /// Leaves the data uninitialized + pub fn noinit(self) -> S + where + S: Singleton, + S::Data: Copy, + { + self.data + } +} + +/// [Type state] Uninitialized object +pub enum Uninitialized {} + +/// [Type state] Initialized object +pub enum Initialized {} + +/// An object that belongs to `POOL` +pub struct Object { + index: u8, + _pool: PhantomData, + _state: PhantomData, +} + +impl Object { + fn get(&self) -> *mut T + where + P: Singleton>, + N: ArrayLength + 'static, + T: 'static, + { + unsafe { P::_var().get_unchecked_mut(usize::from(self.index)) } + } +} + +impl

Object { + /// Initializes the object with the given `value` + pub fn init(self, value: T) -> Object + where + P: Singleton>, + N: ArrayLength + 'static, + T: 'static, + { + unsafe { + ptr::write(self.get(), value); + + Object { + index: self.index, + _pool: PhantomData, + _state: PhantomData, + } + } + } + + /// Leaves the object uninitialized + pub fn noinit(self) -> Object + where + P: Singleton, + P::Data: Copy, + { + Object { + index: self.index, + _pool: PhantomData, + _state: PhantomData, + } + } +} + +impl ops::Deref for Object +where + P: Singleton>, + N: ArrayLength + 'static, + T: 'static, +{ + type Target = T; + + fn deref(&self) -> &T { + // XXX `self.get` doesn't work here for some reason (inference error) + unsafe { P::_var().get_unchecked(usize::from(self.index)) } + } +} + +unsafe impl StableDeref for Object +where + P: Singleton>, + N: ArrayLength + 'static, + T: 'static, +{ +} + +impl AsRef for Object +where + P: Singleton>, + N: ArrayLength + 'static, + T: AsRef + 'static, + U: ?Sized, +{ + fn as_ref(&self) -> &U { + (**self).as_ref() + } +} + +impl AsMut for Object +where + P: Singleton>, + N: ArrayLength + 'static, + T: AsMut + 'static, + U: ?Sized, +{ + fn as_mut(&mut self) -> &mut U { + (**self).as_mut() + } +} + +impl ops::DerefMut for Object +where + P: Singleton>, + N: ArrayLength + 'static, + T: 'static, +{ + fn deref_mut(&mut self) -> &mut T { + // XXX `self.get` doesn't work here for some reason (inference error) + unsafe { P::_var().get_unchecked_mut(usize::from(self.index)) } + } +} + +/// An unsafe marker trait for singleton types that act as proxies for a `static mut` variable +/// +/// A type that implements this trait must also implement the following traits: +/// +/// - `AsMut` +/// - `AsRef` +/// - `Deref` +/// - `DerefMut` +/// - `Into<&'static mut Self::Data>` +/// - `StableDeref` +pub unsafe trait Singleton { + /// The data stored in the `static mut` variable + type Data: 'static; + + #[doc(hidden)] + unsafe fn _var() -> &'static mut Self::Data; +} + +/// A pool of objects +pub struct ObjectPool

{ + _memory: PhantomData

, + free: u8, + head: u8, + initialized: u8, +} + +impl

ObjectPool

{ + /// Creates a new object pool from the given uninitialized memory + /// + /// # Panics + /// + /// This constructor panics if `P` is an array of zero sized values + pub fn new(_memory: Uninit

) -> Self + where + P: Singleton>, + N: ArrayLength + 'static, + T: 'static, + { + assert!(mem::size_of::() >= 1); + + ObjectPool { + free: N::to_u8(), + head: 0, + initialized: 0, + _memory: PhantomData, + } + } + + /// Gets an object from the pool, or returns `None` if the pool is currently empty + pub fn get(&mut self) -> Option> + where + P: Singleton>, + N: ArrayLength + 'static, + T: 'static, + { + unsafe { + let cap = N::to_u8(); + + if self.initialized < cap { + let idx = self.initialized; + *(P::_var().get_unchecked_mut(idx as usize) as *mut _ as *mut u8) = idx + 1; + self.initialized += 1; + } + + if self.free != 0 { + let new_head = + *(P::_var().get_unchecked(usize::from(self.head)) as *const _ as *const u8); + let index = self.head; + self.head = new_head; + + self.free -= 1; + + Some(Object { + index, + _pool: PhantomData, + _state: PhantomData, + }) + } else { + None + } + } + } + + /// Returns an object to the pool + /// + /// The `object` destructor will be called, if the object was initialized + pub fn free(&mut self, object: Object) + where + P: Singleton>, + N: ArrayLength + 'static, + T: 'static, + S: 'static, + { + if TypeId::of::() == TypeId::of::() { + unsafe { + ptr::drop_in_place(object.get()); + } + } + + unsafe { *(object.get() as *mut u8) = self.head }; + + self.free += 1; + self.head = object.index; + } +} + +#[cfg(test)] +mod tests { + use generic_array::typenum::consts::*; + use generic_array::GenericArray; + + use super::{ObjectPool, Singleton}; + + #[test] + fn sanity() { + singleton!(B: GenericArray); + + let mut pool: ObjectPool = ObjectPool::new(unsafe { B::new() }); + + let _0 = pool.get().unwrap(); + assert_eq!(_0.index, 0); + assert_eq!(pool.head, 1); + assert_eq!(pool.free, 3); + assert_eq!(pool.initialized, 1); + + let _1 = pool.get().unwrap(); + assert_eq!(_1.index, 1); + assert_eq!(pool.head, 2); + assert_eq!(pool.free, 2); + assert_eq!(pool.initialized, 2); + + let _2 = pool.get().unwrap(); + assert_eq!(_2.index, 2); + assert_eq!(pool.head, 3); + assert_eq!(pool.free, 1); + assert_eq!(pool.initialized, 3); + + pool.free(_0); + assert_eq!(pool.head, 0); + assert_eq!(pool.free, 2); + assert_eq!(pool.initialized, 3); + assert_eq!(unsafe { (*B::_var())[0] }, 3); + + pool.free(_2); + assert_eq!(pool.head, 2); + assert_eq!(pool.free, 3); + assert_eq!(pool.initialized, 3); + assert_eq!(unsafe { (*B::_var())[2] }, 0); + + let _2 = pool.get().unwrap(); + assert_eq!(_2.index, 2); + assert_eq!(pool.head, 0); + assert_eq!(pool.free, 2); + assert_eq!(pool.initialized, 4); + assert_eq!(unsafe { (*B::_var())[3] }, 4); + } + + // test that deallocated values are dropped + #[test] + fn destructor() { + static mut COUNT: usize = 0; + + pub struct A(u32); + + impl A { + fn new() -> Self { + unsafe { COUNT += 1 } + A(0) + } + } + + impl Drop for A { + fn drop(&mut self) { + unsafe { COUNT -= 1 } + } + } + + singleton!(B: GenericArray); + + { + let mut pool = ObjectPool::new(unsafe { B::new() }); + + let _0 = pool.get().unwrap().init(A::new()); + assert_eq!(unsafe { COUNT }, 1); + + pool.free(_0); + assert_eq!(unsafe { COUNT }, 0); + } + + assert_eq!(unsafe { COUNT }, 0); + } + + // test that values not explicitly deallocated are leaked + #[test] + fn leak() { + static mut COUNT: usize = 0; + + pub struct A(u32); + + impl A { + fn new() -> Self { + unsafe { COUNT += 1 } + A(0) + } + } + + impl Drop for A { + fn drop(&mut self) { + unsafe { COUNT -= 1 } + } + } + + singleton!(B: GenericArray); + + { + let mut pool = ObjectPool::new(unsafe { B::new() }); + + let _0 = pool.get().unwrap().init(A::new()); + assert_eq!(unsafe { COUNT }, 1); + + drop(_0); + + assert_eq!(unsafe { COUNT }, 1); + + let _1 = pool.get().unwrap().init(A::new()); + assert_eq!(unsafe { COUNT }, 2); + + drop(_1); + + assert_eq!(unsafe { COUNT }, 2); + } + + assert_eq!(unsafe { COUNT }, 2); + } + + // test that exhausting the pool and then deallocating works correctly + #[test] + fn empty() { + singleton!(B: GenericArray); + + let mut pool = ObjectPool::new(unsafe { B::new() }); + + let _0 = pool.get().unwrap().init(-1); + let _1 = pool.get().unwrap().init(-1); + let _2 = pool.get().unwrap().init(-1); + let _3 = pool.get().unwrap().init(-1); + + assert!(pool.get().is_none()); + + pool.free(_0); + pool.free(_2); + + let _2 = pool.get().unwrap().init(-1); + assert_eq!(_2.index, 2); + + let _0 = pool.get().unwrap().init(-1); + assert_eq!(_0.index, 0); + } +} diff --git a/src/vec.rs b/src/vec.rs index dbff46cc..9101fac9 100644 --- a/src/vec.rs +++ b/src/vec.rs @@ -312,7 +312,6 @@ where } } - impl FromIterator for Vec where N: ArrayLength, @@ -343,7 +342,7 @@ where next: usize, } -impl Iterator for IntoIter +impl Iterator for IntoIter where N: ArrayLength, { @@ -351,7 +350,7 @@ where fn next(&mut self) -> Option { if self.next < self.vec.len() { let buffer = self.vec.buffer.as_slice(); - let item = unsafe {ptr::read(buffer.get_unchecked(self.next))}; + let item = unsafe { ptr::read(buffer.get_unchecked(self.next)) }; self.next += 1; Some(item) } else { @@ -360,7 +359,7 @@ where } } -impl Drop for IntoIter +impl Drop for IntoIter where N: ArrayLength, { @@ -374,7 +373,7 @@ where } } -impl IntoIterator for Vec +impl IntoIterator for Vec where N: ArrayLength, { @@ -382,10 +381,7 @@ where type IntoIter = IntoIter; fn into_iter(self) -> Self::IntoIter { - IntoIter { - vec: self, - next: 0, - } + IntoIter { vec: self, next: 0 } } } @@ -513,7 +509,7 @@ mod tests { use Vec; macro_rules! droppable { - () => ( + () => { struct Droppable; impl Droppable { fn new() -> Self { @@ -532,12 +528,11 @@ mod tests { } static mut COUNT: i32 = 0; - ) + }; } #[test] fn drop() { - droppable!(); { @@ -652,7 +647,6 @@ mod tests { #[test] fn iter_move_drop() { - droppable!(); {