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

Replies

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