Skip to content

Commit 4e5d2be

Browse files
committed
[util] Ptr doesn't track byte length separately
Since raw pointer slice casts are now guaranteed to preserve element count [1], we no longer need to separately store the byte length of a `Ptr`. Instead, we can compute it from the raw pointer field. [1] rust-lang/reference#1417
1 parent 5811ae0 commit 4e5d2be

File tree

1 file changed

+54
-47
lines changed

1 file changed

+54
-47
lines changed

src/util.rs

Lines changed: 54 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ pub(crate) mod ptr {
1515
use core::{
1616
fmt::{Debug, Formatter},
1717
marker::PhantomData,
18-
mem,
1918
ptr::NonNull,
2019
};
2120

@@ -42,31 +41,14 @@ pub(crate) mod ptr {
4241
// INVARIANTS:
4342
// - `ptr` is derived from some valid Rust allocation, `A`
4443
// - `ptr` has the same provenance as `A`
45-
// - `ptr` addresses a byte range of length `bytes_len` which is
46-
// entirely contained in `A`
47-
// - `bytes_len <= isize::MAX`
44+
// - `ptr` addresses a byte range which is entirely contained in `A`
45+
// - `ptr` addresses a byte range whose length fits in an `isize`
4846
// - `ptr` addresses a byte range which does not wrap around the address
4947
// space
5048
// - `ptr` is validly-aligned for `T`
5149
// - `A` is guaranteed to live for at least `'a`
5250
// - `T: 'a`
5351
ptr: NonNull<T>,
54-
// TODO(https://github.com/rust-lang/reference/pull/1417): Once the
55-
// behavior of slice DST-to-slice DST raw pointer casts is guaranteed,
56-
// we can use it to calculate the length of the memory region from
57-
// `ptr`, and we don't need to store in separately. We can do it like
58-
// this:
59-
//
60-
// let slc = ptr.as_ptr() as *const [()];
61-
// // SAFETY:
62-
// // - `()` has alignment 1, so `slc` is trivially aligned
63-
// // - `slc` was derived from a non-null pointer
64-
// // - the size is 0 regardless of the length, so it is sound to
65-
// // materialize a reference regardless of location
66-
// // - pointer provenance may be an issue, but we never dereference
67-
// let slc = unsafe { &*slc };
68-
// slc.len()
69-
_bytes_len: usize,
7052
_lifetime: PhantomData<&'a ()>,
7153
}
7254

@@ -170,34 +152,30 @@ pub(crate) mod ptr {
170152
// panic.
171153
let (elems, split_at) = U::LAYOUT._validate_cast_and_convert_metadata(
172154
AsAddress::addr(self.ptr.as_ptr()),
173-
self._bytes_len,
155+
self._len(),
174156
cast_type,
175157
)?;
176-
let (offset, ret_len) = match cast_type {
177-
_CastType::_Prefix => (0, split_at),
178-
// Guaranteed not to underflow:
179-
// `validate_cast_and_convert_metadata` promises that `split_at`
180-
// is in the range `[0, bytes_len]`.
181-
#[allow(clippy::arithmetic_side_effects)]
182-
_CastType::_Suffix => (split_at, self._bytes_len - split_at),
158+
let offset = match cast_type {
159+
_CastType::_Prefix => 0,
160+
_CastType::_Suffix => split_at,
183161
};
184162

185163
let ptr = self.ptr.cast::<u8>().as_ptr();
186164
// SAFETY: `offset` is either `0` or `split_at`.
187165
// `validate_cast_and_convert_metadata` promises that `split_at` is
188-
// in the range `[0, bytes_len]`. Thus, in both cases, `offset` is
189-
// in `[0, bytes_len]`. Thus:
166+
// in the range `[0, self.len()]`. Thus, in both cases, `offset` is
167+
// in `[0, self.len()]`. Thus:
190168
// - The resulting pointer is in or one byte past the end of the
191169
// same byte range as `self.ptr`. Since, by invariant, `self.ptr`
192170
// addresses a byte range entirely contained within a single
193171
// allocation, the pointer resulting from this operation is within
194172
// or one byte past the end of that same allocation.
195-
// - By invariant, `bytes_len <= isize::MAX`. Since `offset <=
196-
// bytes_len`, `offset <= isize::MAX`.
173+
// - By invariant, `self.len() <= isize::MAX`. Since `offset <=
174+
// self.len()`, `offset <= isize::MAX`.
197175
// - By invariant, `self.ptr` addresses a byte range which does not
198176
// wrap around the address space. This means that the base pointer
199-
// plus the `bytes_len` does not overflow `usize`. Since `offset
200-
// <= bytes_len`, this addition does not overflow `usize`.
177+
// plus the `self.len()` does not overflow `usize`. Since `offset
178+
// <= self.len()`, this addition does not overflow `usize`.
201179
let base = unsafe { ptr.add(offset) };
202180
// SAFETY: Since `add` is not allowed to wrap around, the preceding line
203181
// produces a pointer whose address is greater than or equal to that of
@@ -216,21 +194,16 @@ pub(crate) mod ptr {
216194
// is a subset of the input byte range. Thus:
217195
// - Since, by invariant, `self.ptr` addresses a byte range
218196
// entirely contained in `A`, so does `ptr`.
219-
// - Since, by invariant, `self.ptr` addresses a range of length
220-
// `self.bytes_len`, which is not longer than `isize::MAX`
221-
// bytes, so does `ptr`.
222-
// - `ret_len` is either `split_at` or `self.bytes_len -
223-
// split_at`. `validate_cast_and_convert_metadata` promises that
224-
// `split_at` is in the range `[0, self.bytes_len]`. Thus, in
225-
// both cases, `ret_len <= self.bytes_len <= isize::MAX`.
197+
// - Since, by invariant, `self.ptr` addresses a range whose
198+
// length is not longer than `isize::MAX` bytes, so does `ptr`.
226199
// - Since, by invariant, `self.ptr` addresses a range which does
227200
// not wrap around the address space, so does `ptr`.
228201
// - `validate_cast_and_convert_metadata` promises that the object
229202
// described by `split_at` is validly-aligned for `U`.
230203
// - By invariant on `self`, `A` is guaranteed to live for at least
231204
// `'a`.
232205
// - `U: 'a` by trait bound.
233-
Some((Ptr { ptr, _bytes_len: ret_len, _lifetime: PhantomData }, split_at))
206+
Some((Ptr { ptr, _lifetime: PhantomData }, split_at))
234207
}
235208

236209
/// Attempts to cast `self` into a `U`, failing if all of the bytes of
@@ -252,12 +225,47 @@ pub(crate) mod ptr {
252225
// details.
253226
#[allow(unstable_name_collisions)]
254227
match self._try_cast_into(_CastType::_Prefix) {
255-
Some((slf, split_at)) if split_at == self._bytes_len => Some(slf),
228+
Some((slf, split_at)) if split_at == self._len() => Some(slf),
256229
Some(_) | None => None,
257230
}
258231
}
259232
}
260233

234+
impl<'a, T> Ptr<'a, [T]> {
235+
/// The number of slice elements referenced by `self`.
236+
fn _len(&self) -> usize {
237+
#[allow(clippy::as_conversions)]
238+
let slc = self.ptr.as_ptr() as *const [()];
239+
// SAFETY:
240+
// - `()` has alignment 1, so `slc` is trivially aligned.
241+
// - `slc` was derived from a non-null pointer.
242+
// - The size is 0 regardless of the length, so it is sound to
243+
// materialize a reference regardless of location.
244+
// - Pointer provenance may be an issue, but we never dereference.
245+
let slc = unsafe { &*slc };
246+
// This is correct because the preceding `as` cast preserves the
247+
// number of slice elements. Per
248+
// https://doc.rust-lang.org/nightly/reference/expressions/operator-expr.html#slice-dst-pointer-to-pointer-cast:
249+
//
250+
// For slice types like `[T]` and `[U]`, the raw pointer types
251+
// `*const [T]`, `*mut [T]`, `*const [U]`, and `*mut [U]` encode
252+
// the number of elements in this slice. Casts between these raw
253+
// pointer types preserve the number of elements. Note that, as a
254+
// consequence, such casts do *not* necessarily preserve the size
255+
// of the pointer's referent (e.g., casting `*const [u16]` to
256+
// `*const [u8]` will result in a raw pointer which refers to an
257+
// object of half the size of the original). The same holds for
258+
// `str` and any compound type whose unsized tail is a slice type,
259+
// such as struct `Foo(i32, [u8])` or `(u64, Foo)`.
260+
//
261+
// TODO(#429),
262+
// TODO(https://github.com/rust-lang/reference/pull/1417): Once this
263+
// text is available on the Stable docs, cite those instead of the
264+
// Nightly docs.
265+
slc.len()
266+
}
267+
}
268+
261269
impl<'a, T: 'a + ?Sized> From<&'a T> for Ptr<'a, T> {
262270
#[inline(always)]
263271
fn from(t: &'a T) -> Ptr<'a, T> {
@@ -268,8 +276,7 @@ pub(crate) mod ptr {
268276
// has the same provenance as `A`
269277
// - Since `NonNull::from` creates a pointer which addresses the
270278
// same bytes as `t`, `ptr` addresses a byte range entirely
271-
// contained in (in this case, identical to) `A` of length
272-
// `mem::size_of_val(t)`
279+
// contained in (in this case, identical to) `A`
273280
// - Since `t: &T`, it addresses no more than `isize::MAX` bytes [1]
274281
// - Since `t: &T`, it addresses a byte range which does not wrap
275282
// around the address space [2]
@@ -290,7 +297,7 @@ pub(crate) mod ptr {
290297
// `isize`?
291298
// - [2] Where does the reference document that allocations don't
292299
// wrap around the address space?
293-
Ptr { ptr: NonNull::from(t), _bytes_len: mem::size_of_val(t), _lifetime: PhantomData }
300+
Ptr { ptr: NonNull::from(t), _lifetime: PhantomData }
294301
}
295302
}
296303

@@ -303,7 +310,7 @@ pub(crate) mod ptr {
303310

304311
#[cfg(test)]
305312
mod tests {
306-
use core::mem::MaybeUninit;
313+
use core::mem::{self, MaybeUninit};
307314

308315
use super::*;
309316
use crate::{util::testutil::AU64, FromBytes};

0 commit comments

Comments
 (0)