1 //! Implements portable horizontal vector min/max reductions. 2 3 macro_rules! impl_reduction_min_max { 4 ([$elem_ty:ident; $elem_count:expr]: $id:ident 5 | $ielem_ty:ident | $test_tt:tt) => { 6 impl $id { 7 /// Largest vector element value. 8 #[inline] 9 pub fn max_element(self) -> $elem_ty { 10 #[cfg(not(any( 11 target_arch = "aarch64", 12 target_arch = "arm", 13 target_arch = "powerpc64", 14 target_arch = "wasm32", 15 )))] 16 { 17 use crate::llvm::simd_reduce_max; 18 let v: $ielem_ty = unsafe { simd_reduce_max(self.0) }; 19 v as $elem_ty 20 } 21 #[cfg(any( 22 target_arch = "aarch64", 23 target_arch = "arm", 24 target_arch = "powerpc64", 25 target_arch = "wasm32", 26 ))] 27 { 28 // FIXME: broken on AArch64 29 // https://github.com/rust-lang-nursery/packed_simd/issues/15 30 // FIXME: broken on WASM32 31 // https://github.com/rust-lang-nursery/packed_simd/issues/91 32 let mut x = self.extract(0); 33 for i in 1..$id::lanes() { 34 x = x.max(self.extract(i)); 35 } 36 x 37 } 38 } 39 40 /// Smallest vector element value. 41 #[inline] 42 pub fn min_element(self) -> $elem_ty { 43 #[cfg(not(any( 44 target_arch = "aarch64", 45 target_arch = "arm", 46 all(target_arch = "x86", not(target_feature = "sse2")), 47 target_arch = "powerpc64", 48 target_arch = "wasm32", 49 ),))] 50 { 51 use crate::llvm::simd_reduce_min; 52 let v: $ielem_ty = unsafe { simd_reduce_min(self.0) }; 53 v as $elem_ty 54 } 55 #[cfg(any( 56 target_arch = "aarch64", 57 target_arch = "arm", 58 all(target_arch = "x86", not(target_feature = "sse2")), 59 target_arch = "powerpc64", 60 target_arch = "wasm32", 61 ))] 62 { 63 // FIXME: broken on AArch64 64 // https://github.com/rust-lang-nursery/packed_simd/issues/15 65 // FIXME: broken on i586-unknown-linux-gnu 66 // https://github.com/rust-lang-nursery/packed_simd/issues/22 67 // FIXME: broken on WASM32 68 // https://github.com/rust-lang-nursery/packed_simd/issues/91 69 let mut x = self.extract(0); 70 for i in 1..$id::lanes() { 71 x = x.min(self.extract(i)); 72 } 73 x 74 } 75 } 76 } 77 test_if! {$test_tt: 78 paste::item! { 79 // Comparisons use integer casts within mantissa^1 range. 80 #[allow(clippy::float_cmp)] 81 pub mod [<$id _reduction_min_max>] { 82 use super::*; 83 #[cfg_attr(not(target_arch = "wasm32"), test)] 84 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] 85 pub fn max_element() { 86 let v = $id::splat(0 as $elem_ty); 87 assert_eq!(v.max_element(), 0 as $elem_ty); 88 if $id::lanes() > 1 { 89 let v = v.replace(1, 1 as $elem_ty); 90 assert_eq!(v.max_element(), 1 as $elem_ty); 91 } 92 let v = v.replace(0, 2 as $elem_ty); 93 assert_eq!(v.max_element(), 2 as $elem_ty); 94 } 95 96 #[cfg_attr(not(target_arch = "wasm32"), test)] 97 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] 98 pub fn min_element() { 99 let v = $id::splat(0 as $elem_ty); 100 assert_eq!(v.min_element(), 0 as $elem_ty); 101 if $id::lanes() > 1 { 102 let v = v.replace(1, 1 as $elem_ty); 103 assert_eq!(v.min_element(), 0 as $elem_ty); 104 } 105 let v = $id::splat(1 as $elem_ty); 106 let v = v.replace(0, 2 as $elem_ty); 107 if $id::lanes() > 1 { 108 assert_eq!(v.min_element(), 1 as $elem_ty); 109 } else { 110 assert_eq!(v.min_element(), 2 as $elem_ty); 111 } 112 if $id::lanes() > 1 { 113 let v = $id::splat(2 as $elem_ty); 114 let v = v.replace(1, 1 as $elem_ty); 115 assert_eq!(v.min_element(), 1 as $elem_ty); 116 } 117 } 118 } 119 } 120 } 121 }; 122 } 123 124 macro_rules! test_reduction_float_min_max { 125 ([$elem_ty:ident; $elem_count:expr]: $id:ident | $test_tt:tt) => { 126 test_if!{ 127 $test_tt: 128 paste::item! { 129 // Comparisons use integer casts within mantissa^1 range. 130 #[allow(clippy::float_cmp)] 131 pub mod [<$id _reduction_min_max_nan>] { 132 use super::*; 133 #[cfg_attr(not(target_arch = "wasm32"), test)] 134 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] 135 fn min_element_test() { 136 let n = crate::$elem_ty::NAN; 137 138 assert_eq!(n.min(-3.), -3.); 139 assert_eq!((-3. as $elem_ty).min(n), -3.); 140 141 let v0 = $id::splat(-3.); 142 143 let target_with_broken_last_lane_nan = !cfg!(any( 144 target_arch = "arm", target_arch = "aarch64", 145 all(target_arch = "x86", 146 not(target_feature = "sse2") 147 ), 148 target_arch = "powerpc64", 149 target_arch = "wasm32", 150 )); 151 152 // The vector is initialized to `-3.`s: [-3, -3, -3, -3] 153 for i in 0..$id::lanes() { 154 // We replace the i-th element of the vector with 155 // `NaN`: [-3, -3, -3, NaN] 156 let mut v = v0.replace(i, n); 157 158 // If the NaN is in the last place, the LLVM 159 // implementation of these methods is broken on some 160 // targets: 161 if i == $id::lanes() - 1 && 162 target_with_broken_last_lane_nan { 163 // FIXME: 164 // https://github.com/rust-lang-nursery/packed_simd/issues/5 165 // 166 // If there is a NaN, the result should always 167 // the smallest element, but currently when the 168 // last element is NaN the current 169 // implementation incorrectly returns NaN. 170 // 171 // The targets mentioned above use different 172 // codegen that produces the correct result. 173 // 174 // These asserts detect if this behavior changes 175 assert!(v.min_element().is_nan(), 176 // FIXME: ^^^ should be -3. 177 "[A]: nan at {} => {} | {:?}", 178 i, v.min_element(), v); 179 180 // If we replace all the elements in the vector 181 // up-to the `i-th` lane with `NaN`s, the result 182 // is still always `-3.` unless all elements of 183 // the vector are `NaN`s: 184 // 185 // This is also broken: 186 for j in 0..i { 187 v = v.replace(j, n); 188 assert!(v.min_element().is_nan(), 189 // FIXME: ^^^ should be -3. 190 "[B]: nan at {} => {} | {:?}", 191 i, v.min_element(), v); 192 } 193 194 // We are done here, since we were in the last 195 // lane which is the last iteration of the loop. 196 break 197 } 198 199 // We are not in the last lane, and there is only 200 // one `NaN` in the vector. 201 202 // If the vector has one lane, the result is `NaN`: 203 if $id::lanes() == 1 { 204 assert!(v.min_element().is_nan(), 205 "[C]: all nans | v={:?} | min={} | \ 206 is_nan: {}", 207 v, v.min_element(), 208 v.min_element().is_nan() 209 ); 210 211 // And we are done, since the vector only has 212 // one lane anyways. 213 break; 214 } 215 216 // The vector has more than one lane, since there is 217 // only one `NaN` in the vector, the result is 218 // always `-3`. 219 assert_eq!(v.min_element(), -3., 220 "[D]: nan at {} => {} | {:?}", 221 i, v.min_element(), v); 222 223 // If we replace all the elements in the vector 224 // up-to the `i-th` lane with `NaN`s, the result is 225 // still always `-3.` unless all elements of the 226 // vector are `NaN`s: 227 for j in 0..i { 228 v = v.replace(j, n); 229 230 if i == $id::lanes() - 1 && j == i - 1 { 231 // All elements of the vector are `NaN`s, 232 // therefore the result is NaN as well. 233 // 234 // Note: the #lanes of the vector is > 1, so 235 // "i - 1" does not overflow. 236 assert!(v.min_element().is_nan(), 237 "[E]: all nans | v={:?} | min={} | \ 238 is_nan: {}", 239 v, v.min_element(), 240 v.min_element().is_nan()); 241 } else { 242 // There are non-`NaN` elements in the 243 // vector, therefore the result is `-3.`: 244 assert_eq!(v.min_element(), -3., 245 "[F]: nan at {} => {} | {:?}", 246 i, v.min_element(), v); 247 } 248 } 249 } 250 251 // If the vector contains all NaNs the result is NaN: 252 assert!($id::splat(n).min_element().is_nan(), 253 "all nans | v={:?} | min={} | is_nan: {}", 254 $id::splat(n), $id::splat(n).min_element(), 255 $id::splat(n).min_element().is_nan()); 256 } 257 #[cfg_attr(not(target_arch = "wasm32"), test)] 258 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] 259 fn max_element_test() { 260 let n = crate::$elem_ty::NAN; 261 262 assert_eq!(n.max(-3.), -3.); 263 assert_eq!((-3. as $elem_ty).max(n), -3.); 264 265 let v0 = $id::splat(-3.); 266 267 let target_with_broken_last_lane_nan = !cfg!(any( 268 target_arch = "arm", target_arch = "aarch64", 269 target_arch = "powerpc64", target_arch = "wasm32", 270 )); 271 272 // The vector is initialized to `-3.`s: [-3, -3, -3, -3] 273 for i in 0..$id::lanes() { 274 // We replace the i-th element of the vector with 275 // `NaN`: [-3, -3, -3, NaN] 276 let mut v = v0.replace(i, n); 277 278 // If the NaN is in the last place, the LLVM 279 // implementation of these methods is broken on some 280 // targets: 281 if i == $id::lanes() - 1 && 282 target_with_broken_last_lane_nan { 283 // FIXME: 284 // https://github.com/rust-lang-nursery/packed_simd/issues/5 285 // 286 // If there is a NaN, the result should 287 // always the largest element, but currently 288 // when the last element is NaN the current 289 // implementation incorrectly returns NaN. 290 // 291 // The targets mentioned above use different 292 // codegen that produces the correct result. 293 // 294 // These asserts detect if this behavior 295 // changes 296 assert!(v.max_element().is_nan(), 297 // FIXME: ^^^ should be -3. 298 "[A]: nan at {} => {} | {:?}", 299 i, v.max_element(), v); 300 301 // If we replace all the elements in the vector 302 // up-to the `i-th` lane with `NaN`s, the result 303 // is still always `-3.` unless all elements of 304 // the vector are `NaN`s: 305 // 306 // This is also broken: 307 for j in 0..i { 308 v = v.replace(j, n); 309 assert!(v.max_element().is_nan(), 310 // FIXME: ^^^ should be -3. 311 "[B]: nan at {} => {} | {:?}", 312 i, v.max_element(), v); 313 } 314 315 // We are done here, since we were in the last 316 // lane which is the last iteration of the loop. 317 break 318 } 319 320 // We are not in the last lane, and there is only 321 // one `NaN` in the vector. 322 323 // If the vector has one lane, the result is `NaN`: 324 if $id::lanes() == 1 { 325 assert!(v.max_element().is_nan(), 326 "[C]: all nans | v={:?} | min={} | \ 327 is_nan: {}", 328 v, v.max_element(), 329 v.max_element().is_nan()); 330 331 // And we are done, since the vector only has 332 // one lane anyways. 333 break; 334 } 335 336 // The vector has more than one lane, since there is 337 // only one `NaN` in the vector, the result is 338 // always `-3`. 339 assert_eq!(v.max_element(), -3., 340 "[D]: nan at {} => {} | {:?}", 341 i, v.max_element(), v); 342 343 // If we replace all the elements in the vector 344 // up-to the `i-th` lane with `NaN`s, the result is 345 // still always `-3.` unless all elements of the 346 // vector are `NaN`s: 347 for j in 0..i { 348 v = v.replace(j, n); 349 350 if i == $id::lanes() - 1 && j == i - 1 { 351 // All elements of the vector are `NaN`s, 352 // therefore the result is NaN as well. 353 // 354 // Note: the #lanes of the vector is > 1, so 355 // "i - 1" does not overflow. 356 assert!(v.max_element().is_nan(), 357 "[E]: all nans | v={:?} | max={} | \ 358 is_nan: {}", 359 v, v.max_element(), 360 v.max_element().is_nan()); 361 } else { 362 // There are non-`NaN` elements in the 363 // vector, therefore the result is `-3.`: 364 assert_eq!(v.max_element(), -3., 365 "[F]: nan at {} => {} | {:?}", 366 i, v.max_element(), v); 367 } 368 } 369 } 370 371 // If the vector contains all NaNs the result is NaN: 372 assert!($id::splat(n).max_element().is_nan(), 373 "all nans | v={:?} | max={} | is_nan: {}", 374 $id::splat(n), $id::splat(n).max_element(), 375 $id::splat(n).max_element().is_nan()); 376 } 377 } 378 } 379 } 380 } 381 } 382