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:
https://github.com/neuronsimulator/nrn/blob/feexc-nvhpc/src/oc/math.cpp#L124
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.
Hello Mat,
Given your suggestions we have fixed the issue for NVHPC 22.3 by compiling the offending file with something similar to -O1 -DNOERRNO
.
All worked well until we tried using NVHPC 23.1 where we see an underflow exception when calling pow(0, 2)
.
Here is an updated version of the above code snippet that executes the pow
and sqrt
functions:
#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 (std::fetestexcept(FE_DIVBYZERO)) {
std::cout << "divbyzero" << std::endl;
}
if (std::fetestexcept(FE_OVERFLOW)) {
std::cout << "overflow" << std::endl;
}
if (std::fetestexcept(FE_UNDERFLOW)) {
std::cout << "underflow" << std::endl;
}
if (check_fe_except) {
std::feclearexcept(FE_ALL_EXCEPT);
}
errno = 0;
if (++hoc_errno_count > MAXERRCOUNT) {
} else {
fprintf(stderr, "%s result out of range\n", s);
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");
}
double hoc_Pow(double x, double y) {
#ifndef NO_CLEAR
clear_fe_except();
#endif
return hoc_errcheck(pow(x, y), "exponentiation");
}
int main() {
double res;
int i;
for (i=1; i>=-1;--i) {
res = hoc_Pow(i,2);
printf("RES pow %d: %f\n",i,res);
}
for (i=1; i>=-1;--i) {
res = hoc_Sqrt(i);
printf("RES sqrt %d: %f\n",i,res);
}
}
Here is also the output from the compilation and execution:
$ nvc++ -O1 -DNOERRNO test.cpp; ./a.out
RES pow 1: 1.000000
underflow
exponentiation result out of range
RES pow 0: 0.000000
RES pow -1: 1.000000
RES sqrt 1: 1.000000
RES sqrt 0: 0.000000
argument out of domain
RES sqrt -1: -nan
Give this output we believe that there is some issue with the implementation of the pow
function by the newest NVHPC.
Do you think there is an easy fix around that, apart from using -Mnobuiltin
or completely disabling exceptions?
I thin the solution here is to add the “-Kieee” flag. Without it, we don’t guarantee adherence to IEEE 754 nor about setting ERRNO, or properly managing overflow/underflow.
Performance of the application may suffer a bit, Then again, you are compiling with -O1 so the compiler doesn’t optimize away the pow calls, so performance may not be of interest here.
% nvc++ -O1 -DNOERRNO -Kieee test.cpp ; ./a.out
RES pow 1: 1.000000
RES pow 0: 0.000000
RES pow -1: 1.000000
RES sqrt 1: 1.000000
RES sqrt 0: 0.000000
argument out of domain
RES sqrt -1: -nan
Hello Mat,
Thank you very much for your reply.
Indeed we don’t care that much for the performance in these specific calls so we can get away with -Kieee
which generates the proper exceptions.
Ioannis