deallocate_zeroed/zero_aware_allocator.rs
1//! The zero-aware memory allocator.
2//!
3//! Zeroed memory is organized into a 2-layer freelist to facillitate fast and
4//! easy allocation:
5//!
6//! 1. By alignment, in a fixed-size array.
7//! 2. By size, in a dynamically-sized splay tree.
8//!
9//! When allocating a block, we use a hybrid best- and first-fit search:
10//!
11//! * First, we prioritize memory blocks with the minimum-satisfying alignment
12//! before considering blocks with alignment greater than requested. This
13//! helps us avoid "wasting" a rare, very-aligned block on an allocation that
14//! doesn't have any particular alignment requirements.
15//!
16//! This prioritization is implemented via a loop over each entry in the
17//! fixed-size array (or "align-class") starting with the align-class that
18//! exactly matches the requested allocation's alignment and moving towards
19//! larger and larger alignments until we find a block that satisfies the
20//! allocation constraints or we fail to allocate.
21//!
22//! * Second, we search for memory blocks that will fit the requested allocation
23//! size, without introducing too much internal fragmentation. We care about
24//! fragmentation because we do not split blocks. Splitting blocks would
25//! require either that the underlying allocator support block splitting
26//! (which it might not and the `Allocator` trait doesn't have methods for
27//! deallocating a split-off chunk of previously allocated block anyways) or
28//! else additional bookkeeping on our part to track when a block is split or
29//! not and whether it can be merged again and then returned to the underlying
30//! allocator.
31
32use core::ptr;
33
34use super::*;
35use metadata::{BlockInfo, FreeList, LiveSet};
36
37mod mutex;
38pub use mutex::{LockingMechanism, Mutex, MutexGuard, SingleThreadedLockingMechanism};
39
40/// The maximum alignment for which we have an align-class.
41const MAX_ALIGN_WITH_CLASS: usize = 4096;
42
43/// The number of align-classes we have.
44const NUM_ALIGN_CLASSES: usize = MAX_ALIGN_WITH_CLASS.ilog2() as usize + 1;
45
46/// We allow satisfying an allocation with a block that is only up to
47///
48/// ```ignore
49/// 1 / ACCEPTABLE_WASTE_DIVISOR
50/// ```
51///
52/// bytes larger than the requested allocation's size.
53///
54/// This balances fragmentation (because we do not split blocks) with allocation
55/// efficiency and how deep in a freelist we will keep searching despite having
56/// seen a block that technically could satisfy the allocation (i.e. best- vs
57/// first-fit). Making this value larger will decrease fragmentation but
58/// increase time spent searching freelists and the probability we will ask the
59/// underlying allocator for a new zeroed block, rather than reusing an
60/// already-zeroed block; making it smaller has the opposite effects.
61///
62/// NB: keep this a power of two so that the compiler can strength-reduce the
63/// division into a shift.
64const ACCEPTABLE_WASTE_DIVISOR: usize = 8;
65
66/// A memory allocator that keeps track of already-zeroed memory blocks.
67///
68/// This lets applications move zeroing off of the `allocate_zeroed` critical
69/// path. Instead they can, for example, bulk-zero memory blocks in the
70/// background before returning them to the allocator.
71///
72/// This allocator wraps an underinglying, inner allocator of type `A`, layering
73/// the bookkeeping of already-zeroed blocks on top of it.
74///
75/// Because this crate is `no_std` and does not assume the presence of an
76/// operating system, you must provide your own locking mechanism via the `L`
77/// type parameter. See the [`LockingMechanism`] trait for details.
78#[derive(Default)]
79pub struct ZeroAwareAllocator<A, L>
80where
81 A: Allocator,
82 L: LockingMechanism,
83{
84 /// The underlying allocator.
85 inner: A,
86
87 /// Metadata keeping track of already-zeroed memory.
88 zeroed: Mutex<Zeroed, L>,
89}
90
91#[derive(Default)]
92struct Zeroed {
93 /// Already-zeroed memory blocks, organized into align-classes.
94 align_classes: [FreeList; NUM_ALIGN_CLASSES],
95
96 /// A fallback freelist for already-zeroed memory blocks that have alignment
97 /// larger than we have an align-class for.
98 very_large_aligns: FreeList,
99
100 /// The set of live allocations we've handed out to the user from one of our
101 /// free-lists.
102 ///
103 /// We maintain this set because the `Layout` that the block was originally
104 /// allocated from `inner` with is not necessarily the same `Layout` that
105 /// the user gave us when they asked for an already-zereod block of
106 /// memory. Consider the following sequence of events, for example:
107 ///
108 /// * `deallocate_zeroed(0x12345678, size = 64KiB)`
109 ///
110 /// Now we have an already-zeroed entry for this allocation in one of our
111 /// free-lists.
112 ///
113 /// * `allocate_zeroed(size = 62KiB) -> 0x12345678`
114 ///
115 /// This new allocation's requested size isn't an exact match for our
116 /// free-list entry, but is close enough that it is worth accepting a
117 /// little bit of fragmentation slop rather than spending time zeroing
118 /// 62KiB of memory.
119 ///
120 /// * `grow(0x12345678, old_size=62KiB, new_size=...)`
121 ///
122 /// When the user grows their allocation of "62KiB", we need to realize
123 /// that it is actually 64KiB in size and act accordingly.
124 ///
125 /// It is critical that whenever we call into the `inner` allocator, we pass
126 /// the allocation's actual, original `Layout`. Passing the wrong layout is
127 /// a violation of the `Allocator` API's safety contract, so we could
128 /// trigger UB if we aren't careful here.
129 live_set: LiveSet,
130}
131
132impl<A, L> ZeroAwareAllocator<A, L>
133where
134 A: Allocator,
135 L: LockingMechanism,
136{
137 /// Create a new `ZeroAwareAllocator` that wraps the given `inner`
138 /// allocator.
139 #[inline]
140 pub const fn new(inner: A, lock: L) -> Self {
141 let zeroed = Zeroed {
142 align_classes: [const { FreeList::new() }; NUM_ALIGN_CLASSES],
143 very_large_aligns: FreeList::new(),
144 live_set: LiveSet::new(),
145 };
146 let zeroed = Mutex::new(zeroed, lock);
147 ZeroAwareAllocator { inner, zeroed }
148 }
149
150 /// Get a shared reference to the inner allocator.
151 #[inline]
152 pub fn inner(&self) -> &A {
153 &self.inner
154 }
155
156 /// Get an exclusive reference to the inner allocator.
157 #[inline]
158 pub fn inner_mut(&mut self) -> &mut A {
159 &mut self.inner
160 }
161
162 /// Return all of the known-zeroed memory blocks to the underlying
163 /// allocator.
164 pub fn return_zeroed_memory_to_inner(&mut self) {
165 let mut zeroed = self.zeroed.lock();
166 let zeroed = &mut *zeroed;
167
168 for freelist in zeroed
169 .align_classes
170 .iter_mut()
171 .chain(Some(&mut zeroed.very_large_aligns))
172 {
173 while let Some(node) = freelist.pop_root() {
174 let block_ptr = node.ptr();
175 let block_layout = node.layout();
176
177 // Safety: the node has been removed from the freelist, was
178 // allocated with `self.inner`, and is currently allocated.
179 unsafe {
180 self.deallocate_block_info(node);
181 }
182
183 // Safety: the block is currently allocated (from the inner
184 // allocator's point of view), has the associated layout, and
185 // nothing else is referencing this block anymore now that its
186 // node has been deallocated.
187 unsafe { self.inner.deallocate(block_ptr, block_layout) };
188 }
189 }
190 }
191
192 /// Allocate a new `BlockInfo` for our internal bookkeeping for the
193 /// given already-zeroed block described by `ptr` and `layout`.
194 fn allocate_block_info(
195 &self,
196 ptr: NonNull<u8>,
197 layout: Layout,
198 ) -> Result<&'static BlockInfo<'static>, AllocError> {
199 let node_ptr = self.inner.allocate(Layout::new::<BlockInfo<'_>>())?;
200 let node_ptr = node_ptr.cast::<BlockInfo<'_>>();
201 // Safety: `node_ptr` is valid for writes, is properly aligned, and is
202 // valid for conversion to a reference.
203 unsafe {
204 node_ptr.write(BlockInfo::new(ptr, layout));
205 Ok(node_ptr.as_ref())
206 }
207 }
208
209 /// Deallocate a `BlockInfo` freelist node.
210 ///
211 /// ### Safety
212 ///
213 /// * The node must not be in a freelist or otherwise referenced by anything
214 /// else.
215 ///
216 /// * The node must have been allocated with `self.inner`.
217 ///
218 /// * The node must be currently allocated.
219 unsafe fn deallocate_block_info(&self, node: &'static BlockInfo<'static>) {
220 self.inner
221 .deallocate(NonNull::from(node).cast(), Layout::new::<BlockInfo<'_>>());
222 }
223
224 /// Attempt to allocate an already-zeroed block of the given layout.
225 ///
226 /// Does not fallback to the inner allocator upon allocation failure.
227 #[inline]
228 fn allocate_already_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
229 debug_assert_ne!(layout.size(), 0);
230
231 // The index of this layout's align-class, if we have one.
232 let align_class = layout.align().ilog2() as usize;
233
234 {
235 let mut zeroed = self.zeroed.lock();
236 let zeroed = &mut *zeroed;
237
238 // Start at the layout's align-class and try to find a suitably-sized
239 // block for the layout, moving to larger and larger align-classes as
240 // necessary.
241 let freelists = zeroed
242 .align_classes
243 .get_mut(align_class..NUM_ALIGN_CLASSES)
244 .into_iter()
245 .flat_map(|classes| classes.iter_mut())
246 .chain(Some(&mut zeroed.very_large_aligns));
247
248 for freelist in freelists {
249 // Look for a block that satisfies this allocation layout. If we
250 // find one, then deallocate our metadata with the underlying
251 // allocator and return the block.
252 if let Some(node) = freelist.remove(&layout) {
253 let ret = node.non_null_slice_ptr();
254
255 // Correctly sized.
256 debug_assert!(
257 ret.len() >= layout.size(),
258 "{ret:#p}'s size should be greater than or equal to user layout's size\n\
259 actual size = {}\n\
260 user layout's size = {}",
261 ret.len(),
262 layout.size()
263 );
264 debug_assert!(
265 ret.len() >= node.layout().size(),
266 "{ret:#p}'s size should be greater than or equal to its original layout's size\n\
267 actual size = {}\n\
268 original layout's size = {}",
269 ret.len(),
270 layout.size()
271 );
272
273 // Correctly aligned.
274 debug_assert_eq!(
275 ret.cast::<u8>().as_ptr() as usize % layout.align(),
276 0,
277 "{ret:#p} should be aligned to user layout's alignment of {:#x}",
278 layout.align()
279 );
280 debug_assert_eq!(
281 ret.cast::<u8>().as_ptr() as usize % node.layout().align(),
282 0,
283 "{ret:#p} should be aligned to user layout's alignment of {:#x}",
284 node.layout().align()
285 );
286
287 // Correctly zeroed.
288 debug_assert!({
289 let slice = unsafe {
290 core::slice::from_raw_parts(
291 node.ptr().as_ptr().cast_const(),
292 node.layout().size(),
293 )
294 };
295 slice.iter().all(|b| *b == 0)
296 });
297
298 zeroed.live_set.insert(node);
299 return Ok(ret);
300 }
301 }
302 }
303
304 // Failed to find an already-zeroed block that satisfied the requested
305 // layout.
306 Err(AllocError)
307 }
308
309 #[inline]
310 unsafe fn deallocate_already_zeroed(&self, ptr: NonNull<u8>, layout: Layout) {
311 // Correctly sized.
312 debug_assert_ne!(layout.size(), 0);
313
314 // Correctly aligned.
315 debug_assert_eq!(ptr.as_ptr() as usize % layout.align(), 0);
316
317 // Correctly zeroed.
318 debug_assert!({
319 let slice = core::slice::from_raw_parts(ptr.as_ptr().cast_const(), layout.size());
320 slice.iter().all(|b| *b == 0)
321 });
322
323 let mut zeroed = self.zeroed.lock();
324 let zeroed = &mut *zeroed;
325 let node = match zeroed.live_set.remove(&ptr) {
326 Some(node) => {
327 debug_assert!(
328 node.layout().size() >= layout.size(),
329 "actual size should be greater than or equal to user's size\n\
330 actual size = {}\n\
331 user's size = {}",
332 node.layout().size(),
333 layout.size(),
334 );
335
336 // NB: the allocation might just happen to be aligned to the
337 // user layout, in which case it may *not* have been the case
338 // that the actual layout's alignment is greater than or equal
339 // to the user layout's alignment.
340 //
341 // The pointer itself must be suitably aligned to the layout
342 // either way, however.
343 debug_assert_eq!(ptr.as_ptr() as usize % node.layout().align(), 0);
344
345 // And any fragmentation we had accepted still be zeroed.
346 debug_assert!({
347 let slice = core::slice::from_raw_parts(
348 ptr.add(layout.size()).as_ptr().cast_const(),
349 node.layout().size() - layout.size(),
350 );
351 slice.iter().all(|b| *b == 0)
352 });
353
354 node
355 }
356 None => match self.allocate_block_info(ptr, layout) {
357 Ok(node) => node,
358 // If the inner allocator fails to allocate the block info that
359 // we need for bookkeeping, then we can't keep track of this
360 // already-zeroed block, so simply eagerly return it to the
361 // inner allocator.
362 Err(_) => {
363 self.inner.deallocate(ptr, layout);
364 return;
365 }
366 },
367 };
368
369 // Get the appropriate freelist for this block's align-class.
370 let align_class = node.layout().align().ilog2() as usize;
371 let freelist = zeroed
372 .align_classes
373 .get_mut(align_class)
374 .unwrap_or_else(|| &mut zeroed.very_large_aligns);
375
376 // Insert the block into its freelist.
377 freelist.insert(node);
378 }
379
380 /// Before a `grow` or `grow_zeroed`, check whether we have the pointer in
381 /// our live-set. If it already satisfies the new size requirements, reuse
382 /// it. Otherwise, remove it from our live-set, as we will need to pass it
383 /// to the underlying allocator or re-allocate it.
384 unsafe fn pre_grow(
385 &self,
386 ptr: NonNull<u8>,
387 user_old_layout: Layout,
388 new_layout: Layout,
389 ) -> PreGrow {
390 // Correctly sized.
391 debug_assert_ne!(user_old_layout.size(), 0);
392
393 // Correctly aligned.
394 debug_assert_eq!(
395 ptr.as_ptr() as usize % user_old_layout.align(),
396 0,
397 "{ptr:#p} should be aligned to user's layout's alignment of {:#x}",
398 user_old_layout.align()
399 );
400
401 let mut zeroed = self.zeroed.lock();
402 let zeroed = &mut *zeroed;
403
404 let actual_old_layout = if let Some(node) = zeroed.live_set.find(&ptr) {
405 debug_assert_eq!(node.ptr(), ptr);
406
407 // Correctly sized.
408 debug_assert!(node.layout().size() >= user_old_layout.size());
409
410 // Correctly aligned.
411 //
412 // NB: it is not necessarily the case that the original layout's
413 // align is greater than or equal to the user layout's align in the
414 // case where we reused an allocation that happened to be aligned
415 // greater than its original layout requested.
416 debug_assert_eq!(
417 ptr.as_ptr() as usize % node.layout().align(),
418 0,
419 "{ptr:#p} should be aligned to original layout's alignment of {:#x}",
420 node.layout().align()
421 );
422
423 // If this is an allocation that was originally already zeroed,
424 // and its original inner layout can already satisfy this new
425 // layout, then we don't need to do anything further.
426 if node.layout().size() >= new_layout.size()
427 && node.layout().align() >= new_layout.align()
428 {
429 return PreGrow::Reuse {
430 ptr: NonNull::slice_from_raw_parts(ptr, node.layout().size()),
431 actual_layout: node.layout(),
432 };
433 }
434
435 let actual_old_layout = node.layout();
436
437 // We no longer need the live-set node. It is only required for
438 // when we are handing out already-zeroed blocks that we
439 // bookkeep internally and whose original layout might not match
440 // what the user thinks its layout is. From this point on, this
441 // allocation will either be fully managed by the inner
442 // allocator (in the case of a successful `grow`) or will be
443 // deallocated and replaced by a new already-zeroed allocation
444 // (in the case of growth failure). In either case, we no longer
445 // need a block-info node for this allocation.
446 zeroed
447 .live_set
448 .remove(node)
449 .expect("just found the node in the live-set, should still be there");
450 self.deallocate_block_info(node);
451
452 actual_old_layout
453 } else {
454 // This is not an allocation we are managing: use the user's
455 // given old layout.
456 user_old_layout
457 };
458
459 if new_layout.size() >= actual_old_layout.size() {
460 PreGrow::DoGrow { actual_old_layout }
461 } else {
462 PreGrow::DoShrink { actual_old_layout }
463 }
464 }
465}
466
467enum PreGrow {
468 /// The underlying inner allocation can be reused and does not need to be
469 /// resized.
470 Reuse {
471 ptr: NonNull<[u8]>,
472 actual_layout: Layout,
473 },
474
475 /// The underlying allocation cannot satisfy the growth request, we need to
476 /// use the inner allocator to grow.
477 DoGrow { actual_old_layout: Layout },
478
479 /// The underlying allocation cannot satisfy the growth request due to
480 /// less-than-requested alignment, but its actual size is *larger* than the
481 /// new layout's size. Therefore, we need to use the inner allocator to
482 /// shrink instead of grow.
483 ///
484 /// For example, consider this sequence of events:
485 ///
486 /// * `allocate(Layout { size: 4096, align: 16 }) -> 0x12345670`
487 ///
488 /// An initial allocation satisfied via the inner allocator.
489 ///
490 /// * `deallocate_zeroed(0x12345670, ..)`
491 ///
492 /// That allocation is now tracked in our internal free lists for
493 /// already-zeroed blocks.
494 ///
495 /// * `allocate_zeroed(Layout { size: 4000, align: 16 }) -> 0x12345670`
496 ///
497 /// We satisfy a new zeroed-allocation request with this block, accepting
498 /// that it will result in some small amount of fragmentation slop.
499 ///
500 /// * `grow(0x12345670, Layout { size: 4001, align: 256 })`
501 ///
502 /// The user wants to grow the allocation by one byte *and* realign it to
503 /// 256. Although we have capacity in this allocation for that growth, it
504 /// is not aligned to 256. Because the original, underlying allocation's
505 /// size is larger than the new size that the user has asked us to "grow"
506 /// this allocation to, we must actually call the inner allocator's
507 /// `shrink` method.
508 DoShrink { actual_old_layout: Layout },
509}
510
511impl<A, L> Drop for ZeroAwareAllocator<A, L>
512where
513 A: Allocator,
514 L: LockingMechanism,
515{
516 fn drop(&mut self) {
517 self.return_zeroed_memory_to_inner();
518 }
519}
520
521unsafe impl<A, L> Allocator for ZeroAwareAllocator<A, L>
522where
523 A: Allocator,
524 L: LockingMechanism,
525{
526 #[inline]
527 fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
528 self.inner
529 .allocate(layout)
530 .or_else(|_| self.allocate_already_zeroed(layout))
531 }
532
533 #[inline]
534 unsafe fn deallocate(&self, ptr: NonNull<u8>, user_layout: Layout) {
535 if user_layout.size() == 0 {
536 self.inner.deallocate(ptr, user_layout);
537 return;
538 }
539
540 let mut zeroed = self.zeroed.lock();
541 let zeroed = &mut *zeroed;
542 let actual_layout = if let Some(node) = zeroed.live_set.remove(&ptr) {
543 let layout = node.layout();
544 self.deallocate_block_info(node);
545 layout
546 } else {
547 user_layout
548 };
549 self.inner.deallocate(ptr, actual_layout);
550 }
551
552 #[inline]
553 fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
554 if layout.size() == 0 {
555 return self.inner.allocate_zeroed(layout);
556 }
557
558 self.allocate_already_zeroed(layout)
559 .or_else(|_| self.inner.allocate_zeroed(layout))
560 }
561
562 #[inline]
563 unsafe fn grow(
564 &self,
565 ptr: NonNull<u8>,
566 user_old_layout: Layout,
567 new_layout: Layout,
568 ) -> Result<NonNull<[u8]>, AllocError> {
569 if user_old_layout.size() == 0 {
570 return self.inner.grow(ptr, user_old_layout, new_layout);
571 }
572
573 let actual_old_layout = match self.pre_grow(ptr, user_old_layout, new_layout) {
574 PreGrow::Reuse {
575 ptr,
576 actual_layout: _,
577 } => {
578 // No need to update our metadata here: the caller is
579 // responsible for keeping track of the correct `Layout` and the
580 // size of the actual underlying block didn't grow as we have
581 // nothing to change.
582 return Ok(ptr);
583 }
584 PreGrow::DoShrink { actual_old_layout } => {
585 // No need to update any metadata here, we are no longer
586 // managing this allocation, the inner allocator is.
587 return self.inner.shrink(ptr, actual_old_layout, new_layout);
588 }
589 PreGrow::DoGrow { actual_old_layout } => actual_old_layout,
590 };
591
592 match self.inner.grow(ptr, actual_old_layout, new_layout) {
593 Ok(ptr) => Ok(ptr),
594
595 // If the inner allocator cannot grow this allocation, see if we
596 // have an already-zeroed block that will work.
597 Err(_) => {
598 let new_ptr = self.allocate_already_zeroed(new_layout)?;
599 debug_assert_ne!(new_ptr.cast::<u8>(), ptr);
600
601 // Copy over the bytes from the old allocation to the new one.
602 ptr::copy_nonoverlapping(
603 ptr.as_ptr().cast_const(),
604 new_ptr.cast::<u8>().as_ptr(),
605 // NB: we only need to copy the user's bytes, not the actual
606 // inner allocation's bytes.
607 user_old_layout.size(),
608 );
609
610 // The contract is that when `grow` succeeds, it takes ownership
611 // of the old pointer, so we must deallocate the old allocation.
612 self.inner.deallocate(ptr, actual_old_layout);
613
614 Ok(new_ptr)
615 }
616 }
617 }
618
619 #[inline]
620 unsafe fn grow_zeroed(
621 &self,
622 ptr: NonNull<u8>,
623 user_old_layout: Layout,
624 new_layout: Layout,
625 ) -> Result<NonNull<[u8]>, AllocError> {
626 if user_old_layout.size() == 0 {
627 return self.inner.allocate_zeroed(new_layout);
628 }
629
630 let actual_old_layout = match self.pre_grow(ptr, user_old_layout, new_layout) {
631 PreGrow::Reuse { ptr, actual_layout } => {
632 // NB: we cannot assume that the fragmentation slop (if any) is
633 // still zeroed, because we give out the whole block as a
634 // `NonNull<[u8]>` slice to the user. Therefore, we have to
635 // manually zero here.
636 let slop = ptr.cast::<u8>().add(user_old_layout.size());
637 let slop_len = actual_layout.size() - user_old_layout.size();
638 slop.write_bytes(0, slop_len);
639
640 // No need to update our metadata here: the caller is
641 // responsible for keeping track of the correct `Layout` and the
642 // size of the actual underlying block didn't grow os we have
643 // nothing to change.
644 return Ok(ptr);
645 }
646 PreGrow::DoShrink { actual_old_layout } => {
647 let new_ptr = self.inner.shrink(ptr, actual_old_layout, new_layout)?;
648
649 // We need to zero the range of bytes between the user's old
650 // size and the new size, which should be within the shrunken
651 // area.
652 debug_assert!(new_layout.size() < actual_old_layout.size());
653 debug_assert!(new_layout.size() >= user_old_layout.size());
654 let to_zero = new_ptr.cast::<u8>().add(user_old_layout.size());
655 let to_zero_len = new_layout.size() - user_old_layout.size();
656 to_zero.write_bytes(0, to_zero_len);
657
658 // No need to update our metadata here: we are no longer
659 // managing this allocation, the inner allocator is.
660 return Ok(new_ptr);
661 }
662 PreGrow::DoGrow { actual_old_layout } => actual_old_layout,
663 };
664
665 // NB: we could have enough capacity in the existing allocation, but
666 // cannot reuse the allocation because it has the wrong alignment.
667 // Therefore, it is not necessarily the case that the old layout is
668 // smaller than the new layout and so we do a saturating subtraction
669 // here.
670 let bytes_to_zero = new_layout.size().saturating_sub(actual_old_layout.size());
671 let bytes_to_copy = new_layout.size() - bytes_to_zero;
672
673 // We can't split or grow blocks in place so if we don't want to use the
674 // underlying allocator, we have to do a new zeroed allocation and copy
675 // over the old data. As a heuristic, if that will end up copying way
676 // more bytes than the new allocation would need to zero (assuming the
677 // old allocation could be grown in place) then just defer to the
678 // underlying allocator.
679 if bytes_to_copy > 2usize.saturating_mul(bytes_to_zero) {
680 if let Ok(p) = self.inner.grow_zeroed(ptr, user_old_layout, new_layout) {
681 return Ok(p);
682 }
683 }
684
685 let new = self.allocate_zeroed(new_layout)?;
686
687 // Copy over the old allocation's contents into the new allocation.
688 ptr::copy_nonoverlapping(
689 ptr.as_ptr().cast_const(),
690 new.cast().as_ptr(),
691 user_old_layout.size(),
692 );
693
694 // Successful growing takes ownership of the old allocation, so we need
695 // to free it since it isn't being reused.
696 self.inner.deallocate(ptr, actual_old_layout);
697
698 Ok(new)
699 }
700
701 #[inline]
702 unsafe fn shrink(
703 &self,
704 ptr: NonNull<u8>,
705 old_layout: Layout,
706 new_layout: Layout,
707 ) -> Result<NonNull<[u8]>, AllocError> {
708 if old_layout.size() == 0 {
709 return self.inner.shrink(ptr, old_layout, new_layout);
710 }
711
712 // We can't split allocations ourselves, so just defer to the inner
713 // allocator.
714
715 let mut zeroed = self.zeroed.lock();
716 let zeroed = &mut *zeroed;
717
718 // Make sure that we are passing the original, underlying `Layout` to
719 // the inner allocator. After shrinking, this pointer will no longer be
720 // managed by us, so remove and deallocate its block-info node from the
721 // live-set (if any).
722 let old_layout = if let Some(node) = zeroed.live_set.remove(&ptr) {
723 let actual_old_layout = node.layout();
724 self.deallocate_block_info(node);
725 actual_old_layout
726 } else {
727 old_layout
728 };
729
730 self.inner.shrink(ptr, old_layout, new_layout)
731 }
732}
733
734impl<A, L> DeallocateZeroed for ZeroAwareAllocator<A, L>
735where
736 A: Allocator,
737 L: LockingMechanism,
738{
739 unsafe fn deallocate_zeroed(&self, ptr: NonNull<u8>, layout: Layout) {
740 if layout.size() == 0 {
741 self.inner.deallocate(ptr, layout);
742 return;
743 }
744
745 self.deallocate_already_zeroed(ptr, layout);
746 }
747}
748
749mod metadata {
750 use super::*;
751 use core::cmp::Ordering;
752 use intrusive_splay_tree::{Node, SplayTree, TreeOrd};
753
754 /// Metadata about an already-zeroed block of memory, allocated from our
755 /// underlying allocator.
756 ///
757 /// Note: the `'a` lifetime is used internally to this module as much as
758 /// possible to keep things free of `unsafe` when possible, but outside this
759 /// module is always erased to `'static` and the lifetimes are manually
760 /// managed.
761 #[derive(Debug)]
762 pub(super) struct BlockInfo<'a> {
763 ptr: NonNull<u8>,
764 layout: Layout,
765 node: Node<'a>,
766 }
767
768 impl<'a> BlockInfo<'a> {
769 pub(super) unsafe fn new(ptr: NonNull<u8>, layout: Layout) -> Self {
770 debug_assert!(
771 core::slice::from_raw_parts(ptr.as_ptr(), layout.size())
772 .iter()
773 .all(|b| *b == 0),
774 "supposedly already-zeroed block contains non-zero memory"
775 );
776 BlockInfo {
777 ptr,
778 layout,
779 node: Node::default(),
780 }
781 }
782
783 fn size(&self) -> usize {
784 self.layout.size()
785 }
786
787 fn align(&self) -> usize {
788 1 << (self.ptr.as_ptr() as usize).trailing_zeros()
789 }
790
791 pub(super) fn ptr(&self) -> NonNull<u8> {
792 self.ptr
793 }
794
795 pub(super) fn non_null_slice_ptr(&self) -> NonNull<[u8]> {
796 NonNull::slice_from_raw_parts(self.ptr, self.size())
797 }
798
799 pub(super) fn layout(&self) -> Layout {
800 self.layout
801 }
802 }
803
804 pub(super) struct ByAlignClass;
805 pub(super) type FreeList = SplayTree<'static, ByAlignClass>;
806
807 /// Comparison between two `BlockInfo`s.
808 ///
809 /// This is used for ordering blocks within a tree.
810 impl<'a> TreeOrd<'a, ByAlignClass> for BlockInfo<'a> {
811 fn tree_cmp(&self, other: &'a BlockInfo<'a>) -> Ordering {
812 // Compare first by size, since that is the first hard constraint we
813 // must satisfy and property we query for.
814 self.size()
815 .cmp(&other.size())
816 // Then by alignment, since that is the other hard constraint.
817 .then_with(|| {
818 let self_align = (self.ptr.as_ptr() as usize).trailing_zeros();
819 let other_align = (other.ptr.as_ptr() as usize).trailing_zeros();
820 self_align.cmp(&other_align)
821 })
822 // And finally by address. This final tie breaker should
823 // generally improve spatial locality between a sequence of
824 // allocations.
825 .then_with(|| {
826 let self_addr = self.ptr.as_ptr() as usize;
827 let other_addr = other.ptr.as_ptr() as usize;
828 self_addr.cmp(&other_addr)
829 })
830 }
831 }
832
833 /// Comparison between a `Layout` and a `BlockInfo`.
834 ///
835 /// This is used when searching for a block to satisfy a requested
836 /// allocation.
837 impl<'a> TreeOrd<'a, ByAlignClass> for Layout {
838 fn tree_cmp(&self, block: &'a BlockInfo<'a>) -> Ordering {
839 // Compare sizes. Allow for some fragmentation, but not too much,
840 // because we can't split blocks ourselves. See the doc comment for
841 // `ACCEPTABLE_WASTE_DIVISOR` for more info.
842 let by_size = match self.size().cmp(&block.size()) {
843 Ordering::Greater => Ordering::Greater,
844 Ordering::Equal => Ordering::Equal,
845 Ordering::Less => {
846 let acceptable_waste = self.size() / ACCEPTABLE_WASTE_DIVISOR;
847 let potential_waste = block.size() - self.size();
848 if potential_waste <= acceptable_waste {
849 Ordering::Equal
850 } else {
851 Ordering::Less
852 }
853 }
854 };
855 by_size
856 // Compare alignments. If the block matches the requested
857 // alignment at all, even if it is actually over-aligned, then
858 // return `Ordering::Equal` to signal that it can satisfy the
859 // allocation.
860 .then_with(|| match self.align().cmp(&block.align()) {
861 Ordering::Less | Ordering::Equal => Ordering::Equal,
862 Ordering::Greater => Ordering::Greater,
863 })
864 }
865 }
866
867 intrusive_splay_tree::impl_intrusive_node! {
868 impl<'a> IntrusiveNode<'a> for ByAlignClass
869 where
870 type Elem = BlockInfo<'a>,
871 node = node;
872 }
873
874 pub(super) struct ByPointer;
875 pub(super) type LiveSet = SplayTree<'static, ByPointer>;
876
877 /// Comparison between two `BlockInfo`s by pointer.
878 ///
879 /// This is used for ordering blocks within a tree.
880 impl<'a> TreeOrd<'a, ByPointer> for BlockInfo<'a> {
881 fn tree_cmp(&self, other: &'a BlockInfo<'a>) -> Ordering {
882 Ord::cmp(&self.ptr, &other.ptr)
883 }
884 }
885
886 /// Comparison between a pointer and a `BlockInfo`.
887 ///
888 /// Used when searching the live-set for a particular allocation.
889 impl<'a> TreeOrd<'a, ByPointer> for NonNull<u8> {
890 fn tree_cmp(&self, other: &'a BlockInfo<'a>) -> Ordering {
891 Ord::cmp(self, &other.ptr)
892 }
893 }
894
895 intrusive_splay_tree::impl_intrusive_node! {
896 impl<'a> IntrusiveNode<'a> for ByPointer
897 where
898 type Elem = BlockInfo<'a>,
899 node = node;
900 }
901}