Skip to content

Commit f7c1d7b

Browse files
committed
Add KnownLayout trait
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
1 parent 3773da2 commit f7c1d7b

File tree

3 files changed

+185
-9
lines changed

3 files changed

+185
-9
lines changed

src/lib.rs

+145-9
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ pub use crate::wrappers::*;
173173
pub use zerocopy_derive::*;
174174

175175
use core::{
176+
alloc::Layout,
176177
cell::{self, RefMut},
177178
cmp::Ordering,
178179
fmt::{self, Debug, Display, Formatter},
@@ -191,9 +192,8 @@ use core::{
191192
extern crate alloc;
192193
#[cfg(feature = "alloc")]
193194
use {
194-
alloc::boxed::Box,
195-
alloc::vec::Vec,
196-
core::{alloc::Layout, ptr::NonNull},
195+
alloc::{boxed::Box, vec::Vec},
196+
core::ptr::NonNull,
197197
};
198198

199199
// This is a hack to allow zerocopy-derive derives to work in this crate. They
@@ -204,6 +204,105 @@ mod zerocopy {
204204
pub(crate) use crate::*;
205205
}
206206

207+
/// The layout of a type which might be dynamically-sized.
208+
///
209+
/// `DstLayout` describes the layout of sized types, slice types, and "custom
210+
/// DSTs" - ie, those that are known by the type system to have a trailing slice
211+
/// (as distinguished from `dyn Trait` types - such types *might* have a
212+
/// trailing slice type, but the type system isn't aware of it).
213+
#[doc(hidden)]
214+
#[allow(missing_debug_implementations, missing_copy_implementations)]
215+
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
216+
pub struct DstLayout {
217+
/// The base size and the alignment of the type:
218+
/// - For sized types, the size encoded by this `Layout` is
219+
/// `size_of::<T>()`. For DSTs, the size represents the size of the type
220+
/// when the trailing slice field contains 0 elements.
221+
/// - For all types, the alignment represents the alignment of the type.
222+
_base_layout: Layout,
223+
/// For sized types, `None`. For DSTs, the size of the element type of the
224+
/// trailing slice.
225+
_trailing_slice_elem_size: Option<usize>,
226+
}
227+
228+
impl DstLayout {
229+
/// Constructs a `DstLayout` which describes `T`.
230+
///
231+
/// # Safety
232+
///
233+
/// Unsafe code may assume that `DstLayout` is the correct layout for `T`.
234+
const fn for_type<T>() -> DstLayout {
235+
DstLayout { _base_layout: Layout::new::<T>(), _trailing_slice_elem_size: None }
236+
}
237+
238+
/// Constructs a `DstLayout` which describes `[T]`.
239+
///
240+
/// # Safety
241+
///
242+
/// Unsafe code may assume that `DstLayout` is the correct layout for `[T]`.
243+
const fn for_slice<T>() -> DstLayout {
244+
DstLayout {
245+
// SAFETY: `[T; 0]` has the same alignment as `T`, but zero size.
246+
// [1] A slice of length 0 has no size, so 0 is the correct size for
247+
// the base of the type.
248+
//
249+
// [1] https://doc.rust-lang.org/reference/type-layout.html#array-layout
250+
_base_layout: Layout::new::<[T; 0]>(),
251+
_trailing_slice_elem_size: Some(mem::size_of::<T>()),
252+
}
253+
}
254+
}
255+
256+
/// A trait which carries information about a type's layout that is used by the
257+
/// internals of this crate.
258+
///
259+
/// This trait is not meant for consumption by code outside of this crate. While
260+
/// the normal semver stability guarantees apply with respect to which types
261+
/// implement this trait and which trait implementations are implied by this
262+
/// trait, no semver stability guarantees are made regarding its internals; they
263+
/// may change at any time, and code which makes use of them may break.
264+
///
265+
/// # Safety
266+
///
267+
/// This trait does not convey any safety guarantees to code outside this crate.
268+
#[doc(hidden)] // TODO: Remove this once KnownLayout is used by other APIs
269+
pub unsafe trait KnownLayout: sealed::KnownLayoutSealed {
270+
#[doc(hidden)]
271+
const LAYOUT: DstLayout;
272+
}
273+
274+
impl<T: KnownLayout> sealed::KnownLayoutSealed for [T] {}
275+
// SAFETY: Delegates safety to `DstLayout::for_slice`.
276+
unsafe impl<T: KnownLayout> KnownLayout for [T] {
277+
const LAYOUT: DstLayout = DstLayout::for_slice::<T>();
278+
}
279+
280+
#[rustfmt::skip]
281+
impl_known_layout!(
282+
(),
283+
u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize, f32, f64,
284+
bool, char,
285+
NonZeroU8, NonZeroI8, NonZeroU16, NonZeroI16, NonZeroU32, NonZeroI32,
286+
NonZeroU64, NonZeroI64, NonZeroU128, NonZeroI128, NonZeroUsize, NonZeroIsize
287+
);
288+
#[rustfmt::skip]
289+
impl_known_layout!(
290+
T => Option<T>,
291+
T: ?Sized => PhantomData<T>,
292+
T => Wrapping<T>,
293+
T => MaybeUninit<T>,
294+
);
295+
impl_known_layout!(const N: usize, T => [T; N]);
296+
297+
safety_comment! {
298+
/// SAFETY:
299+
/// `str` and `ManuallyDrop<[T]>` have the same representations as `[u8]`
300+
/// and `[T]` repsectively. `str` has different bit validity than `[u8]`,
301+
/// but that doesn't affect the soundness of this impl.
302+
unsafe_impl_known_layout!(#[repr([u8])] str);
303+
unsafe_impl_known_layout!(T: ?Sized + KnownLayout => #[repr(T)] ManuallyDrop<T>);
304+
}
305+
207306
/// Types for which a sequence of bytes all set to zero represents a valid
208307
/// instance of the type.
209308
///
@@ -1171,6 +1270,7 @@ mod simd {
11711270
use core::arch::$arch::{$($typ),*};
11721271

11731272
use crate::*;
1273+
impl_known_layout!($($typ),*);
11741274
safety_comment! {
11751275
/// SAFETY:
11761276
/// See comment on module definition for justification.
@@ -2279,7 +2379,8 @@ where
22792379
}
22802380

22812381
mod sealed {
2282-
pub trait Sealed {}
2382+
pub trait ByteSliceSealed {}
2383+
pub trait KnownLayoutSealed {}
22832384
}
22842385

22852386
// ByteSlice and ByteSliceMut abstract over [u8] references (&[u8], &mut [u8],
@@ -2305,7 +2406,9 @@ mod sealed {
23052406
///
23062407
/// [`Vec<u8>`]: alloc::vec::Vec
23072408
/// [`split_at`]: crate::ByteSlice::split_at
2308-
pub unsafe trait ByteSlice: Deref<Target = [u8]> + Sized + self::sealed::Sealed {
2409+
pub unsafe trait ByteSlice:
2410+
Deref<Target = [u8]> + Sized + self::sealed::ByteSliceSealed
2411+
{
23092412
/// Gets a raw pointer to the first byte in the slice.
23102413
#[inline]
23112414
fn as_ptr(&self) -> *const u8 {
@@ -2336,7 +2439,7 @@ pub unsafe trait ByteSliceMut: ByteSlice + DerefMut {
23362439
}
23372440
}
23382441

2339-
impl<'a> sealed::Sealed for &'a [u8] {}
2442+
impl<'a> sealed::ByteSliceSealed for &'a [u8] {}
23402443
// TODO(#61): Add a "SAFETY" comment and remove this `allow`.
23412444
#[allow(clippy::undocumented_unsafe_blocks)]
23422445
unsafe impl<'a> ByteSlice for &'a [u8] {
@@ -2346,7 +2449,7 @@ unsafe impl<'a> ByteSlice for &'a [u8] {
23462449
}
23472450
}
23482451

2349-
impl<'a> sealed::Sealed for &'a mut [u8] {}
2452+
impl<'a> sealed::ByteSliceSealed for &'a mut [u8] {}
23502453
// TODO(#61): Add a "SAFETY" comment and remove this `allow`.
23512454
#[allow(clippy::undocumented_unsafe_blocks)]
23522455
unsafe impl<'a> ByteSlice for &'a mut [u8] {
@@ -2356,7 +2459,7 @@ unsafe impl<'a> ByteSlice for &'a mut [u8] {
23562459
}
23572460
}
23582461

2359-
impl<'a> sealed::Sealed for cell::Ref<'a, [u8]> {}
2462+
impl<'a> sealed::ByteSliceSealed for cell::Ref<'a, [u8]> {}
23602463
// TODO(#61): Add a "SAFETY" comment and remove this `allow`.
23612464
#[allow(clippy::undocumented_unsafe_blocks)]
23622465
unsafe impl<'a> ByteSlice for cell::Ref<'a, [u8]> {
@@ -2366,7 +2469,7 @@ unsafe impl<'a> ByteSlice for cell::Ref<'a, [u8]> {
23662469
}
23672470
}
23682471

2369-
impl<'a> sealed::Sealed for RefMut<'a, [u8]> {}
2472+
impl<'a> sealed::ByteSliceSealed for RefMut<'a, [u8]> {}
23702473
// TODO(#61): Add a "SAFETY" comment and remove this `allow`.
23712474
#[allow(clippy::undocumented_unsafe_blocks)]
23722475
unsafe impl<'a> ByteSlice for RefMut<'a, [u8]> {
@@ -2635,6 +2738,39 @@ mod tests {
26352738
}
26362739
}
26372740

2741+
#[test]
2742+
fn test_known_layout() {
2743+
// Test that `$ty` and `ManuallyDrop<$ty>` have the expected layout.
2744+
// Test that `PhantomData<$ty>` has the same layout as `()` regardless
2745+
// of `$ty`.
2746+
macro_rules! test {
2747+
($ty:ty, $expect:expr) => {
2748+
let expect = $expect;
2749+
assert_eq!(<$ty as KnownLayout>::LAYOUT, expect);
2750+
assert_eq!(<ManuallyDrop<$ty> as KnownLayout>::LAYOUT, expect);
2751+
assert_eq!(<PhantomData<$ty> as KnownLayout>::LAYOUT, <() as KnownLayout>::LAYOUT);
2752+
};
2753+
}
2754+
2755+
let layout = |base_size, align, _trailing_slice_elem_size| DstLayout {
2756+
_base_layout: Layout::from_size_align(base_size, align).unwrap(),
2757+
_trailing_slice_elem_size,
2758+
};
2759+
2760+
test!((), layout(0, 1, None));
2761+
test!(u8, layout(1, 1, None));
2762+
// Use `align_of` because `u64` alignment may be smaller than 8 on some
2763+
// platforms.
2764+
test!(u64, layout(8, mem::align_of::<u64>(), None));
2765+
test!(AU64, layout(8, 8, None));
2766+
2767+
test!(Option<&'static ()>, usize::LAYOUT);
2768+
2769+
test!([()], layout(0, 1, Some(0)));
2770+
test!([u8], layout(0, 1, Some(1)));
2771+
test!(str, layout(0, 1, Some(1)));
2772+
}
2773+
26382774
#[test]
26392775
fn test_object_safety() {
26402776
fn _takes_from_zeroes(_: &dyn FromZeroes) {}

src/macros.rs

+38
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,44 @@ macro_rules! impl_or_verify {
193193
};
194194
}
195195

196+
/// Implements `KnownLayout` for a sized type.
197+
macro_rules! impl_known_layout {
198+
($(const $constvar:ident : $constty:ty, $tyvar:ident $(: ?$optbound:ident)? => $ty:ty),* $(,)?) => {
199+
$(impl_known_layout!(@inner const $constvar: $constty, $tyvar $(: ?$optbound)? => $ty);)*
200+
};
201+
($($tyvar:ident $(: ?$optbound:ident)? => $ty:ty),* $(,)?) => {
202+
$(impl_known_layout!(@inner , $tyvar $(: ?$optbound)? => $ty);)*
203+
};
204+
($($ty:ty),*) => { $(impl_known_layout!(@inner , => $ty);)* };
205+
(@inner $(const $constvar:ident : $constty:ty)? , $($tyvar:ident $(: ?$optbound:ident)?)? => $ty:ty) => {
206+
impl<$(const $constvar : $constty,)? $($tyvar $(: ?$optbound)?)?> sealed::KnownLayoutSealed for $ty {}
207+
// SAFETY: Delegates safety to `DstLayout::for_type`.
208+
unsafe impl<$(const $constvar : $constty,)? $($tyvar $(: ?$optbound)?)?> KnownLayout for $ty {
209+
const LAYOUT: DstLayout = DstLayout::for_type::<$ty>();
210+
}
211+
};
212+
}
213+
214+
/// Implements `KnownLayout` for a type in terms of the implementation of
215+
/// another type with the same representation.
216+
///
217+
/// # Safety
218+
///
219+
/// - `$ty` and `$repr` must have the same:
220+
/// - Fixed prefix size
221+
/// - Alignment
222+
/// - (For DSTs) trailing slice element size
223+
/// - It must be valid to perform an `as` cast from `*mut $repr` to `*mut $ty`,
224+
/// and this operation must preserve referent size (ie, `size_of_val_raw`).
225+
macro_rules! unsafe_impl_known_layout {
226+
($($tyvar:ident: ?Sized + KnownLayout =>)? #[repr($repr:ty)] $ty:ty) => {
227+
impl<$($tyvar: ?Sized + KnownLayout)?> sealed::KnownLayoutSealed for $ty {}
228+
unsafe impl<$($tyvar: ?Sized + KnownLayout)?> KnownLayout for $ty {
229+
const LAYOUT: DstLayout = <$repr as KnownLayout>::LAYOUT;
230+
}
231+
};
232+
}
233+
196234
/// Uses `align_of` to confirm that a type or set of types have alignment 1.
197235
///
198236
/// Note that `align_of<T>` requires `T: Sized`, so this macro doesn't work for

src/util.rs

+2
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,6 @@ pub(crate) mod testutil {
100100
Display::fmt(&self.0, f)
101101
}
102102
}
103+
104+
impl_known_layout!(AU64);
103105
}

0 commit comments

Comments
 (0)