summaryrefslogtreecommitdiffstats
path: root/third_party/rust/packed_simd/src/api/reductions/min_max.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/packed_simd/src/api/reductions/min_max.rs')
-rw-r--r--third_party/rust/packed_simd/src/api/reductions/min_max.rs360
1 files changed, 360 insertions, 0 deletions
diff --git a/third_party/rust/packed_simd/src/api/reductions/min_max.rs b/third_party/rust/packed_simd/src/api/reductions/min_max.rs
new file mode 100644
index 0000000000..a3ce13a451
--- /dev/null
+++ b/third_party/rust/packed_simd/src/api/reductions/min_max.rs
@@ -0,0 +1,360 @@
+//! Implements portable horizontal vector min/max reductions.
+
+macro_rules! impl_reduction_min_max {
+ ([$elem_ty:ident; $elem_count:expr]: $id:ident
+ | $ielem_ty:ident | $test_tt:tt) => {
+ impl $id {
+ /// Largest vector element value.
+ #[inline]
+ pub fn max_element(self) -> $elem_ty {
+ #[cfg(not(any(
+ target_arch = "aarch64",
+ target_arch = "arm",
+ target_arch = "powerpc64",
+ target_arch = "wasm32",
+ )))]
+ {
+ use crate::llvm::simd_reduce_max;
+ let v: $ielem_ty = unsafe { simd_reduce_max(self.0) };
+ v as $elem_ty
+ }
+ #[cfg(any(
+ target_arch = "aarch64",
+ target_arch = "arm",
+ target_arch = "powerpc64",
+ target_arch = "wasm32",
+ ))]
+ {
+ // FIXME: broken on AArch64
+ // https://github.com/rust-lang-nursery/packed_simd/issues/15
+ // FIXME: broken on WASM32
+ // https://github.com/rust-lang-nursery/packed_simd/issues/91
+ let mut x = self.extract(0);
+ for i in 1..$id::lanes() {
+ x = x.max(self.extract(i));
+ }
+ x
+ }
+ }
+
+ /// Smallest vector element value.
+ #[inline]
+ pub fn min_element(self) -> $elem_ty {
+ #[cfg(not(any(
+ target_arch = "aarch64",
+ target_arch = "arm",
+ all(target_arch = "x86", not(target_feature = "sse2")),
+ target_arch = "powerpc64",
+ target_arch = "wasm32",
+ ),))]
+ {
+ use crate::llvm::simd_reduce_min;
+ let v: $ielem_ty = unsafe { simd_reduce_min(self.0) };
+ v as $elem_ty
+ }
+ #[cfg(any(
+ target_arch = "aarch64",
+ target_arch = "arm",
+ all(target_arch = "x86", not(target_feature = "sse2")),
+ target_arch = "powerpc64",
+ target_arch = "wasm32",
+ ))]
+ {
+ // FIXME: broken on AArch64
+ // https://github.com/rust-lang-nursery/packed_simd/issues/15
+ // FIXME: broken on i586-unknown-linux-gnu
+ // https://github.com/rust-lang-nursery/packed_simd/issues/22
+ // FIXME: broken on WASM32
+ // https://github.com/rust-lang-nursery/packed_simd/issues/91
+ let mut x = self.extract(0);
+ for i in 1..$id::lanes() {
+ x = x.min(self.extract(i));
+ }
+ x
+ }
+ }
+ }
+ test_if! {$test_tt:
+ paste::item! {
+ // Comparisons use integer casts within mantissa^1 range.
+ #[allow(clippy::float_cmp)]
+ pub mod [<$id _reduction_min_max>] {
+ use super::*;
+ #[cfg_attr(not(target_arch = "wasm32"), test)]
+ #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
+ pub fn max_element() {
+ let v = $id::splat(0 as $elem_ty);
+ assert_eq!(v.max_element(), 0 as $elem_ty);
+ if $id::lanes() > 1 {
+ let v = v.replace(1, 1 as $elem_ty);
+ assert_eq!(v.max_element(), 1 as $elem_ty);
+ }
+ let v = v.replace(0, 2 as $elem_ty);
+ assert_eq!(v.max_element(), 2 as $elem_ty);
+ }
+
+ #[cfg_attr(not(target_arch = "wasm32"), test)]
+ #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
+ pub fn min_element() {
+ let v = $id::splat(0 as $elem_ty);
+ assert_eq!(v.min_element(), 0 as $elem_ty);
+ if $id::lanes() > 1 {
+ let v = v.replace(1, 1 as $elem_ty);
+ assert_eq!(v.min_element(), 0 as $elem_ty);
+ }
+ let v = $id::splat(1 as $elem_ty);
+ let v = v.replace(0, 2 as $elem_ty);
+ if $id::lanes() > 1 {
+ assert_eq!(v.min_element(), 1 as $elem_ty);
+ } else {
+ assert_eq!(v.min_element(), 2 as $elem_ty);
+ }
+ if $id::lanes() > 1 {
+ let v = $id::splat(2 as $elem_ty);
+ let v = v.replace(1, 1 as $elem_ty);
+ assert_eq!(v.min_element(), 1 as $elem_ty);
+ }
+ }
+ }
+ }
+ }
+ };
+}
+
+macro_rules! test_reduction_float_min_max {
+ ([$elem_ty:ident; $elem_count:expr]: $id:ident | $test_tt:tt) => {
+ test_if! {
+ $test_tt:
+ paste::item! {
+ // Comparisons use integer casts within mantissa^1 range.
+ #[allow(clippy::float_cmp)]
+ pub mod [<$id _reduction_min_max_nan>] {
+ use super::*;
+ #[cfg_attr(not(target_arch = "wasm32"), test)]
+ #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
+ fn min_element_test() {
+ let n = crate::$elem_ty::NAN;
+
+ assert_eq!(n.min(-3.), -3.);
+ assert_eq!((-3. as $elem_ty).min(n), -3.);
+
+ let v0 = $id::splat(-3.);
+
+ let target_with_broken_last_lane_nan = !cfg!(any(
+ target_arch = "arm", target_arch = "aarch64",
+ all(target_arch = "x86",
+ not(target_feature = "sse2")
+ ),
+ target_arch = "powerpc64",
+ target_arch = "wasm32",
+ ));
+
+ // The vector is initialized to `-3.`s: [-3, -3, -3, -3]
+ for i in 0..$id::lanes() {
+ // We replace the i-th element of the vector with
+ // `NaN`: [-3, -3, -3, NaN]
+ let mut v = v0.replace(i, n);
+
+ // If the NaN is in the last place, the LLVM
+ // implementation of these methods is broken on some
+ // targets:
+ if i == $id::lanes() - 1 &&
+ target_with_broken_last_lane_nan {
+ assert_eq!(v.min_element(), -3.,
+ "[A]: nan at {} => {} | {:?}",
+ i, v.min_element(), v);
+
+ // If we replace all the elements in the vector
+ // up-to the `i-th` lane with `NaN`s, the result
+ // is still always `-3.` unless all elements of
+ // the vector are `NaN`s:
+ for j in 0..i {
+ v = v.replace(j, n);
+ if j == i-1 {
+ assert!(v.min_element().is_nan(),
+ "[B]: nan at {} => {} | {:?}",
+ i, v.min_element(), v);
+ } else {
+ assert_eq!(v.min_element(), -3.,
+ "[B]: nan at {} => {} | {:?}",
+ i, v.min_element(), v);
+ }
+ }
+
+ // We are done here, since we were in the last
+ // lane which is the last iteration of the loop.
+ break
+ }
+
+ // We are not in the last lane, and there is only
+ // one `NaN` in the vector.
+
+ // If the vector has one lane, the result is `NaN`:
+ if $id::lanes() == 1 {
+ assert!(v.min_element().is_nan(),
+ "[C]: all nans | v={:?} | min={} | \
+is_nan: {}",
+ v, v.min_element(),
+ v.min_element().is_nan()
+ );
+
+ // And we are done, since the vector only has
+ // one lane anyways.
+ break;
+ }
+
+ // The vector has more than one lane, since there is
+ // only one `NaN` in the vector, the result is
+ // always `-3`.
+ assert_eq!(v.min_element(), -3.,
+ "[D]: nan at {} => {} | {:?}",
+ i, v.min_element(), v);
+
+ // If we replace all the elements in the vector
+ // up-to the `i-th` lane with `NaN`s, the result is
+ // still always `-3.` unless all elements of the
+ // vector are `NaN`s:
+ for j in 0..i {
+ v = v.replace(j, n);
+
+ if i == $id::lanes() - 1 && j == i - 1 {
+ // All elements of the vector are `NaN`s,
+ // therefore the result is NaN as well.
+ //
+ // Note: the #lanes of the vector is > 1, so
+ // "i - 1" does not overflow.
+ assert!(v.min_element().is_nan(),
+ "[E]: all nans | v={:?} | min={} | \
+is_nan: {}",
+ v, v.min_element(),
+ v.min_element().is_nan());
+ } else {
+ // There are non-`NaN` elements in the
+ // vector, therefore the result is `-3.`:
+ assert_eq!(v.min_element(), -3.,
+ "[F]: nan at {} => {} | {:?}",
+ i, v.min_element(), v);
+ }
+ }
+ }
+
+ // If the vector contains all NaNs the result is NaN:
+ assert!($id::splat(n).min_element().is_nan(),
+ "all nans | v={:?} | min={} | is_nan: {}",
+ $id::splat(n), $id::splat(n).min_element(),
+ $id::splat(n).min_element().is_nan());
+ }
+ #[cfg_attr(not(target_arch = "wasm32"), test)]
+ #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
+ fn max_element_test() {
+ let n = crate::$elem_ty::NAN;
+
+ assert_eq!(n.max(-3.), -3.);
+ assert_eq!((-3. as $elem_ty).max(n), -3.);
+
+ let v0 = $id::splat(-3.);
+
+ let target_with_broken_last_lane_nan = !cfg!(any(
+ target_arch = "arm", target_arch = "aarch64",
+ target_arch = "powerpc64", target_arch = "wasm32",
+ ));
+
+ // The vector is initialized to `-3.`s: [-3, -3, -3, -3]
+ for i in 0..$id::lanes() {
+ // We replace the i-th element of the vector with
+ // `NaN`: [-3, -3, -3, NaN]
+ let mut v = v0.replace(i, n);
+
+ // If the NaN is in the last place, the LLVM
+ // implementation of these methods is broken on some
+ // targets:
+ if i == $id::lanes() - 1 &&
+ target_with_broken_last_lane_nan {
+ assert_eq!(v.max_element(), -3.,
+ "[A]: nan at {} => {} | {:?}",
+ i, v.max_element(), v);
+
+ // If we replace all the elements in the vector
+ // up-to the `i-th` lane with `NaN`s, the result
+ // is still always `-3.` unless all elements of
+ // the vector are `NaN`s:
+ for j in 0..i {
+ v = v.replace(j, n);
+ if j == i-1 {
+ assert!(v.min_element().is_nan(),
+ "[B]: nan at {} => {} | {:?}",
+ i, v.min_element(), v);
+ } else {
+ assert_eq!(v.max_element(), -3.,
+ "[B]: nan at {} => {} | {:?}",
+ i, v.max_element(), v);
+ }
+ }
+
+ // We are done here, since we were in the last
+ // lane which is the last iteration of the loop.
+ break
+ }
+
+ // We are not in the last lane, and there is only
+ // one `NaN` in the vector.
+
+ // If the vector has one lane, the result is `NaN`:
+ if $id::lanes() == 1 {
+ assert!(v.max_element().is_nan(),
+ "[C]: all nans | v={:?} | min={} | \
+is_nan: {}",
+ v, v.max_element(),
+ v.max_element().is_nan());
+
+ // And we are done, since the vector only has
+ // one lane anyways.
+ break;
+ }
+
+ // The vector has more than one lane, since there is
+ // only one `NaN` in the vector, the result is
+ // always `-3`.
+ assert_eq!(v.max_element(), -3.,
+ "[D]: nan at {} => {} | {:?}",
+ i, v.max_element(), v);
+
+ // If we replace all the elements in the vector
+ // up-to the `i-th` lane with `NaN`s, the result is
+ // still always `-3.` unless all elements of the
+ // vector are `NaN`s:
+ for j in 0..i {
+ v = v.replace(j, n);
+
+ if i == $id::lanes() - 1 && j == i - 1 {
+ // All elements of the vector are `NaN`s,
+ // therefore the result is NaN as well.
+ //
+ // Note: the #lanes of the vector is > 1, so
+ // "i - 1" does not overflow.
+ assert!(v.max_element().is_nan(),
+ "[E]: all nans | v={:?} | max={} | \
+is_nan: {}",
+ v, v.max_element(),
+ v.max_element().is_nan());
+ } else {
+ // There are non-`NaN` elements in the
+ // vector, therefore the result is `-3.`:
+ assert_eq!(v.max_element(), -3.,
+ "[F]: nan at {} => {} | {:?}",
+ i, v.max_element(), v);
+ }
+ }
+ }
+
+ // If the vector contains all NaNs the result is NaN:
+ assert!($id::splat(n).max_element().is_nan(),
+ "all nans | v={:?} | max={} | is_nan: {}",
+ $id::splat(n), $id::splat(n).max_element(),
+ $id::splat(n).max_element().is_nan());
+ }
+ }
+ }
+ }
+ };
+}