Skip to content

Commit

Permalink
Break up allocator module more
Browse files Browse the repository at this point in the history
  • Loading branch information
udoprog committed Jun 10, 2024
1 parent 123319c commit e5f3652
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 109 deletions.
58 changes: 58 additions & 0 deletions crates/musli/src/alloc/array_buffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use core::mem::MaybeUninit;
use core::ops::{Deref, DerefMut};

use super::DEFAULT_ARRAY_BUFFER;

/// An array that can conveniently be used as a buffer, by default this is
/// [`DEFAULT_ARRAY_BUFFER`] bytes large.
///
/// This is aligned to 8 bytes, since that's an alignment which works with many
/// common Rust types.
///
/// See the [module level documentation][super] for more information.
#[repr(align(8))]
pub struct ArrayBuffer<const N: usize = DEFAULT_ARRAY_BUFFER> {
data: [MaybeUninit<u8>; N],
}

impl ArrayBuffer {
/// Construct a new buffer with the default size of
/// [`DEFAULT_ARRAY_BUFFER`].
pub const fn new() -> Self {
Self::with_size()
}
}

impl Default for ArrayBuffer {
#[inline]
fn default() -> Self {
Self::new()
}
}

impl<const N: usize> ArrayBuffer<N> {
/// Construct a new buffer with a custom size.
pub const fn with_size() -> Self {
Self {
// SAFETY: This is safe to initialize, since it's just an array of
// contiguous uninitialized memory.
data: unsafe { MaybeUninit::uninit().assume_init() },
}
}
}

impl<const N: usize> Deref for ArrayBuffer<N> {
type Target = [MaybeUninit<u8>];

#[inline]
fn deref(&self) -> &Self::Target {
&self.data
}
}

impl<const N: usize> DerefMut for ArrayBuffer<N> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.data
}
}
47 changes: 40 additions & 7 deletions crates/musli/src/alloc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! This crate contains two types of allocators:
//! * The [`System`] allocator, which uses the system allocation facilities.
//! Particularly [`std::alloc::System`].
//! * The [`Stack`] allocator, which can allocate buffers from a fixed-size
//! * The [`Slice`] allocator, which can allocate buffers from a fixed-size
//! slice.
//!
//! The following types are also provided for convenience:
Expand Down Expand Up @@ -41,6 +41,36 @@
//! });
//! ```
//!
//! Explicitly using a buffer on the stack with the [`Slice`] allocator:
//!
//! ```
//! use musli::alloc::{ArrayBuffer, Slice, Vec};
//!
//! let mut buf = ArrayBuffer::new();
//! let alloc = Slice::new(&mut buf);
//!
//! let mut a = Vec::new_in(&alloc);
//! let mut b = Vec::new_in(&alloc);
//!
//! b.write(b"He11o");
//! a.write(b.as_slice());
//!
//! assert_eq!(a.as_slice(), b"He11o");
//! assert_eq!(a.len(), 5);
//!
//! a.write(b" W0rld");
//!
//! assert_eq!(a.as_slice(), b"He11o W0rld");
//! assert_eq!(a.len(), 11);
//!
//! let mut c = Vec::new_in(&alloc);
//! c.write(b"!");
//! a.write(c.as_slice());
//!
//! assert_eq!(a.as_slice(), b"He11o W0rld!");
//! assert_eq!(a.len(), 12);
//! ```
//!
//! [Müsli]: <https://docs.rs/musli>
//! [`std::alloc::System`]: https://doc.rust-lang.org/std/alloc/struct.System.html

Expand All @@ -67,7 +97,10 @@ pub use self::disabled::Disabled;

mod stack;
#[doc(inline)]
pub use self::stack::{Stack, StackBuffer};
pub use self::stack::Slice;

mod array_buffer;
pub use self::array_buffer::ArrayBuffer;

mod string;
pub(crate) use self::string::collect_string;
Expand All @@ -80,7 +113,7 @@ pub use self::vec::Vec;

