use super::align_up;
use crate::{macros::try_opt, memory::DeviceAlignment, DeviceSize, NonZeroDeviceSize};
use std::{
alloc::Layout,
error::Error,
fmt::{Debug, Display, Formatter, Result as FmtResult},
hash::Hash,
mem::size_of,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct DeviceLayout {
size: NonZeroDeviceSize,
alignment: DeviceAlignment,
}
impl DeviceLayout {
pub const MAX_SIZE: DeviceSize = DeviceAlignment::MAX.as_devicesize() - 1;
#[inline]
pub const fn from_layout(layout: Layout) -> Result<Self, TryFromLayoutError> {
let (size, alignment) = Self::size_alignment_from_layout(&layout);
#[cfg(any(
target_pointer_width = "64",
target_pointer_width = "32",
target_pointer_width = "16",
))]
{
const _: () = assert!(size_of::<DeviceSize>() >= size_of::<usize>());
const _: () = assert!(DeviceLayout::MAX_SIZE >= isize::MAX as DeviceSize);
if let Some(size) = NonZeroDeviceSize::new(size) {
Ok(unsafe { DeviceLayout::new_unchecked(size, alignment) })
} else {
Err(TryFromLayoutError)
}
}
}
#[inline]
pub const fn into_layout(self) -> Result<Layout, TryFromDeviceLayoutError> {
let (size, alignment) = (self.size(), self.alignment().as_devicesize());
#[cfg(target_pointer_width = "64")]
{
const _: () = assert!(size_of::<DeviceSize>() <= size_of::<usize>());
const _: () = assert!(DeviceLayout::MAX_SIZE as usize <= isize::MAX as usize);
Ok(unsafe { Layout::from_size_align_unchecked(size as usize, alignment as usize) })
}
#[cfg(any(target_pointer_width = "32", target_pointer_width = "16"))]
{
const _: () = assert!(size_of::<DeviceSize>() > size_of::<usize>());
const _: () = assert!(DeviceLayout::MAX_SIZE > isize::MAX as DeviceSize);
if size > usize::MAX as DeviceSize || alignment > usize::MAX as DeviceSize {
Err(TryFromDeviceLayoutError)
} else if let Ok(layout) = Layout::from_size_align(size as usize, alignment as usize) {
Ok(layout)
} else {
Err(TryFromDeviceLayoutError)
}
}
}
#[inline]
pub const fn from_size_alignment(size: DeviceSize, alignment: DeviceSize) -> Option<Self> {
let size = try_opt!(NonZeroDeviceSize::new(size));
let alignment = try_opt!(DeviceAlignment::new(alignment));
DeviceLayout::new(size, alignment)
}
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
#[inline]
pub const unsafe fn from_size_alignment_unchecked(
size: DeviceSize,
alignment: DeviceSize,
) -> Self {
DeviceLayout::new_unchecked(
NonZeroDeviceSize::new_unchecked(size),
DeviceAlignment::new_unchecked(alignment),
)
}
#[inline]
pub const fn new(size: NonZeroDeviceSize, alignment: DeviceAlignment) -> Option<Self> {
if size.get() > Self::max_size_for_alignment(alignment) {
None
} else {
Some(unsafe { DeviceLayout::new_unchecked(size, alignment) })
}
}
#[inline(always)]
const fn max_size_for_alignment(alignment: DeviceAlignment) -> DeviceSize {
DeviceLayout::MAX_SIZE - (alignment.as_devicesize() - 1)
}
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
#[inline]
pub const unsafe fn new_unchecked(size: NonZeroDeviceSize, alignment: DeviceAlignment) -> Self {
debug_assert!(size.get() <= Self::max_size_for_alignment(alignment));
DeviceLayout { size, alignment }
}
#[inline]
pub const fn size(&self) -> DeviceSize {
self.size.get()
}
#[inline]
pub const fn alignment(&self) -> DeviceAlignment {
self.alignment
}
#[inline]
pub const fn align_to(&self, alignment: DeviceAlignment) -> Option<Self> {
DeviceLayout::new(self.size, DeviceAlignment::max(self.alignment, alignment))
}
#[inline]
pub const fn padding_needed_for(&self, alignment: DeviceAlignment) -> DeviceSize {
let size = self.size();
align_up(size, alignment).wrapping_sub(size)
}
#[inline]
pub const fn pad_to_alignment(&self) -> Self {
unsafe { DeviceLayout::new_unchecked(self.padded_size(), self.alignment) }
}
#[inline(always)]
const fn padded_size(&self) -> NonZeroDeviceSize {
let size = align_up(self.size(), self.alignment);
unsafe { NonZeroDeviceSize::new_unchecked(size) }
}
#[inline]
pub const fn repeat(&self, n: NonZeroDeviceSize) -> Option<(Self, DeviceSize)> {
let stride = self.padded_size();
let size = try_opt!(stride.checked_mul(n));
let layout = try_opt!(DeviceLayout::new(size, self.alignment));
Some((layout, stride.get()))
}
#[inline]
pub const fn extend(&self, next: Self) -> Option<(Self, DeviceSize)> {
self.extend_inner(next.size(), next.alignment)
}
#[inline]
pub const fn extend_with_layout(&self, next: Layout) -> Option<(Self, DeviceSize)> {
let (next_size, next_alignment) = Self::size_alignment_from_layout(&next);
self.extend_inner(next_size, next_alignment)
}
const fn extend_inner(
&self,
next_size: DeviceSize,
next_alignment: DeviceAlignment,
) -> Option<(Self, DeviceSize)> {
let padding = self.padding_needed_for(next_alignment);
let offset = try_opt!(self.size.checked_add(padding));
let size = try_opt!(offset.checked_add(next_size));
let alignment = DeviceAlignment::max(self.alignment, next_alignment);
let layout = try_opt!(DeviceLayout::new(size, alignment));
Some((layout, offset.get()))
}
#[inline]
pub const fn extend_from_layout(self, previous: &Layout) -> Option<(Self, DeviceSize)> {
let (size, alignment) = Self::size_alignment_from_layout(previous);
let padding = align_up(size, self.alignment).wrapping_sub(size);
let offset = try_opt!(size.checked_add(padding));
let size = try_opt!(self.size.checked_add(offset));
let alignment = DeviceAlignment::max(alignment, self.alignment);
let layout = try_opt!(DeviceLayout::new(size, alignment));
Some((layout, offset))
}
#[inline(always)]
const fn size_alignment_from_layout(layout: &Layout) -> (DeviceSize, DeviceAlignment) {
#[cfg(any(
target_pointer_width = "64",
target_pointer_width = "32",
target_pointer_width = "16",
))]
{
const _: () = assert!(size_of::<DeviceSize>() >= size_of::<usize>());
const _: () = assert!(DeviceLayout::MAX_SIZE >= isize::MAX as DeviceSize);
let (size, alignment) = (layout.size() as DeviceSize, layout.align() as DeviceSize);
let alignment = unsafe { DeviceAlignment::new_unchecked(alignment) };
(size, alignment)
}
}
}
impl TryFrom<Layout> for DeviceLayout {
type Error = TryFromLayoutError;
#[inline]
fn try_from(layout: Layout) -> Result<Self, Self::Error> {
DeviceLayout::from_layout(layout)
}
}
impl TryFrom<DeviceLayout> for Layout {
type Error = TryFromDeviceLayoutError;
#[inline]
fn try_from(device_layout: DeviceLayout) -> Result<Self, Self::Error> {
DeviceLayout::into_layout(device_layout)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TryFromLayoutError;
impl Error for TryFromLayoutError {}
impl Display for TryFromLayoutError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.write_str("attempted to convert a zero-size `Layout` to a `DeviceLayout`")
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TryFromDeviceLayoutError;
impl Error for TryFromDeviceLayoutError {}
impl Display for TryFromDeviceLayoutError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.write_str(
"attempted to convert a `DeviceLayout` to a `Layout` which would result in a \
violation of `Layout`'s overflow-invariant",
)
}
}