Skip to content

Commit

Permalink
[wip] UnalignUnsized
Browse files Browse the repository at this point in the history
  • Loading branch information
jswrenn committed Nov 23, 2024
1 parent ec93fdc commit 8d58fff
Show file tree
Hide file tree
Showing 11 changed files with 514 additions and 15 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ jobs:
# which a particular feature is supported.
"zerocopy-core-error-1-81-0",
"zerocopy-diagnostic-on-unimplemented-1-78-0",
"zerocopy-unsized-needs-drop-1-63-0",
"zerocopy-generic-bounds-in-const-fn-1-61-0",
"zerocopy-target-has-atomics-1-60-0",
"zerocopy-aarch64-simd-1-59-0",
Expand Down Expand Up @@ -93,6 +94,8 @@ jobs:
features: "--all-features"
- toolchain: "zerocopy-diagnostic-on-unimplemented-1-78-0"
features: "--all-features"
- toolchain: "zerocopy-unsized-needs-drop-1-63-0"
features: "--all-features"
- toolchain: "zerocopy-generic-bounds-in-const-fn-1-61-0"
features: "--all-features"
- toolchain: "zerocopy-target-has-atomics-1-60-0"
Expand All @@ -117,6 +120,8 @@ jobs:
toolchain: "zerocopy-core-error-1-81-0"
- crate: "zerocopy-derive"
toolchain: "zerocopy-diagnostic-on-unimplemented-1-78-0"
- crate: "zerocopy-derive"
toolchain: "zerocopy-unsized-needs-drop-1-63-0"
- crate: "zerocopy-derive"
toolchain: "zerocopy-generic-bounds-in-const-fn-1-61-0"
- crate: "zerocopy-derive"
Expand Down Expand Up @@ -212,6 +217,28 @@ jobs:
target: "thumbv6m-none-eabi"
- toolchain: "zerocopy-generic-bounds-in-const-fn-1-61-0"
target: "wasm32-wasi"
# Exclude most targets targets from the
# `zerocopy-unsized-needs-drop-1-63-0` toolchain since the
# `zerocopy-unsized-needs-drop-1-63-0` feature is unrelated to
# compilation target. This only leaves i686 and x86_64 targets.
- toolchain: "zerocopy-unsized-needs-drop-1-63-0"
target: "arm-unknown-linux-gnueabi"
- toolchain: "zerocopy-unsized-needs-drop-1-63-0"
target: "aarch64-unknown-linux-gnu"
- toolchain: "zerocopy-unsized-needs-drop-1-63-0"
target: "powerpc-unknown-linux-gnu"
- toolchain: "zerocopy-unsized-needs-drop-1-63-0"
target: "powerpc64-unknown-linux-gnu"
- toolchain: "zerocopy-unsized-needs-drop-1-63-0"
target: "riscv64gc-unknown-linux-gnu"
- toolchain: "zerocopy-unsized-needs-drop-1-63-0"
target: "s390x-unknown-linux-gnu"
- toolchain: "zerocopy-unsized-needs-drop-1-63-0"
target: "x86_64-pc-windows-msvc"
- toolchain: "zerocopy-unsized-needs-drop-1-63-0"
target: "thumbv6m-none-eabi"
- toolchain: "zerocopy-unsized-needs-drop-1-63-0"
target: "wasm32-wasi"
# Exclude `thumbv6m-none-eabi` combined with any feature that implies
# the `std` feature since `thumbv6m-none-eabi` does not include a
# pre-compiled std.
Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ zerocopy-core-error-1-81-0 = "1.81.0"
# From 1.78.0, Rust supports the `#[diagnostic::on_unimplemented]` attribute.
zerocopy-diagnostic-on-unimplemented-1-78-0 = "1.78.0"

# From 1.63.0, Rust supports generic types with trait bounds in `const fn`.
zerocopy-unsized-needs-drop-1-63-0 = "1.63.0"

# From 1.61.0, Rust supports generic types with trait bounds in `const fn`.
zerocopy-generic-bounds-in-const-fn-1-61-0 = "1.61.0"

Expand Down
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
24 changes: 24 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,30 @@ 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)]
#[inline]
unsafe fn destroy(ptr: MaybeAligned<'_, Self, invariant::Exclusive>) {
// SAFETY: The preconditions of `destroy_unsized` are identical to that
// of `destroy` and are ensured by the caller.
unsafe { crate::util::destroy::destroy_unsized(ptr) }
}
}

/// The metadata associated with a [`KnownLayout`] type.
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
103 changes: 103 additions & 0 deletions src/pointer/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,36 @@ mod _conversions {
}
}

/// `Box<mut T>` → `Ptr<'static, T>`
#[cfg(feature = "alloc")]
impl<'a, T> Ptr<'a, T, (Exclusive, Aligned, Valid)>
where
T: 'a + ?Sized,
{
/// Constructs a `Ptr` from a `Box`.
///
/// This leaks the `Box`.
#[inline]
pub(crate) fn from_box(ptr: alloc::boxed::Box<T>) -> Self {
let ptr = alloc::boxed::Box::into_raw(ptr);
let ptr = unsafe { NonNull::new_unchecked(ptr) };
unsafe { Self::new(ptr) }
}

/// Constructs a `Box` from a `Ptr`.
///
/// # Safety
///
/// This must be invoked with a `Ptr<T>` to a referent allocated by a
/// `Box<U>`, where the size and alignments of `T` and `U` are equal.
#[inline]
pub(crate) unsafe fn into_box(ptr: Self) -> alloc::boxed::Box<T> {
let ptr = ptr.as_mut() as *mut _;
// SAFETY: TODO
unsafe { alloc::boxed::Box::from_raw(ptr) }
}
}

/// `Ptr<'a, T>` → `&'a T`
impl<'a, T, I> Ptr<'a, T, I>
where
Expand Down Expand Up @@ -815,6 +845,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 @@ -954,6 +1003,26 @@ mod _casts {
T: 'a + ?Sized,
I: Invariants,
{
/// Casts to a different (unsized) target type.
#[inline]
pub fn try_cast<U>(self) -> Result<Ptr<'a, U, (I::Aliasing, Any, Any)>, Self>
where
T: KnownLayout<PointerMetadata = U::PointerMetadata>,
U: 'a + ?Sized + KnownLayout,
{
let ptr = self.as_non_null();
let src_size = T::size_of_val_raw(ptr);
let meta = T::pointer_to_metadata(ptr.as_ptr());
if meta.size_for_metadata(U::LAYOUT) <= src_size {
let bytes = ptr.cast::<u8>();
let ptr = U::raw_from_ptr_len(bytes, meta);
// SAFETY: TODO
Ok(unsafe { Ptr::new(ptr) })
} else {
Err(self)
}
}

/// Casts to a different (unsized) target type.
///
/// # Safety
Expand Down Expand Up @@ -1641,6 +1710,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
12 changes: 10 additions & 2 deletions src/util/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,14 @@ 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>) {
// SAFETY: The preconditions of `destroy_sized` are
// identical to that of `destroy` and are ensured by the
// caller.
unsafe { crate::util::destroy::destroy_sized(ptr) }
}
}
};
};
Expand All @@ -599,7 +607,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 +628,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 Down
Loading

0 comments on commit 8d58fff

Please sign in to comment.