/// The default stack buffer size for the default allocator provided through
/// [`default!`].
pub const DEFAULT_STACK_BUFFER: usize = 4096;
pub const DEFAULT_ARRAY_BUFFER: usize = 4096;

#[macro_export]
#[doc(hidden)]
Expand All @@ -96,8 +129,8 @@ macro_rules! __default {
/// whether the `alloc` feature is or isn't enabled.
///
/// * If the `alloc` feature is enabled, this is the [`System`] allocator.
/// * If the `alloc` feature is disabled, this is the [`Stack`] allocator with
/// [`DEFAULT_STACK_BUFFER`] bytes allocated on the stack.
/// * If the `alloc` feature is disabled, this is the [`Slice`] allocator with
/// [`DEFAULT_ARRAY_BUFFER`] bytes allocated on the stack.
///
/// # Examples
///
Expand Down Expand Up @@ -146,8 +179,8 @@ macro_rules! __default_allocator_impl {
macro_rules! __default_allocator_impl {
(|$alloc:ident| $body:block) => {{
let mut __buf =
$crate::alloc::StackBuffer::<{ $crate::alloc::DEFAULT_STACK_BUFFER }>::new();
let $alloc = $crate::alloc::Stack::new(&mut __buf);
$crate::alloc::ArrayBuffer::<{ $crate::alloc::DEFAULT_ARRAY_BUFFER }>::new();
let $alloc = $crate::alloc::Slice::new(&mut __buf);
$body
}};
}
Expand Down
112 changes: 47 additions & 65 deletions crates/musli/src/alloc/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,56 +10,11 @@ use core::ptr;

use super::{Allocator, RawVec};

use super::DEFAULT_STACK_BUFFER;

// We keep max bytes to 2^31, since that ensures that addition between two
// magnitutes never overflow.
const MAX_BYTES: usize = i32::MAX as usize;

/// A buffer that can be used to store data on the stack.
///
/// See the [module level documentation][super] for more information.
#[repr(align(8))]
pub struct StackBuffer<const N: usize = DEFAULT_STACK_BUFFER> {
data: [MaybeUninit<u8>; N],
}

impl<const C: usize> StackBuffer<C> {
/// Construct a new buffer with the default size of
/// [`DEFAULT_STACK_BUFFER`].
pub const fn new() -> Self {
Self {
// SAFETY: This is safe to initialize, since it's just an array of
// contiguous uninitialized memory.
data: unsafe { MaybeUninit::uninit().assume_init() },
}
}
}

impl<const C: usize> Default for StackBuffer<C> {
#[inline]
fn default() -> Self {
Self::new()
}
}

impl<const C: usize> Deref for StackBuffer<C> {
type Target = [MaybeUninit<u8>];

#[inline]
fn deref(&self) -> &Self::Target {
&self.data
}
}

impl<const C: usize> DerefMut for StackBuffer<C> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.data
}
}

