Skip to content

Commit d24be14

Browse files
committed
Eliminate ZST allocations in Box and Vec
1 parent 27e10c5 commit d24be14

File tree

4 files changed

+115
-26
lines changed

4 files changed

+115
-26
lines changed

library/alloc/src/boxed.rs

+29-15
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,12 @@ use core::hash::{Hash, Hasher};
157157
use core::iter::FusedIterator;
158158
use core::marker::Tuple;
159159
use core::marker::Unsize;
160-
use core::mem;
160+
use core::mem::{self, SizedTypeProperties};
161161
use core::ops::{
162162
CoerceUnsized, Deref, DerefMut, DispatchFromDyn, Generator, GeneratorState, Receiver,
163163
};
164164
use core::pin::Pin;
165-
use core::ptr::{self, Unique};
165+
use core::ptr::{self, NonNull, Unique};
166166
use core::task::{Context, Poll};
167167

168168
#[cfg(not(no_global_oom_handling))]
@@ -479,8 +479,12 @@ impl<T, A: Allocator> Box<T, A> {
479479
where
480480
A: Allocator,
481481
{
482-
let layout = Layout::new::<mem::MaybeUninit<T>>();
483-
let ptr = alloc.allocate(layout)?.cast();
482+
let ptr = if T::IS_ZST {
483+
NonNull::dangling()
484+
} else {
485+
let layout = Layout::new::<mem::MaybeUninit<T>>();
486+
alloc.allocate(layout)?.cast()
487+
};
484488
unsafe { Ok(Box::from_raw_in(ptr.as_ptr(), alloc)) }
485489
}
486490

@@ -549,8 +553,12 @@ impl<T, A: Allocator> Box<T, A> {
549553
where
550554
A: Allocator,
551555
{
552-
let layout = Layout::new::<mem::MaybeUninit<T>>();
553-
let ptr = alloc.allocate_zeroed(layout)?.cast();
556+
let ptr = if T::IS_ZST {
557+
NonNull::dangling()
558+
} else {
559+
let layout = Layout::new::<mem::MaybeUninit<T>>();
560+
alloc.allocate_zeroed(layout)?.cast()
561+
};
554562
unsafe { Ok(Box::from_raw_in(ptr.as_ptr(), alloc)) }
555563
}
556564

@@ -675,14 +683,16 @@ impl<T> Box<[T]> {
675683
#[unstable(feature = "allocator_api", issue = "32838")]
676684
#[inline]
677685
pub fn try_new_uninit_slice(len: usize) -> Result<Box<[mem::MaybeUninit<T>]>, AllocError> {
678-
unsafe {
686+
let ptr = if T::IS_ZST || len == 0 {
687+
NonNull::dangling()
688+
} else {
679689
let layout = match Layout::array::<mem::MaybeUninit<T>>(len) {
680690
Ok(l) => l,
681691
Err(_) => return Err(AllocError),
682692
};
683-
let ptr = Global.allocate(layout)?;
684-
Ok(RawVec::from_raw_parts_in(ptr.as_mut_ptr() as *mut _, len, Global).into_box(len))
685-
}
693+
Global.allocate(layout)?.cast()
694+
};
695+
unsafe { Ok(RawVec::from_raw_parts_in(ptr.as_ptr(), len, Global).into_box(len)) }
686696
}
687697

688698
/// Constructs a new boxed slice with uninitialized contents, with the memory
@@ -707,14 +717,16 @@ impl<T> Box<[T]> {
707717
#[unstable(feature = "allocator_api", issue = "32838")]
708718
#[inline]
709719
pub fn try_new_zeroed_slice(len: usize) -> Result<Box<[mem::MaybeUninit<T>]>, AllocError> {
710-
unsafe {
720+
let ptr = if T::IS_ZST || len == 0 {
721+
NonNull::dangling()
722+
} else {
711723
let layout = match Layout::array::<mem::MaybeUninit<T>>(len) {
712724
Ok(l) => l,
713725
Err(_) => return Err(AllocError),
714726
};
715-
let ptr = Global.allocate_zeroed(layout)?;
716-
Ok(RawVec::from_raw_parts_in(ptr.as_mut_ptr() as *mut _, len, Global).into_box(len))
717-
}
727+
Global.allocate_zeroed(layout)?.cast()
728+
};
729+
unsafe { Ok(RawVec::from_raw_parts_in(ptr.as_ptr(), len, Global).into_box(len)) }
718730
}
719731
}
720732

@@ -1219,7 +1231,9 @@ unsafe impl<#[may_dangle] T: ?Sized, A: Allocator> Drop for Box<T, A> {
12191231

12201232
unsafe {
12211233
let layout = Layout::for_value_raw(ptr.as_ptr());
1222-
self.1.deallocate(From::from(ptr.cast()), layout)
1234+
if layout.size() != 0 {
1235+
self.1.deallocate(From::from(ptr.cast()), layout);
1236+
}
12231237
}
12241238
}
12251239
}

library/alloc/src/raw_vec.rs

+20-10
Original file line numberDiff line numberDiff line change
@@ -432,16 +432,26 @@ impl<T, A: Allocator> RawVec<T, A> {
432432
let (ptr, layout) = if let Some(mem) = self.current_memory() { mem } else { return Ok(()) };
433433
// See current_memory() why this assert is here
434434
let _: () = const { assert!(mem::size_of::<T>() % mem::align_of::<T>() == 0) };
435-
let ptr = unsafe {
436-
// `Layout::array` cannot overflow here because it would have
437-
// overflowed earlier when capacity was larger.
438-
let new_size = mem::size_of::<T>().unchecked_mul(cap);
439-
let new_layout = Layout::from_size_align_unchecked(new_size, layout.align());
440-
self.alloc
441-
.shrink(ptr, layout, new_layout)
442-
.map_err(|_| AllocError { layout: new_layout, non_exhaustive: () })?
443-
};
444-
self.set_ptr_and_cap(ptr, cap);
435+
436+
// If shrinking to 0, deallocate the buffer. We don't reach this point
437+
// for the T::IS_ZST case since current_memory() will have returned
438+
// None.
439+
if cap == 0 {
440+
unsafe { self.alloc.deallocate(ptr, layout) };
441+
self.ptr = Unique::dangling();
442+
self.cap = 0;
443+
} else {
444+
let ptr = unsafe {
445+
// `Layout::array` cannot overflow here because it would have
446+
// overflowed earlier when capacity was larger.
447+
let new_size = mem::size_of::<T>().unchecked_mul(cap);
448+
let new_layout = Layout::from_size_align_unchecked(new_size, layout.align());
449+
self.alloc
450+
.shrink(ptr, layout, new_layout)
451+
.map_err(|_| AllocError { layout: new_layout, non_exhaustive: () })?
452+
};
453+
self.set_ptr_and_cap(ptr, cap);
454+
}
445455
Ok(())
446456
}
447457
}

