From 9ba8998af371fb114dfb032f11d188f4df84cdd1 Mon Sep 17 00:00:00 2001 From: Jeff Walden Date: Fri, 7 Jun 2013 13:22:45 -0700 Subject: [PATCH] Bug 798179 - Rewrite ToIntWidth to more simply act upon the actual bits of the IEEE-754 representation. r=froydnj --HG-- extra : rebase_source : fb2da4e55b258b6b62c1c9449447fccff8c2012d --- js/src/jsapi-tests/moz.build | 1 + js/src/jsapi-tests/testToIntWidth.cpp | 70 ++++++++ js/src/vm/NumericConversions.h | 233 ++++++++++++-------------- mfbt/FloatingPoint.h | 86 +++------- 4 files changed, 201 insertions(+), 189 deletions(-) create mode 100644 js/src/jsapi-tests/testToIntWidth.cpp diff --git a/js/src/jsapi-tests/moz.build b/js/src/jsapi-tests/moz.build index 049b68b4596..b5342794514 100644 --- a/js/src/jsapi-tests/moz.build +++ b/js/src/jsapi-tests/moz.build @@ -59,6 +59,7 @@ CPP_SOURCES += [ 'testSetProperty.cpp', 'testSourcePolicy.cpp', 'testStringBuffer.cpp', + 'testToIntWidth.cpp', 'testTrap.cpp', 'testTypedArrays.cpp', 'testUTF8.cpp', diff --git a/js/src/jsapi-tests/testToIntWidth.cpp b/js/src/jsapi-tests/testToIntWidth.cpp new file mode 100644 index 00000000000..43094b1fb9d --- /dev/null +++ b/js/src/jsapi-tests/testToIntWidth.cpp @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#include "tests.h" + +#include + +#include "vm/NumericConversions.h" + +using js::detail::ToIntWidth; +using js::detail::ToUintWidth; + +BEGIN_TEST(testToUint8TwiceUint8Range) +{ + double d = -256; + uint8_t expected = 0; + do { + CHECK(ToUintWidth(d) == expected); + + d++; + expected++; + } while (d <= 256); + return true; +} +END_TEST(testToUint8TwiceUint8Range) + +BEGIN_TEST(testToInt8) +{ + double d = -128; + int8_t expected = -128; + do { + CHECK(ToIntWidth(d) == expected); + + d++; + expected++; + } while (expected < 127); + return true; +} +END_TEST(testToInt8) + +BEGIN_TEST(testToUint32Large) +{ + CHECK(ToUintWidth(pow(2.0, 83)) == 0); + CHECK(ToUintWidth(pow(2.0, 83) + pow(2.0, 31)) == (1U << 31)); + CHECK(ToUintWidth(pow(2.0, 83) + 2 * pow(2.0, 31)) == 0); + CHECK(ToUintWidth(pow(2.0, 83) + 3 * pow(2.0, 31)) == (1U << 31)); + CHECK(ToUintWidth(pow(2.0, 84)) == 0); + CHECK(ToUintWidth(pow(2.0, 84) + pow(2.0, 31)) == 0); + CHECK(ToUintWidth(pow(2.0, 84) + pow(2.0, 32)) == 0); + return true; +} +END_TEST(testToUint32Large) + +BEGIN_TEST(testToUint64Large) +{ + CHECK(ToUintWidth(pow(2.0, 115)) == 0); + CHECK(ToUintWidth(pow(2.0, 115) + pow(2.0, 63)) == (1ULL << 63)); + CHECK(ToUintWidth(pow(2.0, 115) + 2 * pow(2.0, 63)) == 0); + CHECK(ToUintWidth(pow(2.0, 115) + 3 * pow(2.0, 63)) == (1ULL << 63)); + CHECK(ToUintWidth(pow(2.0, 116)) == 0); + CHECK(ToUintWidth(pow(2.0, 116) + pow(2.0, 63)) == 0); + CHECK(ToUintWidth(pow(2.0, 116) + pow(2.0, 64)) == 0); + return true; +} +END_TEST(testToUint64Large) diff --git a/js/src/vm/NumericConversions.h b/js/src/vm/NumericConversions.h index 5c0df9b6a6c..496b742b802 100644 --- a/js/src/vm/NumericConversions.h +++ b/js/src/vm/NumericConversions.h @@ -7,9 +7,10 @@ #ifndef NumericConversions_h___ #define NumericConversions_h___ +#include "mozilla/Assertions.h" +#include "mozilla/Casting.h" #include "mozilla/FloatingPoint.h" - -#include "jscpucfg.h" +#include "mozilla/TypeTraits.h" #include @@ -20,137 +21,113 @@ namespace js { namespace detail { -union DoublePun { - struct { -#if defined(IS_LITTLE_ENDIAN) - uint32_t lo, hi; -#else - uint32_t hi, lo; -#endif - } s; - uint64_t u64; - double d; -}; +/* + * Convert a double value to ResultType (an unsigned integral type) using + * ECMAScript-style semantics (that is, in like manner to how ECMAScript's + * ToInt32 converts to int32_t). + * + * If d is infinite or NaN, return 0. + * Otherwise compute d2 = sign(d) * floor(abs(d)), and return the ResultType + * value congruent to d2 mod 2**(bit width of ResultType). + * + * The algorithm below is inspired by that found in + * + * but has been generalized to all integer widths. + */ +template +inline ResultType +ToUintWidth(double d) +{ + MOZ_STATIC_ASSERT(mozilla::IsUnsigned::value, + "ResultType must be an unsigned type"); -} /* namespace detail */ + uint64_t bits = mozilla::BitwiseCast(d); -/* Numeric Conversion base. Round doubles to Ints according to ECMA or WEBIDL standards. */ -template + // Extract the exponent component. (Be careful here! It's not technically + // the exponent in NaN, infinities, and subnormals.) + int_fast16_t exp = + int_fast16_t((bits & mozilla::DoubleExponentBits) >> mozilla::DoubleExponentShift) - + int_fast16_t(mozilla::DoubleExponentBias); + + // If the exponent's less than zero, abs(d) < 1, so the result is 0. (This + // also handles subnormals.) + if (exp < 0) + return 0; + + uint_fast16_t exponent = mozilla::SafeCast(exp); + + // If the exponent is greater than or equal to the bits of precision of a + // double plus ResultType's width, the number is either infinite, NaN, or + // too large to have lower-order bits in the congruent value. (Example: + // 2**84 is exactly representable as a double. The next exact double is + // 2**84 + 2**32. Thus if ResultType is int32_t, an exponent >= 84 implies + // floor(abs(d)) == 0 mod 2**32.) Return 0 in all these cases. + const size_t ResultWidth = CHAR_BIT * sizeof(ResultType); + if (exponent >= mozilla::DoubleExponentShift + ResultWidth) + return 0; + + // The significand contains the bits that will determine the final result. + // Shift those bits left or right, according to the exponent, to their + // locations in the unsigned binary representation of floor(abs(d)). + MOZ_STATIC_ASSERT(sizeof(ResultType) <= sizeof(uint64_t), + "Left-shifting below would lose upper bits"); + ResultType result = (exponent > mozilla::DoubleExponentShift) + ? ResultType(bits << (exponent - mozilla::DoubleExponentShift)) + : ResultType(bits >> (mozilla::DoubleExponentShift - exponent)); + + // Two further complications remain. First, |result| may contain bogus + // sign/exponent bits. Second, IEEE-754 numbers' significands (excluding + // subnormals, but we already handled those) have an implicit leading 1 + // which may affect the final result. + // + // It may appear that there's complexity here depending on how ResultWidth + // and DoubleExponentShift relate, but it turns out there's not. + // + // Assume ResultWidth < DoubleExponentShift: + // Only right-shifts leave bogus bits in |result|. For this to happen, + // we must right-shift by > |DoubleExponentShift - ResultWidth|, implying + // |exponent < ResultWidth|. + // The implicit leading bit only matters if it appears in the final + // result -- if |2**exponent mod 2**ResultWidth != 0|. This implies + // |exponent < ResultWidth|. + // Otherwise assume ResultWidth >= DoubleExponentShift: + // Any left-shift less than |ResultWidth - DoubleExponentShift| leaves + // bogus bits in |result|. This implies |exponent < ResultWidth|. Any + // right-shift less than |ResultWidth| does too, which implies + // |DoubleExponentShift - ResultWidth < exponent|. By assumption, then, + // |exponent| is negative, but we excluded that above. So bogus bits + // need only |exponent < ResultWidth|. + // The implicit leading bit matters identically to the other case, so + // again, |exponent < ResultWidth|. + if (exponent < ResultWidth) { + ResultType implicitOne = ResultType(1) << exponent; + result &= implicitOne - 1; // remove bogus bits + result += implicitOne; // add the implicit bit + } + + // Compute the congruent value in the signed range. + return (bits & mozilla::DoubleSignBit) ? ~result + 1 : result; +} + +template inline ResultType ToIntWidth(double d) { -#if defined(__i386__) || defined(__i386) || defined(__x86_64__) || \ - defined(_M_IX86) || defined(_M_X64) - detail::DoublePun du, duh, twoWidth; - uint32_t di_h, u_tmp, expon, shift_amount; - int32_t mask32; + MOZ_STATIC_ASSERT(mozilla::IsSigned::value, + "ResultType must be a signed type"); - /* - * Algorithm Outline - * Step 1. If d is NaN, +/-Inf or |d|>=2^(width + 52) or |d|<1, then return 0 - * All of this is implemented based on an exponent comparison, - * since anything with a higher exponent is either not finite, or - * going to round to 0.. - * Step 2. If |d|<2^(width - 1), then return (int)d - * The cast to integer (conversion in RZ mode) returns the correct result. - * Step 3. If |d|>=2^width, d:=fmod(d, 2^width) is taken -- but without a call - * Step 4. If |d|>=2^(width - 1), then the fractional bits are cleared before - * applying the correction by 2^width: d - sign(d)*2^width - * Step 5. Return (int)d - */ + const ResultType MaxValue = (1ULL << (CHAR_BIT * sizeof(ResultType) - 1)) - 1; + const ResultType MinValue = -MaxValue - 1; - du.d = d; - di_h = du.s.hi; - - u_tmp = (di_h & 0x7ff00000) - 0x3ff00000; - if (u_tmp >= ((width + 52) << 20)) { - // d is Nan, +/-Inf or +/-0, or |d|>=2^(width+52) or |d|<1, in which case result=0 - // If we need to shift by more than (width + 52), there are no data bits - // to preserve, and the mod will turn out 0. - return 0; - } - - if (u_tmp < ((width - 1) << 20)) { - // |d|<2^(width - 1) - return ResultType(d); - } - - if (u_tmp > ((width - 1) << 20)) { - // |d|>=2^width - // Throw away multiples of 2^width. - // - // That is, compute du.d = the value in (-2^width, 2^width) - // that has the same sign as d and is equal to d modulo 2^width. - // - // This can't be done simply by masking away bits of du because - // the implicit one-bit of the mantissa is one of the ones we want to - // eliminate. So instead we compute duh.d = the appropriate multiple - // of 2^width, which *can* be computed by masking, and then we - // subtract that from du.d. - expon = u_tmp >> 20; - shift_amount = expon - (width - 11); - mask32 = 0x80000000; - if (shift_amount < 32) { - // Shift only affects top word. - mask32 >>= shift_amount; - duh.s.hi = du.s.hi & mask32; - duh.s.lo = 0; - } else { - // Top word all 1s, shift affects bottom word. - mask32 >>= (shift_amount-32); - duh.s.hi = du.s.hi; - duh.s.lo = du.s.lo & mask32; - } - du.d -= duh.d; - } - - di_h = du.s.hi; - - // Eliminate fractional bits - u_tmp = (di_h & 0x7ff00000); - if (u_tmp >= (0x3ff00000 + ((width - 1) << 20))) { - // |d|>=2^(width - 1) - expon = u_tmp >> 20; - - // Same idea as before, except save everything non-fractional. - shift_amount = expon - (0x3ff - 11); - mask32 = 0x80000000; - if (shift_amount < 32) { - // Top word only - mask32 >>= shift_amount; - du.s.hi &= mask32; - du.s.lo = 0; - } else { - // Bottom word. Top word all 1s. - mask32 >>= (shift_amount-32); - du.s.lo &= mask32; - } - // Apply step 4's 2^width correction. - twoWidth.s.hi = (0x3ff00000 + (width << 20)) ^ (du.s.hi & 0x80000000); - twoWidth.s.lo = 0; - du.d -= twoWidth.d; - } - - return ResultType(du.d); -#else - double twoWidth, twoWidthMin1; - - if (!mozilla::IsFinite(d)) - return 0; - - /* FIXME: This relies on undefined behavior; see bug 667739. */ - ResultType i = (ResultType) d; - if ((double) i == d) - return ResultType(i); - - twoWidth = width == 32 ? 4294967296.0 : 18446744073709551616.0; - twoWidthMin1 = width == 32 ? 2147483648.0 : 9223372036854775808.0; - d = fmod(d, twoWidth); - d = (d >= 0) ? floor(d) : ceil(d) + twoWidth; - return (ResultType) (d >= twoWidthMin1 ? d - twoWidth : d); -#endif + typedef typename mozilla::MakeUnsigned::Type UnsignedResult; + UnsignedResult u = ToUintWidth(d); + if (u <= UnsignedResult(MaxValue)) + return static_cast(u); + return (MinValue + static_cast(u - MaxValue)) - 1; } +} /* namespace detail */ + /* ES5 9.5 ToInt32 (specialized for doubles). */ inline int32_t ToInt32(double d) @@ -277,7 +254,7 @@ ToInt32(double d) ); return i; #else - return ToIntWidth<32, int32_t>(d); + return detail::ToIntWidth(d); #endif } @@ -285,21 +262,21 @@ ToInt32(double d) inline uint32_t ToUint32(double d) { - return uint32_t(ToInt32(d)); + return detail::ToUintWidth(d); } /* WEBIDL 4.2.10 */ inline int64_t ToInt64(double d) { - return ToIntWidth<64, int64_t>(d); + return detail::ToIntWidth(d); } /* WEBIDL 4.2.11 */ inline uint64_t ToUint64(double d) { - return uint64_t(ToInt64(d)); + return detail::ToUintWidth(d); } /* ES5 9.4 ToInteger (specialized for doubles). */ diff --git a/mfbt/FloatingPoint.h b/mfbt/FloatingPoint.h index 30af2217b14..a79a32154de 100644 --- a/mfbt/FloatingPoint.h +++ b/mfbt/FloatingPoint.h @@ -10,6 +10,7 @@ #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" +#include "mozilla/Casting.h" #include "mozilla/StandardInteger.h" namespace mozilla { @@ -40,8 +41,6 @@ MOZ_STATIC_ASSERT(sizeof(double) == sizeof(uint64_t), "double must be 64 bits"); const unsigned DoubleExponentBias = 1023; const unsigned DoubleExponentShift = 52; -namespace detail { - const uint64_t DoubleSignBit = 0x8000000000000000ULL; const uint64_t DoubleExponentBits = 0x7ff0000000000000ULL; const uint64_t DoubleSignificandBits = 0x000fffffffffffffULL; @@ -57,58 +56,38 @@ MOZ_STATIC_ASSERT((DoubleSignBit | DoubleExponentBits | DoubleSignificandBits) = ~uint64_t(0), "all bits accounted for"); -union DoublePun -{ - /* - * Every way to pun the bits of a double introduces an additional layer of - * complexity, across a multitude of platforms, architectures, and ABIs. - * Use *only* uint64_t to reduce complexity. Don't add new punning here - * without discussion! - */ - uint64_t u; - double d; -}; - -} /* namespace detail */ - /** Determines whether a double is NaN. */ static MOZ_ALWAYS_INLINE bool IsNaN(double d) { - union detail::DoublePun pun; - pun.d = d; - /* * A double is NaN if all exponent bits are 1 and the significand contains at * least one non-zero bit. */ - return (pun.u & detail::DoubleExponentBits) == detail::DoubleExponentBits && - (pun.u & detail::DoubleSignificandBits) != 0; + uint64_t bits = BitwiseCast(d); + return (bits & DoubleExponentBits) == DoubleExponentBits && + (bits & DoubleSignificandBits) != 0; } /** Determines whether a double is +Infinity or -Infinity. */ static MOZ_ALWAYS_INLINE bool IsInfinite(double d) { - union detail::DoublePun pun; - pun.d = d; - /* Infinities have all exponent bits set to 1 and an all-0 significand. */ - return (pun.u & ~detail::DoubleSignBit) == detail::DoubleExponentBits; + uint64_t bits = BitwiseCast(d); + return (bits & ~DoubleSignBit) == DoubleExponentBits; } /** Determines whether a double is not NaN or infinite. */ static MOZ_ALWAYS_INLINE bool IsFinite(double d) { - union detail::DoublePun pun; - pun.d = d; - /* * NaN and Infinities are the only non-finite doubles, and both have all * exponent bits set to 1. */ - return (pun.u & detail::DoubleExponentBits) != detail::DoubleExponentBits; + uint64_t bits = BitwiseCast(d); + return (bits & DoubleExponentBits) != DoubleExponentBits; } /** @@ -120,36 +99,30 @@ IsNegative(double d) { MOZ_ASSERT(!IsNaN(d), "NaN does not have a sign"); - union detail::DoublePun pun; - pun.d = d; - /* The sign bit is set if the double is negative. */ - return (pun.u & detail::DoubleSignBit) != 0; + uint64_t bits = BitwiseCast(d); + return (bits & DoubleSignBit) != 0; } /** Determines whether a double represents -0. */ static MOZ_ALWAYS_INLINE bool IsNegativeZero(double d) { - union detail::DoublePun pun; - pun.d = d; - /* Only the sign bit is set if the double is -0. */ - return pun.u == detail::DoubleSignBit; + uint64_t bits = BitwiseCast(d); + return bits == DoubleSignBit; } /** Returns the exponent portion of the double. */ static MOZ_ALWAYS_INLINE int_fast16_t ExponentComponent(double d) { - union detail::DoublePun pun; - pun.d = d; - /* * The exponent component of a double is an unsigned number, biased from its * actual value. Subtract the bias to retrieve the actual exponent. */ - return int_fast16_t((pun.u & detail::DoubleExponentBits) >> DoubleExponentShift) - + uint64_t bits = BitwiseCast(d); + return int_fast16_t((bits & DoubleExponentBits) >> DoubleExponentShift) - int_fast16_t(DoubleExponentBias); } @@ -157,28 +130,22 @@ ExponentComponent(double d) static MOZ_ALWAYS_INLINE double PositiveInfinity() { - union detail::DoublePun pun; - /* * Positive infinity has all exponent bits set, sign bit set to 0, and no * significand. */ - pun.u = detail::DoubleExponentBits; - return pun.d; + return BitwiseCast(DoubleExponentBits); } /** Returns -Infinity. */ static MOZ_ALWAYS_INLINE double NegativeInfinity() { - union detail::DoublePun pun; - /* * Negative infinity has all exponent bits set, sign bit set to 1, and no * significand. */ - pun.u = detail::DoubleSignBit | detail::DoubleExponentBits; - return pun.d; + return BitwiseCast(DoubleSignBit | DoubleExponentBits); } /** Constructs a NaN value with the specified sign bit and significand bits. */ @@ -186,24 +153,21 @@ static MOZ_ALWAYS_INLINE double SpecificNaN(int signbit, uint64_t significand) { MOZ_ASSERT(signbit == 0 || signbit == 1); - MOZ_ASSERT((significand & ~detail::DoubleSignificandBits) == 0); - MOZ_ASSERT(significand & detail::DoubleSignificandBits); + MOZ_ASSERT((significand & ~DoubleSignificandBits) == 0); + MOZ_ASSERT(significand & DoubleSignificandBits); - union detail::DoublePun pun; - pun.u = (signbit ? detail::DoubleSignBit : 0) | - detail::DoubleExponentBits | - significand; - MOZ_ASSERT(IsNaN(pun.d)); - return pun.d; + double d = BitwiseCast((signbit ? DoubleSignBit : 0) | + DoubleExponentBits | + significand); + MOZ_ASSERT(IsNaN(d)); + return d; } /** Computes the smallest non-zero positive double value. */ static MOZ_ALWAYS_INLINE double MinDoubleValue() { - union detail::DoublePun pun; - pun.u = 1; - return pun.d; + return BitwiseCast(uint64_t(1)); } static MOZ_ALWAYS_INLINE bool @@ -224,7 +188,7 @@ DoubleIsInt32(double d, int32_t* i) static MOZ_ALWAYS_INLINE double UnspecifiedNaN() { - return mozilla::SpecificNaN(0, 0xfffffffffffffULL); + return SpecificNaN(0, 0xfffffffffffffULL); } } /* namespace mozilla */