macosclang++sigfpe

How do you enable floating point exceptions for clang in OS X?


I want my code to terminate when there is a floating point error. In linux-gcc the "feenableexcept()" function does the job, but that isn't available on in OSX. When using gcc on OS X the approach taken in (Enabling floating point interrupts on Mac OS X Intel) works just fine when using gcc, but does not work when using clang.

Example code:

#include <stdio.h>
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <xmmintrin.h>

void handler(int sig) {
  void *array[10];
  size_t size;
  size = backtrace(array, 10);
  fprintf(stderr, "Error: signal %d:\n", sig);
  backtrace_symbols_fd(array, size, STDERR_FILENO);
  exit(1);
}

int main(int argc, char **argv)
{
  _MM_SET_EXCEPTION_MASK( ( _MM_EXCEPT_INVALID |
                _MM_EXCEPT_DENORM |
                _MM_EXCEPT_DIV_ZERO |
                _MM_EXCEPT_OVERFLOW |
                _MM_EXCEPT_UNDERFLOW |
                _MM_EXCEPT_INEXACT ) );

  signal(SIGSEGV, handler);
  signal(SIGFPE, handler);

  std::cout<<"Perform 1.0/0.0"<<std::endl;
  double a = 1.0/0.0;
  std::cout<<"1.0/0.0 didn't kill program, result is "<<a<<std::endl<<std::endl;

  int* foo = (int*) - 1 ;// make a bad pointer
  std::cout<<"Attempting to print a bad pointer"<<std::endl;
  printf("%d\n", *foo);
  std::cout<<"Bad pointer didn't kill program."<<std::ends;
}

When compiled using gcc5, the result is:

Perform 1.0/0.0
Error: signal 8:
0   a.out                               0x000000010f97cb7f _Z7handleri + 28
1   libsystem_platform.dylib            0x00007fff895c652a _sigtramp + 26
2   ???                                 0x00007fff6eab6568 0x0 + 140735050114408
3   libdyld.dylib                       0x00007fff936a15ad start + 1

Wonderful. Works great. However, when compiled using clang (Apple LLVM version 7.3.0 (clang-703.0.29)) the result is this:

Perform 1.0/0.0
1.0/0.0 didn't kill program, result is inf

Attempting to print a bad pointer
Error: signal 11:
0   a.out                               0x000000010d501d1f _Z7handleri + 31
1   libsystem_platform.dylib            0x00007fff895c652a _sigtramp + 26
2   ???                                 0x00007fff62b7e568 0x0 + 140734849607016
3   libdyld.dylib                       0x00007fff936a15ad start + 1

Not great. Did not raise the FPE, and the code just kept chugging along. I have looked around, and can't find how to get clang to raise the FPE. Does anyone have experience with this? Thanks!


Solution

  • The accepted approach seems to be a new header to a project's built-in includes. For Linux, this file will be ignored and simply fallback to the stock fenv.h.

    This code is credit 2009, David N. Williams, borrowed verbatim from the ardupilot project, which was the first result I could find. I can confirm this works on MacOS 10.13 with Apple LLVM version 9.0.0 (clang-900.0.39.2).

    Note: Note, although many suggestions I've seen recommend uniquely naming this header for clarity's sake, in many ways it reminds me of a polyfill (term from JS/webdev), so mimicing that ideology the project the code is borrowed from as well as my own project have kept it names fenv.h. Rename as needed.

    fenv.h

    #pragma once
    
    #include_next <fenv.h>
    
    #if defined(__APPLE__) && defined(__MACH__)
    
    // Public domain polyfill for feenableexcept on OS X
    // http://www-personal.umich.edu/~williams/archive/computation/fe-handling-example.c
    
    inline int feenableexcept(unsigned int excepts)
    {
        static fenv_t fenv;
        unsigned int new_excepts = excepts & FE_ALL_EXCEPT;
        // previous masks
        unsigned int old_excepts;
    
        if (fegetenv(&fenv)) {
            return -1;
        }
        old_excepts = fenv.__control & FE_ALL_EXCEPT;
    
        // unmask
        fenv.__control &= ~new_excepts;
        fenv.__mxcsr   &= ~(new_excepts << 7);
    
        return fesetenv(&fenv) ? -1 : old_excepts;
    }
    
    inline int fedisableexcept(unsigned int excepts)
    {
        static fenv_t fenv;
        unsigned int new_excepts = excepts & FE_ALL_EXCEPT;
        // all previous masks
        unsigned int old_excepts;
    
        if (fegetenv(&fenv)) {
            return -1;
        }
        old_excepts = fenv.__control & FE_ALL_EXCEPT;
    
        // mask
        fenv.__control |= new_excepts;
        fenv.__mxcsr   |= new_excepts << 7;
    
        return fesetenv(&fenv) ? -1 : old_excepts;
    }
    
    #endif