Skip to content

Commit 24b1350

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 eef2e15 commit 24b1350

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
@@ -219,6 +219,24 @@ macro_rules! transmute {
219219
/// assert_eq!(size_of_val(src), size_of_val(dst));
220220
/// ```
221221
///
222+
/// ## `#![allow(shrink)]`
223+
///
224+
/// If `#![allow(shrink)]` is provided, `transmute_ref!` additionally supports
225+
/// transmutations that shrink the size of the referent; e.g.:
226+
///
227+
/// ```
228+
/// # use zerocopy::transmute_ref;
229+
/// # use core::mem::size_of_val; // Not in the prelude on our MSRV
230+
/// let src: &[[u8; 3]] = &[[0, 1, 2], [3, 4, 5], [6, 7, 8]][..];
231+
/// let dst: &[[u8; 2]] = transmute_ref!(#![allow(shrink)] src);
232+
///
233+
/// assert_eq!(src.len(), 3);
234+
/// assert_eq!(dst.len(), 4);
235+
/// assert_eq!(size_of_val(src), 9);
236+
/// assert_eq!(size_of_val(dst), 8);
237+
/// assert_eq!(dst, [[0, 1], [2, 3], [4, 5], [6, 7]]);
238+
/// ```
239+
///
222240
/// # Errors
223241
///
224242
/// Violations of the alignment and size compatibility checks are detected
@@ -305,7 +323,18 @@ macro_rules! transmute {
305323
/// `Dst: Sized`.
306324
#[macro_export]
307325
macro_rules! transmute_ref {
308-
($e:expr) => {{
326+
(#![allow(shrink)] $e:expr) => {
327+
$crate::__transmute_ref_inner!(true, $e)
328+
};
329+
($e:expr) => {
330+
$crate::__transmute_ref_inner!(false, $e)
331+
};
332+
}
333+
334+
#[macro_export]
335+
#[doc(hidden)]
336+
macro_rules! __transmute_ref_inner {
337+
($allow_shrink:literal, $e:expr) => {{
309338
// NOTE: This must be a macro (rather than a function with trait bounds)
310339
// because there's no way, in a generic context, to enforce that two
311340
// types have the same size or alignment.
@@ -344,10 +373,10 @@ macro_rules! transmute_ref {
344373
// - `Src: IntoBytes + Immutable`
345374
// - `Dst: FromBytes + Immutable`
346375
unsafe {
347-
t.transmute_ref()
376+
t.transmute_ref::<$allow_shrink>()
348377
}
349378
}
350-
}}
379+
}};
351380
}
352381

353382
/// Safely transmutes a mutable reference of one type to a mutable reference of
@@ -393,6 +422,29 @@ macro_rules! transmute_ref {
393422
/// assert_eq!(size_of_val(src), dst_size);
394423
/// ```
395424
///
425+
/// ## `#![allow(shrink)]`
426+
///
427+
/// If `#![allow(shrink)]` is provided, `transmute_mut!` additionally supports
428+
/// transmutations that shrink the size of the referent; e.g.:
429+
///
430+
/// ```
431+
/// # use zerocopy::transmute_mut;
432+
/// # use core::mem::size_of_val; // Not in the prelude on our MSRV
433+
/// let src: &mut [[u8; 3]] = &mut [[0, 1, 2], [3, 4, 5], [6, 7, 8]][..];
434+
/// let dst: &mut [[u8; 2]] = transmute_mut!(#![allow(shrink)] src);
435+
///
436+
///
437+
/// let dst_len = dst.len();
438+
/// let dst_size = size_of_val(dst);
439+
/// assert_eq!(dst, [[0, 1], [2, 3], [4, 5], [6, 7]]);
440+
///
441+
/// assert_eq!(src.len(), 3);
442+
/// assert_eq!(dst_len, 4);
443+
///
444+
/// assert_eq!(size_of_val(src), 9);
445+
/// assert_eq!(dst_size, 8);
446+
/// ```
447+
///
396448
/// # Errors
397449
///
398450
/// Violations of the alignment and size compatibility checks are detected
@@ -481,7 +533,18 @@ macro_rules! transmute_ref {
481533
/// ```
482534
#[macro_export]
483535
macro_rules! transmute_mut {
484-
($e:expr) => {{
536+
(#![allow(shrink)] $e:expr) => {
537+
$crate::__transmute_mut_inner!(true, $e)
538+
};
539+
($e:expr) => {
540+
$crate::__transmute_mut_inner!(false, $e)
541+
};
542+
}
543+
544+
#[doc(hidden)]
545+
#[macro_export]
546+
macro_rules! __transmute_mut_inner {
547+
($allow_shrink:literal, $e:expr) => {{
485548
// NOTE: This must be a macro (rather than a function with trait bounds)
486549
// because, for backwards-compatibility on v0.8.x, we use the autoref
487550
// specialization trick to dispatch to different `transmute_mut`
@@ -495,7 +558,7 @@ macro_rules! transmute_mut {
495558
#[allow(unused)]
496559
use $crate::util::macro_util::TransmuteMutDst as _;
497560
let t = $crate::util::macro_util::Wrap::new(e);
498-
t.transmute_mut()
561+
t.transmute_mut::<$allow_shrink>()
499562
}}
500563
}
501564

@@ -1243,6 +1306,11 @@ mod tests {
12431306
let slice_of_u16s: &[U16] = <[U16]>::ref_from_bytes(&[0, 1, 2, 3, 4, 5][..]).unwrap();
12441307
assert_eq!(x, slice_of_u16s);
12451308

1309+
// Test that transmuting from a larger sized type to a smaller sized
1310+
// type works.
1311+
let x: &u8 = transmute_ref!(#![allow(shrink)] &0u16);
1312+
assert_eq!(*x, 0);
1313+
12461314
// Test that transmuting from a type with larger trailing slice offset
12471315
// and larger trailing slice element works.
12481316
let bytes = &[0, 1, 2, 3, 4, 5, 6, 7][..];
@@ -1251,6 +1319,15 @@ mod tests {
12511319
let x: &SliceDst<U16, u8> = transmute_ref!(slice_dst_big);
12521320
assert_eq!(x, slice_dst_small);
12531321

1322+
let bytes = &[0, 1, 2, 3, 4, 5, 6, 7][..];
1323+
let slice_dst_big = SliceDst::<[u8; 4], [u8; 4]>::ref_from_bytes(bytes).unwrap();
1324+
let slice_dst_small = SliceDst::<[u8; 3], [u8; 3]>::ref_from_bytes(&bytes[..6]).unwrap();
1325+
let x: &SliceDst<[u8; 3], [u8; 3]> = transmute_ref!(
1326+
#![allow(shrink)]
1327+
slice_dst_big
1328+
);
1329+
assert_eq!(x, slice_dst_small);
1330+
12541331
// Test that it's legal to transmute a reference while shrinking the
12551332
// lifetime (note that `X` has the lifetime `'static`).
12561333
let x: &[u8; 8] = transmute_ref!(X);
@@ -1431,6 +1508,14 @@ mod tests {
14311508
let x: &mut [i16] = transmute_mut!(array_of_u16s);
14321509
assert_eq!(x, array_of_i16s);
14331510

1511+
// Test that transmuting from a larger sized type to a smaller sized
1512+
// type works.
1513+
let mut large: [u8; 2] = [1, 1];
1514+
let x: &mut u8 = transmute_mut!(#![allow(shrink)] &mut large);
1515+
assert_eq!(*x, 1);
1516+
*x = 0;
1517+
assert_eq!(large, [0, 1]);
1518+
14341519
// Test that transmuting from a type with larger trailing slice offset
14351520
// and larger trailing slice element works.
14361521
let mut bytes = [0, 1, 2, 3, 4, 5, 6, 7];
@@ -1439,6 +1524,16 @@ mod tests {
14391524
let slice_dst_small = SliceDst::<U16, u8>::mut_from_bytes(&mut bytes[..]).unwrap();
14401525
let x: &mut SliceDst<U16, u8> = transmute_mut!(slice_dst_big);
14411526
assert_eq!(x, slice_dst_small);
1527+
1528+
let mut bytes = [0, 1, 2, 3, 4, 5, 6, 7];
1529+
let slice_dst_big = SliceDst::<[u8; 4], [u8; 4]>::mut_from_bytes(&mut bytes[..]).unwrap();
1530+
let mut bytes = [0, 1, 2, 3, 4, 5];
1531+
let slice_dst_small = SliceDst::<[u8; 3], [u8; 3]>::mut_from_bytes(&mut bytes[..]).unwrap();
1532+
let x: &mut SliceDst<[u8; 3], [u8; 3]> = transmute_mut!(
1533+
#![allow(shrink)]
1534+
slice_dst_big
1535+
);
1536+
assert_eq!(x, slice_dst_small);
14421537
}
14431538

14441539
#[test]

0 commit comments

Comments
 (0)