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
_______________________________________________
svn-src-head@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/svn-src-head
To unsubscribe, send any mail to "svn-src-head-unsubscr...@freebsd.org"

Reply via email to