Skip to content

Commit

Permalink
Teach transmute_{ref,mut}! to handle slice DSTs
Browse files Browse the repository at this point in the history
TODO:
- Figure out how to make this backwards-compatible (since current macros
  work with `T: Sized`, but `T: Sized` does not imply `T: KnownLayout`)
- Make it so that `Sized` bounds are still enforced on older toolchains
  that don't support slice DST transmutes
- Write safety comments
- Get rid of now-unused macros and functions in util::macro_util

Makes progress on #1817

gherrit-pr-id: Ib4bc62202e0b3b09d155333b525087f7aa8f02c2
  • Loading branch information
joshlf committed Oct 16, 2024
1 parent 0bee231 commit f1868fa
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 71 deletions.
165 changes: 97 additions & 68 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,22 +182,23 @@ macro_rules! transmute_ref {
// this macro expression is `&U` where `U: 'u + Sized + FromBytes +
// Immutable`, and that `'t` outlives `'u`.

struct AssertSrcIsSized<'a, T: ::core::marker::Sized>(&'a T);
// struct AssertSrcIsSized<'a, T: ::core::marker::Sized>(&'a T);
struct AssertSrcIsIntoBytes<'a, T: ?::core::marker::Sized + $crate::IntoBytes>(&'a T);
struct AssertSrcIsImmutable<'a, T: ?::core::marker::Sized + $crate::Immutable>(&'a T);
struct AssertDstIsSized<'a, T: ::core::marker::Sized>(&'a T);
// struct AssertDstIsSized<'a, T: ::core::marker::Sized>(&'a T);
struct AssertDstIsFromBytes<'a, U: ?::core::marker::Sized + $crate::FromBytes>(&'a U);
struct AssertDstIsImmutable<'a, T: ?::core::marker::Sized + $crate::Immutable>(&'a T);

let _ = AssertSrcIsSized(e);
// let _ = AssertSrcIsSized(e);
let _ = AssertSrcIsIntoBytes(e);
let _ = AssertSrcIsImmutable(e);

// if true {
// #[allow(unused, unreachable_code)]
// let u = AssertDstIsSized(loop {});
// u.0
// } else
if true {
#[allow(unused, unreachable_code)]
let u = AssertDstIsSized(loop {});
u.0
} else if true {
#[allow(unused, unreachable_code)]
let u = AssertDstIsFromBytes(loop {});
u.0
Expand All @@ -206,36 +207,20 @@ macro_rules! transmute_ref {
let u = AssertDstIsImmutable(loop {});
u.0
}
} else if false {
// This branch, though never taken, ensures that `size_of::<T>() ==
// size_of::<U>()` and that that `align_of::<T>() >=
// align_of::<U>()`.

// `t` is inferred to have type `T` because it's assigned to `e` (of
// type `&T`) as `&t`.
let mut t = loop {};
e = &t;

// `u` is inferred to have type `U` because it's used as `&u` as the
// value returned from this branch.
let u;

$crate::assert_size_eq!(t, u);
$crate::assert_align_gt_eq!(t, u);

&u
} else {
// SAFETY: For source type `Src` and destination type `Dst`:
// - We know that `Src: IntoBytes + Immutable` and `Dst: FromBytes +
// Immutable` thanks to the uses of `AssertSrcIsIntoBytes`,
// `AssertSrcIsImmutable`, `AssertDstIsFromBytes`, and
// `AssertDstIsImmutable` above.
// - We know that `size_of::<Src>() == size_of::<Dst>()` thanks to
// the use of `assert_size_eq!` above.
// - We know that `align_of::<Src>() >= align_of::<Dst>()` thanks to
// the use of `assert_align_gt_eq!` above.
let u = unsafe { $crate::util::macro_util::transmute_ref(e) };
$crate::util::macro_util::must_use(u)
let opt = $crate::util::macro_util::static_assert_transmute_ref(&*e);
if let Some(u) = opt {
loop {}
&*u
} else {
let e: *const _ = e;
#[allow(clippy::as_conversions)]
let u = e as *const _;
// SAFETY: TODO (needs to be valid for both older and newer toolchains).
#[allow(clippy::as_conversions)]
let u = unsafe { &*u };
$crate::util::macro_util::must_use(u)
}
}
}}
}
Expand Down Expand Up @@ -334,26 +319,27 @@ macro_rules! transmute_mut {
// writing, mutable references are banned), the error message
// appears to originate in the user's code rather than in the
// internals of this macro.
struct AssertSrcIsSized<'a, T: ::core::marker::Sized>(&'a T);
// struct AssertSrcIsSized<'a, T: ::core::marker::Sized>(&'a T);
struct AssertSrcIsFromBytes<'a, T: ?::core::marker::Sized + $crate::FromBytes>(&'a T);
struct AssertSrcIsIntoBytes<'a, T: ?::core::marker::Sized + $crate::IntoBytes>(&'a T);
struct AssertDstIsSized<'a, T: ::core::marker::Sized>(&'a T);
// struct AssertDstIsSized<'a, T: ::core::marker::Sized>(&'a T);
struct AssertDstIsFromBytes<'a, T: ?::core::marker::Sized + $crate::FromBytes>(&'a T);
struct AssertDstIsIntoBytes<'a, T: ?::core::marker::Sized + $crate::IntoBytes>(&'a T);

