Skip to content

Commit 806e640

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

File tree

2 files changed

+108
-14
lines changed

2 files changed

+108
-14
lines changed

library/alloc/src/boxed/thin.rs

+106-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,93 @@ 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+
// SAFETY: this is a zero-sized type, there can't be any mutable memory here.
114+
// It's a private type so this can never leak to the user either.
115+
// If the user tried to write through the shared reference to this type,
116+
// they would be causing library UB as that's a write outside the memory
117+
// inhabited by their type (which is zero-sized). Therefore making this
118+
// language UB is justified.
119+
unsafe impl<T> Freeze for EmptyArray<T> {}
120+
121+
// Allocate header with padding in the beginning:
122+
// ```
123+
// [ padding | header ]
124+
// ```
125+
// where the struct is aligned to both header and value.
126+
#[repr(C)]
127+
struct AlignedHeader<H: Copy, T> {
128+
header_data: MaybeUninit<ReprC<H, EmptyArray<T>>>,
129+
}
130+
131+
impl<H: Copy + Freeze, T> AlignedHeader<H, T> {
132+
const fn make(header: H) -> Self {
133+
let mut header_data = MaybeUninit::<ReprC<H, EmptyArray<T>>>::zeroed();
134+
unsafe {
135+
header_data.as_mut_ptr().add(1).cast::<H>().sub(1).write(header);
136+
}
137+
AlignedHeader { header_data }
138+
}
139+
}
140+
141+
#[repr(C)]
142+
struct DynZstAlloc<T, Dyn: ?Sized> {
143+
header: AlignedHeader<<Dyn as Pointee>::Metadata, T>,
144+
value: EmptyArray<T>,
145+
}
146+
147+
impl<T, Dyn: ?Sized> DynZstAlloc<T, Dyn>
148+
where
149+
T: Unsize<Dyn>,
150+
{
151+
const ALLOCATION: DynZstAlloc<T, Dyn> = DynZstAlloc {
152+
header: AlignedHeader::make(ptr::metadata::<Dyn>(
153+
ptr::dangling::<T>() as *const Dyn
154+
)),
155+
value: EmptyArray { _array: [] },
156+
};
157+
158+
fn static_alloc<'a>() -> &'a DynZstAlloc<T, Dyn> {
159+
&Self::ALLOCATION
160+
}
161+
}
162+
163+
let alloc: &DynZstAlloc<T, Dyn> = DynZstAlloc::<T, Dyn>::static_alloc();
164+
165+
let value_offset = mem::offset_of!(DynZstAlloc<T, Dyn>, value);
166+
assert_eq!(value_offset, mem::size_of::<AlignedHeader<<Dyn as Pointee>::Metadata, T>>());
167+
168+
let ptr = WithOpaqueHeader(
169+
NonNull::new(
170+
// SAFETY: there's no overflow here because we add field offset.
171+
unsafe {
172+
(alloc as *const DynZstAlloc<T, Dyn> as *mut u8).add(value_offset) as *mut _
173+
},
174+
)
175+
.unwrap(),
176+
);
177+
let thin_box = ThinBox::<Dyn> { ptr, _marker: PhantomData };
178+
// Forget the value to avoid double drop.
179+
mem::forget(value);
180+
thin_box
181+
}
182+
94183
/// Moves a type to the heap with its [`Metadata`] stored in the heap allocation instead of on
95184
/// the stack.
96185
///
@@ -109,9 +198,13 @@ impl<Dyn: ?Sized> ThinBox<Dyn> {
109198
where
110199
T: Unsize<Dyn>,
111200
{
112-
let meta = ptr::metadata(&value as &Dyn);
113-
let ptr = WithOpaqueHeader::new(meta, value);
114-
ThinBox { ptr, _marker: PhantomData }
201+
if mem::size_of::<T>() == 0 {
202+
Self::new_unsize_zst(value)
203+
} else {
204+
let meta = ptr::metadata(&value as &Dyn);
205+
let ptr = WithOpaqueHeader::new(meta, value);
206+
ThinBox { ptr, _marker: PhantomData }
207+
}
115208
}
116209
}
117210

@@ -300,20 +393,19 @@ impl<H> WithHeader<H> {
300393

301394
impl<H> Drop for DropGuard<H> {
302395
fn drop(&mut self) {
396+
// All ZST are allocated statically.
397+
if self.value_layout.size() == 0 {
398+
return;
399+
}
400+
303401
unsafe {
304402
// SAFETY: Layout must have been computable if we're in drop
305403
let (layout, value_offset) =
306404
WithHeader::<H>::alloc_layout(self.value_layout).unwrap_unchecked();
307405

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-
}
406+
// Since we only allocate for non-ZSTs, the layout size cannot be zero.
407+
debug_assert!(layout.size() != 0);
408+
alloc::dealloc(self.ptr.as_ptr().sub(value_offset), layout);
317409
}
318410
}
319411
}

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)