diff --git a/Cargo.toml b/Cargo.toml index 4c02093..8ee1345 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "alloc-traits", "exit-stack", "fill", + "same-alloc", "static-alloc", "unsize", "without-alloc", diff --git a/same-alloc/Cargo.toml b/same-alloc/Cargo.toml new file mode 100644 index 0000000..a877894 --- /dev/null +++ b/same-alloc/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "same-alloc" +version = "1.0.0-beta" +description = "Reuse an allocated buffer for data with different types." +authors = ["Andreas Molzer "] +edition = "2021" +license = "MIT OR Apache-2.0 OR Zlib" +documentation = "https://docs.rs/same-alloc" +repository = "https://github.com/HeroicKatora/static-alloc" +readme = "Readme.md" +categories = ["data-structures", "embedded", "memory-management", "no-std"] + +[dependencies] diff --git a/same-alloc/Readme.md b/same-alloc/Readme.md new file mode 100644 index 0000000..7013e78 --- /dev/null +++ b/same-alloc/Readme.md @@ -0,0 +1,35 @@ +The `SameVec` makes it possible to re-use allocations across multiple +invocations of zero-copy parsers. + +This crate provides an allocated buffer that can be used by vectors of +different element types, as long as they have the same layout. Most prominently +this allows use of one buffer where the element type depends on a function +local lifetime. The required vector type would be impossible to name outside +the function. + +```rust +fn select_median_name(unparsed: &str) -> &str { + // Problem: This type depends on the lifetime parameter. Ergo, we can not normally store _one_ + // vector in the surrounding function, and instead need to allocate here a new one. + let mut names: Vec<_> = unparsed.split(' ').collect(); + let idx = names.len() / 2; + *names.select_nth_unstable(idx).1 +} + +fn select_median_name_with_buffer<'names>( + unparsed: &'names str, + buf: &mut SameVec<*const str>, +) -> &'names str { + let mut names = buf.use_for(same::for_ref()); + names.extend(unparsed.split(' ')); + let idx = names.len() / 2; + *names.select_nth_unstable(idx).1 +} +``` + +# License + +This project is licensed under Zlib OR Apache-2.0 OR MIT. You may alternatively +choose [the Unlicense](http://unlicense.org/) instead in which case the +copyright headers signify the parts dedicated to the public domain to the +fullest possible extent instead. diff --git a/same-alloc/src/boxed.rs b/same-alloc/src/boxed.rs new file mode 100644 index 0000000..ae86acc --- /dev/null +++ b/same-alloc/src/boxed.rs @@ -0,0 +1,62 @@ +use core::mem::MaybeUninit; +use alloc::boxed::Box; +use crate::same::SameLayout; + +/// An allocated buffer for types with the same layout. +pub struct SameBox { + element_buffer: Box>, +} + +pub struct TempBox<'lt, U> { + from: &'lt mut dyn DynBufferWith, + boxed: Option>, +} + +/// A compatible wrapper around a `Vec`, meant to be used as wrapping a mutable pointer to one. +/// Here we capture that `SameLayout` is inhabited without any indirection layer. This allows +/// us to erase the type parameter of the original vector and swap it for a different one. +#[repr(transparent)] +struct Wrap { + elements: Box>, + marker: SameLayout, +} + +/// Type-erase way for Vec with elements layout compatible to `U`. +trait DynBufferWith { + fn swap_internal_with(&mut self, _: &mut Option>); +} + +impl Default for SameBox { + fn default() -> Self { + SameBox { element_buffer: Box::new(MaybeUninit::uninit()) } + } +} + +impl Drop for TempBox<'_, T> { + fn drop(&mut self) { + self.from.swap_internal_with(&mut self.boxed); + } +} + +impl core::ops::Deref for TempBox<'_, T> { + type Target = Box; + + fn deref(&self) -> &Box { + self.boxed.as_ref().unwrap() + } +} + +impl core::ops::DerefMut for TempBox<'_, T> { + fn deref_mut(&mut self) -> &mut Box { + self.boxed.as_mut().unwrap() + } +} + +impl DynBufferWith for Wrap { + fn swap_internal_with(&mut self, v: &mut Option>) { + let temp = core::mem::take(v).unwrap(); + let (v, mut temp) = self.marker.transpose().deinit_box(temp); + drop(v); + core::mem::swap(&mut temp, &mut self.elements); + } +} diff --git a/same-alloc/src/lib.rs b/same-alloc/src/lib.rs new file mode 100644 index 0000000..ff10322 --- /dev/null +++ b/same-alloc/src/lib.rs @@ -0,0 +1,40 @@ +//! The `SameVec` makes it possible to re-use allocations across multiple invocations of +//! zero-copy parsers. +//! +//! This crate provides an allocated buffer that can be used by vectors of +//! different element types, as long as they have the same layout. Most prominently +//! this allows use of one buffer where the element type depends on a function +//! local lifetime. The required vector type would be impossible to name outside +//! the function. +//! +//! # Example +//! +//! ```rust +//! # use same_alloc::{same, VecBuffer}; +//! +//! fn select_median_name(unparsed: &str) -> &str { +//! // Problem: This type depends on the lifetime parameter. Ergo, we can not normally store +//! // _one_vector in the surrounding function, and instead need to allocate here a new one. +//! let mut names: Vec<_> = unparsed.split(' ').collect(); +//! let idx = names.len() / 2; +//! *names.select_nth_unstable(idx).1 +//! } +//! +//! fn select_median_name_with_buffer<'names>( +//! unparsed: &'names str, +//! buf: &mut VecBuffer<*const str>, +//! ) -> &'names str { +//! let mut names = buf.use_for(same::for_ref()); +//! names.extend(unparsed.split(' ')); +//! let idx = names.len() / 2; +//! *names.select_nth_unstable(idx).1 +//! } +//! ``` +#![no_std] +extern crate alloc; + +pub mod same; +pub mod vec; + +pub use vec::{VecBuffer, TempVec}; +pub type SameVec = VecBuffer; diff --git a/same-alloc/src/same.rs b/same-alloc/src/same.rs new file mode 100644 index 0000000..daab378 --- /dev/null +++ b/same-alloc/src/same.rs @@ -0,0 +1,150 @@ +//! Contains a proof type, demonstrating the layout equality of two types. +//! +//! This is relevant to allocated containers as well as other reuse of raw memory since layout +//! equality guarantees certain types of soundness. For example, the memory allocated for a +//! `Box` can be reused for storing a type `B` exactly if those two types have the same layout. +//! +//! The module defines a number of helpers (`for_*`) that _guarantee_ construction. Also note that +//! most of the methods are usable in `const` contexts. Albeit, in practice you might need to use a +//! generic lifetime parameter in one of your proofs but this is not possible in constants. +//! Instead, wait for `const {}` blocks to stabilize. +use core::alloc::Layout; +use core::mem::MaybeUninit; +use core::marker::PhantomData; +use core::ptr::NonNull; + +use alloc::boxed::Box; +use alloc::vec::Vec; + +/// A proof type, showing two types `A` and `B` have the **same** layout. +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct SameLayout(PhantomData<(A, B)>); + +impl SameLayout { + pub const fn new() -> Option { + let layout_a = Layout::new::(); + let layout_b = Layout::new::(); + // Direct comparison requires `ops::Eq` which obviously is NOT yet const fn. + // Also this is exactly what we required for allocators, as documented there. + if layout_a.size() == layout_b.size() && layout_a.align() == layout_b.align() { + Some(SameLayout(PhantomData)) + } else { + None + } + } + + /// 'Transmute' a vector by reusing its buffer. + /// NOTE: This will _forget_ all elements. You must clear the vector first if they are + /// important, or `set_len` on the result if you can guarantee that old elements are valid + /// initializers for the new type. + /// This affords more flexibility for the caller as they might want to use As as an initializer + /// for Bs which would be invalid if we dropped them. Manually drain the vector if this is not + /// desirable. + pub fn forget_vec(self, vec: Vec) -> Vec { + let mut vec = core::mem::ManuallyDrop::new(vec); + let cap = vec.capacity(); + let ptr = vec.as_mut_ptr(); + // SAFETY: + // - ptr was previously allocated with Vec. + // - B has the same alignment and size as per our invariants. + // - 0 is less than or equal to capacity. + // - capacity is the capacity the Vec was allocated with. + // - All elements (there are none) as initialized. + unsafe { Vec::from_raw_parts(ptr as *mut B, 0, cap) } + } + + /// 'Transmute' a box by reusing its buffer. + /// NOTE: for the same flexibility as Vec, forget about the returned `A`. + pub fn deinit_box(self, boxed: Box) -> (A, Box>) { + let ptr = Box::into_raw(boxed); + // SAFETY: just was a valid box.. + let a = unsafe { core::ptr::read(ptr) }; + // SAFETY: + // - ptr was previously allocated with Box. + // - The ptr is valid for reads and writes as it comes from a Box. + // - B has the same alignment and size as per our invariants. + // - Any instance of MaybeUninit is always valid. + (a, unsafe { Box::from_raw(ptr as *mut MaybeUninit) }) + } +} + +impl Clone for SameLayout { + fn clone(&self) -> Self { + SameLayout(self.0) + } +} + +impl Copy for SameLayout {} + +impl SameLayout { + pub const fn array(self) -> SameLayout<[A; N], [B; N]> { + SameLayout(PhantomData) + } + + /// Apply a transitive argument to construct a new relation proof. + pub const fn chain(self, _: SameLayout) -> SameLayout { + SameLayout(PhantomData) + } + + /// Use commutativity of equality. + pub const fn transpose(self) -> SameLayout { + SameLayout(PhantomData) + } +} + +/// A proof that any type has the same layout as itself. +pub const fn id() -> SameLayout { + SameLayout(PhantomData) +} + +/// A proof that any reference has same layout as a raw pointer. +pub const fn for_ref<'a, A: ?Sized>() -> SameLayout<*const A, &'a A> { + SameLayout(PhantomData) +} + +/// A proof that any mutable reference has same layout as a raw pointer. +/// FIXME: this is not const because of the very narrow formulation of https://github.com/rust-lang/rust/issues/57349 +pub fn for_mut<'a, A: ?Sized>() -> SameLayout<*const A, &'a mut A> { + SameLayout(PhantomData) +} + +/// A proof that any option wrapped reference has same layout as an pure reference. +pub const fn for_ref_opt<'a, A: ?Sized>() -> SameLayout<&'a A, Option<&'a A>> { + SameLayout(PhantomData) +} + +/// A proof that any option wrapped mutable reference has same layout as an pure reference. +/// FIXME: this is not const because of the very narrow formulation of https://github.com/rust-lang/rust/issues/57349 +pub fn for_mut_opt<'a, A: ?Sized>() -> SameLayout<&'a mut A, Option<&'a mut A>> { + SameLayout(PhantomData) +} + +/// A proof that sized pointers have same layout as any other sized pointer. +pub const fn for_sized_ptr() -> SameLayout<*const A, *const B> { + SameLayout(PhantomData) +} + +/// A proof that mutable pointer has the same layout as a const pointer. +pub const fn for_ptr_mut() -> SameLayout<*const A, *mut A> { + SameLayout(PhantomData) +} + +/// A proof that a non-null pointer has the same layout as a raw pointer. +pub const fn for_non_null() -> SameLayout<*const A, NonNull> { + SameLayout(PhantomData) +} + +/// A proof that an option of a non-null pointer has the same layout as a raw pointer. +pub const fn for_non_null_opt() -> SameLayout<*const A, Option>> { + SameLayout(PhantomData) +} + +/// A proof that any box has same layout as a raw pointer. +pub const fn for_box() -> SameLayout<*const A, Box> { + SameLayout(PhantomData) +} + +/// A proof that any optional box has same layout as a raw pointer. +pub const fn for_box_opt() -> SameLayout<*const A, Option>> { + SameLayout(PhantomData) +} diff --git a/same-alloc/src/vec.rs b/same-alloc/src/vec.rs new file mode 100644 index 0000000..be09f5d --- /dev/null +++ b/same-alloc/src/vec.rs @@ -0,0 +1,107 @@ +use alloc::vec::Vec; +use crate::same::SameLayout; + +/// A dynamically sized buffer for vectors of types with the same layout. +pub struct VecBuffer { + element_buffer: Vec, +} + +/// A temporary view on a VecBuffer, with a different element type. +pub struct TempVec<'lt, T> { + from: &'lt mut dyn DynBufferWith, + vec: Vec, +} + +/// Type-erase way for Vec with elements layout compatible to `T`. +trait DynBufferWith { + fn swap_internal_with(&mut self, _: &mut Vec); +} + +/// A compatible wrapper around a `Vec`, meant to be used as wrapping a mutable pointer to one. +/// Here we capture that `SameLayout` is inhabited without any indirection layer. This allows +/// us to erase the type parameter of the original vector and swap it for a different one. +#[repr(transparent)] +struct Wrap { + elements: alloc::vec::Vec, + marker: SameLayout, +} + +impl VecBuffer { + /// Create an empty buffer. + pub fn new() -> Self { + VecBuffer::default() + } + + /// Create a buffer with a pre-defined capacity. + /// + /// This buffer will not need to reallocate until the element count required for any temporary + /// vector exceeds this number of elements. + pub fn with_capacity(cap: usize) -> Self { + VecBuffer { + element_buffer: Vec::with_capacity(cap), + } + } + + /// Use the allocated buffer for a compatible type of elements. + /// + /// When the temporary view is dropped the allocation is returned to the buffer. This means its + /// capacity might be automatically increased, or decreased, based on the used of the vector. + pub fn use_for(&mut self, marker: SameLayout) -> TempVec<'_, U> { + let from = Wrap::new(&mut self.element_buffer, marker); + let elements = core::mem::take(&mut from.elements); + let vec = from.marker.forget_vec(elements); + TempVec { from, vec, } + } +} + +impl From> for VecBuffer { + fn from(mut element_buffer: Vec) -> Self { + element_buffer.clear(); + VecBuffer { element_buffer } + } +} + +impl Default for VecBuffer { + fn default() -> Self { + VecBuffer { element_buffer: Vec::new() } + } +} + +impl Drop for TempVec<'_, T> { + fn drop(&mut self) { + self.from.swap_internal_with(&mut self.vec); + } +} + +impl core::ops::Deref for TempVec<'_, T> { + type Target = Vec; + + fn deref(&self) -> &Vec { + &self.vec + } +} + +impl core::ops::DerefMut for TempVec<'_, T> { + fn deref_mut(&mut self) -> &mut Vec { + &mut self.vec + } +} + +impl Wrap { + fn new(vec: &mut Vec, _: SameLayout) -> &mut Self { + unsafe { &mut *(vec as *mut _ as *mut Wrap) } + } +} + +impl DynBufferWith for Wrap { + fn swap_internal_with(&mut self, v: &mut Vec) { + let mut temp = core::mem::take(v); + + temp.clear(); + let mut temp = self.marker.transpose().forget_vec(temp); + core::mem::swap(&mut temp, &mut self.elements); + + temp.clear(); + *v = self.marker.forget_vec(temp); + } +} diff --git a/same-alloc/tests/boxed_arrays.rs b/same-alloc/tests/boxed_arrays.rs new file mode 100644 index 0000000..e69de29 diff --git a/same-alloc/tests/median_name.rs b/same-alloc/tests/median_name.rs new file mode 100644 index 0000000..8d13e75 --- /dev/null +++ b/same-alloc/tests/median_name.rs @@ -0,0 +1,34 @@ +use same_alloc::{same, VecBuffer}; + +/// Given a list of space separated names, give the 'middle' one if they were sorted. +fn select_median_name(unparsed: &str) -> &str { + // Problem: This type depends on the lifetime parameter. Ergo, we can not normally store _one_ + // vector in the surrounding function, and instead need to allocate here a new one. + let mut names: Vec<_> = unparsed.split(' ').collect(); + let idx = names.len() / 2; + *names.select_nth_unstable(idx).1 +} + +fn select_median_name_with_buffer<'names>( + unparsed: &'names str, + buf: &mut VecBuffer<*const str>, +) -> &'names str { + let mut names = buf.use_for(same::for_ref()); + names.extend(unparsed.split(' ')); + let idx = names.len() / 2; + *names.select_nth_unstable(idx).1 +} + +#[test] +fn works() { + let names = "Adrian Carla Beren Eliza Dala"; + + let mut buffer = VecBuffer::default(); + assert_eq!(buffer.use_for(same::id()).capacity(), 0); + + assert_eq!(select_median_name(names), select_median_name_with_buffer(names, &mut buffer)); + assert!(buffer.use_for(same::id()).capacity() >= 5); + + // Now this second call does allocate a new buffer with the use of this library. + assert_eq!(select_median_name(names), select_median_name_with_buffer(names, &mut buffer)); +}