Skip to main content

bump_scope/chunk/
size_config.rs

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