Skip to content

Commit

Permalink
Various improvements as suggested by @HeroicKatora
Browse files Browse the repository at this point in the history
It features:

  - enhancing `Vec::reserve_uninit` to a more general API for accessing with the backing buffer / capacity of a Vec;

  - Rename `idx` to `.get_out`

  - (Ab)use `.copy_from_slice()` in read implementations to use less unsafe

TODO:

  - The iterator API,

  - Documentation

Co-Authored-By: Andreas Molzer <[email protected]>
  • Loading branch information
danielhenrymantilla and HeroicKatora committed Feb 7, 2020
1 parent 62a5a14 commit e9f9568
Show file tree
Hide file tree
Showing 12 changed files with 420 additions and 134 deletions.
2 changes: 2 additions & 0 deletions .cargo/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build]
rustdocflags = ["-C", "opt-level=2"]
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[lib]
crate-type = ["rlib", "cdylib"]

[package]
name = "uninit"
version = "0.1.0"
Expand All @@ -20,9 +23,13 @@ require_unsafe_in_body = "0.2.0"
[features]
nightly = []
specialization = ["nightly"]
const_generics = ["nightly"]
chain = []

default = []

[package.metadata.docs.rs]
features = [ "nightly" ]

[profile.test]
opt-level = 2
81 changes: 65 additions & 16 deletions src/extension_traits/as_out.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ mod private {
impl<T : ?Sized> Is for T { type Eq = T; }
}

/// Helper / extension trait to convert a `&mut _` into a `&out T` by calling
/// Extension trait to convert a `&mut _` into a `&out T` by calling
/// `.as_out::<T>()` on it.
///
/// By autoref, this means that you can even just extract a `&out T` reference
Expand Down Expand Up @@ -129,8 +129,55 @@ impl<'out, T : 'out> AsOut<[T]> for &'out mut [ManuallyDrop<T>] {
}
}

macro_rules! impl_arrays {( $($N:tt)* ) => ($(
impl<'out, T : 'out> AsOut<[T]> for &'out mut [MaybeUninit<T>; $N] {
#[cfg(not(feature = "const_generics"))]
const _: () = {
macro_rules! impl_arrays {( $($N:tt)* ) => ($(
impl<'out, T : 'out> AsOut<[T]> for &'out mut [MaybeUninit<T>; $N] {
type Out = OutSlice<'out, T>;

#[inline]
fn as_out<Pointee : ?Sized + Is<Eq=[T]>> (self: Self)
-> OutSlice<'out, T>
{
From::from(&mut self[..])
}
}
impl<'out, T : 'out> AsOut<[T]> for &'out mut [T; $N]
where
T : Copy,
{
type Out = OutSlice<'out, T>;

#[inline]
fn as_out<Pointee : ?Sized + Is<Eq=[T]>> (self: Self)
-> OutSlice<'out, T>
{
From::from(&mut self[..])
}
}
)*)}

impl_arrays! {
0 1 2 3 4 5 6 7
8 9 10 11 12 13 14 15
16 17 18 19 20 21 22 23
24 25 26 27 28 29 30 31
32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47
48 49 50 51 52 53 54 55
56 57 58 59 60 61 62 63
64
128
256
512
1024
}
};

#[cfg(feature = "const_generics")]
const _: () = {
#[doc(cfg(feature = "const_generics"))]
impl<'out, T : 'out, const N: usize> AsOut<[T]> for &'out mut [MaybeUninit<T>; N] {
type Out = OutSlice<'out, T>;

#[inline]
Expand All @@ -140,16 +187,18 @@ macro_rules! impl_arrays {( $($N:tt)* ) => ($(
From::from(&mut self[..])
}
}
)*)}

impl_arrays! {
0 1 2 3 4 5 6 7 8
9 10 11 12 13 14 15 16
17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32
33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48
49 50 51 52 53 54 55 56
57 58 59 60 61 62 63 64
128 256 512 1024
}
#[doc(cfg(feature = "const_generics"))]
impl<'out, T : 'out, const N: usize> AsOut<[T]> for &'out mut [T; N]
where
T : Copy,
{
type Out = OutSlice<'out, T>;

#[inline]
fn as_out<Pointee : ?Sized + Is<Eq=[T]>> (self: Self)
-> OutSlice<'out, T>
{
From::from(&mut self[..])
}
}
};
160 changes: 160 additions & 0 deletions src/extension_traits/boxed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
use_prelude!();

use self::private::Sealed;
mod private { pub trait Sealed : Sized {} }
impl<T> Sealed for Box<MaybeUninit<T>> {}

