From 4a4acb27fac0ab3559ae9b822c3a8bc42a25cc4d Mon Sep 17 00:00:00 2001 From: James Munns Date: Wed, 6 Dec 2023 13:34:07 +0100 Subject: [PATCH] Improvements from discussions --- Cargo.toml | 16 ++++- src/alloc_single.rs | 166 ++++++++++++++++++++++++++++++++++++++++++++ src/const_init.rs | 50 +++++++++++++ src/lib.rs | 6 +- src/uninit.rs | 57 ++++++++++++--- 5 files changed, 282 insertions(+), 13 deletions(-) create mode 100644 src/alloc_single.rs create mode 100644 src/const_init.rs diff --git a/Cargo.toml b/Cargo.toml index 51a66ba..1917449 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grounded" -version = "0.1.1" +version = "0.2.0" authors = ["James Munns "] 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"] diff --git a/src/alloc_single.rs b/src/alloc_single.rs new file mode 100644 index 0000000..a9c690b --- /dev/null +++ b/src/alloc_single.rs @@ -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 { + taken: AtomicBool, + storage: GroundedCell, +} + +impl AllocSingle { + /// 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> { + // 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 AllocSingle { + /// 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> { + // 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, +} + +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 = 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()); + } +} diff --git a/src/const_init.rs b/src/const_init.rs new file mode 100644 index 0000000..8d8a2f6 --- /dev/null +++ b/src/const_init.rs @@ -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 ConstInit for [T; N] +where + T: ConstInit, +{ + const VAL: Self = [T::VAL; N]; +} + +impl ConstInit for Option { + const VAL: Self = None; +} diff --git a/src/lib.rs b/src/lib.rs index 23105ee..1ced464 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/uninit.rs b/src/uninit.rs index 9482b13..68965bf 100644 --- a/src/uninit.rs +++ b/src/uninit.rs @@ -3,6 +3,8 @@ use core::{cell::UnsafeCell, mem::MaybeUninit}; +use crate::const_init::ConstInit; + /// ## GroundedCell /// /// [GroundedCell] is a type that contains a single `T`. The contained T is wrapped @@ -27,6 +29,22 @@ pub struct GroundedCell { unsafe impl Sync for GroundedCell {} +impl GroundedCell { + /// Create a new GroundedCell with the cell initialized with + /// the value of [ConstInit::VAL]. + /// + /// ```rust + /// use grounded::uninit::GroundedCell; + /// + /// static EXAMPLE: GroundedCell<[u8; 1024]> = GroundedCell::const_init(); + /// ``` + pub const fn const_init() -> Self { + Self { + inner: UnsafeCell::new(MaybeUninit::new(T::VAL)), + } + } +} + impl GroundedCell { /// Create an uninitialized `GroundedCell`. /// @@ -55,7 +73,7 @@ impl GroundedCell { /// let ptr: *mut u32 = EXAMPLE.get(); /// assert_ne!(core::ptr::null_mut(), ptr); /// ``` - pub fn get(&'static self) -> *mut T { + pub fn get(&self) -> *mut T { let mu_ptr: *mut MaybeUninit = self.inner.get(); let t_ptr: *mut T = mu_ptr.cast::(); t_ptr @@ -86,6 +104,26 @@ pub struct GroundedArrayCell { unsafe impl Sync for GroundedArrayCell {} +impl GroundedArrayCell { + /// Create a new GroundedArrayCell with all cells initialized with + /// the value of [ConstInit::VAL]. + /// + /// If your type's implementation of [ConstInit] happens to be all zeroes, like it + /// is for many integer and boolean primitives, it is likely your static will end + /// up in `.bss`. + /// + /// ```rust + /// use grounded::uninit::GroundedArrayCell; + /// + /// static EXAMPLE: GroundedArrayCell = GroundedArrayCell::const_init(); + /// ``` + pub const fn const_init() -> Self { + Self { + inner: UnsafeCell::new(MaybeUninit::new(<[T; N] as ConstInit>::VAL)), + } + } +} + impl GroundedArrayCell { /// Create an uninitialized `GroundedArrayCell`. /// @@ -107,7 +145,7 @@ impl GroundedArrayCell { /// The caller must ensure that no other access is made to the data contained within this /// cell for the duration of this function #[inline] - pub unsafe fn initialize_all_copied(&'static self, val: T) + pub unsafe fn initialize_all_copied(&self, val: T) where T: Copy, { @@ -126,8 +164,7 @@ impl GroundedArrayCell { /// The caller must ensure that no other access is made to the data contained within this /// cell for the duration of this function #[inline] - pub unsafe fn initialize_all_with T>(&'static self, mut f: F) - { + pub unsafe fn initialize_all_with T>(&self, mut f: F) { let (mut ptr, len) = self.get_ptr_len(); let end = ptr.add(len); while ptr != end { @@ -151,7 +188,7 @@ impl GroundedArrayCell { /// assert_ne!(core::ptr::null_mut(), ptr); /// ``` #[inline] - pub fn as_mut_ptr(&'static self) -> *mut T { + pub fn as_mut_ptr(&self) -> *mut T { let mu_ptr: *mut MaybeUninit<[T; N]> = self.inner.get(); let arr_ptr: *mut [T; N] = mu_ptr.cast::<[T; N]>(); let t_ptr: *mut T = arr_ptr.cast::(); @@ -189,7 +226,7 @@ impl GroundedArrayCell { /// * [Self::get_subslice_unchecked()] /// * [Self::get_subslice_mut_unchecked()] #[inline] - pub fn get_ptr_len(&'static self) -> (*mut T, usize) { + pub fn get_ptr_len(&self) -> (*mut T, usize) { (self.as_mut_ptr(), N) } @@ -209,7 +246,7 @@ impl GroundedArrayCell { /// while the reference is live /// * `offset` is < N #[inline] - pub unsafe fn get_element_unchecked(&'static self, offset: usize) -> &'static T { + pub unsafe fn get_element_unchecked(&self, offset: usize) -> &'_ T { &*self.as_mut_ptr().add(offset) } @@ -231,7 +268,7 @@ impl GroundedArrayCell { /// * `offset` is < N #[allow(clippy::mut_from_ref)] #[inline] - pub unsafe fn get_element_mut_unchecked(&'static self, offset: usize) -> &'static mut T { + pub unsafe fn get_element_mut_unchecked(&self, offset: usize) -> &mut T { &mut *self.as_mut_ptr().add(offset) } @@ -252,7 +289,7 @@ impl GroundedArrayCell { /// while the slice is live /// * `offset` and `offset + len` are <= N #[inline] - pub unsafe fn get_subslice_unchecked(&'static self, offset: usize, len: usize) -> &'static [T] { + pub unsafe fn get_subslice_unchecked(&self, offset: usize, len: usize) -> &'_ [T] { core::slice::from_raw_parts(self.as_mut_ptr().add(offset), len) } @@ -274,7 +311,7 @@ impl GroundedArrayCell { /// * `offset` and `offset + len` are <= N #[allow(clippy::mut_from_ref)] #[inline] - pub unsafe fn get_subslice_mut_unchecked(&'static self, offset: usize, len: usize) -> &'static mut [T] { + pub unsafe fn get_subslice_mut_unchecked(&self, offset: usize, len: usize) -> &'_ mut [T] { core::slice::from_raw_parts_mut(self.as_mut_ptr().add(offset), len) } }