svn commit: r253215 - head/lib/msun/src

Bruce Evans brde at optusnet.com.au
Sun Jul 14 05:09:54 UTC 2013


On Sat, 13 Jul 2013, Tijl Coosemans wrote:

> On 2013-07-12 11:14, Bruce Evans wrote:
>> On Thu, 11 Jul 2013, Tijl Coosemans wrote:
>>> On 2013-07-11 22:03, Tijl Coosemans wrote:
>>>> ...
>>>> isnan(double) is part of SUSv2. It should be visible when compiling with
>>>> -D_XOPEN_SOURCE=500. I think you need something like this:
>>>>
>>>> #if (__BSD_VISIBLE || __XSI_VISIBLE <= 500) && __ISO_C_VISIBLE < 1999
>>>> int    isinf(double) __pure2;
>>>> int    isnan(double) __pure2;
>>>> #endif
>>>
>>> Actually this:
>>>
>>> #if (__BSD_VISIBLE || (defined(__XSI_VISIBLE) && __XSI_VISIBLE <= 500))
>>> && __ISO_C_VISIBLE < 1999
>>
>> Remove the __ISO_C_VISIBLE part, since this is not in C90.  This also
>> fixes a style bug (long line).
>
> I shouldn't have mentioned C90. What I meant to say is the
> not-C99-or-higher case which is further restricted by __BSD_VISIBLE
> and __XSI_VISIBLE, but where the macros aren't defined.

Maybe just put it back where it was then.  We still have isnanf() under
__BSD_VISIBLE and no other ifdef.  It is unsorted into the __BSD_VISIBLE
section for doubles.  We also have finite() and finitef().  These are
old aliases for isfinite().  At least they are sorted.  I'd like to remove
all of these.  But msun still uses all of them internally.  It still has
a compatibility hack to misuse isnanf.  This shows how hard it is to remove
zombies.

> These two symbols are the cause for the original problem report about
> clang's cmath header (in C++11 isnan/isinf are functions returning bool
> not int). Their visibility had to be constrained somehow and that
> cascaded into redefining the isnan and isinf macros because they were
> implemented using these functions. I think completely removing these
> symbols is wrong however because it breaks the API in the specific case
> of the #if above.

It's safer to clean only 1 thing at a time anyway.

>> I noticed some more problems in the implementation of these macros
>> and others:
>> - many or all of the __pure2's in the prototypes are wrong, since even
>>   the classification functions can have side effects for signaling
>>   NaNs.  It is impossible to avoid these side effects for extern
>>   functions in some cases, since the ABI gives them.  I think static
>>   inline functions must have the same results as extern functions,
>>   so compilers should pessimize inline functions as necessary to
>>   get the same bad results, but compilers don't do that.
>
> Apparently, in the 2008 version of IEEE 754 they are considered
> non-computational and never generate exceptions, even for sNaN.
>
> The old IEEE 754-1985 only mentions isfinite and isnan and says
> implementations may consider them non-arithmetic.

IEEE 854-1987 says the same as 754-1987 in an appendix (recommended
behaviour; not part of the standard).  It says the same for copysign()
and unary negation.  It also says that isnan(x) is equivalent to x !=
x and that isfinite(x) is true precisely when -Inf < x < Inf.  Many
comparisons are specified to raise the invalid exception for invalid
operands, and signaling NaNs can certainly be considered invalid.  The
!= comparsion mentioned in the appendix is one of the ones that must
be "unordered" and thus must raise the exception.  However, comparisons
are not considered as operations, so the requirement that operations
with signaling NaNs raise the exception doesn't apply.  The standard
is unclear here.

>> - classification functions are specified to convert to the semantic
>>   type (remove any extra precision or exponent range) before classifying.
>
> Yes, it makes the macros more function-like.
>
>>   For example, if x is a double slightly larger than sqrt(DBL_MAX), and
>>   if double expressions are evaluated in extra exponent range, then x*x
>>   is finite with the extra range but infinite in the semantic type.  So
>>   isinf(x*x) is true, and the implementation
>>   #define isinf(x) (fabs(x) == INFINITY) is invalid.  clang on x86 gets
>>   __builtin_isinf(x*x) this right as a side effect of its pessimization
>>   of fabs() to non-inline -- parameter passing to the extern fabs()
>>   converts to the semantic type.  Sometimes the arg is known not to have
>>   extra range, so no conversion is needed.
>
> If isinf isn't supposed to generate exceptions then it cannot use a
> floating point comparison either. That would only leave bit operations.

