-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
18ee87d
commit 4a4acb2
Showing
5 changed files
with
282 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
[package] | ||
name = "grounded" | ||
version = "0.1.1" | ||
version = "0.2.0" | ||
authors = ["James Munns <[email protected]>"] | ||
edition = "2021" | ||
readme = "README.md" | ||
|
@@ -9,5 +9,17 @@ description = "A toolkit for managing unsafe statics" | |
license = "MIT OR Apache-2.0" | ||
documentation = "https://docs.rs/grounded/" | ||
|
||
[dependencies.portable-atomic] | ||
version = "1.3" | ||
default-features = false | ||
|
||
[dependencies] | ||
[features] | ||
default = [] | ||
# components that require compare-and-swap operations | ||
cas = ["portable-atomic/require-cas"] | ||
# Allow for use on non-atomic systems by use of critical-sections | ||
critical-section = ["portable-atomic/critical-section"] | ||
|
||
[package.metadata.docs.rs] | ||
features = ["cas"] | ||
rustdoc-args = ["--cfg", "doc_cfg"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
//! Utilities for allocating a single item, using a box-like smart pointer | ||
use core::{ | ||
ops::{Deref, DerefMut}, | ||
sync::atomic::Ordering, | ||
}; | ||
use portable_atomic::AtomicBool; | ||
|
||
use crate::{const_init::ConstInit, uninit::GroundedCell}; | ||
|
||
/// AllocSingle is our one-element allocator pool | ||
/// | ||
/// If your type implements [ConstInit], consider using | ||
/// [AllocSingle::alloc_const_val] instead of [AllocSingle::alloc] | ||
/// to avoid unnecessary stack usage. | ||
/// | ||
/// This does require use of CAS atomics. You must enable the `cas` | ||
/// feature, and if your target does not have native atomic CAS, you | ||
/// must also enable the `critical-section` feature. | ||
/// | ||
/// ```rust | ||
/// use grounded::alloc_single::AllocSingle; | ||
/// | ||
/// static SINGLE: AllocSingle<[u8; 256]> = AllocSingle::new(); | ||
/// | ||
/// // alloc a single item | ||
/// let mut s1 = SINGLE.alloc([4; 256]).unwrap(); | ||
/// s1.iter().for_each(|b| assert_eq!(*b, 4)); | ||
/// | ||
/// // we can't alloc while `s1` is still live | ||
/// assert!(SINGLE.alloc([5; 256]).is_none()); | ||
/// | ||
/// // now drop it | ||
/// drop(s1); | ||
/// | ||
/// // and we can alloc again | ||
/// let mut s2 = SINGLE.alloc([7; 256]).unwrap(); | ||
/// s2.iter().for_each(|b| assert_eq!(*b, 7)); | ||
/// ``` | ||
pub struct AllocSingle<T> { | ||
taken: AtomicBool, | ||
storage: GroundedCell<T>, | ||
} | ||
|
||
impl<T> AllocSingle<T> { | ||
/// Create a new, uninitalized, single-element allocation pool | ||
pub const fn new() -> Self { | ||
Self { | ||
taken: AtomicBool::new(false), | ||
storage: GroundedCell::uninit(), | ||
} | ||
} | ||
|
||
/// Attempts to allocate a single item. Returns None and | ||
/// discards `t` if an allocation is already live. | ||
#[inline] | ||
pub fn alloc(&self, t: T) -> Option<SingleBox<'_, T>> { | ||
// Set taken, and if it was already taken before, we can't | ||
// allocate | ||
if self.taken.swap(true, Ordering::AcqRel) { | ||
// already taken | ||
return None; | ||
} | ||
let new = SingleBox { single: self }; | ||
// Initialize by moving t into the storage | ||
unsafe { | ||
new.as_ptr().write(t); | ||
} | ||
Some(new) | ||
} | ||
} | ||
|
||
impl<T: ConstInit> AllocSingle<T> { | ||
/// Attempts to allocate a single item, using `ConstInit::VAL` as | ||
/// the initializer. Returns None if the item is already allocated | ||
pub fn alloc_const_val(&self) -> Option<SingleBox<'_, T>> { | ||
// Set taken, and if it was already taken before, we can't | ||
// allocate | ||
if self.taken.swap(true, Ordering::AcqRel) { | ||
// already taken | ||
return None; | ||
} | ||
let new = SingleBox { single: self }; | ||
// Initialize by writing t into the storage | ||
unsafe { | ||
new.as_ptr().write(T::VAL); | ||
} | ||
Some(new) | ||
} | ||
} | ||
|
||
pub struct SingleBox<'a, T> { | ||
single: &'a AllocSingle<T>, | ||
} | ||
|
||
impl<'a, T> SingleBox<'a, T> { | ||
fn as_ptr(&self) -> *mut T { | ||
self.single.storage.get() | ||
} | ||
} | ||
|
||
impl<'a, T> Drop for SingleBox<'a, T> { | ||
fn drop(&mut self) { | ||
// When we drop the SingleBox, mark the AllocSingle as available again | ||
unsafe { self.as_ptr().drop_in_place() } | ||
self.single.taken.store(false, Ordering::Release); | ||
} | ||
} | ||
|
||
impl<'a, T> Deref for SingleBox<'a, T> { | ||
type Target = T; | ||
|
||
fn deref(&self) -> &Self::Target { | ||
unsafe { &*self.as_ptr() } | ||
} | ||
} | ||
|
||
impl<'a, T> DerefMut for SingleBox<'a, T> { | ||
fn deref_mut(&mut self) -> &mut Self::Target { | ||
unsafe { &mut *self.as_ptr() } | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
pub mod test { | ||
use super::AllocSingle; | ||
use crate::const_init::ConstInit; | ||
use core::ops::Deref; | ||
|
||
#[derive(Debug)] | ||
struct Demo([u8; 512]); | ||
|
||
impl ConstInit for Demo { | ||
const VAL: Self = Demo([44u8; 512]); | ||
} | ||
|
||
#[test] | ||
fn smoke() { | ||
static SINGLE: AllocSingle<[u8; 1024]> = AllocSingle::new(); | ||
static SINGLE_DEMO: AllocSingle<Demo> = AllocSingle::new(); | ||
|
||
{ | ||
let buf = [0xAF; 1024]; | ||
let mut bx = SINGLE.alloc(buf).unwrap(); | ||
println!("{:?}", bx.as_slice()); | ||
bx.iter_mut().for_each(|b| *b = 123); | ||
println!("{:?}", bx.as_slice()); | ||
|
||
// Second alloc fails | ||
let buf2 = [0x01; 1024]; | ||
assert!(SINGLE.alloc(buf2).is_none()); | ||
} | ||
|
||
// bx is dropped because we left scope, which means we can | ||
// alloc again | ||
let buf3 = [0x42; 1024]; | ||
let mut bx2 = SINGLE.alloc(buf3).unwrap(); | ||
println!("{:?}", bx2.as_slice()); | ||
bx2.iter_mut().for_each(|b| *b = 231); | ||
println!("{:?}", bx2.as_slice()); | ||
|
||
// look ma no stack | ||
let bx3 = SINGLE_DEMO.alloc_const_val().unwrap(); | ||
println!("{:?}", bx3.deref()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
//! Const Init | ||
//! | ||
//! A trait that is like `Default`, but const | ||
/// A trait that is like `Default`, but const | ||
pub trait ConstInit { | ||
/// The constant default value | ||
const VAL: Self; | ||
} | ||
|
||
// Here's some impls that roughly match the default | ||
// value of these types | ||
|
||
macro_rules! impl_const_init_for { | ||
($(($tyname:ty, $val:expr),)+) => { | ||
$( | ||
impl ConstInit for $tyname { | ||
const VAL: Self = $val; | ||
} | ||
)+ | ||
}; | ||
} | ||
|
||
impl_const_init_for! { | ||
(u8, 0), | ||
(u16, 0), | ||
(u32, 0), | ||
(u64, 0), | ||
(u128, 0), | ||
(i8, 0), | ||
(i16, 0), | ||
(i32, 0), | ||
(i64, 0), | ||
(i128, 0), | ||
(f32, 0.0), | ||
(f64, 0.0), | ||
(bool, false), | ||
((), ()), | ||
} | ||
|
||
impl<T, const N: usize> ConstInit for [T; N] | ||
where | ||
T: ConstInit, | ||
{ | ||
const VAL: Self = [T::VAL; N]; | ||
} | ||
|
||
impl<T> ConstInit for Option<T> { | ||
const VAL: Self = None; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,8 @@ | ||
#![no_std] | ||
#![cfg_attr(not(test), no_std)] | ||
#![doc = include_str!("../README.md")] | ||
|
||
pub mod const_init; | ||
pub mod uninit; | ||
|
||
#[cfg(feature = "cas")] | ||
pub mod alloc_single; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters