Skip to content

Commit ad45aa0

Browse files
committed
[WIP] transmute_from!(@allow_shrink)
gherrit-pr-id: I10874e2bc703fb6b7fcdea050b8971de869a850a
1 parent bafb400 commit ad45aa0

File tree

4 files changed

+128
-65
lines changed

4 files changed

+128
-65
lines changed

src/layout.rs

+64-27
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,9 @@ mod cast_from_raw {
623623
/// [cast_from_raw]: crate::pointer::SizeCompat::cast_from_raw
624624
//
625625
// TODO(#1817): Support Sized->Unsized and Unsized->Sized casts
626-
pub(crate) fn cast_from_raw<Src, Dst>(src: PtrInner<'_, Src>) -> PtrInner<'_, Dst>
626+
pub(crate) fn cast_from_raw<const ALLOW_SHRINK: bool, Src, Dst>(
627+
src: PtrInner<'_, Src>,
628+
) -> PtrInner<'_, Dst>
627629
where
628630
Src: KnownLayout<PointerMetadata = usize> + ?Sized,
629631
Dst: KnownLayout<PointerMetadata = usize> + ?Sized,
@@ -694,12 +696,19 @@ mod cast_from_raw {
694696
/// `Src`'s alignment must not be smaller than `Dst`'s alignment.
695697
#[derive(Copy, Clone)]
696698
struct CastParams {
697-
offset_delta_elems: usize,
698-
elem_multiple: usize,
699+
// `offset_delta / dst.elem_size = offset_delta_elems_num / denom`
700+
offset_delta_elems_num: usize,
701+
// `src.elem_size / dst.elem_size = elem_multiple_num / denom`
702+
elem_multiple_num: usize,
703+
denom: NonZeroUsize,
699704
}
700705

701706
impl CastParams {
702-
const fn try_compute(src: &DstLayout, dst: &DstLayout) -> Option<CastParams> {
707+
const fn try_compute(
708+
src: &DstLayout,
709+
dst: &DstLayout,
710+
allow_shrink: bool,
711+
) -> Option<CastParams> {
703712
if src.align.get() < dst.align.get() {
704713
return None;
705714
}
@@ -724,33 +733,60 @@ mod cast_from_raw {
724733
return None;
725734
};
726735

727-
// PANICS: `dst_elem_size: NonZeroUsize`, so this won't div by zero.
728-
#[allow(clippy::arithmetic_side_effects)]
729-
let delta_mod_other_elem = offset_delta % dst_elem_size.get();
736+
const fn gcd(a: usize, b: usize) -> usize {
737+
if a == 0 {
738+
b
739+
} else {
740+
#[allow(clippy::arithmetic_side_effects)]
741+
gcd(b % a, a)
742+
}
743+
}
730744

731-
// PANICS: `dst_elem_size: NonZeroUsize`, so this won't div by zero.
745+
let gcd = gcd(gcd(offset_delta, src.elem_size), dst_elem_size.get());
746+
// PANICS: `dst_elem_size.get()` is non-zero, and so `denom`
747+
// will be non-zero.
748+
749+
#[allow(clippy::arithmetic_side_effects)]
750+
let offset_delta_elems_num = offset_delta / gcd;
732751
#[allow(clippy::arithmetic_side_effects)]
733-
let elem_remainder = src.elem_size % dst_elem_size.get();
752+
let elem_multiple_num = src.elem_size / gcd;
753+
// PANICS: `dst_elem_size` is non-zero, and `gcd` is no greater
754+
// than it by construction. Thus, this should be at least 1.
755+
let denom = NonZeroUsize::new(dst_elem_size.get() / gcd)
756+
.expect("CastParams::try_compute: denom should be non-zero");
734757

735-
if delta_mod_other_elem != 0 || src.elem_size < dst.elem_size || elem_remainder != 0
736-
{
758+
if denom.get() != 1 && !allow_shrink {
737759
return None;
738760
}
739761

740-
// PANICS: `dst_elem_size: NonZeroUsize`, so this won't div by zero.
741-
#[allow(clippy::arithmetic_side_effects)]
742-
let offset_delta_elems = offset_delta / dst_elem_size.get();
762+
// // PANICS: `dst_elem_size: NonZeroUsize`, so this won't div by zero.
763+
// #[allow(clippy::arithmetic_side_effects)]
764+
// let delta_mod_other_elem = offset_delta % dst_elem_size.get();
743765

744-
// PANICS: `dst_elem_size: NonZeroUsize`, so this won't div by zero.
745-
#[allow(clippy::arithmetic_side_effects)]
746-
let elem_multiple = src.elem_size / dst_elem_size.get();
766+
// // PANICS: `dst_elem_size: NonZeroUsize`, so this won't div by zero.
767+
// #[allow(clippy::arithmetic_side_effects)]
768+
// let elem_remainder = src.elem_size % dst_elem_size.get();
769+
770+
// if delta_mod_other_elem != 0 || src.elem_size < dst.elem_size || elem_remainder != 0
771+
// {
772+
// return None;
773+
// }
774+
775+
// // PANICS: `dst_elem_size: NonZeroUsize`, so this won't div by zero.
776+
// #[allow(clippy::arithmetic_side_effects)]
777+
// let offset_delta_elems = offset_delta / dst_elem_size.get();
778+
779+
// // PANICS: `dst_elem_size: NonZeroUsize`, so this won't div by zero.
780+
// #[allow(clippy::arithmetic_side_effects)]
781+
// let elem_multiple = src.elem_size / dst_elem_size.get();
747782

748783
// SAFETY: We checked above that `src.align >= dst.align`.
749784
Some(CastParams {
750785
// SAFETY: We checked above that this is an exact ratio.
751-
offset_delta_elems,
786+
offset_delta_elems_num,
752787
// SAFETY: We checked above that this is an exact ratio.
753-
elem_multiple,
788+
elem_multiple_num,
789+
denom,
754790
})
755791
}
756792

@@ -774,24 +810,25 @@ mod cast_from_raw {
774810
// metadata, this math will not overflow, and the returned value
775811
// will describe a `Dst` of the same size.
776812
#[allow(unstable_name_collisions)]
777-
unsafe {
778-
self.offset_delta_elems
779-
.unchecked_add(src_meta.unchecked_mul(self.elem_multiple))
780-
}
813+
let num = unsafe {
814+
self.offset_delta_elems_num
815+
.unchecked_add(src_meta.unchecked_mul(self.elem_multiple_num))
816+
};
817+
num / self.denom.get()
781818
}
782819
}
783820

784-
trait Params<Src: ?Sized> {
821+
trait Params<const ALLOW_SHRINK: bool, Src: ?Sized> {
785822
const CAST_PARAMS: CastParams;
786823
}
787824

788-
impl<Src, Dst> Params<Src> for Dst
825+
impl<const ALLOW_SHRINK: bool, Src, Dst> Params<ALLOW_SHRINK, Src> for Dst
789826
where
790827
Src: KnownLayout + ?Sized,
791828
Dst: KnownLayout<PointerMetadata = usize> + ?Sized,
792829
{
793830
const CAST_PARAMS: CastParams =
794-
match CastParams::try_compute(&Src::LAYOUT, &Dst::LAYOUT) {
831+
match CastParams::try_compute(&Src::LAYOUT, &Dst::LAYOUT, ALLOW_SHRINK) {
795832
Some(params) => params,
796833
None => const_panic!(
797834
"cannot `transmute_ref!` or `transmute_mut!` between incompatible types"
@@ -800,7 +837,7 @@ mod cast_from_raw {
800837
}
801838

802839
let src_meta = <Src as KnownLayout>::pointer_to_metadata(src.as_non_null().as_ptr());
803-
let params = <Dst as Params<Src>>::CAST_PARAMS;
840+
let params = <Dst as Params<ALLOW_SHRINK, Src>>::CAST_PARAMS;
804841

805842
// SAFETY: `src: PtrInner`, and so by invariant on `PtrInner`, `src`'s
806843
// referent is no larger than `isize::MAX`.

src/macros.rs

+27-5
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,18 @@ macro_rules! transmute {
224224
/// `Dst: Sized`.
225225
#[macro_export]
226226
macro_rules! transmute_ref {
227-
($e:expr) => {{
227+
($e:expr) => {
228+
$crate::__transmute_ref_inner!(false, $e)
229+
};
230+
(#[allow(shrink)] $e:expr) => {
231+
$crate::__transmute_ref_inner!(true, $e)
232+
};
233+
}
234+
235+
#[macro_export]
236+
#[doc(hidden)]
237+
macro_rules! __transmute_ref_inner {
238+
($allow_shrink:literal, $e:expr) => {{
228239
// NOTE: This must be a macro (rather than a function with trait bounds)
229240
// because there's no way, in a generic context, to enforce that two
230241
// types have the same size or alignment.
@@ -263,10 +274,10 @@ macro_rules! transmute_ref {
263274
// - `Src: IntoBytes + Immutable`
264275
// - `Dst: FromBytes + Immutable`
265276
unsafe {
266-
t.transmute_ref()
277+
t.transmute_ref::<$allow_shrink>()
267278
}
268279
}
269-
}}
280+
}};
270281
}
271282

272283
/// Safely transmutes a mutable reference of one type to a mutable reference of
@@ -400,7 +411,18 @@ macro_rules! transmute_ref {
400411
/// ```
401412
#[macro_export]
402413
macro_rules! transmute_mut {
403-
($e:expr) => {{
414+
($e:expr) => {
415+
$crate::__transmute_mut_inner!(false, $e)
416+
};
417+
(#[allow(shrink)] $e:expr) => {
418+
$crate::__transmute_mut_inner!(true, $e)
419+
};
420+
}
421+
422+
#[doc(hidden)]
423+
#[macro_export]
424+
macro_rules! __transmute_mut_inner {
425+
($allow_shrink:literal, $e:expr) => {{
404426
// NOTE: This must be a macro (rather than a function with trait bounds)
405427
// because, for backwards-compatibility on v0.8.x, we use the autoref
406428
// specialization trick to dispatch to different `transmute_mut`
@@ -414,7 +436,7 @@ macro_rules! transmute_mut {
414436
#[allow(unused)]
415437
use $crate::util::macro_util::TransmuteMutDst as _;
416438
let t = $crate::util::macro_util::Wrap::new(e);
417-
t.transmute_mut()
439+
t.transmute_mut::<$allow_shrink>()
418440
}}
419441
}
420442

src/util/macro_util.rs

+14-10
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,9 @@ impl<'a, Src, Dst> Wrap<&'a Src, &'a Dst> {
713713
/// - `mem::align_of::<Dst>() <= mem::align_of::<Src>()`
714714
#[inline(always)]
715715
#[must_use]
716-
pub const unsafe fn transmute_ref(self) -> &'a Dst {
716+
pub const unsafe fn transmute_ref<const ALLOW_SHRINK: bool>(self) -> &'a Dst {
717+
// TODO: Permit ALLOW_SHRINK = true and do a different static_assert!
718+
// (namely, size_of::<Dst>() <= size_of::<Src>()).
717719
static_assert!(Src, Dst => mem::size_of::<Dst>() == mem::size_of::<Src>());
718720
static_assert!(Src, Dst => mem::align_of::<Dst>() <= mem::align_of::<Src>());
719721

@@ -750,11 +752,13 @@ impl<'a, Src, Dst> Wrap<&'a mut Src, &'a mut Dst> {
750752
/// - `mem::align_of::<Dst>() <= mem::align_of::<Src>()`
751753
#[inline(always)]
752754
#[must_use]
753-
pub fn transmute_mut(self) -> &'a mut Dst
755+
pub fn transmute_mut<const ALLOW_SHRINK: bool>(self) -> &'a mut Dst
754756
where
755757
Src: FromBytes + IntoBytes,
756758
Dst: FromBytes + IntoBytes,
757759
{
760+
// TODO: Permit ALLOW_SHRINK = true and do a different static_assert!
761+
// (namely, size_of::<Dst>() <= size_of::<Src>()).
758762
static_assert!(Src, Dst => mem::size_of::<Dst>() == mem::size_of::<Src>());
759763
static_assert!(Src, Dst => mem::align_of::<Dst>() <= mem::align_of::<Src>());
760764

@@ -777,7 +781,7 @@ pub trait TransmuteRefDst<'a> {
777781
type Dst: ?Sized;
778782

779783
#[must_use]
780-
fn transmute_ref(self) -> &'a Self::Dst;
784+
fn transmute_ref<const ALLOW_SHRINK: bool>(self) -> &'a Self::Dst;
781785
}
782786

783787
impl<'a, Src: ?Sized, Dst: ?Sized> TransmuteRefDst<'a> for Wrap<&'a Src, &'a Dst>
@@ -788,18 +792,18 @@ where
788792
type Dst = Dst;
789793

790794
#[inline(always)]
791-
fn transmute_ref(self) -> &'a Dst {
795+
fn transmute_ref<const ALLOW_SHRINK: bool>(self) -> &'a Dst {
792796
static_assert!(Src: ?Sized + KnownLayout, Dst: ?Sized + KnownLayout => {
793797
Src::LAYOUT.align.get() >= Dst::LAYOUT.align.get()
794798
}, "cannot transmute reference when destination type has higher alignment than source type");
795799

796800
// SAFETY: We only use `S` as `S<Src>` and `D` as `D<Dst>`.
797801
unsafe {
798-
unsafe_with_size_compat!(<S<Src>, D<Dst>> {
802+
unsafe_with_size_compat!(<S<Src>, D<ALLOW_SHRINK, Dst>>, {
799803
let ptr = Ptr::from_ref(self.0)
800804
.transmute::<S<Src>, invariant::Valid, BecauseImmutable>()
801805
.recall_validity::<invariant::Initialized, _>()
802-
.transmute::<D<Dst>, invariant::Initialized, (crate::pointer::BecauseMutationCompatible, _)>()
806+
.transmute::<D<{ALLOW_SHRINK}, Dst>, invariant::Initialized, (crate::pointer::BecauseMutationCompatible, _)>()
803807
.recall_validity::<invariant::Valid, _>();
804808

805809
#[allow(unused_unsafe)]
@@ -817,7 +821,7 @@ where
817821
pub trait TransmuteMutDst<'a> {
818822
type Dst: ?Sized;
819823
#[must_use]
820-
fn transmute_mut(self) -> &'a mut Self::Dst;
824+
fn transmute_mut<const ALLOW_SHRINK: bool>(self) -> &'a mut Self::Dst;
821825
}
822826

823827
impl<'a, Src: ?Sized, Dst: ?Sized> TransmuteMutDst<'a> for Wrap<&'a mut Src, &'a mut Dst>
@@ -828,18 +832,18 @@ where
828832
type Dst = Dst;
829833

830834
#[inline(always)]
831-
fn transmute_mut(self) -> &'a mut Dst {
835+
fn transmute_mut<const ALLOW_SHRINK: bool>(self) -> &'a mut Dst {
832836
static_assert!(Src: ?Sized + KnownLayout, Dst: ?Sized + KnownLayout => {
833837
Src::LAYOUT.align.get() >= Dst::LAYOUT.align.get()
834838
}, "cannot transmute reference when destination type has higher alignment than source type");
835839

836840
// SAFETY: We only use `S` as `S<Src>` and `D` as `D<Dst>`.
837841
unsafe {
838-
unsafe_with_size_compat!(<S<Src>, D<Dst>> {
842+
unsafe_with_size_compat!(<S<Src>, D<ALLOW_SHRINK, Dst>>, {
839843
let ptr = Ptr::from_mut(self.0)
840844
.transmute::<S<Src>, invariant::Valid, _>()
841845
.recall_validity::<invariant::Initialized, (_, (_, _))>()
842-
.transmute::<D<Dst>, invariant::Initialized, _>()
846+
.transmute::<D<{ALLOW_SHRINK}, Dst>, invariant::Initialized, _>()
843847
.recall_validity::<invariant::Valid, (_, (_, _))>();
844848

845849
#[allow(unused_unsafe)]

0 commit comments

Comments
 (0)