From b0674d36921e7e432253b977d107f6dff432f28c Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Sun, 1 Sep 2019 08:05:08 -0400 Subject: [PATCH 1/2] Attempt to make Box::default() construct in-place --- src/liballoc/boxed.rs | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/liballoc/boxed.rs b/src/liballoc/boxed.rs index c61e3183409f2..c0a1b4a9d2a86 100644 --- a/src/liballoc/boxed.rs +++ b/src/liballoc/boxed.rs @@ -151,6 +151,43 @@ impl Box { Box(ptr.cast().into()) } + /// Allocates memory on the heap *then* constructs `T` in-place. + /// + /// If the allocation fails, the initialization closure won't be called; because the allocation + /// can fail, the optimizer often couldn't construct `T` in-place even in theory because having + /// the allocation happen first is an observable change if the code that creates the value has + /// side-effects such as being able to panic. + /// + /// FIXME: This is intended to work via return-value-optimization, but due to compiler + /// limitations can't reliably do that yet. + #[inline(always)] + fn new_in_place(f: impl FnOnce() -> T) -> Box { + let mut r: Box> = Box::new_uninit(); + let uninit: &mut mem::MaybeUninit = &mut *r; + + unsafe { + // So why aren't we using ptr::write() here? + // + // For return-value-optimization to work, the compiler would have to call f with the + // pointer as the return value. But a pointer isn't guaranteed to actually point to + // valid memory: if the pointer was invalid, eg. due to being null, eliding the copy + // would change where the invalid memory access would occur, changing the behavior in + // an observable way. + // + // An invalid reference OTOH is undefined behavior, so the compiler is free to assume + // it is valid. + // + // Unfortunately, this optimization isn't actually implemented yet. Though if + // enough of f() can be inlined the compiler can generally elide the copy anyway. + // + // Finally, this is leak free because MaybeUninit::new() can't panic: either f() + // succesfully creates the value, and assume_init() is called, or f() panics, frees its + // resources, and r is deallocated as a Box> + *uninit = mem::MaybeUninit::new(f()); + r.assume_init() + } + } + /// Constructs a new `Pin>`. If `T` does not implement `Unpin`, then /// `x` will be pinned in memory and unable to be moved. #[stable(feature = "pin", since = "1.33.0")] @@ -480,7 +517,7 @@ unsafe impl<#[may_dangle] T: ?Sized> Drop for Box { impl Default for Box { /// Creates a `Box`, with the `Default` value for T. fn default() -> Box { - box Default::default() + Box::new_in_place(T::default) } } From 9b57963f81d9c79648b7b8e37835ba336dea1cee Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Wed, 4 Sep 2019 21:44:21 -0400 Subject: [PATCH 2/2] Attempt to create Rc/Arc default values in place --- src/liballoc/rc.rs | 14 +++++++++++++- src/liballoc/sync.rs | 14 +++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/liballoc/rc.rs b/src/liballoc/rc.rs index 2b222caf13f3d..a6cade43c71df 100644 --- a/src/liballoc/rc.rs +++ b/src/liballoc/rc.rs @@ -358,6 +358,18 @@ impl Rc { } } + #[inline(always)] + fn new_in_place(f: impl FnOnce() -> T) -> Rc { + let mut r: Rc> = Rc::new_uninit(); + + unsafe { + let uninit: &mut mem::MaybeUninit = Rc::get_mut_unchecked(&mut r); + + *uninit = mem::MaybeUninit::new(f()); + r.assume_init() + } + } + /// Constructs a new `Pin>`. If `T` does not implement `Unpin`, then /// `value` will be pinned in memory and unable to be moved. #[stable(feature = "pin", since = "1.33.0")] @@ -1146,7 +1158,7 @@ impl Default for Rc { /// ``` #[inline] fn default() -> Rc { - Rc::new(Default::default()) + Rc::new_in_place(T::default) } } diff --git a/src/liballoc/sync.rs b/src/liballoc/sync.rs index 9ffc1673e5ab8..683b2d6af561c 100644 --- a/src/liballoc/sync.rs +++ b/src/liballoc/sync.rs @@ -338,6 +338,18 @@ impl Arc { } } + #[inline(always)] + fn new_in_place(f: impl FnOnce() -> T) -> Arc { + let mut r: Arc> = Arc::new_uninit(); + + unsafe { + let uninit: &mut mem::MaybeUninit = Arc::get_mut_unchecked(&mut r); + + *uninit = mem::MaybeUninit::new(f()); + r.assume_init() + } + } + /// Constructs a new `Pin>`. If `T` does not implement `Unpin`, then /// `data` will be pinned in memory and unable to be moved. #[stable(feature = "pin", since = "1.33.0")] @@ -1933,7 +1945,7 @@ impl Default for Arc { /// assert_eq!(*x, 0); /// ``` fn default() -> Arc { - Arc::new(Default::default()) + Arc::new_in_place(T::default) } }