DSPark 1.6.1
Header-only audio DSP framework in pure C++20 — zero dependencies
Loading...
Searching...
No Matches
DenormalGuard.h
Go to the documentation of this file.
1// DSPark - Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi - MIT License
3
4#pragma once
5
14#include <cstdint>
15
16// Capability gates: a branch is enabled only when the architecture AND the
17// toolchain/FPU features it needs are all present; every other target
18// compiles as a safe no-op (denormals stay enabled - a performance matter,
19// never a correctness one).
20//
21// - x86/x64 needs SSE for LDMXCSR/STMXCSR. Always present on x64; assumed on
22// 32-bit MSVC (every Windows-supported x86 CPU has SSE); on 32-bit
23// GCC/Clang only with -msse (__SSE__) - without it the SSE builtins do not
24// even compile.
25// - AArch64 GCC/Clang (including clang-cl) uses mrs/msr inline assembly.
26// MSVC has no inline assembly on ARM64 and uses the status-register
27// intrinsics instead.
28// - 32-bit ARM vmrs/vmsr are VFP instructions: only available with a
29// hardware FPU (__ARM_FP, per ACLE). Soft-float targets (e.g. Cortex-M0/M3)
30// get the no-op.
31#if defined(__x86_64__) || defined(_M_X64) || defined(_M_IX86) \
32 || (defined(__i386__) && defined(__SSE__))
33 #define DSPARK_DENORMAL_X86 1
34 #include <immintrin.h>
35#elif (defined(__aarch64__) || defined(_M_ARM64)) && (defined(__GNUC__) || defined(__clang__))
36 #define DSPARK_DENORMAL_ARM64_ASM 1
37#elif defined(_M_ARM64) && defined(_MSC_VER)
38 #define DSPARK_DENORMAL_ARM64_MSVC 1
39 #include <intrin.h> // _ReadStatusReg / _WriteStatusReg
40#elif defined(__arm__) && defined(__ARM_FP) && (defined(__GNUC__) || defined(__clang__))
41 #define DSPARK_DENORMAL_ARM32 1
42#endif
43
44namespace dspark {
45
85class [[nodiscard]] DenormalGuard
86{
87public:
91 DenormalGuard() noexcept
92 {
93#if defined(DSPARK_DENORMAL_X86)
94 previousState_ = _mm_getcsr();
95 // 0x8040u = FTZ (bit 15) | DAZ (bit 6)
96 _mm_setcsr(static_cast<unsigned int>(previousState_) | 0x8040u);
97
98#elif defined(DSPARK_DENORMAL_ARM64_ASM)
99 unsigned long long fpcr;
100 __asm__ __volatile__("mrs %0, fpcr" : "=r"(fpcr));
101 previousState_ = fpcr;
102 fpcr |= (1ULL << 24); // FZ bit
103 __asm__ __volatile__("msr fpcr, %0" : : "r"(fpcr));
104
105#elif defined(DSPARK_DENORMAL_ARM64_MSVC)
106 const long long fpcr = _ReadStatusReg(ARM64_FPCR);
107 previousState_ = static_cast<uint64_t>(fpcr);
108 _WriteStatusReg(ARM64_FPCR, fpcr | (1LL << 24)); // FZ bit
109
110#elif defined(DSPARK_DENORMAL_ARM32)
111 unsigned int fpscr;
112 __asm__ __volatile__("vmrs %0, fpscr" : "=r"(fpscr));
113 previousState_ = fpscr;
114 fpscr |= (1u << 24); // FZ bit
115 __asm__ __volatile__("vmsr fpscr, %0" : : "r"(fpscr));
116
117#else
118 // No-op fallback: WebAssembly (no FTZ mode exists), 32-bit MSVC ARM,
119 // x86/ARM builds without the required SSE/FPU features, and any
120 // architecture not listed above.
121 previousState_ = 0;
122#endif
123 }
124
128 ~DenormalGuard() noexcept
129 {
130#if defined(DSPARK_DENORMAL_X86)
131 _mm_setcsr(static_cast<unsigned int>(previousState_));
132
133#elif defined(DSPARK_DENORMAL_ARM64_ASM)
134 const unsigned long long fpcr = static_cast<unsigned long long>(previousState_);
135 __asm__ __volatile__("msr fpcr, %0" : : "r"(fpcr));
136
137#elif defined(DSPARK_DENORMAL_ARM64_MSVC)
138 _WriteStatusReg(ARM64_FPCR, static_cast<long long>(previousState_));
139
140#elif defined(DSPARK_DENORMAL_ARM32)
141 const unsigned int fpscr = static_cast<unsigned int>(previousState_);
142 __asm__ __volatile__("vmsr fpscr, %0" : : "r"(fpscr));
143#endif
144 }
145
153 [[nodiscard]] static constexpr bool isActive() noexcept
154 {
155#if defined(DSPARK_DENORMAL_X86) || defined(DSPARK_DENORMAL_ARM64_ASM) \
156 || defined(DSPARK_DENORMAL_ARM64_MSVC) || defined(DSPARK_DENORMAL_ARM32)
157 return true;
158#else
159 return false;
160#endif
161 }
162
163 // A scope guard is tied to its constructing scope and thread: neither
164 // copyable nor movable.
165 DenormalGuard(const DenormalGuard&) = delete;
169
170private:
171 uint64_t previousState_ = 0;
172};
173
174} // namespace dspark
175
176#undef DSPARK_DENORMAL_X86
177#undef DSPARK_DENORMAL_ARM64_ASM
178#undef DSPARK_DENORMAL_ARM64_MSVC
179#undef DSPARK_DENORMAL_ARM32
RAII scope guard to disable denormalised (subnormal) floating-point numbers.
DenormalGuard & operator=(const DenormalGuard &)=delete
DenormalGuard(const DenormalGuard &)=delete
DenormalGuard & operator=(DenormalGuard &&)=delete
static constexpr bool isActive() noexcept
True when this build has a real flush-to-zero implementation.
DenormalGuard(DenormalGuard &&)=delete
~DenormalGuard() noexcept
Destroys the guard, restoring the exact FP state captured at construction.
DenormalGuard() noexcept
Constructs the guard, saving the current FP state and enabling FTZ (plus DAZ on x86).
Main namespace for the DSPark framework.