Skip to content

Commit d494109

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 3d8da5b commit d494109

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
@@ -138,6 +138,24 @@ macro_rules! transmute {
138138
/// assert_eq!(size_of_val(src), size_of_val(dst));
139139
/// ```
140140
///
141+
/// ## `#![allow(shrink)]`
142+
///
143+
/// If `#![allow(shrink)]` is provided, `transmute_ref!` additionally supports
144+
/// transmutations that shrink the size of the referent; e.g.:
145+
///
146+
/// ```
147+
/// # use zerocopy::transmute_ref;
148+
/// # use core::mem::size_of_val; // Not in the prelude on our MSRV
149+
/// let src: &[[u8; 3]] = &[[0, 1, 2], [3, 4, 5], [6, 7, 8]][..];
150+
/// let dst: &[[u8; 2]] = transmute_ref!(#![allow(shrink)] src);
151+
///
152+
/// assert_eq!(src.len(), 3);
153+
/// assert_eq!(dst.len(), 4);
154+
/// assert_eq!(size_of_val(src), 9);
155+
/// assert_eq!(size_of_val(dst), 8);
156+
/// assert_eq!(dst, [[0, 1], [2, 3], [4, 5], [6, 7]]);
157+
/// ```
158+
///
141159
/// # Errors
142160
///
143161
/// Violations of the alignment and size compatibility checks are detected
@@ -224,7 +242,18 @@ macro_rules! transmute {
224242
/// `Dst: Sized`.
225243
#[macro_export]
226244
macro_rules! transmute_ref {
227-
($e:expr) => {{
245+
(#![allow(shrink)] $e:expr) => {
246+
$crate::__transmute_ref_inner!(true, $e)
247+
};
248+
($e:expr) => {
249+
$crate::__transmute_ref_inner!(false, $e)
250+
};
251+
}
252+
253+
#[macro_export]
254+
#[doc(hidden)]
255+
macro_rules! __transmute_ref_inner {
256+
($allow_shrink:literal, $e:expr) => {{
228257
// NOTE: This must be a macro (rather than a function with trait bounds)
229258
// because there's no way, in a generic context, to enforce that two
230259
// types have the same size or alignment.
@@ -263,10 +292,10 @@ macro_rules! transmute_ref {
263292
// - `Src: IntoBytes + Immutable`
264293
// - `Dst: FromBytes + Immutable`
265294
unsafe {
266-
t.transmute_ref()
295+
t.transmute_ref::<$allow_shrink>()
267296
}
268297
}
269-
}}
298+
}};
270299
}
271300

272301
/// Safely transmutes a mutable reference of one type to a mutable reference of
@@ -312,6 +341,29 @@ macro_rules! transmute_ref {
312341
/// assert_eq!(size_of_val(src), dst_size);
313342
/// ```
314343
///
344+
/// ## `#![allow(shrink)]`
345+
///
346+
/// If `#![allow(shrink)]` is provided, `transmute_mut!` additionally supports
347+
/// transmutations that shrink the size of the referent; e.g.:
348+
///
349+
/// ```
350+
/// # use zerocopy::transmute_mut;
351+
/// # use core::mem::size_of_val; // Not in the prelude on our MSRV
352+
/// let src: &mut [[u8; 3]] = &mut [[0, 1, 2], [3, 4, 5], [6, 7, 8]][..];
353+
/// let dst: &mut [[u8; 2]] = transmute_mut!(#![allow(shrink)] src);
354+
///
355+
///
356+
/// let dst_len = dst.len();
357+
/// let dst_size = size_of_val(dst);
358+
/// assert_eq!(dst, [[0, 1], [2, 3], [4, 5], [6, 7]]);
359+
///
360+
/// assert_eq!(src.len(), 3);
361+
/// assert_eq!(dst_len, 4);
362+
///
363+
/// assert_eq!(size_of_val(src), 9);
364+
/// assert_eq!(dst_size, 8);
365+
/// ```
366+
///
315367
/// # Errors
316368
///
317369
/// Violations of the alignment and size compatibility checks are detected
@@ -400,7 +452,18 @@ macro_rules! transmute_ref {
400452
/// ```
401453
#[macro_export]
402454
macro_rules! transmute_mut {
403-
($e:expr) => {{
455+
(#![allow(shrink)] $e:expr) => {
456+
$crate::__transmute_mut_inner!(true, $e)
457+
};
458+
($e:expr) => {
459+
$crate::__transmute_mut_inner!(false, $e)
460+
};
461+
}
462+
463+
#[doc(hidden)]
464+
#[macro_export]
465+
macro_rules! __transmute_mut_inner {
466+
($allow_shrink:literal, $e:expr) => {{
404467
// NOTE: This must be a macro (rather than a function with trait bounds)
405468
// because, for backwards-compatibility on v0.8.x, we use the autoref
406469
// specialization trick to dispatch to different `transmute_mut`
@@ -414,7 +477,7 @@ macro_rules! transmute_mut {
414477
#[allow(unused)]
415478
use $crate::util::macro_util::TransmuteMutDst as _;
416479
let t = $crate::util::macro_util::Wrap::new(e);
417-
t.transmute_mut()
480+
t.transmute_mut::<$allow_shrink>()
418481
}}
419482
}
420483

@@ -1154,6 +1217,11 @@ mod tests {
11541217
let slice_of_u16s: &[U16] = <[U16]>::ref_from_bytes(&[0, 1, 2, 3, 4, 5][..]).unwrap();
11551218
assert_eq!(x, slice_of_u16s);
11561219

1220+
// Test that transmuting from a larger sized type to a smaller sized
1221+
// type works.
1222+
let x: &u8 = transmute_ref!(#![allow(shrink)] &0u16);
1223+
assert_eq!(*x, 0);
1224+
11571225
// Test that transmuting from a type with larger trailing slice offset
11581226
// and larger trailing slice element works.
11591227
let bytes = &[0, 1, 2, 3, 4, 5, 6, 7][..];
@@ -1162,6 +1230,15 @@ mod tests {
11621230
let x: &SliceDst<U16, u8> = transmute_ref!(slice_dst_big);
11631231
assert_eq!(x, slice_dst_small);
11641232

1233+
let bytes = &[0, 1, 2, 3, 4, 5, 6, 7][..];
1234+
let slice_dst_big = SliceDst::<[u8; 4], [u8; 4]>::ref_from_bytes(bytes).unwrap();
1235+
let slice_dst_small = SliceDst::<[u8; 3], [u8; 3]>::ref_from_bytes(&bytes[..6]).unwrap();
1236+
let x: &SliceDst<[u8; 3], [u8; 3]> = transmute_ref!(
1237+
#![allow(shrink)]
1238+
slice_dst_big
1239+
);
1240+
assert_eq!(x, slice_dst_small);
1241+
11651242
// Test that it's legal to transmute a reference while shrinking the
11661243
// lifetime (note that `X` has the lifetime `'static`).
11671244
let x: &[u8; 8] = transmute_ref!(X);
@@ -1342,6 +1419,14 @@ mod tests {
13421419
let x: &mut [i16] = transmute_mut!(array_of_u16s);
13431420
assert_eq!(x, array_of_i16s);
13441421

1422+
// Test that transmuting from a larger sized type to a smaller sized
1423+
// type works.
1424+
let mut large: [u8; 2] = [1, 1];
1425+
let x: &mut u8 = transmute_mut!(#![allow(shrink)] &mut large);
1426+
assert_eq!(*x, 1);
1427+
*x = 0;
1428+
assert_eq!(large, [0, 1]);
1429+
13451430
// Test that transmuting from a type with larger trailing slice offset
13461431
// and larger trailing slice element works.
13471432
let mut bytes = [0, 1, 2, 3, 4, 5, 6, 7];
@@ -1350,6 +1435,16 @@ mod tests {
13501435
let slice_dst_small = SliceDst::<U16, u8>::mut_from_bytes(&mut bytes[..]).unwrap();
13511436
let x: &mut SliceDst<U16, u8> = transmute_mut!(slice_dst_big);
13521437
assert_eq!(x, slice_dst_small);
1438+
1439+
let mut bytes = [0, 1, 2, 3, 4, 5, 6, 7];
1440+
let slice_dst_big = SliceDst::<[u8; 4], [u8; 4]>::mut_from_bytes(&mut bytes[..]).unwrap();
1441+
let mut bytes = [0, 1, 2, 3, 4, 5];
1442+
let slice_dst_small = SliceDst::<[u8; 3], [u8; 3]>::mut_from_bytes(&mut bytes[..]).unwrap();
1443+
let x: &mut SliceDst<[u8; 3], [u8; 3]> = transmute_mut!(
1444+
#![allow(shrink)]
1445+
slice_dst_big
1446+
);
1447+
assert_eq!(x, slice_dst_small);
13531448
}
13541449

13551450
#[test]

0 commit comments

Comments
 (0)