Simplify internals of {Rc,Arc}::default

This commit simplifies the internal implementation of `Default` for
these two pointer types to have the same performance characteristics as
before (a side effect of changes in 131460) while avoid use of internal
private APIs of Rc/Arc. To preserve the same codegen as before some
non-generic functions needed to be tagged as `#[inline]` as well, but
otherwise the same IR is produced before/after this change.

The motivation of this commit is I was studying up on the state of
initialization of `Arc` and `Rc` and figured it'd be nicer to reduce the
use of internal APIs and instead use public stable APIs where possible,
even in the implementation itself.
This commit is contained in:
Alex Crichton
2026-02-13 14:55:54 -08:00
parent d7daac06d8
commit ce4c17f615
3 changed files with 42 additions and 24 deletions
+18 -7
View File
@@ -289,6 +289,7 @@ struct RcInner<T: ?Sized> {
}
/// Calculate layout for `RcInner<T>` using the inner value's layout
#[inline]
fn rc_inner_layout_for_value_layout(layout: Layout) -> Layout {
// Calculate layout using the given value layout.
// Previously, layout was calculated on the expression
@@ -2518,15 +2519,25 @@ impl<T: Default> Default for Rc<T> {
/// ```
#[inline]
fn default() -> Self {
// First create an uninitialized allocation before creating an instance
// of `T`. This avoids having `T` on the stack and avoids the need to
// codegen a call to the destructor for `T` leading to generally better
// codegen. See #131460 for some more details.
let mut rc = Rc::new_uninit();
// SAFETY: this is a freshly allocated `Rc` so it's guaranteed there are
// no other strong or weak pointers other than `rc` itself.
unsafe {
Self::from_inner(
Box::leak(Box::write(
Box::new_uninit(),
RcInner { strong: Cell::new(1), weak: Cell::new(1), value: T::default() },
))
.into(),
)
let raw = Rc::get_mut_unchecked(&mut rc);
// Note that `ptr::write` here is used specifically instead of
// `MaybeUninit::write` to avoid creating an extra stack copy of `T`
// in debug mode. See #136043 for more context.
ptr::write(raw.as_mut_ptr(), T::default());
}
// SAFETY: this allocation was just initialized above.
unsafe { rc.assume_init() }
}
}
+18 -11
View File
@@ -392,6 +392,7 @@ struct ArcInner<T: ?Sized> {
}
/// Calculate layout for `ArcInner<T>` using the inner value's layout
#[inline]
fn arcinner_layout_for_value_layout(layout: Layout) -> Layout {
// Calculate layout using the given value layout.
// Previously, layout was calculated on the expression
@@ -3724,19 +3725,25 @@ impl<T: Default> Default for Arc<T> {
/// assert_eq!(*x, 0);
/// ```
fn default() -> Arc<T> {
// First create an uninitialized allocation before creating an instance
// of `T`. This avoids having `T` on the stack and avoids the need to
// codegen a call to the destructor for `T` leading to generally better
// codegen. See #131460 for some more details.
let mut arc = Arc::new_uninit();
// SAFETY: this is a freshly allocated `Arc` so it's guaranteed there
// are no other strong or weak pointers other than `arc` itself.
unsafe {
Self::from_inner(
Box::leak(Box::write(
Box::new_uninit(),
ArcInner {
strong: atomic::AtomicUsize::new(1),
weak: atomic::AtomicUsize::new(1),
data: T::default(),
},
))
.into(),
)
let raw = Arc::get_mut_unchecked(&mut arc);
// Note that `ptr::write` here is used specifically instead of
// `MaybeUninit::write` to avoid creating an extra stack copy of `T`
// in debug mode. See #136043 for more context.
ptr::write(raw.as_mut_ptr(), T::default());
}
// SAFETY: this allocation was just initialized above.
unsafe { arc.assume_init() }
}
}
+6 -6
View File
@@ -10,9 +10,9 @@
pub fn new_from_array(x: u64) -> Arc<[u64]> {
// Ensure that we only generate one alloca for the array.
// CHECK: alloca
// CHECK: %[[A:.+]] = alloca
// CHECK-SAME: [8000 x i8]
// CHECK-NOT: alloca
// CHECK-NOT: %[[B:.+]] = alloca
let array = [x; 1000];
Arc::new(array)
}
@@ -20,8 +20,9 @@ pub fn new_from_array(x: u64) -> Arc<[u64]> {
// CHECK-LABEL: @new_uninit
#[no_mangle]
pub fn new_uninit(x: u64) -> Arc<[u64; 1000]> {
// CHECK: call alloc::sync::arcinner_layout_for_value_layout
// CHECK-NOT: call alloc::sync::arcinner_layout_for_value_layout
// CHECK: %[[A:.+]] = alloca
// CHECK-SAME: [8000 x i8]
// CHECK-NOT: %[[B:.+]] = alloca
let mut arc = Arc::new_uninit();
unsafe { Arc::get_mut_unchecked(&mut arc) }.write([x; 1000]);
unsafe { arc.assume_init() }
@@ -30,8 +31,7 @@ pub fn new_from_array(x: u64) -> Arc<[u64]> {
// CHECK-LABEL: @new_uninit_slice
#[no_mangle]
pub fn new_uninit_slice(x: u64) -> Arc<[u64]> {
// CHECK: call alloc::sync::arcinner_layout_for_value_layout
// CHECK-NOT: call alloc::sync::arcinner_layout_for_value_layout
// CHECK-NOT: %[[B:.+]] = alloca
let mut arc = Arc::new_uninit_slice(1000);
for elem in unsafe { Arc::get_mut_unchecked(&mut arc) } {
elem.write(x);