Math_errhandling not working as expected with nvc++ compiler - part 2

Hello,

Following up on Math_errhandling not working as expected with nvc++ compiler.

With the same code snippet, turns out that whenever we have an optimization level bigger than 1, FE_INVALID is not set. i.e.:

O2

$ nvc++ -O2  t.cpp
"t.cpp", line 7: warning: unrecognized STDC pragma
  #pragma STDC FENV_ACCESS ON
               ^

$ ./a.out 
sqrt(100) = 10
sqrt(2) = 1.41421
golden ratio = 1.61803
sqrt(-0) = 0
errno_enabled: 1 check_fe_except: 2
sqrt(-1.0) = nan
    errno = 0 Success

O1

$ nvc++ -O1 t.cpp
"t.cpp", line 7: warning: unrecognized STDC pragma
  #pragma STDC FENV_ACCESS ON
               ^

 ./a.out 
sqrt(100) = 10
sqrt(2) = 1.41421
golden ratio = 1.61803
sqrt(-0) = 0
errno_enabled: 1 check_fe_except: 2
sqrt(-1.0) = -nan
    errno = 0 Success
    FE_INVALID raised

Whereas nvcc performs as expected:

$ nvcc -O2 t.cpp
$ ./a.out 
sqrt(100) = 10
sqrt(2) = 1.41421
golden ratio = 1.61803
sqrt(-0) = -0
errno_enabled: 1 check_fe_except: 2
sqrt(-1.0) = -nan
    errno = 33 Numerical argument out of domain
    errno = EDOM Numerical argument out of domain
    FE_INVALID raised

Is there a way to make use of the NVHPC math builtins (i.e. vsqrt) in order to have consistent FE Exceptions set regardless of the optimization level?

Thanks and regards,
Alex

What’s happening here is that at high opt level, the compiler sees that “sqrt(-1)” is invariant so is precomputing it at compile time. Hence vsqrt isn’t called at runtime, and no exception raised.

You’ll need to change the code so the compiler can’t do this optimization, for example by passing in “-1” as a command line argument:

% cat test.cpp
#include <iostream>
#include <cmath>
#include <cerrno>
#include <cfenv>
#include <cstring>

#pragma STDC FENV_ACCESS ON

int main( int argc, char *argv[] )
{
    int val;
    val = atoi(argv[1]);
    // normal use
    std::cout << "sqrt(100) = " << std::sqrt(100) << '\n'
              << "sqrt(2) = " << std::sqrt(2) << '\n'
              << "golden ratio = " << (1+std::sqrt(5))/2 << '\n';
    // special values
    std::cout << "sqrt(-0) = " << std::sqrt(-0.0) << '\n';
    // error handling
    errno = 0;
    const auto errno_enabled = math_errhandling & MATH_ERRNO;
    const auto check_fe_except = math_errhandling & MATH_ERREXCEPT;
    std::cout << "errno_enabled: " << errno_enabled << " check_fe_except: " << check_fe_except << std::endl;
    std::feclearexcept(FE_ALL_EXCEPT);
    double d = std::sqrt(val);;
    std::cout << "sqrt(" << val << ") = " << d << '\n';
    std::cout << "    errno = " << errno << " " << std::strerror(errno) << '\n';
    if(errno == EDOM)
        std::cout << "    errno = EDOM " << std::strerror(errno) << '\n';
    if(std::fetestexcept(FE_INVALID))
        std::cout << "    FE_INVALID raised\n";
}
% nvc++ -w -O2 test.cpp ; a.out -1
sqrt(100) = 10
sqrt(2) = 1.41421
golden ratio = 1.61803
sqrt(-0) = 0
errno_enabled: 1 check_fe_except: 2
sqrt(-1) = -nan
    errno = 0 Success
    FE_INVALID raised

-Mat

Thank you Mat,

Actually the described behavior is happening at runtime (I’ve tried to narrow it down to the example in the original thread but apparently there is something different in light of your reply).

In the original code, if we add a print before the FE exception check, it works as expected and we get FE_INVALID raised. You can check it out here:

