Skip to content
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

Add stronger alternatives to align_to #105296

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 202 additions & 0 deletions library/core/src/slice/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3485,6 +3485,10 @@ impl<T> [T] {
/// matter, such as a sanitizer attempting to find alignment bugs. Regular code running
/// in a default (debug or release) execution *will* return a maximal middle part.
///
/// If this behavior is not what you desire, as you don't want fallback paths for the bytes
/// outside the aligned part, consider using [aligned_subslice] or [transmute_elements] instead,
/// as these have stronger guarantees.
///
/// This method has no purpose when either input element `T` or output element `U` are
/// zero-sized and will return the original slice without splitting anything.
///
Expand Down Expand Up @@ -3547,6 +3551,10 @@ impl<T> [T] {
/// matter, such as a sanitizer attempting to find alignment bugs. Regular code running
/// in a default (debug or release) execution *will* return a maximal middle part.
///
/// If this behavior is not what you desire, as you don't want fallback paths for the bytes
/// outside the aligned part, consider using [aligned_subslice_mut] or [transmute_elements_mut]
/// instead, as these have stronger guarantees.
///
/// This method has no purpose when either input element `T` or output element `U` are
/// zero-sized and will return the original slice without splitting anything.
///
Expand Down Expand Up @@ -3607,6 +3615,200 @@ impl<T> [T] {
}
}

/// Get a subslice where the first element is aligned to a given alignment and the size
/// of the slice is a multiple of the alignment.
oli-obk marked this conversation as resolved.
Show resolved Hide resolved
///
/// # Panics
///
/// This method requires the alignment to be a multiple (larger than 1) of the alignment of
/// the slice element's alignment.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// #![feature(slice_align_to_ish)]
/// let bytes: [u8; 7] = [1, 2, 3, 4, 5, 6, 7];
/// let ints = bytes.aligned_subslice(std::mem::align_of::<u32>());
/// assert_eq!(ints.len(), 1);
/// ```
#[must_use]
#[unstable(feature = "slice_align_to_ish", issue = "none")]
#[inline]
pub fn aligned_subslice(&self, align: usize) -> &[T] {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the align be const? Does one ever need dynamic alignment?

let size = crate::mem::size_of::<T>();
assert!(
size < align,
"aligned_subslice does nothing for alignments below or at the element type's alignment"
);
assert!(
align % size == 0,
"aligned_subslice only works for alignments that are multiples of the element's size"
);
let offset = self.as_ptr().addr() % align;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this align is ever a non-constant it'll have to actually emit the division.

Consider taking https://doc.rust-lang.org/std/ptr/struct.Alignment.html instead so you can optimize based on it always being a power of two.

// SAFETY: See the `align_to_mut` method for the detailed safety comment.
let end_offset = unsafe { self.as_ptr().offset(self.len() as isize) }.addr() % align;
let end = self.len() - (align / size - end_offset);
&self[offset..end]
}

/// Get a subslice where the first element is aligned to a given alignment and the size
/// of the slice is a multiple of the alignment.
///
/// # Panics
///
/// This method requires the alignment to be a multiple (larger than 1) of the alignment of
/// the slice element's alignment.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// #![feature(slice_align_to_ish)]
/// let mut bytes: [u8; 7] = [1, 2, 3, 4, 5, 6, 7];
/// let ints = bytes.aligned_subslice_mut(std::mem::align_of::<u32>());
/// assert_eq!(ints.len(), 1);
/// ```
#[must_use]
#[unstable(feature = "slice_align_to_ish", issue = "none")]
#[inline]
pub fn aligned_subslice_mut(&mut self, align: usize) -> &mut [T] {
let size = crate::mem::size_of::<T>();
assert!(
size < align,
"aligned_subslice does nothing for alignments below or at the element type's alignment"
);
assert!(
align % size == 0,
"aligned_subslice only works for alignments that are multiples of the element's size"
);
let offset = self.as_ptr().addr() % align;
// SAFETY: See the `align_to_mut` method for the detailed safety comment.
let end_offset = unsafe { self.as_ptr().offset(self.len() as isize) }.addr() % align;
let end = self.len() - (align / size - end_offset);
&mut self[offset..end]
}

/// Transmute the slice elements to another type.
///
/// If the target element type is smaller than the source element type, the
/// returned slice will have multiple elements per element of the original slice.
///
/// Cannot be used to go to an element type with higher alignment requirements.
/// Use `aligned_subslice` for that instead.
///
/// # Panics
///
/// The element sizes and the slice length must be such that all elements of the source
/// slice fit exactly into a slice of the destination element type. Resize your input slice
/// before invoking `transmute_elements` to uphold this checked requirement.
///
/// # Safety
///
/// This method is essentially a `transmute` between different elements, and even from
/// multiple elements into a single one or vice versa, so all the usual caveats
/// pertaining to `transmute::<T, U>` also apply here.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// unsafe {
/// let ints: [u32; 2] = [1, 2];
/// let smaller_ints = ints.transmute_elements::<u16>();
/// assert_eq!(smaller_ints.len(), 4);
/// }
/// ```
#[must_use]
#[unstable(feature = "slice_align_to_ish", issue = "none")]
#[track_caller]
pub const unsafe fn transmute_elements<U>(&self) -> &[U] {
const {
let align_u = crate::mem::align_of::<U>();
let align_t = crate::mem::align_of::<T>();
assert!(align_u <= align_t, "use `aligned_subslice` instead");
};
let size_u = crate::mem::size_of::<U>();
let size_t = crate::mem::size_of::<T>();
if size_u > size_t {
assert!(
self.len() * size_u % size_t == 0,
"input slice does not fit exactly into a slice of the output element type"
);
} else {
assert!(
self.len() * size_t % size_u == 0,
"input slice does not fit exactly into a slice of the output element type"
);
}
Comment on lines +3736 to +3746
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do the panics get eliminated when the types are exact multiples in size and alignment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, I should optimize that.

// SAFETY: The size of the slice is such that with the new element size, all new
// elements are still within the bounds of the original slice. The change in element
// type is something the caller needs to make sure is sound.
unsafe { from_raw_parts(self.as_ptr() as *const _, self.len() * size_t / size_u) }
}

/// Transmute the slice elements to another type.
///
/// If the target element type is smaller than the source element type, the
/// returned slice will have multiple elements per element of the original slice.
///
/// Cannot be used to go to an element type with higher alignment requirements.
/// Use `aligned_subslice_mut` for that instead.
///
/// # Panics
///
/// The element sizes and the slice length must be such that all elements of the source
/// slice fit exactly into a slice of the destination element type. Resize your input slice
/// before invoking `transmute_elements_mut` to uphold this checked requirement.
///
/// # Safety
///
/// This method is essentially a `transmute` between different elements, and even from
/// multiple elements into a single one or vice versa, so all the usual caveats
/// pertaining to `transmute::<T, U>` also apply here.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// unsafe {
/// let mut ints: [u32; 2] = [1, 2];
/// let smaller_ints = ints.transmute_elements_mut::<u16>();
/// assert_eq!(smaller_ints.len(), 4);
/// }
/// ```
#[must_use]
#[unstable(feature = "slice_align_to_ish", issue = "none")]
#[track_caller]
pub const unsafe fn transmute_elements_mut<U>(&mut self) -> &mut [U] {
const {
let align_u = crate::mem::align_of::<U>();
let align_t = crate::mem::align_of::<T>();
assert!(align_u <= align_t, "use `aligned_subslice_mut` instead");
};
let size_u = crate::mem::size_of::<U>();
let size_t = crate::mem::size_of::<T>();
if size_u > size_t {
assert!(
self.len() * size_u % size_t == 0,
"input slice does not fit exactly into a slice of the output element type"
);
} else {
assert!(
self.len() * size_t % size_u == 0,
"input slice does not fit exactly into a slice of the output element type"
);
}
// SAFETY: The size of the slice is such that with the new element size, all new
// elements are still within the bounds of the original slice. The change in element
// type is something the caller needs to make sure is sound.
unsafe { from_raw_parts_mut(self.as_mut_ptr() as *mut _, self.len() * size_t / size_u) }
}

/// Split a slice into a prefix, a middle of aligned SIMD types, and a suffix.
///
/// This is a safe wrapper around [`slice::align_to`], so has the same weak
Expand Down