-
Notifications
You must be signed in to change notification settings - Fork 88
Implement common swizzle operations. #335
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
base: master
Are you sure you want to change the base?
Changes from 4 commits
71baa3f
c28564d
ffe58c9
5d30d9f
024fb9a
4575d28
94b2f08
ae495bd
0c9a7ab
14904c9
73621ab
8c07d1a
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 |
---|---|---|
|
@@ -364,4 +364,131 @@ where | |
|
||
(Even::swizzle2(self, other), Odd::swizzle2(self, other)) | ||
} | ||
|
||
/// Takes a slice of a vector to produce a shorter vector. | ||
/// | ||
/// This is equivalent to computing `&self[OFFSET..OFFSET+LEN]` on | ||
/// the underlying array. | ||
/// | ||
/// ``` | ||
/// # #![feature(portable_simd)] | ||
/// # use core::simd::Simd; | ||
/// let x = Simd::from_array([0, 1, 2, 3, 4, 5, 6, 7]); | ||
/// let y = x.slice::<2, 4>(); | ||
/// assert_eq!(y.to_array(), [2, 3, 4, 5]); | ||
reinerp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// ``` | ||
/// | ||
/// Will be rejected at compile time if `OFFSET + LEN > LANES`. | ||
reinerp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
#[inline] | ||
#[must_use = "method returns a new vector and does not mutate the original inputs"] | ||
pub fn slice<const OFFSET: usize, const LEN: usize>(self) -> Simd<T, LEN> | ||
reinerp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
where | ||
LaneCount<LEN>: SupportedLaneCount, | ||
{ | ||
const fn slice_index<const LEN: usize>(offset: usize, lanes: usize) -> [usize; LEN] { | ||
assert!(offset + LEN <= lanes, "slice out of bounds"); | ||
let mut index = [0; LEN]; | ||
let mut i = 0; | ||
while i < LEN { | ||
index[i] = i + offset; | ||
i += 1; | ||
} | ||
index | ||
} | ||
struct Slice<const OFFSET: usize>; | ||
impl<const OFFSET: usize, const LEN: usize, const LANES: usize> Swizzle<LANES, LEN> | ||
for Slice<OFFSET> | ||
{ | ||
const INDEX: [usize; LEN] = slice_index::<LEN>(OFFSET, LANES); | ||
} | ||
Slice::<OFFSET>::swizzle(self) | ||
} | ||
|
||
/// Concatenates two vectors of equal length. | ||
/// | ||
/// Due to limitations in const generics, the length of the resulting vector cannot be inferred | ||
/// from the input vectors. You must specify it explicitly. | ||
/// | ||
/// ``` | ||
/// # #![feature(portable_simd)] | ||
/// # use core::simd::Simd; | ||
/// let x = Simd::from_array([0, 1, 2, 3]); | ||
/// let y = Simd::from_array([4, 5, 6, 7]); | ||
/// let z = x.concat_to::<8>(y); | ||
/// assert_eq!(z.to_array(), [0, 1, 2, 3, 4, 5, 6, 7]); | ||
/// ``` | ||
/// | ||
/// Will be rejected at compile time if `LANES * 2 != DOUBLE_LANES`. | ||
reinerp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
#[inline] | ||
#[must_use = "method returns a new vector and does not mutate the original inputs"] | ||
pub fn concat_to<const DOUBLE_LANES: usize>(self, other: Self) -> Simd<T, DOUBLE_LANES> | ||
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.
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. I chose
The idea of the Open to other suggestions. One option would be to call it 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. Oh hm.
reinerp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
where | ||
LaneCount<DOUBLE_LANES>: SupportedLaneCount, | ||
{ | ||
const fn concat_index<const DOUBLE_LANES: usize>(lanes: usize) -> [Which; DOUBLE_LANES] { | ||
assert!(lanes * 2 == DOUBLE_LANES); | ||
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. Just checking: Are you aware that when you put something in a 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. it's only used at compile time, so that doesn't matter here. 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. Ah, right, I see now, it happens in the associated constant. Then that requires monomorphization which puts it at risk of depending on trait and const evaluation details. Hmm. I do want to allow us to use certain post-monomorphization errors but I currently don't fully understand the evaluation patterns that rustc will do to intuit in what cases this will/will not happen. There are cases where, due to various inputs you can give to the compiler, "dead" code can potentially get resurrected and monomorphized anyways (starting, of course, with 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. The guarantee: if you don't instantiate this generic at an invalid input/output type pair, then you won't get a post-monomorphization error. So this is like a type error, except with worse ergonomics because it's raised during monomorphization rather than type checking. If we had More broadly, I would like to be able to use |
||
let mut index = [Which::First(0); DOUBLE_LANES]; | ||
let mut i = 0; | ||
while i < lanes { | ||
index[i] = Which::First(i); | ||
index[i + lanes] = Which::Second(i); | ||
i += 1; | ||
} | ||
index | ||
} | ||
struct Concat; | ||
impl<const LANES: usize, const DOUBLE_LANES: usize> Swizzle2<LANES, DOUBLE_LANES> for Concat { | ||
const INDEX: [Which; DOUBLE_LANES] = concat_index::<DOUBLE_LANES>(LANES); | ||
} | ||
Concat::swizzle2(self, other) | ||
} | ||
|
||
/// For each lane `i`, swaps it with lane `i ^ SWAP_MASK`. | ||
/// | ||
/// Also known as `grev` in the RISC-V Bitmanip specification, this is a powerful | ||
reinerp marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// swizzle operation that can implement many common patterns as special cases. | ||
/// | ||
/// ``` | ||
/// # #![feature(portable_simd)] | ||
/// # use core::simd::Simd; | ||
/// let x = Simd::from_array([0, 1, 2, 3, 4, 5, 6, 7]); | ||
/// // Swap adjacent lanes: | ||
/// assert_eq!(x.general_reverse::<1>().to_array(), [1, 0, 3, 2, 5, 4, 7, 6]); | ||
/// // Swap lanes separated by distance 2: | ||
/// assert_eq!(x.general_reverse::<2>().to_array(), [2, 3, 0, 1, 6, 7, 4, 5]); | ||
/// // Swap lanes separated by distance 4: | ||
/// assert_eq!(x.general_reverse::<4>().to_array(), [4, 5, 6, 7, 0, 1, 2, 3]); | ||
/// // Reverse lanes, within each 4-lane group: | ||
/// assert_eq!(x.general_reverse::<3>().to_array(), [3, 2, 1, 0, 7, 6, 5, 4]); | ||
/// ``` | ||
/// | ||
/// Commonly useful for horizontal reductions, for example: | ||
/// | ||
/// ``` | ||
/// # #![feature(portable_simd)] | ||
/// # use core::simd::Simd; | ||
/// let x = Simd::from_array([0u32, 1, 2, 3, 4, 5, 6, 7]); | ||
/// let x = x + x.general_reverse::<1>(); | ||
/// let x = x + x.general_reverse::<2>(); | ||
/// let x = x + x.general_reverse::<4>(); | ||
/// assert_eq!(x.to_array(), [28, 28, 28, 28, 28, 28, 28, 28]); | ||
/// ``` | ||
#[inline] | ||
#[must_use = "method returns a new vector and does not mutate the original inputs"] | ||
pub fn general_reverse<const SWAP_MASK: usize>(self) -> Self { | ||
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. imho this should include something like 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. For something that effectively implements a very specific xor-striding pattern, 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. well, 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. grev -- general bit reverse 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. Just because an ISA has picked a terrible name doesn't mean we need to copy it. 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. Looking at the precedent of Other names I considered:
Currently I lean towards swap_lanes_xor. Open to other suggestions! 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.
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.
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.
afaict e.g. 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. Thanks all for the suggestions. Of the proposals so far, my preference order is:
I have gone with |
||
const fn general_reverse_index<const LANES: usize>(swap_mask: usize) -> [usize; LANES] { | ||
let mut index = [0; LANES]; | ||
let mut i = 0; | ||
while i < LANES { | ||
index[i] = i ^ swap_mask; | ||
i += 1; | ||
} | ||
index | ||
} | ||
struct GeneralReverse<const DISTANCE: usize>; | ||
impl<const LANES: usize, const DISTANCE: usize> Swizzle<LANES, LANES> for GeneralReverse<DISTANCE> { | ||
const INDEX: [usize; LANES] = general_reverse_index::<LANES>(DISTANCE); | ||
} | ||
GeneralReverse::<SWAP_MASK>::swizzle(self) | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.