Skip to content

Commit 2048773

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

File tree

2 files changed

+96
-13
lines changed

2 files changed

+96
-13
lines changed

library/alloc/src/boxed/thin.rs

+94-13
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
use crate::alloc::{self, Layout, LayoutError};
55
use core::error::Error;
66
use core::fmt::{self, Debug, Display, Formatter};
7-
use core::marker::PhantomData;
87
#[cfg(not(no_global_oom_handling))]
98
use core::marker::Unsize;
10-
use core::mem::{self, SizedTypeProperties};
9+
use core::marker::{Freeze, PhantomData};
10+
use core::mem::{self, MaybeUninit, SizedTypeProperties};
1111
use core::ops::{Deref, DerefMut};
1212
use core::ptr::Pointee;
1313
use core::ptr::{self, NonNull};
@@ -91,6 +91,83 @@ impl<T> ThinBox<T> {
9191

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

@@ -155,7 +236,7 @@ impl<T: ?Sized> DerefMut for ThinBox<T> {
155236
impl<T: ?Sized> Drop for ThinBox<T> {
156237
fn drop(&mut self) {
157238
unsafe {
158-
let value = self.deref_mut();
239+
let value: &mut T = self.deref_mut();
159240
let value = value as *mut T;
160241
self.with_header().drop::<T>(value);
161242
}
@@ -300,20 +381,20 @@ impl<H> WithHeader<H> {
300381

301382
impl<H> Drop for DropGuard<H> {
302383
fn drop(&mut self) {
384+
// All ZST are allocated statically.
385+
if self.value_layout.size() == 0 {
386+
return;
387+
}
388+
303389
unsafe {
304390
// SAFETY: Layout must have been computable if we're in drop
305391
let (layout, value_offset) =
306392
WithHeader::<H>::alloc_layout(self.value_layout).unwrap_unchecked();
307393

308394
// Note: Don't deallocate if the layout size is zero, because the pointer
309395
// 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-
}
396+
debug_assert!(layout.size() != 0);
397+
alloc::dealloc(self.ptr.as_ptr().sub(value_offset), layout);
317398
}
318399
}
319400
}

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)