Bug 798179 - Rewrite ToIntWidth to more simply act upon the actual bits of the IEEE-754 representation. r=froydnj

--HG--
extra : rebase_source : fb2da4e55b258b6b62c1c9449447fccff8c2012d
This commit is contained in:
Jeff Walden 2013-06-07 13:22:45 -07:00
parent 4ee21886e5
commit 99f84749f1
4 changed files with 201 additions and 189 deletions

View File

@ -59,6 +59,7 @@ CPP_SOURCES += [
'testSetProperty.cpp',
'testSourcePolicy.cpp',
'testStringBuffer.cpp',
'testToIntWidth.cpp',
'testTrap.cpp',
'testTypedArrays.cpp',
'testUTF8.cpp',

View File

@ -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 <math.h>
#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<uint8_t>(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<int8_t>(d) == expected);
d++;
expected++;
} while (expected < 127);
return true;
}
END_TEST(testToInt8)
BEGIN_TEST(testToUint32Large)
{
CHECK(ToUintWidth<uint32_t>(pow(2.0, 83)) == 0);
CHECK(ToUintWidth<uint32_t>(pow(2.0, 83) + pow(2.0, 31)) == (1U << 31));
CHECK(ToUintWidth<uint32_t>(pow(2.0, 83) + 2 * pow(2.0, 31)) == 0);
CHECK(ToUintWidth<uint32_t>(pow(2.0, 83) + 3 * pow(2.0, 31)) == (1U << 31));
CHECK(ToUintWidth<uint32_t>(pow(2.0, 84)) == 0);
CHECK(ToUintWidth<uint32_t>(pow(2.0, 84) + pow(2.0, 31)) == 0);
CHECK(ToUintWidth<uint32_t>(pow(2.0, 84) + pow(2.0, 32)) == 0);
return true;
}
END_TEST(testToUint32Large)
BEGIN_TEST(testToUint64Large)
{
CHECK(ToUintWidth<uint64_t>(pow(2.0, 115)) == 0);
CHECK(ToUintWidth<uint64_t>(pow(2.0, 115) + pow(2.0, 63)) == (1ULL << 63));
CHECK(ToUintWidth<uint64_t>(pow(2.0, 115) + 2 * pow(2.0, 63)) == 0);
CHECK(ToUintWidth<uint64_t>(pow(2.0, 115) + 3 * pow(2.0, 63)) == (1ULL << 63));
CHECK(ToUintWidth<uint64_t>(pow(2.0, 116)) == 0);
CHECK(ToUintWidth<uint64_t>(pow(2.0, 116) + pow(2.0, 63)) == 0);
CHECK(ToUintWidth<uint64_t>(pow(2.0, 116) + pow(2.0, 64)) == 0);
return true;
}
END_TEST(testToUint64Large)

View File

@ -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 <math.h>
@ -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
* <http://trac.webkit.org/changeset/67825/trunk/JavaScriptCore/runtime/JSValue.cpp>
* but has been generalized to all integer widths.
*/
template<typename ResultType>
inline ResultType
ToUintWidth(double d)
{
MOZ_STATIC_ASSERT(mozilla::IsUnsigned<ResultType>::value,
"ResultType must be an unsigned type");
} /* namespace detail */
uint64_t bits = mozilla::BitwiseCast<uint64_t>(d);
/* Numeric Conversion base. Round doubles to Ints according to ECMA or WEBIDL standards. */
template<size_t width, typename ResultType>
// 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<uint_fast16_t>(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<typename ResultType>
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<ResultType>::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<ResultType>::Type UnsignedResult;
UnsignedResult u = ToUintWidth<UnsignedResult>(d);
if (u <= UnsignedResult(MaxValue))
return static_cast<ResultType>(u);
return (MinValue + static_cast<ResultType>(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<int32_t>(d);
#endif
}
@ -285,21 +262,21 @@ ToInt32(double d)
inline uint32_t
ToUint32(double d)
{
return uint32_t(ToInt32(d));
return detail::ToUintWidth<uint32_t>(d);
}
/* WEBIDL 4.2.10 */
inline int64_t
ToInt64(double d)
{
return ToIntWidth<64, int64_t>(d);
return detail::ToIntWidth<int64_t>(d);
}
/* WEBIDL 4.2.11 */
inline uint64_t
ToUint64(double d)
{
return uint64_t(ToInt64(d));
return detail::ToUintWidth<uint64_t>(d);
}
/* ES5 9.4 ToInteger (specialized for doubles). */

View File

@ -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<uint64_t>(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<uint64_t>(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<uint64_t>(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<uint64_t>(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<uint64_t>(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<uint64_t>(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<double>(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<double>(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<double>((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<double>(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 */