-
Couldn't load subscription status.
- Fork 13.9k
Add clarifying context to the most confusing pointer APIs #95851
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -520,9 +520,16 @@ impl<T: ?Sized> *const T { | |
| /// Calculates the distance between two pointers. The returned value is in | ||
| /// units of T: the distance in bytes is divided by `mem::size_of::<T>()`. | ||
| /// | ||
| /// This function is the inverse of [`offset`]. | ||
| /// This is equivalent to `(self as isize - other as isize) / (mem::size_of::<T>() as isize)`, | ||
| /// except that it has a lot more opportunities for UB, in exchange for the compiler | ||
| /// understanding what you're doing better. | ||
| /// | ||
| /// [`offset`]: #method.offset | ||
| /// The primary motivation of this method is for computing the `len` of an array/slice | ||
| /// of `T` that you are currently representing as a "start" and "end" pointer | ||
| /// (and "end" is "one past the end" of the array). | ||
| /// In that case, `end.offset_from(start)` gets you the length of the array. | ||
| /// | ||
| /// All of the following safety requirements are trivially satisfied for this usecase. | ||
| /// | ||
| /// # Safety | ||
| /// | ||
|
|
@@ -560,6 +567,7 @@ impl<T: ?Sized> *const T { | |
| /// such large allocations either.) | ||
| /// | ||
| /// [`add`]: #method.add | ||
| /// [`offset`]: #method.offset | ||
| /// [allocated object]: crate::ptr#allocated-object | ||
| /// | ||
| /// # Panics | ||
|
|
@@ -1021,6 +1029,31 @@ impl<T: ?Sized> *const T { | |
| /// Computes the offset that needs to be applied to the pointer in order to make it aligned to | ||
| /// `align`. | ||
| /// | ||
| /// Before getting into the precise semantics, it helps to have some context for why this | ||
| /// operation seems so weird on the surface. This is for two reasons: | ||
| /// | ||
| /// * It's for SIMD, specifically [`slice::align_to`] | ||
| /// * It wants code that uses SIMD operations to work in a `const fn` | ||
| /// | ||
| /// In particular, this operation is intended for breaking up a loop into its component | ||
| /// unaligned and aligned parts, so that the aligned parts can be processed using SIMD. | ||
| /// For that kind of pattern it's always ok to "fail" to align the pointer, because the | ||
| /// unaligned path can always handle all the data. | ||
| /// | ||
| /// Lots of really basic operations want to use SIMD under the hood, so it would be very | ||
| /// frustrating if using this pattern made it impossible for an operation to work in | ||
| /// const contexts. Unfortunately, observing any properties of a pointer's address | ||
| /// is a very dangerous and problematic operation in const contexts, because it's a huge | ||
| /// reproducibility issue (which has actual soundness implications with compilation units). | ||
|
||
| /// | ||
| /// Thankfully, because the precise SIMD pattern we're interested in *already* has a fallback | ||
| /// mode where the alignment completely fails, the compiler can just make this operation always | ||
| /// fail to align the pointer when doing const evaluation, and then everything is | ||
| /// always deterministic and reproducible (and the const evaluator is an interpreter anyway, | ||
| /// so you weren't actually going to get amazing SIMD speedups). | ||
|
Comment on lines
+1049
to
+1053
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. More directly: given a slice with a random position in memory, we can't know the head of the slice is SIMD-aligned, and given a slice, we can have any number of values. Thus we must have a head and tail handling routine, because asserting on the size is not good enough (may be off-alignment) and asserting on the alignment is not good enough (may be an uneven number of elements, thus not trivially vectorized without tail-handling), and asserting on both just gives you a panic generator where a useful program should be, because you usually don't really have any way to pre-establish those guarantees in a useful manner based on the type information currently present in the system. And if you actually do, you have much bigger tricks to deploy on your code than this. Thus, "you better believe you are going to be writing code to handle this correctly, Miri's semantics be damned." |
||
| /// | ||
| /// Alright, now on to the actual semantics: | ||
| /// | ||
| /// If it is not possible to align the pointer, the implementation returns | ||
| /// `usize::MAX`. It is permissible for the implementation to *always* | ||
| /// return `usize::MAX`. Only your algorithm's performance can depend | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NB: I dropped the note that it was the "inverse" of offset because this is a genuinely confusing statement.
offsetis an asymmetric binary operation, it's like talking about the "inverse" of xy. There are two answers! (yth root and logx)