Floating point exception trapping on M1

I have written a simple test c++ program (below) that takes the square root of a negative number and then tries to print it out. I would like to trap the floating point exception caused by taking the square root of a negative number (e.g., I'd like the program to halt with an error after the floating point exception). On Intel Macs, I know how to do this. Is this possible on an Apple Silicon Mac?

#include <cmath>
#include <iostream>

int main() {
  const double x = -1.0;
  double y = x;
  y = sqrt(y);  // floating point exception...possible to build program so it terminates here?
  std::cout << y << "\n";
  return 0;
}

At present the compiler does not support -ffp-exception-behavior=strict when compiling for Apple M1 processors.

If you just want to test for exceptions in the code yourself you can check the floating point status using routines from cfenv header (in particular the std::fetestexcept() function can be used to test for a specific exception).

If you want to trap on floating point exceptions, you can manually manipulate the exception bits of the float point control register using std::fesetenv().

Thanks very much for the reply, @Frameworks Engineer! I tried this as you suggest, but it didn't quite work. Here's the code I tried:

#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";
}

When I compile with

clang++ -g -std=c++17 -o fpe fpe.cpp

and then run, then I get the following output on the M1 Mac:

nan
zsh: illegal hardware instruction  ./fpe

Does this mean that the M1 chip just doesn't support FPE trapping on the hardware level?

That's just how the trap manifests. If you run the code in lldb, you should see it has stopped on the fsqrt instruction that is being passed the negative value.

It seems that on the M1, MacOS does not send a SIGFPE, but rather a SIGILL, when floating point exceptions are unblocked. Here is a version of the code above that will work.

I've also included a call to sigaction and a second handler to query the signal code that is sent.

#include <fenv.h>    
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

void fpe_signal_handler(int sig)
{
    printf("Floating point exception\n");
    int i = fetestexcept(FE_INVALID);
    if (i & FE_INVALID)
        printf("----> Invalid valued detected\n");

    exit(1);
}

static void
fpe_signal_action( int sig, siginfo_t *sip, void *scp )
{
    /* see signal.h for codes */
    int fe_code = sip->si_code;
    if (fe_code == ILL_ILLTRP)
        printf("Illegal trap detected\n");
    else
        printf("Code detected : %d\n",fe_code);

    int i = fetestexcept(FE_INVALID);
    if (i & FE_INVALID)
        printf("---> Invalid valued detected\n");

    abort();
}

void enable_floating_point_exceptions()
{
    fenv_t env;
    fegetenv(&env);
    env.__fpcr = env.__fpcr | __fpcr_trap_invalid;
    fesetenv(&env);

#if 1
    signal(SIGILL,fpe_signal_handler);
#else
    struct sigaction act;
    act.sa_sigaction = fpe_signal_action;
    sigemptyset (&act.sa_mask);
    act.sa_flags = SA_SIGINFO;
    sigaction(SIGILL, &act, NULL);
#endif    
}

void main()
{    
    const double x = -1;
    printf("y = %f\n",sqrt(x));    
    enable_floating_point_exceptions();
    printf("y = %f\n",sqrt(x));
}

I compiled this as:

gcc -o fpe fpe.c

and the output is:

y = nan
Floating point exception
----> Invalid valued detected

The good news is that only exceptions that are unmasked will be handled. If an exception remains masked, the code terminates normally. Also, it does not seem to be an issue with the M1, but rather with MacOS (Monterey 12.3.1, in my case) on the M1. On a Linux VM (on Apple silicon), the SIGFPE works as expected.

One question I have is how reliable fetestexcept is in this case. Can it be relied upon to determine the exception that was caught when used in the signal handler? Or is it better to use codes sent by SIGFPE (when available) and detect FPE_FLTINV, FPE_FLTDIV and so on?

Related StackOverflow posts :

How to trap floating-point exceptions on M1 Macs

Trapping floating point exceptions and signal handling on Apple silicon

You can decode the nature of the FPU exception in the SIGILL handler by declaring the third argument of your sigaction handler as a struct ucontext*, and then decoding scp->uc_mcontext->__es.esr in your handler. This is the value of the ESR_ELx, Exception Syndrome Register (ELx) register. If its top 6 bits (EC) are 0b101100, then the signal was triggered by a trapping AArch64 FPU operation. If in that case bit 23 of that register (TFV) is also 1, then the register's lower 7 bits will match what the lower 7 bits of the fpsr register would have been in case trapping would have been disabled (i.e., the kind of FPU exception triggered by the instruction).

See section D7.2.27 of the ARMv8 architecture manual for more information.

Has this deficiency in the compiler been addressed by now? If so, which version of clang? Having to write custom code to trap FPEs is ugly and not great for interoperability when developing code for macOS, Linux etc.

Floating point exception trapping on M1
 
 
Q