Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed unsoundness of using &mut MaybeUninit<T> as the out type for … #1

Merged
merged 5 commits into from
Feb 29, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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"]
4 changes: 4 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,6 +23,7 @@ require_unsafe_in_body = "0.2.0"
[features]
nightly = []
specialization = ["nightly"]
const_generics = ["nightly"]
chain = []

default = []
Expand Down
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