Skip to content

Commit 829b199

Browse files
joshlfjswrenn
andcommitted
Teach transmute_{ref,mut}! to handle slice DSTs
TODO: - Convince ourselves that it's acceptable to drop the `TransmuteFrom: SizeCompatible` super-trait bound. - Write Kani tests for layout computations Makes progress on #1817 Co-authored-by: Jack Wrenn <jswrenn@amazon.com> gherrit-pr-id: Ib4bc62202e0b3b09d155333b525087f7aa8f02c2
1 parent 4d09d7c commit 829b199

File tree

7 files changed

+487
-231
lines changed

7 files changed

+487
-231
lines changed

src/impls.rs

+14-38
Original file line numberDiff line numberDiff line change
@@ -179,39 +179,13 @@ safety_comment! {
179179
});
180180
}
181181

182-
// SAFETY: `str` and `[u8]` have the same layout [1].
183-
//
184-
// [1] Per https://doc.rust-lang.org/1.81.0/reference/type-layout.html#str-layout:
185-
//
186-
// String slices are a UTF-8 representation of characters that have the same
187-
// layout as slices of type `[u8]`.
188-
unsafe impl pointer::SizeEq<str> for [u8] {
189-
fn cast_from_raw(s: NonNull<str>) -> NonNull<[u8]> {
190-
cast!(s)
191-
}
192-
}
193-
// SAFETY: See previous safety comment.
194-
unsafe impl pointer::SizeEq<[u8]> for str {
195-
fn cast_from_raw(bytes: NonNull<[u8]>) -> NonNull<str> {
196-
cast!(bytes)
197-
}
198-
}
182+
impl_size_eq!(str, [u8]);
199183

