diff --git a/src/layout.rs b/src/layout.rs index 903ea4e077..6c83676c80 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -751,8 +751,8 @@ mod cast_from { // of the referent – it only modifies pointer metadata. unsafe impl crate::pointer::cast::Cast for CastFrom where - Src: KnownLayout + ?Sized, - Dst: KnownLayout + ?Sized, + Src: KnownLayout + ?Sized, + Dst: KnownLayout + ?Sized, { } @@ -760,8 +760,8 @@ mod cast_from { // the referent (see inline comments for a more detailed proof of this). unsafe impl crate::pointer::cast::CastExact for CastFrom where - Src: KnownLayout + ?Sized, - Dst: KnownLayout + ?Sized, + Src: KnownLayout + ?Sized, + Dst: KnownLayout + ?Sized, { } @@ -771,8 +771,8 @@ mod cast_from { // operations. unsafe impl crate::pointer::cast::Project for CastFrom where - Src: KnownLayout + ?Sized, - Dst: KnownLayout + ?Sized, + Src: KnownLayout + ?Sized, + Dst: KnownLayout + ?Sized, { /// # PME /// @@ -781,140 +781,228 @@ mod cast_from { // // FIXME(#1817): Support Sized->Unsized and Unsized->Sized casts fn project(src: PtrInner<'_, Src>) -> *mut Dst { - // At compile time (specifically, post-monomorphization time), we - // need to compute two things: - // - Whether, given *any* `*Src`, it is possible to construct a - // `*Dst` which addresses the same number of bytes (ie, whether, - // for any `Src` pointer metadata, there exists `Dst` pointer - // metadata that addresses the same number of bytes) - // - If this is possible, any information necessary to perform the - // `Src`->`Dst` metadata conversion at runtime. - // - // Assume that `Src` and `Dst` are slice DSTs, and define: - // - `S_OFF = Src::LAYOUT.size_info.offset` - // - `S_ELEM = Src::LAYOUT.size_info.elem_size` - // - `D_OFF = Dst::LAYOUT.size_info.offset` - // - `D_ELEM = Dst::LAYOUT.size_info.elem_size` - // - // We are trying to solve the following equation: - // - // D_OFF + d_meta * D_ELEM = S_OFF + s_meta * S_ELEM - // - // At runtime, we will be attempting to compute `d_meta`, given - // `s_meta` (a runtime value) and all other parameters (which are - // compile-time values). We can solve like so: - // - // D_OFF + d_meta * D_ELEM = S_OFF + s_meta * S_ELEM - // - // d_meta * D_ELEM = S_OFF - D_OFF + s_meta * S_ELEM - // - // d_meta = (S_OFF - D_OFF + s_meta * S_ELEM)/D_ELEM - // - // Since `d_meta` will be a `usize`, we need the right-hand side to - // be an integer, and this needs to hold for *any* value of `s_meta` - // (in order for our conversion to be infallible - ie, to not have - // to reject certain values of `s_meta` at runtime). This means - // that: - // - `s_meta * S_ELEM` must be a multiple of `D_ELEM` - // - Since this must hold for any value of `s_meta`, `S_ELEM` must - // be a multiple of `D_ELEM` - // - `S_OFF - D_OFF` must be a multiple of `D_ELEM` - // - // Thus, let `OFFSET_DELTA_ELEMS = (S_OFF - D_OFF)/D_ELEM` and - // `ELEM_MULTIPLE = S_ELEM/D_ELEM`. We can rewrite the above - // expression as: - // - // d_meta = (S_OFF - D_OFF + s_meta * S_ELEM)/D_ELEM - // - // d_meta = OFFSET_DELTA_ELEMS + s_meta * ELEM_MULTIPLE - // - // Thus, we just need to compute the following and confirm that they - // have integer solutions in order to both a) determine whether - // infallible `Src` -> `Dst` casts are possible and, b) pre-compute - // the parameters necessary to perform those casts at runtime. These - // parameters are encapsulated in `CastParams`, which acts as a - // witness that such infallible casts are possible. - /// The parameters required in order to perform a pointer cast from - /// `Src` to `Dst` as described above. + /// `Src` to `Dst`. /// - /// These are a compile-time function of the layouts of `Src` and - /// `Dst`. + /// These are a compile-time function of the layouts of `Src` + /// and `Dst`. /// /// # Safety /// - /// `offset_delta_elems` and `elem_multiple` must be valid as - /// described above. - /// /// `Src`'s alignment must not be smaller than `Dst`'s alignment. + struct CastParams { + inner: CastParamsInner, + _src: PhantomData, + _dst: PhantomData, + } + #[derive(Copy, Clone)] - struct CastParams { - offset_delta_elems: usize, - elem_multiple: usize, + enum CastParamsInner { + // At compile time (specifically, post-monomorphization time), + // we need to compute two things: + // - Whether, given *any* `*Src`, it is possible to construct a + // `*Dst` which addresses the same number of bytes (ie, + // whether, for any `Src` pointer metadata, there exists `Dst` + // pointer metadata that addresses the same number of bytes) + // - If this is possible, any information necessary to perform + // the `Src`->`Dst` metadata conversion at runtime. + // + // Assume that `Src` and `Dst` are slice DSTs, and define: + // - `S_OFF = Src::LAYOUT.size_info.offset` + // - `S_ELEM = Src::LAYOUT.size_info.elem_size` + // - `D_OFF = Dst::LAYOUT.size_info.offset` + // - `D_ELEM = Dst::LAYOUT.size_info.elem_size` + // + // We are trying to solve the following equation: + // + // D_OFF + d_meta * D_ELEM = S_OFF + s_meta * S_ELEM + // + // At runtime, we will be attempting to compute `d_meta`, given + // `s_meta` (a runtime value) and all other parameters (which + // are compile-time values). We can solve like so: + // + // D_OFF + d_meta * D_ELEM = S_OFF + s_meta * S_ELEM + // + // d_meta * D_ELEM = S_OFF - D_OFF + s_meta * S_ELEM + // + // d_meta = (S_OFF - D_OFF + s_meta * S_ELEM)/D_ELEM + // + // Since `d_meta` will be a `usize`, we need the right-hand side + // to be an integer, and this needs to hold for *any* value of + // `s_meta` (in order for our conversion to be infallible - ie, + // to not have to reject certain values of `s_meta` at runtime). + // This means that: + // + // - `s_meta * S_ELEM` must be a multiple of `D_ELEM` + // - Since this must hold for any value of `s_meta`, `S_ELEM` + // must be a multiple of `D_ELEM` + // - `S_OFF - D_OFF` must be a multiple of `D_ELEM` + // + // Thus, let `OFFSET_DELTA_ELEMS = (S_OFF - D_OFF)/D_ELEM` and + // `ELEM_MULTIPLE = S_ELEM/D_ELEM`. We can rewrite the above + // expression as: + // + // d_meta = (S_OFF - D_OFF + s_meta * S_ELEM)/D_ELEM + // + // d_meta = OFFSET_DELTA_ELEMS + s_meta * ELEM_MULTIPLE + // + // Thus, we just need to compute the following and confirm that + // they have integer solutions in order to both a) determine + // whether infallible `Src` -> `Dst` casts are possible and, b) + // pre-compute the parameters necessary to perform those casts + // at runtime. These parameters are encapsulated in + // `CastParams`, which acts as a witness that such infallible + // casts are possible. + /// The parameters required in order to perform an + /// unsized-to-unsized pointer cast from `Src` to `Dst` as + /// described above. + /// + /// # Safety + /// + /// `Src` and `Dst` must both be slice DSTs. + /// + /// `offset_delta_elems` and `elem_multiple` must be valid as + /// described above. + UnsizedToUnsized { offset_delta_elems: usize, elem_multiple: usize }, + + /// The metadata of a `Dst` which has the same size as `Src: + /// Sized`. + /// + /// # Safety + /// + /// `Src: Sized` and `Dst` must be a slice DST. + /// + /// A raw `Dst` pointer with metadata `dst_meta` must address + /// `size_of::()` bytes. + SizedToUnsized { dst_meta: usize }, + + /// The metadata of a `Dst` which has the same size as `Src: + /// Sized`. + /// + /// # Safety + /// + /// `Src` and `Dst` must both be `Sized` and `size_of::() + /// == size_of::()`. + SizedToSized, + } + + impl Copy for CastParams {} + impl Clone for CastParams { + fn clone(&self) -> Self { + *self + } } - impl CastParams { - const fn try_compute(src: &DstLayout, dst: &DstLayout) -> Option { + impl CastParams { + const fn try_compute( + src: &DstLayout, + dst: &DstLayout, + ) -> Option> { if src.align.get() < dst.align.get() { return None; } - let (src, dst) = if let (SizeInfo::SliceDst(src), SizeInfo::SliceDst(dst)) = - (src.size_info, dst.size_info) - { - (src, dst) - } else { - return None; - }; + let inner = match (src.size_info, dst.size_info) { + ( + SizeInfo::Sized { size: src_size }, + SizeInfo::Sized { size: dst_size }, + ) => { + if src_size != dst_size { + return None; + } + + // SAFETY: We checked above that `src_size == + // dst_size`. + CastParamsInner::SizedToSized + } + (SizeInfo::Sized { size: src_size }, SizeInfo::SliceDst(dst)) => { + let offset_delta = if let Some(od) = src_size.checked_sub(dst.offset) { + od + } else { + return None; + }; - let offset_delta = if let Some(od) = src.offset.checked_sub(dst.offset) { - od - } else { - return None; - }; + let dst_elem_size = if let Some(e) = NonZeroUsize::new(dst.elem_size) { + e + } else { + return None; + }; - let dst_elem_size = if let Some(e) = NonZeroUsize::new(dst.elem_size) { - e - } else { - return None; - }; + // PANICS: `dst_elem_size: NonZeroUsize`, so this won't + // divide by zero. + #[allow(clippy::arithmetic_side_effects)] + let delta_mod_other_elem = offset_delta % dst_elem_size.get(); - // PANICS: `dst_elem_size: NonZeroUsize`, so this won't - // divide by zero. - #[allow(clippy::arithmetic_side_effects)] - let delta_mod_other_elem = offset_delta % dst_elem_size.get(); + if delta_mod_other_elem != 0 { + return None; + } - // PANICS: `dst_elem_size: NonZeroUsize`, so this won't - // divide by zero. - #[allow(clippy::arithmetic_side_effects)] - let elem_remainder = src.elem_size % dst_elem_size.get(); + // PANICS: `dst_elem_size: NonZeroUsize`, so this won't + // divide by zero. + #[allow(clippy::arithmetic_side_effects)] + let dst_meta = offset_delta / dst_elem_size.get(); - if delta_mod_other_elem != 0 - || src.elem_size < dst.elem_size - || elem_remainder != 0 - { - return None; - } + // SAFETY: The preceding math ensures that a `Dst` + // with `dst_meta` addresses `src_size` bytes. + CastParamsInner::SizedToUnsized { dst_meta } + } + (SizeInfo::SliceDst(src), SizeInfo::SliceDst(dst)) => { + let offset_delta = if let Some(od) = src.offset.checked_sub(dst.offset) + { + od + } else { + return None; + }; - // PANICS: `dst_elem_size: NonZeroUsize`, so this won't - // divide by zero. - #[allow(clippy::arithmetic_side_effects)] - let offset_delta_elems = offset_delta / dst_elem_size.get(); + let dst_elem_size = if let Some(e) = NonZeroUsize::new(dst.elem_size) { + e + } else { + return None; + }; - // PANICS: `dst_elem_size: NonZeroUsize`, so this won't - // divide by zero. - #[allow(clippy::arithmetic_side_effects)] - let elem_multiple = src.elem_size / dst_elem_size.get(); + // PANICS: `dst_elem_size: NonZeroUsize`, so this won't + // divide by zero. + #[allow(clippy::arithmetic_side_effects)] + let delta_mod_other_elem = offset_delta % dst_elem_size.get(); + + // PANICS: `dst_elem_size: NonZeroUsize`, so this won't + // divide by zero. + #[allow(clippy::arithmetic_side_effects)] + let elem_remainder = src.elem_size % dst_elem_size.get(); + + if delta_mod_other_elem != 0 + || src.elem_size < dst.elem_size + || elem_remainder != 0 + { + return None; + } + + // PANICS: `dst_elem_size: NonZeroUsize`, so this won't + // divide by zero. + #[allow(clippy::arithmetic_side_effects)] + let offset_delta_elems = offset_delta / dst_elem_size.get(); + + // PANICS: `dst_elem_size: NonZeroUsize`, so this won't + // divide by zero. + #[allow(clippy::arithmetic_side_effects)] + let elem_multiple = src.elem_size / dst_elem_size.get(); + + CastParamsInner::UnsizedToUnsized { + // SAFETY: We checked above that this is an exact ratio. + offset_delta_elems, + // SAFETY: We checked above that this is an exact ratio. + elem_multiple, + } + } + _ => return None, + }; // SAFETY: We checked above that `src.align >= dst.align`. - Some(CastParams { - // SAFETY: We checked above that this is an exact ratio. - offset_delta_elems, - // SAFETY: We checked above that this is an exact ratio. - elem_multiple, - }) + Some(CastParams { inner, _src: PhantomData, _dst: PhantomData }) } + } + impl CastParams { /// # Safety /// /// `src_meta` describes a `Src` whose size is no larger than @@ -922,41 +1010,57 @@ mod cast_from { /// /// The returned metadata describes a `Dst` of the same size as /// the original `Src`. - unsafe fn cast_metadata(self, src_meta: usize) -> usize { + #[inline(always)] + unsafe fn cast_metadata( + self, + src_meta: Src::PointerMetadata, + ) -> Dst::PointerMetadata { #[allow(unused)] use crate::util::polyfills::*; - // SAFETY: `self` is a witness that the following equation - // holds: - // - // D_OFF + d_meta * D_ELEM = S_OFF + s_meta * S_ELEM - // - // Since the caller promises that `src_meta` is valid `Src` - // metadata, this math will not overflow, and the returned - // value will describe a `Dst` of the same size. - #[allow(unstable_name_collisions, clippy::multiple_unsafe_ops_per_block)] - unsafe { - self.offset_delta_elems - .unchecked_add(src_meta.unchecked_mul(self.elem_multiple)) - } + let dst_meta = match self.inner { + CastParamsInner::UnsizedToUnsized { offset_delta_elems, elem_multiple } => { + let src_meta = src_meta.to_elem_count(); + #[allow( + unstable_name_collisions, + clippy::multiple_unsafe_ops_per_block + )] + // SAFETY: `self` is a witness that the following + // equation holds: + // + // D_OFF + d_meta * D_ELEM = S_OFF + s_meta * S_ELEM + // + // Since the caller promises that `src_meta` is + // valid `Src` metadata, this math will not + // overflow, and the returned value will describe a + // `Dst` of the same size. + unsafe { + offset_delta_elems + .unchecked_add(src_meta.unchecked_mul(elem_multiple)) + } + } + CastParamsInner::SizedToUnsized { dst_meta } => dst_meta, + CastParamsInner::SizedToSized => 0, + }; + Dst::PointerMetadata::from_elem_count(dst_meta) } } trait Params { - const CAST_PARAMS: CastParams; + const CAST_PARAMS: CastParams; } impl Params for Dst where Src: KnownLayout + ?Sized, - Dst: KnownLayout + ?Sized, + Dst: KnownLayout + ?Sized, { - const CAST_PARAMS: CastParams = + const CAST_PARAMS: CastParams = match CastParams::try_compute(&Src::LAYOUT, &Dst::LAYOUT) { Some(params) => params, None => const_panic!( - "cannot `transmute_ref!` or `transmute_mut!` between incompatible types" - ), + "cannot `transmute_ref!` or `transmute_mut!` between incompatible types" + ), }; } diff --git a/src/lib.rs b/src/lib.rs index 45d01a1778..74fb9c6632 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -888,6 +888,12 @@ pub trait PointerMetadata: Copy + Eq + Debug { /// `elems`. No other types are currently supported. fn from_elem_count(elems: usize) -> Self; + /// Converts `self` to an element count. + /// + /// If `Self = ()`, this returns `0`. If `Self = usize`, this returns + /// `self`. No other types are currently supported. + fn to_elem_count(self) -> usize; + /// Computes the size of the object with the given layout and pointer /// metadata. /// @@ -909,6 +915,11 @@ impl PointerMetadata for () { #[allow(clippy::unused_unit)] fn from_elem_count(_elems: usize) -> () {} + #[inline] + fn to_elem_count(self) -> usize { + 0 + } + #[inline] fn size_for_metadata(self, layout: DstLayout) -> Option { match layout.size_info { @@ -926,6 +937,11 @@ impl PointerMetadata for usize { elems } + #[inline] + fn to_elem_count(self) -> usize { + self + } + #[inline] fn size_for_metadata(self, layout: DstLayout) -> Option { match layout.size_info {