eOn client
Long-timescale dynamics: aKMC, NEB, parallel replica
☾
Toggle main menu visibility
Loading...
Searching...
No Matches
fpe_handler.cpp
Go to the documentation of this file.
1
/*
2
** This file is part of eOn.
3
**
4
** SPDX-License-Identifier: BSD-3-Clause
5
**
6
** Copyright (c) 2010--present, eOn Development Team
7
** All rights reserved.
8
**
9
** Repo:
10
** https://github.com/TheochemUI/eOn
11
*/
12
#include "
eon/fpe_handler.h
"
13
14
#include <cfenv>
15
#include <csignal>
16
#include <cstdio>
17
18
#ifndef _WIN32
19
#include <unistd.h>
20
#endif
21
22
#ifdef _WIN32
23
#define WIN32_LEAN_AND_MEAN
24
#include <float.h>
25
#include <windows.h>
26
#endif
27
28
#if defined(__linux__)
29
#include <ucontext.h>
30
#endif
31
32
#if defined(__APPLE__) && defined(__x86_64__)
33
#include <xmmintrin.h>
34
#endif
35
36
namespace
eonc
{
37
38
#ifdef _WIN32
39
// Report each exception class once. Clearing the status alone is not enough
40
// for a true continue: the faulting op re-executes and re-traps forever.
41
// After the first report, unmask-trapping is demoted for that class so the
42
// instruction completes with the IEEE default (Inf/NaN) and the process
43
// proceeds.
44
static
LONG WINAPI windowsFPEHandler(EXCEPTION_POINTERS *info) {
45
DWORD code = info->ExceptionRecord->ExceptionCode;
46
static
bool
reported_div =
false
;
47
static
bool
reported_inv =
false
;
48
static
bool
reported_ovf =
false
;
49
static
bool
reported_other =
false
;
50
switch
(code) {
51
case
EXCEPTION_FLT_DIVIDE_BY_ZERO:
52
if
(!reported_div) {
53
reported_div =
true
;
54
fprintf(stderr,
"FPE (continuing, masking further): division by zero\n"
);
55
}
56
break
;
57
case
EXCEPTION_FLT_INVALID_OPERATION:
58
if
(!reported_inv) {
59
reported_inv =
true
;
60
fprintf(stderr,
"FPE (continuing, masking further): invalid operation\n"
);
61
}
62
break
;
63
case
EXCEPTION_FLT_OVERFLOW:
64
if
(!reported_ovf) {
65
reported_ovf =
true
;
66
fprintf(stderr,
"FPE (continuing, masking further): overflow\n"
);
67
}
68
break
;
69
case
EXCEPTION_FLT_UNDERFLOW:
70
case
EXCEPTION_FLT_INEXACT_RESULT:
71
case
EXCEPTION_FLT_DENORMAL_OPERAND:
72
case
EXCEPTION_FLT_STACK_CHECK:
73
if
(!reported_other) {
74
reported_other =
true
;
75
fprintf(stderr,
"FPE (continuing, masking further): other float fault\n"
);
76
}
77
break
;
78
default
:
79
return
EXCEPTION_CONTINUE_SEARCH;
80
}
81
// Re-mask every class we care about so CONTINUE_EXECUTION does not re-trap.
82
_clearfp();
83
unsigned
int
control = 0;
84
_controlfp_s(&control, _MCW_EM, _MCW_EM);
85
return
EXCEPTION_CONTINUE_EXECUTION;
86
}
87
#else
88
// MXCSR exception-mask bits (Intel SDM): bit7=IM, bit8=DM, bit9=ZM,
89
// bit10=OM, bit11=UM, bit12=PM. Sticky status flags are bits 0-5.
90
static
constexpr
unsigned
MXCSR_MASK_IM
= 1u << 7;
91
static
constexpr
unsigned
MXCSR_MASK_ZM
= 1u << 9;
92
static
constexpr
unsigned
MXCSR_MASK_OM
= 1u << 10;
93
94
static
void
fpe_signal_handler
(
int
sig, siginfo_t *sip,
void
*scp) {
95
// Async-signal-safe only: write(2) and sig_atomic_t. No iostream, malloc,
96
// backtrace, or fenv helpers (fedisableexcept / feclearexcept are not
97
// async-signal-safe). All continue-state is written into the saved ucontext
98
// so it is restored on sigreturn.
99
//
100
// x86 cannot "continue" past a trapped FP op by clearing sticky flags:
101
// flags are bits 0-5 of MXCSR/swd, but the exception MASK bits live at
102
// MXCSR 7-12. Clearing 0x3F leaves trapping armed, so the faulting
103
// instruction re-executes on the same operands and re-raises forever
104
// (report, sigreturn, refault) -- multi-GB identical stderr lines and a
105
// client stuck at ~100% CPU. Mask the class in the restored MXCSR so
106
// re-execution produces the IEEE default (Inf/NaN) and proceeds.
107
static
volatile
sig_atomic_t reported_div = 0;
108
static
volatile
sig_atomic_t reported_inv = 0;
109
static
volatile
sig_atomic_t reported_ovf = 0;
110
static
volatile
sig_atomic_t reported_unk = 0;
111
112
static
constexpr
char
prefix[] =
"FPE (continuing, masking further): "
;
113
static
constexpr
char
msg_div[] =
"division by zero\n"
;
114
static
constexpr
char
msg_inv[] =
"invalid operation\n"
;
115
static
constexpr
char
msg_ovf[] =
"overflow\n"
;
116
static
constexpr
char
msg_unk[] =
"unknown\n"
;
117
118
// Default: mask all three classes we enable at startup, so an unknown
119
// si_code cannot leave trapping armed and re-storm.
120
unsigned
mxcsr_mask_bits =
MXCSR_MASK_IM
|
MXCSR_MASK_ZM
|
MXCSR_MASK_OM
;
121
volatile
sig_atomic_t *reported = &reported_unk;
122
const
char
*msg = msg_unk;
123
size_t
msg_len =
sizeof
(msg_unk) - 1;
124
125
switch
(sip->si_code) {
126
case
FPE_FLTDIV:
127
reported = &reported_div;
128
msg = msg_div;
129
msg_len =
sizeof
(msg_div) - 1;
130
mxcsr_mask_bits =
MXCSR_MASK_ZM
;
131
break
;
132
case
FPE_FLTINV:
133
reported = &reported_inv;
134
msg = msg_inv;
135
msg_len =
sizeof
(msg_inv) - 1;
136
mxcsr_mask_bits =
MXCSR_MASK_IM
;
137
break
;
138
case
FPE_FLTOVF:
139
reported = &reported_ovf;
140
msg = msg_ovf;
141
msg_len =
sizeof
(msg_ovf) - 1;
142
mxcsr_mask_bits =
MXCSR_MASK_OM
;
143
break
;
144
default
:
145
break
;
146
}
147
148
if
(*reported == 0) {
149
*reported = 1;
150
write(STDERR_FILENO, prefix,
sizeof
(prefix) - 1);
151
write(STDERR_FILENO, msg, msg_len);
152
#if defined(__linux__) && defined(__x86_64__)
153
// First-fault RIP for post-mortem addr2line / offline diagnosis.
154
ucontext_t *ctx_log =
static_cast<
ucontext_t *
>
(scp);
155
unsigned
long
rip =
156
static_cast<
unsigned
long
>
(ctx_log->uc_mcontext.gregs[REG_RIP]);
157
char
hex[] =
"FPE rip=0x0000000000000000\n"
;
158
for
(
int
i = 0; i < 16; ++i) {
159
unsigned
nibble =
static_cast<
unsigned
>
((rip >> (4 * (15 - i))) & 0xFu);
160
hex[10 + i] =
161
static_cast<
char
>
(nibble < 10 ?
'0'
+ nibble :
'a'
+ (nibble - 10));
162
}
163
write(STDERR_FILENO, hex,
sizeof
(hex) - 1);
164
#endif
165
}
166
167
#if defined(__linux__) && (defined(__x86_64__) || defined(__i386__))
168
ucontext_t *ctx =
static_cast<
ucontext_t *
>
(scp);
169
if
(ctx->uc_mcontext.fpregs) {
170
// Clear sticky exception FLAGS (bits 0-5) and arm the MASK bit(s) for
171
// the fault class (bits 7-12). Mask sticks after sigreturn because the
172
// restored MXCSR becomes the live CPU state.
173
ctx->uc_mcontext.fpregs->swd &= ~0x3Fu;
174
ctx->uc_mcontext.fpregs->mxcsr &= ~0x3Fu;
175
ctx->uc_mcontext.fpregs->mxcsr |= mxcsr_mask_bits;
176
// x87 control word: mask bits are 0-5 of cwd (IM, DM, ZM, OM, UM, PM).
177
// Set the matching masks so a legacy x87 fault cannot re-storm either.
178
if
(mxcsr_mask_bits &
MXCSR_MASK_ZM
) {
179
ctx->uc_mcontext.fpregs->cwd |= (1u << 2);
// x87 ZM
180
}
181
if
(mxcsr_mask_bits &
MXCSR_MASK_IM
) {
182
ctx->uc_mcontext.fpregs->cwd |= (1u << 0);
// x87 IM
183
}
184
if
(mxcsr_mask_bits &
MXCSR_MASK_OM
) {
185
ctx->uc_mcontext.fpregs->cwd |= (1u << 3);
// x87 OM
186
}
187
}
188
#endif
189
(void)sig;
190
}
191
#endif
192
193
void
enableFPE
() {
194
#ifdef _WIN32
195
// Register Windows SEH handler for FPE reporting
196
SetUnhandledExceptionFilter(windowsFPEHandler);
197
// Enable floating-point exceptions on Windows
198
_controlfp_s(
nullptr
, 0, _MCW_EM);
199
_controlfp_s(
nullptr
, ~(_EM_ZERODIVIDE | _EM_INVALID | _EM_OVERFLOW),
200
_MCW_EM);
201
#elif defined(__unix__)
202
// Enable floating-point exceptions on Unix
203
feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
204
#elif defined(__APPLE__) && defined(__aarch64__)
205
// Enable floating-point exceptions on ARM macOS
206
fenv_t env;
207
fegetenv(&env);
208
env.__fpsr &= ~(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
209
fesetenv(&env);
210
#elif defined(__APPLE__) && defined(__x86_64__)
211
// Enable floating-point exceptions on Intel macOS
212
_MM_SET_EXCEPTION_MASK(
213
_MM_MASK_MASK &
214
~(_MM_MASK_INVALID | _MM_MASK_DIV_ZERO | _MM_MASK_OVERFLOW));
215
#else
216
fprintf(stderr,
"FPE trapping not supported on this platform.\n"
);
217
#endif
218
219
#ifndef _WIN32
220
// Register POSIX signal handler
221
struct
sigaction act;
222
act.sa_sigaction =
fpe_signal_handler
;
223
sigemptyset(&act.sa_mask);
224
act.sa_flags = SA_SIGINFO;
225
sigaction(SIGFPE, &act,
nullptr
);
226
#endif
227
}
228
229
void
disableFPE
() {
230
#ifdef _WIN32
231
// Mask all floating-point exceptions (restore default behavior)
232
unsigned
int
control;
233
_controlfp_s(&control, _MCW_EM, _MCW_EM);
234
#elif defined(__unix__)
235
fedisableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
236
#elif defined(__APPLE__)
237
fenv_t env;
238
fegetenv(&env);
239
#if defined(__aarch64__)
240
env.__fpsr |= (FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
241
#endif
242
fesetenv(&env);
243
#endif
244
}
245
246
void
FPEHandler::eat_fpe
() {
247
std::lock_guard<std::mutex> lock(
mutex_
);
248
feholdexcept(&
orig_feenv
);
249
}
250
251
void
FPEHandler::restore_fpe
() {
252
std::lock_guard<std::mutex> lock(
mutex_
);
253
fesetenv(&
orig_feenv
);
254
}
255
256
}
// namespace eonc
eonc::FPEHandler::mutex_
std::mutex mutex_
Definition
fpe_handler.h:34
eonc::FPEHandler::orig_feenv
fenv_t orig_feenv
Definition
fpe_handler.h:33
eonc::FPEHandler::restore_fpe
void restore_fpe()
Definition
fpe_handler.cpp:251
eonc::FPEHandler::eat_fpe
void eat_fpe()
Definition
fpe_handler.cpp:246
fpe_handler.h
eonc
RAII resource manager for the ARTn C library with global synchronization.
Definition
ARTnSaddleSearch.cpp:19
eonc::fpe_signal_handler
static void fpe_signal_handler(int sig, siginfo_t *sip, void *scp)
Definition
fpe_handler.cpp:94
eonc::MXCSR_MASK_ZM
static constexpr unsigned MXCSR_MASK_ZM
Definition
fpe_handler.cpp:91
eonc::enableFPE
void enableFPE()
Definition
fpe_handler.cpp:193
eonc::MXCSR_MASK_OM
static constexpr unsigned MXCSR_MASK_OM
Definition
fpe_handler.cpp:92
eonc::disableFPE
void disableFPE()
Definition
fpe_handler.cpp:229
eonc::MXCSR_MASK_IM
static constexpr unsigned MXCSR_MASK_IM
Definition
fpe_handler.cpp:90
client
fpe_handler.cpp
Generated by
1.17.0
Generated by
Doxygen 1.17.0
Analytics by
Antics
provided by
TurtleTech ehf