Skip to content

Commit

Permalink
[WIP] Make pointer casts work in const fn
Browse files Browse the repository at this point in the history
gherrit-pr-id: I3016a31a5d3c9c0461a5e7fcf3fd9bc4d0a111bc
  • Loading branch information
joshlf committed Oct 23, 2024
1 parent 0fae530 commit a6c1312
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 23 deletions.
24 changes: 12 additions & 12 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,10 +258,10 @@ impl<Src, Dst: ?Sized> AlignmentError<Src, Dst> {
///
/// The caller must ensure that `Dst`'s alignment requirement is greater
/// than one.
pub(crate) unsafe fn new_unchecked(src: Src) -> Self {
pub(crate) const unsafe fn new_unchecked(src: Src) -> Self {
// INVARIANT: The caller guarantees that `Dst`'s alignment requirement
// is greater than one.
Self { src, dst: SendSyncPhantomData::default() }
Self { src, dst: SendSyncPhantomData::new() }
}

/// Produces the source underlying the failed conversion.
Expand All @@ -274,7 +274,7 @@ impl<Src, Dst: ?Sized> AlignmentError<Src, Dst> {
// INVARIANT: `with_src` doesn't change the type of `Dst`, so the
// invariant that `Dst`'s alignment requirement is greater than one is
// preserved.
AlignmentError { src: new_src, dst: SendSyncPhantomData::default() }
AlignmentError { src: new_src, dst: SendSyncPhantomData::new() }
}

/// Maps the source value associated with the conversion error.
Expand All @@ -299,7 +299,7 @@ impl<Src, Dst: ?Sized> AlignmentError<Src, Dst> {
/// ```
#[inline]
pub fn map_src<NewSrc>(self, f: impl Fn(Src) -> NewSrc) -> AlignmentError<NewSrc, Dst> {
AlignmentError { src: f(self.src), dst: SendSyncPhantomData::default() }
AlignmentError { src: f(self.src), dst: SendSyncPhantomData::new() }
}

pub(crate) const fn into<S, V>(self) -> ConvertError<Self, S, V> {
Expand Down Expand Up @@ -416,8 +416,8 @@ pub struct SizeError<Src, Dst: ?Sized> {
}

impl<Src, Dst: ?Sized> SizeError<Src, Dst> {
pub(crate) fn new(src: Src) -> Self {
Self { src, dst: SendSyncPhantomData::default() }
pub(crate) const fn new(src: Src) -> Self {
Self { src, dst: SendSyncPhantomData::new() }
}

/// Produces the source underlying the failed conversion.
Expand All @@ -428,7 +428,7 @@ impl<Src, Dst: ?Sized> SizeError<Src, Dst> {

/// Sets the source value associated with the conversion error.
pub(crate) fn with_src<NewSrc>(self, new_src: NewSrc) -> SizeError<NewSrc, Dst> {
SizeError { src: new_src, dst: SendSyncPhantomData::default() }
SizeError { src: new_src, dst: SendSyncPhantomData::new() }
}

/// Maps the source value associated with the conversion error.
Expand All @@ -454,12 +454,12 @@ impl<Src, Dst: ?Sized> SizeError<Src, Dst> {
/// ```
#[inline]
pub fn map_src<NewSrc>(self, f: impl Fn(Src) -> NewSrc) -> SizeError<NewSrc, Dst> {
SizeError { src: f(self.src), dst: SendSyncPhantomData::default() }
SizeError { src: f(self.src), dst: SendSyncPhantomData::new() }
}

/// Sets the destination type associated with the conversion error.
pub(crate) fn with_dst<NewDst: ?Sized>(self) -> SizeError<Src, NewDst> {
SizeError { src: self.src, dst: SendSyncPhantomData::default() }
SizeError { src: self.src, dst: SendSyncPhantomData::new() }
}

/// Converts the error into a general [`ConvertError`].
Expand Down Expand Up @@ -559,8 +559,8 @@ pub struct ValidityError<Src, Dst: ?Sized + TryFromBytes> {
}

impl<Src, Dst: ?Sized + TryFromBytes> ValidityError<Src, Dst> {
pub(crate) fn new(src: Src) -> Self {
Self { src, dst: SendSyncPhantomData::default() }
pub(crate) const fn new(src: Src) -> Self {
Self { src, dst: SendSyncPhantomData::new() }
}

/// Produces the source underlying the failed conversion.
Expand Down Expand Up @@ -591,7 +591,7 @@ impl<Src, Dst: ?Sized + TryFromBytes> ValidityError<Src, Dst> {
/// ```
#[inline]
pub fn map_src<NewSrc>(self, f: impl Fn(Src) -> NewSrc) -> ValidityError<NewSrc, Dst> {
ValidityError { src: f(self.src), dst: SendSyncPhantomData::default() }
ValidityError { src: f(self.src), dst: SendSyncPhantomData::new() }
}

/// Converts the error into a general [`ConvertError`].
Expand Down
10 changes: 9 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,14 @@ pub unsafe trait KnownLayout {
/// The type of metadata stored in a pointer to `Self`.
///
/// This is `()` for sized types and `usize` for slice DSTs.
///
/// # Safety
///
/// Callers may assume for soundness that one of the following holds:
/// - `Self::LAYOUT.size_info` is a `SizeInfo::Sized` and
/// `Self::PointerMetadata = ()`
/// - `Self::LAYOUT.size_info` is a `SizeInfo::SliceDst` and
/// `Self::PointerMetadata = usize`
type PointerMetadata: PointerMetadata;

/// The layout of `Self`.
Expand Down Expand Up @@ -763,7 +771,7 @@ pub unsafe trait KnownLayout {

/// The metadata associated with a [`KnownLayout`] type.
#[doc(hidden)]
pub trait PointerMetadata: Copy + Eq + Debug {
pub trait PointerMetadata: Copy + Eq + Debug + IntoBytes + Immutable {
/// Constructs a `Self` from an element count.
///
/// If `Self = ()`, this returns `()`. If `Self = usize`, this returns
Expand Down
15 changes: 8 additions & 7 deletions src/pointer/inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ impl<'a, T> PtrInner<'a, [T]> {
/// # Safety
///
/// `range` is a valid range (`start <= end`) and `end <= self.len()`.
pub(crate) unsafe fn slice_unchecked(self, range: Range<usize>) -> Self {
pub(crate) const unsafe fn slice_unchecked(self, range: Range<usize>) -> Self {
let base = self.as_non_null().cast::<T>().as_ptr();

// SAFETY: The caller promises that `start <= end <= self.len()`. By
Expand Down Expand Up @@ -209,7 +209,7 @@ impl<'a, T> PtrInner<'a, [T]> {
///
/// Given `let (left, right) = ptr.split_at(l_len)`, it is guaranteed
/// that `left` and `right` are contiguous and non-overlapping.
pub(crate) unsafe fn split_at(self, l_len: usize) -> (Self, Self) {
pub(crate) const unsafe fn split_at(self, l_len: usize) -> (Self, Self) {
// SAFETY: The caller promises that `l_len <= self.len()`.
// Trivially, `0 <= l_len`.
let left = unsafe { self.slice_unchecked(0..l_len) };
Expand Down Expand Up @@ -304,7 +304,7 @@ impl<'a, T> PtrInner<'a, [T]> {
/// # Safety
///
/// Unsafe code my rely on `len` satisfying the above contract.
pub(crate) fn len(&self) -> usize {
pub(crate) const fn len(&self) -> usize {
self.trailing_slice_len()
}
}
Expand Down Expand Up @@ -395,7 +395,7 @@ impl<'a> PtrInner<'a, [u8]> {
/// `self`. Finally:
/// - If this is a prefix cast, `ptr` has the same address as `self`.
/// - If this is a suffix cast, `remainder` has the same address as `self`.
pub(crate) fn try_cast_into<U>(
pub(crate) const fn try_cast_into<U>(
self,
cast_type: CastType,
meta: Option<U::PointerMetadata>,
Expand All @@ -408,7 +408,7 @@ impl<'a> PtrInner<'a, [u8]> {
// This can return `None` if the metadata describes an object
// which can't fit in an `isize`.
Some(meta) => {
let size = match meta.size_for_metadata(U::LAYOUT) {
let size = match crate::util::size_for_metadata::<U>(meta) {
Some(size) => size,
None => return Err(CastError::Size(SizeError::new(self))),
};
Expand All @@ -423,7 +423,7 @@ impl<'a> PtrInner<'a, [u8]> {
// `validate_cast_and_convert_metadata` will only panic if `U` is a DST
// whose trailing slice element is zero-sized.
let maybe_metadata = layout.validate_cast_and_convert_metadata(
AsAddress::addr(self.as_non_null().as_ptr()),
self.as_non_null().as_ptr() as *mut u8 as usize,
self.len(),
cast_type,
);
Expand Down Expand Up @@ -451,7 +451,8 @@ impl<'a> PtrInner<'a, [u8]> {

let base = target.as_non_null().cast::<u8>();

let elems = <U as KnownLayout>::PointerMetadata::from_elem_count(elems);
// let elems = <U as KnownLayout>::PointerMetadata::from_elem_count(elems);
let elems = crate::util::metadata_from_elem_count::<U>(elems);
// For a slice DST type, if `meta` is `Some(elems)`, then we synthesize
// `layout` to describe a sized type whose size is equal to the size of
// the instance that we are asked to cast. For sized types,
Expand Down
107 changes: 104 additions & 3 deletions src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use core::{
use crate::{
error::AlignmentError,
pointer::invariant::{self, Invariants},
Unalign,
TrailingSliceLayout, Unalign,
};

/// A type which has the same layout as the type it wraps.
Expand Down Expand Up @@ -481,8 +481,8 @@ unsafe impl<T: ?Sized> Send for SendSyncPhantomData<T> {}
// to be called from multiple threads.
unsafe impl<T: ?Sized> Sync for SendSyncPhantomData<T> {}

impl<T: ?Sized> Default for SendSyncPhantomData<T> {
fn default() -> SendSyncPhantomData<T> {
impl<T: ?Sized> SendSyncPhantomData<T> {
pub(crate) const fn new() -> SendSyncPhantomData<T> {
SendSyncPhantomData(PhantomData)
}
}
Expand Down Expand Up @@ -679,6 +679,107 @@ pub(crate) unsafe fn copy_unchecked(src: &[u8], dst: &mut [u8]) {
};
}

pub(crate) const fn metadata_from_elem_count<T: ?Sized + crate::KnownLayout>(
elems: usize,
) -> T::PointerMetadata {
use crate::layout::SizeInfo;

// SAFETY: Per `KnownLayout::PointerMetadata`:
//
// # Safety
//
// Callers may assume for soundness that one of the following holds:
// - `Self::LAYOUT.size_info` is a `SizeInfo::Sized` and
// `Self::PointerMetadata = ()`
// - `Self::LAYOUT.size_info` is a `SizeInfo::SliceDst` and
// `Self::PointerMetadata = usize`
//
// In the following branches, we only transmute as consistent with this
// invariant.
match T::LAYOUT.size_info {
SizeInfo::Sized { .. } => unsafe { transmute::<(), T::PointerMetadata>(()) },
SizeInfo::SliceDst(_) => unsafe { transmute::<usize, T::PointerMetadata>(elems) },
}
}

/// Computes the size of the `T` with the given pointer metadata.
///
/// # Safety
///
/// `size_for_metadata` promises to only return `None` if the resulting size
/// would not fit in a `usize`.
pub(crate) const fn size_for_metadata<T: ?Sized + crate::KnownLayout>(
meta: T::PointerMetadata,
) -> Option<usize> {
use crate::layout::{SizeInfo, TrailingSliceLayout};

match T::LAYOUT.size_info {
SizeInfo::Sized { size } => Some(size),
// NOTE: This branch is unreachable, but we return `None` rather
// than `unreachable!()` to avoid generating panic paths.
SizeInfo::SliceDst(TrailingSliceLayout { offset, elem_size }) => {
// SAFETY: Per `KnownLayout::PointerMetadata`:
//
// Callers may assume for soundness that one of the following
// holds:
// ...
// - `Self::LAYOUT.size_info` is a `SizeInfo::SliceDst` and
// `Self::PointerMetadata = usize`
//
// Since, in this branch, we know that `T::LAYOUT.size_info =
// SizeInfo::SliceDst`, we can assume that `T::PointerMetadata =
// usize`.
let count: usize = unsafe { transmute(meta) };
let Some(slice_len) = elem_size.checked_mul(count) else { return None };
let Some(without_padding) = offset.checked_add(slice_len) else { return None };
without_padding
.checked_add(crate::util::padding_needed_for(without_padding, T::LAYOUT.align))
}
}
}

/// Casts a `*mut Src` to a `*mut Dst`.
///
/// # Safety
///
/// The caller must ensure that `Src` and `Dst` must either both be `Sized` or
/// both be unsized.
#[inline(always)]
pub(crate) const unsafe fn cast_unchecked<Src: ?Sized, Dst: ?Sized>(src: *mut Src) -> *mut Dst {
// TODO(https://github.com/rust-lang/reference/pull/1661): Add safety
// comment once this lands.
unsafe { transmute(src) }
}

/// Like [`core::mem::transmute`], but works on generic types and types of
/// different sizes.
///
/// # Safety
///
/// The caller must ensure that reinterpreting the bytes of `src` as a `Dst` is
/// sound, including if `Src` and `Dst` are different sizes.
const unsafe fn transmute<Src, Dst>(src: Src) -> Dst {
use core::mem::ManuallyDrop;

#[repr(C)]
union Transmute<Src, Dst> {
src: ManuallyDrop<Src>,
dst: ManuallyDrop<Dst>,
}

// SAFETY: The caller promises that performing this transmute is sound.
// Since `Transmute` is a `#[repr(C)]` union, both `src` and `dst` are at
// byte offset 0 within `Transmute` [1], and so this is equivalent to
// transmuting a `ManuallyDrop<Src>` into a `ManuallyDrop<Dst>`. Since
// `ManuallyDrop<T>` has the same layout as `T` [2], this is equivalent to
// transmuting `src` directly into `dst`.
//
// [1] TODO
//
// [2] TODO
ManuallyDrop::into_inner(unsafe { Transmute { src: ManuallyDrop::new(src) }.dst })
}

/// Since we support multiple versions of Rust, there are often features which
/// have been stabilized in the most recent stable release which do not yet
/// exist (stably) on our MSRV. This module provides polyfills for those
Expand Down

0 comments on commit a6c1312

Please sign in to comment.