blob: a3ce13a45123c0f6ddf115be075544f0715860f7 [file] [log] [blame]
//! 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());
}
}
}
}
};
}