bump_scope/
bumping.rs

1#![forbid(unsafe_code)]
2#![expect(clippy::needless_pass_by_value)]
3//! Make sure you sync this file `src/bumping.rs`
4//! with `crates/fuzzing-support/src/from_bump_scope/bumping.rs`.
5//!
6//! This file intentionally doesn't import anything other than `core`
7//! to make it easy to fuzz (see above) and debug.
8
9use core::{alloc::Layout, num::NonZeroUsize, ops::Range};
10
11#[cold]
12#[inline(always)]
13pub(crate) fn cold() {}
14
15#[inline(always)]
16pub(crate) fn unlikely(condition: bool) -> bool {
17    if condition {
18        cold();
19    } else {
20        // ...
21    }
22
23    condition
24}
25
26pub(crate) const MIN_CHUNK_ALIGN: usize = 16;
27
28macro_rules! debug_assert_aligned {
29    ($addr:expr, $align:expr) => {
30        let addr = $addr;
31        let align = $align;
32
33        debug_assert!(align.is_power_of_two());
34        let is_aligned = addr & (align - 1) == 0;
35
36        debug_assert!(
37            is_aligned,
38            "expected `{}` ({}) to be aligned to `{}` ({})",
39            stringify!($addr),
40            addr,
41            stringify!($align),
42            align,
43        );
44    };
45}
46
47macro_rules! debug_assert_ge {
48    ($lhs:expr, $rhs:expr) => {
49        let lhs = $lhs;
50        let rhs = $rhs;
51
52        debug_assert!(
53            lhs >= rhs,
54            "expected `{}` ({}) to be greater or equal to `{}` ({})",
55            stringify!($lhs),
56            lhs,
57            stringify!($rhs),
58            rhs,
59        )
60    };
61}
62
63macro_rules! debug_assert_le {
64    ($lhs:expr, $rhs:expr) => {
65        let lhs = $lhs;
66        let rhs = $rhs;
67
68        debug_assert!(
69            lhs <= rhs,
70            "expected `{}` ({}) to be less than or equal to `{}` ({})",
71            stringify!($lhs),
72            lhs,
73            stringify!($rhs),
74            rhs,
75        )
76    };
77}
78
79/// Arguments for [`bump_up`], [`bump_down`], [`bump_prepare_up`] and [`bump_prepare_down`].
80pub(crate) struct BumpProps {
81    /// The start of the remaining free allocation region.
82    ///
83    /// This must not be zero.
84    /// This must be less than or equal to [`end`](Self::end).
85    pub(crate) start: usize,
86
87    /// The end of the remaining free allocation region.
88    ///
89    /// This must not be zero.
90    /// This must be greater or equal to [`start`](Self::start).
91    pub(crate) end: usize,
92
93    /// The minimum alignment of the bump allocator.
94    ///
95    /// This must be less or equal to [`MIN_CHUNK_ALIGN`].
96    pub(crate) min_align: usize,
97
98    /// The allocation layout.
99    pub(crate) layout: Layout,
100
101    /// Whether the allocation layout's alignment is known at compile time.
102    ///
103    /// This is an optimization hint. `false` is always valid.
104    pub(crate) align_is_const: bool,
105
106    /// Whether the allocation layout's size is known at compile time.
107    ///
108    /// This is an optimization hint. `false` is always valid.
109    pub(crate) size_is_const: bool,
110
111    /// Whether the allocation layout's size is a multiple of its alignment and that is known at compile time.
112    ///
113    /// This is an optimization hint. `false` is always valid.
114    ///
115    /// This must only be true if `layout.size() % layout.align()` is also true.
116    pub(crate) size_is_multiple_of_align: bool,
117}
118
119impl BumpProps {
120    #[inline(always)]
121    fn debug_assert_valid(&self) {
122        debug_assert_ne!(self.start, 0);
123        debug_assert_ne!(self.end, 0);
124
125        debug_assert!(self.min_align.is_power_of_two());
126        debug_assert!(self.min_align <= MIN_CHUNK_ALIGN);
127
128        if self.size_is_multiple_of_align {
129            debug_assert_eq!(self.layout.size() % self.layout.align(), 0);
130        }
131
132        debug_assert_le!(self.start, self.end);
133    }
134}
135
136/// A successful upwards bump allocation. Returned from [`bump_up`].
137pub(crate) struct BumpUp {
138    /// The new position of the bump allocator (the next [`BumpProps::start`]).
139    pub(crate) new_pos: usize,
140
141    /// The address of the allocation's pointer.
142    pub(crate) ptr: usize,
143}
144
145/// Does upwards bump allocation.
146///
147/// - `end` must be a multiple of [`MIN_CHUNK_ALIGN`]
148#[inline(always)]
149pub(crate) fn bump_up(props: BumpProps) -> Option<BumpUp> {
150    props.debug_assert_valid();
151
152    let BumpProps {
153        mut start,
154        end,
155        layout,
156        min_align,
157        align_is_const,
158        size_is_const,
159        size_is_multiple_of_align,
160    } = props;
161
162    debug_assert_eq!(start % min_align, 0);
163    debug_assert_eq!(end % MIN_CHUNK_ALIGN, 0);
164
165    // Used for assertion at the end of the function.
166    let original_start = start;
167
168    let mut new_pos;
169
170    // Doing the `layout.size() < MIN_CHUNK_ALIGN` trick here (as seen in !UP)
171    // results in worse codegen, so we don't.
172
173    if align_is_const && layout.align() <= MIN_CHUNK_ALIGN {
174        // Constant, small alignment fast path!
175
176        if align_is_const && layout.align() <= min_align {
177            // Alignment is already sufficient.
178        } else {
179            // Aligning an address that is `<= range.end` with an alignment `<= MIN_CHUNK_ALIGN`
180            // can't exceed `range.end` nor overflow as `range.end` is always aligned to `MIN_CHUNK_ALIGN`.
181            start = up_align_unchecked(start, layout.align());
182        }
183
184        let remaining = end - start;
185
186        if unlikely(layout.size() > remaining) {
187            return None;
188        }
189
190        // Doesn't exceed `end` because of the check above.
191        new_pos = start + layout.size();
192    } else {
193        // Alignment is `> MIN_CHUNK_ALIGN` or not const.
194
195        // `start` and `align` are both nonzero.
196        // `aligned_down` is the aligned pointer minus `layout.align()`.
197        let aligned_down = (start - 1) & !(layout.align() - 1);
198
199        // `align + size` cannot overflow as per `Layout`'s rules.
200        //
201        // This could also be a `checked_add`, but we use `saturating_add` to save us a branch.
202        // The `if` below will return None if the addition saturated and returned `usize::MAX`.
203        new_pos = aligned_down.saturating_add(layout.align() + layout.size());
204
205        // Note that `new_pos` being `usize::MAX` is an invalid value for `new_pos` and we MUST return None.
206        // Due to `end` being always aligned to `MIN_CHUNK_ALIGN`, it can't be `usize::MAX`.
207        // Thus when `new_pos` is `usize::MAX` this will always return None.
208        if unlikely(new_pos > end) {
209            return None;
210        }
211
212        // Doesn't exceed `end` because `aligned_down + align + size` didn't.
213        start = aligned_down + layout.align();
214    }
215
216    if (align_is_const && size_is_multiple_of_align && layout.align() >= min_align)
217        || (size_is_const && (layout.size() % min_align == 0))
218    {
219        // We are already aligned to `min_align`.
220    } else {
221        // Up aligning an address `<= range.end` with an alignment `<= MIN_CHUNK_ALIGN` (which `min_align` is)
222        // can't exceed `range.end` and thus also can't overflow.
223        new_pos = up_align_unchecked(new_pos, min_align);
224    }
225
226    debug_assert_aligned!(start, layout.align());
227    debug_assert_aligned!(start, min_align);
228    debug_assert_aligned!(new_pos, min_align);
229    debug_assert_ne!(new_pos, 0);
230    debug_assert_ne!(start, 0);
231    debug_assert_le!(start, end);
232    debug_assert_ge!(start, original_start);
233    debug_assert_ge!(new_pos - start, layout.size());
234    debug_assert_le!(new_pos, end);
235    debug_assert_ge!(new_pos, start);
236
237    Some(BumpUp { new_pos, ptr: start })
238}
239
240/// Does downwards bump allocation.
241#[inline(always)]
242pub(crate) fn bump_down(props: BumpProps) -> Option<usize> {
243    props.debug_assert_valid();
244
245    let BumpProps {
246        start,
247        mut end,
248        layout,
249        min_align,
250        align_is_const,
251        size_is_const,
252        size_is_multiple_of_align,
253    } = props;
254
255    debug_assert_eq!(start % MIN_CHUNK_ALIGN, 0);
256    debug_assert_eq!(end % min_align, 0);
257
258    // Used for assertions only.
259    let original_end = end;
260
261    // This variables is meant to be computed at compile time.
262    let needs_aligning = {
263        // The bump pointer must end up aligned to the layout's alignment.
264        //
265        // Manual alignment for the layout's alignment can be elided
266        // if the layout's size is a multiple of its alignment
267        // and its alignment is less or equal to the minimum alignment.
268        let can_elide_aligning_for_layout = size_is_multiple_of_align && align_is_const && layout.align() <= min_align;
269
270        // The bump pointer must end up aligned to the minimum alignment again.
271        //
272        // Manual alignment for the minimum alignment can be elided if:
273        // - the layout's size is a multiple of its alignment
274        //   and its alignment is greater or equal to the minimum alignment
275        // - the layout's size is a multiple of the minimum alignment
276        //
277        // In either case the bump pointer will end up inherently aligned when the pointer
278        // is bumped downwards by the layout's size.
279        let can_elide_aligning_for_min_align = {
280            let due_to_layout_align = size_is_multiple_of_align && align_is_const && layout.align() >= min_align;
281            let due_to_layout_size = size_is_const && (layout.size() % min_align == 0);
282            due_to_layout_align || due_to_layout_size
283        };
284
285        let can_elide_aligning = can_elide_aligning_for_layout && can_elide_aligning_for_min_align;
286
287        !can_elide_aligning
288    };
289
290    if size_is_const && layout.size() <= MIN_CHUNK_ALIGN {
291        // When `size <= MIN_CHUNK_ALIGN` subtracting it from `end` can't overflow, as the lowest value for `end` would be `start` which is aligned to `MIN_CHUNK_ALIGN`,
292        // thus its address can't be smaller than it.
293        end -= layout.size();
294
295        if needs_aligning {
296            // At this point layout's align is const, because we assume `size_is_const` implies `align_is_const`.
297            // That means `max` is evaluated at compile time, so we don't bother having different cases for either alignment.
298            end = down_align(end, layout.align().max(min_align));
299        }
300
301        if unlikely(end < start) {
302            return None;
303        }
304    } else if align_is_const && layout.align() <= MIN_CHUNK_ALIGN {
305        // Constant, small alignment fast path!
306        let remaining = end - start;
307
308        if unlikely(layout.size() > remaining) {
309            return None;
310        }
311
312        // Doesn't overflow because of the check above.
313        end -= layout.size();
314
315        if needs_aligning {
316            // Down aligning an address `>= range.start` with an alignment `<= MIN_CHUNK_ALIGN` (which `layout.align()` is)
317            // can't exceed `range.start`, and thus also can't overflow.
318            end = down_align(end, layout.align().max(min_align));
319        }
320    } else {
321        // Alignment is `> MIN_CHUNK_ALIGN` or not const.
322
323        // This could also be a `checked_sub`, but we use `saturating_sub` to save us a branch.
324        // The `if` below will return None if the addition saturated and returned `0`.
325        end = end.saturating_sub(layout.size());
326        end = down_align(end, layout.align().max(min_align));
327
328        // Note that `end` being `0` is an invalid value for `end` and we MUST return None.
329        // Due to `start` being `NonNull`, it can't be `0`.
330        // Thus when `end` is `0` this will always return None.
331        if unlikely(end < start) {
332            return None;
333        }
334    }
335
336    debug_assert_aligned!(end, layout.align());
337    debug_assert_aligned!(end, min_align);
338    debug_assert_ne!(end, 0);
339    debug_assert_ge!(end, start);
340    debug_assert_ge!(original_end - end, layout.size());
341
342    Some(end)
343}
344
345/// Prepares a slice allocation by returning the start and end address for a maximally sized region
346/// where both start and end are aligned to `layout.align()`.
347///
348/// - `end` must be a multiple of [`MIN_CHUNK_ALIGN`]
349#[inline(always)]
350pub(crate) fn bump_prepare_up(props: BumpProps) -> Option<Range<usize>> {
351    props.debug_assert_valid();
352
353    let BumpProps {
354        mut start,
355        end,
356        layout,
357        min_align,
358        align_is_const,
359        size_is_const: _,
360        size_is_multiple_of_align: _,
361    } = props;
362
363    debug_assert_eq!(end % MIN_CHUNK_ALIGN, 0);
364
365    if align_is_const && layout.align() <= min_align {
366        // Alignment is already sufficient.
367    } else {
368        // `start` needs to be aligned.
369        if align_is_const && layout.align() <= MIN_CHUNK_ALIGN {
370            // Aligning an address that is `<= range.end` with an alignment
371            // that is `<= MIN_CHUNK_ALIGN` can't exceed `range.end` and
372            // thus can't overflow.
373            start = up_align_unchecked(start, layout.align());
374        } else {
375            start = up_align(start, layout.align())?.get();
376
377            if unlikely(start > end) {
378                return None;
379            }
380        }
381    }
382
383    let remaining = end - start;
384
385    if unlikely(layout.size() > remaining) {
386        return None;
387    }
388
389    // Layout fits, we just trim off the excess to make end aligned.
390    //
391    // Note that `end` does *not* need to be aligned to `min_align` because the `prepare` operation doesn't
392    // move the bump pointer which is the thing that does need to be aligned.
393    //
394    // Aligning to `min_align` will happen once `use_prepared_slice_allocation` is called.
395    let end = down_align(end, layout.align());
396
397    debug_assert_aligned!(start, layout.align());
398    debug_assert_aligned!(end, layout.align());
399    debug_assert_ne!(start, 0);
400    debug_assert_ne!(end, 0);
401
402    Some(start..end)
403}
404
405/// Prepares a slice allocation by returning the start and end address for a maximally sized region
406/// where both start and end are aligned to `layout.align()`.
407#[inline(always)]
408pub(crate) fn bump_prepare_down(props: BumpProps) -> Option<Range<usize>> {
409    props.debug_assert_valid();
410
411    let BumpProps {
412        start,
413        mut end,
414        layout,
415        min_align,
416        align_is_const,
417        size_is_const: _,
418        size_is_multiple_of_align: _,
419    } = props;
420
421    if align_is_const && layout.align() <= min_align {
422        // Alignment is already sufficient.
423    } else {
424        end = down_align(end, layout.align());
425
426        if align_is_const && layout.align() <= MIN_CHUNK_ALIGN {
427            // End is valid.
428        } else {
429            // End could be less than start at this point.
430            if unlikely(end < start) {
431                return None;
432            }
433        }
434    }
435
436    let remaining = end - start;
437
438    if unlikely(layout.size() > remaining) {
439        return None;
440    }
441
442    // Layout fits, we just trim off the excess to make start aligned.
443    //
444    // Note that `start` doesn't need to be aligned to `min_align` because the `prepare` operation doesn't
445    // move the bump pointer which is the thing that needs to be aligned.
446    //
447    // Aligning to `min_align` will happen once `use_prepared_slice_allocation` is called.
448    let start = up_align_unchecked(start, layout.align());
449
450    debug_assert_aligned!(start, layout.align());
451    debug_assert_aligned!(end, layout.align());
452    debug_assert_ne!(start, 0);
453    debug_assert_ne!(end, 0);
454
455    Some(start..end)
456}
457
458#[inline(always)]
459const fn down_align(addr: usize, align: usize) -> usize {
460    debug_assert!(align.is_power_of_two());
461    let mask = align - 1;
462    addr & !mask
463}
464
465/// Doesn't check for overflow.
466#[inline(always)]
467const fn up_align_unchecked(addr: usize, align: usize) -> usize {
468    debug_assert!(align.is_power_of_two());
469    let mask = align - 1;
470    (addr + mask) & !mask
471}
472
473#[inline(always)]
474const fn up_align(addr: usize, align: usize) -> Option<NonZeroUsize> {
475    debug_assert!(align.is_power_of_two());
476    let mask = align - 1;
477    let Some(addr_plus_mask) = addr.checked_add(mask) else {
478        return None;
479    };
480    NonZeroUsize::new(addr_plus_mask & !mask)
481}