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