Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 3df4fbb

Browse files
committedMar 29, 2024·
Do not allocate for ZST ThinBox
1 parent 40aedab commit 3df4fbb

File tree

2 files changed

+102
-14
lines changed

2 files changed

+102
-14
lines changed
 

‎library/alloc/src/boxed/thin.rs

+100-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,87 @@ 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+
unsafe impl<T> Freeze for EmptyArray<T> {}
114+
115+
// Allocate header with padding in the beginning:
116+
// ```
117+
// [ padding | header ]
118+
// ```
119+
// where the struct is aligned to both header and value.
120+
#[repr(C)]
121+
struct AlignedHeader<H: Copy, T> {
122+
header_data: MaybeUninit<ReprC<H, EmptyArray<T>>>,
123+
}
124+
125+
impl<H: Copy + Freeze, T> AlignedHeader<H, T> {
126+
const fn make(header: H) -> Self {
127+
let mut header_data = MaybeUninit::<ReprC<H, EmptyArray<T>>>::zeroed();
128+
unsafe {
129+
header_data.as_mut_ptr().add(1).cast::<H>().sub(1).write(header);
130+
}
131+
AlignedHeader { header_data }
132+
}
133+
}
134+
135+
#[repr(C)]
136+
struct DynZstAlloc<T, Dyn: ?Sized> {
137+
header: AlignedHeader<<Dyn as Pointee>::Metadata, T>,
138+
value: EmptyArray<T>,
139+
}
140+
141+
impl<T, Dyn: ?Sized> DynZstAlloc<T, Dyn>
142+
where
143+
T: Unsize<Dyn>,
144+
{
145+
const ALLOCATION: DynZstAlloc<T, Dyn> = DynZstAlloc {
146+
header: AlignedHeader::make(ptr::metadata::<Dyn>(
147+
ptr::dangling::<T>() as *const Dyn
148+
)),
149+
value: EmptyArray { _array: [] },
150+
};
151+
152+
fn static_alloc<'a>() -> &'a DynZstAlloc<T, Dyn> {
153+
&Self::ALLOCATION
154+
}
155+
}
156+
157+
let alloc: &DynZstAlloc<T, Dyn> = DynZstAlloc::<T, Dyn>::static_alloc();
158+
159+
let value_offset = mem::offset_of!(DynZstAlloc<T, Dyn>, value);
160+
assert_eq!(value_offset, mem::size_of::<AlignedHeader<<Dyn as Pointee>::Metadata, T>>());
161+
162+
let ptr = WithOpaqueHeader(
163+
NonNull::new(
164+
// SAFETY: there's no overflow here because we add field offset.
165+
unsafe {
166+
(alloc as *const DynZstAlloc<T, Dyn> as *mut u8).add(value_offset) as *mut _
167+
},
168+
)
169+
.unwrap(),
170+
);
171+
let thin_box = ThinBox::<Dyn> { ptr, _marker: PhantomData };
172+
// Forget the value to avoid double drop.
173+
mem::forget(value);
174+
thin_box
175+
}
176+
94177
/// Moves a type to the heap with its [`Metadata`] stored in the heap allocation instead of on
95178
/// the stack.
96179
///
@@ -109,9 +192,13 @@ impl<Dyn: ?Sized> ThinBox<Dyn> {
109192
where
110193
T: Unsize<Dyn>,
111194
{
112-
let meta = ptr::metadata(&value as &Dyn);
113-
let ptr = WithOpaqueHeader::new(meta, value);
114-
ThinBox { ptr, _marker: PhantomData }
195+
if mem::size_of::<T>() == 0 {
196+
Self::new_unsize_zst(value)
197+
} else {
198+
let meta = ptr::metadata(&value as &Dyn);
199+
let ptr = WithOpaqueHeader::new(meta, value);
200+
ThinBox { ptr, _marker: PhantomData }
201+
}
115202
}
116203
}
117204

@@ -300,20 +387,19 @@ impl<H> WithHeader<H> {
300387

301388
impl<H> Drop for DropGuard<H> {
302389
fn drop(&mut self) {
390+
// All ZST are allocated statically.
391+
if self.value_layout.size() == 0 {
392+
return;
393+
}
394+
303395
unsafe {
304396
// SAFETY: Layout must have been computable if we're in drop
305397
let (layout, value_offset) =
306398
WithHeader::<H>::alloc_layout(self.value_layout).unwrap_unchecked();
307399

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-
}
400+
// Since we only allocate for non-ZSTs, the layout size cannot be zero.
401+
debug_assert!(layout.size() != 0);
402+
alloc::dealloc(self.ptr.as_ptr().sub(value_offset), layout);
317403
}
318404
}
319405
}

‎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)
Please sign in to comment.