diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 0000000..0b82483 --- /dev/null +++ b/.cargo/config @@ -0,0 +1,2 @@ +[build] +rustdocflags = ["-C", "opt-level=2"] diff --git a/Cargo.toml b/Cargo.toml index 4ac5848..12e18b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,6 @@ +[lib] +crate-type = ["rlib", "cdylib"] + [package] name = "uninit" version = "0.1.0" @@ -20,6 +23,7 @@ require_unsafe_in_body = "0.2.0" [features] nightly = [] specialization = ["nightly"] +const_generics = ["nightly"] chain = [] default = [] diff --git a/src/extension_traits/as_out.rs b/src/extension_traits/as_out.rs index f270682..265fa23 100644 --- a/src/extension_traits/as_out.rs +++ b/src/extension_traits/as_out.rs @@ -11,7 +11,7 @@ mod private { impl 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::()` on it. /// /// By autoref, this means that you can even just extract a `&out T` reference @@ -129,8 +129,55 @@ impl<'out, T : 'out> AsOut<[T]> for &'out mut [ManuallyDrop] { } } -macro_rules! impl_arrays {( $($N:tt)* ) => ($( - impl<'out, T : 'out> AsOut<[T]> for &'out mut [MaybeUninit; $N] { +#[cfg(not(feature = "const_generics"))] +const _: () = { + macro_rules! impl_arrays {( $($N:tt)* ) => ($( + impl<'out, T : 'out> AsOut<[T]> for &'out mut [MaybeUninit; $N] { + type Out = OutSlice<'out, T>; + + #[inline] + fn as_out> (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> (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; N] { type Out = OutSlice<'out, T>; #[inline] @@ -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> (self: Self) + -> OutSlice<'out, T> + { + From::from(&mut self[..]) + } + } +}; diff --git a/src/extension_traits/boxed.rs b/src/extension_traits/boxed.rs new file mode 100644 index 0000000..7e142fd --- /dev/null +++ b/src/extension_traits/boxed.rs @@ -0,0 +1,160 @@ +use_prelude!(); + +use self::private::Sealed; +mod private { pub trait Sealed : Sized {} } +impl Sealed for Box> {} + +/// 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 = 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>>` +/// 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 BoxUninit for Box> { + type T = T; + + /// Idiomatic allocation-failure unwrapping of [`BoxUninit::try_alloc`]`()`. + #[inline] + fn uninit () + -> Box> + { + let layout = alloc::Layout::new::(); + 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>> + {Some({ + if ::core::mem::size_of::() == 0 { + Box::new(MaybeUninit::uninit()) + } else { + unsafe { + let layout = alloc::Layout::new::(); + Self::from_raw( + ptr::NonNull::::new(alloc::alloc(layout).cast())? + .as_ptr() + .cast() + + ) + } + } + })} + + /// Safely initialize a `Box::MaybeUninit` by providing a `value: T` + /// (that can be inlined into the `Box`), and safely return the ergonomic + /// `Box` witness of that initialization. + #[inline(always)] + fn init (mut self: Box>, value: T) + -> Box + { + 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 + ; + fn init (self, value: Self::T) + -> Box + ; +} diff --git a/src/extension_traits/manually_drop_mut.rs b/src/extension_traits/manually_drop_mut.rs index 76bf215..c2dd77a 100644 --- a/src/extension_traits/manually_drop_mut.rs +++ b/src/extension_traits/manually_drop_mut.rs @@ -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 diff --git a/src/extension_traits/mod.rs b/src/extension_traits/mod.rs index aee5ae1..6d8dcb5 100644 --- a/src/extension_traits/mod.rs +++ b/src/extension_traits/mod.rs @@ -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, }; @@ -15,6 +20,6 @@ mod maybe_uninit; pub use self::vec::{ VecExtendFromReader, - VecReserveUninit, + VecAllocation, }; mod vec; diff --git a/src/extension_traits/vec.rs b/src/extension_traits/vec.rs index ef4592d..11d8fef 100644 --- a/src/extension_traits/vec.rs +++ b/src/extension_traits/vec.rs @@ -1,70 +1,174 @@ use crate::*; use ::core::slice; -/// Extension trait for [`Vec`], that reserves extra uninitialized memory for -/// it, and **returns a mutable handle on those extra (uninitialized) elements**. -/// -/// # Example -/// -/// ```rust -/// # use ::core::mem::MaybeUninit; -/// use ::uninit::prelude::*; -/// -/// let mut vec = b"Hello, ".to_vec(); -/// const WORLD: &[u8] = b"World!"; -/// -/// let extra: &mut [MaybeUninit] = vec.reserve_uninit(WORLD.len()); -/// extra.as_out::<[u8]>().copy_from_slice(WORLD); -/// -/// // `.reserve_uninit()` guarantees the following properties: -/// assert_eq!(extra.len(), WORLD.len()); -/// let extra_start: *mut u8 = extra.as_mut_ptr().cast(); -/// let uninit_start: *mut u8 = vec.as_mut_ptr().wrapping_add(vec.len()); -/// assert_eq!(extra_start, uninit_start); -/// -/// unsafe { -/// // # Safety -/// // -/// // - `.copy_from_slice()` contract guarantees initialization -/// // of `extra`, which, in turn, from `reserve_uninit`'s contract, -/// // leads to the `vec` extra capacity having been initialized. -/// vec.set_len(vec.len() + WORLD.len()); -/// } -/// assert_eq!( -/// vec, -/// b"Hello, World!", -/// ); -/// ``` +/// Extension trait for [`Vec`], allowing a non-`unsafe` API to interact +/// with the backing buffer / allocation. pub -trait VecReserveUninit { +trait VecAllocation : Sealed { type Item; - + fn get_backing_buffer (self: &'_ mut Self) + -> OutSlice<'_, Self::Item> + ; + fn into_backing_buffer (self: Self) + -> Vec> // ideally this would be a `Box<[]>`, but `Vec` does not guarantee it. + ; + fn split_at_extra_capacity (self: &'_ mut Self) + -> (&'_ mut [Self::Item], &'_ mut [MaybeUninit]) + ; fn reserve_uninit (self: &'_ mut Self, additional: usize) - -> &'_ mut [MaybeUninit] + -> &'_ mut [MaybeUninit] ; } -impl VecReserveUninit for Vec { + +mod private { pub trait Sealed : Sized {} } use private::Sealed; + +impl Sealed for Vec {} +impl VecAllocation for Vec +where + T : Copy, // Opinionated position against accidental memory leaks +{ type Item = T; - fn reserve_uninit (self: &'_ mut Self, additional: usize) - -> &'_ mut [MaybeUninit] + /// Gets an [`&out` slice][`OutSlice`] (of `self.capacity()` elements) to the backing buffer. + fn get_backing_buffer (self: &'_ mut Vec) + -> OutSlice<'_, T> { - self.reserve(additional); + let capacity = self.capacity(); unsafe { // # Safety // - // - Vec contract guarantees that after a call to `.reserve(n)` - // at least `n` uninitialized elements after the end of the - // Vec's current length can be soundly written to. + // - `Vec` safety invariant / layout guarantees state that + // it owns a `Box<[MaybeUninit]` of length `self.capacity()` + // and starting at `self.as_mut_ptr()`. slice::from_raw_parts_mut( - utils::ptr_cast_mut::>( - self.as_mut_ptr() - .wrapping_add(self.len()) + self.as_mut_ptr().cast::>(), + capacity, + ).as_out::<[Self::Item]>() + } + } + + /// Extracts an owned handle to the backing buffer. + /// + /// Ideally the return type would be a `Box<[MaybeUninit]>`, but AFAIK, + /// it is not officially guaranteed that the allocation of a `Vec` + /// (a `RawVec`) matches the allocation of a boxed slice, even if in their + /// current implementations they do. + fn into_backing_buffer (self: Vec) + -> Vec> // ideally this would be a `Box<[]>`, but `Vec` does not guarantee it. + { + let mut this = mem::ManuallyDrop::new(self); + let capacity = this.capacity(); + unsafe { + // Safety: + // + // - same layout; + // + // - `MaybeUninit>` is sound to `.assume_init()`; + // + // - init -> uninit conversion is sound by "covariance of ownership". + Vec::from_raw_parts( + this.as_mut_ptr().cast::>(), + capacity, + capacity, + ) + } + } + + /// Splits the `Vec`'s [backing buffer] into two slices of initialized and + /// unitialized elements. + /// + /// Imagine this as doing + /// `self.get_backing_buffer().split_at_out(self.len())` + /// while upgrading the first half to `&mut [T]` and the second half to a + /// `&mut [MaybeUninit]`. + /// + /// [backing buffer]: `VecAllocation::get_backing_buffer` + fn split_at_extra_capacity (self: &'_ mut Vec) + -> (&'_ mut [T], &'_ mut [MaybeUninit]) + { + let (ptr, len, cap) = { + let len = self.len(); + let cap = self.capacity(); + let ptr = self.as_mut_ptr().cast::>(); + (ptr, len, cap) + }; + unsafe { + // # Safety + // + // - `ptr[.. cap]` can be `&mut`-ed ("backing buffer"); + // + // - ptr[.. len] and ptr[len .. cap] do not overlap and thus do + // not alias; + // + // - ptr[.. len] is made of initialized elements; + // + // - ptr[len .. cap] is made of uninitialized elements; + // + // - the "optimized indexing" guarantees of `Vec` make the usage + // of `.add()` indexing sound (`RawVec` guards against the + // `mem::size_of::() * cap > isize::MAX as usize` overflow). + // + // - `cap >= len` is a safety invariant of `Vec`. + ( + slice::from_raw_parts_mut( + ptr.cast::(), + len, + ), + slice::from_raw_parts_mut( + ptr.add(len), + cap .checked_sub(len) + .unwrap_or_else(|| hint::unreachable_unchecked()) + , ), - additional, ) } } + + /// [Reserves][`Vec::reserve`] extra (uninitialized) memory for it, + /// **returning a mutable handle to those extra (uninitialized) elements**. + /// + /// # Example + /// + /// ```rust + /// # use ::core::mem::MaybeUninit; + /// use ::uninit::prelude::*; + /// + /// let mut vec = b"Hello, ".to_vec(); + /// const WORLD: &[u8] = b"World!"; + /// + /// let extra: &mut [MaybeUninit] = vec.reserve_uninit(WORLD.len()); + /// extra.as_out::<[u8]>().copy_from_slice(WORLD); + /// + /// // `.reserve_uninit()` guarantees the following properties: + /// assert_eq!(extra.len(), WORLD.len()); + /// let extra_start: *mut u8 = extra.as_mut_ptr().cast(); + /// let uninit_start: *mut u8 = vec.as_mut_ptr().wrapping_add(vec.len()); + /// assert_eq!(extra_start, uninit_start); + /// + /// unsafe { + /// // # Safety + /// // + /// // - `.copy_from_slice()` contract guarantees initialization + /// // of `extra`, which, in turn, from `reserve_uninit`'s contract, + /// // leads to the `vec` extra capacity having been initialized. + /// vec.set_len(vec.len() + WORLD.len()); + /// } + /// assert_eq!( + /// vec, + /// b"Hello, World!", + /// ); + /// ``` + fn reserve_uninit (self: &'_ mut Vec, additional: usize) + -> &'_ mut [MaybeUninit] + { + self.reserve(additional); + let (_, extra) = self.split_at_extra_capacity(); + unsafe { + // Safety: `Vec` guarantees that `cap >= len + additional` and + // thus that `cap - len >= additional`. + extra.get_unchecked_mut(.. additional) + } + } } /// Extension trait for [`Vec`], that grows the vec by a _bounded_ amount of @@ -104,7 +208,7 @@ impl VecExtendFromReader for Vec { mut reader: R, ) -> io::Result<()> { - let buf: OutSlice = self.reserve_uninit(count).into(); + let buf: OutSlice<'_, u8> = self.reserve_uninit(count).into(); let buf: &mut [u8] = reader.read_into_uninit_exact(buf)?; let count: usize = buf.len(); debug_assert_eq!( diff --git a/src/lib.rs b/src/lib.rs index 87ad3ab..95ad769 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,12 @@ #![cfg_attr(feature = "specialization", feature(specialization), )] +#![cfg_attr(feature = "const_generics", + feature(const_generics), +)] +#![deny( + elided_lifetimes_in_paths, +)] #[macro_use] extern crate require_unsafe_in_body; @@ -16,8 +22,9 @@ pub mod prelude { pub use crate::{ extension_traits::{ AsOut, + BoxUninit, ManuallyDropMut, - VecReserveUninit, + VecAllocation, }, out_references::{ Out, @@ -25,6 +32,7 @@ pub mod prelude { }, uninit_array, }; + pub use ::core::mem::MaybeUninit; } use_prelude!(); diff --git a/src/out_references.rs b/src/out_references.rs index 8cf26b3..48c9312 100644 --- a/src/out_references.rs +++ b/src/out_references.rs @@ -3,7 +3,6 @@ use ::core::{ ManuallyDrop, MaybeUninit, }, - ptr, slice, }; @@ -271,7 +270,7 @@ impl<'out, T : 'out> OutSlice<'out, T> { #[inline] pub - fn idx (self: OutSlice<'out, T>, idx: Index) + fn get_out (self: OutSlice<'out, T>, idx: Index) -> Option where Index : SliceIndex<'out, T>, @@ -379,38 +378,23 @@ impl<'out, T : 'out> OutSlice<'out, T> { /// ``` pub fn copy_from_slice ( - mut self: OutSlice<'out, T>, + self: OutSlice<'out, T>, source_slice: &'_ [T], ) -> &'out mut [T] where T : Copy, { - assert_eq!( - self.len(), - source_slice.len(), - "`copy_from_slice()`: length mismatch", - ); unsafe { // # Safety // - // - `T : Copy` - // - // - `OutSlice` is unaliased and thus guarantees no overlap; - // - // - `self[.. len]` is valid to write to; - // - // - `source_slice[.. len]` is valid to read (and copy) from. - ptr::copy_nonoverlapping( - source_slice.as_ptr(), - self.as_mut_ptr(), - self.len(), - ); - } - unsafe { - // # Safety + // - Writing to `self.0` is fine since `source_slice` only + // contains initialized elements. // // - the `copy_nonoverlapping()` call guarantees that the buffer // has been initialized. + self.0.copy_from_slice( + crate::extension_traits::MaybeUninitExt::from_ref(source_slice) + ); self.assume_init() } } @@ -429,7 +413,7 @@ impl<'out, T : 'out> OutSlice<'out, T> { { let len = self.len(); (0 .. len).for_each(|i| { - self.r().idx(i).unwrap().write(factory(i)); + self.r().get_out(i).unwrap().write(factory(i)); }); unsafe { // Safety: The `len` values of the buffer have been initialized diff --git a/src/read/impls.rs b/src/read/impls.rs index 9dae785..16be8fb 100644 --- a/src/read/impls.rs +++ b/src/read/impls.rs @@ -26,42 +26,22 @@ unsafe impl ReadIntoUninit for &'_ [u8] { fn read_into_uninit<'buf> ( self: &'_ mut Self, - mut buf: OutSlice<'buf, u8>, + buf: OutSlice<'buf, u8>, ) -> io::Result<&'buf mut [u8]> { let count = ::std::cmp::min(buf.len(), self.len()); let (to_copy, remaining) = self.split_at(count); *self = remaining; - // First check if the amount of bytes we want to read is small: + // Taken from stdlib: + // "First check if the amount of bytes we want to read is small: // `copy_from_slice` will generally expand to a call to `memcpy`, and - // for a single byte the overhead is significant. + // for a single byte the overhead is significant." if count == 1 { - buf.reborrow().idx(0).unwrap().write(to_copy[0]); + Ok( slice::from_mut(buf.get_out(0).unwrap().write(to_copy[0])) ) } else { - unsafe { - // # Safety - // - // This is an unchecked version of `copy_from_slice`: - // - // - `to_copy[.. count]` is aligned and valid to read from, - // - // - `buf[.. count]` is aligned and valid to write to, - // - // - they cannot overlap given the `&mut` access on `buf` - ptr::copy_nonoverlapping::( - to_copy.as_ptr(), - buf.as_mut_ptr(), - count, - ); - } + Ok( buf.get_out(.. count).unwrap().copy_from_slice(to_copy) ) } - Ok(unsafe { - // # Safety - // - // - `buf[.. count]` has been initialized - buf.idx(.. count).unwrap().assume_init() - }) } } diff --git a/src/read/mod.rs b/src/read/mod.rs index 664e58b..b049dde 100644 --- a/src/read/mod.rs +++ b/src/read/mod.rs @@ -68,7 +68,7 @@ trait ReadIntoUninit : Read { }, | Ok(n) => { // buf = &mut buf[n ..]; - buf = buf.idx(n ..).unwrap(); + buf = buf.get_out(n ..).unwrap(); }, | Err(ref e) if e.kind() == io::ErrorKind::Interrupted @@ -210,13 +210,7 @@ mod chain { { let len = buf.len(); if len == 0 { - return Ok(unsafe { - // # Safety - // - // - since it has `0` elements, - // it does have `0` initialized elements. - buf.assume_init() - }) + return Ok(self.copy_from_slice(&[])); } if self.first_done.not() { let buf_ = self.first.read_into_uninit(buf.r())?; @@ -227,7 +221,7 @@ mod chain { // Safety: `buf_` has been a witness of the // initialization of these bytes. let len = buf_.len(); - let buf = buf.idx(.. len).unwrap(); + let buf = buf.get_out(.. len).unwrap(); Ok(buf.assume_init()) }; } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 9a1ccaf..dfef50c 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -3,10 +3,3 @@ mod macros; pub(in crate) mod prelude; - -#[inline] -pub(in crate) -fn ptr_cast_mut (p: *mut T) -> *mut U -{ - p as _ -}