bump_scope/
stats.rs

1//! Contains types for inspecting memory usage in bump allocators.
2//!
3//! This module defines both generic types like [`Stats`] and type-erased counterparts prefixed
4//! with `Any*` (like [`AnyStats`]). The generic types are slightly more efficient to use.
5//! You can turn the generic types into their `Any*` variants using `from` and `into`.
6//!
7//! The `Any*` types are returned by the [`BumpAllocator`](crate::BumpAllocator) trait
8//! and the `allocator_stats` method of collections whereas `Stats` is returned from [`Bump`](crate::Bump) and [`BumpScope`](crate::BumpScope).
9
10use core::{
11    fmt::{self, Debug},
12    iter::FusedIterator,
13    marker::PhantomData,
14    ptr::NonNull,
15};
16
17use crate::{RawChunk, maybe_default_allocator};
18
19#[cfg(debug_assertions)]
20use crate::chunk_header::ChunkHeader;
21
22mod any;
23
24pub use any::{AnyChunk, AnyChunkNextIter, AnyChunkPrevIter, AnyStats};
25
26macro_rules! make_type {
27    ($($allocator_parameter:tt)*) => {
28        /// Provides statistics about the memory usage of the bump allocator.
29        ///
30        /// This is returned from the `stats` method of [`Bump`](crate::Bump) and [`BumpScope`](crate::BumpScope).
31        pub struct Stats<
32            'a,
33            $($allocator_parameter)*,
34            const UP: bool = true,
35            const GUARANTEED_ALLOCATED: bool = true,
36        > {
37            chunk: RawChunk<A, UP, GUARANTEED_ALLOCATED>,
38            marker: PhantomData<&'a ()>,
39        }
40    };
41}
42
43maybe_default_allocator!(make_type);
44
45impl<A, const UP: bool, const GUARANTEED_ALLOCATED: bool> Clone for Stats<'_, A, UP, GUARANTEED_ALLOCATED> {
46    fn clone(&self) -> Self {
47        *self
48    }
49}
50
51impl<A, const UP: bool, const GUARANTEED_ALLOCATED: bool> Copy for Stats<'_, A, UP, GUARANTEED_ALLOCATED> {}
52
53impl<A, const UP: bool, const GUARANTEED_ALLOCATED: bool> PartialEq for Stats<'_, A, UP, GUARANTEED_ALLOCATED> {
54    fn eq(&self, other: &Self) -> bool {
55        self.chunk == other.chunk
56    }
57}
58
59impl<A, const UP: bool, const GUARANTEED_ALLOCATED: bool> Eq for Stats<'_, A, UP, GUARANTEED_ALLOCATED> {}
60
61impl<A, const UP: bool, const GUARANTEED_ALLOCATED: bool> Debug for Stats<'_, A, UP, GUARANTEED_ALLOCATED> {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        AnyStats::from(*self).debug_format("Stats", f)
64    }
65}
66
67impl<'a, A, const UP: bool, const GUARANTEED_ALLOCATED: bool> Stats<'a, A, UP, GUARANTEED_ALLOCATED> {
68    #[inline]
69    pub(crate) fn from_raw_chunk(chunk: RawChunk<A, UP, GUARANTEED_ALLOCATED>) -> Self {
70        Self {
71            chunk,
72            marker: PhantomData,
73        }
74    }
75
76    /// Returns the number of chunks.
77    #[must_use]
78    pub fn count(self) -> usize {
79        let Some(current) = self.get_current_chunk() else { return 0 };
80
81        let mut sum = 1;
82        current.iter_prev().for_each(|_| sum += 1);
83        current.iter_next().for_each(|_| sum += 1);
84        sum
85    }
86
87    /// Returns the total size of all chunks.
88    #[must_use]
89    pub fn size(self) -> usize {
90        let Some(current) = self.get_current_chunk() else { return 0 };
91
92        let mut sum = current.size();
93        current.iter_prev().for_each(|chunk| sum += chunk.size());
94        current.iter_next().for_each(|chunk| sum += chunk.size());
95        sum
96    }
97
98    /// Returns the total capacity of all chunks.
99    #[must_use]
100    pub fn capacity(self) -> usize {
101        let Some(current) = self.get_current_chunk() else { return 0 };
102
103        let mut sum = current.capacity();
104        current.iter_prev().for_each(|chunk| sum += chunk.capacity());
105        current.iter_next().for_each(|chunk| sum += chunk.capacity());
106        sum
107    }
108
109    /// Returns the amount of allocated bytes.
110    /// This includes padding and wasted space due to reallocations.
111    ///
112    /// This is equal to the `allocated` bytes of the current chunk
113    /// plus the `capacity` of all previous chunks.
114    #[must_use]
115    pub fn allocated(self) -> usize {
116        let Some(current) = self.get_current_chunk() else { return 0 };
117
118        let mut sum = current.allocated();
119        current.iter_prev().for_each(|chunk| sum += chunk.capacity());
120        sum
121    }
122
123    /// Returns the remaining capacity in bytes.
124    ///
125    /// This is equal to the `remaining` capacity of the current chunk
126    /// plus the `capacity` of all following chunks.
127    #[must_use]
128    pub fn remaining(self) -> usize {
129        let Some(current) = self.get_current_chunk() else { return 0 };
130
131        let mut sum = current.remaining();
132        current.iter_next().for_each(|chunk| sum += chunk.capacity());
133        sum
134    }
135
136    /// Returns an iterator from smallest to biggest chunk.
137    #[must_use]
138    pub fn small_to_big(self) -> ChunkNextIter<'a, A, UP> {
139        let Some(mut start) = self.get_current_chunk() else {
140            return ChunkNextIter { chunk: None };
141        };
142
143        while let Some(chunk) = start.prev() {
144            start = chunk;
145        }
146
147        ChunkNextIter { chunk: Some(start) }
148    }
149
150    /// Returns an iterator from biggest to smallest chunk.
151    #[must_use]
152    pub fn big_to_small(self) -> ChunkPrevIter<'a, A, UP> {
153        let Some(mut start) = self.get_current_chunk() else {
154            return ChunkPrevIter { chunk: None };
155        };
156
157        while let Some(chunk) = start.next() {
158            start = chunk;
159        }
160
161        ChunkPrevIter { chunk: Some(start) }
162    }
163
164    /// Turns this `Stats` into a `Stats` where `GUARANTEED_ALLOCATED = true`.
165    #[inline]
166    #[must_use]
167    pub fn guaranteed_allocated(self) -> Option<Stats<'a, A, UP, true>> {
168        Some(self.chunk.guaranteed_allocated()?.stats())
169    }
170
171    /// Turns this `Stats` into a `Stats` where `GUARANTEED_ALLOCATED = false`.
172    #[inline]
173    #[must_use]
174    pub fn not_guaranteed_allocated(self) -> Stats<'a, A, UP, false> {
175        self.chunk.not_guaranteed_allocated().stats()
176    }
177
178    fn get_current_chunk(self) -> Option<Chunk<'a, A, UP>> {
179        Some(Chunk {
180            chunk: self.chunk.guaranteed_allocated()?,
181            marker: PhantomData,
182        })
183    }
184}
185
186impl<'a, A, const UP: bool> Stats<'a, A, UP, true> {
187    /// This is the chunk we are currently allocating on.
188    #[must_use]
189    pub fn current_chunk(self) -> Chunk<'a, A, UP> {
190        Chunk {
191            chunk: self.chunk,
192            marker: self.marker,
193        }
194    }
195}
196
197impl<'a, A, const UP: bool> Stats<'a, A, UP, false> {
198    /// This is the chunk we are currently allocating on.
199    #[must_use]
200    pub fn current_chunk(self) -> Option<Chunk<'a, A, UP>> {
201        Some(Chunk {
202            chunk: self.chunk.guaranteed_allocated()?,
203            marker: self.marker,
204        })
205    }
206
207    pub(crate) fn unallocated() -> Self {
208        Self {
209            chunk: RawChunk::UNALLOCATED,
210            marker: PhantomData,
211        }
212    }
213}
214
215impl<'a, A, const UP: bool, const GUARANTEED_ALLOCATED: bool> From<Chunk<'a, A, UP>>
216    for Stats<'a, A, UP, GUARANTEED_ALLOCATED>
217{
218    fn from(chunk: Chunk<'a, A, UP>) -> Self {
219        Stats {
220            chunk: chunk.chunk.coerce_guaranteed_allocated(),
221            marker: PhantomData,
222        }
223    }
224}
225
226impl<A, const UP: bool> Default for Stats<'_, A, UP, false> {
227    fn default() -> Self {
228        Self::unallocated()
229    }
230}
231
232/// Refers to a chunk of memory that was allocated by the bump allocator.
233///
234/// See [`Stats`].
235#[repr(transparent)]
236pub struct Chunk<'a, A, const UP: bool> {
237    chunk: RawChunk<A, UP, true>,
238    marker: PhantomData<&'a ()>,
239}
240
241impl<A, const UP: bool> Clone for Chunk<'_, A, UP> {
242    fn clone(&self) -> Self {
243        *self
244    }
245}
246
247impl<A, const UP: bool> Copy for Chunk<'_, A, UP> {}
248
249impl<A, const UP: bool> PartialEq for Chunk<'_, A, UP> {
250    fn eq(&self, other: &Self) -> bool {
251        self.chunk == other.chunk
252    }
253}
254
255impl<A, const UP: bool> Eq for Chunk<'_, A, UP> {}
256
257impl<A, const UP: bool> Debug for Chunk<'_, A, UP> {
258    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259        f.debug_struct("Chunk")
260            .field("allocated", &self.allocated())
261            .field("capacity", &self.capacity())
262            .finish()
263    }
264}
265
266impl<'a, A, const UP: bool> Chunk<'a, A, UP> {
267    #[cfg(debug_assertions)]
268    pub(crate) fn header(self) -> NonNull<ChunkHeader> {
269        self.chunk.header().cast()
270    }
271
272    /// Returns the previous (smaller) chunk.
273    #[must_use]
274    #[inline(always)]
275    pub fn prev(self) -> Option<Self> {
276        Some(Chunk {
277            chunk: self.chunk.prev()?,
278            marker: PhantomData,
279        })
280    }
281
282    /// Returns the next (bigger) chunk.
283    #[must_use]
284    #[inline(always)]
285    pub fn next(self) -> Option<Self> {
286        Some(Chunk {
287            chunk: self.chunk.next()?,
288            marker: PhantomData,
289        })
290    }
291
292    /// Returns an iterator over all previous (smaller) chunks.
293    #[must_use]
294    #[inline(always)]
295    pub fn iter_prev(self) -> ChunkPrevIter<'a, A, UP> {
296        ChunkPrevIter { chunk: self.prev() }
297    }
298
299    /// Returns an iterator over all next (bigger) chunks.
300    #[must_use]
301    #[inline(always)]
302    pub fn iter_next(self) -> ChunkNextIter<'a, A, UP> {
303        ChunkNextIter { chunk: self.next() }
304    }
305
306    /// Returns the size of this chunk in bytes.
307    #[must_use]
308    #[inline]
309    pub fn size(self) -> usize {
310        self.chunk.size().get()
311    }
312
313    /// Returns the capacity of this chunk in bytes.
314    #[must_use]
315    #[inline]
316    pub fn capacity(self) -> usize {
317        self.chunk.capacity()
318    }
319
320    /// Returns the amount of allocated bytes.
321    /// This includes padding and wasted space due to reallocations.
322    ///
323    /// This property can be misleading for chunks that come after the current chunk because
324    /// their `bump_position` and consequently the `allocated` property is not reset until
325    /// they become the current chunk again.
326    #[must_use]
327    #[inline]
328    pub fn allocated(self) -> usize {
329        self.chunk.allocated()
330    }
331
332    /// Returns the remaining capacity.
333    ///
334    /// This property can be misleading for chunks that come after the current chunk because
335    /// their `bump_position` and consequently the `remaining` property is not reset until
336    /// they become the current chunk again.
337    #[must_use]
338    #[inline]
339    pub fn remaining(self) -> usize {
340        self.chunk.remaining()
341    }
342
343    /// Returns a pointer to the start of the chunk.
344    #[must_use]
345    #[inline]
346    pub fn chunk_start(self) -> NonNull<u8> {
347        self.chunk.chunk_start()
348    }
349
350    /// Returns a pointer to the end of the chunk.
351    #[must_use]
352    #[inline]
353    pub fn chunk_end(self) -> NonNull<u8> {
354        self.chunk.chunk_end()
355    }
356
357    /// Returns a pointer to the start of the chunk's content.
358    #[must_use]
359    #[inline]
360    pub fn content_start(self) -> NonNull<u8> {
361        self.chunk.content_start()
362    }
363
364    /// Returns a pointer to the end of the chunk's content.
365    #[must_use]
366    #[inline]
367    pub fn content_end(self) -> NonNull<u8> {
368        self.chunk.content_end()
369    }
370
371    /// Returns the bump pointer. It lies within the chunk's content range.
372    ///
373    /// This property can be misleading for chunks that come after the current chunk because
374    /// their `bump_position` is not reset until they become the current chunk again.
375    #[must_use]
376    #[inline]
377    pub fn bump_position(self) -> NonNull<u8> {
378        self.chunk.pos()
379    }
380
381    /// Returns a reference to the base allocator.
382    #[must_use]
383    #[inline(always)]
384    pub fn allocator(&self) -> &'a A {
385        self.chunk.allocator()
386    }
387
388    #[cfg(debug_assertions)]
389    pub(crate) fn contains_addr_or_end(self, addr: usize) -> bool {
390        self.chunk.contains_addr_or_end(addr)
391    }
392}
393
394/// Iterator that iterates over previous chunks by continuously calling [`Chunk::prev`].
395pub struct ChunkPrevIter<'a, A, const UP: bool> {
396    #[expect(missing_docs)]
397    pub chunk: Option<Chunk<'a, A, UP>>,
398}
399
400impl<A, const UP: bool> Clone for ChunkPrevIter<'_, A, UP> {
401    fn clone(&self) -> Self {
402        *self
403    }
404}
405
406impl<A, const UP: bool> Copy for ChunkPrevIter<'_, A, UP> {}
407
408impl<A, const UP: bool> PartialEq for ChunkPrevIter<'_, A, UP> {
409    fn eq(&self, other: &Self) -> bool {
410        self.chunk == other.chunk
411    }
412}
413
414impl<A, const UP: bool> Eq for ChunkPrevIter<'_, A, UP> {}
415
416impl<A, const UP: bool> Default for ChunkPrevIter<'_, A, UP> {
417    fn default() -> Self {
418        Self { chunk: None }
419    }
420}
421
422impl<'a, A, const UP: bool> Iterator for ChunkPrevIter<'a, A, UP> {
423    type Item = Chunk<'a, A, UP>;
424
425    #[inline(always)]
426    fn next(&mut self) -> Option<Self::Item> {
427        let chunk = self.chunk?;
428        self.chunk = chunk.prev();
429        Some(chunk)
430    }
431}
432
433impl<A, const UP: bool> FusedIterator for ChunkPrevIter<'_, A, UP> {}
434
435impl<A, const UP: bool> Debug for ChunkPrevIter<'_, A, UP> {
436    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
437        f.debug_list().entries(*self).finish()
438    }
439}
440
441/// Iterator that iterates over next chunks by continuously calling [`Chunk::next`].
442pub struct ChunkNextIter<'a, A, const UP: bool> {
443    #[expect(missing_docs)]
444    pub chunk: Option<Chunk<'a, A, UP>>,
445}
446
447impl<A, const UP: bool> Clone for ChunkNextIter<'_, A, UP> {
448    fn clone(&self) -> Self {
449        *self
450    }
451}
452
453impl<A, const UP: bool> Copy for ChunkNextIter<'_, A, UP> {}
454
455impl<A, const UP: bool> PartialEq for ChunkNextIter<'_, A, UP> {
456    fn eq(&self, other: &Self) -> bool {
457        self.chunk == other.chunk
458    }
459}
460
461impl<A, const UP: bool> Eq for ChunkNextIter<'_, A, UP> {}
462
463impl<A, const UP: bool> Default for ChunkNextIter<'_, A, UP> {
464    fn default() -> Self {
465        Self { chunk: None }
466    }
467}
468
469impl<'a, A, const UP: bool> Iterator for ChunkNextIter<'a, A, UP> {
470    type Item = Chunk<'a, A, UP>;
471
472    #[inline(always)]
473    fn next(&mut self) -> Option<Self::Item> {
474        let chunk = self.chunk?;
475        self.chunk = chunk.next();
476        Some(chunk)
477    }
478}
479
480impl<A, const UP: bool> FusedIterator for ChunkNextIter<'_, A, UP> {}
481
482impl<A, const UP: bool> Debug for ChunkNextIter<'_, A, UP> {
483    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
484        f.debug_list().entries(self.map(Chunk::size)).finish()
485    }
486}