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