test_helpers/
subnormals.rs

1pub trait FlushSubnormals: Sized {
2    fn flush(self) -> Self {
3        self
4    }
5}
6
7impl<T> FlushSubnormals for *const T {}
8impl<T> FlushSubnormals for *mut T {}
9
10macro_rules! impl_float {
11    { $($ty:ty),* } => {
12        $(
13        impl FlushSubnormals for $ty {
14            fn flush(self) -> Self {
15                let is_f32 = size_of::<Self>() == 4;
16                let ppc_flush = is_f32 && cfg!(all(
17                    any(target_arch = "powerpc", all(target_arch = "powerpc64", target_endian = "big")),
18                    target_feature = "altivec",
19                    not(target_feature = "vsx"),
20                ));
21                let arm_flush = is_f32 && cfg!(all(target_arch = "arm", target_feature = "neon"));
22                let flush = ppc_flush || arm_flush;
23                if flush && self.is_subnormal() {
24                    <$ty>::copysign(0., self)
25                } else {
26                    self
27                }
28            }
29        }
30        )*
31    }
32}
33
34macro_rules! impl_else {
35    { $($ty:ty),* } => {
36        $(
37        impl FlushSubnormals for $ty {}
38        )*
39    }
40}
41
42impl_float! { f32, f64 }
43impl_else! { i8, i16, i32, i64, isize, u8, u16, u32, u64, usize }
44
45/// AltiVec should flush subnormal inputs to zero, but QEMU seems to only flush outputs.
46/// https://gitlab.com/qemu-project/qemu/-/issues/1779
47#[cfg(all(
48    any(target_arch = "powerpc", target_arch = "powerpc64"),
49    target_feature = "altivec"
50))]
51fn in_buggy_qemu() -> bool {
52    use std::sync::OnceLock;
53    static BUGGY: OnceLock<bool> = OnceLock::new();
54
55    fn add(x: f32, y: f32) -> f32 {
56        #[cfg(target_arch = "powerpc")]
57        use core::arch::powerpc::*;
58        #[cfg(target_arch = "powerpc64")]
59        use core::arch::powerpc64::*;
60
61        let array: [f32; 4] =
62            unsafe { core::mem::transmute(vec_add(vec_splats(x), vec_splats(y))) };
63        array[0]
64    }
65
66    *BUGGY.get_or_init(|| add(-1.0857398e-38, 0.).is_sign_negative())
67}
68
69#[cfg(all(
70    any(target_arch = "powerpc", target_arch = "powerpc64"),
71    target_feature = "altivec"
72))]
73pub fn flush_in<T: FlushSubnormals>(x: T) -> T {
74    if in_buggy_qemu() {
75        x
76    } else {
77        x.flush()
78    }
79}
80
81#[cfg(not(all(
82    any(target_arch = "powerpc", target_arch = "powerpc64"),
83    target_feature = "altivec"
84)))]
85pub fn flush_in<T: FlushSubnormals>(x: T) -> T {
86    x.flush()
87}
88
89pub fn flush<T: FlushSubnormals>(x: T) -> T {
90    x.flush()
91}