From d194d5eddea3377d5a8976f48e7a65c23e471f43 Mon Sep 17 00:00:00 2001 From: Randolph Settgast Date: Sun, 1 Dec 2024 13:43:29 -0800 Subject: [PATCH 1/4] 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/4] 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; From f173d1c7772574e8954912941c7266d3545c19e0 Mon Sep 17 00:00:00 2001 From: Randolph Settgast Date: Mon, 2 Mar 2026 20:17:48 -0800 Subject: [PATCH 3/4] some cleanup and documentation --- src/system.cpp | 274 +++++++++++++++------- src/system.hpp | 7 +- unitTests/testFloatingPointExceptions.cpp | 33 ++- 3 files changed, 225 insertions(+), 89 deletions(-) diff --git a/src/system.cpp b/src/system.cpp index 21d8279c..1dd013d4 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -10,7 +10,6 @@ #include "Macros.hpp" // System includes -#include #include #include #include @@ -31,6 +30,17 @@ #include #endif +/* + * Implementation map: + * 1) Low-level stack frame collection and symbol/source resolution. + * 2) Public stack trace and type demangling helpers. + * 3) Error and signal handling (including FPE signal decoding). + * 4) Cross-platform floating-point environment control: + * - enable/disable traps, + * - translate FE_* masks where platform internals differ, + * - flush denormals/subnormals to zero. + */ + /** * @struct UnwindState * @brief Holds info used in unwindCallback. @@ -300,6 +310,7 @@ static std::string addr2line( const char * flag, const void * addr ) static std::string getSourceLocationFromFrame( void const * const address ) { #if defined( LVARRAY_ADDR2LINE_EXEC ) + // "-Cpe" asks addr2line for demangled symbol + file:line + inlined call info. std::string const source_line = addr2line( "-Cpe", address ); if( !source_line.empty() && source_line[0] != '?' ) { @@ -317,11 +328,10 @@ namespace LvArray namespace system { -/// An alias for a function that takes an int and returns nothing. -using handle_type = void ( * )( int ); - -/// A map containing the initial signal handlers. -static std::map< int, handle_type > initialHandler; +// Snapshot of handlers that were active before LvArray installs its own handlers. +// We keep these so resetSignalHandling() can restore previous process behavior. +static struct sigaction g_oldAction[NSIG]; +static bool g_oldActionSet[NSIG] = {}; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// std::string stackTrace( bool const location ) @@ -329,6 +339,7 @@ std::string stackTrace( bool const location ) constexpr int MAX_FRAMES = 25; void * array[ MAX_FRAMES ]; + // Skip this helper frame so frame 0 is the caller of stackTrace(). std::size_t const size = collect( array, MAX_FRAMES, 1 ); std::ostringstream oss; @@ -432,8 +443,9 @@ void callErrorHandler() /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -void signalHandler( int sig, siginfo_t * info, void * /*ucontext*/ ) +void signalHandler( int sig, siginfo_t * info, void * ucontext ) { + LVARRAY_UNUSED_VARIABLE( ucontext ); std::ostringstream oss; if( sig >= 0 && sig < NSIG ) @@ -448,6 +460,7 @@ void signalHandler( int sig, siginfo_t * info, void * /*ucontext*/ ) switch( info->si_code ) { + // POSIX SIGFPE subcodes let us report the concrete floating-point fault. 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; @@ -459,6 +472,22 @@ void signalHandler( int sig, siginfo_t * info, void * /*ucontext*/ ) } } } + else if( sig == SIGILL && info ) + { +#if defined(__APPLE__) && defined(__MACH__) && defined(__aarch64__) + if( info->si_code == ILL_ILLTRP ) + { + // Apple arm64 may report FP traps as SIGILL/ILL_ILLTRP instead of SIGFPE. + // In that mode the subtype is not exposed, so we emit the best available text. + oss << " SIGILL si_code = " << info->si_code + << " (floating-point trap, subtype unavailable on this platform)\n"; + } + else +#endif + { + oss << " SIGILL si_code = " << info->si_code << "\n"; + } + } } oss << stackTrace( true ) << std::endl; @@ -468,47 +497,146 @@ void signalHandler( int sig, siginfo_t * info, void * /*ucontext*/ ) } -static struct sigaction g_oldAction[NSIG]; - void setSignalHandling( void (* handler)( int, siginfo_t * info, void * ) ) { struct sigaction sa; + memset( &sa, 0, sizeof( sa ) ); sigemptyset( &sa.sa_mask ); - sa.sa_sigaction = handler; - sa.sa_flags = SA_SIGINFO; + if( handler == nullptr ) + { + sa.sa_handler = SIG_DFL; + sa.sa_flags = 0; + } + else + { + sa.sa_sigaction = handler; + sa.sa_flags = SA_SIGINFO; + } auto install = [&]( int sig ) { - sigaction( sig, &sa, &g_oldAction[sig] ); + if( sig <= 0 || sig >= NSIG ) + { + return; + } + + if( g_oldActionSet[sig] ) + { + // We already cached the original action for this signal, so do not overwrite it. + sigaction( sig, &sa, nullptr ); + } + else if( sigaction( sig, &sa, &g_oldAction[sig] ) == 0 ) + { + // First install: capture previous action for future resetSignalHandling(). + g_oldActionSet[sig] = true; + } }; +#ifdef SIGHUP install( SIGHUP ); +#endif +#ifdef SIGINT install( SIGINT ); +#endif +#ifdef SIGQUIT install( SIGQUIT ); +#endif +#ifdef SIGILL install( SIGILL ); +#endif +#ifdef SIGTRAP install( SIGTRAP ); +#endif +#ifdef SIGABRT install( SIGABRT ); +#endif +#ifdef SIGFPE install( SIGFPE ); +#endif +#ifdef SIGBUS install( SIGBUS ); +#endif +#ifdef SIGSEGV install( SIGSEGV ); +#endif +#ifdef SIGSYS install( SIGSYS ); +#endif +#ifdef SIGPIPE install( SIGPIPE ); +#endif +#ifdef SIGTERM install( SIGTERM ); +#endif // Do NOT try SIGKILL/SIGSTOP: they can’t be caught. } +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void resetSignalHandling() +{ + auto restore = []( int sig ) + { + if( sig <= 0 || sig >= NSIG || !g_oldActionSet[sig] ) + { + return; + } + + sigaction( sig, &g_oldAction[sig], nullptr ); + }; + +#ifdef SIGHUP + restore( SIGHUP ); +#endif +#ifdef SIGINT + restore( SIGINT ); +#endif +#ifdef SIGQUIT + restore( SIGQUIT ); +#endif +#ifdef SIGILL + restore( SIGILL ); +#endif +#ifdef SIGTRAP + restore( SIGTRAP ); +#endif +#ifdef SIGABRT + restore( SIGABRT ); +#endif +#ifdef SIGFPE + restore( SIGFPE ); +#endif +#ifdef SIGBUS + restore( SIGBUS ); +#endif +#ifdef SIGSEGV + restore( SIGSEGV ); +#endif +#ifdef SIGSYS + restore( SIGSYS ); +#endif +#ifdef SIGPIPE + restore( SIGPIPE ); +#endif +#ifdef SIGTERM + restore( SIGTERM ); +#endif +} + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// int getDefaultFloatingPointExceptions() { + // These are the "hard" numerical faults that should typically stop execution. return ( FE_DIVBYZERO | FE_OVERFLOW | FE_INVALID ); } -unsigned long long int translateFloatingPointException( unsigned long long int const exception ) +static unsigned long long int translateFloatingPointException( unsigned long long int const exception ) { unsigned long long int result = 0; #if defined(__APPLE__) && defined(__MACH__) // if apple + // Darwin arm64 stores trap masks in fpcr-specific bits (__fpcr_trap_*), + // so FE_* must be translated before writing env.__fpcr. if( exception & FE_INEXACT ) { result |= __fpcr_trap_inexact; @@ -529,40 +657,15 @@ unsigned long long int translateFloatingPointException( unsigned long long int c { 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__) +#if defined(__x86_64__) || defined(__i386__) + // Linux x86 feenableexcept/fedisableexcept already use FE_* bit positions. result = exception; #endif #endif -return result; -} - -#if defined(__APPLE__) && defined(__MACH__)&& !defined(__x86_64__) -static void -fpe_signal_handler( int sig, siginfo_t *sip, void *scp ) -{ - LVARRAY_UNUSED_VARIABLE( sig ); - LVARRAY_UNUSED_VARIABLE( scp ); - - int fe_code = sip->si_code; - - if( fe_code == ILL_ILLTRP ) - 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 ); - - abort(); + return result; } -#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// int enableFloatingPointExceptions( int const exceptions ) @@ -570,23 +673,17 @@ int enableFloatingPointExceptions( int const exceptions ) #if defined(__APPLE__) && defined(__MACH__) #if !defined(__x86_64__) - unsigned long long int const exceptionMasks = translateFloatingPointException( exceptions ); + // Apple arm64 path: manipulate fpcr trap bits via fenv_t. + unsigned long long int const exceptionMasks = translateFloatingPointException( exceptions ); fenv_t env; - fegetenv( &env ); - -// std::cout< const & handler ); void callErrorHandler(); /** - * @brief Print signal information and a stack trace to standard out, optionally aborting. + * @brief Print signal information and a stack trace, then terminate. * @param sig The signal received. - * @param exit If true abort execution. + * @param info Additional signal information. + * @param ucontext Platform-specific user context (unused). */ -void signalHandler( int sig, siginfo_t * info, void * /*ucontext*/ ); +void signalHandler( int sig, siginfo_t * info, void * ucontext ); /** diff --git a/unitTests/testFloatingPointExceptions.cpp b/unitTests/testFloatingPointExceptions.cpp index 1ae5ee80..4af78478 100644 --- a/unitTests/testFloatingPointExceptions.cpp +++ b/unitTests/testFloatingPointExceptions.cpp @@ -8,7 +8,6 @@ // Source includes #include "testFloatingPointExceptionsHelpers.hpp" #include "system.hpp" -#include "system.hpp" // TPL includes #include @@ -20,7 +19,18 @@ using namespace testFloatingPointExceptionsHelpers; -const char IGNORE_OUTPUT[] = ".*"; +#if defined(__APPLE__) && defined(__MACH__) && defined(__aarch64__) +char const DIVIDE_BY_ZERO_REGEX[] = + R"(((floating divide by zero)|(floating-point trap, subtype unavailable on this platform))(.|\n)*StackTrace)"; +char const OVERFLOW_REGEX[] = + R"(((floating overflow)|(floating-point trap, subtype unavailable on this platform))(.|\n)*StackTrace)"; +char const INVALID_REGEX[] = + R"(((floating invalid operation)|(floating-point trap, subtype unavailable on this platform))(.|\n)*StackTrace)"; +#else +char const DIVIDE_BY_ZERO_REGEX[] = R"((floating divide by zero)(.|\n)*StackTrace)"; +char const OVERFLOW_REGEX[] = R"((floating overflow)(.|\n)*StackTrace)"; +char const INVALID_REGEX[] = R"((floating invalid operation)(.|\n)*StackTrace)"; +#endif namespace LvArray { @@ -37,19 +47,30 @@ TEST( TestFloatingPointEnvironment, Underflow ) TEST( TestFloatingPointEnvironment, DivideByZero ) { system::setFPE(); - EXPECT_DEATH_IF_SUPPORTED( divide( 1, 0 ), R"((floating divide by zero)(.|\n)*StackTrace)" ); + EXPECT_DEATH_IF_SUPPORTED( divide( 1, 0 ), DIVIDE_BY_ZERO_REGEX ); } -TEST( TestFloatingPointEnvironment, Overlow ) +TEST( TestFloatingPointEnvironment, Overflow ) { system::setFPE(); - EXPECT_DEATH_IF_SUPPORTED( multiply( DBL_MAX, 2 ), R"((floating overflow)(.|\n)*StackTrace)" ); + EXPECT_DEATH_IF_SUPPORTED( multiply( DBL_MAX, 2 ), OVERFLOW_REGEX ); } TEST( TestFloatingPointEnvironment, Invalid ) { system::setFPE(); - EXPECT_DEATH_IF_SUPPORTED( invalid(), R"((floating invalid operation)(.|\n)*StackTrace)" ); + EXPECT_DEATH_IF_SUPPORTED( invalid(), INVALID_REGEX ); +} + +TEST( TestFloatingPointEnvironment, FloatingPointExceptionGuard ) +{ + system::setFPE(); + + { + system::FloatingPointExceptionGuard guard( FE_UNDERFLOW ); + divide( DBL_MIN, 2 ); + EXPECT_DEATH_IF_SUPPORTED( multiply( DBL_MAX, 2 ), OVERFLOW_REGEX ); + } } } // namespace testing From 7c71c947dac404aeab8e855504caef97b5fc99d2 Mon Sep 17 00:00:00 2001 From: Randolph Settgast Date: Mon, 16 Mar 2026 13:44:55 -0700 Subject: [PATCH 4/4] cleanup --- src/system.cpp | 155 ++++++++++++++++++++----------------------------- src/system.hpp | 16 +++-- 2 files changed, 73 insertions(+), 98 deletions(-) diff --git a/src/system.cpp b/src/system.cpp index 1dd013d4..070e4f24 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -443,6 +443,16 @@ void callErrorHandler() /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * @brief Default signal handler: print diagnostics and exit. + * @param sig The signal received. + * @param info Additional signal information (si_code identifies the fault). + * @param ucontext Platform-specific user context (unused). + * @note Uses non-async-signal-safe functions (ostringstream, strsignal, cerr) + * for actionable diagnostics. This is a deliberate best-effort tradeoff; + * if the process is in a state where these fail, the _Exit below will + * still terminate cleanly. + */ void signalHandler( int sig, siginfo_t * info, void * ucontext ) { LVARRAY_UNUSED_VARIABLE( ucontext ); @@ -493,9 +503,49 @@ void signalHandler( int sig, siginfo_t * info, void * ucontext ) oss << stackTrace( true ) << std::endl; std::cerr << oss.str(); - std::_Exit( 1 ); + std::_Exit( ( sig > 0 ) ? ( 128 + sig ) : EXIT_FAILURE ); } +/// Signals that LvArray installs handlers for. +/// Do NOT include SIGKILL or SIGSTOP — they cannot be caught. +static constexpr int handledSignals[] = { +#ifdef SIGHUP + SIGHUP, +#endif +#ifdef SIGINT + SIGINT, +#endif +#ifdef SIGQUIT + SIGQUIT, +#endif +#ifdef SIGILL + SIGILL, +#endif +#ifdef SIGTRAP + SIGTRAP, +#endif +#ifdef SIGABRT + SIGABRT, +#endif +#ifdef SIGFPE + SIGFPE, +#endif +#ifdef SIGBUS + SIGBUS, +#endif +#ifdef SIGSEGV + SIGSEGV, +#endif +#ifdef SIGSYS + SIGSYS, +#endif +#ifdef SIGPIPE + SIGPIPE, +#endif +#ifdef SIGTERM + SIGTERM, +#endif +}; void setSignalHandling( void (* handler)( int, siginfo_t * info, void * ) ) { @@ -513,11 +563,11 @@ void setSignalHandling( void (* handler)( int, siginfo_t * info, void * ) ) sa.sa_flags = SA_SIGINFO; } - auto install = [&]( int sig ) + for( int sig : handledSignals ) { if( sig <= 0 || sig >= NSIG ) { - return; + continue; } if( g_oldActionSet[sig] ) @@ -530,100 +580,24 @@ void setSignalHandling( void (* handler)( int, siginfo_t * info, void * ) ) // First install: capture previous action for future resetSignalHandling(). g_oldActionSet[sig] = true; } - }; - -#ifdef SIGHUP - install( SIGHUP ); -#endif -#ifdef SIGINT - install( SIGINT ); -#endif -#ifdef SIGQUIT - install( SIGQUIT ); -#endif -#ifdef SIGILL - install( SIGILL ); -#endif -#ifdef SIGTRAP - install( SIGTRAP ); -#endif -#ifdef SIGABRT - install( SIGABRT ); -#endif -#ifdef SIGFPE - install( SIGFPE ); -#endif -#ifdef SIGBUS - install( SIGBUS ); -#endif -#ifdef SIGSEGV - install( SIGSEGV ); -#endif -#ifdef SIGSYS - install( SIGSYS ); -#endif -#ifdef SIGPIPE - install( SIGPIPE ); -#endif -#ifdef SIGTERM - install( SIGTERM ); -#endif - // Do NOT try SIGKILL/SIGSTOP: they can’t be caught. + } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void resetSignalHandling() { - auto restore = []( int sig ) + for( int sig : handledSignals ) { if( sig <= 0 || sig >= NSIG || !g_oldActionSet[sig] ) { - return; + continue; } sigaction( sig, &g_oldAction[sig], nullptr ); - }; - -#ifdef SIGHUP - restore( SIGHUP ); -#endif -#ifdef SIGINT - restore( SIGINT ); -#endif -#ifdef SIGQUIT - restore( SIGQUIT ); -#endif -#ifdef SIGILL - restore( SIGILL ); -#endif -#ifdef SIGTRAP - restore( SIGTRAP ); -#endif -#ifdef SIGABRT - restore( SIGABRT ); -#endif -#ifdef SIGFPE - restore( SIGFPE ); -#endif -#ifdef SIGBUS - restore( SIGBUS ); -#endif -#ifdef SIGSEGV - restore( SIGSEGV ); -#endif -#ifdef SIGSYS - restore( SIGSYS ); -#endif -#ifdef SIGPIPE - restore( SIGPIPE ); -#endif -#ifdef SIGTERM - restore( SIGTERM ); -#endif + } } - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// int getDefaultFloatingPointExceptions() { @@ -631,12 +605,15 @@ int getDefaultFloatingPointExceptions() return ( FE_DIVBYZERO | FE_OVERFLOW | FE_INVALID ); } +/// Translate standard FE_* masks to platform-specific fpcr trap bits. +/// This is only needed on Apple arm64 where fpcr uses a different bit layout. +/// Linux (x86 and aarch64) uses feenableexcept/fedisableexcept directly. +#if defined(__APPLE__) && defined(__MACH__) && !defined(__x86_64__) static unsigned long long int translateFloatingPointException( unsigned long long int const exception ) { - unsigned long long int result = 0; -#if defined(__APPLE__) && defined(__MACH__) // if apple // Darwin arm64 stores trap masks in fpcr-specific bits (__fpcr_trap_*), // so FE_* must be translated before writing env.__fpcr. + unsigned long long int result = 0; if( exception & FE_INEXACT ) { result |= __fpcr_trap_inexact; @@ -657,15 +634,9 @@ static unsigned long long int translateFloatingPointException( unsigned long lon { result |= __fpcr_trap_invalid; } -#else // if not apple -#if defined(__x86_64__) || defined(__i386__) - // Linux x86 feenableexcept/fedisableexcept already use FE_* bit positions. - result = exception; -#endif -#endif - return result; } +#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// int enableFloatingPointExceptions( int const exceptions ) diff --git a/src/system.hpp b/src/system.hpp index 6448819c..a407b987 100644 --- a/src/system.hpp +++ b/src/system.hpp @@ -18,8 +18,7 @@ #include #include #include - - +#include namespace LvArray { @@ -83,7 +82,6 @@ void callErrorHandler(); */ void signalHandler( int sig, siginfo_t * info, void * ucontext ); - /** * @brief Set the signal handler for common signals. * @param handler The signal handler. @@ -124,7 +122,7 @@ void setFPE(); /** * @class FloatingPointExceptionGuard - * @brief Changes the floating point environment and reverts it when destoyed. + * @brief Changes the floating point environment and reverts it when destroyed. */ class FloatingPointExceptionGuard { @@ -138,10 +136,16 @@ class FloatingPointExceptionGuard {} /** - * @brief Re-enable the floating point exceptions that were active on construction. + * @brief Clear stale FE status flags and re-enable the floating point exceptions + * that were active on construction. + * @details Clearing flags before re-enabling traps prevents spurious SIGFPE + * from FE flags accumulated by third-party libraries during the guarded scope. */ ~FloatingPointExceptionGuard() - { enableFloatingPointExceptions( m_previousExceptions ); } + { + std::feclearexcept( FE_ALL_EXCEPT ); + enableFloatingPointExceptions( m_previousExceptions ); + } private: /// The floating point exceptions that were active on construction.