Skip to content

Commit a4f33b4

Browse files
committed
add Arc pointer backed by a memory pool
1 parent 05c21c4 commit a4f33b4

File tree

3 files changed

+356
-0
lines changed

3 files changed

+356
-0
lines changed

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
//!
4444
//! List of currently implemented data structures:
4545
//!
46+
//! - [`Arc`](pool/singleton/arc/struct.Arc.html) -- Thread-safe reference-counting pointer backed by a memory pool
4647
//! - [`BinaryHeap`](binary_heap/struct.BinaryHeap.html) -- priority queue
4748
//! - [`IndexMap`](struct.IndexMap.html) -- hash table
4849
//! - [`IndexSet`](struct.IndexSet.html) -- hash set
@@ -79,6 +80,8 @@ pub use histbuf::HistoryBuffer;
7980
pub use indexmap::{Bucket, FnvIndexMap, IndexMap, Pos};
8081
pub use indexset::{FnvIndexSet, IndexSet};
8182
pub use linear_map::LinearMap;
83+
#[cfg(all(has_cas, feature = "cas"))]
84+
pub use pool::singleton::arc::Arc;
8285
pub use string::String;
8386
pub use vec::Vec;
8487

src/pool/singleton.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ use core::{
1212

1313
use super::{Init, Node, Uninit};
1414

15+
pub mod arc;
16+
1517
/// Instantiates a pool as a global singleton
1618
// NOTE(any(test)) makes testing easier (no need to enable Cargo features for testing)
1719
#[cfg(any(

src/pool/singleton/arc.rs

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
//! Like [`std::sync::Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) but backed by a
2+
//! memory [`Pool`](trait.Pool.html) rather than `#[global_allocator]`
3+
//!
4+
//! Note that the same limitations that apply to ["Box" pool] also apply to the "Arc" pool.
5+
//!
6+
//! ["Box" pool]: ../../index.html
7+
//!
8+
//! # Examples
9+
//!
10+
//! ``` ignore
11+
//! use heapless::{arc_pool, Arc};
12+
//!
13+
//! pub struct BigStruct { // <- does NOT implement Clone
14+
//! data: [u8; 128],
15+
//! // ..
16+
//! }
17+
//!
18+
//! // declare a memory pool
19+
//! arc_pool!(P: BigStruct);
20+
//!
21+
//!
22+
//! #[cortex_m_rt::entry]
23+
//! fn main() -> ! {
24+
//! static mut MEMORY: [u8; 1024] = [0; 1024];
25+
//!
26+
//! // give some static memory to the pool
27+
//! P::grow(MEMORY);
28+
//!
29+
//! let x: Arc<P> = P::alloc(BigStruct::new()).ok().expect("OOM");
30+
//! // ^ NOTE: this is the Pool type, not the data type
31+
//!
32+
//! // cloning is cheap; it increases the refcount
33+
//! let y = x.clone();
34+
//!
35+
//! // same data address
36+
//! assert_eq!(&*x as *const _, &*y as *const _);
37+
//!
38+
//! // auto-deref
39+
//! let data: &[u8] = &x.data;
40+
//!
41+
//! // decrease refcount
42+
//! drop(x);
43+
//!
44+
//! // refcount decreased to 0; memory is returned to the pool
45+
//! drop(y);
46+
//!
47+
//! // ..
48+
//! }
49+
//! ```
50+
51+
use core::{
52+
cmp, fmt,
53+
hash::{Hash, Hasher},
54+
marker::PhantomData,
55+
ops::Deref,
56+
ptr,
57+
sync::atomic::{self, AtomicUsize, Ordering},
58+
};
59+
60+
use crate::pool::{self, stack::Ptr, Node};
61+
62+
/// Instantiates a pool of Arc pointers as a global singleton
63+
// NOTE(any(test)) makes testing easier (no need to enable Cargo features for testing)
64+
#[cfg(any(
65+
armv7a,
66+
armv7r,
67+
armv7m,
68+
armv8m_main,
69+
all(
70+
any(target_arch = "x86_64", target_arch = "x86"),
71+
feature = "x86-sync-pool"
72+
),
73+
test
74+
))]
75+
#[macro_export]
76+
macro_rules! arc_pool {
77+
($(#[$($attr:tt)*])* $ident:ident: $ty:ty) => {
78+
pub struct $ident;
79+
80+
impl $crate::pool::singleton::arc::Pool for $ident {
81+
type Data = $ty;
82+
83+
fn ptr() -> &'static $crate::pool::Pool<$crate::pool::singleton::arc::ArcInner<$ty>> {
84+
$(#[$($attr)*])*
85+
static POOL: $crate::pool::Pool<$crate::pool::singleton::arc::ArcInner<$ty>> =
86+
$crate::pool::Pool::new();
87+
88+
&POOL
89+
}
90+
}
91+
92+
impl $ident {
93+
/// Allocates a new `Arc` and writes `data` to it
94+
///
95+
/// Returns an `Err`or if the backing memory pool is empty
96+
pub fn alloc(data: $ty) -> Result<$crate::Arc<Self>, $ty>
97+
where
98+
Self: Sized,
99+
{
100+
$crate::Arc::new(data)
101+
}
102+
103+
/// Increases the capacity of the pool
104+
///
105+
/// This method might *not* fully utilize the given memory block due to alignment requirements
106+
///
107+
/// This method returns the number of *new* blocks that can be allocated.
108+
pub fn grow(memory: &'static mut [u8]) -> usize {
109+
<Self as $crate::pool::singleton::arc::Pool>::ptr().grow(memory)
110+
}
111+
}
112+
};
113+
}
114+
115+
/// Pool of Arc pointers
116+
pub trait Pool {
117+
/// The data behind the Arc pointer
118+
type Data: 'static;
119+
120+
#[doc(hidden)]
121+
fn ptr() -> &'static pool::Pool<ArcInner<Self::Data>>;
122+
}
123+
124+
// mostly a verbatim copy of liballoc(/src/sync.rs) as of v1.54.0 minus the `Weak` API
125+
// anything that diverges has been marked with `XXX`
126+
127+
/// `std::sync::Arc` but backed by a memory [`Pool`] rather than `#[global_allocator]`
128+
///
129+
/// [`Pool`]: trait.Pool.html
130+
///
131+
/// An example and more details can be found in the [module level documentation](index.html).
132+
// XXX `Pool::Data` is not `?Sized` -- `Unsize` coercions cannot be implemented on stable
133+
pub struct Arc<P>
134+
where
135+
P: Pool,
136+
{
137+
phantom: PhantomData<ArcInner<P::Data>>,
138+
ptr: Ptr<Node<ArcInner<P::Data>>>,
139+
pool: PhantomData<P>,
140+
}
141+
142+
impl<P> Arc<P>
143+
where
144+
P: Pool,
145+
{
146+
/// Constructs a new `Arc`
147+
///
148+
/// Returns an `Err`or if the backing memory pool is empty
149+
// XXX original API is "infallible"
150+
pub fn new(data: P::Data) -> Result<Self, P::Data> {
151+
if let Some(node) = P::ptr().stack.try_pop() {
152+
unsafe {
153+
ptr::write(
154+
node.as_ref().data.get(),
155+
ArcInner {
156+
strong: AtomicUsize::new(1),
157+
data,
158+
},
159+
)
160+
}
161+
162+
Ok(Self {
163+
phantom: PhantomData,
164+
pool: PhantomData,
165+
ptr: node,
166+
})
167+
} else {
168+
Err(data)
169+
}
170+
}
171+
172+
fn inner(&self) -> &ArcInner<P::Data> {
173+
unsafe { &*self.ptr.as_ref().data.get() }
174+
}
175+
176+
fn from_inner(ptr: Ptr<Node<ArcInner<P::Data>>>) -> Self {
177+
Self {
178+
phantom: PhantomData,
179+
pool: PhantomData,
180+
ptr,
181+
}
182+
}
183+
184+
unsafe fn get_mut_unchecked(this: &mut Self) -> &mut P::Data {
185+
&mut (*this.ptr.as_ref().data.get()).data
186+
// &mut (*this.ptr.as_ptr()).data
187+
}
188+
189+
#[inline(never)]
190+
unsafe fn drop_slow(&mut self) {
191+
// run `P::Data`'s destructor
192+
ptr::drop_in_place(Self::get_mut_unchecked(self));
193+
194+
// XXX memory pool instead of `#[global_allocator]`
195+
// return memory to pool
196+
P::ptr().stack.push(self.ptr);
197+
}
198+
}
199+
200+
const MAX_REFCOUNT: usize = (isize::MAX) as usize;
201+
202+
impl<P> AsRef<P::Data> for Arc<P>
203+
where
204+
P: Pool,
205+
{
206+
fn as_ref(&self) -> &P::Data {
207+
&**self
208+
}
209+
}
210+
211+
// XXX no `Borrow` implementation due to 'conflicting implementations of trait' error
212+
213+
impl<P> Clone for Arc<P>
214+
where
215+
P: Pool,
216+
{
217+
fn clone(&self) -> Self {
218+
let old_size = self.inner().strong.fetch_add(1, Ordering::Relaxed);
219+
220+
if old_size > MAX_REFCOUNT {
221+
// XXX original code calls `intrinsics::abort` which is unstable API
222+
panic!();
223+
}
224+
225+
Self::from_inner(self.ptr)
226+
}
227+
}
228+
229+
impl<P> fmt::Debug for Arc<P>
230+
where
231+
P: Pool,
232+
P::Data: fmt::Debug,
233+
{
234+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235+
fmt::Debug::fmt(&**self, f)
236+
}
237+
}
238+
239+
impl<P> Deref for Arc<P>
240+
where
241+
P: Pool,
242+
{
243+
type Target = P::Data;
244+
245+
fn deref(&self) -> &P::Data {
246+
&self.inner().data
247+
}
248+
}
249+
250+
impl<P> fmt::Display for Arc<P>
251+
where
252+
P: Pool,
253+
P::Data: fmt::Display,
254+
{
255+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256+
fmt::Display::fmt(&**self, f)
257+
}
258+
}
259+
260+
// XXX original uses `#[may_dangle]` which is an unstable language feature
261+
impl<P> Drop for Arc<P>
262+
where
263+
P: Pool,
264+
{
265+
fn drop(&mut self) {
266+
if self.inner().strong.fetch_sub(1, Ordering::Release) != 1 {
267+
return;
268+
}
269+
270+
atomic::fence(Ordering::Acquire);
271+
272+
unsafe {
273+
self.drop_slow();
274+
}
275+
}
276+
}
277+
278+
impl<P> Eq for Arc<P>
279+
where
280+
P: Pool,
281+
P::Data: Eq,
282+
{
283+
}
284+
285+
impl<P> Hash for Arc<P>
286+
where
287+
P: Pool,
288+
P::Data: Hash,
289+
{
290+
fn hash<H>(&self, state: &mut H)
291+
where
292+
H: Hasher,
293+
{
294+
(**self).hash(state)
295+
}
296+
}
297+
298+
impl<P> Ord for Arc<P>
299+
where
300+
P: Pool,
301+
P::Data: Ord,
302+
{
303+
fn cmp(&self, other: &Self) -> cmp::Ordering {
304+
(**self).cmp(&**other)
305+
}
306+
}
307+
308+
impl<P> PartialEq for Arc<P>
309+
where
310+
P: Pool,
311+
P::Data: PartialEq,
312+
{
313+
fn eq(&self, other: &Self) -> bool {
314+
// XXX missing pointer equality specialization, which uses an unstable language feature
315+
(**self).eq(&**other)
316+
}
317+
}
318+
319+
impl<P> PartialOrd for Arc<P>
320+
where
321+
P: Pool,
322+
P::Data: PartialOrd,
323+
{
324+
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
325+
(**self).partial_cmp(&**other)
326+
}
327+
}
328+
329+
unsafe impl<P> Send for Arc<P>
330+
where
331+
P: Pool,
332+
P::Data: Sync + Send,
333+
{
334+
}
335+
336+
unsafe impl<P> Sync for Arc<P>
337+
where
338+
P: Pool,
339+
P::Data: Sync + Send,
340+
{
341+
}
342+
343+
impl<P> Unpin for Arc<P> where P: Pool {}
344+
345+
#[doc(hidden)]
346+
pub struct ArcInner<T> {
347+
data: T,
348+
strong: AtomicUsize,
349+
// XXX `Weak` API not implemented
350+
// weak: AtomicUsize,
351+
}

0 commit comments

Comments
 (0)