diff --git a/zerocopy/src/layout/cast_from.rs b/zerocopy/src/layout/cast_from.rs index 4be9f1bddd..eea5a6812c 100644 --- a/zerocopy/src/layout/cast_from.rs +++ b/zerocopy/src/layout/cast_from.rs @@ -9,8 +9,323 @@ // This file may not be copied, modified, or distributed except according to // those terms. +//! Compile-time cast planning and runtime pointer projection. +//! +//! `CastPlan` selects and applies numerical metadata transformations from two +//! layouts. `CastParams` associates a selected plan with a `KnownLayout` type +//! pair and converts their typed metadata to and from element counts. Its +//! associated constant performs selection at compile time; `CastFrom::project` +//! uses that constant to transform metadata and construct the destination +//! pointer at runtime. + use crate::*; +/// A numerical metadata transformation selected for a pair of layouts. +/// +/// A raw plan contains no type or layout identity. Its parameters preserve size +/// only in relation to the layouts used by `try_compute`. `CastParams` retains +/// that connection to the `KnownLayout` pair used for pointer projection. +#[derive(Copy, Clone)] +enum CastPlan { + // 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. + // + // For slice DSTs, destination metadata is an affine function + // of source metadata: + // + // dst_meta = offset_delta_elems + src_meta * elem_multiple + // + // `elem_multiple` scales the destination's trailing element + // size to the source's. `offset_delta_elems` selects a + // destination size equal to the source's zero-element size. + // We then prove that the two complete rounded size formulas + // produce the same sequence for all metadata. This is + // necessary because a packed outer DST can retain a nested + // field's rounding operation; comparing only physical + // trailing-slice offsets is not sufficient. + /// Converts between slice DSTs using a size-preserving affine map. + UnsizedToUnsized { offset_delta_elems: usize, elem_multiple: usize }, + + /// Uses the metadata selected for a sized source and a slice DST. + SizedToUnsized { dst_meta: usize }, + + /// Converts between sized layouts with equal sizes. + SizedToSized, +} + +impl CastPlan { + /// Given a nonzero `dst.elem_size` that exactly divides + /// `src.elem_size`, produces `true` only when the following + /// metadata map preserves the complete object size for every + /// source element count `src_meta`: + /// + /// ```text + /// dst_meta = dst_base + src_meta * (src.elem_size / dst.elem_size) + /// ``` + /// + /// This recognizes sufficient conditions for equality. A + /// `false` result also includes equivalent size sequences + /// that this test cannot recognize, or an adjustment whose + /// normalized components cannot be represented in `usize`. + const fn size_sequences_match( + src: TrailingSliceLayout, + dst: TrailingSliceLayout, + dst_base: usize, + ) -> bool { + let base_bytes = match dst_base.checked_mul(dst.elem_size) { + Some(bytes) => bytes, + None => return false, + }; + let shifted_dst = match dst.advance(base_bytes, src.elem_size) { + Some(layout) => layout, + None => return false, + }; + src.has_same_size_sequence(shifted_dst) + } + + /// Given the complete layouts of `Src` and `Dst`, produces + /// parameters for a cast that preserves object size. + /// + /// A `Some` result establishes that `Src`'s alignment is at + /// least `Dst`'s and that the metadata map preserves size for + /// every valid source metadata value. + /// + /// Supports casts between sized types of equal size, from a + /// sized type to a slice DST, and between slice DSTs. A slice + /// DST destination must have nonzero-sized elements; between + /// slice DSTs, its element size must exactly divide the + /// source's nonzero element size. + /// + /// Produces `None` for unsupported casts or when this method + /// cannot establish a size-preserving metadata map. Rejection + /// does not establish that no such map exists. + const fn try_compute(src_layout: &DstLayout, dst_layout: &DstLayout) -> Option { + if src_layout.align.get() < dst_layout.align.get() { + return None; + } + + let plan = match (src_layout.size_info, dst_layout.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`. + CastPlan::SizedToSized + } + (SizeInfo::Sized { size: src_size }, SizeInfo::SliceDst(_)) => { + let dst_meta = match dst_layout.metadata_for_exact_size(src_size) { + Some(meta) => meta, + None => return None, + }; + + // SAFETY: The preceding math ensures that a `Dst` + // with `dst_meta` addresses `src_size` bytes. + CastPlan::SizedToUnsized { dst_meta } + } + (SizeInfo::SliceDst(src), SizeInfo::SliceDst(dst)) => { + let dst_elem_size = if let Some(e) = NonZeroUsize::new(dst.elem_size) { + e + } else { + return None; + }; + + if src.elem_size < dst.elem_size { + return None; + } + + let (elem_multiple, described_src_elem_size) = + super::max_elems_for_bytes(src.elem_size, dst_elem_size); + if described_src_elem_size != src.elem_size { + return None; + } + + // Prefer an exact destination-element adjustment + // between `src.size_offset()` and + // `dst.size_offset()`. This preserves offset-based + // cast compatibility and metadata selection when + // several lengths have the same rounded size. It + // also avoids choosing a length whose subsequent + // padding sequence differs from the source's. + let exact_offset_delta_elems = + match src.size_offset().checked_sub(dst.size_offset()) { + Some(delta) => { + let (elems, described_delta) = + super::max_elems_for_bytes(delta, dst_elem_size); + if described_delta == delta { + Some(elems) + } else { + None + } + } + None => None, + }; + + let offset_delta_elems = match exact_offset_delta_elems { + Some(elems) if Self::size_sequences_match(src, dst, elems) => elems, + _ => { + let src_zero_size = match src.size_for_elems(0) { + Some(size) => size, + None => return None, + }; + let elems = match dst_layout.metadata_for_exact_size(src_zero_size) { + Some(elems) => elems, + None => return None, + }; + if !Self::size_sequences_match(src, dst, elems) { + return None; + } + elems + } + }; + + CastPlan::UnsizedToUnsized { + // SAFETY: `size_sequences_match` proves that + // this affine metadata map preserves size. + offset_delta_elems, + // SAFETY: We checked above that this is an exact + // ratio of source to destination element size. + elem_multiple, + } + } + _ => return None, + }; + + // SAFETY: We checked above that `src.align >= dst.align`. + Some(plan) + } + + /// Applies the numerical metadata transformation to an element count. + /// + /// # Safety + /// + /// For `UnsizedToUnsized`, neither `src_meta * elem_multiple` nor + /// `offset_delta_elems + src_meta * elem_multiple` may overflow `usize`. + /// The other variants impose no condition on `src_meta` and ignore it. + #[inline(always)] + unsafe fn cast_metadata(self, src_meta: usize) -> usize { + match self { + CastPlan::UnsizedToUnsized { offset_delta_elems, elem_multiple } => { + // SAFETY: The caller provides the helper's complete + // no-overflow precondition. + unsafe { super::add_scaled_metadata(offset_delta_elems, src_meta, elem_multiple) } + } + CastPlan::SizedToUnsized { dst_meta } => dst_meta, + CastPlan::SizedToSized => 0, + } + } +} + +/// A selected plan associated with the layouts of `Src` and `Dst`. +/// +/// The private fields are initialized only by `CAST_PARAMS`, which selects the +/// plan using `Src::LAYOUT` and `Dst::LAYOUT`. The markers retain this type-pair +/// association when the plan is applied to typed pointer metadata at runtime. +/// +/// # Safety +/// +/// `Src`'s alignment must not be smaller than `Dst`'s alignment. +struct CastParams { + plan: CastPlan, + _src: PhantomData, + _dst: PhantomData, +} + +impl Copy for CastParams {} +impl Clone for CastParams { + fn clone(&self) -> Self { + *self + } +} + +impl CastParams { + /// Computes the parameters at compile time, rejecting incompatible layouts + /// with a post-monomorphization error. + const CAST_PARAMS: Self = match CastPlan::try_compute(&Src::LAYOUT, &Dst::LAYOUT) { + // SAFETY: `try_compute` checked that `Src::LAYOUT.align >= + // Dst::LAYOUT.align`. + Some(plan) => Self { plan, _src: PhantomData, _dst: PhantomData }, + None => { + const_panic!("cannot `transmute_ref!` or `transmute_mut!` between incompatible types") + } + }; + + /// # Safety + /// + /// `src_meta` describes a `Src` whose size is no larger than `isize::MAX`. + /// + /// The returned metadata describes a `Dst` of the same size as the original + /// `Src`. + #[inline(always)] + unsafe fn cast_metadata(self, src_meta: Src::PointerMetadata) -> Dst::PointerMetadata { + let src_meta = match self.plan { + CastPlan::UnsizedToUnsized { .. } => src_meta.to_elem_count(), + _ => 0, + }; + + // SAFETY: The sized variants perform no unchecked arithmetic. + // For `UnsizedToUnsized`, `self.plan` was selected for the layouts and + // witnesses that this affine map makes `Src` and `Dst`'s complete + // rounded size formulas equal for every source metadata value. + // Interpret the arithmetic in this proof over the + // mathematical nonnegative integers. Since the + // caller promises that `src_meta` is valid `Src` + // metadata, the source object size `src_size` it + // describes is at most `isize::MAX`. Let + // `src_elem_size` and `dst_elem_size` be the + // respective trailing-slice element sizes. Then: + // + // src_meta * src_elem_size + // <= src_size <= isize::MAX. + // + // Since `elem_multiple` is the exact ratio + // `src_elem_size / dst_elem_size` and + // `dst_elem_size >= 1`, + // + // src_meta * elem_multiple + // <= src_meta * src_elem_size + // <= isize::MAX <= usize::MAX. + // + // Thus `scaled_elems = src_meta * elem_multiple` + // is representable, and its byte contribution is + // `scaled_elems * dst_elem_size = src_meta * + // src_elem_size`. Let `base_bytes = + // offset_delta_elems * dst_elem_size`. + // `size_sequences_match` compares the source + // formula with the destination formula advanced by + // `base_bytes`. The latter's unrounded term + // contains `base_bytes + src_meta * src_elem_size`, + // and its complete size is at least that large + // (formally checked by Kani in + // `prove_size_formula_bounds_size_offset`). + // Sequence equality therefore gives, over the + // mathematical nonnegative integers, + // + // base_bytes + src_meta * src_elem_size + // = (offset_delta_elems + scaled_elems) + // * dst_elem_size + // <= src_size. + // + // Since `dst_elem_size >= 1`, the metadata sum + // `offset_delta_elems + scaled_elems` is also at + // most `src_size`, and thus at most `isize::MAX <= + // usize::MAX`. The sum is therefore representable, + // and the returned metadata describes a `Dst` of + // the same size. + // SAFETY: The bounds above establish the helper's + // complete no-overflow precondition. + let dst_meta = unsafe { self.plan.cast_metadata(src_meta) }; + Dst::PointerMetadata::from_elem_count(dst_meta) + } +} + pub(crate) struct CastFrom { _never: core::convert::Infallible, _marker: PhantomData, @@ -50,337 +365,8 @@ where // // FIXME(#1817): Support Sized->Unsized and Unsized->Sized casts fn project(src: PtrInner<'_, Src>) -> *mut Dst { - /// The parameters required in order to perform a pointer cast from - /// `Src` to `Dst`. - /// - /// These are a compile-time function of the layouts of `Src` - /// and `Dst`. - /// - /// # Safety - /// - /// `Src`'s alignment must not be smaller than `Dst`'s alignment. - struct CastParams { - inner: CastParamsInner, - _src: PhantomData, - _dst: PhantomData, - } - - #[derive(Copy, Clone)] - 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. - // - // For slice DSTs, destination metadata is an affine function - // of source metadata: - // - // dst_meta = offset_delta_elems + src_meta * elem_multiple - // - // `elem_multiple` scales the destination's trailing element - // size to the source's. `offset_delta_elems` selects a - // destination size equal to the source's zero-element size. - // We then prove that the two complete rounded size formulas - // produce the same sequence for all metadata. This is - // necessary because a packed outer DST can retain a nested - // field's rounding operation; comparing only physical - // trailing-slice offsets is not sufficient. - /// 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 { - /// Given a nonzero `dst.elem_size` that exactly divides - /// `src.elem_size`, produces `true` only when the following - /// metadata map preserves the complete object size for every - /// source element count `src_meta`: - /// - /// ```text - /// dst_meta = dst_base + src_meta * (src.elem_size / dst.elem_size) - /// ``` - /// - /// This recognizes sufficient conditions for equality. A - /// `false` result also includes equivalent size sequences - /// that this test cannot recognize, or an adjustment whose - /// normalized components cannot be represented in `usize`. - const fn size_sequences_match( - src: TrailingSliceLayout, - dst: TrailingSliceLayout, - dst_base: usize, - ) -> bool { - let base_bytes = match dst_base.checked_mul(dst.elem_size) { - Some(bytes) => bytes, - None => return false, - }; - let shifted_dst = match dst.advance(base_bytes, src.elem_size) { - Some(layout) => layout, - None => return false, - }; - src.has_same_size_sequence(shifted_dst) - } - - /// Given the complete layouts of `Src` and `Dst`, produces - /// parameters for a cast that preserves object size. - /// - /// A `Some` result establishes that `Src`'s alignment is at - /// least `Dst`'s and that the metadata map preserves size for - /// every valid source metadata value. - /// - /// Supports casts between sized types of equal size, from a - /// sized type to a slice DST, and between slice DSTs. A slice - /// DST destination must have nonzero-sized elements; between - /// slice DSTs, its element size must exactly divide the - /// source's nonzero element size. - /// - /// Produces `None` for unsupported casts or when this method - /// cannot establish a size-preserving metadata map. Rejection - /// does not establish that no such map exists. - const fn try_compute( - src_layout: &DstLayout, - dst_layout: &DstLayout, - ) -> Option> { - if src_layout.align.get() < dst_layout.align.get() { - return None; - } - - let inner = match (src_layout.size_info, dst_layout.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(_)) => { - let dst_meta = match dst_layout.metadata_for_exact_size(src_size) { - Some(meta) => meta, - None => 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 dst_elem_size = if let Some(e) = NonZeroUsize::new(dst.elem_size) { - e - } else { - return None; - }; - - if src.elem_size < dst.elem_size { - return None; - } - - let (elem_multiple, described_src_elem_size) = - super::max_elems_for_bytes(src.elem_size, dst_elem_size); - if described_src_elem_size != src.elem_size { - return None; - } - - // Prefer an exact destination-element adjustment - // between `src.size_offset()` and - // `dst.size_offset()`. This preserves offset-based - // cast compatibility and metadata selection when - // several lengths have the same rounded size. It - // also avoids choosing a length whose subsequent - // padding sequence differs from the source's. - let exact_offset_delta_elems = - match src.size_offset().checked_sub(dst.size_offset()) { - Some(delta) => { - let (elems, described_delta) = - super::max_elems_for_bytes(delta, dst_elem_size); - if described_delta == delta { - Some(elems) - } else { - None - } - } - None => None, - }; - - let offset_delta_elems = match exact_offset_delta_elems { - Some(elems) if Self::size_sequences_match(src, dst, elems) => elems, - _ => { - let src_zero_size = match src.size_for_elems(0) { - Some(size) => size, - None => return None, - }; - let elems = - match dst_layout.metadata_for_exact_size(src_zero_size) { - Some(elems) => elems, - None => return None, - }; - if !Self::size_sequences_match(src, dst, elems) { - return None; - } - elems - } - }; - - CastParamsInner::UnsizedToUnsized { - // SAFETY: `size_sequences_match` proves that - // this affine metadata map preserves size. - offset_delta_elems, - // SAFETY: We checked above that this is an exact - // ratio of source to destination element size. - elem_multiple, - } - } - _ => return None, - }; - - // SAFETY: We checked above that `src.align >= dst.align`. - Some(CastParams { inner, _src: PhantomData, _dst: PhantomData }) - } - } - - impl CastParams { - /// Produces `Dst` metadata describing an object of the same - /// size as the `Src` described by `src_meta`. - /// - /// # Safety - /// - /// `src_meta` describes a `Src` whose size is no larger than - /// `isize::MAX`. - #[inline(always)] - unsafe fn cast_metadata( - self, - src_meta: Src::PointerMetadata, - ) -> Dst::PointerMetadata { - let dst_meta = match self.inner { - CastParamsInner::UnsizedToUnsized { offset_delta_elems, elem_multiple } => { - let src_meta = src_meta.to_elem_count(); - // SAFETY: `self` witnesses that this affine map - // makes `Src` and `Dst`'s complete rounded size - // formulas equal for every source metadata value. - // Interpret the arithmetic in this proof over the - // mathematical nonnegative integers. Since the - // caller promises that `src_meta` is valid `Src` - // metadata, the source object size `src_size` it - // describes is at most `isize::MAX`. Let - // `src_elem_size` and `dst_elem_size` be the - // respective trailing-slice element sizes. Then: - // - // src_meta * src_elem_size - // <= src_size <= isize::MAX. - // - // Since `elem_multiple` is the exact ratio - // `src_elem_size / dst_elem_size` and - // `dst_elem_size >= 1`, - // - // src_meta * elem_multiple - // <= src_meta * src_elem_size - // <= isize::MAX <= usize::MAX. - // - // Thus `scaled_elems = src_meta * elem_multiple` - // is representable, and its byte contribution is - // `scaled_elems * dst_elem_size = src_meta * - // src_elem_size`. Let `base_bytes = - // offset_delta_elems * dst_elem_size`. - // `size_sequences_match` compares the source - // formula with the destination formula advanced by - // `base_bytes`. The latter's unrounded term - // contains `base_bytes + src_meta * src_elem_size`, - // and its complete size is at least that large - // (formally checked by Kani in - // `prove_size_formula_bounds_size_offset`). - // Sequence equality therefore gives, over the - // mathematical nonnegative integers, - // - // base_bytes + src_meta * src_elem_size - // = (offset_delta_elems + scaled_elems) - // * dst_elem_size - // <= src_size. - // - // Since `dst_elem_size >= 1`, the metadata sum - // `offset_delta_elems + scaled_elems` is also at - // most `src_size`, and thus at most `isize::MAX <= - // usize::MAX`. The sum is therefore representable, - // and the returned metadata describes a `Dst` of - // the same size. - // SAFETY: The bounds above establish the helper's - // complete no-overflow precondition. - unsafe { - super::add_scaled_metadata( - offset_delta_elems, - src_meta, - elem_multiple, - ) - } - } - CastParamsInner::SizedToUnsized { dst_meta } => dst_meta, - CastParamsInner::SizedToSized => 0, - }; - Dst::PointerMetadata::from_elem_count(dst_meta) - } - } - - trait Params { - const CAST_PARAMS: CastParams; - } - - impl Params for Dst - where - Src: KnownLayout + ?Sized, - Dst: KnownLayout + ?Sized, - { - 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" - ), - }; - } - let src_meta = ::pointer_to_metadata(src.as_ptr()); - let params = >::CAST_PARAMS; + let params = CastParams::::CAST_PARAMS; // SAFETY: `src: PtrInner` guarantees that `src`'s referent is zero // bytes or lives in a single allocation, which means that it is no @@ -391,4 +377,4 @@ where ::raw_from_ptr_len(src.as_non_null().cast(), dst_meta).as_ptr() } -} \ No newline at end of file +}