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}