Skip to content

Commit f1868fa

Browse files
committed
Teach transmute_{ref,mut}! to handle slice DSTs
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
1 parent 0bee231 commit f1868fa

File tree

3 files changed

+132
-71
lines changed

3 files changed

+132
-71
lines changed

src/macros.rs

Lines changed: 97 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -182,22 +182,23 @@ macro_rules! transmute_ref {
182182
// this macro expression is `&U` where `U: 'u + Sized + FromBytes +
183183
// Immutable`, and that `'t` outlives `'u`.
184184

185-
struct AssertSrcIsSized<'a, T: ::core::marker::Sized>(&'a T);
185+
// struct AssertSrcIsSized<'a, T: ::core::marker::Sized>(&'a T);
186186
struct AssertSrcIsIntoBytes<'a, T: ?::core::marker::Sized + $crate::IntoBytes>(&'a T);
187187
struct AssertSrcIsImmutable<'a, T: ?::core::marker::Sized + $crate::Immutable>(&'a T);
188-
struct AssertDstIsSized<'a, T: ::core::marker::Sized>(&'a T);
188+
// struct AssertDstIsSized<'a, T: ::core::marker::Sized>(&'a T);
189189
struct AssertDstIsFromBytes<'a, U: ?::core::marker::Sized + $crate::FromBytes>(&'a U);
190190
struct AssertDstIsImmutable<'a, T: ?::core::marker::Sized + $crate::Immutable>(&'a T);
191191

192-
let _ = AssertSrcIsSized(e);
192+
// let _ = AssertSrcIsSized(e);
193193
let _ = AssertSrcIsIntoBytes(e);
194194
let _ = AssertSrcIsImmutable(e);
195195

196+
// if true {
197+
// #[allow(unused, unreachable_code)]
198+
// let u = AssertDstIsSized(loop {});
199+
// u.0
200+
// } else
196201
if true {
197-
#[allow(unused, unreachable_code)]
198-
let u = AssertDstIsSized(loop {});
199-
u.0
200-
} else if true {
201202
#[allow(unused, unreachable_code)]
202203
let u = AssertDstIsFromBytes(loop {});
203204
u.0
@@ -206,36 +207,20 @@ macro_rules! transmute_ref {
206207
let u = AssertDstIsImmutable(loop {});
207208
u.0
208209
}
209-
} else if false {
210-
// This branch, though never taken, ensures that `size_of::<T>() ==
211-
// size_of::<U>()` and that that `align_of::<T>() >=
212-
// align_of::<U>()`.
213-
214-
// `t` is inferred to have type `T` because it's assigned to `e` (of
215-
// type `&T`) as `&t`.
216-
let mut t = loop {};
217-
e = &t;
218-
219-
// `u` is inferred to have type `U` because it's used as `&u` as the
220-
// value returned from this branch.
221-
let u;
222-
223-
$crate::assert_size_eq!(t, u);
224-
$crate::assert_align_gt_eq!(t, u);
225-
226-
&u
227210
} else {
228-
// SAFETY: For source type `Src` and destination type `Dst`:
229-
// - We know that `Src: IntoBytes + Immutable` and `Dst: FromBytes +
230-
// Immutable` thanks to the uses of `AssertSrcIsIntoBytes`,
231-
// `AssertSrcIsImmutable`, `AssertDstIsFromBytes`, and
232-
// `AssertDstIsImmutable` above.
233-
// - We know that `size_of::<Src>() == size_of::<Dst>()` thanks to
234-
// the use of `assert_size_eq!` above.
235-
// - We know that `align_of::<Src>() >= align_of::<Dst>()` thanks to
236-
// the use of `assert_align_gt_eq!` above.
237-
let u = unsafe { $crate::util::macro_util::transmute_ref(e) };
238-
$crate::util::macro_util::must_use(u)
211+
let opt = $crate::util::macro_util::static_assert_transmute_ref(&*e);
212+
if let Some(u) = opt {
213+
loop {}
214+
&*u
215+
} else {
216+
let e: *const _ = e;
217+
#[allow(clippy::as_conversions)]
218+
let u = e as *const _;
219+
// SAFETY: TODO (needs to be valid for both older and newer toolchains).
220+
#[allow(clippy::as_conversions)]
221+
let u = unsafe { &*u };
222+
$crate::util::macro_util::must_use(u)
223+
}
239224
}
240225
}}
241226
}
@@ -334,26 +319,27 @@ macro_rules! transmute_mut {
334319
// writing, mutable references are banned), the error message
335320
// appears to originate in the user's code rather than in the
336321
// internals of this macro.
337-
struct AssertSrcIsSized<'a, T: ::core::marker::Sized>(&'a T);
322+
// struct AssertSrcIsSized<'a, T: ::core::marker::Sized>(&'a T);
338323
struct AssertSrcIsFromBytes<'a, T: ?::core::marker::Sized + $crate::FromBytes>(&'a T);
339324
struct AssertSrcIsIntoBytes<'a, T: ?::core::marker::Sized + $crate::IntoBytes>(&'a T);
340-
struct AssertDstIsSized<'a, T: ::core::marker::Sized>(&'a T);
325+
// struct AssertDstIsSized<'a, T: ::core::marker::Sized>(&'a T);
341326
struct AssertDstIsFromBytes<'a, T: ?::core::marker::Sized + $crate::FromBytes>(&'a T);
342327
struct AssertDstIsIntoBytes<'a, T: ?::core::marker::Sized + $crate::IntoBytes>(&'a T);
343328