if true {
let _ = AssertSrcIsSized(&*e);
// let _ = AssertSrcIsSized(&*e);
} else if true {
let _ = AssertSrcIsFromBytes(&*e);
} else {
let _ = AssertSrcIsIntoBytes(&*e);
}

// if true {
// #[allow(unused, unreachable_code)]
// let u = AssertDstIsSized(loop {});
// &mut *u.0
// } else
if true {
#[allow(unused, unreachable_code)]
let u = AssertDstIsSized(loop {});
&mut *u.0
} else if true {
#[allow(unused, unreachable_code)]
let u = AssertDstIsFromBytes(loop {});
&mut *u.0
Expand All @@ -362,32 +348,20 @@ macro_rules! transmute_mut {
let u = AssertDstIsIntoBytes(loop {});
&mut *u.0
}
} else if false {
// This branch, though never taken, ensures that `size_of::<T>() ==
// size_of::<U>()` and that that `align_of::<T>() >=
// align_of::<U>()`.

// `t` is inferred to have type `T` because it's assigned to `e` (of
// type `&mut T`) as `&mut t`.
let mut t = loop {};
e = &mut t;

// `u` is inferred to have type `U` because it's used as `&mut u` as
// the value returned from this branch.
let u;

$crate::assert_size_eq!(t, u);
$crate::assert_align_gt_eq!(t, u);

&mut u
} else {
// SAFETY: For source type `Src` and destination type `Dst`:
// - We know that `size_of::<Src>() == size_of::<Dst>()` thanks to
// the use of `assert_size_eq!` above.
// - We know that `align_of::<Src>() >= align_of::<Dst>()` thanks to
// the use of `assert_align_gt_eq!` above.
let u = unsafe { $crate::util::macro_util::transmute_mut(e) };
$crate::util::macro_util::must_use(u)
let opt = $crate::util::macro_util::static_assert_transmute_ref(&*e);
if let Some(u) = opt {
loop {}
&mut *u
} else {
let e: *mut _ = e;
#[allow(clippy::as_conversions)]
let u = e as *mut _;
// SAFETY: TODO (needs to be valid for both older and newer toolchains).
#[allow(clippy::as_conversions)]
let u = unsafe { &mut *u };
$crate::util::macro_util::must_use(u)
}
}
}}
}
Expand Down Expand Up @@ -793,6 +767,37 @@ mod tests {
const X: &'static [[u8; 2]; 4] = transmute_ref!(&ARRAY_OF_U8S);
assert_eq!(*X, ARRAY_OF_ARRAYS);

// Test that `transmute_ref!` works on slice DSTs in and that memory is
// transmuted as expected.
#[derive(KnownLayout, Immutable, FromBytes, IntoBytes, PartialEq, Debug)]
#[repr(C)]
struct SliceDst<T> {
a: u8,
b: [T],
}

use crate::byteorder::native_endian::U16;
let slice_dst_of_u8s =
SliceDst::<[u8; 2]>::ref_from_bytes(&[0, 1, 2, 3, 4, 5, 6][..]).unwrap();
let slice_dst_of_u16s =
SliceDst::<U16>::ref_from_bytes(&[0, 1, 2, 3, 4, 5, 6][..]).unwrap();
let x: &SliceDst<U16> = transmute_ref!(slice_dst_of_u8s);
assert_eq!(x, slice_dst_of_u16s);

// Test that `transmute_ref!` works on slices in a const context and
// that memory is transmuted as expected.
const ARRAY_OF_U16S: &[u16] = &[0u16, 1, 2];
const ARRAY_OF_I16S: &[i16] = &[0i16, 1, 2];
const Y: &[i16] = transmute_ref!(ARRAY_OF_U16S);
assert_eq!(Y, ARRAY_OF_I16S);

// Test that `transmute_ref!` works on slice DSTs in a const context. We
// have no way of synthesizing slice DST values in a const context, so
// this just tests that it compiles.
const fn _transmute_slice_dst_ref(from: &SliceDst<[u8; 2]>) -> &SliceDst<U16> {
transmute_ref!(from)
}

// Test that it's legal to transmute a reference while shrinking the
// lifetime (note that `X` has the lifetime `'static`).
let x: &[u8; 8] = transmute_ref!(X);
Expand Down Expand Up @@ -947,6 +952,30 @@ mod tests {
#[allow(clippy::useless_transmute)]
let y: &u8 = transmute_mut!(&mut x);
assert_eq!(*y, 0);

// Test that `transmute_mut!` works on slice DSTs in and that memory is
// transmuted as expected.
#[derive(KnownLayout, Immutable, FromBytes, IntoBytes, PartialEq, Debug)]
#[repr(C)]
struct SliceDst<T> {
a: u8,
b: [T],
}

use crate::byteorder::native_endian::U16;
let mut bytes = [0, 1, 2, 3, 4, 5, 6];
let slice_dst_of_u8s = SliceDst::<[u8; 2]>::mut_from_bytes(&mut bytes[..]).unwrap();
let mut bytes = [0, 1, 2, 3, 4, 5, 6];
let slice_dst_of_u16s = SliceDst::<U16>::mut_from_bytes(&mut bytes[..]).unwrap();
let x: &mut SliceDst<U16> = transmute_mut!(slice_dst_of_u8s);
assert_eq!(x, slice_dst_of_u16s);

// Test that `transmute_mut!` works on slices that memory is transmuted
// as expected.
let array_of_u16s: &mut [u16] = &mut [0u16, 1, 2];
let array_of_i16s: &mut [i16] = &mut [0i16, 1, 2];
let x: &mut [i16] = transmute_mut!(array_of_u16s);
assert_eq!(x, array_of_i16s);
}

#[test]
Expand Down
33 changes: 32 additions & 1 deletion src/util/macro_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,38 @@ macro_rules! assert_size_eq {
}};
}

