Skip to content

Commit

Permalink
[wip] UnalignUnsized
Browse files Browse the repository at this point in the history
  • Loading branch information
jswrenn committed Nov 14, 2024
1 parent 7da393c commit e8cc8eb
Show file tree
Hide file tree
Showing 7 changed files with 358 additions and 3 deletions.
7 changes: 6 additions & 1 deletion src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ pub(crate) enum MetadataCastError {

impl DstLayout {
/// The minimum possible alignment of a type.
const MIN_ALIGN: NonZeroUsize = match NonZeroUsize::new(1) {
pub(crate) const MIN_ALIGN: NonZeroUsize = match NonZeroUsize::new(1) {
Some(min_align) => min_align,
None => const_unreachable!(),
};
Expand Down Expand Up @@ -598,6 +598,11 @@ impl DstLayout {

Ok((elems, split_at))
}

/// Produces `true` if `self.align` equals 1; otherwise `false`.
pub(crate) const fn is_trivially_aligned(&self) -> bool {
matches!(self.align, DstLayout::MIN_ALIGN)
}
}

// TODO(#67): For some reason, on our MSRV toolchain, this `allow` isn't
Expand Down
127 changes: 127 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,25 @@ pub unsafe trait KnownLayout {
// resulting size would not fit in a `usize`.
meta.size_for_metadata(Self::LAYOUT)
}

/// Run the destructor of `ptr`'s referent.
///
/// # Panics
///
/// Implementations of this function never panic.
///
/// # Compile-Time Assertions
///
/// Implementations of this function must emit a post-monomorphization error
/// if `ptr`'s referent has a non-trivial drop that cannot be run.
///
/// # Safety
///
/// This function may only be called from the destructor (i.e.,
/// `Drop::drop`) of transitive owner of `ptr`'s referent. After invoking
/// this function, it is forbidden to re-use `ptr` or its referent.
#[doc(hidden)]
unsafe fn destroy(ptr: MaybeAligned<'_, Self, invariant::Exclusive>);
}

/// The metadata associated with a [`KnownLayout`] type.
Expand Down Expand Up @@ -939,6 +958,114 @@ unsafe impl<T> KnownLayout for [T] {
// struct `Foo(i32, [u8])` or `(u64, Foo)`.
slc.len()
}

#[cfg(feature = "alloc")]
#[inline]
unsafe fn destroy(ptr: MaybeAligned<'_, Self, invariant::Exclusive>) {
match ptr.try_recall_trivially_aligned() {
// If `Self` is trivially aligned, it can simply be dropped in
// place.
Ok(ptr) => {
// SAFETY: By contract on the caller, this function is only
// invoked from the destructor of an transitive owner `ptr`'s
// referent, and `ptr`'s referent is never subsequently
// re-accessed.
unsafe {
ptr.drop_in_place();
}
}
// Otherwise, can destroy an arbitrarily-aligned [`[T]`] by:
// 1. allocating a well-aligned `aligned: Box<MaybeUninit<[T]>>`
// 2. copying `ptr`'s referent to `aligned`
// 3. casting `aligned` to `Box<[T]>`
// 4. dropping `aligned`
Err(ptr) => {
// First, we allocate `aligned`.
let ptr = ptr.as_non_null().as_ptr();
let meta = KnownLayout::pointer_to_metadata(ptr);
let aligned = MaybeUninit::<Self>::new_boxed_uninit(meta).expect("unreachable");
let aligned = Box::into_raw(aligned);

// Next, we copy `ptr`'s referent to `aligned`.
let size = meta.size_for_metadata(Self::LAYOUT).expect("unreachable");
// SAFETY: This invocation satisfies the safety contract of
// copy_nonoverlapping [1]:
// - `ptr as *mut u8` is valid for reads of `size` bytes,
// because it is derived from a `Ptr` whose referent is
// exclusively-aliased. This is sufficent, since
// `copy_nonoverlapping` does not require its source referent
// to be valid or even initialized [1].
// - `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 {
#[allow(clippy::as_conversions)]
core::ptr::copy_nonoverlapping(ptr as *mut u8, aligned as *mut u8, size);
}

// Finally, we reconstitute `aligned` as a `Box<Self>` and
// immediately drop it.
//
// LEMMA 1: `aligned`'s referent is a bit-valid and aligned
// instance of `Self`. It is well-aligned, because it was
// initialized from a `Box<MaybeUninit<Self>>`, whose referent
// has the same alignment as `Self`. It is valid, because
// because the preceeding `copy_nonoverlapping`, initialized its
// referent with a valid instance of `Self.`
#[allow(clippy::as_conversions)]
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 layouts of
// `Self` and `MaybeUninit<Self>` are the same, by invariant 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.
let _ = unsafe { Box::from_raw(aligned) };
}
}
}

#[cfg(not(feature = "alloc"))]
#[inline]
unsafe fn destroy(ptr: MaybeAligned<'_, Self, invariant::Exclusive>) {
// In environments without allocators, we cannot run `[T]`'s non-trivial
// destructor if `T` is non-trivially aligned, since it is presently
// impossible to statically allocate a well-aligned (and, thus,
// droppable) buffer of dynamic size.
//
// Rather than panic or forgetting `[T]` (which might be unexpected) in
// such cases, we emit a post-monomorphization error; the user can
// explicitly choose to forget their type by wrapping it in
// `ManuallyDrop`.
static_assert!(
Self: ?Sized + KnownLayout =>
!core::mem::needs_drop::<Self>() || Self::LAYOUT.is_trivially_aligned()
);
// Nonetheless, we can run the destructor of well-aligned `[T]`.
if let Ok(ptr) = ptr.try_recall_trivially_aligned() {
// SAFETY: By contract on the caller, this function is only invoked
// from the destructor of an transitive owner `ptr`'s referent, and
// `ptr`'s referent is never subsequently re-accessed.
unsafe {
ptr.drop_in_place();
}
}
}
}

