Skip to content

Commit a6c1b4c

Browse files
joshlfjswrenn
andcommitted
[macros] Support shrinking reference transmutes
In `transmute_ref!` and `transmute_mut!`, support an `#![allow(shrink)]` attribute which is invoked as follows: transmute_ref!(#![allow(shrink)] src); When this attribute is provided, the macros will permit shrinking transmutes, in which the destination value may be smaller than the source value. Makes progress on #1817 Co-authored-by: Jack Wrenn <[email protected]> gherrit-pr-id: I10874e2bc703fb6b7fcdea050b8971de869a850a
1 parent 3fc3abf commit a6c1b4c

File tree

4 files changed

+214
-79
lines changed

4 files changed

+214
-79
lines changed

src/layout.rs

Lines changed: 63 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -623,11 +623,15 @@ mod cast_from_raw {
623623
/// [cast_from_raw]: crate::pointer::SizeFrom::cast_from_raw
624624
//
625625
// FIXME(#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<Src, Dst, const ALLOW_SHRINK: bool>(
627+
src: PtrInner<'_, Src>,
628+
) -> PtrInner<'_, Dst>
627629
where
628630
Src: KnownLayout<PointerMetadata = usize> + ?Sized,
629631
Dst: KnownLayout<PointerMetadata = usize> + ?Sized,
630632
{
633+
// TODO: Update this comment.
634+
//
631635
// At compile time (specifically, post-monomorphization time), we need
632636
// to compute two things:
633637
// - Whether, given *any* `*Src`, it is possible to construct a `*Dst`
@@ -694,12 +698,19 @@ mod cast_from_raw {
694698
/// `Src`'s alignment must not be smaller than `Dst`'s alignment.
695699
#[derive(Copy, Clone)]
696700
struct CastParams {
697-
offset_delta_elems: usize,
698-
elem_multiple: usize,
701+
// `offset_delta / dst.elem_size = offset_delta_elems_num / denom`
702+
offset_delta_elems_num: usize,
703+
// `src.elem_size / dst.elem_size = elem_multiple_num / denom`
704+
elem_multiple_num: usize,
705+
denom: NonZeroUsize,
699706
}
700707

701708
impl CastParams {
702-
const fn try_compute(src: &DstLayout, dst: &DstLayout) -> Option<CastParams> {
709+
const fn try_compute(
710+
src: &DstLayout,
711+
dst: &DstLayout,
712+
allow_shrink: bool,
713+
) -> Option<CastParams> {
703714
if src.align.get() < dst.align.get() {
704715
return None;
705716
}
@@ -724,33 +735,44 @@ mod cast_from_raw {
724735
return None;
725736
};
726737

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();
738+
const fn gcd(a: usize, b: usize) -> usize {
739+
if a == 0 {
740+
b
741+
} else {
742+
#[allow(clippy::arithmetic_side_effects)]
743+
gcd(b % a, a)
744+
}
745+
}
730746

731-
// PANICS: `dst_elem_size: NonZeroUsize`, so this won't div by zero.
732-
#[allow(clippy::arithmetic_side_effects)]
733-
let elem_remainder = src.elem_size % dst_elem_size.get();
747+
let gcd = gcd(gcd(offset_delta, src.elem_size), dst_elem_size.get());
734748

735-
if delta_mod_other_elem != 0 || src.elem_size < dst.elem_size || elem_remainder != 0
736-
{
737-
return None;
738-
}
749+
// PANICS: `gcd` is non-zero.
750+
#[allow(clippy::arithmetic_side_effects)]
751+
let offset_delta_elems_num = offset_delta / gcd;
739752

740-
// PANICS: `dst_elem_size: NonZeroUsize`, so this won't div by zero.
753+
// PANICS: `gcd` is non-zero.
741754
#[allow(clippy::arithmetic_side_effects)]
742-
let offset_delta_elems = offset_delta / dst_elem_size.get();
755+
let elem_multiple_num = src.elem_size / gcd;
743756

744-
// PANICS: `dst_elem_size: NonZeroUsize`, so this won't div by zero.
757+
// PANICS: `dst_elem_size` is non-zero, and `gcd` is no greater
758+
// than it by construction. Thus, this should be at least 1.
745759
#[allow(clippy::arithmetic_side_effects)]
746-
let elem_multiple = src.elem_size / dst_elem_size.get();
760+
let denom = match NonZeroUsize::new(dst_elem_size.get() / gcd) {
761+
Some(d) => d,
762+
None => const_panic!("CastParams::try_compute: denom should be non-zero"),
763+
};
764+
765+
if denom.get() != 1 && !allow_shrink {
766+
return None;
767+
}
747768

748769
// SAFETY: We checked above that `src.align >= dst.align`.
749770
Some(CastParams {
750771
// SAFETY: We checked above that this is an exact ratio.
751-
offset_delta_elems,
772+
offset_delta_elems_num,
752773
// SAFETY: We checked above that this is an exact ratio.
753-
elem_multiple,
774+
elem_multiple_num,
775+
denom,
754776
})
755777
}
756778

@@ -759,12 +781,17 @@ mod cast_from_raw {
759781
/// `src_meta` describes a `Src` whose size is no larger than
760782
/// `isize::MAX`.
761783
///
762-
/// The returned metadata describes a `Dst` of the same size as the
763-
/// original `Src`.
784+
/// If `self.denom == 1`, then the returned metadata describes a
785+
/// `Dst` of the same size as the original `Src`. Otherwise, the
786+
/// returned metadata describes a `Dst` whose size is no greater
787+
/// than the size of the original `Src`.
764788
unsafe fn cast_metadata(self, src_meta: usize) -> usize {
765789
#[allow(unused)]
766790
use crate::util::polyfills::*;
767791

792+
// TODO: Update this safety comment. Make sure that even if
793+
// `denom > 1`, these arithmetic operations will not overflow.
794+
//
768795
// SAFETY: `self` is a witness that the following equation
769796
// holds:
770797
//
@@ -774,24 +801,25 @@ mod cast_from_raw {
774801
// metadata, this math will not overflow, and the returned value
775802
// will describe a `Dst` of the same size.
776803
#[allow(unstable_name_collisions)]
777-
unsafe {
778-
self.offset_delta_elems
779-
.unchecked_add(src_meta.unchecked_mul(self.elem_multiple))
780-
}
804+
let num = unsafe {
805+
self.offset_delta_elems_num
806+
.unchecked_add(src_meta.unchecked_mul(self.elem_multiple_num))
807+
};
808+
num / self.denom
781809
}
782810
}
783811

784-
trait Params<Src: ?Sized> {
812+
trait Params<Src: ?Sized, const ALLOW_SHRINK: bool> {
785813
const CAST_PARAMS: CastParams;
786814
}
787815

788-
impl<Src, Dst> Params<Src> for Dst
816+
impl<Src, Dst, const ALLOW_SHRINK: bool> Params<Src, ALLOW_SHRINK> for Dst
789817
where
790818
Src: KnownLayout + ?Sized,
791819
Dst: KnownLayout<PointerMetadata = usize> + ?Sized,
792820
{
793821
const CAST_PARAMS: CastParams =
794-
match CastParams::try_compute(&Src::LAYOUT, &Dst::LAYOUT) {
822+
match CastParams::try_compute(&Src::LAYOUT, &Dst::LAYOUT, ALLOW_SHRINK) {
795823
Some(params) => params,
796824
None => const_panic!(
797825
"cannot `transmute_ref!` or `transmute_mut!` between incompatible types"
@@ -800,7 +828,7 @@ mod cast_from_raw {
800828
}
801829

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

805833
// SAFETY: `src: PtrInner`, and so by invariant on `PtrInner`, `src`'s
806834
// referent is no larger than `isize::MAX`.
@@ -809,11 +837,11 @@ mod cast_from_raw {
809837
let dst = <Dst as KnownLayout>::raw_from_ptr_len(src.as_non_null().cast(), dst_meta);
810838

811839
// SAFETY: By post-condition on `params.cast_metadata`, `dst` addresses
812-
// the same number of bytes as `src`. Since `src: PtrInner`, `src` has
813-
// provenance for its entire referent, which lives inside of a single
814-
// allocation. Since `dst` has the same address as `src` and was
815-
// constructed using provenance-preserving operations, it addresses a
816-
// subset of those bytes, and has provenance for those bytes.
840+
// no more bytes than `src`. Since `src: PtrInner`, `src` has provenance
841+
// for its entire referent, which lives inside of a single allocation.
842+
// Since `dst` has the same address as `src` and was constructed using
843+
// provenance-preserving operations, it addresses a subset of those
844+
// bytes, and has provenance for those bytes.
817845
unsafe { PtrInner::new(dst) }
818846
}
819847
}

src/macros.rs

Lines changed: 100 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,24 @@ macro_rules! transmute {
227227
/// assert_eq!(size_of_val(src), size_of_val(dst));
228228
/// ```
229229
///
230+
/// ## `#![allow(shrink)]`
231+
///
232+
/// If `#![allow(shrink)]` is provided, `transmute_ref!` additionally supports
233+
/// transmutations that shrink the size of the referent; e.g.:
234+
///
235+
/// ```
236+
/// # use zerocopy::transmute_ref;
237+
/// # use core::mem::size_of_val; // Not in the prelude on our MSRV
238+
/// let src: &[[u8; 3]] = &[[0, 1, 2], [3, 4, 5], [6, 7, 8]][..];
239+
/// let dst: &[[u8; 2]] = transmute_ref!(#![allow(shrink)] src);
240+
///
241+
/// assert_eq!(src.len(), 3);
242+
/// assert_eq!(dst.len(), 4);
243+
/// assert_eq!(size_of_val(src), 9);
244+
/// assert_eq!(size_of_val(dst), 8);
245+
/// assert_eq!(dst, [[0, 1], [2, 3], [4, 5], [6, 7]]);
246+
/// ```
247+
///
230248
/// # Errors
231249
///
232250
/// Violations of the alignment and size compatibility checks are detected
@@ -313,7 +331,18 @@ macro_rules! transmute {
313331
/// `Dst: Sized`.
314332
#[macro_export]
315333
macro_rules! transmute_ref {
316-
($e:expr) => {{
334+
(#![allow(shrink)] $e:expr) => {
335+
$crate::__transmute_ref_inner!(true, $e)
336+
};
337+
($e:expr) => {
338+
$crate::__transmute_ref_inner!(false, $e)
339+
};
340+
}
341+
342+
#[macro_export]
343+
#[doc(hidden)]
344+
macro_rules! __transmute_ref_inner {
345+
($allow_shrink:literal, $e:expr) => {{
317346
// NOTE: This must be a macro (rather than a function with trait bounds)
318347
// because there's no way, in a generic context, to enforce that two
319348
// types have the same size or alignment.
@@ -352,10 +381,10 @@ macro_rules! transmute_ref {
352381
// - `Src: IntoBytes + Immutable`
353382
// - `Dst: FromBytes + Immutable`
354383
unsafe {
355-
t.transmute_ref()
384+
t.transmute_ref::<$allow_shrink>()
356385
}
357386
}
358-
}}
387+
}};
359388
}
360389

361390
/// Safely transmutes a mutable reference of one type to a mutable reference of
@@ -401,6 +430,29 @@ macro_rules! transmute_ref {
401430
/// assert_eq!(size_of_val(src), dst_size);
402431
/// ```
403432
///
433+
/// ## `#![allow(shrink)]`
434+
///
435+
/// If `#![allow(shrink)]` is provided, `transmute_mut!` additionally supports
436+
/// transmutations that shrink the size of the referent; e.g.:
437+
///
438+
/// ```
439+
/// # use zerocopy::transmute_mut;
440+
/// # use core::mem::size_of_val; // Not in the prelude on our MSRV
441+
/// let src: &mut [[u8; 3]] = &mut [[0, 1, 2], [3, 4, 5], [6, 7, 8]][..];
442+
/// let dst: &mut [[u8; 2]] = transmute_mut!(#![allow(shrink)] src);
443+
///
444+
///
445+
/// let dst_len = dst.len();
446+
/// let dst_size = size_of_val(dst);
447+
/// assert_eq!(dst, [[0, 1], [2, 3], [4, 5], [6, 7]]);
448+
///
449+
/// assert_eq!(src.len(), 3);
450+
/// assert_eq!(dst_len, 4);
451+
///
452+
/// assert_eq!(size_of_val(src), 9);
453+
/// assert_eq!(dst_size, 8);
454+
/// ```
455+
///
404456
/// # Errors
405457
///
406458
/// Violations of the alignment and size compatibility checks are detected
@@ -489,7 +541,18 @@ macro_rules! transmute_ref {
489541
/// ```
490542
#[macro_export]
491543
macro_rules! transmute_mut {
492-
($e:expr) => {{
544+
(#![allow(shrink)] $e:expr) => {
545+
$crate::__transmute_mut_inner!(true, $e)
546+
};
547+
($e:expr) => {
548+
$crate::__transmute_mut_inner!(false, $e)
549+
};
550+
}
551+
552+
#[doc(hidden)]
553+
#[macro_export]
554+
macro_rules! __transmute_mut_inner {
555+
($allow_shrink:literal, $e:expr) => {{
493556
// NOTE: This must be a macro (rather than a function with trait bounds)
494557
// because, for backwards-compatibility on v0.8.x, we use the autoref
495558
// specialization trick to dispatch to different `transmute_mut`
@@ -503,7 +566,7 @@ macro_rules! transmute_mut {
503566
#[allow(unused)]
504567
use $crate::util::macro_util::TransmuteMutDst as _;
505568
let t = $crate::util::macro_util::Wrap::new(e);
506-
t.transmute_mut()
569+
t.transmute_mut::<$allow_shrink>()
507570
}}
508571
}
509572

@@ -1251,6 +1314,11 @@ mod tests {
12511314
let slice_of_u16s: &[U16] = <[U16]>::ref_from_bytes(&[0, 1, 2, 3, 4, 5][..]).unwrap();
12521315
assert_eq!(x, slice_of_u16s);
12531316

1317+
// Test that transmuting from a larger sized type to a smaller sized
1318+
// type works.
1319+
let x: &u8 = transmute_ref!(#![allow(shrink)] &0u16);
1320+
assert_eq!(*x, 0);
1321+
12541322
// Test that transmuting from a type with larger trailing slice offset
12551323
// and larger trailing slice element works.
12561324
let bytes = &[0, 1, 2, 3, 4, 5, 6, 7][..];
@@ -1259,6 +1327,15 @@ mod tests {
12591327
let x: &SliceDst<U16, u8> = transmute_ref!(slice_dst_big);
12601328
assert_eq!(x, slice_dst_small);
12611329

1330+
let bytes = &[0, 1, 2, 3, 4, 5, 6, 7][..];
1331+
let slice_dst_big = SliceDst::<[u8; 4], [u8; 4]>::ref_from_bytes(bytes).unwrap();
1332+
let slice_dst_small = SliceDst::<[u8; 3], [u8; 3]>::ref_from_bytes(&bytes[..6]).unwrap();
1333+
let x: &SliceDst<[u8; 3], [u8; 3]> = transmute_ref!(
1334+
#![allow(shrink)]
1335+
slice_dst_big
1336+
);
1337+
assert_eq!(x, slice_dst_small);
1338+
12621339
// Test that it's legal to transmute a reference while shrinking the
12631340
// lifetime (note that `X` has the lifetime `'static`).
12641341
let x: &[u8; 8] = transmute_ref!(X);
@@ -1439,6 +1516,14 @@ mod tests {
14391516
let x: &mut [i16] = transmute_mut!(array_of_u16s);
14401517
assert_eq!(x, array_of_i16s);
14411518

1519+
// Test that transmuting from a larger sized type to a smaller sized
1520+
// type works.
1521+
let mut large: [u8; 2] = [1, 1];
1522+
let x: &mut u8 = transmute_mut!(#![allow(shrink)] &mut large);
1523+
assert_eq!(*x, 1);
1524+
*x = 0;
1525+
assert_eq!(large, [0, 1]);
1526+
14421527
// Test that transmuting from a type with larger trailing slice offset
14431528
// and larger trailing slice element works.
14441529
let mut bytes = [0, 1, 2, 3, 4, 5, 6, 7];
@@ -1447,6 +1532,16 @@ mod tests {
14471532
let slice_dst_small = SliceDst::<U16, u8>::mut_from_bytes(&mut bytes[..]).unwrap();
14481533
let x: &mut SliceDst<U16, u8> = transmute_mut!(slice_dst_big);
14491534
assert_eq!(x, slice_dst_small);
1535+
1536+
let mut bytes = [0, 1, 2, 3, 4, 5, 6, 7];
1537+
let slice_dst_big = SliceDst::<[u8; 4], [u8; 4]>::mut_from_bytes(&mut bytes[..]).unwrap();
1538+
let mut bytes = [0, 1, 2, 3, 4, 5];
1539+
let slice_dst_small = SliceDst::<[u8; 3], [u8; 3]>::mut_from_bytes(&mut bytes[..]).unwrap();
1540+
let x: &mut SliceDst<[u8; 3], [u8; 3]> = transmute_mut!(
1541+
#![allow(shrink)]
1542+
slice_dst_big
1543+
);
1544+
assert_eq!(x, slice_dst_small);
14501545
}
14511546

14521547
#[test]

0 commit comments

Comments
 (0)