// TODO: Document that this MUST be called from a live codepath in order for its
// guarantees to hold.
#[doc(hidden)]
#[inline(always)]
#[cfg(zerocopy_generic_bounds_in_const_fn)]
pub const fn static_assert_transmute_ref<T, U>(_t: &T) -> Option<&U>
where
T: ?Sized + crate::KnownLayout,
U: ?Sized + crate::KnownLayout,
{
use crate::{layout::*, KnownLayout};
static_assert!(T: ?Sized + KnownLayout, U: ?Sized + KnownLayout => {
let t = T::LAYOUT;
let u = U::LAYOUT;
t.align.get() >= u.align.get() && match (t.size_info, u.size_info) {
(SizeInfo::Sized { size: t }, SizeInfo::Sized { size: u }) => t == u,
(
SizeInfo::SliceDst(TrailingSliceLayout { offset: t_offset, elem_size: t_elem_size }),
SizeInfo::SliceDst(TrailingSliceLayout { offset: u_offset, elem_size: u_elem_size })
) => t_offset == u_offset && t_elem_size == u_elem_size,
_ => false,
}
});

None
}

// #[cfg(not(zerocopy_generic_bounds_in_const_fn))]
// macro_rules! assert_transmute_ref_layouts {
// ($e:ident) => {};
// }

/// Transmutes a reference of one type to a reference of another type.
///
/// # Safety
Expand Down Expand Up @@ -487,7 +519,6 @@ pub const unsafe fn transmute_ref<'dst, 'src: 'dst, Src: 'src, Dst: 'dst>(
/// - `Dst: FromBytes + IntoBytes`
/// - `size_of::<Src>() == size_of::<Dst>()`
/// - `align_of::<Src>() >= align_of::<Dst>()`
// TODO(#686): Consider removing the `Immutable` requirement.
#[inline(always)]
pub unsafe fn transmute_mut<'dst, 'src: 'dst, Src: 'src, Dst: 'dst>(
src: &'src mut Src,
Expand Down
5 changes: 3 additions & 2 deletions src/util/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -739,14 +739,15 @@ macro_rules! static_assert {
const ASSERT: bool;
}

impl<$($tyvar $(: $(? $optbound +)* $($bound +)*)?,)*> StaticAssert for ($($tyvar,)*) {
// NOTE: We use `PhantomData` so we can support unsized types.
impl<$($tyvar $(: $(? $optbound +)* $($bound +)*)?,)*> StaticAssert for ($(core::marker::PhantomData<$tyvar>,)*) {
const ASSERT: bool = {
const_assert!($condition $(, $args)*);
$condition
};
}

const_assert!(<($($tyvar,)*) as StaticAssert>::ASSERT);
const_assert!(<($(core::marker::PhantomData<$tyvar>,)*) as StaticAssert>::ASSERT);
}};
}

Expand Down

0 comments on commit f1868fa

Please sign in to comment.