Skip to content

Commit 5196fac

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 - Print useful error message when `try_compute_cast_params` fails during static assertion - Test when post padding doesn't match between types Makes progress on #1817 Co-authored-by: Jack Wrenn <jswrenn@amazon.com> gherrit-pr-id: Ib4bc62202e0b3b09d155333b525087f7aa8f02c2
1 parent 04cafb2 commit 5196fac

File tree

7 files changed

+627
-231
lines changed

7 files changed

+627
-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

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

95+
/// Parameters necessary to perform an infallible reference cast on slice DSTs.
96+
///
97+
/// # Safety
98+
///
99+
/// Given `Src: KnownLayout` and `Dst: KnownLayout`, it is only possible to
100+
/// produce `CastParams` via
101+
/// `Src::LAYOUT.try_compute_cast_params(&Dst::LAYOUT)`, and that method will
102+
/// only return `Some` if it is possible to perform an infallible reference cast
103+
/// from `&Src` to `&Dst`.
104+
pub(crate) struct CastParams {
105+
/// Given `sl: TrailingSliceLayout` (source layout) and `dl:
106+
/// TrailingSliceLayout` (destination layout), this is computed as:
107+
///
108+
/// `(sl.offset - dl.offset) / dl.elem_size`
109+
///
110+
/// INVARIANT: `CastParams` can only be constructed if `sl.offset -
111+
/// dl.offset` is an integer multiple of `dl.elem_size`.
112+
offset_delta_elems: usize,
113+
/// `sl.elem_size / dl.elem_size`
114+
///
115+
/// INVARIANT: `CastParams` can only be constructed if `sl.elem_size` is an
116+
/// integer multiple of `dl.elem_size`, and if `dl.elem_size > 0`.
117+
elem_multiple: usize,
118+
}
119+
120+
impl CastParams {
121+
/// Given the metadata from `src: &Src`, computes the metadata required in
122+
/// order for `dst: &Dst` with the same address to reference the same number
123+
/// of bytes as `src`.
124+
///
125+
/// # Safety
126+
///
127+
/// If `Src: KnownLayout` and `Dst: KnownLayout`, and if `self` was computed
128+
/// as `Src::LAYOUT.try_compute_cast_params(&Dst::LAYOUT)`, then the
129+
/// following is guaranteed:
130+
///
131+
/// Let `src_meta` be the pointer metadata from a `src: &Src`, and compute
132+
/// `let dst_meta = self.compute_cast(src_meta)`. If `dst: &Dst` is
133+
/// constructed using the address of `src` and using `dst_meta` as its
134+
/// pointer metadata, then `dst` will address the same bytes as `src`.
135+
///
136+
/// TODO: Mention that post-padding may not be preserved.
137+
///
138+
/// # Panics
139+
///
140+
/// If the safety preconditions do not hold, then `compute_cast` may panic
141+
/// due to arithmetic overflow. Note that, as `compute_cast` is a safe
142+
/// function, failing to uphold the safety preconditions cannot cause
143+
/// immediate undefined behavior.
144+
pub(crate) const fn compute_cast(self, src_meta: usize) -> usize {
145+
// PANICS: This function is permitted to panic if its safety
146+
// preconditions are not upheld.
147+
//
148+
// SAFETY/PANICS: If `Src: KnownLayout`, `Dst: KnownLayout`, and `self`
149+
// was computed as `Src::LAYOUT.try_compute_cast_params(&Dst::LAYOUT)`,
150+
// then `self` is a witness to the infallibility of casts from `&Src` to
151+
// `&Dst`, and the internal invariants hold with respect to `Src` and
152+
// `Dst`. Given `sl: Src::LAYOUT` and `dl: Dst::LAYOUT`, these
153+
// invariants guarantee that:
154+
//
155+
// (1) `self.offset_delta_elems = (sl.offset - dl.offset) /
156+
// dl.elem_size`
157+
//
158+
// (2) `self.elem_multiple = sl.elem_size / dl.elem_size` where
159+
// `sl.elem_size` is an integer multiple of `dl.elem_size`
160+
//
161+
// If the safety preconditions are upheld, then the caller has promised
162+
// that `src_meta` is the pointer metadata from some `src: &Src`. By
163+
// invariant on `&Src`, `src` cannot address more than `isize::MAX`
164+
// bytes [1]. Thus:
165+
//
166+
// (3) `sl.offset + (sl.elem_size * src_meta) = src_len <= isize::MAX`
167+
//
168+
// (4) `self.offset_delta_elems * dl.elem_size = sl.offset - dl.offset`
169+
// (rearranging (1))
170+
//
171+
// (5) `sl.offset = (self.offset_delta_elems * dl.elem_size) +
172+
// dl.offset`
173+
//
174+
// (6) `(self.offset_delta_elems * dl.elem_size) + dl.offset +
175+
// (sl.elem_size * src_meta) = src_len <= isize::MAX` (substituting
176+
// (5) into (3))
177+
//
178+
// (7) `sl.elem_size = self.elem_multiple * dl.elem_size` (rearranging
179+
// (2))
180+
//
181+
// (8) `(self.offset_delta_elems * dl.elem_size) + dl.offset +
182+
// (self.elem_multiple * dl.elem_size * src_meta) = src_len <=
183+
// isize::MAX` (substituting (7) into (6))
184+
//
185+
// (9) `(self.offset_delta_elems * dl.elem_size) + (self.elem_multiple
186+
// * dl.elem_size * src_meta) = src_len - dl.offset <= isize::MAX -
187+
// dl.offset`
188+
//
189+
// (10) `self.offset_delta_elems + (self.elem_multiple * src_meta) =
190+
// (src_len - dl.offset) / dl.elem_size <= (isize::MAX - dl.offset)
191+
// / dl.elem_size`
192+
//
193+
// The left-hand side of (10) is the expression we compute and return
194+
// from this method. Since `dl.offset >= 0` and `dl.elem_size > 1`, the
195+
// right-hand side is not greater than `usize::MAX`, and thus this
196+
// computation will not overflow, and thus it will not panic.
197+
//
198+
// We further need to prove that, if the caller takes this resulting
199+
// value as `dst_meta` and uses it to synthesize a `dst &Dst`, this will
200+
// be address the same number of bytes as `src`. The number of bytes is
201+
// given by:
202+
//
203+
// (11) `dst_len = dl.offset + (dl.elem_size * dst_meta)`
204+
//
205+
// (12) `dst_len = dl.offset + (dl.elem_size * (self.offset_delta_elems
206+
// + (self.elem_multiple * src_meta)))` (substituing this method's
207+
// return value)
208+
//
209+
// (13) `dst_len = dl.offset + (dl.elem_size * ((src_len - dl.offset) /
210+
// dl.elem_size))` (substituting (10))
211+
//
212+
// (14) `dst_len = dl.offset + (src_len - dl.offset)`
213+
//
214+
// (15) `dst_len = src_len`
215+
//
216+
// [1] TODO
217+
//
218+
// TODO: Clarify that this algorithm is agnostic to post-padding, and
219+
// justify that this is acceptable.
220+
#[allow(clippy::arithmetic_side_effects)]
221+
return self.offset_delta_elems + (self.elem_multiple * src_meta);
222+
}
223+
}
224+
95225
impl DstLayout {
96226
/// The minimum possible alignment of a type.
97227
const MIN_ALIGN: NonZeroUsize = match NonZeroUsize::new(1) {
@@ -202,6 +332,48 @@ impl DstLayout {
202332
}
203333
}
204334

335+
pub(crate) const fn try_compute_cast_params(self, other: &DstLayout) -> Option<CastParams> {
336+
let (slf, other) = if let (SizeInfo::SliceDst(slf), SizeInfo::SliceDst(other)) =
337+
(self.size_info, other.size_info)
338+
{
339+
(slf, other)
340+
} else {
341+
return None;
342+
};
343+
344+
let offset_delta = if let Some(od) = slf.offset.checked_sub(other.offset) {
345+
od
346+
} else {
347+
return None;
348+
};
349+
350+
let delta_mod_other_elem = offset_delta.checked_rem(other.elem_size);
351+
let elem_remainder = slf.elem_size.checked_rem(other.elem_size);
352+
353+
let (delta_mod_other_elem, elem_remainder) =
354+
if let (Some(dmoe), Some(em)) = (delta_mod_other_elem, elem_remainder) {
355+
(dmoe, em)
356+
} else {
357+
return None;
358+
};
359+
360+
if delta_mod_other_elem != 0 || slf.elem_size < other.elem_size || elem_remainder != 0 {
361+
return None;
362+
}
363+
364+
// PANICS: The preceding `slf.elem_size.checked_rem(other.elem_size)`
365+
// could only return `Some` if `other.elem_size > 0`, so this division
366+
// will not divide by zero.
367+
#[allow(clippy::arithmetic_side_effects)]
368+
let offset_delta_elems = offset_delta / other.elem_size;
369+
// PANICS: See previous panics comment.
370+
#[allow(clippy::arithmetic_side_effects)]
371+
let elem_multiple = slf.elem_size / other.elem_size;
372+
373+
// INVARIANTS: TODO
374+
Some(CastParams { offset_delta_elems, elem_multiple })
375+
}
376+
205377
/// Like `Layout::extend`, this creates a layout that describes a record
206378
/// whose layout consists of `self` followed by `next` that includes the
207379
/// necessary inter-field padding, but not any trailing padding.

0 commit comments

Comments
 (0)