From b3b57bc4a5f90b50d3a8892fbe627e18aeb3f2eb Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Fri, 12 Mar 2021 12:40:22 +0100 Subject: [PATCH 01/10] Add a wrapper for existing memory Due to the design of `Bump` this is only available for the unsync version for now. It will be ported in time. --- static-alloc/src/unsync/bump.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/static-alloc/src/unsync/bump.rs b/static-alloc/src/unsync/bump.rs index fd6d3b5..12fb8a9 100644 --- a/static-alloc/src/unsync/bump.rs +++ b/static-alloc/src/unsync/bump.rs @@ -163,6 +163,31 @@ impl MemBump { } impl MemBump { + /// Wrap a raw slice of memory. + /// + /// This allows bump allocating from a memory resource that has been acquired through other + /// means, such as but not limited to, from a chunk of RAM passed by a boot service, some slice + /// allocated via `alloca`, a local arena for storing related values, etc. + pub fn with_memory(memory: &mut [MaybeUninit]) -> Option<&mut Self> { + let head_layout = Layout::new::>(); + let wasted_head = memory.as_ptr().align_offset(head_layout.align()); + let aligned = memory.get_mut(wasted_head..)?; + + let data_len = aligned.len().checked_sub(head_layout.size())?; + let head = aligned.as_mut_ptr() as *mut Cell; + // SAFETY: has room for at least `Cell` and is aligned to it. + // * asserted by subtracting the size from total length + // * and by manually aligning it according to offset. + unsafe { head.write(Cell::new(0)) }; + + let slice = ptr::slice_from_raw_parts_mut(aligned.as_mut_ptr(), data_len); + // SAFETY: has the declared size, and is initialized. The data tail does not need to be + // initialized, it only contains `MaybeUninit` data. + let bump = unsafe { &mut *(slice as *mut MemBump) }; + debug_assert_eq!(bump.data.len(), data_len); + Some(bump) + } + /// Allocate a region of memory. /// /// This is a safe alternative to [GlobalAlloc::alloc](#impl-GlobalAlloc). From 8b88c33e2a72238f6d90bcf68f293906ab87741c Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Sat, 13 Mar 2021 00:45:13 +0100 Subject: [PATCH 02/10] Add the main functionality of buffer-vec --- Cargo.toml | 1 + buffer-vec/Cargo.toml | 13 ++++ buffer-vec/Readme.md | 32 ++++++++ buffer-vec/src/lib.rs | 96 +++++++++++++++++++++++ buffer-vec/src/same.rs | 132 ++++++++++++++++++++++++++++++++ buffer-vec/tests/median_name.rs | 34 ++++++++ 6 files changed, 308 insertions(+) create mode 100644 buffer-vec/Cargo.toml create mode 100644 buffer-vec/Readme.md create mode 100644 buffer-vec/src/lib.rs create mode 100644 buffer-vec/src/same.rs create mode 100644 buffer-vec/tests/median_name.rs diff --git a/Cargo.toml b/Cargo.toml index 681d33d..bbda316 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "alloc-traits", + "buffer-vec", "exit-stack", "fill", "static-alloc", diff --git a/buffer-vec/Cargo.toml b/buffer-vec/Cargo.toml new file mode 100644 index 0000000..71bf9dc --- /dev/null +++ b/buffer-vec/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "buffer-vec" +version = "1.0.0-alpha" +description = "Reuse an allocated buffer for data with different types." +authors = ["Andreas Molzer "] +edition = "2018" +license = "MIT OR Apache-2.0 OR Zlib" +documentation = "https://docs.rs/buffer-vec" +repository = "https://github.com/HeroicKatora/static-alloc" +readme = "Readme.md" +categories = ["data-structures", "embedded", "memory-management", "no-std"] + +[dependencies] diff --git a/buffer-vec/Readme.md b/buffer-vec/Readme.md new file mode 100644 index 0000000..8cc1d25 --- /dev/null +++ b/buffer-vec/Readme.md @@ -0,0 +1,32 @@ +This crate provides a buffer whose allocation one 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 BufferVec<*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/buffer-vec/src/lib.rs b/buffer-vec/src/lib.rs new file mode 100644 index 0000000..f875bf3 --- /dev/null +++ b/buffer-vec/src/lib.rs @@ -0,0 +1,96 @@ +#![no_std] +extern crate alloc; + +pub mod same; + +use alloc::vec::Vec; +pub use crate::same::SameLayout; + +/// A dynamically sized buffer for various types. +pub struct BufferVec { + elements: Vec, +} + +/// A temporary view on a BufferVec, with a different element type. +pub struct TempVec<'lt, T> { + from: &'lt mut dyn DynBufferWith, + vec: Vec, +} + +impl BufferVec { + pub fn new() -> Self { + BufferVec::default() + } + + pub fn with_capacity(cap: usize) -> Self { + BufferVec { + elements: 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.elements, marker); + let elements = core::mem::take(&mut from.elements); + let vec = from.marker.forget_vec(elements); + TempVec { from, vec, } + } +} + +impl Default for BufferVec { + fn default() -> Self { + BufferVec { elements: 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 + } +} + +struct Wrap { + marker: SameLayout, + elements: alloc::vec::Vec, +} + +/// Type-erase way for Vec with elements layout compatible to `T`. +trait DynBufferWith { + fn swap_internal_with(&mut self, _: &mut 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/buffer-vec/src/same.rs b/buffer-vec/src/same.rs new file mode 100644 index 0000000..c060c3e --- /dev/null +++ b/buffer-vec/src/same.rs @@ -0,0 +1,132 @@ +//! 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::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. + 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) } + } +} + +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/buffer-vec/tests/median_name.rs b/buffer-vec/tests/median_name.rs new file mode 100644 index 0000000..e8fa280 --- /dev/null +++ b/buffer-vec/tests/median_name.rs @@ -0,0 +1,34 @@ +use buffer_vec::{same, BufferVec}; + +/// 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 BufferVec<*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 = BufferVec::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)); +} From d4e86336033cd8d78f7ad0c56f7645afd26c5cb7 Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Tue, 16 Mar 2021 20:15:35 +0100 Subject: [PATCH 03/10] Document and add Box method --- buffer-vec/Readme.md | 5 ++++- buffer-vec/src/lib.rs | 36 ++++++++++++++++++++++++++++++++++++ buffer-vec/src/same.rs | 18 ++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/buffer-vec/Readme.md b/buffer-vec/Readme.md index 8cc1d25..c1cffd6 100644 --- a/buffer-vec/Readme.md +++ b/buffer-vec/Readme.md @@ -1,4 +1,7 @@ -This crate provides a buffer whose allocation one can be used by vectors of +The `BufferVec` 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 diff --git a/buffer-vec/src/lib.rs b/buffer-vec/src/lib.rs index f875bf3..4fd87f9 100644 --- a/buffer-vec/src/lib.rs +++ b/buffer-vec/src/lib.rs @@ -1,3 +1,34 @@ +//! The `BufferVec` 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 +//! 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 BufferVec<*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; @@ -18,10 +49,15 @@ pub struct TempVec<'lt, T> { } impl BufferVec { + /// Create an empty buffer. pub fn new() -> Self { BufferVec::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 { BufferVec { elements: Vec::with_capacity(cap), diff --git a/buffer-vec/src/same.rs b/buffer-vec/src/same.rs index c060c3e..daab378 100644 --- a/buffer-vec/src/same.rs +++ b/buffer-vec/src/same.rs @@ -9,6 +9,7 @@ //! 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; @@ -36,6 +37,9 @@ impl SameLayout { /// 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(); @@ -48,6 +52,20 @@ impl SameLayout { // - 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 { From 0465db064cd8da19d39735c9f92f469b08c0a93a Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Tue, 16 Mar 2021 22:34:59 +0100 Subject: [PATCH 04/10] One final rename of the crate --- Cargo.toml | 2 +- {buffer-vec => same-alloc}/Cargo.toml | 4 ++-- {buffer-vec => same-alloc}/Readme.md | 4 ++-- {buffer-vec => same-alloc}/src/lib.rs | 20 +++++++++---------- {buffer-vec => same-alloc}/src/same.rs | 0 .../tests/median_name.rs | 0 6 files changed, 15 insertions(+), 15 deletions(-) rename {buffer-vec => same-alloc}/Cargo.toml (85%) rename {buffer-vec => same-alloc}/Readme.md (91%) rename {buffer-vec => same-alloc}/src/lib.rs (88%) rename {buffer-vec => same-alloc}/src/same.rs (100%) rename {buffer-vec => same-alloc}/tests/median_name.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index bbda316..6ad84cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [workspace] members = [ "alloc-traits", - "buffer-vec", "exit-stack", "fill", + "same-alloc", "static-alloc", "without-alloc", ] diff --git a/buffer-vec/Cargo.toml b/same-alloc/Cargo.toml similarity index 85% rename from buffer-vec/Cargo.toml rename to same-alloc/Cargo.toml index 71bf9dc..5e924e2 100644 --- a/buffer-vec/Cargo.toml +++ b/same-alloc/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "buffer-vec" +name = "same-alloc" version = "1.0.0-alpha" description = "Reuse an allocated buffer for data with different types." authors = ["Andreas Molzer "] edition = "2018" license = "MIT OR Apache-2.0 OR Zlib" -documentation = "https://docs.rs/buffer-vec" +documentation = "https://docs.rs/same-alloc" repository = "https://github.com/HeroicKatora/static-alloc" readme = "Readme.md" categories = ["data-structures", "embedded", "memory-management", "no-std"] diff --git a/buffer-vec/Readme.md b/same-alloc/Readme.md similarity index 91% rename from buffer-vec/Readme.md rename to same-alloc/Readme.md index c1cffd6..7013e78 100644 --- a/buffer-vec/Readme.md +++ b/same-alloc/Readme.md @@ -1,4 +1,4 @@ -The `BufferVec` makes it possible to re-use allocations across multiple +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 @@ -18,7 +18,7 @@ fn select_median_name(unparsed: &str) -> &str { fn select_median_name_with_buffer<'names>( unparsed: &'names str, - buf: &mut BufferVec<*const str>, + buf: &mut SameVec<*const str>, ) -> &'names str { let mut names = buf.use_for(same::for_ref()); names.extend(unparsed.split(' ')); diff --git a/buffer-vec/src/lib.rs b/same-alloc/src/lib.rs similarity index 88% rename from buffer-vec/src/lib.rs rename to same-alloc/src/lib.rs index 4fd87f9..f32a152 100644 --- a/buffer-vec/src/lib.rs +++ b/same-alloc/src/lib.rs @@ -1,4 +1,4 @@ -//! The `BufferVec` makes it possible to re-use allocations across multiple invocations of +//! 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 @@ -20,7 +20,7 @@ //! //! fn select_median_name_with_buffer<'names>( //! unparsed: &'names str, -//! buf: &mut BufferVec<*const str>, +//! buf: &mut SameVec<*const str>, //! ) -> &'names str { //! let mut names = buf.use_for(same::for_ref()); //! names.extend(unparsed.split(' ')); @@ -37,21 +37,21 @@ pub mod same; use alloc::vec::Vec; pub use crate::same::SameLayout; -/// A dynamically sized buffer for various types. -pub struct BufferVec { +/// A dynamically sized buffer for types with the same layout. +pub struct SameVec { elements: Vec, } -/// A temporary view on a BufferVec, with a different element type. +/// A temporary view on a SameVec, with a different element type. pub struct TempVec<'lt, T> { from: &'lt mut dyn DynBufferWith, vec: Vec, } -impl BufferVec { +impl SameVec { /// Create an empty buffer. pub fn new() -> Self { - BufferVec::default() + SameVec::default() } /// Create a buffer with a pre-defined capacity. @@ -59,7 +59,7 @@ impl BufferVec { /// 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 { - BufferVec { + SameVec { elements: Vec::with_capacity(cap), } } @@ -76,9 +76,9 @@ impl BufferVec { } } -impl Default for BufferVec { +impl Default for SameVec { fn default() -> Self { - BufferVec { elements: Vec::new() } + SameVec { elements: Vec::new() } } } diff --git a/buffer-vec/src/same.rs b/same-alloc/src/same.rs similarity index 100% rename from buffer-vec/src/same.rs rename to same-alloc/src/same.rs diff --git a/buffer-vec/tests/median_name.rs b/same-alloc/tests/median_name.rs similarity index 100% rename from buffer-vec/tests/median_name.rs rename to same-alloc/tests/median_name.rs From 9f1f1f53a05e135271b25bc391d2ce68b4df0899 Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Fri, 20 Oct 2023 21:35:09 +0200 Subject: [PATCH 05/10] Migrate vector to a separate module --- same-alloc/Cargo.toml | 4 +- same-alloc/src/lib.rs | 99 +------------------------------------- same-alloc/src/vec.rs | 107 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 99 deletions(-) create mode 100644 same-alloc/src/vec.rs diff --git a/same-alloc/Cargo.toml b/same-alloc/Cargo.toml index 5e924e2..a877894 100644 --- a/same-alloc/Cargo.toml +++ b/same-alloc/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "same-alloc" -version = "1.0.0-alpha" +version = "1.0.0-beta" description = "Reuse an allocated buffer for data with different types." authors = ["Andreas Molzer "] -edition = "2018" +edition = "2021" license = "MIT OR Apache-2.0 OR Zlib" documentation = "https://docs.rs/same-alloc" repository = "https://github.com/HeroicKatora/static-alloc" diff --git a/same-alloc/src/lib.rs b/same-alloc/src/lib.rs index f32a152..fb13347 100644 --- a/same-alloc/src/lib.rs +++ b/same-alloc/src/lib.rs @@ -28,105 +28,10 @@ //! *names.select_nth_unstable(idx).1 //! } //! ``` -//! #![no_std] extern crate alloc; pub mod same; +pub mod vec; -use alloc::vec::Vec; -pub use crate::same::SameLayout; - -/// A dynamically sized buffer for types with the same layout. -pub struct SameVec { - elements: Vec, -} - -/// A temporary view on a SameVec, with a different element type. -pub struct TempVec<'lt, T> { - from: &'lt mut dyn DynBufferWith, - vec: Vec, -} - -impl SameVec { - /// Create an empty buffer. - pub fn new() -> Self { - SameVec::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 { - SameVec { - elements: 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.elements, marker); - let elements = core::mem::take(&mut from.elements); - let vec = from.marker.forget_vec(elements); - TempVec { from, vec, } - } -} - -impl Default for SameVec { - fn default() -> Self { - SameVec { elements: 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 - } -} - -struct Wrap { - marker: SameLayout, - elements: alloc::vec::Vec, -} - -/// Type-erase way for Vec with elements layout compatible to `T`. -trait DynBufferWith { - fn swap_internal_with(&mut self, _: &mut 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); - } -} +pub use vec::{SameVec, TempVec}; diff --git a/same-alloc/src/vec.rs b/same-alloc/src/vec.rs new file mode 100644 index 0000000..6879a11 --- /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 types with the same layout. +pub struct SameVec { + element_buffer: Vec, +} + +/// A temporary view on a SameVec, with a different element type. +pub struct TempVec<'lt, T> { + from: &'lt mut dyn DynBufferWith, + vec: Vec, +} + +impl SameVec { + /// Create an empty buffer. + pub fn new() -> Self { + SameVec::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 { + SameVec { + 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 SameVec { + fn from(mut element_buffer: Vec) -> Self { + element_buffer.clear(); + SameVec { element_buffer } + } +} + +impl Default for SameVec { + fn default() -> Self { + SameVec { 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 + } +} + +/// 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, +} + +/// Type-erase way for Vec with elements layout compatible to `T`. +trait DynBufferWith { + fn swap_internal_with(&mut self, _: &mut 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); + } +} From 0a0e27daad1bb44685ec736b58e150b39c3f5725 Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Mon, 23 Oct 2023 10:03:16 +0200 Subject: [PATCH 06/10] Cleanup symbol order --- same-alloc/src/vec.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/same-alloc/src/vec.rs b/same-alloc/src/vec.rs index 6879a11..2678832 100644 --- a/same-alloc/src/vec.rs +++ b/same-alloc/src/vec.rs @@ -12,6 +12,20 @@ pub struct TempVec<'lt, T> { 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 SameVec { /// Create an empty buffer. pub fn new() -> Self { @@ -73,20 +87,6 @@ impl core::ops::DerefMut for TempVec<'_, T> { } } -/// 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, -} - -/// Type-erase way for Vec with elements layout compatible to `T`. -trait DynBufferWith { - fn swap_internal_with(&mut self, _: &mut Vec); -} - impl Wrap { fn new(vec: &mut Vec, _: SameLayout) -> &mut Self { unsafe { &mut *(vec as *mut _ as *mut Wrap) } From 79d9586ea9e04a730a1bc0182e79f7903608b435 Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Tue, 24 Oct 2023 23:21:16 +0200 Subject: [PATCH 07/10] Rename BufferVec to VecBuffer --- same-alloc/src/lib.rs | 3 ++- same-alloc/src/vec.rs | 20 ++++++++++---------- same-alloc/tests/median_name.rs | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/same-alloc/src/lib.rs b/same-alloc/src/lib.rs index fb13347..3ba115c 100644 --- a/same-alloc/src/lib.rs +++ b/same-alloc/src/lib.rs @@ -34,4 +34,5 @@ extern crate alloc; pub mod same; pub mod vec; -pub use vec::{SameVec, TempVec}; +pub use vec::{VecBuffer, TempVec}; +pub type SameVec = VecBuffer; diff --git a/same-alloc/src/vec.rs b/same-alloc/src/vec.rs index 2678832..be09f5d 100644 --- a/same-alloc/src/vec.rs +++ b/same-alloc/src/vec.rs @@ -1,12 +1,12 @@ use alloc::vec::Vec; use crate::same::SameLayout; -/// A dynamically sized buffer for types with the same layout. -pub struct SameVec { +/// A dynamically sized buffer for vectors of types with the same layout. +pub struct VecBuffer { element_buffer: Vec, } -/// A temporary view on a SameVec, with a different element type. +/// A temporary view on a VecBuffer, with a different element type. pub struct TempVec<'lt, T> { from: &'lt mut dyn DynBufferWith, vec: Vec, @@ -26,10 +26,10 @@ struct Wrap { marker: SameLayout, } -impl SameVec { +impl VecBuffer { /// Create an empty buffer. pub fn new() -> Self { - SameVec::default() + VecBuffer::default() } /// Create a buffer with a pre-defined capacity. @@ -37,7 +37,7 @@ impl SameVec { /// 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 { - SameVec { + VecBuffer { element_buffer: Vec::with_capacity(cap), } } @@ -54,16 +54,16 @@ impl SameVec { } } -impl From> for SameVec { +impl From> for VecBuffer { fn from(mut element_buffer: Vec) -> Self { element_buffer.clear(); - SameVec { element_buffer } + VecBuffer { element_buffer } } } -impl Default for SameVec { +impl Default for VecBuffer { fn default() -> Self { - SameVec { element_buffer: Vec::new() } + VecBuffer { element_buffer: Vec::new() } } } diff --git a/same-alloc/tests/median_name.rs b/same-alloc/tests/median_name.rs index e8fa280..f86c923 100644 --- a/same-alloc/tests/median_name.rs +++ b/same-alloc/tests/median_name.rs @@ -1,4 +1,4 @@ -use buffer_vec::{same, BufferVec}; +use same_alloc::{same, BufferVec}; /// Given a list of space separated names, give the 'middle' one if they were sorted. fn select_median_name(unparsed: &str) -> &str { From c348564b772a7b15ddebd18a42f237a96bb6ff46 Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Fri, 11 Jul 2025 00:06:18 +0200 Subject: [PATCH 08/10] Add Box equivalent for Vec --- same-alloc/src/boxed.rs | 62 ++++++++++++++++++++++++++++++++ same-alloc/tests/boxed_arrays.rs | 0 2 files changed, 62 insertions(+) create mode 100644 same-alloc/src/boxed.rs create mode 100644 same-alloc/tests/boxed_arrays.rs 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/tests/boxed_arrays.rs b/same-alloc/tests/boxed_arrays.rs new file mode 100644 index 0000000..e69de29 From c9e302994f7eba9d669dbd41dd9b11a01ff43f2f Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Thu, 9 Oct 2025 13:45:44 +0200 Subject: [PATCH 09/10] Revert "Add a wrapper for existing memory" This reverts commit b3b57bc4a5f90b50d3a8892fbe627e18aeb3f2eb. --- static-alloc/src/unsync/bump.rs | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/static-alloc/src/unsync/bump.rs b/static-alloc/src/unsync/bump.rs index 12fb8a9..fd6d3b5 100644 --- a/static-alloc/src/unsync/bump.rs +++ b/static-alloc/src/unsync/bump.rs @@ -163,31 +163,6 @@ impl MemBump { } impl MemBump { - /// Wrap a raw slice of memory. - /// - /// This allows bump allocating from a memory resource that has been acquired through other - /// means, such as but not limited to, from a chunk of RAM passed by a boot service, some slice - /// allocated via `alloca`, a local arena for storing related values, etc. - pub fn with_memory(memory: &mut [MaybeUninit]) -> Option<&mut Self> { - let head_layout = Layout::new::>(); - let wasted_head = memory.as_ptr().align_offset(head_layout.align()); - let aligned = memory.get_mut(wasted_head..)?; - - let data_len = aligned.len().checked_sub(head_layout.size())?; - let head = aligned.as_mut_ptr() as *mut Cell; - // SAFETY: has room for at least `Cell` and is aligned to it. - // * asserted by subtracting the size from total length - // * and by manually aligning it according to offset. - unsafe { head.write(Cell::new(0)) }; - - let slice = ptr::slice_from_raw_parts_mut(aligned.as_mut_ptr(), data_len); - // SAFETY: has the declared size, and is initialized. The data tail does not need to be - // initialized, it only contains `MaybeUninit` data. - let bump = unsafe { &mut *(slice as *mut MemBump) }; - debug_assert_eq!(bump.data.len(), data_len); - Some(bump) - } - /// Allocate a region of memory. /// /// This is a safe alternative to [GlobalAlloc::alloc](#impl-GlobalAlloc). From 88f87eb126027f095fba0ae9956cd7fe27a52840 Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Thu, 9 Oct 2025 13:50:57 +0200 Subject: [PATCH 10/10] Rename VecBuffer in test --- same-alloc/src/lib.rs | 4 +++- same-alloc/tests/median_name.rs | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/same-alloc/src/lib.rs b/same-alloc/src/lib.rs index 3ba115c..ff10322 100644 --- a/same-alloc/src/lib.rs +++ b/same-alloc/src/lib.rs @@ -10,6 +10,8 @@ //! # 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. @@ -20,7 +22,7 @@ //! //! fn select_median_name_with_buffer<'names>( //! unparsed: &'names str, -//! buf: &mut SameVec<*const str>, +//! buf: &mut VecBuffer<*const str>, //! ) -> &'names str { //! let mut names = buf.use_for(same::for_ref()); //! names.extend(unparsed.split(' ')); diff --git a/same-alloc/tests/median_name.rs b/same-alloc/tests/median_name.rs index f86c923..8d13e75 100644 --- a/same-alloc/tests/median_name.rs +++ b/same-alloc/tests/median_name.rs @@ -1,4 +1,4 @@ -use same_alloc::{same, BufferVec}; +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 { @@ -11,7 +11,7 @@ fn select_median_name(unparsed: &str) -> &str { fn select_median_name_with_buffer<'names>( unparsed: &'names str, - buf: &mut BufferVec<*const str>, + buf: &mut VecBuffer<*const str>, ) -> &'names str { let mut names = buf.use_for(same::for_ref()); names.extend(unparsed.split(' ')); @@ -23,7 +23,7 @@ fn select_median_name_with_buffer<'names>( fn works() { let names = "Adrian Carla Beren Eliza Dala"; - let mut buffer = BufferVec::default(); + 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));