Skip to content

Commit

Permalink
Add KnownLayout trait
Browse files Browse the repository at this point in the history
In its initial form, the `KnownLayout` trait encodes type layout
information slightly more complex than can be gleaned from any arbitrary
`T: ?Sized`. This allows it to support not just sized and slice types,
but also "custom DSTs" (those with fixed-size fields followed by a
trailing slice type). This is the first step to supporting various
operations on arbitrary custom DSTs.

Makes progress on #29
  • Loading branch information
joshlf committed Sep 7, 2023
1 parent 3773da2 commit d406386
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 11 deletions.
143 changes: 132 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,7 @@ use core::{
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use {
alloc::boxed::Box,
alloc::vec::Vec,
core::{alloc::Layout, ptr::NonNull},
};
use {alloc::boxed::Box, alloc::vec::Vec, core::alloc::Layout};

// This is a hack to allow zerocopy-derive derives to work in this crate. They
// assume that zerocopy is linked as an extern crate, so they access items from
Expand All @@ -204,6 +200,127 @@ mod zerocopy {
pub(crate) use crate::*;
}

/// The layout of a type which might be dynamically-sized.
///
/// `DstLayout` describes the layout of sized types, slice types, and "custom
/// DSTs" - ie, those that are known by the type system to have a trailing slice
/// (as distinguished from `dyn Trait` types - such types *might* have a
/// trailing slice type, but the type system isn't aware of it).
#[doc(hidden)]
#[allow(missing_debug_implementations, missing_copy_implementations)]
pub struct DstLayout {
/// For sized types, `size_of::<T>()`. For DSTs, the size of the type when
/// the trailing slice field contains 0 elements.
_fixed_prefix_size: usize,
/// The alignment of the type.
_align: NonZeroUsize,
/// For sized types, `None`. For DSTs, the size of the element type of the
/// trailing slice.
_trailing_slice_elem_size: Option<usize>,
}

impl DstLayout {
/// Constructs a `DstLayout` which describes `T`.
///
/// # Safety
///
/// Unsafe code may assume that `DstLayout` is the correct layout for `T`.
const fn for_type<T>() -> DstLayout {
DstLayout {
_fixed_prefix_size: mem::size_of::<T>(),
_align: match NonZeroUsize::new(mem::align_of::<T>()) {
Some(align) => align,
None => panic!("core::mem::align_of should never return 0"),
},
_trailing_slice_elem_size: None,
}
}

/// Constructs a `DstLayout` which describes `[T]`.
///
/// # Safety
///
/// Unsafe code may assume that `DstLayout` is the correct layout for `[T]`.
const fn for_slice<T>() -> DstLayout {
DstLayout {
_fixed_prefix_size: 0,
_align: match NonZeroUsize::new(mem::align_of::<T>()) {
Some(align) => align,
None => panic!("core::mem::align_of should never return 0"),
},
_trailing_slice_elem_size: Some(mem::size_of::<T>()),
}
}
}

/// A trait which carries information about a type's layout that is used by the
/// internals of this crate.
///
/// This trait is not meant for consumption by code outsie of this crate. While
/// the normal semver stability guarantees apply with respect to which types
/// implement this trait and which trait implementations are implied by this
/// trait, no semver stability guarantees are made regarding its internals; they
/// may change at any time, and code which makes use of them may break.
///
/// # Safety
///
/// This trait does not convey any safety guarantees to code outside this crate.
#[doc(hidden)] // TODO: Remove this once KnownLayout is used by other APIs
pub unsafe trait KnownLayout: sealed::KnownLayoutSealed {
#[doc(hidden)]
const LAYOUT: DstLayout;
}

impl<T: KnownLayout> sealed::KnownLayoutSealed for [T] {}
// SAFETY: Delegates safety to `DstLayout::for_slice`.
unsafe impl<T: KnownLayout> KnownLayout for [T] {
const LAYOUT: DstLayout = DstLayout::for_slice::<T>();
}

/// Implements `KnownLayout` for a sized type.
macro_rules! impl_known_layout {
($(const $constvar:ident : $constty:ty, $tyvar:ident $(: ?$optbound:ident)? => $ty:ty),* $(,)?) => {
$(impl_known_layout!(@inner const $constvar: $constty, $tyvar $(: ?$optbound)? => $ty);)*
};
($($tyvar:ident $(: ?$optbound:ident)? => $ty:ty),* $(,)?) => {
$(impl_known_layout!(@inner , $tyvar $(: ?$optbound)? => $ty);)*
};
($($ty:ty),*) => { $(impl_known_layout!(@inner , => $ty);)* };
(@inner $(const $constvar:ident : $constty:ty)? , $($tyvar:ident $(: ?$optbound:ident)?)? => $ty:ty) => {
impl<$(const $constvar : $constty,)? $($tyvar $(: ?$optbound)?)?> sealed::KnownLayoutSealed for $ty {}
// SAFETY: Delegates safety to `DstLayout::for_type`.
unsafe impl<$(const $constvar : $constty,)? $($tyvar $(: ?$optbound)?)?> KnownLayout for $ty {
const LAYOUT: DstLayout = DstLayout::for_type::<$ty>();
}
};
}

#[rustfmt::skip]
impl_known_layout!(
(),
u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize, f32, f64,
bool, char,
NonZeroU8, NonZeroI8, NonZeroU16, NonZeroI16, NonZeroU32, NonZeroI32,
NonZeroU64, NonZeroI64, NonZeroU128, NonZeroI128, NonZeroUsize, NonZeroIsize
);
#[rustfmt::skip]
impl_known_layout!(
T => Option<T>,
T: ?Sized => PhantomData<T>,
T => Wrapping<T>,
T => MaybeUninit<T>,
);
impl_known_layout!(const N: usize, T => [T; N]);

safety_comment! {
/// SAFETY:
/// `str` and `ManuallyDrop<[T]>` have the same representations as `[u8]`
/// and `[T]` repsectively. `str` has different bit validity than `[u8]`,
/// but that doesn't affect the soundness of this impl.
unsafe_impl_known_layout!(#[repr([u8])] str);
unsafe_impl_known_layout!(T: ?Sized + KnownLayout => #[repr(T)] ManuallyDrop<T>);
}

/// Types for which a sequence of bytes all set to zero represents a valid
/// instance of the type.
///
Expand Down Expand Up @@ -1171,6 +1288,7 @@ mod simd {
use core::arch::$arch::{$($typ),*};

use crate::*;
impl_known_layout!($($typ),*);
safety_comment! {
/// SAFETY:
/// See comment on module definition for justification.
Expand Down Expand Up @@ -2279,7 +2397,8 @@ where
}

mod sealed {
pub trait Sealed {}
pub trait ByteSliceSealed {}
pub trait KnownLayoutSealed {}
}

// ByteSlice and ByteSliceMut abstract over [u8] references (&[u8], &mut [u8],
Expand All @@ -2305,7 +2424,9 @@ mod sealed {
///
/// [`Vec<u8>`]: alloc::vec::Vec
/// [`split_at`]: crate::ByteSlice::split_at
pub unsafe trait ByteSlice: Deref<Target = [u8]> + Sized + self::sealed::Sealed {
pub unsafe trait ByteSlice:
Deref<Target = [u8]> + Sized + self::sealed::ByteSliceSealed
{
/// Gets a raw pointer to the first byte in the slice.
#[inline]
fn as_ptr(&self) -> *const u8 {
Expand Down Expand Up @@ -2336,7 +2457,7 @@ pub unsafe trait ByteSliceMut: ByteSlice + DerefMut {
}
}

impl<'a> sealed::Sealed for &'a [u8] {}
impl<'a> sealed::ByteSliceSealed for &'a [u8] {}
// TODO(#61): Add a "SAFETY" comment and remove this `allow`.
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe impl<'a> ByteSlice for &'a [u8] {
Expand All @@ -2346,7 +2467,7 @@ unsafe impl<'a> ByteSlice for &'a [u8] {
}
}

impl<'a> sealed::Sealed for &'a mut [u8] {}
impl<'a> sealed::ByteSliceSealed for &'a mut [u8] {}
// TODO(#61): Add a "SAFETY" comment and remove this `allow`.
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe impl<'a> ByteSlice for &'a mut [u8] {
Expand All @@ -2356,7 +2477,7 @@ unsafe impl<'a> ByteSlice for &'a mut [u8] {
}
}

impl<'a> sealed::Sealed for cell::Ref<'a, [u8]> {}
impl<'a> sealed::ByteSliceSealed for cell::Ref<'a, [u8]> {}
// TODO(#61): Add a "SAFETY" comment and remove this `allow`.
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe impl<'a> ByteSlice for cell::Ref<'a, [u8]> {
Expand All @@ -2366,7 +2487,7 @@ unsafe impl<'a> ByteSlice for cell::Ref<'a, [u8]> {
}
}

impl<'a> sealed::Sealed for RefMut<'a, [u8]> {}
impl<'a> sealed::ByteSliceSealed for RefMut<'a, [u8]> {}
// TODO(#61): Add a "SAFETY" comment and remove this `allow`.
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe impl<'a> ByteSlice for RefMut<'a, [u8]> {
Expand Down
20 changes: 20 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,26 @@ macro_rules! impl_or_verify {
};
}

/// Implements `KnownLayout` for a type in terms of the implementation of
/// another type with the same representation.
///
/// # Safety
///
/// - `$ty` and `$repr` must have the same:
/// - Fixed prefix size
/// - Alignment
/// - (For DSTs) trailing slice element size
/// - It must be valid to perform an `as` cast from `*mut $repr` to `*mut $ty`,
/// and this operation must preserve referent size (ie, `size_of_val_raw`).
macro_rules! unsafe_impl_known_layout {
($($tyvar:ident: ?Sized + KnownLayout =>)? #[repr($repr:ty)] $ty:ty) => {
impl<$($tyvar: ?Sized + KnownLayout)?> sealed::KnownLayoutSealed for $ty {}
unsafe impl<$($tyvar: ?Sized + KnownLayout)?> KnownLayout for $ty {
const LAYOUT: DstLayout = <$repr as KnownLayout>::LAYOUT;
}
};
}

/// Uses `align_of` to confirm that a type or set of types have alignment 1.
///
/// Note that `align_of<T>` requires `T: Sized`, so this macro doesn't work for
Expand Down

0 comments on commit d406386

Please sign in to comment.