Re: CURRENT: CLANG 3.3 and -stad=c++11 and -stdlib=libc++: isnan()/isninf() oddity

From: Bruce Evans <brde_at_optusnet.com.au>
Date: Thu, 11 Jul 2013 14:21:19 +1000 (EST)
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
implementation:
- 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.

Bruce
Received on Thu Jul 11 2013 - 02:21:43 UTC

This archive was generated by hypermail 2.4.0 : Wed May 19 2021 - 11:40:39 UTC