bump_scope/stats/
any.rs

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