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