Thanks and regards,
Alex

Thanks Alex. I derived a test code from this (below), and as best I can tell, the problem is with the call to “std::feclearexcept”. If I simply don’t call this (or move it into main), then the invalid exception is raised.

You can also use the “-Mnobuiltin” so the libm sqrt is called and errno will be set.

% cat test.cpp
#include <iostream>
#include <cmath>
#include <cerrno>
#include <cfenv>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#define MAXERRCOUNT 5
#ifdef NOERRNO
const auto errno_enabled = false;
const auto check_fe_except = true;
#else
const auto errno_enabled = true;
const auto check_fe_except = false;
#endif
int hoc_errno_count;

#ifndef NO_CLEAR
static inline void clear_fe_except() {
    if (check_fe_except) {
        std::feclearexcept((0x20 | 0x04 | 0x10 | 0x08 | 0x01));
    }
}
#endif

double hoc_errcheck(double d, const char* s) /* check result of library call */
{

    if ((errno_enabled && errno == EDOM) || (check_fe_except && std::fetestexcept(FE_INVALID))) {
        if (check_fe_except) {
            std::feclearexcept(FE_ALL_EXCEPT);
        }
        errno = 0;
        fprintf(stderr, "argument out of domain\n");
    } else if ((errno_enabled && errno == ERANGE) ||
               (check_fe_except &&
                (std::fetestexcept(FE_DIVBYZERO) || std::fetestexcept(FE_OVERFLOW) ||
                 std::fetestexcept(FE_UNDERFLOW)))) {
        if (check_fe_except) {
            std::feclearexcept(FE_ALL_EXCEPT);
        }
        errno = 0;
        if (++hoc_errno_count > MAXERRCOUNT) {
        } else {
            fprintf(stderr, "result out of range\n");
            if (hoc_errno_count == MAXERRCOUNT) {
                fprintf(stderr, "No more errno warnings during this execution\n");
            }
        }
    }
    return d;
}

double hoc_Sqrt(double x) {
#ifndef NO_CLEAR
    clear_fe_except();
#endif
    return hoc_errcheck(sqrt(x), "sqrt");
}

int main() {

   double res;
   int i;
   for (i=10; i>=-1;--i) {
        res = hoc_Sqrt(i);
        printf("RES %d: %f\n",i,res);
   }

}
% nvc++ -O2 -Mnobuiltin test.cpp ; a.out
RES 10: 3.162278
RES 9: 3.000000
RES 8: 2.828427
RES 7: 2.645751
RES 6: 2.449490
RES 5: 2.236068
RES 4: 2.000000
RES 3: 1.732051
RES 2: 1.414214
RES 1: 1.000000
RES 0: 0.000000
argument out of domain
RES -1: -nan
% nvc++ -O2 -DNOERRNO -Mnobuiltin test.cpp ; a.out
RES 10: 3.162278
RES 9: 3.000000
RES 8: 2.828427
RES 7: 2.645751
RES 6: 2.449490
RES 5: 2.236068
RES 4: 2.000000
RES 3: 1.732051
RES 2: 1.414214
RES 1: 1.000000
RES 0: 0.000000
argument out of domain
RES -1: -nan
% nvc++ -O2 -DNOERRNO  test.cpp ; a.out
RES 10: 3.162278
RES 9: 3.000000
RES 8: 2.828427
RES 7: 2.645751
RES 6: 2.449490
RES 5: 2.236068
RES 4: 2.000000
RES 3: 1.732051
RES 2: 1.414214
RES 1: 1.000000
RES 0: 0.000000
RES -1: -nan
% nvc++ -O2 -DNOERRNO -DNO_CLEAR test.cpp ; a.out
RES 10: 3.162278
RES 9: 3.000000
RES 8: 2.828427
RES 7: 2.645751
RES 6: 2.449490
RES 5: 2.236068
RES 4: 2.000000
RES 3: 1.732051
RES 2: 1.414214
RES 1: 1.000000
RES 0: 0.000000
argument out of domain
RES -1: -nan

Thanks Mat for looking into this. We will experiment further.