Skip to main content

audio_engine_core/
runtime.rs

1thread_local! {
2    static AUDIO_THREAD_FLOAT_MODE_INITIALIZED: std::cell::Cell<bool> =
3        const { std::cell::Cell::new(false) };
4}
5
6/// Initialize floating-point mode for a real-time audio thread.
7///
8/// FTZ (flush-to-zero) and DAZ (denormals-are-zero, where available) are
9/// thread-local CPU flags. Set them from the actual callback/playback thread so
10/// biquad tails cannot fall into slow subnormal arithmetic.
11pub fn audio_thread_init() {
12    AUDIO_THREAD_FLOAT_MODE_INITIALIZED.with(|initialized| {
13        if initialized.get() {
14            return;
15        }
16        set_audio_thread_float_mode();
17        initialized.set(true);
18    });
19}
20
21#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
22fn set_audio_thread_float_mode() {
23    const DAZ_BIT: u32 = 1 << 6;
24    const FTZ_BIT: u32 = 1 << 15;
25
26    // SAFETY: MXCSR is thread-local on x86/x86_64. This only enables FTZ/DAZ
27    // for the current audio thread and does not access memory or cross threads.
28    unsafe {
29        let mut mxcsr = read_mxcsr();
30        mxcsr |= DAZ_BIT | FTZ_BIT;
31        write_mxcsr(mxcsr);
32    }
33}
34
35#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
36unsafe fn read_mxcsr() -> u32 {
37    let mut mxcsr = 0u32;
38    // SAFETY: `stmxcsr` stores the current thread's MXCSR into a valid local
39    // stack slot. It does not dereference any caller-provided pointer.
40    unsafe {
41        std::arch::asm!("stmxcsr [{}]", in(reg) &mut mxcsr, options(nostack, preserves_flags));
42    }
43    mxcsr
44}
45
46#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
47unsafe fn write_mxcsr(mxcsr: u32) {
48    // SAFETY: `ldmxcsr` loads the current thread's MXCSR from a valid local
49    // stack slot. The caller supplies only FTZ/DAZ changes over the prior value.
50    unsafe {
51        std::arch::asm!("ldmxcsr [{}]", in(reg) &mxcsr, options(nostack, preserves_flags));
52    }
53}
54
55#[cfg(target_arch = "aarch64")]
56fn set_audio_thread_float_mode() {
57    let mut fpcr: u64;
58    // SAFETY: FPCR is a thread-local floating-point control register. We only
59    // set bit 24 (FZ) for the current audio thread.
60    unsafe {
61        std::arch::asm!("mrs {fpcr}, fpcr", fpcr = out(reg) fpcr);
62        fpcr |= 1 << 24;
63        std::arch::asm!("msr fpcr, {fpcr}", fpcr = in(reg) fpcr);
64    }
65}
66
67#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
68fn set_audio_thread_float_mode() {
69    log::warn!("Audio thread FTZ/DAZ mode is unsupported on this CPU architecture");
70}
71
72#[cfg(any(test, debug_assertions))]
73pub fn audio_thread_float_mode_is_enabled() -> bool {
74    audio_thread_init();
75    audio_thread_float_mode_is_enabled_unchecked()
76}
77
78#[cfg(all(
79    any(test, debug_assertions),
80    any(target_arch = "x86", target_arch = "x86_64")
81))]
82fn audio_thread_float_mode_is_enabled_unchecked() -> bool {
83    const DAZ_BIT: u32 = 1 << 6;
84    const FTZ_BIT: u32 = 1 << 15;
85
86    // SAFETY: Reading MXCSR is thread-local and has no memory side effects.
87    let csr = unsafe { read_mxcsr() };
88    csr & (DAZ_BIT | FTZ_BIT) == (DAZ_BIT | FTZ_BIT)
89}
90
91#[cfg(all(any(test, debug_assertions), target_arch = "aarch64"))]
92fn audio_thread_float_mode_is_enabled_unchecked() -> bool {
93    let fpcr: u64;
94    // SAFETY: Reading FPCR is thread-local and has no memory side effects.
95    unsafe {
96        std::arch::asm!("mrs {fpcr}, fpcr", fpcr = out(reg) fpcr);
97    }
98    fpcr & (1 << 24) != 0
99}
100
101#[cfg(all(
102    any(test, debug_assertions),
103    not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))
104))]
105fn audio_thread_float_mode_is_enabled_unchecked() -> bool {
106    false
107}
108
109#[inline(always)]
110pub fn flush_subnormal_sample(sample: f64) -> f64 {
111    #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
112    {
113        sample
114    }
115
116    #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
117    {
118        if sample != 0.0 && sample.abs() < f64::MIN_POSITIVE {
119            0.0
120        } else {
121            sample
122        }
123    }
124}