Indeed.  I thought that the i387 unordered comparison instructions doesn't
raise the exception for signaling NaNs, but it does.  Similarly for
SSE unordered comparision.

Recent changes actually broke many cases that used to work :-(.  Compilers
don't understand this, so they generate x != x.  __isnan(), __isnanf()
and __isnanl() work since they use bit operations, provided calling them
doesn't change the precision.  So the cases that used to work on x86
except ones where the arg is float or double AND the arch is i386 AND
SSE is not used AND arg passing involves copying through the i387 (the
latter depends on the compiler and compiler options).  All cases with
signaling NaNs on x86 are now broken.

>> I think C11 has new mistakes for extra precision.  It specifies that
>> return reduces to the semantic type, like the classification macros
>> are required to do for their arg.  clang -std=c11 doesn't implement
>> this bug for at least:
>>
>>     #include <math.h>
>>     double sq(double x) { return (x*x); }
>>     double sq2(double x) { return (fabs(x*x); }
>>
>> On i386 without SSE2 (so extra precision), this generates the same code
>> as with -std=c99.  Squaring x gives extra precision and exponent range.
>> This is not destroyed on return, so extra precision is not defeated by
>> writing the squaring operation as a function.  fabs() is inlined in both
>> cases, so it has little effect here (no effect unless x is NaN), but I
>> think even C99 doesn't permit this.  If fabs() were not inline, then
>> the ABI would force destruction of the extra precision and range when
>> it is called, and I think C99 requires conversion to the semantic type
>> for calls.
>
> For function parameters both C99 and C11 state that they are converted
> as if by assignment meaning extra precision should be removed. This is
> also required by IEEE 754-2008. Both C99 and C11 state that making a
> function inline suggests calls to the function should be as fast as
> possible, but I don't think this allows skipping any conversions so
> even if a function is inlined the compiler should remove extra
> precision.
>
> If a floating point value already has the right precision then
> assignment and as-if-by-assignment may be seen as copy operations that
> don't raise any exceptions even for sNaN. I suppose this means math
> functions must always use each argument in at least one arithmetic
> operation to trigger sNaNs.
>
> For return statements both C99 and C11 state that it's not an
> assignment. Only if the type of the return expression differs from the
> return type is the result converted as if by assignment. There's a
> footnote that says this allows floating point values to be returned with
> extra precision if there's no conversion. The extra precision can be
> removed with a cast. IEEE 754-2008 requires that extra precision is
> removed.

The C99 requirement is bizarre.  It allows sloppy code like

     include <math.h>
     double sq(double x) { return (x*x); }

to return the extra precision,  but non-sloppy code like

     include <math.h>
     double sq(double x) { return ((double_t)x*x); }

that uses double_t to ensure no internal loss of extra precsision is forced
to lose the extra precision on return.  (double_t in this example does
nothing.  In a real example it would be used so that extra precision is
not lost on assignment or due to compiler spilling bugs.)

My understanding of the C11 regression is that the extra precision is
required to be lost as if by assignment on return even when the return
type is the same as the expression type.

I'm surprised if IEEE 754-2008 gets this wrong.  IEEE 854-1987 only has
the sqrt() function.  It is allowed to evaluate the result in extra
precision, but only if the destination is wide enough to hold the
extra precision.  It is required to be provided in all supported
precisions (this is presumably only from each precision to itself) and
deliver correctly rounded results in all precision.  i387 sqrt()
probably doesn't comply.  It essentially only supports long double
precision, and I don't know of anything to prevent double rounding
when reducing to lower precisions.

> Also, fabs is somewhat special. In IEEE 754-1985 it's considered equal
> to copysign(x, 1.0) which may but doesn't have to raise exceptions even
> for sNaN. In IEEE 754-2008, operations that only affect the sign, like
> fabs, don't raise exceptions.

fabs() is interesting for C too.  An IEEE binding for it shouldn't
raise exceptions or lose extra precision, as if by assignment, of the
arg.  I think IEEE doesn't have so many problems with extra precision
since its fabs is sort of type-generic.

This reminds me of another problem in code that uses double_t.  C99
provides null support for determining what double_t is, so for example
if you have an expression of type double_t, it is hard know without
using full tgmath.h or messy macros like the old ones for isnan(),
what the correct functions to apply to it are, starting with fabs().
Using fabs() is required by C to lose the extra precision (but doesn't
always).  Using fabsl() would be wasteful if double_t is just double.

Bruce


More information about the svn-src-head mailing list