library/alloc/tests/vec.rs

+65
Original file line numberDiff line numberDiff line change
@@ -2497,3 +2497,68 @@ fn test_into_flattened_size_overflow() {
24972497
let v = vec![[(); usize::MAX]; 2];
24982498
let _ = v.into_flattened();
24992499
}
2500+
2501+
#[cfg(not(bootstrap))]
2502+
#[test]
2503+
fn test_box_zero_allocator() {
2504+
use core::{alloc::AllocError, cell::RefCell};
2505+
use std::collections::HashSet;
2506+
2507+
// Track ZST allocations and ensure that they all have a matching free.
2508+
struct ZstTracker {
2509+
state: RefCell<(HashSet<usize>, usize)>,
2510+
}
2511+
unsafe impl Allocator for ZstTracker {
2512+
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
2513+
let ptr = if layout.size() == 0 {
2514+
let mut state = self.state.borrow_mut();
2515+
let addr = state.1;
2516+
assert!(state.0.insert(addr));
2517+
state.1 += 1;
2518+
std::println!("allocating {addr}");
2519+
std::ptr::invalid_mut(addr)
2520+
} else {
2521+
unsafe { std::alloc::alloc(layout) }
2522+
};
2523+
Ok(NonNull::slice_from_raw_parts(NonNull::new(ptr).ok_or(AllocError)?, layout.size()))
2524+
}
2525+
2526+
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
2527+
if layout.size() == 0 {
2528+
let addr = ptr.as_ptr() as usize;
2529+
let mut state = self.state.borrow_mut();
2530+
std::println!("freeing {addr}");
2531+
assert!(state.0.remove(&addr), "ZST free that wasn't allocated");
2532+
} else {
2533+
unsafe { std::alloc::dealloc(ptr.as_ptr(), layout) }
2534+
}
2535+
}
2536+
}
2537+
2538+
// Start the state at 100 to avoid returning null pointers.
2539+
let alloc = ZstTracker { state: RefCell::new((HashSet::new(), 100)) };
2540+
2541+
// Ensure that unsizing retains the same behavior.
2542+
{
2543+
let b1: Box<[u8; 0], &ZstTracker> = Box::new_in([], &alloc);
2544+
let b2: Box<[u8], &ZstTracker> = b1.clone();
2545+
let _b3: Box<[u8], &ZstTracker> = b2.clone();
2546+
}
2547+
2548+
// Ensure that shrinking doesn't leak a ZST allocation.
2549+
{
2550+
let mut v1: Vec<u8, &ZstTracker> = Vec::with_capacity_in(100, &alloc);
2551+
v1.shrink_to_fit();
2552+
}
2553+
2554+
// Ensure that conversion to/from vec works.
2555+
{
2556+
let v1: Vec<(), &ZstTracker> = Vec::with_capacity_in(100, &alloc);
2557+
let _b1: Box<[()], &ZstTracker> = v1.into_boxed_slice();
2558+
let b2: Box<[()], &ZstTracker> = Box::new_in([(), (), ()], &alloc);
2559+
let _v2: Vec<(), &ZstTracker> = b2.into();
2560+
}
2561+
2562+
// Ensure all ZSTs have been freed.
2563+
assert!(alloc.state.borrow().0.is_empty());
2564+
}
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
thread 'main' panicked at 'capacity overflow', library/alloc/src/raw_vec.rs:524:5
1+
thread 'main' panicked at 'capacity overflow', library/alloc/src/raw_vec.rs:534:5
22
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

0 commit comments

Comments
 (0)