Skip to content

Commit c5268ee

Browse files
committed
Do not allocate for ZST ThinBox
1 parent db2f975 commit c5268ee

File tree

2 files changed

+98
-13
lines changed

2 files changed

+98
-13
lines changed

library/alloc/src/boxed/thin.rs

+96-13
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ use core::error::Error;
66
use core::fmt::{self, Debug, Display, Formatter};
77
use core::marker::PhantomData;
88
#[cfg(not(no_global_oom_handling))]
9-
use core::marker::Unsize;
10-
use core::mem::{self, SizedTypeProperties};
9+
use core::marker::{Freeze, Unsize};
10+
use core::mem;
11+
#[cfg(not(no_global_oom_handling))]
12+
use core::mem::MaybeUninit;
1113
use core::ops::{Deref, DerefMut};
1214
use core::ptr::Pointee;
1315
use core::ptr::{self, NonNull};
@@ -91,6 +93,83 @@ impl<T> ThinBox<T> {
9193

9294
#[unstable(feature = "thin_box", issue = "92791")]
9395
impl<Dyn: ?Sized> ThinBox<Dyn> {
96+
#[cfg(not(no_global_oom_handling))]
97+
fn new_unsize_zst<T>(value: T) -> Self
98+
where
99+
T: Unsize<Dyn>,
100+
{
101+
#[repr(C)]
102+
struct ReprC<A, B> {
103+
a: A,
104+
b: B,
105+
}
106+
107+
// Allocate header like this:
108+
// ```
109+
// [ ... | header ]
110+
// ```
111+
// where the struct is aligned to both header and value.
112+
#[repr(C)]
113+
struct AlignedHeader<H: Copy, T> {
114+
header_data: MaybeUninit<ReprC<H, [T; 0]>>,
115+
}
116+
117+
impl<H: Copy, T> AlignedHeader<H, T> {
118+
const fn make(header: H) -> Self {
119+
let mut data = MaybeUninit::<ReprC<H, [T; 0]>>::zeroed();
120+
unsafe {
121+
data.as_mut_ptr().add(1).cast::<H>().sub(1).write(header);
122+
}
123+
AlignedHeader { header_data: data }
124+
}
125+
}
126+
127+
#[repr(C)]
128+
struct DynZstAlloc<T, Dyn: ?Sized> {
129+
header: AlignedHeader<<Dyn as Pointee>::Metadata, T>,
130+
value: MaybeUninit<T>,
131+
}
132+
133+
// We need `Freeze` so we could call `&DynZstAlloc::LAYOUT`.
134+
// SAFETY: data is immutable.
135+
unsafe impl<T, Dyn: ?Sized> Freeze for DynZstAlloc<T, Dyn> {}
136+
137+
impl<T, Dyn: ?Sized> DynZstAlloc<T, Dyn>
138+
where
139+
T: Unsize<Dyn>,
140+
{
141+
const LAYOUT: DynZstAlloc<T, Dyn> = DynZstAlloc {
142+
header: AlignedHeader::make(ptr::metadata::<Dyn>(
143+
ptr::dangling::<T>() as *const Dyn
144+
)),
145+
value: MaybeUninit::uninit(),
146+
};
147+
148+
fn static_alloc<'a>() -> &'a DynZstAlloc<T, Dyn> {
149+
&Self::LAYOUT
150+
}
151+
}
152+
153+
let alloc: &DynZstAlloc<T, Dyn> = DynZstAlloc::<T, Dyn>::static_alloc();
154+
155+
let value_offset = mem::offset_of!(DynZstAlloc<T, Dyn>, value);
156+
assert_eq!(value_offset, mem::size_of::<AlignedHeader<<Dyn as Pointee>::Metadata, T>>());
157+
158+
let ptr = WithOpaqueHeader(
159+
NonNull::new(
160+
// SAFETY: there's no overflow here because we add field offset.
161+
unsafe {
162+
(alloc as *const DynZstAlloc<T, Dyn> as *mut u8).add(value_offset) as *mut _
163+
},
164+
)
165+
.unwrap(),
166+
);
167+
let thin_box = ThinBox::<Dyn> { ptr, _marker: PhantomData };
168+
// Forget the value to avoid double drop.
169+
mem::forget(value);
170+
thin_box
171+
}
172+
94173
/// Moves a type to the heap with its [`Metadata`] stored in the heap allocation instead of on
95174
/// the stack.
96175
///
@@ -109,9 +188,13 @@ impl<Dyn: ?Sized> ThinBox<Dyn> {
109188
where
110189
T: Unsize<Dyn>,
111190
{
112-
let meta = ptr::metadata(&value as &Dyn);
113-
let ptr = WithOpaqueHeader::new(meta, value);
114-
ThinBox { ptr, _marker: PhantomData }
191+
if mem::size_of::<T>() == 0 && mem::size_of::<<Dyn as Pointee>::Metadata>() != 0 {
192+
Self::new_unsize_zst(value)
193+
} else {
194+
let meta = ptr::metadata(&value as &Dyn);
195+
let ptr = WithOpaqueHeader::new(meta, value);
196+
ThinBox { ptr, _marker: PhantomData }
197+
}
115198
}
116199
}
117200

@@ -155,7 +238,7 @@ impl<T: ?Sized> DerefMut for ThinBox<T> {
155238
impl<T: ?Sized> Drop for ThinBox<T> {
156239
fn drop(&mut self) {
157240
unsafe {
158-
let value = self.deref_mut();
241+
let value: &mut T = self.deref_mut();
159242
let value = value as *mut T;
160243
self.with_header().drop::<T>(value);
161244
}
@@ -300,20 +383,20 @@ impl<H> WithHeader<H> {
300383

301384
impl<H> Drop for DropGuard<H> {
302385
fn drop(&mut self) {
386+
// All ZST are allocated statically.
387+
if self.value_layout.size() == 0 {
388+
return;
389+
}
390+
303391
unsafe {
304392
// SAFETY: Layout must have been computable if we're in drop
305393
let (layout, value_offset) =
306394
WithHeader::<H>::alloc_layout(self.value_layout).unwrap_unchecked();
307395

308396
// Note: Don't deallocate if the layout size is zero, because the pointer
309397
// didn't come from the allocator.
310-
if layout.size() != 0 {
311-
alloc::dealloc(self.ptr.as_ptr().sub(value_offset), layout);
312-
} else {
313-
debug_assert!(
314-
value_offset == 0 && H::IS_ZST && self.value_layout.size() == 0
315-
);
316-
}
398+
debug_assert!(layout.size() != 0);
399+
alloc::dealloc(self.ptr.as_ptr().sub(value_offset), layout);
317400
}
318401
}
319402
}

library/alloc/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@
130130
#![feature(extend_one)]
131131
#![feature(fmt_internals)]
132132
#![feature(fn_traits)]
133+
#![feature(freeze)]
134+
#![feature(freeze_impls)]
133135
#![feature(generic_nonzero)]
134136
#![feature(hasher_prefixfree_extras)]
135137
#![feature(hint_assert_unchecked)]

0 commit comments

Comments
 (0)