Skip to content

Commit

Permalink
Improvements from discussions
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesmunns committed Dec 6, 2023
1 parent 18ee87d commit 4a4acb2
Show file tree
Hide file tree
Showing 5 changed files with 282 additions and 13 deletions.
16 changes: 14 additions & 2 deletions Cargo.toml
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"
Expand All @@ -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"]
166 changes: 166 additions & 0 deletions src/alloc_single.rs
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());
}
}
50 changes: 50 additions & 0 deletions src/const_init.rs
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;
}
6 changes: 5 additions & 1 deletion src/lib.rs
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;
57 changes: 47 additions & 10 deletions src/uninit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -27,6 +29,22 @@ pub struct GroundedCell<T> {

unsafe impl<T: Sync> Sync for GroundedCell<T> {}

impl<T: ConstInit> GroundedCell<T> {
/// 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<T> GroundedCell<T> {
/// Create an uninitialized `GroundedCell`.
///
Expand Down Expand Up @@ -55,7 +73,7 @@ impl<T> GroundedCell<T> {
/// 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<T> = self.inner.get();
let t_ptr: *mut T = mu_ptr.cast::<T>();
t_ptr
Expand Down Expand Up @@ -86,6 +104,26 @@ pub struct GroundedArrayCell<T, const N: usize> {

unsafe impl<T: Sync, const N: usize> Sync for GroundedArrayCell<T, N> {}

impl<T: ConstInit, const N: usize> GroundedArrayCell<T, N> {
/// 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<u8, 1024> = GroundedArrayCell::const_init();
/// ```
pub const fn const_init() -> Self {
Self {
inner: UnsafeCell::new(MaybeUninit::new(<[T; N] as ConstInit>::VAL)),
}
}
}

impl<T, const N: usize> GroundedArrayCell<T, N> {
/// Create an uninitialized `GroundedArrayCell`.
///
Expand All @@ -107,7 +145,7 @@ impl<T, const N: usize> GroundedArrayCell<T, N> {
/// 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,
{
Expand All @@ -126,8 +164,7 @@ impl<T, const N: usize> GroundedArrayCell<T, N> {
/// 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<F: FnMut() -> T>(&'static self, mut f: F)
{
pub unsafe fn initialize_all_with<F: FnMut() -> T>(&self, mut f: F) {
let (mut ptr, len) = self.get_ptr_len();
let end = ptr.add(len);
while ptr != end {
Expand All @@ -151,7 +188,7 @@ impl<T, const N: usize> GroundedArrayCell<T, N> {
/// 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::<T>();
Expand Down Expand Up @@ -189,7 +226,7 @@ impl<T, const N: usize> GroundedArrayCell<T, N> {
/// * [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)
}

Expand All @@ -209,7 +246,7 @@ impl<T, const N: usize> GroundedArrayCell<T, N> {
/// 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)
}

Expand All @@ -231,7 +268,7 @@ impl<T, const N: usize> GroundedArrayCell<T, N> {
/// * `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)
}

Expand All @@ -252,7 +289,7 @@ impl<T, const N: usize> GroundedArrayCell<T, N> {
/// 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)
}

Expand All @@ -274,7 +311,7 @@ impl<T, const N: usize> GroundedArrayCell<T, N> {
/// * `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)
}
}

0 comments on commit 4a4acb2

Please sign in to comment.