pub struct Simd<T, const N: usize>(/* private fields */) where LaneCount<N>: SupportedLaneCount, T: SimdElement;
portable_simd
A SIMD vector with the shape of [T; N] but the operations of T.
[T; N]
T
Simd<T, N> supports the operators (+, *, etc.) that T does in “elementwise” fashion. These take the element at each index from the left-hand side and right-hand side, perform the operation, then return the result in the same index in a vector of equal size. However, Simd differs from normal iteration and normal arrays:
Simd<T, N>
Simd
N
break
By always imposing these constraints on Simd, it is easier to compile elementwise operations into machine instructions that can themselves be executed in parallel.
let a: [i32; 4] = [-2, 0, 2, 4]; let b = [10, 9, 8, 7]; let sum = array::from_fn(|i| a[i] + b[i]); let prod = array::from_fn(|i| a[i] * b[i]); / `Simd<T, N>` implements `From<[T; N]>` let (v, w) = (Simd::from(a), Simd::from(b)); / Which means arrays implement `Into<Simd<T, N>>`. assert_eq!(v + w, sum.into()); assert_eq!(v * w, prod.into());
Simd with integer elements treats operators as wrapping, as if T was Wrapping<T>. Thus, Simd does not implement wrapping_add, because that is the default behavior. This means there is no warning on overflows, even in “debug” builds. For most applications where Simd is appropriate, it is “not a bug” to wrap, and even “debug builds” are unlikely to tolerate the loss of performance. You may want to consider using explicitly checked arithmetic if such is required. Division by zero on integers still causes a panic, so you may want to consider using f32 or f64 if that is unacceptable.
Wrapping<T>
wrapping_add
f32
f64
Simd<T, N> has a layout similar to [T; N] (identical “shapes”), with a greater alignment. [T; N] is aligned to T, but Simd<T, N> will have an alignment based on both T and N. Thus it is sound to transmute Simd<T, N> to [T; N] and should optimize to “zero cost”, but the reverse transmutation may require a copy the compiler cannot simply elide.
transmute
Due to Rust’s safety guarantees, Simd<T, N> is currently passed and returned via memory, not SIMD registers, except as an optimization. Using #[inline] on functions that accept Simd<T, N> or return it is recommended, at the cost of code generation time, as inlining SIMD-using functions can omit a large function prolog or epilog and thus improve both speed and code size. The need for this may be corrected in the future.
#[inline]
Using #[inline(always)] still requires additional care.
#[inline(always)]
Operations with Simd are typically safe, but there are many reasons to want to combine SIMD with unsafe code. Care must be taken to respect differences between Simd and other types it may be transformed into or derived from. In particular, the layout of Simd<T, N> may be similar to [T; N], and may allow some transmutations, but references to [T; N] are not interchangeable with those to Simd<T, N>. Thus, when using unsafe Rust to read and write Simd<T, N> through raw pointers, it is a good idea to first try with read_unaligned and write_unaligned. This is because:
unsafe
read_unaligned
write_unaligned
read
write
[T]
Less obligations mean unaligned reads and writes are less likely to make the program unsound, and may be just as fast as stricter alternatives. When trying to guarantee alignment, [T]::as_simd is an option for converting [T] to [Simd<T, N>], and allows soundly operating on an aligned SIMD body, but it may cost more time when handling the scalar head and tail. If these are not enough, it is most ideal to design data structures to be already aligned to align_of::<Simd<T, N>>() before using unsafe Rust to read or write. Other ways to compensate for these facts, like materializing Simd to or from an array first, are handled by safe methods like Simd::from_array and Simd::from_slice.
[T]::as_simd
[Simd<T, N>]
align_of::<Simd<T, N>>()
Simd::from_array
Simd::from_slice
Reverse the order of the elements in the vector.
Rotates the vector such that the first OFFSET elements of the slice move to the end while the last self.len() - OFFSET elements move to the front. After calling rotate_elements_left, the element previously at index OFFSET will become the first element in the slice.
OFFSET
self.len() - OFFSET
rotate_elements_left
let a = Simd::from_array([0, 1, 2, 3]); let x = a.rotate_elements_left::<3>(); assert_eq!(x.to_array(), [3, 0, 1, 2]); let y = a.rotate_elements_left::<7>(); assert_eq!(y.to_array(), [3, 0, 1, 2]);
Rotates the vector such that the first self.len() - OFFSET elements of the vector move to the end while the last OFFSET elements move to the front. After calling rotate_elements_right, the element previously at index self.len() - OFFSET will become the first element in the slice.
rotate_elements_right
let a = Simd::from_array([0, 1, 2, 3]); let x = a.rotate_elements_right::<3>(); assert_eq!(x.to_array(), [1, 2, 3, 0]); let y = a.rotate_elements_right::<7>(); assert_eq!(y.to_array(), [1, 2, 3, 0]);
Shifts the vector elements to the left by OFFSET, filling in with padding from the right.
padding
let a = Simd::from_array([0, 1, 2, 3]); let x = a.shift_elements_left::<3>(255); assert_eq!(x.to_array(), [3, 255]); let y = a.shift_elements_left::<7>(255); assert_eq!(y.to_array(), [255, 255]);
Shifts the vector elements to the right by OFFSET, filling in with padding from the left.
let a = Simd::from_array([0, 1, 2, 3]); let x = a.shift_elements_right::<3>(255); assert_eq!(x.to_array(), [255, 255, 0]); let y = a.shift_elements_right::<7>(255); assert_eq!(y.to_array(), [255, 255]);
Interleave two vectors.
The resulting vectors contain elements taken alternatively from self and other, first filling the first result, and then the second.
self
other
The reverse of this operation is Simd::deinterleave.
Simd::deinterleave
let a = Simd::from_array([0, 1, 2, 3]); let b = Simd::from_array([4, 5, 6, 7]); let (x, y) = a.interleave(b); assert_eq!(x.to_array(), [0, 4, 1, 5]); assert_eq!(y.to_array(), [2, 6, 3, 7]);
Deinterleave two vectors.
The first result takes every other element of self and then other, starting with the first element.
The second result takes every other element of self and then other, starting with the second element.
The reverse of this operation is Simd::interleave.
Simd::interleave
let a = Simd::from_array([0, 4, 1, 5]); let b = Simd::from_array([2, 6, 3, 7]); let (x, y) = a.deinterleave(b); assert_eq!(x.to_array(), [0, 1, 2, 3]); assert_eq!(y.to_array(), [4, 5, 6, 7]);
Resize a vector.
If M > N, extends the length of a vector, setting the new elements to value. If M < N, truncates the vector to the first M elements.
M
value
let x = u32x4::from_array([0, 1, 2, 3]); assert_eq!(x.resize::<8>(9).to_array(), [0, 1, 2, 3, 9]); assert_eq!(x.resize::<2>(9).to_array(), [0, 1]);
Extract a vector from another vector.
let x = u32x4::from_array([0, 1, 2, 3]); assert_eq!(x.extract::<1, 2>().to_array(), [1, 2]);
Swizzle a vector of bytes according to the index vector. Indices within range select the appropriate byte. Indices “out of bounds” instead select 0.
Note that the current implementation is selected during build-time of the standard library, so cargo build -Zbuild-std may be necessary to unlock better performance, especially for larger vectors. A planned compiler improvement will enable using #[target_feature] instead.
cargo build -Zbuild-std
#[target_feature]
Number of elements in this vector.
Returns the number of elements in this SIMD vector.
let v = u32x4::splat(0); assert_eq!(v.len(), 4);
Constructs a new SIMD vector with all elements set to the given value.
let v = u32x4::splat(8); assert_eq!(v.as_array(), &[8, 8]);
Returns an array reference containing the entire SIMD vector.
let v: u64x4 = Simd::from_array([0, 1, 2, 3]); assert_eq!(v.as_array(), &[0, 1, 2, 3]);
Returns a mutable array reference containing the entire SIMD vector.
Converts an array to a SIMD vector.
Converts a SIMD vector to an array.
Converts a slice to a SIMD vector containing slice[..N].
slice[..N]
Panics if the slice’s length is less than the vector’s Simd::N. Use load_or_default for an alternative that does not panic.
Simd::N
load_or_default
let source = vec![1, 2, 3, 4, 5, 6]; let v = u32x4::from_slice(&source); assert_eq!(v.as_array(), &[1, 2, 3, 4]);
Writes a SIMD vector to the first N elements of a slice.
Panics if the slice’s length is less than the vector’s Simd::N.
let mut dest = vec![0; 6]; let v = u32x4::from_array([1, 2, 3, 4]); v.copy_to_slice(&mut dest); assert_eq!(&dest, &[1, 2, 3, 4, 0]);
Reads contiguous elements from slice. Elements are read so long as they’re in-bounds for the slice. Otherwise, the default value for the element type is returned.
slice
let vec: Vec<i32> = vec![10, 11]; let result = Simd::<i32, 4>::load_or_default(&vec); assert_eq!(result, Simd::from_array([10, 11, 0]));
Reads contiguous elements from slice. Elements are read so long as they’re in-bounds for the slice. Otherwise, the corresponding value from or is passed through.
or
let vec: Vec<i32> = vec![10, 11]; let or = Simd::from_array([-5, -4, -3, -2]); let result = Simd::load_or(&vec, or); assert_eq!(result, Simd::from_array([10, 11, -3, -2]));
Reads contiguous elements from slice. Each element is read from memory if its corresponding element in enable is true.
enable
true
When the element is disabled or out of bounds for the slice, that memory location is not accessed and the corresponding value from or is passed through.
let vec: Vec<i32> = vec![10, 11, 12, 13, 14, 15, 16, 17, 18]; let enable = Mask::from_array([true, true, false, true]); let or = Simd::from_array([-5, -4, -3, -2]); let result = Simd::load_select(&vec, enable, or); assert_eq!(result, Simd::from_array([10, 11, -3, 13]));
When the element is disabled, that memory location is not accessed and the corresponding value from or is passed through.
Enabled loads must not exceed the length of slice.
Reads contiguous elements starting at ptr. Each element is read from memory if its corresponding element in enable is true.
ptr
Enabled ptr elements must be safe to read as if by std::ptr::read.
std::ptr::read
Reads from potentially discontiguous indices in slice to construct a SIMD vector. If an index is out-of-bounds, the element is instead selected from the or vector.
let vec: Vec<i32> = vec![10, 11, 12, 13, 14, 15, 16, 17, 18]; let idxs = Simd::from_array([9, 3, 0, 5]); / Note the index that is out-of-bounds let alt = Simd::from_array([-5, -4, -3, -2]); let result = Simd::gather_or(&vec, idxs, alt); assert_eq!(result, Simd::from_array([-5, 13, 10, 15]));
Reads from indices in slice to construct a SIMD vector. If an index is out-of-bounds, the element is set to the default given by T: Default.
T: Default
let vec: Vec<i32> = vec![10, 11, 12, 13, 14, 15, 16, 17, 18]; let idxs = Simd::from_array([9, 3, 0, 5]); / Note the index that is out-of-bounds let result = Simd::gather_or_default(&vec, idxs); assert_eq!(result, Simd::from_array([0, 13, 10, 15]));
Reads from indices in slice to construct a SIMD vector. The mask enables all true indices and disables all false indices. If an index is disabled or is out-of-bounds, the element is selected from the or vector.
false
let vec: Vec<i32> = vec![10, 11, 12, 13, 14, 15, 16, 17, 18]; let idxs = Simd::from_array([9, 3, 0, 5]); / Includes an out-of-bounds index let alt = Simd::from_array([-5, -4, -3, -2]); let enable = Mask::from_array([true, true, false]); / Includes a masked element let result = Simd::gather_select(&vec, enable, idxs, alt); assert_eq!(result, Simd::from_array([-5, 13, 10, -2]));
Reads from indices in slice to construct a SIMD vector. The mask enables all true indices and disables all false indices. If an index is disabled, the element is selected from the or vector.
Calling this function with an enabled out-of-bounds index is undefined behavior even if the resulting value is not used.
let vec: Vec<i32> = vec![10, 11, 12, 13, 14, 15, 16, 17, 18]; let idxs = Simd::from_array([9, 3, 0, 5]); / Includes an out-of-bounds index let alt = Simd::from_array([-5, -4, -3, -2]); let enable = Mask::from_array([true, true, false]); / Includes a masked element / If this mask was used to gather, it would be unsound. Let's fix that. let enable = enable & idxs.simd_lt(Simd::splat(vec.len())); / The out-of-bounds index has been masked, so it's safe to gather now. let result = unsafe { Simd::gather_select_unchecked(&vec, enable, idxs, alt) }; assert_eq!(result, Simd::from_array([-5, 13, 10, -2]));
Reads elementwise from pointers into a SIMD vector.
Each read must satisfy the same conditions as core::ptr::read.
core::ptr::read
let values = [6, 2, 4, 9]; let offsets = Simd::from_array([1, 0, 3]); let source = Simd::splat(values.as_ptr()).wrapping_add(offsets); let gathered = unsafe { Simd::gather_ptr(source) }; assert_eq!(gathered, Simd::from_array([2, 6, 9]));
Conditionally read elementwise from pointers into a SIMD vector. The mask enables all true pointers and disables all false pointers. If a pointer is disabled, the element is selected from the or vector, and no read is performed.
Enabled elements must satisfy the same conditions as core::ptr::read.
let values = [6, 2, 4, 9]; let enable = Mask::from_array([true, true, false, true]); let offsets = Simd::from_array([1, 0, 3]); let source = Simd::splat(values.as_ptr()).wrapping_add(offsets); let gathered = unsafe { Simd::gather_select_ptr(source, enable, Simd::splat(0)) }; assert_eq!(gathered, Simd::from_array([2, 6, 0, 9]));
Conditionally write contiguous elements to slice. The enable mask controls which elements are written, as long as they’re in-bounds of the slice. If the element is disabled or out of bounds, no memory access to that location is made.
let mut arr = [0i32; 4]; let write = Simd::from_array([-5, -4, -3, -2]); let enable = Mask::from_array([false, true]); write.store_select(&mut arr[..3], enable); assert_eq!(arr, [0, -4, -3, 0]);
Conditionally write contiguous elements to slice. The enable mask controls which elements are written.
Every enabled element must be in bounds for the slice.
let mut arr = [0i32; 4]; let write = Simd::from_array([-5, -4, -3, -2]); let enable = Mask::from_array([false, true]); unsafe { write.store_select_unchecked(&mut arr, enable) }; assert_eq!(arr, [0, -4, -3, -2]);
Conditionally write contiguous elements starting from ptr. The enable mask controls which elements are written. When disabled, the memory location corresponding to that element is not accessed.
Memory addresses for element are calculated pointer::wrapping_offset and each enabled element must satisfy the same conditions as core::ptr::write.
pointer::wrapping_offset
core::ptr::write
Writes the values in a SIMD vector to potentially discontiguous indices in slice. If an index is out-of-bounds, the write is suppressed without panicking. If two elements in the scattered vector would write to the same index only the last element is guaranteed to actually be written.
let mut vec: Vec<i32> = vec![10, 11, 12, 13, 14, 15, 16, 17, 18]; let idxs = Simd::from_array([9, 3, 0]); / Note the duplicate index. let vals = Simd::from_array([-27, 82, -41, 124]); vals.scatter(&mut vec, idxs); / two logical writes means the last wins. assert_eq!(vec, vec![124, 11, 12, 82, 14, 15, 16, 17, 18]);
Writes values from a SIMD vector to multiple potentially discontiguous indices in slice. The mask enables all true indices and disables all false indices. If an enabled index is out-of-bounds, the write is suppressed without panicking. If two enabled elements in the scattered vector would write to the same index, only the last element is guaranteed to actually be written.
let mut vec: Vec<i32> = vec![10, 11, 12, 13, 14, 15, 16, 17, 18]; let idxs = Simd::from_array([9, 3, 0]); / Includes an out-of-bounds index let vals = Simd::from_array([-27, 82, -41, 124]); let enable = Mask::from_array([true, true, false]); / Includes a masked element vals.scatter_select(&mut vec, enable, idxs); / The last write is masked, thus omitted. assert_eq!(vec, vec![-41, 11, 12, 82, 14, 15, 16, 17, 18]);
Writes values from a SIMD vector to multiple potentially discontiguous indices in slice. The mask enables all true indices and disables all false indices. If two enabled elements in the scattered vector would write to the same index, only the last element is guaranteed to actually be written.
Calling this function with an enabled out-of-bounds index is undefined behavior, and may lead to memory corruption.
let mut vec: Vec<i32> = vec![10, 11, 12, 13, 14, 15, 16, 17, 18]; let idxs = Simd::from_array([9, 3, 0]); let vals = Simd::from_array([-27, 82, -41, 124]); let enable = Mask::from_array([true, true, false]); / Masks the final index / If this mask was used to scatter, it would be unsound. Let's fix that. let enable = enable & idxs.simd_lt(Simd::splat(vec.len())); / We have masked the OOB index, so it's safe to scatter now. unsafe { vals.scatter_select_unchecked(&mut vec, enable, idxs); } / The second write to index 0 was masked, thus omitted. assert_eq!(vec, vec![-41, 11, 12, 82, 14, 15, 16, 17, 18]);
Writes pointers elementwise into a SIMD vector.
Each write must satisfy the same conditions as core::ptr::write.
let mut values = [0; 4]; let offset = Simd::from_array([3, 2, 1, 0]); let ptrs = Simd::splat(values.as_mut_ptr()).wrapping_add(offset); unsafe { Simd::from_array([6, 3, 5, 7]).scatter_ptr(ptrs); } assert_eq!(values, [7, 5, 3, 6]);
Conditionally write pointers elementwise into a SIMD vector. The mask enables all true pointers and disables all false pointers. If a pointer is disabled, the write to its pointee is skipped.
Enabled pointers must satisfy the same conditions as core::ptr::write.
let mut values = [0; 4]; let offset = Simd::from_array([3, 2, 1, 0]); let ptrs = Simd::splat(values.as_mut_ptr()).wrapping_add(offset); let enable = Mask::from_array([true, true, false]); unsafe { Simd::from_array([6, 3, 5, 7]).scatter_select_ptr(ptrs, enable); } assert_eq!(values, [0, 0, 3, 6]);
+
&
|
^
A Simd<T, N> has a debug format like the one for [T]:
let floats = Simd::<f32, 4>::splat(-1.0); assert_eq!(format!("{:?}", [-1.0; 4]), format!("{:?}", floats));
/