#[must_use = "denormal flush state reverts when this guard is dropped"]
pub struct DenormalGuard {
saved: u64,
}
#[cfg(target_arch = "x86_64")]
const MXCSR_FTZ: u32 = 1 << 15;
#[cfg(target_arch = "x86_64")]
const MXCSR_DAZ: u32 = 1 << 6;
impl DenormalGuard {
#[inline]
pub fn new() -> Self {
#[cfg(target_arch = "x86_64")]
{
let mut saved: u32 = 0;
unsafe {
std::arch::asm!(
"stmxcsr [{0}]",
in(reg) &raw mut saved,
options(nostack, preserves_flags),
);
let new = saved | MXCSR_FTZ | MXCSR_DAZ;
std::arch::asm!(
"ldmxcsr [{0}]",
in(reg) &raw const new,
options(nostack, preserves_flags),
);
}
return Self {
saved: u64::from(saved),
};
}
#[cfg(target_arch = "aarch64")]
{
let saved: u64;
unsafe {
std::arch::asm!(
"mrs {0}, fpcr",
out(reg) saved,
options(nomem, nostack, preserves_flags),
);
let new = saved | (1u64 << 24);
std::arch::asm!(
"msr fpcr, {0}",
in(reg) new,
options(nomem, nostack, preserves_flags),
);
}
return Self { saved };
}
#[allow(unreachable_code)]
Self { saved: 0 }
}
}
impl Default for DenormalGuard {
fn default() -> Self {
Self::new()
}
}
impl Drop for DenormalGuard {
#[inline]
fn drop(&mut self) {
#[cfg(target_arch = "x86_64")]
{
#[allow(clippy::cast_possible_truncation)]
let restore: u32 = self.saved as u32;
unsafe {
std::arch::asm!(
"ldmxcsr [{0}]",
in(reg) &raw const restore,
options(nostack, preserves_flags),
);
}
}
#[cfg(target_arch = "aarch64")]
{
unsafe {
std::arch::asm!(
"msr fpcr, {0}",
in(reg) self.saved,
options(nomem, nostack, preserves_flags),
);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn guard_construct_drop_doesnt_panic() {
let _guard = DenormalGuard::new();
}
#[test]
fn nested_guards_restore_in_lifo_order() {
let outer = DenormalGuard::new();
{
let _inner = DenormalGuard::new();
}
drop(outer);
}
}