CURRENT: CLANG 3.3 and -stad=c++11 and -stdlib=libc++: isnan()/isninf() oddity
brde at optusnet.com.au
Thu Jul 11 04:21:43 UTC 2013
On Wed, 10 Jul 2013, Garrett Wollman wrote:
> <<On Wed, 10 Jul 2013 22:12:59 +0200, Tijl Coosemans <tijl at freebsd.org> said:
>> I think isnan(double) and isinf(double) in math.h should only be
>> visible if (_BSD_VISIBLE || _XSI_VISIBLE) && __ISO_C_VISIBLE < 1999.
>> For C99 and higher there should only be the isnan/isinf macros.
> I believe you are correct. POSIX.1-2008 (which is aligned with C99)
> consistently calls isnan() a "macro", and gives a pseudo-prototype of
> int isnan(real-floating x);
Almost any macro may be implemented as a function, if no conforming
program can tell the difference. It is impossible for technical reasons
to implement isnan() as a macro (except on weird implementations where
all real-floating types are physically the same). In the FreeBSD
implementation, isnan() is a macro, but it is also a function, and
the macro expands to the function in double precision:
% #define isnan(x) \
% ((sizeof (x) == sizeof (float)) ? __isnanf(x) \
% : (sizeof (x) == sizeof (double)) ? isnan(x) \
% : __isnanl(x))
I don't see how any conforming program can access the isnan() function
directly. It is just as protected as __isnan() would be. (isnan)()
gives the function (the function prototype uses this), but conforming
programs can't do that since the function might not exist. Maybe some
non-conforming program like autoconfig reads <math.h> or libm.a and
creates a bug for C++.
The FreeBSD isnan() implementation would be broken by removing the
isnan() function from libm.a or ifdefing it in <math.h>. Changing the
function to __isnan() would cause compatibility problems. The function
is intentionally named isnan() to reduce compatibility problems.
OTOH, the all of the extern sub-functions that are currently used should
bever never be used, since using them gives a very low quality of
- the functions are very slow
- the functions have names that confuse compilers and thus prevent
compilers from replacing them by builtins. Currently, only gcc
automatically replaces isnan() by __builtin_isnan(). This only
works in double precision. So the FreeBSD implementation only
works right in double precision too, only with gcc, __because__
it replaces the macro isnan(x) by the function isnan(x). The
result is inline expansion, the same as if the macro isnan()
is replaced by __builtin_isnan(). clang never does this automatic
replacement, so it generates calls to the slow library functions.
Other things go wrong for gcc in other precisions:
- if <math.h> is not included, then isnan(x) gives
__builtin_isnan((double)x). This sort of works on x86, but is
low quality since it is broken for signaling NaNs (see below).
One of the main reasons reason for the existence of the
classification macros is that simply converting the arg to a common
type and classifying the result doesn't always work.
- if <math.h> is not included, then spelling the API isnanf() or
isnanl() gives correct results but a warning about these APIs
not being declared. These APIs are nonstandard but are converted
to __builtin_isnan[fl] by gcc.
- if <math.h> is included, then:
- if the API is spelled isnan(), then the macro converts to
__isnanf() or __isnanl(). gcc doesn't understand these, and
the slow extern functions are used.
- if the API is spelled isnanf() or isnanl(), then the result is
correct and the warning magically goes away. <math.h> declares
isnanf(), but gcc apparently declares both iff <math.h> is included.
gcc also optimizes isnanl() on a float arg to __builtin_isnanf().
- no function version can work in some cases, because any function version
may have unwanted side effects. This is another of the main reason
for the existence of these and other macros. The main unwanted side
effect is signaling for signaling NaNs. C99 doesn't really support
signaling NaNs, even with the IEC 60559 extensions, so almost anything
is allowed for them. But IEEE 854 is fairly clear that isnan() and
classification macros shouldn't raise any exceptions. IEEE 854 is
even clearer that copying values without changing their representation
should (shall?) not cause exceptions. But on i387, just loading a float
or double value changes its representation and generates an exception
for signaling NaNs, while just loading a long double value conforms to
IEEE 854 and doesn't change its representation or generate an exception.
Passing of args to functions may or may not load the values. ABIs may
require a change of representation. On i387, passing of double args
should go through the FPU for efficiency reasons, and this changes the
representation twice to not even get back to the original (for signaling
NaNs, it generates an exception and sets the quiet bit in the result;
thus a classification function can never see a signaling NaN in double
precision). So a high quality inplementation must not use function
versions, and it must also use builtins that don't even load the values
into normal FP registers if this might cause an exception or change the
values. Both gcc's and clang's builtins are broken on i387 (i386 or
amd64 -m32) since the do load the value.
In view of all these bugs, the best available implementation of isnan(x)
is ((x) != (x)) (using an unportable statement-expression to avoid
multiple evaluation). The known bugs in this implementation are:
- it will load the value and thus give unwanted exceptions for signaling
NaNs in some cases. But compiler builtins are no better.
(IEEE 854 has a lot to say about exceptions for comparison operators,
and the builtin comparison operators exist in C99 for related reasons.
IIRC, the comparison operators are useless with IEEE 854 conformance,
since ordinary comparison operators then do the right thing. The details
are unclear, but I couldn't find any cases where gcc or clang on x86
do the wrong thing or do different things for the builtin comparison
operators. For ((x) != (x)), they generate an "unordered" comparison
and this is the right thing for isnan(). Their builtin isnan()s generate
exactly the same code as for ((x) != (x)).)
- it may be broken by flags like -ffast-math that turn off IEEE conformance.
More information about the freebsd-standards