Skip to main content

bump_scope/stats/
any.rs

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