#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
const MXCSR_FTZ: u32 = 1 << 15;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
const MXCSR_DAZ: u32 = 1 << 6;
#[cfg(target_arch = "aarch64")]
const FPCR_FZ: u64 = 1 << 24;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[inline(always)]
unsafe fn read_mxcsr() -> u32 {
let mut val: u32 = 0;
unsafe {
core::arch::asm!(
"stmxcsr [{ptr}]",
ptr = in(reg) &mut val as *mut u32,
options(nostack, preserves_flags),
);
}
val
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[inline(always)]
unsafe fn write_mxcsr(val: u32) {
unsafe {
core::arch::asm!(
"ldmxcsr [{ptr}]",
ptr = in(reg) &val as *const u32,
options(nostack, preserves_flags),
);
}
}
#[inline(always)]
pub fn flush_to_zero(x: f32) -> f32 {
if x.is_subnormal() { 0.0 } else { x }
}
pub struct DenormalGuard {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
prev_mxcsr: u32,
#[cfg(target_arch = "aarch64")]
prev_fpcr: u64,
}
impl DenormalGuard {
#[inline]
pub fn new() -> Self {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let prev_mxcsr = unsafe { read_mxcsr() };
unsafe { write_mxcsr(prev_mxcsr | MXCSR_FTZ | MXCSR_DAZ) };
Self { prev_mxcsr }
}
#[cfg(target_arch = "aarch64")]
{
let prev_fpcr: u64;
unsafe {
core::arch::asm!("mrs {0}, fpcr", out(reg) prev_fpcr);
core::arch::asm!("msr fpcr, {0}", in(reg) prev_fpcr | FPCR_FZ);
}
Self { prev_fpcr }
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
Self {}
}
}
impl Default for DenormalGuard {
fn default() -> Self {
Self::new()
}
}
impl Drop for DenormalGuard {
#[inline]
fn drop(&mut self) {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
unsafe {
write_mxcsr(self.prev_mxcsr);
}
#[cfg(target_arch = "aarch64")]
unsafe {
core::arch::asm!("msr fpcr, {0}", in(reg) self.prev_fpcr);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Mutex, MutexGuard};
static FPU_TEST_LOCK: Mutex<()> = Mutex::new(());
fn fpu_lock() -> MutexGuard<'static, ()> {
FPU_TEST_LOCK
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
#[test]
fn normal_values_pass_through() {
for &v in &[
1.0_f32,
-1.0,
0.5,
-0.5,
1.18e-38,
f32::MAX,
f32::MIN_POSITIVE,
] {
assert_eq!(
flush_to_zero(v),
v,
"normal value {v} was unexpectedly changed"
);
}
}
#[test]
fn zero_passes_through() {
assert_eq!(flush_to_zero(0.0_f32), 0.0);
assert_eq!(flush_to_zero(-0.0_f32), -0.0_f32);
}
#[test]
fn subnormal_is_flushed() {
let subnormal = f32::from_bits(1);
assert!(subnormal.is_subnormal());
assert_eq!(flush_to_zero(subnormal), 0.0);
}
#[test]
fn near_min_positive_subnormal_is_flushed() {
let nearly_normal = f32::MIN_POSITIVE - f32::from_bits(1);
if nearly_normal.is_subnormal() {
assert_eq!(flush_to_zero(nearly_normal), 0.0);
}
}
#[test]
fn special_values_pass_through() {
let nan = f32::NAN;
let inf = f32::INFINITY;
let neg_inf = f32::NEG_INFINITY;
assert!(flush_to_zero(nan).is_nan());
assert_eq!(flush_to_zero(inf), inf);
assert_eq!(flush_to_zero(neg_inf), neg_inf);
}
#[test]
fn guard_constructs_and_drops() {
let _fpu = fpu_lock();
let _guard = DenormalGuard::new();
}
#[test]
fn nested_guards_restore_state() {
let _fpu = fpu_lock();
{
let _outer = DenormalGuard::new();
{
let _inner = DenormalGuard::new();
} } }
#[cfg(target_arch = "x86_64")]
#[test]
fn x86_ftz_daz_bits_are_set_and_restored() {
let _fpu = fpu_lock();
let baseline = unsafe { read_mxcsr() } & !(MXCSR_FTZ | MXCSR_DAZ);
unsafe { write_mxcsr(baseline) };
{
let _guard = DenormalGuard::new();
let live = unsafe { read_mxcsr() };
assert!(live & MXCSR_FTZ != 0, "FTZ bit not set while guard is live");
assert!(live & MXCSR_DAZ != 0, "DAZ bit not set while guard is live");
}
let after = unsafe { read_mxcsr() };
assert_eq!(
after & (MXCSR_FTZ | MXCSR_DAZ),
baseline & (MXCSR_FTZ | MXCSR_DAZ),
"FTZ/DAZ bits not restored after guard drop"
);
}
#[cfg(target_arch = "x86_64")]
#[test]
fn x86_subnormal_output_is_flushed_by_hardware() {
let _fpu = fpu_lock();
let small: f32 = 1.0e-20;
let without_guard = small * small;
if without_guard == 0.0 {
return;
}
assert!(
without_guard.is_subnormal(),
"test precondition: product must be subnormal"
);
let with_guard = {
let _guard = DenormalGuard::new();
std::hint::black_box(small) * std::hint::black_box(small)
};
assert_eq!(
with_guard, 0.0,
"FTZ hardware flush did not zero the subnormal product"
);
}
#[cfg(target_arch = "x86_64")]
#[test]
fn x86_guard_restores_arbitrary_prior_mxcsr() {
let _fpu = fpu_lock();
let original = unsafe { read_mxcsr() };
let prior = (original | MXCSR_FTZ) & !MXCSR_DAZ;
unsafe { write_mxcsr(prior) };
{
let _guard = DenormalGuard::new();
let live = unsafe { read_mxcsr() };
assert!(live & MXCSR_FTZ != 0, "FTZ must be on while guard is live");
assert!(live & MXCSR_DAZ != 0, "DAZ must be on while guard is live");
}
let restored = unsafe { read_mxcsr() };
assert_eq!(
restored, prior,
"guard must restore the exact prior MXCSR, including the cleared DAZ bit"
);
unsafe { write_mxcsr(original) };
}
}