The way to trap floating-point exceptions is architecture-dependent. This is code I have tested successfully on an Intel (x86) Mac: it takes the square root of a negative number twice, once before, and once after, enabling floating-point exception trapping. The second time, fpe_signal_handler() is called.
#include <cmath> // for sqrt()
#include <csignal> // for signal()
#include <iostream>
#include <xmmintrin.h> // for _mm_setcsr
void fpe_signal_handler(int /*signal*/) {
std::cerr << "Floating point exception!\n";
exit(1);
}
void enable_floating_point_exceptions() {
_mm_setcsr(_MM_MASK_MASK & ~_MM_MASK_INVALID);
signal(SIGFPE, fpe_signal_handler);
}
int main() {
const double x{-1.0};
std::cout << sqrt(x) << "\n";
enable_floating_point_exceptions();
std::cout << sqrt(x) << "\n";
}
Compiling with the apple-clang compiler provided by Xcode
clang++ -g -std=c++17 -o fpe fpe.cpp
and running gives the following expected output:
nan
Floating point exception!
I would like to write an analogous program that does the same thing as the above program on an M1 (arm64) Mac. I tried the following:
#include <cfenv> // for std::fenv_t
#include <cmath> // for sqrt()
#include <csignal> // for signal()
#include <fenv.h> // for fegetenv(), fesetenv()
#include <iostream>
void fpe_signal_handler(int /*signal*/) {
std::cerr << "Floating point exception!\n";
exit(1);
}
void enable_floating_point_exceptions() {
std::fenv_t env;
fegetenv(&env);
env.__fpcr = env.__fpcr | __fpcr_trap_invalid;
fesetenv(&env);
signal(SIGFPE, fpe_signal_handler);
}
int main() {
const double x{-1.0};
std::cout << sqrt(x) << "\n";
enable_floating_point_exceptions();
std::cout << sqrt(x) << "\n";
}
It almost works: After compiling with the apple-clang compiler provided by Xcode
clang++ -g -std=c++17 -o fpe fpe.cpp
I get the following output:
nan
zsh: illegal hardware instruction ./fpe
I tried adding the -fexceptions flag, but that didn't make a difference. I noticed that the ARM Compiler toolchain "does not support floating-point exception trapping for AArch64 targets," but I'm not sure if this applies to M1 Macs with Apple's toolchain.
Am I correct that the M1 Mac hardware just doesn't support floating-point exception trapping? Or is there a way to modify this program so it traps the second floating-point exception and then calls fpe_signal_handler()?
Synchronously testing for exceptions within the same thread does work fine, using ISO C fetestexcept from <fenv.h> as in the cppreference example. The problem here is getting FP exceptions to actually trap so the OS delivers SIGFPE, instead of just setting sticky flags in the FP environment.
Based on the long discussion below (many thanks to the patience of @Peter Cordes), it seems that with MacOS on Aarch64, unmasking FP exceptions and then generating the corresponding bad FP math results in a SIGILL rather than a SIGFPE. A signal code ILL_ILLTRP can be detected in the handler.
This results in :
Other floating point exceptions can be detected in a similar manner, e.g unmasking
__fpcr_trap_divbyzero, and then generatingdouble x=0; double y=1/x. If the exception is not unmasked then the program terminates normally.Without a SIGFPE, however, it doesn't seem possible to detect exactly which floating point operation triggered the signal handler. One can imagine unmasking all exceptions (e.g. using FE_ALL_EXCEPT). Then, when a bad math op generates a SIGILL, we don't have enough information in the handler to know what operation sent the signal. Unmasking all exceptions and playing around a bit with
fetestexceptin the handler didn't produce very reliable results. This might take some more investigation.