From d194d5eddea3377d5a8976f48e7a65c23e471f43 Mon Sep 17 00:00:00 2001 From: Randolph Settgast Date: Sun, 1 Dec 2024 13:43:29 -0800 Subject: [PATCH 1/2] cleanup of FPE on macOS --- src/system.cpp | 90 +++++++++++++++++++---- src/system.hpp | 3 + unitTests/testFloatingPointExceptions.cpp | 4 - 3 files changed, 78 insertions(+), 19 deletions(-) diff --git a/src/system.cpp b/src/system.cpp index a6532ac5..e1a76d9a 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -530,7 +530,48 @@ void resetSignalHandling() /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// int getDefaultFloatingPointExceptions() { - return ( FE_DIVBYZERO | FE_OVERFLOW | FE_INVALID ); + return ( FE_DIVBYZERO | FE_UNDERFLOW | FE_OVERFLOW | FE_INVALID ); +} + +unsigned long long int translateFloatingPointException( unsigned long long int const exception ) +{ + unsigned long long int result = 0; +#if defined(__APPLE__) && defined(__MACH__) // if apple + if( exception & FE_INEXACT ) + { + result |= __fpcr_trap_inexact; + } + if( exception & FE_UNDERFLOW ) + { + result |= __fpcr_trap_underflow; + } + if( exception & FE_OVERFLOW ) + { + result |= __fpcr_trap_overflow; + } + if( exception & FE_DIVBYZERO ) + { + result |= __fpcr_trap_divbyzero; + } + if( exception & FE_INVALID ) + { + result |= __fpcr_trap_invalid; + } + +#if defined(__arm__) || defined(__arm64__) // if apple arm +#elif defined(__x86_64__) // if apple x86_64 +#else // if apple but not arm or x86_64 + std::cerr<< "LvArray::system::translateFloatingPointException() not implemented for this architecture" << std::endl; +#endif + + +#else // if not apple +#if defined(__x86_64__) + result = exception; +#endif +#endif + +return result; } #if defined(__APPLE__) && defined(__MACH__)&& !defined(__x86_64__) @@ -542,10 +583,8 @@ fpe_signal_handler( int sig, siginfo_t *sip, void *scp ) int fe_code = sip->si_code; - printf( "In signal handler : " ); - if( fe_code == ILL_ILLTRP ) - printf( "Illegal trap detected\n" ); + printf( "Illegal trap detected. If you see this you have a FPE, but Apple Silicon doesn't provide data on which FPE has occured.\n" ); else printf( "Code detected : %d\n", fe_code ); @@ -559,19 +598,22 @@ int enableFloatingPointExceptions( int const exceptions ) #if defined(__APPLE__) && defined(__MACH__) #if !defined(__x86_64__) - LVARRAY_UNUSED_VARIABLE( exceptions ); + unsigned long long int const exceptionMasks = translateFloatingPointException( exceptions ); fenv_t env; fegetenv( &env ); - env.__fpcr = env.__fpcr | __fpcr_trap_invalid; +// std::cout< #include - -#if defined(__x86_64__) - using namespace testFloatingPointExceptionsHelpers; const char IGNORE_OUTPUT[] = ".*"; @@ -74,7 +71,6 @@ TEST( TestFloatingPointEnvironment, FloatingPointExceptionGuard ) } // namespace testing } // namespace LvArray -#endif // This is the default gtest main method. It is included for ease of debugging. int main( int argc, char * * argv ) From 031918d0f8556bd0f5c290086cd21e1f4adc22c4 Mon Sep 17 00:00:00 2001 From: "Randolph R. Settgast" Date: Wed, 4 Feb 2026 14:39:57 -0800 Subject: [PATCH 2/2] some general cleanup --- src/system.cpp | 180 ++++++++++------------ src/system.hpp | 11 +- unitTests/testFloatingPointExceptions.cpp | 27 +--- 3 files changed, 93 insertions(+), 125 deletions(-) diff --git a/src/system.cpp b/src/system.cpp index e1a76d9a..21d8279c 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -18,7 +18,8 @@ #include #include -#if defined(__x86_64__) +#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) + #include // or #include #endif @@ -311,41 +312,6 @@ static std::string getSourceLocationFromFrame( void const * const address ) return ""; } -/** - * @brief Return a string representing the current floating point exception. - * @return A string representing the current floating point exception. - */ -static std::string getFpeDetails() -{ - std::ostringstream oss; - int const fpe = fetestexcept( FE_ALL_EXCEPT ); - - oss << "Floating point exception:"; - - if( fpe & FE_DIVBYZERO ) - { - oss << " Division by zero;"; - } - if( fpe & FE_INEXACT ) - { - oss << " Inexact result;"; - } - if( fpe & FE_INVALID ) - { - oss << " Invalid argument;"; - } - if( fpe & FE_OVERFLOW ) - { - oss << " Overflow;"; - } - if( fpe & FE_UNDERFLOW ) - { - oss << " Underflow;"; - } - - return oss.str(); -} - namespace LvArray { namespace system @@ -465,72 +431,78 @@ void callErrorHandler() } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -void stackTraceHandler( int const sig, bool const exit ) + +void signalHandler( int sig, siginfo_t * info, void * /*ucontext*/ ) { std::ostringstream oss; if( sig >= 0 && sig < NSIG ) { - // sys_signame not available on linux, so just print the code; strsignal is POSIX oss << "Received signal " << sig << ": " << strsignal( sig ) << "\n"; if( sig == SIGFPE ) { - oss << getFpeDetails() << "\n"; + if( info ) + { + oss << " SIGFPE si_code = " << info->si_code << " "; + + switch( info->si_code ) + { + case FPE_FLTDIV: oss << "(floating divide by zero)\n"; break; + case FPE_FLTOVF: oss << "(floating overflow)\n"; break; + case FPE_FLTUND: oss << "(floating underflow)\n"; break; + case FPE_FLTINV: oss << "(floating invalid operation)\n"; break; + case FPE_FLTRES: oss << "(floating inexact)\n"; break; + case FPE_INTDIV: oss << "(integer divide by zero)\n"; break; + case FPE_INTOVF: oss << "(integer overflow)\n"; break; + default: oss << "(other)\n"; break; + } + } } } oss << stackTrace( true ) << std::endl; - std::cout << oss.str(); + std::cerr << oss.str(); - if( exit ) - { - // An infinite loop was encountered when an FPE was received. Resetting the handlers didn't - // fix it because they would just recurse. This does. - setSignalHandling( nullptr ); - callErrorHandler(); - } + std::_Exit( 1 ); } -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -void setSignalHandling( void (* handler)( int ) ) -{ - initialHandler[SIGHUP] = signal( SIGHUP, handler ); - initialHandler[SIGINT] = signal( SIGINT, handler ); - initialHandler[SIGQUIT] = signal( SIGQUIT, handler ); - initialHandler[SIGILL] = signal( SIGILL, handler ); - initialHandler[SIGTRAP] = signal( SIGTRAP, handler ); - initialHandler[SIGABRT] = signal( SIGABRT, handler ); -#if (defined(_POSIX_C_SOURCE) && !defined(_DARWIN_C_SOURCE)) - initialHandler[SIGPOLL] = signal( SIGPOLL, handler ); -#else - initialHandler[SIGIOT] = signal( SIGIOT, handler ); - initialHandler[SIGEMT] = signal( SIGEMT, handler ); -#endif - initialHandler[SIGFPE] = signal( SIGFPE, handler ); - initialHandler[SIGKILL] = signal( SIGKILL, handler ); - initialHandler[SIGBUS] = signal( SIGBUS, handler ); - initialHandler[SIGSEGV] = signal( SIGSEGV, handler ); - initialHandler[SIGSYS] = signal( SIGSYS, handler ); - initialHandler[SIGPIPE] = signal( SIGPIPE, handler ); - initialHandler[SIGTERM] = signal( SIGTERM, handler ); - - return; -} -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -void resetSignalHandling() +static struct sigaction g_oldAction[NSIG]; + +void setSignalHandling( void (* handler)( int, siginfo_t * info, void * ) ) { - for( auto a : initialHandler ) - { - signal( a.first, a.second ); - } + struct sigaction sa; + sigemptyset( &sa.sa_mask ); + sa.sa_sigaction = handler; + sa.sa_flags = SA_SIGINFO; + + auto install = [&]( int sig ) + { + sigaction( sig, &sa, &g_oldAction[sig] ); + }; + + install( SIGHUP ); + install( SIGINT ); + install( SIGQUIT ); + install( SIGILL ); + install( SIGTRAP ); + install( SIGABRT ); + install( SIGFPE ); + install( SIGBUS ); + install( SIGSEGV ); + install( SIGSYS ); + install( SIGPIPE ); + install( SIGTERM ); + // Do NOT try SIGKILL/SIGSTOP: they can’t be caught. } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// int getDefaultFloatingPointExceptions() { - return ( FE_DIVBYZERO | FE_UNDERFLOW | FE_OVERFLOW | FE_INVALID ); + return ( FE_DIVBYZERO | FE_OVERFLOW | FE_INVALID ); } unsigned long long int translateFloatingPointException( unsigned long long int const exception ) @@ -674,40 +646,48 @@ int disableFloatingPointExceptions( int const exceptions ) #endif } - -void setFlushToZero() +static void enableFlushDenormalsToZero() { +#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) -#if defined(__APPLE__) && defined(__MACH__) // if apple + // x86/x86-64: MXCSR control, via SSE intrinsics + _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); + #ifdef _MM_DENORMALS_ZERO_ON + _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); + #endif -#if defined(__arm__) || defined(__arm64__) // if apple arm - fenv_t env; - fegetenv( &env ); - env.__fpcr = env.__fpcr | __fpcr_flush_to_zero ; - // std::cout< #include #include +#include + + namespace LvArray { @@ -77,13 +80,14 @@ void callErrorHandler(); * @param sig The signal received. * @param exit If true abort execution. */ -void stackTraceHandler( int const sig, bool const exit ); +void signalHandler( int sig, siginfo_t * info, void * /*ucontext*/ ); + /** * @brief Set the signal handler for common signals. * @param handler The signal handler. */ -void setSignalHandling( void (* handler)( int ) ); +void setSignalHandling( void (* handler)( int, siginfo_t * info, void * ) = signalHandler ); /** * @brief Rest the signal handling back to the original state. @@ -117,9 +121,6 @@ int disableFloatingPointExceptions( int const exceptions = getDefaultFloatingPoi */ void setFPE(); - -void setFlushToZero(); - /** * @class FloatingPointExceptionGuard * @brief Changes the floating point environment and reverts it when destoyed. diff --git a/unitTests/testFloatingPointExceptions.cpp b/unitTests/testFloatingPointExceptions.cpp index 3ceed03a..1ae5ee80 100644 --- a/unitTests/testFloatingPointExceptions.cpp +++ b/unitTests/testFloatingPointExceptions.cpp @@ -29,43 +29,27 @@ namespace testing TEST( TestFloatingPointEnvironment, Underflow ) { - system::enableFloatingPointExceptions( FE_UNDERFLOW ); - EXPECT_DEATH_IF_SUPPORTED( divide( DBL_MIN, 2 ), IGNORE_OUTPUT ); - system::disableFloatingPointExceptions( FE_UNDERFLOW ); - system::setFPE(); double fpnum = divide( DBL_MIN, 2 ); - int fpclassification = std::fpclassify( fpnum ); - EXPECT_NE( fpclassification, FP_SUBNORMAL ); + EXPECT_DOUBLE_EQ( fpnum, 0.0 ); } TEST( TestFloatingPointEnvironment, DivideByZero ) { system::setFPE(); - EXPECT_DEATH_IF_SUPPORTED( divide( 1, 0 ), IGNORE_OUTPUT ); + EXPECT_DEATH_IF_SUPPORTED( divide( 1, 0 ), R"((floating divide by zero)(.|\n)*StackTrace)" ); } TEST( TestFloatingPointEnvironment, Overlow ) { system::setFPE(); - EXPECT_DEATH_IF_SUPPORTED( multiply( DBL_MAX, 2 ), IGNORE_OUTPUT ); + EXPECT_DEATH_IF_SUPPORTED( multiply( DBL_MAX, 2 ), R"((floating overflow)(.|\n)*StackTrace)" ); } TEST( TestFloatingPointEnvironment, Invalid ) { system::setFPE(); - EXPECT_DEATH_IF_SUPPORTED( invalid(), IGNORE_OUTPUT ); -} - -TEST( TestFloatingPointEnvironment, FloatingPointExceptionGuard ) -{ - system::setFPE(); - - { - system::FloatingPointExceptionGuard guard( FE_UNDERFLOW ); - divide( DBL_MIN, 2 ); - EXPECT_DEATH_IF_SUPPORTED( multiply( DBL_MAX, 2 ), IGNORE_OUTPUT ); - } + EXPECT_DEATH_IF_SUPPORTED( invalid(), R"((floating invalid operation)(.|\n)*StackTrace)" ); } } // namespace testing @@ -75,6 +59,9 @@ TEST( TestFloatingPointEnvironment, FloatingPointExceptionGuard ) // This is the default gtest main method. It is included for ease of debugging. int main( int argc, char * * argv ) { + + LvArray::system::setSignalHandling(); + ::testing::InitGoogleTest( &argc, argv ); int const result = RUN_ALL_TESTS(); return result;