bump_scope/chunk_size/
chunk_size_config.rs

1#![forbid(unsafe_code)]
2//! Make sure you sync this file `src/chunk_size/chunk_size_config.rs`
3//! with `crates/fuzzing-support/src/from_bump_scope/chunk_size_config.rs`.
4//!
5//! This file intentionally doesn't import anything other than `core`
6//! to make it easy to fuzz (see above) and debug.
7
8use core::{alloc::Layout, num::NonZeroUsize};
9
10pub const ASSUMED_PAGE_SIZE: usize = 0x1000;
11pub const MIN_CHUNK_ALIGN: usize = 16;
12
13#[derive(Clone, Copy)]
14pub struct ChunkSizeConfig {
15    pub up: bool,
16    pub assumed_malloc_overhead_layout: Layout,
17    pub chunk_header_layout: Layout,
18}
19
20/// Alternative to using the `?` operator that works in const functions.
21macro_rules! attempt {
22    ($expr:expr) => {
23        match $expr {
24            Some(some) => some,
25            None => return None,
26        }
27    };
28}
29
30impl ChunkSizeConfig {
31    /// This function must be called to align the size of the allocation returned
32    /// by the allocator.
33    ///
34    /// # Context
35    /// The final chunk size must always be a multiple of `MIN_CHUNK_ALIGN`.
36    /// We use optimizations in `alloc` that make use of that invariant.
37    ///
38    /// When downwards allocating, the final chunk size must also be aligned to the chunk header
39    /// alignment so we can put the header at `ptr.byte_add(size).cast::<ChunkHeader<A>>().sub(1)`.
40    ///
41    /// This aligning must not result in a size smaller than the layout's size
42    /// because we need `size` to be a size that [fits] the allocated memory block.
43    /// (The size must be between the original requested size and the size the allocator actually returned.)
44    ///
45    /// This is ensured by already having `align_size`'d the layout's size in `calc_size_from_hint`.
46    /// A downwards alignment of a size greater or equal than the layout's size, which
47    /// the allocation's size is, cannot result in a size smaller than the layout's.
48    ///
49    /// [fits]: https://doc.rust-lang.org/std/alloc/trait.Allocator.html#memory-fitting
50    #[inline(always)]
51    pub const fn align_size(self, size: usize) -> usize {
52        let Self {
53            up, chunk_header_layout, ..
54        } = self;
55
56        down_align(
57            size,
58            if up {
59                MIN_CHUNK_ALIGN
60            } else {
61                max(MIN_CHUNK_ALIGN, chunk_header_layout.align())
62            },
63        )
64    }
65
66    #[inline(always)]
67    pub const fn calc_size_from_hint(self, size_hint: usize) -> Option<NonZeroUsize> {
68        let Self {
69            assumed_malloc_overhead_layout,
70            chunk_header_layout,
71            ..
72        } = self;
73
74        let min = {
75            let mut offset = 0;
76            offset = attempt!(offset_add_layout(offset, assumed_malloc_overhead_layout));
77            offset = attempt!(offset_add_layout(offset, chunk_header_layout));
78            offset
79        };
80
81        let size_step = max(ASSUMED_PAGE_SIZE, chunk_header_layout.align());
82        let size_hint = max(size_hint, min);
83
84        let mut size = attempt!(if size_hint < size_step {
85            // the name is misleading, this will return `size` if it is already a power of two
86            size_hint.checked_next_power_of_two()
87        } else {
88            up_align(size_hint, size_step)
89        });
90
91        debug_assert!(size % chunk_header_layout.align() == 0);
92        debug_assert!(size >= min);
93
94        debug_assert!(if size < size_step {
95            size.is_power_of_two()
96        } else {
97            size % size_step == 0
98        });
99
100        // When downwards allocating with a base allocator that has a higher alignment than 16
101        // we don't subtract an assumed malloc overhead.
102        //
103        // Since we also need to align the size after subtraction we could end up with a size of `0`,
104        // which would be illegal since the chunk header would not fit, or we could end up with a
105        // size that would no longer have enough space for the given capacity (`calc_hint_from_capacity`).
106        if self.up || self.chunk_header_layout.align() <= MIN_CHUNK_ALIGN {
107            let size_without_overhead = size - assumed_malloc_overhead_layout.size();
108            size = self.align_size(size_without_overhead);
109        }
110
111        NonZeroUsize::new(size)
112    }
113
114    #[inline(always)]
115    pub const fn calc_hint_from_capacity(self, layout: Layout) -> Option<usize> {
116        let Self { chunk_header_layout, .. } = self;
117
118        let maximum_required_padding = layout.align().saturating_sub(chunk_header_layout.align());
119        let required_size = attempt!(layout.size().checked_add(maximum_required_padding));
120        self.calc_hint_from_capacity_bytes(required_size)
121    }
122
123    #[inline(always)]
124    pub const fn calc_hint_from_capacity_bytes(self, bytes: usize) -> Option<usize> {
125        let Self {
126            up,
127            assumed_malloc_overhead_layout,
128            chunk_header_layout,
129            ..
130        } = self;
131
132        let mut size = 0;
133
134        if up {
135            size = attempt!(offset_add_layout(size, assumed_malloc_overhead_layout));
136            size = attempt!(offset_add_layout(size, chunk_header_layout));
137            size = attempt!(size.checked_add(bytes));
138        } else {
139            size = attempt!(offset_add_layout(size, assumed_malloc_overhead_layout));
140            size = attempt!(size.checked_add(bytes));
141            size = attempt!(offset_add_layout(size, chunk_header_layout));
142        }
143
144        // The final size will be aligned to `MIN_CHUNK_ALIGN = 16`.
145        // To make sure aligning the size downwards does not result in
146        // a size that does not have space for `bytes` we add `MIN_CHUNK_ALIGN` here.
147        size = attempt!(size.checked_add(MIN_CHUNK_ALIGN));
148
149        Some(size)
150    }
151}
152
153const fn max(lhs: usize, rhs: usize) -> usize {
154    if lhs > rhs { lhs } else { rhs }
155}
156
157const fn offset_add_layout(mut offset: usize, layout: Layout) -> Option<usize> {
158    offset = attempt!(up_align(offset, layout.align()));
159    offset = attempt!(offset.checked_add(layout.size()));
160    Some(offset)
161}
162
163#[inline(always)]
164const fn up_align(addr: usize, align: usize) -> Option<usize> {
165    debug_assert!(align.is_power_of_two());
166    let mask = align - 1;
167    let addr_plus_mask = attempt!(addr.checked_add(mask));
168    let aligned = addr_plus_mask & !mask;
169    Some(aligned)
170}
171
172#[inline(always)]
173const fn down_align(addr: usize, align: usize) -> usize {
174    debug_assert!(align.is_power_of_two());
175    let mask = align - 1;
176    addr & !mask
177}