200184
macro_rules! unsafe_impl_try_from_bytes_for_nonzero {
201185
($($nonzero:ident[$prim:ty]),*) => {
202186
$(
203187
unsafe_impl!(=> TryFromBytes for $nonzero; |n| {
204-
unsafe impl pointer::SizeEq<$nonzero> for Unalign<$prim> {
205-
fn cast_from_raw(n: NonNull<$nonzero>) -> NonNull<Unalign<$prim>> {
206-
cast!(n)
207-
}
208-
}
209-
unsafe impl pointer::SizeEq<Unalign<$prim>> for $nonzero {
210-
fn cast_from_raw(p: NonNull<Unalign<$prim>>) -> NonNull<$nonzero> {
211-
cast!(p)
212-
}
213-
}
214-
188+
impl_size_eq!($nonzero, Unalign<$prim>);
215189
let n = n.transmute::<Unalign<$prim>, invariant::Valid, _>();
216190
$nonzero::new(n.read_unaligned().into_inner()).is_some()
217191
});
@@ -430,7 +404,7 @@ mod atomics {
430404
($($($tyvar:ident)? => $atomic:ty [$prim:ty]),*) => {
431405
const _: () = {
432406
use core::{cell::UnsafeCell, ptr::NonNull};
433-
use crate::pointer::{TransmuteFrom, SizeEq, invariant::Valid};
407+
use crate::pointer::{TransmuteFrom, SizeCompat, invariant::Valid};
434408

435409
$(
436410
#[allow(unused_unsafe)] // Force the caller to call this macro inside `safety_comment!`.
@@ -443,36 +417,38 @@ mod atomics {
443417
// the same size and bit validity.
444418
unsafe impl<$($tyvar)?> TransmuteFrom<$prim, Valid, Valid> for $atomic {}
445419

446-
// SAFETY: THe caller promised that `$atomic` and `$prim`
447-
// have the same size.
448-
unsafe impl<$($tyvar)?> SizeEq<$atomic> for $prim {
420+
// SAFETY: The caller promised that `$atomic` and `$prim`
421+
// have the same size. Thus, this cast preserves address,
422+
// referent size, and provenance.
423+
unsafe impl<$($tyvar)?> SizeCompat<$atomic> for $prim {
449424
fn cast_from_raw(a: NonNull<$atomic>) -> NonNull<$prim> {
450425
cast!(a)
451426
}
452427
}
453-
// SAFETY: THe caller promised that `$atomic` and `$prim`
454-
// have the same size.
455-
unsafe impl<$($tyvar)?> SizeEq<$prim> for $atomic {
428+
// SAFETY: See previous safety comment.
429+
unsafe impl<$($tyvar)?> SizeCompat<$prim> for $atomic {
456430
fn cast_from_raw(p: NonNull<$prim>) -> NonNull<$atomic> {
457431
cast!(p)
458432
}
459433
}
434+
460435
// SAFETY: The caller promised that `$atomic` and `$prim`
461436
// have the same size. `UnsafeCell<T>` has the same size as
462-
// `T` [1].
437+
// `T` [1]. Thus, this cast preserves address, referent
438+
// size, and provenance.
463439
//
464440
// [1] Per https://doc.rust-lang.org/1.85.0/std/cell/struct.UnsafeCell.html#memory-layout:
465441
//
466442
// `UnsafeCell<T>` has the same in-memory representation as
467443
// its inner type `T`. A consequence of this guarantee is that
468444
// it is possible to convert between `T` and `UnsafeCell<T>`.
469-
unsafe impl<$($tyvar)?> SizeEq<$atomic> for UnsafeCell<$prim> {
445+
unsafe impl<$($tyvar)?> SizeCompat<$atomic> for UnsafeCell<$prim> {
470446
fn cast_from_raw(a: NonNull<$atomic>) -> NonNull<UnsafeCell<$prim>> {
471447
cast!(a)
472448
}
473449
}
474450
// SAFETY: See previous safety comment.
475-
unsafe impl<$($tyvar)?> SizeEq<UnsafeCell<$prim>> for $atomic {
451+
unsafe impl<$($tyvar)?> SizeCompat<UnsafeCell<$prim>> for $atomic {
476452
fn cast_from_raw(p: NonNull<UnsafeCell<$prim>>) -> NonNull<$atomic> {
477453
cast!(p)
478454
}

src/layout.rs

+32
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,17 @@ pub(crate) enum MetadataCastError {
9292
Size,
9393
}
9494

95+
pub(crate) struct CastParams {
96+
pub(crate) offset_delta_elems: usize,
97+
pub(crate) elem_multiple: usize,
98+
}
99+
100+
impl CastParams {
101+
pub(crate) const fn compute_cast(self, meta: usize) -> usize {
102+
self.offset_delta_elems + (meta * self.elem_multiple)
103+
}
104+
}
105+
95106
impl DstLayout {
96107
/// The minimum possible alignment of a type.
97108
const MIN_ALIGN: NonZeroUsize = match NonZeroUsize::new(1) {
@@ -202,6 +213,27 @@ impl DstLayout {
202213
}
203214
}
204215

216+
pub(crate) const fn try_compute_cast_params(self, other: &DstLayout) -> Option<CastParams> {
217+
if let (SizeInfo::SliceDst(slf), SizeInfo::SliceDst(other)) =
218+
(self.size_info, other.size_info)
219+
{
220+
if let Some(offset_delta) = slf.offset.checked_sub(other.offset) {
221+
if offset_delta % other.elem_size == 0
222+
&& other.elem_size > 0
223+
&& slf.elem_size >= other.elem_size
224+
&& slf.elem_size % other.elem_size == 0
225+
{
226+
return Some(CastParams {
227+
offset_delta_elems: offset_delta / other.elem_size,
228+
elem_multiple: slf.elem_size / other.elem_size,
229+
});
230+
}
231+
}
232+
}
233+
234+
None
235+
}
236+
205237
/// Like `Layout::extend`, this creates a layout that describes a record
206238
/// whose layout consists of `self` followed by `next` that includes the
207239
/// necessary inter-field padding, but not any trailing padding.

src/macros.rs

+100-69
Original file line numberDiff line numberDiff line change
@@ -182,22 +182,15 @@ 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);
186185
struct AssertSrcIsIntoBytes<'a, T: ?::core::marker::Sized + $crate::IntoBytes>(&'a T);
187186
struct AssertSrcIsImmutable<'a, T: ?::core::marker::Sized + $crate::Immutable>(&'a T);
188-
struct AssertDstIsSized<'a, T: ::core::marker::Sized>(&'a T);
189187
struct AssertDstIsFromBytes<'a, U: ?::core::marker::Sized + $crate::FromBytes>(&'a U);
190188
struct AssertDstIsImmutable<'a, T: ?::core::marker::Sized + $crate::Immutable>(&'a T);
191189

192-
let _ = AssertSrcIsSized(e);
193190
let _ = AssertSrcIsIntoBytes(e);
194191
let _ = AssertSrcIsImmutable(e);
195192

196193
if true {
197-
#[allow(unused, unreachable_code)]
198-
let u = AssertDstIsSized(loop {});
199-
u.0
200-
} else if true {
201194
#[allow(unused, unreachable_code)]
202195
let u = AssertDstIsFromBytes(loop {});
203196
u.0
@@ -206,36 +199,13 @@ macro_rules! transmute_ref {
206199
let u = AssertDstIsImmutable(loop {});
207200
u.0
208201
}
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
227202
} 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)
203+
use $crate::util::macro_util::TransmuteRefDst;
204+
let t = $crate::util::macro_util::Wrap(e);
205+
// SAFETY: todo
206+
unsafe {
207+
t.transmute_ref()
208+
}
239209
}
240210
}}
241211
}
@@ -334,26 +304,18 @@ macro_rules! transmute_mut {
334304
// writing, mutable references are banned), the error message
335305
// appears to originate in the user's code rather than in the
336306
// internals of this macro.
337-
struct AssertSrcIsSized<'a, T: ::core::marker::Sized>(&'a T);
338307
struct AssertSrcIsFromBytes<'a, T: ?::core::marker::Sized + $crate::FromBytes>(&'a T);
339308
struct AssertSrcIsIntoBytes<'a, T: ?::core::marker::Sized + $crate::IntoBytes>(&'a T);
340-
struct AssertDstIsSized<'a, T: ::core::marker::Sized>(&'a T);
341309
struct AssertDstIsFromBytes<'a, T: ?::core::marker::Sized + $crate::FromBytes>(&'a T);
342310
struct AssertDstIsIntoBytes<'a, T: ?::core::marker::Sized + $crate::IntoBytes>(&'a T);
343311