#[rustfmt::skip]
Expand Down
22 changes: 22 additions & 0 deletions src/pointer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,28 @@ where
unsafe { core::ptr::read_unaligned(raw) }
}

/// Reads the value from `MaybeAligned`.
///
/// # Safety
///
/// If `T` has a non-trivial destructor, using the returned `T` (including
/// dropping it) and the original referent may cause undefined behavior. The
/// caller ensures this does not occur.
#[must_use]
#[inline]
pub(crate) unsafe fn read_unaligned_unchecked<R>(self) -> T
where
R: AliasingSafeReason,
T: AliasingSafe<T, Aliasing, R> + Sized,
{
let raw = self.as_non_null().as_ptr();
// SAFETY: By invariant on `MaybeAligned`, `raw` contains
// validly-initialized data for `T`. By `T: AliasingSafe`, we are
// permitted to perform a read of `raw`'s referent. The caller ensures
// that subsequent uses of `T` do not induce UB.
unsafe { core::ptr::read_unaligned(raw) }
}

/// Views the value as an aligned reference.
///
/// This is only available if `T` is [`Unaligned`].
Expand Down
53 changes: 53 additions & 0 deletions src/pointer/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,25 @@ mod _transitions {
unsafe { self.assume_alignment::<Aligned>() }
}

/// Attempt to recall that `self`'s referent is trivially aligned.
#[inline]
// TODO(#859): Reconsider the name of this method before making it
// public.
pub(crate) fn try_recall_trivially_aligned(
self,
) -> Result<Ptr<'a, T, (I::Aliasing, Aligned, I::Validity)>, Self>
where
T: KnownLayout,
{
if T::LAYOUT.is_trivially_aligned() {
// SAFETY: The above check ensures that `T` has no non-trivial
// alignment requirement.
Ok(unsafe { self.assume_alignment::<Aligned>() })
} else {
Err(self)
}
}

/// Assumes that `self`'s referent conforms to the validity requirement
/// of `V`.
///
Expand Down Expand Up @@ -1641,6 +1660,40 @@ mod _project {
}
}

