• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2016 The vulkano developers
2 // Licensed under the Apache License, Version 2.0
3 // <LICENSE-APACHE or
4 // https://www.apache.org/licenses/LICENSE-2.0> or the MIT
5 // license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
6 // at your option. All files in the project carrying such
7 // notice may not be copied, modified, or distributed except
8 // according to those terms.
9 
10 use super::align_up;
11 use crate::{macros::try_opt, memory::DeviceAlignment, DeviceSize, NonZeroDeviceSize};
12 use std::{
13     alloc::Layout,
14     error::Error,
15     fmt::{Debug, Display, Formatter, Result as FmtResult},
16     hash::Hash,
17     mem::size_of,
18 };
19 
20 /// Vulkan analog of std's [`Layout`], represented using [`DeviceSize`]s.
21 ///
22 /// Unlike `Layout`s, `DeviceLayout`s are required to have non-zero size.
23 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
24 pub struct DeviceLayout {
25     size: NonZeroDeviceSize,
26     alignment: DeviceAlignment,
27 }
28 
29 impl DeviceLayout {
30     /// The maximum size of a memory block after its layout's size has been rounded up to the
31     /// nearest multiple of its layout's alignment.
32     ///
33     /// This invariant is enforced to avoid arithmetic overflows when constructing layouts and when
34     /// allocating memory. Any layout that doesn't uphold this invariant will therefore *lead to
35     /// undefined behavior*.
36     pub const MAX_SIZE: DeviceSize = DeviceAlignment::MAX.as_devicesize() - 1;
37 
38     /// Creates a new `DeviceLayout` from a [`Layout`], or returns an error if the `Layout` has
39     /// zero size.
40     #[inline]
from_layout(layout: Layout) -> Result<Self, TryFromLayoutError>41     pub const fn from_layout(layout: Layout) -> Result<Self, TryFromLayoutError> {
42         let (size, alignment) = Self::size_alignment_from_layout(&layout);
43 
44         #[cfg(any(
45             target_pointer_width = "64",
46             target_pointer_width = "32",
47             target_pointer_width = "16",
48         ))]
49         {
50             const _: () = assert!(size_of::<DeviceSize>() >= size_of::<usize>());
51             const _: () = assert!(DeviceLayout::MAX_SIZE >= isize::MAX as DeviceSize);
52 
53             if let Some(size) = NonZeroDeviceSize::new(size) {
54                 // SAFETY: Under the precondition that `usize` can't overflow `DeviceSize`, which
55                 // we checked above, `Layout`'s overflow-invariant is the same if not stricter than
56                 // that of `DeviceLayout`.
57                 Ok(unsafe { DeviceLayout::new_unchecked(size, alignment) })
58             } else {
59                 Err(TryFromLayoutError)
60             }
61         }
62     }
63 
64     /// Converts the `DeviceLayout` into a [`Layout`], or returns an error if the `DeviceLayout`
65     /// doesn't meet the invariants of `Layout`.
66     #[inline]
into_layout(self) -> Result<Layout, TryFromDeviceLayoutError>67     pub const fn into_layout(self) -> Result<Layout, TryFromDeviceLayoutError> {
68         let (size, alignment) = (self.size(), self.alignment().as_devicesize());
69 
70         #[cfg(target_pointer_width = "64")]
71         {
72             const _: () = assert!(size_of::<DeviceSize>() <= size_of::<usize>());
73             const _: () = assert!(DeviceLayout::MAX_SIZE as usize <= isize::MAX as usize);
74 
75             // SAFETY: Under the precondition that `DeviceSize` can't overflow `usize`, which we
76             // checked above, `DeviceLayout`'s overflow-invariant is the same if not stricter that
77             // of `Layout`.
78             Ok(unsafe { Layout::from_size_align_unchecked(size as usize, alignment as usize) })
79         }
80         #[cfg(any(target_pointer_width = "32", target_pointer_width = "16"))]
81         {
82             const _: () = assert!(size_of::<DeviceSize>() > size_of::<usize>());
83             const _: () = assert!(DeviceLayout::MAX_SIZE > isize::MAX as DeviceSize);
84 
85             if size > usize::MAX as DeviceSize || alignment > usize::MAX as DeviceSize {
86                 Err(TryFromDeviceLayoutError)
87             } else if let Ok(layout) = Layout::from_size_align(size as usize, alignment as usize) {
88                 Ok(layout)
89             } else {
90                 Err(TryFromDeviceLayoutError)
91             }
92         }
93     }
94 
95     /// Creates a new `DeviceLayout` from the given `size` and `alignment`.
96     ///
97     /// Returns [`None`] if `size` is zero, `alignment` is not a power of two, or if `size` would
98     /// exceed [`DeviceLayout::MAX_SIZE`] when rounded up to the nearest multiple of `alignment`.
99     #[inline]
from_size_alignment(size: DeviceSize, alignment: DeviceSize) -> Option<Self>100     pub const fn from_size_alignment(size: DeviceSize, alignment: DeviceSize) -> Option<Self> {
101         let size = try_opt!(NonZeroDeviceSize::new(size));
102         let alignment = try_opt!(DeviceAlignment::new(alignment));
103 
104         DeviceLayout::new(size, alignment)
105     }
106 
107     /// Creates a new `DeviceLayout` from the given `size` and `alignment` without doing any
108     /// checks.
109     ///
110     /// # Safety
111     ///
112     /// - `size` must be non-zero.
113     /// - `alignment` must be a power of two, which also means it must be non-zero.
114     /// - `size`, when rounded up to the nearest multiple of `alignment`, must not exceed
115     ///   [`DeviceLayout::MAX_SIZE`].
116     #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
117     #[inline]
from_size_alignment_unchecked( size: DeviceSize, alignment: DeviceSize, ) -> Self118     pub const unsafe fn from_size_alignment_unchecked(
119         size: DeviceSize,
120         alignment: DeviceSize,
121     ) -> Self {
122         DeviceLayout::new_unchecked(
123             NonZeroDeviceSize::new_unchecked(size),
124             DeviceAlignment::new_unchecked(alignment),
125         )
126     }
127 
128     /// Creates a new `DeviceLayout` from the given `size` and `alignment`.
129     ///
130     /// Returns [`None`] if `size` would exceed [`DeviceLayout::MAX_SIZE`] when rounded up to the
131     /// nearest multiple of `alignment`.
132     #[inline]
new(size: NonZeroDeviceSize, alignment: DeviceAlignment) -> Option<Self>133     pub const fn new(size: NonZeroDeviceSize, alignment: DeviceAlignment) -> Option<Self> {
134         if size.get() > Self::max_size_for_alignment(alignment) {
135             None
136         } else {
137             // SAFETY: We checked that the rounded-up size won't exceed `DeviceLayout::MAX_SIZE`.
138             Some(unsafe { DeviceLayout::new_unchecked(size, alignment) })
139         }
140     }
141 
142     #[inline(always)]
max_size_for_alignment(alignment: DeviceAlignment) -> DeviceSize143     const fn max_size_for_alignment(alignment: DeviceAlignment) -> DeviceSize {
144         // `DeviceLayout::MAX_SIZE` is `DeviceAlignment::MAX - 1`, so this can't overflow.
145         DeviceLayout::MAX_SIZE - (alignment.as_devicesize() - 1)
146     }
147 
148     /// Creates a new `DeviceLayout` from the given `size` and `alignment` without checking for
149     /// potential overflow.
150     ///
151     /// # Safety
152     ///
153     /// - `size`, when rounded up to the nearest multiple of `alignment`, must not exceed
154     ///   [`DeviceLayout::MAX_SIZE`].
155     #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
156     #[inline]
new_unchecked(size: NonZeroDeviceSize, alignment: DeviceAlignment) -> Self157     pub const unsafe fn new_unchecked(size: NonZeroDeviceSize, alignment: DeviceAlignment) -> Self {
158         debug_assert!(size.get() <= Self::max_size_for_alignment(alignment));
159 
160         DeviceLayout { size, alignment }
161     }
162 
163     /// Returns the minimum size in bytes for a memory block of this layout.
164     #[inline]
size(&self) -> DeviceSize165     pub const fn size(&self) -> DeviceSize {
166         self.size.get()
167     }
168 
169     /// Returns the minimum alignment for a memory block of this layout.
170     #[inline]
alignment(&self) -> DeviceAlignment171     pub const fn alignment(&self) -> DeviceAlignment {
172         self.alignment
173     }
174 
175     /// Creates a new `DeviceLayout` from `self` that is also aligned to `alignment` at minimum.
176     ///
177     /// Returns [`None`] if `self.size()` would overflow [`DeviceLayout::MAX_SIZE`] when rounded up
178     /// to the nearest multiple of `alignment`.
179     #[inline]
align_to(&self, alignment: DeviceAlignment) -> Option<Self>180     pub const fn align_to(&self, alignment: DeviceAlignment) -> Option<Self> {
181         DeviceLayout::new(self.size, DeviceAlignment::max(self.alignment, alignment))
182     }
183 
184     /// Returns the amount of padding that needs to be added to `self.size()` such that the result
185     /// is a multiple of `alignment`.
186     #[inline]
padding_needed_for(&self, alignment: DeviceAlignment) -> DeviceSize187     pub const fn padding_needed_for(&self, alignment: DeviceAlignment) -> DeviceSize {
188         let size = self.size();
189 
190         align_up(size, alignment).wrapping_sub(size)
191     }
192 
193     /// Creates a new `DeviceLayout` by rounding up `self.size()` to the nearest multiple of
194     /// `self.alignment()`.
195     #[inline]
pad_to_alignment(&self) -> Self196     pub const fn pad_to_alignment(&self) -> Self {
197         // SAFETY: `DeviceLayout`'s invariant guarantees that the rounded up size won't exceed
198         // `DeviceLayout::MAX_SIZE`.
199         unsafe { DeviceLayout::new_unchecked(self.padded_size(), self.alignment) }
200     }
201 
202     #[inline(always)]
padded_size(&self) -> NonZeroDeviceSize203     const fn padded_size(&self) -> NonZeroDeviceSize {
204         let size = align_up(self.size(), self.alignment);
205 
206         // SAFETY: `DeviceLayout`'s invariant guarantees that the rounded up size won't overflow.
207         unsafe { NonZeroDeviceSize::new_unchecked(size) }
208     }
209 
210     /// Creates a new `DeviceLayout` describing the record for `n` instances of `self`, possibly
211     /// with padding at the end of each to ensure correct alignment of all instances.
212     ///
213     /// Returns a tuple consisting of the new layout and the stride, in bytes, of `self`, or
214     /// returns [`None`] on arithmetic overflow or when the total size would exceed
215     /// [`DeviceLayout::MAX_SIZE`].
216     #[inline]
repeat(&self, n: NonZeroDeviceSize) -> Option<(Self, DeviceSize)>217     pub const fn repeat(&self, n: NonZeroDeviceSize) -> Option<(Self, DeviceSize)> {
218         let stride = self.padded_size();
219         let size = try_opt!(stride.checked_mul(n));
220         let layout = try_opt!(DeviceLayout::new(size, self.alignment));
221 
222         Some((layout, stride.get()))
223     }
224 
225     /// Creates a new `DeviceLayout` describing the record for `self` followed by `next`, including
226     /// potential padding between them to ensure `next` will be properly aligned, but without any
227     /// trailing padding. You should use [`pad_to_alignment`] after you are done extending the
228     /// layout with all fields to get a valid `#[repr(C)]` layout.
229     ///
230     /// The alignments of the two layouts get combined by picking the maximum between them.
231     ///
232     /// Returns a tuple consisting of the resulting layout as well as the offset, in bytes, of
233     /// `next`.
234     ///
235     /// Returns [`None`] on arithmetic overflow or when the total size rounded up to the nearest
236     /// multiple of the combined alignment would exceed [`DeviceLayout::MAX_SIZE`].
237     ///
238     /// [`pad_to_alignment`]: Self::pad_to_alignment
239     #[inline]
extend(&self, next: Self) -> Option<(Self, DeviceSize)>240     pub const fn extend(&self, next: Self) -> Option<(Self, DeviceSize)> {
241         self.extend_inner(next.size(), next.alignment)
242     }
243 
244     /// Same as [`extend`], except it extends with a [`Layout`].
245     ///
246     /// [`extend`]: Self::extend
247     #[inline]
extend_with_layout(&self, next: Layout) -> Option<(Self, DeviceSize)>248     pub const fn extend_with_layout(&self, next: Layout) -> Option<(Self, DeviceSize)> {
249         let (next_size, next_alignment) = Self::size_alignment_from_layout(&next);
250 
251         self.extend_inner(next_size, next_alignment)
252     }
253 
extend_inner( &self, next_size: DeviceSize, next_alignment: DeviceAlignment, ) -> Option<(Self, DeviceSize)>254     const fn extend_inner(
255         &self,
256         next_size: DeviceSize,
257         next_alignment: DeviceAlignment,
258     ) -> Option<(Self, DeviceSize)> {
259         let padding = self.padding_needed_for(next_alignment);
260         let offset = try_opt!(self.size.checked_add(padding));
261         let size = try_opt!(offset.checked_add(next_size));
262         let alignment = DeviceAlignment::max(self.alignment, next_alignment);
263         let layout = try_opt!(DeviceLayout::new(size, alignment));
264 
265         Some((layout, offset.get()))
266     }
267 
268     /// Creates a new `DeviceLayout` describing the record for the `previous` [`Layout`] followed
269     /// by `self`. This function is otherwise the same as [`extend`].
270     ///
271     /// [`extend`]: Self::extend
272     #[inline]
extend_from_layout(self, previous: &Layout) -> Option<(Self, DeviceSize)>273     pub const fn extend_from_layout(self, previous: &Layout) -> Option<(Self, DeviceSize)> {
274         let (size, alignment) = Self::size_alignment_from_layout(previous);
275 
276         let padding = align_up(size, self.alignment).wrapping_sub(size);
277         let offset = try_opt!(size.checked_add(padding));
278         let size = try_opt!(self.size.checked_add(offset));
279         let alignment = DeviceAlignment::max(alignment, self.alignment);
280         let layout = try_opt!(DeviceLayout::new(size, alignment));
281 
282         Some((layout, offset))
283     }
284 
285     #[inline(always)]
size_alignment_from_layout(layout: &Layout) -> (DeviceSize, DeviceAlignment)286     const fn size_alignment_from_layout(layout: &Layout) -> (DeviceSize, DeviceAlignment) {
287         #[cfg(any(
288             target_pointer_width = "64",
289             target_pointer_width = "32",
290             target_pointer_width = "16",
291         ))]
292         {
293             const _: () = assert!(size_of::<DeviceSize>() >= size_of::<usize>());
294             const _: () = assert!(DeviceLayout::MAX_SIZE >= isize::MAX as DeviceSize);
295 
296             // We checked that `usize` can't overflow `DeviceSize`, so this can't truncate.
297             let (size, alignment) = (layout.size() as DeviceSize, layout.align() as DeviceSize);
298 
299             // SAFETY: `Layout`'s alignment-invariant guarantees that it is a power of two.
300             let alignment = unsafe { DeviceAlignment::new_unchecked(alignment) };
301 
302             (size, alignment)
303         }
304     }
305 }
306 
307 impl TryFrom<Layout> for DeviceLayout {
308     type Error = TryFromLayoutError;
309 
310     #[inline]
try_from(layout: Layout) -> Result<Self, Self::Error>311     fn try_from(layout: Layout) -> Result<Self, Self::Error> {
312         DeviceLayout::from_layout(layout)
313     }
314 }
315 
316 impl TryFrom<DeviceLayout> for Layout {
317     type Error = TryFromDeviceLayoutError;
318 
319     #[inline]
try_from(device_layout: DeviceLayout) -> Result<Self, Self::Error>320     fn try_from(device_layout: DeviceLayout) -> Result<Self, Self::Error> {
321         DeviceLayout::into_layout(device_layout)
322     }
323 }
324 
325 /// Error that can happen when converting a [`Layout`] to a [`DeviceLayout`].
326 ///
327 /// It occurs when the `Layout` has zero size.
328 #[derive(Clone, Debug, PartialEq, Eq)]
329 pub struct TryFromLayoutError;
330 
331 impl Error for TryFromLayoutError {}
332 
333 impl Display for TryFromLayoutError {
fmt(&self, f: &mut Formatter<'_>) -> FmtResult334     fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
335         f.write_str("attempted to convert a zero-size `Layout` to a `DeviceLayout`")
336     }
337 }
338 
339 /// Error that can happen when converting a [`DeviceLayout`] to a [`Layout`].
340 #[derive(Clone, Debug, PartialEq, Eq)]
341 pub struct TryFromDeviceLayoutError;
342 
343 impl Error for TryFromDeviceLayoutError {}
344 
345 impl Display for TryFromDeviceLayoutError {
fmt(&self, f: &mut Formatter<'_>) -> FmtResult346     fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
347         f.write_str(
348             "attempted to convert a `DeviceLayout` to a `Layout` which would result in a \
349             violation of `Layout`'s overflow-invariant",
350         )
351     }
352 }
353