ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
fe_trap.hpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2024-2026 The ESPResSo project
3 *
4 * This file is part of ESPResSo.
5 *
6 * ESPResSo is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * ESPResSo is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#pragma once
21
22#include <config/config.hpp>
23
24#ifdef ESPRESSO_FPE
25
26#include <memory>
27#include <mutex>
28#include <optional>
29#include <utility>
30
31/**
32 * @brief Floating-point exception trap.
33 *
34 * Thread-safe RAII-style mechanism to trap floating-point exceptions
35 * (on platforms that support floating-point environment management) for
36 * the duration of a scoped block. Exception traps are set when the object
37 * is created; when getting out-of-scope, either normally or during stack
38 * unwinding, the exception traps are automatically reset.
39 * This idiom is called "scope-based resource management".
40 *
41 * Please note "exception" and "exception handling" have a specific meaning
42 * in this context and are completely unrelated to C++ exceptions.
43 * For more details, see annex F IEC 60559 "floating-point arithmetic"
44 * in ISO/EIC 9899 @cite ISO-EIC-9899-1999 and chapter 7
45 * "Exceptions and default exception handling" in
46 * ISO/IEC 60559:2020(E) @cite ISO-EIC-60559-2020.
47 *
48 * The exception handling behavior is implementation-defined. For example,
49 * GNU libc sends the @c SIGFPE signal on x86 architectures; it can be caught
50 * by a signal handler that leverages stack environments and long jumps via
51 * [`sigsetjmp()`](https://www.man7.org/linux/man-pages/man3/sigsetjmp.3.html).
52 * On Armv8, trapping is controlled by FPCR flags; for more details,
53 * see section C5.2.8 "FPCR, Floating-point Control Register" in the Armv8
54 * manual @cite ARM-DDI-0487-2024. AppleClang sends the @c SIGILL signal
55 * on Apple Silicon architectures.
56 *
57 * A modified singleton pattern is leveraged to guarantee only one trap is
58 * active at any time; once expired, a new trap can be instantiated.
59 * The @ref make_unique_scoped function returns a wrapper object whose
60 * lifetime determines the trap duration. To set a trap in a recursive
61 * function, use @ref make_shared_scoped instead.
62 *
63 * Usage:
64 * @code{.cpp}
65 * #include <instrumentation/fe_trap.hpp>
66 * #include <cmath>
67 * int main() {
68 * auto volatile zero = 0.;
69 * auto value = 1. / zero; // generate NaN
70 * {
71 * auto trap = fe_trap::make_unique_scoped();
72 * value = 1. / zero; // execution flow should be interrupted here
73 * }
74 * value = 1. / zero; // generate NaN
75 * return std::isnan(value) ? 0 : 1;
76 * }
77 * @endcode
78 * Build the code without fast-math and without any optimization
79 * (optimizations always assume divisions by zero cannot happen):
80 * @code{.sh}
81 * g++ -std=c++20 -O0 -fno-fast-math -I../src/instrumentation/include \
82 * -I../src/config/include -Isrc/config/include \
83 * main.cpp ../src/instrumentation/src/fe_trap.cpp
84 * ./a.out
85 * }
86 * @endcode
87 */
88class fe_trap {
89 struct global_state_params {
90 std::weak_ptr<fe_trap> observer;
91 std::mutex mutex;
92 };
93 static global_state_params global_state;
94
95 /** @brief Scope-based handle to manage an exception trap lifetime. */
96 struct scoped_instance {
97 explicit scoped_instance(std::shared_ptr<fe_trap> ptr)
98 : m_resource{std::move(ptr)} {}
99 scoped_instance(scoped_instance const &) = delete;
100 scoped_instance(scoped_instance &&) noexcept = default;
101 scoped_instance &operator=(scoped_instance const &) = delete;
102 scoped_instance &operator=(scoped_instance &&) noexcept = default;
103 bool is_unique() const { return m_resource->is_unique(); }
104 bool is_active() const { return m_resource->is_active(); }
105 int get_flags() const { return m_resource->get_flags(); }
106 auto &get_trap() { return *m_resource; }
107 auto const &get_trap() const { return *m_resource; }
108 bool operator==(scoped_instance const &) const = default;
109
110 private:
111 std::shared_ptr<fe_trap> m_resource;
112 };
113
114 /** @brief Scope-based handle to temporarily disable an exception trap. */
115 struct scoped_pause {
116 std::weak_ptr<fe_trap> m_resource;
117 explicit scoped_pause(std::shared_ptr<fe_trap> ptr) : m_resource{ptr} {
118 ptr->deactivate();
119 }
120 ~scoped_pause() {
121 if (auto const ptr = m_resource.lock()) {
122 ptr->activate();
123 }
124 }
125 };
126
127 struct deleter {
128 void operator()(fe_trap *ptr) { delete ptr; }
129 };
130 friend deleter;
131
132 int m_flags;
133 bool m_unique;
134 bool m_active; // this flag is not strictly equivalent to `not m_pause`
135 std::weak_ptr<scoped_pause> m_pause;
136
137 fe_trap(std::optional<int> excepts, bool unique)
138 : m_flags{parse_excepts(excepts)}, m_unique{unique}, m_active{false},
139 m_pause{} {
140 activate();
141 }
142 ~fe_trap() { deactivate(); }
143
144 static int parse_excepts(std::optional<int> excepts);
145
146public:
147 fe_trap(fe_trap const &) = delete;
148 fe_trap(fe_trap &&) noexcept = delete;
149 fe_trap &operator=(fe_trap const &) = delete;
150 fe_trap &operator=(fe_trap &&) noexcept = delete;
151 /** @brief Get floating-point exception flags. */
152 int get_flags() const { return m_flags; }
153 /** @brief Check if this handle is a unique handle. */
154 bool is_unique() const { return m_unique; }
155 /** @brief Check if this handle has a currrently active trap. */
156 bool is_active() const { return m_active; }
157
158 /**
159 * @brief Generate a unique trap with the lifetime of the current scope.
160 * @param excepts Combination of floating-point exception flags.
161 */
162 static scoped_instance
163 make_unique_scoped(std::optional<int> excepts = std::nullopt);
164 /**
165 * @brief Generate a shared trap with the lifetime of the current scope.
166 * Subsequent calls to this function will yield the same trap handle,
167 * as long as they have the same parameter @c excepts.
168 * @param excepts Combination of floating-point exception flags.
169 */
170 static scoped_instance
171 make_shared_scoped(std::optional<int> excepts = std::nullopt);
172
173 /**
174 * @brief Generate a shared handle to temporarily disable any currently
175 * active exception trap for the lifetime of the current scope.
176 * If no exception trap is currently active, return a null pointer.
177 */
178 static std::shared_ptr<scoped_pause> make_shared_pause_scoped();
179
180 /** @brief Manually activate the exception trap. */
181 void activate();
182
183 /**
184 * @brief Manually deactivate the exception trap.
185 * Useful when calling a third-party library that is known to send signals.
186 * This should only be used in exceptional cases
187 * (@ref make_shared_pause_scoped provides a scope-based alternative).
188 * Call @ref activate() to re-activate the exception trap.
189 */
190 void deactivate();
191};
192
193#endif // ESPRESSO_FPE
Floating-point exception trap.
Definition fe_trap.hpp:88
bool is_unique() const
Check if this handle is a unique handle.
Definition fe_trap.hpp:154
fe_trap(fe_trap const &)=delete
void activate()
Manually activate the exception trap.
Definition fe_trap.cpp:42
bool is_active() const
Check if this handle has a currrently active trap.
Definition fe_trap.hpp:156
void deactivate()
Manually deactivate the exception trap.
Definition fe_trap.cpp:72
static std::shared_ptr< scoped_pause > make_shared_pause_scoped()
Generate a shared handle to temporarily disable any currently active exception trap for the lifetime ...
Definition fe_trap.cpp:144
fe_trap(fe_trap &&) noexcept=delete
static scoped_instance make_shared_scoped(std::optional< int > excepts=std::nullopt)
Generate a shared trap with the lifetime of the current scope.
Definition fe_trap.cpp:126
int get_flags() const
Get floating-point exception flags.
Definition fe_trap.hpp:152
static scoped_instance make_unique_scoped(std::optional< int > excepts=std::nullopt)
Generate a unique trap with the lifetime of the current scope.
Definition fe_trap.cpp:114