| //! 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()); |
| } |
| } |
| } |
| } |
| }; |
| } |