344329
if true {
345-
let _ = AssertSrcIsSized(&*e);
330+
// let _ = AssertSrcIsSized(&*e);
346331
} else if true {
347332
let _ = AssertSrcIsFromBytes(&*e);
348333
} else {
349334
let _ = AssertSrcIsIntoBytes(&*e);
350335
}
351336

337+
// if true {
338+
// #[allow(unused, unreachable_code)]
339+
// let u = AssertDstIsSized(loop {});
340+
// &mut *u.0
341+
// } else
352342
if true {
353-
#[allow(unused, unreachable_code)]
354-
let u = AssertDstIsSized(loop {});
355-
&mut *u.0
356-
} else if true {
357343
#[allow(unused, unreachable_code)]
358344
let u = AssertDstIsFromBytes(loop {});
359345
&mut *u.0
@@ -362,32 +348,20 @@ macro_rules! transmute_mut {
362348
let u = AssertDstIsIntoBytes(loop {});
363349
&mut *u.0
364350
}
365-
} else if false {
366-
// This branch, though never taken, ensures that `size_of::<T>() ==
367-
// size_of::<U>()` and that that `align_of::<T>() >=
368-
// align_of::<U>()`.
369-
370-
// `t` is inferred to have type `T` because it's assigned to `e` (of
371-
// type `&mut T`) as `&mut t`.
372-
let mut t = loop {};
373-
e = &mut t;
374-
375-
// `u` is inferred to have type `U` because it's used as `&mut u` as
376-
// the value returned from this branch.
377-
let u;
378-
379-
$crate::assert_size_eq!(t, u);
380-
$crate::assert_align_gt_eq!(t, u);
381-
382-
&mut u
383351
} else {
384-
// SAFETY: For source type `Src` and destination type `Dst`:
385-
// - We know that `size_of::<Src>() == size_of::<Dst>()` thanks to
386-
// the use of `assert_size_eq!` above.
387-
// - We know that `align_of::<Src>() >= align_of::<Dst>()` thanks to
388-
// the use of `assert_align_gt_eq!` above.
389-
let u = unsafe { $crate::util::macro_util::transmute_mut(e) };
390-
$crate::util::macro_util::must_use(u)
352+
let opt = $crate::util::macro_util::static_assert_transmute_ref(&*e);
353+
if let Some(u) = opt {
354+
loop {}
355+
&mut *u
356+
} else {
357+
let e: *mut _ = e;
358+
#[allow(clippy::as_conversions)]
359+
let u = e as *mut _;
360+
// SAFETY: TODO (needs to be valid for both older and newer toolchains).
361+
#[allow(clippy::as_conversions)]
362+
let u = unsafe { &mut *u };
363+
$crate::util::macro_util::must_use(u)
364+
}
391365
}
392366
}}
393367
}
@@ -793,6 +767,37 @@ mod tests {
793767
const X: &'static [[u8; 2]; 4] = transmute_ref!(&ARRAY_OF_U8S);
794768
assert_eq!(*X, ARRAY_OF_ARRAYS);
795769

770+
// Test that `transmute_ref!` works on slice DSTs in and that memory is
771+
// transmuted as expected.
772+
#[derive(KnownLayout, Immutable, FromBytes, IntoBytes, PartialEq, Debug)]
773+
#[repr(C)]
774+
struct SliceDst<T> {
775+
a: u8,
776+
b: [T],
777+
}
778+
779+
use crate::byteorder::native_endian::U16;
780+
let slice_dst_of_u8s =
781+
SliceDst::<[u8; 2]>::ref_from_bytes(&[0, 1, 2, 3, 4, 5, 6][..]).unwrap();
782+
let slice_dst_of_u16s =
783+
SliceDst::<U16>::ref_from_bytes(&[0, 1, 2, 3, 4, 5, 6][..]).unwrap();
784+
let x: &SliceDst<U16> = transmute_ref!(slice_dst_of_u8s);
785+
assert_eq!(x, slice_dst_of_u16s);
786+
787+
// Test that `transmute_ref!` works on slices in a const context and
788+
// that memory is transmuted as expected.
789+
const ARRAY_OF_U16S: &[u16] = &[0u16, 1, 2];
790+
const ARRAY_OF_I16S: &[i16] = &[0i16, 1, 2];
791+
const Y: &[i16] = transmute_ref!(ARRAY_OF_U16S);
792+
assert_eq!(Y, ARRAY_OF_I16S);
793+
794+
// Test that `transmute_ref!` works on slice DSTs in a const context. We
795+
// have no way of synthesizing slice DST values in a const context, so
796+
// this just tests that it compiles.
797+
const fn _transmute_slice_dst_ref(from: &SliceDst<[u8; 2]>) -> &SliceDst<U16> {
798+
transmute_ref!(from)
799+
}
800+
796801
// Test that it's legal to transmute a reference while shrinking the
797802
// lifetime (note that `X` has the lifetime `'static`).
798803
let x: &[u8; 8] = transmute_ref!(X);
@@ -947,6 +952,30 @@ mod tests {
947952
#[allow(clippy::useless_transmute)]
948953
let y: &u8 = transmute_mut!(&mut x);
949954
assert_eq!(*y, 0);
955+
956+
// Test that `transmute_mut!` works on slice DSTs in and that memory is
957+
// transmuted as expected.
958+
#[derive(KnownLayout, Immutable, FromBytes, IntoBytes, PartialEq, Debug)]
959+
#[repr(C)]
960+
struct SliceDst<T> {
961+
a: u8,
962+
b: [T],
963+
}
964+
965+
use crate::byteorder::native_endian::U16;
966+
let mut bytes = [0, 1, 2, 3, 4, 5, 6];
967+
let slice_dst_of_u8s = SliceDst::<[u8; 2]>::mut_from_bytes(&mut bytes[..]).unwrap();
968+
let mut bytes = [0, 1, 2, 3, 4, 5, 6];
969+
let slice_dst_of_u16s = SliceDst::<U16>::mut_from_bytes(&mut bytes[..]).unwrap();
970+
let x: &mut SliceDst<U16> = transmute_mut!(slice_dst_of_u8s);
971+
assert_eq!(x, slice_dst_of_u16s);
972+
973+
// Test that `transmute_mut!` works on slices that memory is transmuted
974+
// as expected.
975+
let array_of_u16s: &mut [u16] = &mut [0u16, 1, 2];
976+
let array_of_i16s: &mut [i16] = &mut [0i16, 1, 2];
977+
let x: &mut [i16] = transmute_mut!(array_of_u16s);
978+
assert_eq!(x, array_of_i16s);
950979
}
951980

952981
#[test]

src/util/macro_util.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,38 @@ macro_rules! assert_size_eq {
443443
}};
444444
}
445445