344312
if true {
345-
let _ = AssertSrcIsSized(&*e);
346-
} else if true {
347313
let _ = AssertSrcIsFromBytes(&*e);
348314
} else {
349315
let _ = AssertSrcIsIntoBytes(&*e);
350316
}
351317

352318
if true {
353-
#[allow(unused, unreachable_code)]
354-
let u = AssertDstIsSized(loop {});
355-
&mut *u.0
356-
} else if true {
357319
#[allow(unused, unreachable_code)]
358320
let u = AssertDstIsFromBytes(loop {});
359321
&mut *u.0
@@ -362,32 +324,13 @@ macro_rules! transmute_mut {
362324
let u = AssertDstIsIntoBytes(loop {});
363325
&mut *u.0
364326
}
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
383327
} 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)
328+
use $crate::util::macro_util::TransmuteMutDst;
329+
let t = $crate::util::macro_util::Wrap(e);
330+
// SAFETY: todo
331+
unsafe {
332+
t.transmute_mut()
333+
}
391334
}
392335
}}
393336
}
@@ -1038,6 +981,18 @@ mod tests {
1038981
assert_eq!(x.into_inner(), 1);
1039982
}
1040983

984+
// A `Sized` type which doesn't implement `KnownLayout` (it is "not
985+
// `KnownLayout`", or `Nkl`).
986+
//
987+
// This permits us to test that `transmute_ref!` and `transmute_mut!` work
988+
// for types which are `Sized + !KnownLayout`. When we added support for
989+
// slice DSTs in #1924, this new support relied on `KnownLayout`, but we
990+
// need to make sure to remain backwards-compatible with code which ueses
991+
// these macros with types which are `!KnownLayout`.
992+
#[derive(FromBytes, IntoBytes, Immutable, PartialEq, Eq, Debug)]
993+
#[repr(transparent)]
994+
struct Nkl<T>(T);
995+
1041996
#[test]
1042997
fn test_transmute_ref() {
1043998
// Test that memory is transmuted as expected.
@@ -1055,6 +1010,49 @@ mod tests {
10551010
const X: &'static [[u8; 2]; 4] = transmute_ref!(&ARRAY_OF_U8S);
10561011
assert_eq!(*X, ARRAY_OF_ARRAYS);
10571012

1013+
// Call through a generic function to make sure our autoref
1014+
// specialization trick works even when types are generic.
1015+
const fn transmute_ref<T, U>(t: &T) -> &U
1016+
where
1017+
T: IntoBytes + Immutable,
1018+
U: FromBytes + Immutable,
1019+
{
1020+
transmute_ref!(t)
1021+
}
1022+
1023+
// Test that `transmute_ref!` supports non-`KnownLayout` `Sized` types.
1024+
const ARRAY_OF_NKL_U8S: Nkl<[u8; 8]> = Nkl([0u8, 1, 2, 3, 4, 5, 6, 7]);
1025+
const ARRAY_OF_NKL_ARRAYS: Nkl<[[u8; 2]; 4]> = Nkl([[0, 1], [2, 3], [4, 5], [6, 7]]);
1026+
const X_NKL: &Nkl<[[u8; 2]; 4]> = transmute_ref(&ARRAY_OF_NKL_U8S);
1027+
assert_eq!(*X_NKL, ARRAY_OF_NKL_ARRAYS);
1028+
1029+
// Test that `transmute_ref!` works on slice DSTs in and that memory is
1030+
// transmuted as expected.
1031+
#[derive(KnownLayout, Immutable, FromBytes, IntoBytes, PartialEq, Debug)]
1032+
#[repr(C)]
1033+
struct SliceDst<T> {
1034+
a: U16,
1035+
b: [T],
1036+
}
1037+
1038+
use crate::byteorder::native_endian::U16;
1039+
let slice_dst_of_u8s =
1040+
SliceDst::<[u8; 2]>::ref_from_bytes(&[0, 1, 2, 3, 4, 5][..]).unwrap();
1041+
let slice_dst_of_u16s = SliceDst::<U16>::ref_from_bytes(&[0, 1, 2, 3, 4, 5][..]).unwrap();
1042+
let x: &SliceDst<U16> = transmute_ref!(slice_dst_of_u8s);
1043+
assert_eq!(x, slice_dst_of_u16s);
1044+
1045+
let slice_dst_of_u8s = SliceDst::<u8>::ref_from_bytes(&[0, 1, 2, 3, 4, 5][..]).unwrap();
1046+
let x: &[u8] = transmute_ref!(slice_dst_of_u8s);
1047+
assert_eq!(x, [0, 1, 2, 3, 4, 5]);
1048+
1049+
let x: &[u8] = transmute_ref!(slice_dst_of_u16s);
1050+
assert_eq!(x, [0, 1, 2, 3, 4, 5]);
1051+
1052+
let x: &[U16] = transmute_ref!(slice_dst_of_u16s);
1053+
let slice_of_u16s: &[U16] = <[U16]>::ref_from_bytes(&[0, 1, 2, 3, 4, 5][..]).unwrap();
1054+
assert_eq!(x, slice_of_u16s);
1055+
10581056
// Test that it's legal to transmute a reference while shrinking the
10591057
// lifetime (note that `X` has the lifetime `'static`).
10601058
let x: &[u8; 8] = transmute_ref!(X);
@@ -1198,6 +1196,15 @@ mod tests {
11981196
let x: &mut [u8; 8] = transmute_mut!(&mut array_of_arrays);
11991197
assert_eq!(*x, array_of_u8s);
12001198
}
1199+
1200+
// Test that `transmute_mut!` supports non-`KnownLayout` types.
1201+
let mut array_of_u8s = Nkl([0u8, 1, 2, 3, 4, 5, 6, 7]);
1202+
let mut array_of_arrays = Nkl([[0, 1], [2, 3], [4, 5], [6, 7]]);
1203+
let x: &mut Nkl<[[u8; 2]; 4]> = transmute_mut!(&mut array_of_u8s);
1204+
assert_eq!(*x, array_of_arrays);
1205+
let x: &mut Nkl<[u8; 8]> = transmute_mut!(&mut array_of_arrays);
1206+
assert_eq!(*x, array_of_u8s);
1207+
12011208
// Test that `transmute_mut!` supports decreasing alignment.
12021209
let mut u = AU64(0);
12031210
let array = [0, 0, 0, 0, 0, 0, 0, 0];
@@ -1209,6 +1216,30 @@ mod tests {
12091216
#[allow(clippy::useless_transmute)]
12101217
let y: &u8 = transmute_mut!(&mut x);
12111218
assert_eq!(*y, 0);
1219+
1220+
// Test that `transmute_mut!` works on slice DSTs in and that memory is
1221+
// transmuted as expected.
1222+
#[derive(KnownLayout, Immutable, FromBytes, IntoBytes, PartialEq, Debug)]
1223+
#[repr(C)]
1224+
struct SliceDst<T> {
1225+
a: u8,
1226+
b: [T],
1227+
}
1228+
1229+
use crate::byteorder::native_endian::U16;
1230+
let mut bytes = [0, 1, 2, 3, 4, 5, 6];
1231+
let slice_dst_of_u8s = SliceDst::<[u8; 2]>::mut_from_bytes(&mut bytes[..]).unwrap();
1232+
let mut bytes = [0, 1, 2, 3, 4, 5, 6];
1233+
let slice_dst_of_u16s = SliceDst::<U16>::mut_from_bytes(&mut bytes[..]).unwrap();
1234+
let x: &mut SliceDst<U16> = transmute_mut!(slice_dst_of_u8s);
1235+
assert_eq!(x, slice_dst_of_u16s);
1236+
1237+
// Test that `transmute_mut!` works on slices that memory is transmuted
1238+
// as expected.
1239+
let array_of_u16s: &mut [u16] = &mut [0u16, 1, 2];
1240+
let array_of_i16s: &mut [i16] = &mut [0i16, 1, 2];
1241+
let x: &mut [i16] = transmute_mut!(array_of_u16s);
1242+
assert_eq!(x, array_of_i16s);
12121243
}
12131244

12141245
#[test]

0 commit comments

Comments
 (0)