fix double-processing of DT_RELR relocations in ldso relocating itself
[musl] / src / math / fmal.c
index 200bd5a..4506aac 100644 (file)
@@ -34,6 +34,13 @@ long double fmal(long double x, long double y, long double z)
 }
 #elif (LDBL_MANT_DIG == 64 || LDBL_MANT_DIG == 113) && LDBL_MAX_EXP == 16384
 #include <fenv.h>
+#if LDBL_MANT_DIG == 64
+#define LASTBIT(u) (u.i.m & 1)
+#define SPLIT (0x1p32L + 1)
+#elif LDBL_MANT_DIG == 113
+#define LASTBIT(u) (u.i.lo & 1)
+#define SPLIT (0x1p57L + 1)
+#endif
 
 /*
  * A struct dd represents a floating-point number with twice the precision
@@ -75,12 +82,12 @@ static inline struct dd dd_add(long double a, long double b)
 static inline long double add_adjusted(long double a, long double b)
 {
        struct dd sum;
-       union IEEEl2bits u;
+       union ldshape u;
 
        sum = dd_add(a, b);
        if (sum.lo != 0) {
-               u.e = sum.hi;
-               if ((u.bits.manl & 1) == 0)
+               u.f = sum.hi;
+               if (!LASTBIT(u))
                        sum.hi = nextafterl(sum.hi, INFINITY * sum.lo);
        }
        return (sum.hi);
@@ -95,7 +102,7 @@ static inline long double add_and_denormalize(long double a, long double b, int
 {
        struct dd sum;
        int bits_lost;
-       union IEEEl2bits u;
+       union ldshape u;
 
        sum = dd_add(a, b);
 
@@ -110,12 +117,12 @@ static inline long double add_and_denormalize(long double a, long double b, int
         * break the ties manually.
         */
        if (sum.lo != 0) {
-               u.e = sum.hi;
-               bits_lost = -u.bits.exp - scale + 1;
-               if (bits_lost != 1 ^ (int)(u.bits.manl & 1))
+               u.f = sum.hi;
+               bits_lost = -u.i.se - scale + 1;
+               if ((bits_lost != 1) ^ LASTBIT(u))
                        sum.hi = nextafterl(sum.hi, INFINITY * sum.lo);
        }
-       return (ldexp(sum.hi, scale));
+       return scalbnl(sum.hi, scale);
 }
 
 /*
@@ -125,20 +132,15 @@ static inline long double add_and_denormalize(long double a, long double b, int
  */
 static inline struct dd dd_mul(long double a, long double b)
 {
-#if LDBL_MANT_DIG == 64
-       static const long double split = 0x1p32L + 1.0;
-#elif LDBL_MANT_DIG == 113
-       static const long double split = 0x1p57L + 1.0;
-#endif
        struct dd ret;
        long double ha, hb, la, lb, p, q;
 
-       p = a * split;
+       p = a * SPLIT;
        ha = a - p;
        ha += p;
        la = a - ha;
 
-       p = b * split;
+       p = b * SPLIT;
        hb = b - p;
        hb += p;
        lb = b - hb;
@@ -162,6 +164,7 @@ static inline struct dd dd_mul(long double a, long double b)
  */
 long double fmal(long double x, long double y, long double z)
 {
+       #pragma STDC FENV_ACCESS ON
        long double xs, ys, zs, adj;
        struct dd xy, r;
        int oround;
@@ -173,14 +176,14 @@ long double fmal(long double x, long double y, long double z)
         * return values here are crucial in handling special cases involving
         * infinities, NaNs, overflows, and signed zeroes correctly.
         */
-       if (x == 0.0 || y == 0.0)
-               return (x * y + z);
-       if (z == 0.0)
-               return (x * y);
        if (!isfinite(x) || !isfinite(y))
                return (x * y + z);
        if (!isfinite(z))
                return (z);
+       if (x == 0.0 || y == 0.0)
+               return (x * y + z);
+       if (z == 0.0)
+               return (x * y);
 
        xs = frexpl(x, &ex);
        ys = frexpl(y, &ey);
@@ -194,31 +197,41 @@ long double fmal(long double x, long double y, long double z)
         * modes other than FE_TONEAREST are painful.
         */
        if (spread < -LDBL_MANT_DIG) {
+#ifdef FE_INEXACT
                feraiseexcept(FE_INEXACT);
+#endif
+#ifdef FE_UNDERFLOW
                if (!isnormal(z))
                        feraiseexcept(FE_UNDERFLOW);
+#endif
                switch (oround) {
-               case FE_TONEAREST:
+               default: /* FE_TONEAREST */
                        return (z);
+#ifdef FE_TOWARDZERO
                case FE_TOWARDZERO:
                        if (x > 0.0 ^ y < 0.0 ^ z < 0.0)
                                return (z);
                        else
                                return (nextafterl(z, 0));
+#endif
+#ifdef FE_DOWNWARD
                case FE_DOWNWARD:
                        if (x > 0.0 ^ y < 0.0)
                                return (z);
                        else
                                return (nextafterl(z, -INFINITY));
-               default:        /* FE_UPWARD */
+#endif
+#ifdef FE_UPWARD
+               case FE_UPWARD:
                        if (x > 0.0 ^ y < 0.0)
                                return (nextafterl(z, INFINITY));
                        else
                                return (z);
+#endif
                }
        }
        if (spread <= LDBL_MANT_DIG * 2)
-               zs = ldexpl(zs, -spread);
+               zs = scalbnl(zs, -spread);
        else
                zs = copysignl(LDBL_MIN, zs);
 
@@ -244,23 +257,37 @@ long double fmal(long double x, long double y, long double z)
                 */
                fesetround(oround);
                volatile long double vzs = zs; /* XXX gcc CSE bug workaround */
-               return (xy.hi + vzs + ldexpl(xy.lo, spread));
+               return xy.hi + vzs + scalbnl(xy.lo, spread);
        }
 
        if (oround != FE_TONEAREST) {
                /*
                 * There is no need to worry about double rounding in directed
                 * rounding modes.
+                * But underflow may not be raised correctly, example in downward rounding:
+                * fmal(0x1.0000000001p-16000L, 0x1.0000000001p-400L, -0x1p-16440L)
                 */
+               long double ret;
+#if defined(FE_INEXACT) && defined(FE_UNDERFLOW)
+               int e = fetestexcept(FE_INEXACT);
+               feclearexcept(FE_INEXACT);
+#endif
                fesetround(oround);
                adj = r.lo + xy.lo;
-               return (ldexpl(r.hi + adj, spread));
+               ret = scalbnl(r.hi + adj, spread);
+#if defined(FE_INEXACT) && defined(FE_UNDERFLOW)
+               if (ilogbl(ret) < -16382 && fetestexcept(FE_INEXACT))
+                       feraiseexcept(FE_UNDERFLOW);
+               else if (e)
+                       feraiseexcept(FE_INEXACT);
+#endif
+               return ret;
        }
 
        adj = add_adjusted(r.lo, xy.lo);
        if (spread + ilogbl(r.hi) > -16383)
-               return (ldexpl(r.hi + adj, spread));
+               return scalbnl(r.hi + adj, spread);
        else
-               return (add_and_denormalize(r.hi, adj, spread));
+               return add_and_denormalize(r.hi, adj, spread);
 }
 #endif