|
| 1 | +use_prelude!(); |
| 2 | + |
| 3 | +use self::private::Sealed; |
| 4 | +mod private { pub trait Sealed : Sized {} } |
| 5 | +impl<T> Sealed for Box<MaybeUninit<T>> {} |
| 6 | + |
| 7 | +/// Extension trait for uninitalized `Box` allocations and |
| 8 | +/// the optimized delayed-initialization pattern. |
| 9 | +/// |
| 10 | +/// # Optimized in-place heap initialization |
| 11 | +/// |
| 12 | +/// The `Box::uninit().init(...)` delayed-initialization pattern is suprisingly |
| 13 | +/// effective in helping the optimizer inline the creation of the value directly |
| 14 | +/// into the heap. |
| 15 | +/// |
| 16 | +/// - In other words, this bundles [`::copyless`](https://docs.rs/copyless) |
| 17 | +/// functionality. |
| 18 | +/// |
| 19 | +/// - For those wondering why `Box::new(...)` could not be made as efficient, |
| 20 | +/// the answer lies in temporaries: the `...` temporary when calling |
| 21 | +/// `Box::new()` is created _before_ attempting the allocation, and given |
| 22 | +/// that this allocation can fail / have side-effects, the optimizer is not |
| 23 | +/// allowed to reorder the creation of the temporary after the allocation, |
| 24 | +/// since it can change the semantics of the code for these corner (but |
| 25 | +/// not unreachable) cases. It is hence illegal for the optimizer to inline |
| 26 | +/// the creation of `...` directly into the heap. |
| 27 | +/// |
| 28 | +/// Whereas `Box::uninit().init(...)` only creates the temporary after |
| 29 | +/// the allocation attempted in `uninit()` has succeeded, at which point |
| 30 | +/// it should be trivial for the optimizer to inline its creation directly |
| 31 | +/// into the heap. |
| 32 | +/// |
| 33 | +/// - Note, however, that this property cannot be guaranteed from a library |
| 34 | +/// perspective; for instance, **the heap-inlined initialization does not |
| 35 | +/// seem to happen when the optimization level (`opt-level`) is less than |
| 36 | +/// `2`. Inversely, the author has observed that the heap-inlined |
| 37 | +/// initialization does seem to kick in when compiling with `-C |
| 38 | +/// opt-level=2`** (or `3`), _e.g._, when running on `--release`. |
| 39 | +/// |
| 40 | +/// |
| 41 | +/// ### Example |
| 42 | +/// |
| 43 | +/// ```rust |
| 44 | +/// use ::uninit::prelude::*; |
| 45 | +/// |
| 46 | +/// let ft: Box<u8> = Box::uninit().init(42); |
| 47 | +/// assert_eq!(*ft, 42); |
| 48 | +/// ``` |
| 49 | +/// |
| 50 | +/// This optimization can even allow creating arrays too big to fit in the |
| 51 | +/// stack. |
| 52 | +/// |
| 53 | +/// - For instance, the following implementation panics: |
| 54 | +/// |
| 55 | +/// ```rust,should_panic |
| 56 | +/// fn alloc_big_boxed_array () -> Box<[u64; 10_000_000]> |
| 57 | +/// { |
| 58 | +/// // This can panic because of the `[0; 10_000_000]` stack |
| 59 | +/// // temporary overflowing the stack. |
| 60 | +/// Box::new([0; 10_000_000]) |
| 61 | +/// } |
| 62 | +/// # println!("Address: {:p}", alloc_big_boxed_array()); |
| 63 | +/// ``` |
| 64 | +/// |
| 65 | +/// - Whereas the following one does not |
| 66 | +/// (doc-tested with `RUSTDOCFLAGS=-Copt-level=2`): |
| 67 | +/// |
| 68 | +/// ```rust |
| 69 | +/// # use ::uninit::prelude::*; |
| 70 | +/// fn alloc_big_boxed_array () -> Box<[u64; 10_000_000]> |
| 71 | +/// { |
| 72 | +/// // But this works fine, since there is no stack temporary! |
| 73 | +/// Box::uninit().init([0; 10_000_000]) |
| 74 | +/// } |
| 75 | +/// # println!("Address: {:p}", alloc_big_boxed_array()); |
| 76 | +/// ``` |
| 77 | +/// |
| 78 | +/// # Handling allocation failure |
| 79 | +/// |
| 80 | +/// A neat side-effect of this implementation is to expose the intermediate |
| 81 | +/// state of `Box::try_alloc()`, which yields an `Option<Box<MaybeUninit<T>>>` |
| 82 | +/// depending on whether the attempted allocation succeeded or not. |
| 83 | +/// |
| 84 | +/// ### Example |
| 85 | +/// |
| 86 | +/// ```rust,no_run |
| 87 | +/// use ::uninit::prelude::*; |
| 88 | +/// |
| 89 | +/// let buf: Box<[u8; ::core::i32::MAX as _]> = match Box::try_alloc() { |
| 90 | +/// | Some(uninit) => uninit.init([0; ::core::i32::MAX as _]), |
| 91 | +/// | None => { |
| 92 | +/// panic!("Failed to allocate 2GB of memory"); |
| 93 | +/// } |
| 94 | +/// }; |
| 95 | +/// # let _ = buf; |
| 96 | +/// ``` |
| 97 | +impl<T> BoxUninit for Box<MaybeUninit<T>> { |
| 98 | + type T = T; |
| 99 | + |
| 100 | + /// Idiomatic allocation-failure unwrapping of [`BoxUninit::try_alloc`]`()`. |
| 101 | + #[inline] |
| 102 | + fn uninit () |
| 103 | + -> Box<MaybeUninit<T>> |
| 104 | + { |
| 105 | + let layout = alloc::Layout::new::<T>(); |
| 106 | + if let Some(it) = Self::try_alloc() { it } else { |
| 107 | + alloc::handle_alloc_error(layout); |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + /// Attempts to `Box`-allocate memory for `T`, without initializing it. |
| 112 | + /// |
| 113 | + /// Returns `None` when the allocation fails. |
| 114 | + #[inline] |
| 115 | + fn try_alloc () |
| 116 | + -> Option<Box<MaybeUninit<T>>> |
| 117 | + {Some({ |
| 118 | + if ::core::mem::size_of::<T>() == 0 { |
| 119 | + Box::new(MaybeUninit::uninit()) |
| 120 | + } else { |
| 121 | + unsafe { |
| 122 | + let layout = alloc::Layout::new::<T>(); |
| 123 | + Self::from_raw( |
| 124 | + ptr::NonNull::<T>::new(alloc::alloc(layout).cast())? |
| 125 | + .as_ptr() |
| 126 | + .cast() |
| 127 | + |
| 128 | + ) |
| 129 | + } |
| 130 | + } |
| 131 | + })} |
| 132 | + |
| 133 | + /// Safely initialize a `Box::MaybeUninit<T>` by providing a `value: T` |
| 134 | + /// (that can be inlined into the `Box`), and safely return the ergonomic |
| 135 | + /// `Box<T>` witness of that initialization. |
| 136 | + #[inline(always)] |
| 137 | + fn init (mut self: Box<MaybeUninit<T>>, value: T) |
| 138 | + -> Box<T> |
| 139 | + { |
| 140 | + unsafe { |
| 141 | + self.as_mut_ptr().write(value); |
| 142 | + Box::from_raw(Box::into_raw(self).cast()) |
| 143 | + } |
| 144 | + } |
| 145 | +} |
| 146 | +/// Extension trait for uninitalized `Box` allocations and |
| 147 | +/// the optimized delayed-initialization pattern. |
| 148 | +pub |
| 149 | +trait BoxUninit : Sealed { |
| 150 | + type T; |
| 151 | + fn uninit () |
| 152 | + -> Self |
| 153 | + ; |
| 154 | + fn try_alloc () |
| 155 | + -> Option<Self> |
| 156 | + ; |
| 157 | + fn init (self, value: Self::T) |
| 158 | + -> Box<Self::T> |
| 159 | + ; |
| 160 | +} |
0 commit comments