Skip to content

Commit 9d1bd60

Browse files
committed
Auto merge of #64062 - petertodd:2019-new-in-place, r=<try>
Attempt to make Box::default() construct in-place + Box::new_in_place() To start with, I'll say straight up this is more of a sketch of a possible API then something I'm sure we should merge. The first part here is the `Box::new_in_place()` API. Basically the idea is that we could provide a way to reliably create large boxed values in-place via return-value-optimization. This is something that often is *never* possible to optimize, even in theory, because allocation is fallible and if the creation of the value is itself fallible, the optimizer can't move the allocation to happen first because that would be an observable change. Unfortunately this doesn't quite work if the closure can't be inlined sufficiently due to optimizer limitations. But hopefully this at least shows a way forward that might be interesting to others. Secondly, I use this `Box::new_in_place()` API to optimize `Box::default()`. I'm not totally sure this is a good idea to actually merge, as currently this is kind of an observable change in behavior for things like large arrays that would otherwise blow up the stack; people might rely on it, and currently the optimization is unreliable. Finally, this of course this could be extended to `Rc` and `Arc` as well in much the same fashion.
2 parents e29faf0 + b0674d3 commit 9d1bd60

File tree

1 file changed

+38
-1
lines changed

1 file changed

+38
-1
lines changed

src/liballoc/boxed.rs

+38-1
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,43 @@ impl<T> Box<T> {
151151
Box(ptr.cast().into())
152152
}
153153

154+
/// Allocates memory on the heap *then* constructs `T` in-place.
155+
///
156+
/// If the allocation fails, the initialization closure won't be called; because the allocation
157+
/// can fail, the optimizer often couldn't construct `T` in-place even in theory because having
158+
/// the allocation happen first is an observable change if the code that creates the value has
159+
/// side-effects such as being able to panic.
160+
///
161+
/// FIXME: This is intended to work via return-value-optimization, but due to compiler
162+
/// limitations can't reliably do that yet.
163+
#[inline(always)]
164+
fn new_in_place(f: impl FnOnce() -> T) -> Box<T> {
165+
let mut r: Box<mem::MaybeUninit<T>> = Box::new_uninit();
166+
let uninit: &mut mem::MaybeUninit<T> = &mut *r;
167+
168+
unsafe {
169+
// So why aren't we using ptr::write() here?
170+
//
171+
// For return-value-optimization to work, the compiler would have to call f with the
172+
// pointer as the return value. But a pointer isn't guaranteed to actually point to
173+
// valid memory: if the pointer was invalid, eg. due to being null, eliding the copy
174+
// would change where the invalid memory access would occur, changing the behavior in
175+
// an observable way.
176+
//
177+
// An invalid reference OTOH is undefined behavior, so the compiler is free to assume
178+
// it is valid.
179+
//
180+
// Unfortunately, this optimization isn't actually implemented yet. Though if
181+
// enough of f() can be inlined the compiler can generally elide the copy anyway.
182+
//
183+
// Finally, this is leak free because MaybeUninit::new() can't panic: either f()
184+
// succesfully creates the value, and assume_init() is called, or f() panics, frees its
185+
// resources, and r is deallocated as a Box<MaybeUninit<T>>
186+
*uninit = mem::MaybeUninit::new(f());
187+
r.assume_init()
188+
}
189+
}
190+
154191
/// Constructs a new `Pin<Box<T>>`. If `T` does not implement `Unpin`, then
155192
/// `x` will be pinned in memory and unable to be moved.
156193
#[stable(feature = "pin", since = "1.33.0")]
@@ -480,7 +517,7 @@ unsafe impl<#[may_dangle] T: ?Sized> Drop for Box<T> {
480517
impl<T: Default> Default for Box<T> {
481518
/// Creates a `Box<T>`, with the `Default` value for T.
482519
fn default() -> Box<T> {
483-
box Default::default()
520+
Box::new_in_place(T::default)
484521
}
485522
}
486523

0 commit comments

Comments
 (0)