Skip to content

Commit a38d06e

Browse files
committed
Do not allocate for ZST ThinBox
1 parent 40aedab commit a38d06e

File tree

2 files changed

+103
-14
lines changed

2 files changed

+103
-14
lines changed

library/alloc/src/boxed/thin.rs

+101-14
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, SizedTypeProperties};
1113
use core::ops::{Deref, DerefMut};
1214
use core::ptr::Pointee;
1315
use core::ptr::{self, NonNull};
@@ -91,6 +93,88 @@ 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+
assert!(mem::size_of::<T>() == 0);
102+
103+
#[repr(C)]
104+
struct ReprC<A, B> {
105+
a: A,
106+
b: B,
107+
}
108+
109+
struct EmptyArray<T> {
110+
_array: [T; 0],
111+
}
112+
113+
// FIXME: empty array should be automatically `Freeze` for empty arrays.
114+
unsafe impl<T> Freeze for EmptyArray<T> {}
115+
116+
// Allocate header with padding in the beginning:
117+
// ```
118+
// [ padding | header ]
119+
// ```
120+
// where the struct is aligned to both header and value.
121+
#[repr(C)]
122+
struct AlignedHeader<H: Copy, T> {
123+
header_data: MaybeUninit<ReprC<H, EmptyArray<T>>>,
124+
}
125+
126+
impl<H: Copy + Freeze, T> AlignedHeader<H, T> {
127+
const fn make(header: H) -> Self {
128+
let mut data = MaybeUninit::<ReprC<H, EmptyArray<T>>>::zeroed();
129+
unsafe {
130+
data.as_mut_ptr().add(1).cast::<H>().sub(1).write(header);
131+
}
132+
AlignedHeader { header_data: data }
133+
}
134+
}
135+
136+
#[repr(C)]
137+
struct DynZstAlloc<T, Dyn: ?Sized> {
138+
header: AlignedHeader<<Dyn as Pointee>::Metadata, T>,
139+
value: EmptyArray<T>,
140+
}
141+
142+
impl<T, Dyn: ?Sized> DynZstAlloc<T, Dyn>
143+
where
144+
T: Unsize<Dyn>,
145+
{
146+
const ALLOCATION: DynZstAlloc<T, Dyn> = DynZstAlloc {
147+
header: AlignedHeader::make(ptr::metadata::<Dyn>(
148+
ptr::dangling::<T>() as *const Dyn
149+
)),
150+
value: EmptyArray { _array: [] },
151+
};
152+
153+
fn static_alloc<'a>() -> &'a DynZstAlloc<T, Dyn> {
154+
&Self::ALLOCATION
155+
}
156+
}
157+
158+
let alloc: &DynZstAlloc<T, Dyn> = DynZstAlloc::<T, Dyn>::static_alloc();
159+
160+
let value_offset = mem::offset_of!(DynZstAlloc<T, Dyn>, value);
161+
assert_eq!(value_offset, mem::size_of::<AlignedHeader<<Dyn as Pointee>::Metadata, T>>());
162+
163+
let ptr = WithOpaqueHeader(
164+
NonNull::new(
165+
// SAFETY: there's no overflow here because we add field offset.
166+
unsafe {
167+
(alloc as *const DynZstAlloc<T, Dyn> as *mut u8).add(value_offset) as *mut _
168+
},
169+
)
170+
.unwrap(),
171+
);
172+
let thin_box = ThinBox::<Dyn> { ptr, _marker: PhantomData };
173+
// Forget the value to avoid double drop.
174+
mem::forget(value);
175+
thin_box
176+
}
177+
94178
/// Moves a type to the heap with its [`Metadata`] stored in the heap allocation instead of on
95179
/// the stack.
96180
///
@@ -109,9 +193,13 @@ impl<Dyn: ?Sized> ThinBox<Dyn> {
109193
where
110194
T: Unsize<Dyn>,
111195
{
112-
let meta = ptr::metadata(&value as &Dyn);
113-
let ptr = WithOpaqueHeader::new(meta, value);
114-
ThinBox { ptr, _marker: PhantomData }
196+
if mem::size_of::<T>() == 0 {
197+
Self::new_unsize_zst(value)
198+
} else {
199+
let meta = ptr::metadata(&value as &Dyn);
200+
let ptr = WithOpaqueHeader::new(meta, value);
201+
ThinBox { ptr, _marker: PhantomData }
202+
}
115203
}
116204
}
117205

@@ -300,20 +388,19 @@ impl<H> WithHeader<H> {
300388

301389
impl<H> Drop for DropGuard<H> {
302390
fn drop(&mut self) {
391+
// All ZST are allocated statically.
392+
if self.value_layout.size() == 0 {
393+
return;
394+
}
395+
303396
unsafe {
304397
// SAFETY: Layout must have been computable if we're in drop
305398
let (layout, value_offset) =
306399
WithHeader::<H>::alloc_layout(self.value_layout).unwrap_unchecked();
307400

308-
// Note: Don't deallocate if the layout size is zero, because the pointer
309-
// 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-
}
401+
// Since we only allocate for non-ZSTs, the layout size cannot be zero.
402+
debug_assert!(layout.size() != 0);
403+
alloc::dealloc(self.ptr.as_ptr().sub(value_offset), layout);
317404
}
318405
}
319406
}

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)