/// A no-std compatible fixed-memory allocator that can be used with the `musli`
/// A no-std compatible slice-based allocator that can be used with the `musli`
/// crate.
///
/// It is geared towards handling few allocations, but they can be arbitrarily
Expand All @@ -76,7 +31,37 @@ impl<const C: usize> DerefMut for StackBuffer<C> {
/// is fine for use with the `musli` crate, but might be a limitation for other
/// use-cases.
///
/// # Design
/// ## Examples
///
/// ```
/// use musli::alloc::{ArrayBuffer, Slice, Vec};
///
/// let mut buf = ArrayBuffer::new();
/// let alloc = Slice::new(&mut buf);
///
/// let mut a = Vec::new_in(&alloc);
/// let mut b = Vec::new_in(&alloc);
///
/// b.write(b"He11o");
/// a.write(b.as_slice());
///
/// assert_eq!(a.as_slice(), b"He11o");
/// assert_eq!(a.len(), 5);
///
/// a.write(b" W0rld");
///
/// assert_eq!(a.as_slice(), b"He11o W0rld");
/// assert_eq!(a.len(), 11);
///
/// let mut c = Vec::new_in(&alloc);
/// c.write(b"!");
/// a.write(c.as_slice());
///
/// assert_eq!(a.as_slice(), b"He11o W0rld!");
/// assert_eq!(a.len(), 12);
/// ```
///
/// ## Design
///
/// The allocator takes a buffer of contiguous memory. This is dynamically
/// diviced into two parts:
Expand Down Expand Up @@ -123,7 +108,7 @@ impl<const C: usize> DerefMut for StackBuffer<C> {
/// # bbbbb moved to 0
/// bbbbbbxxxxaaaaaa
/// ```
pub struct Stack<'a> {
pub struct Slice<'a> {
// This must be an unsafe cell, since it's mutably accessed through an
// immutable pointers. We simply make sure that those accesses do not
// clobber each other, which we can do since the API is restricted through
Expand All @@ -133,19 +118,14 @@ pub struct Stack<'a> {
_marker: PhantomData<&'a mut [MaybeUninit<u8>]>,
}

impl<'a> Stack<'a> {
/// Build a new no-std allocator.
///
/// The buffer must be aligned by 8 bytes, and should be a multiple of 8 bytes.
impl<'a> Slice<'a> {
/// Construct a new slice allocator.
///
/// See [type-level documentation][Stack] for more information.
/// See [type-level documentation][Slice] for more information.
///
/// # Panics
///
/// This panics if called with a buffer larger than `2^31` bytes.
///
/// An easy way to align a buffer is to use [`StackBuffer`] when
/// constructing it.
pub fn new(buffer: &'a mut [MaybeUninit<u8>]) -> Self {
let size = buffer.len();
assert!(
Expand Down Expand Up @@ -188,8 +168,8 @@ impl<'a> Stack<'a> {
}
}

impl Allocator for Stack<'_> {
type RawVec<'this, T> = StackBuf<'this, T> where Self: 'this, T: 'this;
impl Allocator for Slice<'_> {
type RawVec<'this, T> = SliceBuf<'this, T> where Self: 'this, T: 'this;

#[inline]
fn new_raw_vec<'a, T>(&'a self) -> Self::RawVec<'a, T>
Expand All @@ -208,22 +188,24 @@ impl Allocator for Stack<'_> {
}
};

StackBuf {
SliceBuf {
region,
internal: &self.internal,
_marker: PhantomData,
}
}
}

/// A no-std allocated buffer.
pub struct StackBuf<'a, T> {
/// A slice allocated buffer.
///
/// See [`Slice`].
pub struct SliceBuf<'a, T> {
region: Option<HeaderId>,
internal: &'a UnsafeCell<Internal>,
_marker: PhantomData<T>,
}

impl<'a, T> RawVec<T> for StackBuf<'a, T> {
impl<'a, T> RawVec<T> for SliceBuf<'a, T> {
#[inline]
fn resize(&mut self, len: usize, additional: usize) -> bool {
if additional == 0 || size_of::<T>() == 0 {
Expand Down Expand Up @@ -352,7 +334,7 @@ impl<'a, T> RawVec<T> for StackBuf<'a, T> {
}
}

impl<T> Drop for StackBuf<'_, T> {
impl<T> Drop for SliceBuf<'_, T> {
fn drop(&mut self) {
if let Some(region) = self.region.take() {
// SAFETY: We have exclusive access to the internal state.
Expand Down Expand Up @@ -424,7 +406,7 @@ struct Range {
}

impl Range {
fn new(range: std::ops::Range<*mut MaybeUninit<u8>>) -> Self {
fn new(range: core::ops::Range<*mut MaybeUninit<u8>>) -> Self {
Self {
start: range.start,
end: range.end,
Expand Down Expand Up @@ -538,7 +520,7 @@ impl Internal {

/// Free a region.
unsafe fn free_region(&mut self, region: Region) -> Header {
self.unlink(&*region);
self.unlink(&region);

region.ptr.replace(Header {
range: self.full.head(),
Expand Down
Loading

0 comments on commit e5f3652

Please sign in to comment.