446+
// TODO: Document that this MUST be called from a live codepath in order for its
447+
// guarantees to hold.
448+
#[doc(hidden)]
449+
#[inline(always)]
450+
#[cfg(zerocopy_generic_bounds_in_const_fn)]
451+
pub const fn static_assert_transmute_ref<T, U>(_t: &T) -> Option<&U>
452+
where
453+
T: ?Sized + crate::KnownLayout,
454+
U: ?Sized + crate::KnownLayout,
455+
{
456+
use crate::{layout::*, KnownLayout};
457+
static_assert!(T: ?Sized + KnownLayout, U: ?Sized + KnownLayout => {
458+
let t = T::LAYOUT;
459+
let u = U::LAYOUT;
460+
t.align.get() >= u.align.get() && match (t.size_info, u.size_info) {
461+
(SizeInfo::Sized { size: t }, SizeInfo::Sized { size: u }) => t == u,
462+
(
463+
SizeInfo::SliceDst(TrailingSliceLayout { offset: t_offset, elem_size: t_elem_size }),
464+
SizeInfo::SliceDst(TrailingSliceLayout { offset: u_offset, elem_size: u_elem_size })
465+
) => t_offset == u_offset && t_elem_size == u_elem_size,
466+
_ => false,
467+
}
468+
});
469+
470+
None
471+
}
472+
473+
// #[cfg(not(zerocopy_generic_bounds_in_const_fn))]
474+
// macro_rules! assert_transmute_ref_layouts {
475+
// ($e:ident) => {};
476+
// }
477+
446478
/// Transmutes a reference of one type to a reference of another type.
447479
///
448480
/// # Safety
@@ -487,7 +519,6 @@ pub const unsafe fn transmute_ref<'dst, 'src: 'dst, Src: 'src, Dst: 'dst>(
487519
/// - `Dst: FromBytes + IntoBytes`
488520
/// - `size_of::<Src>() == size_of::<Dst>()`
489521
/// - `align_of::<Src>() >= align_of::<Dst>()`
490-
// TODO(#686): Consider removing the `Immutable` requirement.
491522
#[inline(always)]
492523
pub unsafe fn transmute_mut<'dst, 'src: 'dst, Src: 'src, Dst: 'dst>(
493524
src: &'src mut Src,

src/util/macros.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -739,14 +739,15 @@ macro_rules! static_assert {
739739
const ASSERT: bool;
740740
}
741741

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

749-
const_assert!(<($($tyvar,)*) as StaticAssert>::ASSERT);
750+
const_assert!(<($(core::marker::PhantomData<$tyvar>,)*) as StaticAssert>::ASSERT);
750751
}};
751752
}
752753

0 commit comments

Comments
 (0)