/// Extension trait for uninitalized `Box` allocations and
/// the optimized delayed-initialization pattern.
///
/// # Optimized in-place heap initialization
///
/// The `Box::uninit().init(...)` delayed-initialization pattern is suprisingly
/// effective in helping the optimizer inline the creation of the value directly
/// into the heap.
///
/// - In other words, this bundles [`::copyless`](https://docs.rs/copyless)
/// functionality.
///
/// - For those wondering why `Box::new(...)` could not be made as efficient,
/// the answer lies in temporaries: the `...` temporary when calling
/// `Box::new()` is created _before_ attempting the allocation, and given
/// that this allocation can fail / have side-effects, the optimizer is not
/// allowed to reorder the creation of the temporary after the allocation,
/// since it can change the semantics of the code for these corner (but
/// not unreachable) cases. It is hence illegal for the optimizer to inline
/// the creation of `...` directly into the heap.
///
/// Whereas `Box::uninit().init(...)` only creates the temporary after
/// the allocation attempted in `uninit()` has succeeded, at which point
/// it should be trivial for the optimizer to inline its creation directly
/// into the heap.
///
/// - Note, however, that this property cannot be guaranteed from a library
/// perspective; for instance, **the heap-inlined initialization does not
/// seem to happen when the optimization level (`opt-level`) is less than
/// `2`. Inversely, the author has observed that the heap-inlined
/// initialization does seem to kick in when compiling with `-C
/// opt-level=2`** (or `3`), _e.g._, when running on `--release`.
///
///
/// ### Example
///
/// ```rust
/// use ::uninit::prelude::*;
///
/// let ft: Box<u8> = Box::uninit().init(42);
/// assert_eq!(*ft, 42);
/// ```
///
/// This optimization can even allow creating arrays too big to fit in the
/// stack.
///
/// - For instance, the following implementation panics:
///
/// ```rust,should_panic
/// fn alloc_big_boxed_array () -> Box<[u64; 10_000_000]>
/// {
/// // This can panic because of the `[0; 10_000_000]` stack
/// // temporary overflowing the stack.
/// Box::new([0; 10_000_000])
/// }
/// # println!("Address: {:p}", alloc_big_boxed_array());
/// ```
///
/// - Whereas the following one does not
/// (doc-tested with `RUSTDOCFLAGS=-Copt-level=2`):
///
/// ```rust
/// # use ::uninit::prelude::*;
/// fn alloc_big_boxed_array () -> Box<[u64; 10_000_000]>
/// {
/// // But this works fine, since there is no stack temporary!
/// Box::uninit().init([0; 10_000_000])
/// }
/// # println!("Address: {:p}", alloc_big_boxed_array());
/// ```
///
/// # Handling allocation failure
///
/// A neat side-effect of this implementation is to expose the intermediate
/// state of `Box::try_alloc()`, which yields an `Option<Box<MaybeUninit<T>>>`
/// depending on whether the attempted allocation succeeded or not.
///
/// ### Example
///
/// ```rust,no_run
/// use ::uninit::prelude::*;
///
/// let buf: Box<[u8; ::core::i32::MAX as _]> = match Box::try_alloc() {
/// | Some(uninit) => uninit.init([0; ::core::i32::MAX as _]),
/// | None => {
/// panic!("Failed to allocate 2GB of memory");
/// }
/// };
/// # let _ = buf;
/// ```
impl<T> BoxUninit for Box<MaybeUninit<T>> {
type T = T;

/// Idiomatic allocation-failure unwrapping of [`BoxUninit::try_alloc`]`()`.
#[inline]
fn uninit ()
-> Box<MaybeUninit<T>>
{
let layout = alloc::Layout::new::<T>();
if let Some(it) = Self::try_alloc() { it } else {
alloc::handle_alloc_error(layout);
}
}

/// Attempts to `Box`-allocate memory for `T`, without initializing it.
///
/// Returns `None` when the allocation fails.
#[inline]
fn try_alloc ()
-> Option<Box<MaybeUninit<T>>>
{Some({
if ::core::mem::size_of::<T>() == 0 {
Box::new(MaybeUninit::uninit())
} else {
unsafe {
let layout = alloc::Layout::new::<T>();
Self::from_raw(
ptr::NonNull::<T>::new(alloc::alloc(layout).cast())?
.as_ptr()
.cast()

)
}
}
})}

/// Safely initialize a `Box::MaybeUninit<T>` by providing a `value: T`
/// (that can be inlined into the `Box`), and safely return the ergonomic
/// `Box<T>` witness of that initialization.
#[inline(always)]
fn init (mut self: Box<MaybeUninit<T>>, value: T)
-> Box<T>
{
unsafe {
self.as_mut_ptr().write(value);
Box::from_raw(Box::into_raw(self).cast())
}
}
}
/// Extension trait for uninitalized `Box` allocations and
/// the optimized delayed-initialization pattern.
pub
trait BoxUninit : Sealed {
type T;
fn uninit ()
-> Self
;
fn try_alloc ()
-> Option<Self>
;
fn init (self, value: Self::T)
-> Box<Self::T>
;
}
2 changes: 1 addition & 1 deletion src/extension_traits/manually_drop_mut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use ::core::mem::ManuallyDrop;

#[cfg(doc)] use crate::Out;

/// An extension trait providing a cast to the [`ManuallyDrop`] type.
/// Extension trait providing a cast to the [`ManuallyDrop`] type.
///
/// This is useful if you want to use an [`Out`] reference to something that
/// is not `Copy` (potentially because it has drop glue, in which case you
Expand Down
7 changes: 6 additions & 1 deletion src/extension_traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ pub use self::as_out::{
};
mod as_out;

pub use self::boxed::{
BoxUninit,
};
mod boxed;

pub use self::manually_drop_mut::{
ManuallyDropMut,
};
Expand All @@ -15,6 +20,6 @@ mod maybe_uninit;

pub use self::vec::{
VecExtendFromReader,
VecReserveUninit,
VecAllocation,
};
mod vec;
Loading

0 comments on commit e9f9568

Please sign in to comment.