Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 35 additions & 71 deletions crates/test_helpers/src/subnormals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,81 +7,45 @@ pub trait FlushSubnormals: Sized {
impl<T> FlushSubnormals for *const T {}
impl<T> FlushSubnormals for *mut T {}

macro_rules! impl_float {
{ $($ty:ty),* } => {
$(
impl FlushSubnormals for $ty {
fn flush(self) -> Self {
let is_f32 = size_of::<Self>() == 4;
let ppc_flush = is_f32 && cfg!(all(
any(target_arch = "powerpc", all(target_arch = "powerpc64", target_endian = "big")),
target_feature = "altivec",
not(target_feature = "vsx"),
));
let arm_flush = is_f32 && cfg!(all(target_arch = "arm", target_feature = "neon"));
let flush = ppc_flush || arm_flush;
if flush && self.is_subnormal() {
<$ty>::copysign(0., self)
} else {
self
}
}
impl FlushSubnormals for i8 {}
impl FlushSubnormals for i16 {}
impl FlushSubnormals for i32 {}
impl FlushSubnormals for i64 {}
impl FlushSubnormals for isize {}

impl FlushSubnormals for u8 {}
impl FlushSubnormals for u16 {}
impl FlushSubnormals for u32 {}
impl FlushSubnormals for u64 {}
impl FlushSubnormals for usize {}

impl FlushSubnormals for f16 {}
impl FlushSubnormals for f64 {}

impl FlushSubnormals for f32 {
fn flush(self) -> Self {
// Correct for non-IEEE behavior for f32 on arm and powerpc.
let ppc_flush = cfg!(all(
any(
target_arch = "powerpc",
all(target_arch = "powerpc64", target_endian = "big")
),
target_feature = "altivec",
not(target_feature = "vsx"),
));
let arm_flush = cfg!(all(target_arch = "arm", target_feature = "neon"));
let flush = ppc_flush || arm_flush;

if flush && self.is_subnormal() {
f32::copysign(0.0, self)
} else {
self
}
)*
}
}

macro_rules! impl_else {
{ $($ty:ty),* } => {
$(
impl FlushSubnormals for $ty {}
)*
}
}

impl_float! { f16, f32, f64 }
impl_else! { i8, i16, i32, i64, isize, u8, u16, u32, u64, usize }

/// AltiVec should flush subnormal inputs to zero, but QEMU seems to only flush outputs.
/// https://gitlab.com/qemu-project/qemu/-/issues/1779
#[cfg(all(
any(target_arch = "powerpc", target_arch = "powerpc64"),
target_feature = "altivec"
))]
fn in_buggy_qemu() -> bool {
use std::sync::OnceLock;
static BUGGY: OnceLock<bool> = OnceLock::new();

fn add(x: f32, y: f32) -> f32 {
#[cfg(target_arch = "powerpc")]
use core::arch::powerpc::*;
#[cfg(target_arch = "powerpc64")]
use core::arch::powerpc64::*;

let array: [f32; 4] =
unsafe { core::mem::transmute(vec_add(vec_splats(x), vec_splats(y))) };
array[0]
}

*BUGGY.get_or_init(|| add(-1.0857398e-38, 0.).is_sign_negative())
}

#[cfg(all(
any(target_arch = "powerpc", target_arch = "powerpc64"),
target_feature = "altivec"
))]
pub fn flush_in<T: FlushSubnormals>(x: T) -> T {
if in_buggy_qemu() {
x
} else {
x.flush()
}
}

#[cfg(not(all(
any(target_arch = "powerpc", target_arch = "powerpc64"),
target_feature = "altivec"
)))]
/// NOTE: altivec had a subnormal flushing bug in older QEMU versions.
/// <https://gitlab.com/qemu-project/qemu/-/issues/1779>
pub fn flush_in<T: FlushSubnormals>(x: T) -> T {
x.flush()
}
Expand Down
Loading