Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wip] UnalignUnsized #1828

Draft
wants to merge 2 commits into
base: maybe-uninit
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ fn main() {
println!(
"cargo:rustc-check-cfg=cfg(__ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS)"
);
println!("cargo:rustc-check-cfg=cfg(zerocopy_unstable)");
println!("cargo:rustc-check-cfg=cfg(coverage_nightly)");
}

Expand Down
30 changes: 16 additions & 14 deletions src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
// This file may not be copied, modified, or distributed except according to
// those terms.

use core::mem::MaybeUninit as CoreMaybeUninit;

use super::*;

safety_comment! {
Expand Down Expand Up @@ -630,14 +632,14 @@ safety_comment! {
/// SAFETY:
/// `TryFromBytes` (with no validator), `FromZeros`, `FromBytes`:
/// `MaybeUninit<T>` has no restrictions on its contents.
unsafe_impl!(T => TryFromBytes for MaybeUninit<T>);
unsafe_impl!(T => FromZeros for MaybeUninit<T>);
unsafe_impl!(T => FromBytes for MaybeUninit<T>);
unsafe_impl!(T => TryFromBytes for CoreMaybeUninit<T>);
unsafe_impl!(T => FromZeros for CoreMaybeUninit<T>);
unsafe_impl!(T => FromBytes for CoreMaybeUninit<T>);
}

impl_for_transparent_wrapper!(T: Immutable => Immutable for MaybeUninit<T>);
impl_for_transparent_wrapper!(T: Unaligned => Unaligned for MaybeUninit<T>);
assert_unaligned!(MaybeUninit<()>, MaybeUninit<u8>);
impl_for_transparent_wrapper!(T: Immutable => Immutable for CoreMaybeUninit<T>);
impl_for_transparent_wrapper!(T: Unaligned => Unaligned for CoreMaybeUninit<T>);
assert_unaligned!(CoreMaybeUninit<()>, CoreMaybeUninit<u8>);

impl_for_transparent_wrapper!(T: ?Sized + Immutable => Immutable for ManuallyDrop<T>);
impl_for_transparent_wrapper!(T: ?Sized + TryFromBytes => TryFromBytes for ManuallyDrop<T>);
Expand Down Expand Up @@ -1254,8 +1256,8 @@ mod tests {
ManuallyDrop<UnsafeCell<()>>,
ManuallyDrop<[UnsafeCell<u8>]>,
ManuallyDrop<[UnsafeCell<bool>]>,
MaybeUninit<NotZerocopy>,
MaybeUninit<UnsafeCell<()>>,
CoreMaybeUninit<NotZerocopy>,
CoreMaybeUninit<UnsafeCell<()>>,
Wrapping<UnsafeCell<()>>
);

Expand Down Expand Up @@ -1297,9 +1299,9 @@ mod tests {
Option<FnManyArgs>,
Option<extern "C" fn()>,
Option<ECFnManyArgs>,
MaybeUninit<u8>,
MaybeUninit<NotZerocopy>,
MaybeUninit<UnsafeCell<()>>,
CoreMaybeUninit<u8>,
CoreMaybeUninit<NotZerocopy>,
CoreMaybeUninit<UnsafeCell<()>>,
ManuallyDrop<UnsafeCell<()>>,
ManuallyDrop<[UnsafeCell<u8>]>,
ManuallyDrop<[UnsafeCell<bool>]>,
Expand Down Expand Up @@ -1761,9 +1763,9 @@ mod tests {
assert_impls!(ManuallyDrop<[UnsafeCell<u8>]>: KnownLayout, TryFromBytes, FromZeros, FromBytes, IntoBytes, Unaligned, !Immutable);
assert_impls!(ManuallyDrop<[UnsafeCell<bool>]>: KnownLayout, TryFromBytes, FromZeros, IntoBytes, Unaligned, !Immutable, !FromBytes);

assert_impls!(MaybeUninit<u8>: KnownLayout, Immutable, TryFromBytes, FromZeros, FromBytes, Unaligned, !IntoBytes);
assert_impls!(MaybeUninit<NotZerocopy>: KnownLayout, TryFromBytes, FromZeros, FromBytes, !Immutable, !IntoBytes, !Unaligned);
assert_impls!(MaybeUninit<UnsafeCell<()>>: KnownLayout, TryFromBytes, FromZeros, FromBytes, Unaligned, !Immutable, !IntoBytes);
assert_impls!(CoreMaybeUninit<u8>: KnownLayout, Immutable, TryFromBytes, FromZeros, FromBytes, Unaligned, !IntoBytes);
assert_impls!(CoreMaybeUninit<NotZerocopy>: KnownLayout, TryFromBytes, FromZeros, FromBytes, !Immutable, !IntoBytes, !Unaligned);
assert_impls!(CoreMaybeUninit<UnsafeCell<()>>: KnownLayout, TryFromBytes, FromZeros, FromBytes, Unaligned, !Immutable, !IntoBytes);

assert_impls!(Wrapping<u8>: KnownLayout, Immutable, TryFromBytes, FromZeros, FromBytes, IntoBytes, Unaligned);
// This test is important because it allows us to test our hand-rolled
Expand Down
113 changes: 107 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ use core::{
fmt::{self, Debug, Display, Formatter},
hash::Hasher,
marker::PhantomData,
mem::{self, ManuallyDrop, MaybeUninit},
mem::{self, ManuallyDrop, MaybeUninit as CoreMaybeUninit},
num::{
NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128,
NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, Wrapping,
Expand Down Expand Up @@ -724,6 +724,22 @@ pub unsafe trait KnownLayout {
/// This is `()` for sized types and `usize` for slice DSTs.
type PointerMetadata: PointerMetadata;

/// A maybe-uninitialized analog of `Self`
///
/// # Safety
///
/// Users may assume that:
/// - `Self` and `Self::MaybeUninit` have the same:
/// - Fixed prefix size
/// - Alignment
/// - (For DSTs) trailing slice element size
/// - It is valid to perform an `as` cast from `*mut Self` and `*mut
/// Self::MaybeUninit`, and this operation preserves referent size (ie,
/// `size_of_val_raw`).
#[cfg(zerocopy_unstable)]
#[doc(hidden)]
type MaybeUninit: ?Sized + KnownLayout<PointerMetadata = Self::PointerMetadata>;

/// The layout of `Self`.
///
/// # Safety
Expand Down Expand Up @@ -780,6 +796,16 @@ pub unsafe trait KnownLayout {
// resulting size would not fit in a `usize`.
meta.size_for_metadata(Self::LAYOUT)
}

/// Run `slf`'s destructor (if any).
///
/// # Safety
///
/// `slf`'s destructor must not already have been run. After invoking this
/// function, re-using `slf` may be undefined behavior.
#[cfg(zerocopy_unstable)]
#[doc(hidden)]
unsafe fn drop(slf: &mut UnalignUnsized<Self>);
}

/// The metadata associated with a [`KnownLayout`] type.
Expand Down Expand Up @@ -856,6 +882,17 @@ unsafe impl<T> KnownLayout for [T] {

type PointerMetadata = usize;

// SAFETY: `CoreMaybeUninit<T>` has the same size and alignment as `T`.
// Consequently, `[CoreMaybeUninit<T>]` has the same size and alignment as
// `[T]`, becuase `CoreMaybeUninit<T>` has the same size and alignment as
// `T` [1].
//
// [1] Per https://doc.rust-lang.org/1.81.0/std/mem/union.MaybeUninit.html#layout-1:
//
// `MaybeUninit<T>` is guaranteed to have the same size, alignment, and ABI as
// `T`
type MaybeUninit = [CoreMaybeUninit<T>];

const LAYOUT: DstLayout = DstLayout::for_slice::<T>();

// SAFETY: `.cast` preserves address and provenance. The returned pointer
Expand Down Expand Up @@ -893,6 +930,57 @@ unsafe impl<T> KnownLayout for [T] {
// struct `Foo(i32, [u8])` or `(u64, Foo)`.
slc.len()
}

#[cfg(feature = "alloc")]
unsafe fn drop(slf: &mut UnalignUnsized<Self>) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this play nicely with Pin'd types?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, Pin bars you from moving the referent before running its destructor. It doesn't place any restrictions on what you do in the destructor.

let meta = KnownLayout::pointer_to_metadata(slf);
let size = meta.size_for_metadata(Self::LAYOUT).unwrap();
let aligned = MaybeUninit::<Self>::new_boxed_uninit(meta).unwrap();
let aligned = Box::into_raw(aligned);
// SAFETY: This invocation satisfies the safety contract of
// copy_nonoverlapping [1]:
// - `slf as *mut _ as *mut u8` is valid for reads of `size` bytes
// - `aligned as *mut u8` is valid for writes of `size` bytes, because
// `aligned`'s referent is greater-than-or-equal in size to that of
// `slf`, because `aligned` might include trailing padding.
// - `src` and `dst` are, trivially, properly aligned
// - the region of memory beginning at `src` with a size of `size` bytes
// does not overlap with the region of memory beginning at `aligned`
// with the same size, because `aligned` is derived from a fresh
// allocation.
//
// [1] https://doc.rust-lang.org/1.81.0/core/ptr/fn.copy_nonoverlapping.html#safety
unsafe {
core::ptr::copy_nonoverlapping(slf as *mut _ as *mut u8, aligned as *mut u8, size);
}
// LEMMA 1: `aligned`'s referent is a bit-valid and aligned instance of
// `Self`. In its declaration, `aligned` was initialized from a
// `Box<MaybeUninit<Self>>`, which has the same alignment as `Self`.
// Then, via the preceeding `copy_nonoverlapping`, `aligned` was
// initialized with a valid instance of `Self.`
let aligned = aligned as *mut Self;
// SAFETY: This invocation satisfies the safety contract of
// `Box::from_raw` [1], because `aligned` is directly derived from
// `Box::into_raw`. By LEMMA 1, `aligned`'s referent is additionally a
// valid instance of `Self`. The `Layout`s of `Self` and
// `MaybeUninit<Self>` are the same, by contract on `MaybeUninit<Self>`.
//
// [1] Per https://doc.rust-lang.org/1.81.0/alloc/boxed/struct.Box.html#method.from_raw:
//
// It is valid to convert both ways between a `Box`` and a raw pointer
// allocated with the `Global`` allocator, given that the `Layout`
// used with the allocator is correct for the type.
unsafe {
// drop `aligned`
let _ = Box::from_raw(aligned);
}
}

#[cfg(not(feature = "alloc"))]
unsafe fn drop(_: &mut UnalignUnsized<Self>) {
// PME if T needs drop.
static_assert!(T=> !core::mem::needs_drop::<T>());
}
}

#[rustfmt::skip]
Expand All @@ -908,7 +996,7 @@ impl_known_layout!(
T => Option<T>,
T: ?Sized => PhantomData<T>,
T => Wrapping<T>,
T => MaybeUninit<T>,
T => CoreMaybeUninit<T>,
T: ?Sized => *const T,
T: ?Sized => *mut T
);
Expand Down Expand Up @@ -941,6 +1029,19 @@ safety_comment! {
unsafe_impl_known_layout!(T: ?Sized + KnownLayout => #[repr(T)] UnsafeCell<T>);
}

safety_comment! {
/// SAFETY:
/// - By invariant on `KnownLayout::MaybeUninit`, `T` and `T::MaybeUninit`
/// have the same:
/// - Fixed prefix size
/// - Alignment
/// - (For DSTs) trailing slice element size
/// - By invariant on `KnownLayout`, it is valid to perform an `as` cast
/// from `*mut T` and `*mut T::MaybeUninit`, and this operation preserves
/// referent size (ie, `size_of_val_raw`).
unsafe_impl_known_layout!(T: ?Sized + KnownLayout => #[repr(T::MaybeUninit)] MaybeUninit<T>);
}

/// Analyzes whether a type is [`FromZeros`].
///
/// This derive analyzes, at compile time, whether the annotated type satisfies
Expand Down Expand Up @@ -2539,7 +2640,7 @@ pub unsafe trait TryFromBytes {
where
Self: Sized,
{
let candidate = match MaybeUninit::<Self>::read_from_bytes(source) {
let candidate = match CoreMaybeUninit::<Self>::read_from_bytes(source) {
Ok(candidate) => candidate,
Err(e) => {
return Err(TryReadError::Size(e.with_dst()));
Expand Down Expand Up @@ -2600,7 +2701,7 @@ pub unsafe trait TryFromBytes {
where
Self: Sized,
{
let (candidate, suffix) = match MaybeUninit::<Self>::read_from_prefix(source) {
let (candidate, suffix) = match CoreMaybeUninit::<Self>::read_from_prefix(source) {
Ok(candidate) => candidate,
Err(e) => {
return Err(TryReadError::Size(e.with_dst()));
Expand Down Expand Up @@ -2662,7 +2763,7 @@ pub unsafe trait TryFromBytes {
where
Self: Sized,
{
let (prefix, candidate) = match MaybeUninit::<Self>::read_from_suffix(source) {
let (prefix, candidate) = match CoreMaybeUninit::<Self>::read_from_suffix(source) {
Ok(candidate) => candidate,
Err(e) => {
return Err(TryReadError::Size(e.with_dst()));
Expand Down Expand Up @@ -2735,7 +2836,7 @@ fn swap<T, U>((t, u): (T, U)) -> (U, T) {
#[inline(always)]
unsafe fn try_read_from<S, T: TryFromBytes>(
source: S,
mut candidate: MaybeUninit<T>,
mut candidate: CoreMaybeUninit<T>,
) -> Result<T, TryReadError<S, T>> {
// We use `from_mut` despite not mutating via `c_ptr` so that we don't need
// to add a `T: Immutable` bound.
Expand Down
16 changes: 15 additions & 1 deletion src/util/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,8 @@ macro_rules! impl_known_layout {

type PointerMetadata = ();

type MaybeUninit = core::mem::MaybeUninit<Self>;

const LAYOUT: crate::DstLayout = crate::DstLayout::for_type::<$ty>();

// SAFETY: `.cast` preserves address and provenance.
Expand All @@ -544,6 +546,10 @@ macro_rules! impl_known_layout {
#[inline(always)]
fn pointer_to_metadata(_ptr: *mut Self) -> () {
}

unsafe fn drop(slf: &mut crate::UnalignUnsized<Self>) {
let _ = unsafe { slf.take() };
}
}
};
};
Expand All @@ -561,7 +567,7 @@ macro_rules! impl_known_layout {
/// - 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) => {
($($tyvar:ident: ?Sized + KnownLayout =>)? #[repr($(packed,)? $repr:ty)] $ty:ty) => {
const _: () = {
use core::ptr::NonNull;

Expand All @@ -572,6 +578,7 @@ macro_rules! unsafe_impl_known_layout {
fn only_derive_is_allowed_to_implement_this_trait() {}

type PointerMetadata = <$repr as KnownLayout>::PointerMetadata;
type MaybeUninit = <$repr as KnownLayout>::MaybeUninit;

const LAYOUT: DstLayout = <$repr as KnownLayout>::LAYOUT;

Expand All @@ -594,6 +601,13 @@ macro_rules! unsafe_impl_known_layout {
let ptr = ptr as *mut $repr;
<$repr>::pointer_to_metadata(ptr)
}

unsafe fn drop(slf: &mut UnalignUnsized<Self>) {
// SAFETY: TODO
let slf = unsafe { &mut *(slf as *mut _ as *mut UnalignUnsized<$repr>) };
// SAFETY: TODO
unsafe { KnownLayout::drop(slf) }
}
}
};
};
Expand Down
2 changes: 2 additions & 0 deletions src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,8 @@ pub(crate) unsafe fn copy_unchecked(src: &[u8], dst: &mut [u8]) {
// bytes does not overlap with the region of memory beginning at `dst`
// with the same size, because `dst` is derived from an exclusive
// reference.
//
// [1] https://doc.rust-lang.org/1.81.0/core/ptr/fn.copy_nonoverlapping.html#safety
unsafe {
core::ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr(), src.len());
};
Expand Down
Loading
Loading