bones_schema/alloc/layout.rs
1//! Utilities for working with [`Layout`].
2
3use std::alloc::{Layout, LayoutError};
4
5/// Extension trait for the [`Layout`] type that copies useful nightly functions so that we can use
6/// them on stable.
7pub trait LayoutExt {
8 /// Creates a layout describing the record for `n` instances of
9 /// `self`, with a suitable amount of padding between each to
10 /// ensure that each instance is given its requested size and
11 /// alignment. On success, returns `(k, offs)` where `k` is the
12 /// layout of the array and `offs` is the distance between the start
13 /// of each element in the array.
14 ///
15 /// On arithmetic overflow, returns `LayoutError`.
16 fn repeat(&self, n: usize) -> Result<(Layout, usize), LayoutError>;
17 // Returns the amount of padding we must insert after `self`
18 /// to ensure that the following address will satisfy `align`
19 /// (measured in bytes).
20 ///
21 /// e.g., if `self.size()` is 9, then `self.padding_needed_for(4)`
22 /// returns 3, because that is the minimum number of bytes of
23 /// padding required to get a 4-aligned address (assuming that the
24 /// corresponding memory block starts at a 4-aligned address).
25 ///
26 /// The return value of this function has no meaning if `align` is
27 /// not a power-of-two.
28 ///
29 /// Note that the utility of the returned value requires `align`
30 /// to be less than or equal to the alignment of the starting
31 /// address for the whole allocated block of memory. One way to
32 /// satisfy this constraint is to ensure `align <= self.align()`.
33 fn padding_needed_for(&self, align: usize) -> usize;
34}
35
36// Hack to construct a [`LayoutError`] which cannot be constructed directly.
37const LAYOUT_ERR: LayoutError = if let Err(e) = Layout::from_size_align(0, 0) {
38 e
39} else {
40 unreachable!()
41};
42
43impl LayoutExt for Layout {
44 #[inline]
45 fn padding_needed_for(&self, align: usize) -> usize {
46 let len = self.size();
47
48 // Rounded up value is:
49 // len_rounded_up = (len + align - 1) & !(align - 1);
50 // and then we return the padding difference: `len_rounded_up - len`.
51 //
52 // We use modular arithmetic throughout:
53 //
54 // 1. align is guaranteed to be > 0, so align - 1 is always
55 // valid.
56 //
57 // 2. `len + align - 1` can overflow by at most `align - 1`,
58 // so the &-mask with `!(align - 1)` will ensure that in the
59 // case of overflow, `len_rounded_up` will itself be 0.
60 // Thus the returned padding, when added to `len`, yields 0,
61 // which trivially satisfies the alignment `align`.
62 //
63 // (Of course, attempts to allocate blocks of memory whose
64 // size and padding overflow in the above manner should cause
65 // the allocator to yield an error anyway.)
66
67 let len_rounded_up = len.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1);
68 len_rounded_up.wrapping_sub(len)
69 }
70
71 #[inline]
72 fn repeat(&self, n: usize) -> Result<(Self, usize), LayoutError> {
73 // This cannot overflow. Quoting from the invariant of Layout:
74 // > `size`, when rounded up to the nearest multiple of `align`,
75 // > must not overflow isize (i.e., the rounded value must be
76 // > less than or equal to `isize::MAX`)
77 let padded_size = self.size() + Layout::padding_needed_for(self, self.align());
78 let alloc_size = padded_size.checked_mul(n).ok_or(LAYOUT_ERR)?;
79
80 // The safe constructor is called here to enforce the isize size limit.
81 let layout = Layout::from_size_align(alloc_size, self.align())?;
82 Ok((layout, padded_size))
83 }
84}