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] Make pointer casts work in const fn #1968

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
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
Loading