Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ members = [
"alloc-traits",
"exit-stack",
"fill",
"same-alloc",
"static-alloc",
"unsize",
"without-alloc",
Expand Down
13 changes: 13 additions & 0 deletions same-alloc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>"]
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]
35 changes: 35 additions & 0 deletions same-alloc/Readme.md
Original file line number Diff line number Diff line change
@@ -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.
62 changes: 62 additions & 0 deletions same-alloc/src/boxed.rs
Original file line number Diff line number Diff line change
@@ -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<T> {
element_buffer: Box<MaybeUninit<T>>,
}

pub struct TempBox<'lt, U> {
from: &'lt mut dyn DynBufferWith<U>,
boxed: Option<Box<U>>,
}

/// A compatible wrapper around a `Vec`, meant to be used as wrapping a mutable pointer to one.
/// Here we capture that `SameLayout<T, U>` 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<T, U> {
elements: Box<MaybeUninit<T>>,
marker: SameLayout<T, U>,
}

/// Type-erase way for Vec with elements layout compatible to `U`.
trait DynBufferWith<U> {
fn swap_internal_with(&mut self, _: &mut Option<Box<U>>);
}

impl<T> Default for SameBox<T> {
fn default() -> Self {
SameBox { element_buffer: Box::new(MaybeUninit::uninit()) }
}
}

impl<T> Drop for TempBox<'_, T> {
fn drop(&mut self) {
self.from.swap_internal_with(&mut self.boxed);
}
}

impl<T> core::ops::Deref for TempBox<'_, T> {
type Target = Box<T>;

fn deref(&self) -> &Box<T> {
self.boxed.as_ref().unwrap()
}
}

impl<T> core::ops::DerefMut for TempBox<'_, T> {
fn deref_mut(&mut self) -> &mut Box<T> {
self.boxed.as_mut().unwrap()
}
}

impl<T, U> DynBufferWith<U> for Wrap<T, U> {
fn swap_internal_with(&mut self, v: &mut Option<Box<U>>) {
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);
}
}
40 changes: 40 additions & 0 deletions same-alloc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<T> = VecBuffer<T>;
150 changes: 150 additions & 0 deletions same-alloc/src/same.rs
Original file line number Diff line number Diff line change
@@ -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<A>` 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<A, B>(PhantomData<(A, B)>);

impl<A, B> SameLayout<A, B> {
pub const fn new() -> Option<Self> {
let layout_a = Layout::new::<A>();
let layout_b = Layout::new::<B>();
// 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<A>) -> Vec<B> {
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>) -> (A, Box<MaybeUninit<B>>) {
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<B>) })
}
}

impl<A, B> Clone for SameLayout<A, B> {
fn clone(&self) -> Self {
SameLayout(self.0)
}
}

impl<A, B> Copy for SameLayout<A, B> {}

impl<A, B> SameLayout<A, B> {
pub const fn array<const N: usize>(self) -> SameLayout<[A; N], [B; N]> {
SameLayout(PhantomData)
}

/// Apply a transitive argument to construct a new relation proof.
pub const fn chain<C>(self, _: SameLayout<B, C>) -> SameLayout<A, C> {
SameLayout(PhantomData)
}

/// Use commutativity of equality.
pub const fn transpose(self) -> SameLayout<B, A> {
SameLayout(PhantomData)
}
}

/// A proof that any type has the same layout as itself.
pub const fn id<A>() -> SameLayout<A, A> {
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<A, B>() -> 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<A: ?Sized>() -> 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<A: ?Sized>() -> SameLayout<*const A, NonNull<A>> {
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<A: ?Sized>() -> SameLayout<*const A, Option<NonNull<A>>> {
SameLayout(PhantomData)
}

/// A proof that any box has same layout as a raw pointer.
pub const fn for_box<A: ?Sized>() -> SameLayout<*const A, Box<A>> {
SameLayout(PhantomData)
}

/// A proof that any optional box has same layout as a raw pointer.
pub const fn for_box_opt<A: ?Sized>() -> SameLayout<*const A, Option<Box<A>>> {
SameLayout(PhantomData)
}
Loading
Loading