mod _misc {
use super::*;

impl<T, I> Ptr<'_, T, I>
where
T: ?Sized,
I: Invariants<Aliasing = Exclusive, Alignment = Aligned, Validity = Valid>,
{
/// Executes the referent's destructor.
///
/// # Safety
///
/// This function may only be invoked from the destructor of an
/// transitive owner `ptr`'s referent. After invoking this function, it
/// is forbidden to re-use `ptr`'s referent.
pub(crate) unsafe fn drop_in_place(self) {
let ptr = self.as_non_null().as_ptr();
// SAFETY: This invocation satisfies `drop_in_place`'s safety
// invariants [1]:
// - `ptr` is valid for both reads and writes, because it derived
// from a `Ptr` whose referent is exclusively aliased,
// well-aligned, and valid.
// - `ptr` is well-aligned; see above.
// - `ptr` is non-null; see above.
// - `ptr`'s referent is presumed to be a library-valid
// - `ptr` is exclusively aliased and thus is the sole pointer to
// its referent.
//
// [1] https://doc.rust-lang.org/1.82.0/std/ptr/fn.drop_in_place.html#safety
unsafe { core::ptr::drop_in_place(ptr) }
}
}
}

#[cfg(test)]
mod tests {
use core::mem::{self, MaybeUninit};
Expand Down
40 changes: 38 additions & 2 deletions src/util/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,32 @@ macro_rules! impl_known_layout {
#[inline(always)]
fn pointer_to_metadata(_ptr: *mut Self) -> () {
}

#[inline]
unsafe fn destroy(ptr: crate::MaybeAligned<'_, Self, crate::invariant::Exclusive>) {
match ptr.try_recall_trivially_aligned() {
// If `Self` is trivially aligned, it can simply be
// dropped in place.
Ok(ptr) => {
// SAFETY: By contract on the caller, this function
// is only invoked from the destructor of an
// transitive owner `ptr`'s referent, and `ptr`'s
// referent is never subsequently re-accessed.
unsafe { ptr.drop_in_place(); }
},
// If `Self` is not trivially-aligned, read it onto the
// stack (so it is well-aligned) and drop it.
Err(ptr) => {
// SAFETY: By contract on the caller, this function
// is only invoked from the destructor of an
// transitive owner `ptr`'s referent, and `ptr`'s
// referent is never subsequently re-accessed.
let _ = unsafe {
ptr.read_unaligned_unchecked::<crate::BecauseExclusive>()
};
}
}
}
}
};
};
Expand All @@ -599,7 +625,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 @@ -620,7 +646,7 @@ macro_rules! unsafe_impl_known_layout {
// TODO(#429): Add documentation to `NonNull::new_unchecked`
// that it preserves provenance.
#[inline(always)]
fn raw_from_ptr_len(bytes: NonNull<u8>, meta: <$repr as KnownLayout>::PointerMetadata) -> NonNull<Self> {
fn raw_from_ptr_len(bytes: NonNull<u8>, meta: Self::PointerMetadata) -> NonNull<Self> {
#[allow(clippy::as_conversions)]
let ptr = <$repr>::raw_from_ptr_len(bytes, meta).as_ptr() as *mut Self;
// SAFETY: `ptr` was converted from `bytes`, which is non-null.
Expand All @@ -633,6 +659,16 @@ macro_rules! unsafe_impl_known_layout {
let ptr = ptr as *mut $repr;
<$repr>::pointer_to_metadata(ptr)
}

#[inline]
unsafe fn destroy(_: crate::MaybeAligned<'_, Self, crate::invariant::Exclusive>)
{
// Do nothing, except static assert that `Self` is trivially
// drop. This is permissable for all `Self`s, and, in fact,
// ideal for all `unsafe_impl_known_layout` targets except
// `UnsafeCell` (which might have a non-trivial drop).
static_assert!(Self: ?Sized => !core::mem::needs_drop::<Self>());
}
}
};
};
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

0 comments on commit e8cc8eb

Please sign in to comment.