From 8dd16259287f58f9273002717ec4d27e97127719 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 12 Jun 2024 07:43:14 +0200 Subject: Merging upstream version 127.0. Signed-off-by: Daniel Baumann --- js/src/builtin/temporal/Temporal.cpp | 811 +++++++++++++---------------------- 1 file changed, 302 insertions(+), 509 deletions(-) (limited to 'js/src/builtin/temporal/Temporal.cpp') diff --git a/js/src/builtin/temporal/Temporal.cpp b/js/src/builtin/temporal/Temporal.cpp index 3960a2832d..f729a7ce0f 100644 --- a/js/src/builtin/temporal/Temporal.cpp +++ b/js/src/builtin/temporal/Temporal.cpp @@ -6,8 +6,10 @@ #include "builtin/temporal/Temporal.h" +#include "mozilla/Casting.h" #include "mozilla/CheckedInt.h" #include "mozilla/Likely.h" +#include "mozilla/MathAlgorithms.h" #include "mozilla/Maybe.h" #include @@ -15,8 +17,10 @@ #include #include #include +#include #include #include +#include #include #include "jsfriendapi.h" @@ -25,6 +29,7 @@ #include "NamespaceImports.h" #include "builtin/temporal/Instant.h" +#include "builtin/temporal/Int128.h" #include "builtin/temporal/PlainDate.h" #include "builtin/temporal/PlainDateTime.h" #include "builtin/temporal/PlainMonthDay.h" @@ -47,7 +52,6 @@ #include "js/RootingAPI.h" #include "js/String.h" #include "js/Value.h" -#include "vm/BigIntType.h" #include "vm/BytecodeUtil.h" #include "vm/GlobalObject.h" #include "vm/JSAtomState.h" @@ -137,16 +141,16 @@ static bool GetNumberOption(JSContext* cx, Handle options, bool js::temporal::ToTemporalRoundingIncrement(JSContext* cx, Handle options, Increment* increment) { - // Step 1. + // Steps 1-3. double number = 1; if (!GetNumberOption(cx, options, cx->names().roundingIncrement, &number)) { return false; } - // Step 3. (Reordered) + // Step 5. (Reordered) number = std::trunc(number); - // Steps 2 and 4. + // Steps 4 and 6. if (!std::isfinite(number) || number < 1 || number > 1'000'000'000) { ToCStringBuf cbuf; const char* numStr = NumberToCString(&cbuf, number); @@ -157,6 +161,7 @@ bool js::temporal::ToTemporalRoundingIncrement(JSContext* cx, return false; } + // Step 7. *increment = Increment{uint32_t(number)}; return true; } @@ -177,7 +182,7 @@ bool js::temporal::ValidateTemporalRoundingIncrement(JSContext* cx, // Steps 3-4. if (increment.value() > maximum || dividend % increment.value() != 0) { Int32ToCStringBuf cbuf; - const char* numStr = Int32ToCString(&cbuf, increment.value()); + const char* numStr = Int32ToCString(&cbuf, int32_t(increment.value())); // TODO: Better error message could be helpful. JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, @@ -394,7 +399,7 @@ bool js::temporal::GetTemporalUnit(JSContext* cx, Handle value, bool js::temporal::ToTemporalRoundingMode(JSContext* cx, Handle options, TemporalRoundingMode* mode) { - // Step 1. + // Steps 1-2. Rooted string(cx); if (!GetStringOption(cx, options, cx->names().roundingMode, &string)) { return false; @@ -439,556 +444,339 @@ bool js::temporal::ToTemporalRoundingMode(JSContext* cx, return true; } -static BigInt* Divide(JSContext* cx, Handle dividend, int64_t divisor, - TemporalRoundingMode roundingMode) { - MOZ_ASSERT(divisor > 0); - - Rooted div(cx, BigInt::createFromInt64(cx, divisor)); - if (!div) { - return nullptr; - } - - Rooted quotient(cx); - Rooted remainder(cx); - if (!BigInt::divmod(cx, dividend, div, "ient, &remainder)) { - return nullptr; - } - - // No rounding needed when the remainder is zero. - if (remainder->isZero()) { - return quotient; - } - - switch (roundingMode) { - case TemporalRoundingMode::Ceil: { - if (!remainder->isNegative()) { - return BigInt::inc(cx, quotient); - } - return quotient; - } - case TemporalRoundingMode::Floor: { - if (remainder->isNegative()) { - return BigInt::dec(cx, quotient); - } - return quotient; - } - case TemporalRoundingMode::Trunc: - // BigInt division truncates. - return quotient; - case TemporalRoundingMode::Expand: { - if (!remainder->isNegative()) { - return BigInt::inc(cx, quotient); - } - return BigInt::dec(cx, quotient); - } - case TemporalRoundingMode::HalfCeil: { - int64_t rem; - MOZ_ALWAYS_TRUE(BigInt::isInt64(remainder, &rem)); - - if (!remainder->isNegative()) { - if (uint64_t(std::abs(rem)) * 2 >= uint64_t(divisor)) { - return BigInt::inc(cx, quotient); - } - } else { - if (uint64_t(std::abs(rem)) * 2 > uint64_t(divisor)) { - return BigInt::dec(cx, quotient); - } - } - return quotient; - } - case TemporalRoundingMode::HalfFloor: { - int64_t rem; - MOZ_ALWAYS_TRUE(BigInt::isInt64(remainder, &rem)); - - if (remainder->isNegative()) { - if (uint64_t(std::abs(rem)) * 2 >= uint64_t(divisor)) { - return BigInt::dec(cx, quotient); - } - } else { - if (uint64_t(std::abs(rem)) * 2 > uint64_t(divisor)) { - return BigInt::inc(cx, quotient); - } - } - return quotient; - } - case TemporalRoundingMode::HalfExpand: { - int64_t rem; - MOZ_ALWAYS_TRUE(BigInt::isInt64(remainder, &rem)); - - if (uint64_t(std::abs(rem)) * 2 >= uint64_t(divisor)) { - if (!dividend->isNegative()) { - return BigInt::inc(cx, quotient); - } - return BigInt::dec(cx, quotient); - } - return quotient; - } - case TemporalRoundingMode::HalfTrunc: { - int64_t rem; - MOZ_ALWAYS_TRUE(BigInt::isInt64(remainder, &rem)); - - if (uint64_t(std::abs(rem)) * 2 > uint64_t(divisor)) { - if (!dividend->isNegative()) { - return BigInt::inc(cx, quotient); - } - return BigInt::dec(cx, quotient); - } - return quotient; - } - case TemporalRoundingMode::HalfEven: { - int64_t rem; - MOZ_ALWAYS_TRUE(BigInt::isInt64(remainder, &rem)); - - if (uint64_t(std::abs(rem)) * 2 == uint64_t(divisor)) { - bool isOdd = !quotient->isZero() && (quotient->digit(0) & 1) == 1; - if (isOdd) { - if (!dividend->isNegative()) { - return BigInt::inc(cx, quotient); - } - return BigInt::dec(cx, quotient); - } - } - if (uint64_t(std::abs(rem)) * 2 > uint64_t(divisor)) { - if (!dividend->isNegative()) { - return BigInt::inc(cx, quotient); - } - return BigInt::dec(cx, quotient); - } - return quotient; - } - } - - MOZ_CRASH("invalid rounding mode"); +#ifdef DEBUG +template +static bool IsValidMul(const T& x, const T& y) { + return (mozilla::CheckedInt(x) * y).isValid(); } -static BigInt* Divide(JSContext* cx, Handle dividend, - Handle divisor, - TemporalRoundingMode roundingMode) { - MOZ_ASSERT(!divisor->isNegative()); - MOZ_ASSERT(!divisor->isZero()); +// Copied from mozilla::CheckedInt. +template <> +bool IsValidMul(const Int128& x, const Int128& y) { + static constexpr auto min = Int128{1} << 127; + static constexpr auto max = ~min; - Rooted quotient(cx); - Rooted remainder(cx); - if (!BigInt::divmod(cx, dividend, divisor, "ient, &remainder)) { - return nullptr; - } - - // No rounding needed when the remainder is zero. - if (remainder->isZero()) { - return quotient; - } - - switch (roundingMode) { - case TemporalRoundingMode::Ceil: { - if (!remainder->isNegative()) { - return BigInt::inc(cx, quotient); - } - return quotient; - } - case TemporalRoundingMode::Floor: { - if (remainder->isNegative()) { - return BigInt::dec(cx, quotient); - } - return quotient; - } - case TemporalRoundingMode::Trunc: - // BigInt division truncates. - return quotient; - case TemporalRoundingMode::Expand: { - if (!remainder->isNegative()) { - return BigInt::inc(cx, quotient); - } - return BigInt::dec(cx, quotient); - } - case TemporalRoundingMode::HalfCeil: { - BigInt* rem = BigInt::add(cx, remainder, remainder); - if (!rem) { - return nullptr; - } - - if (!remainder->isNegative()) { - if (BigInt::absoluteCompare(rem, divisor) >= 0) { - return BigInt::inc(cx, quotient); - } - } else { - if (BigInt::absoluteCompare(rem, divisor) > 0) { - return BigInt::dec(cx, quotient); - } - } - return quotient; - } - case TemporalRoundingMode::HalfFloor: { - BigInt* rem = BigInt::add(cx, remainder, remainder); - if (!rem) { - return nullptr; - } - - if (remainder->isNegative()) { - if (BigInt::absoluteCompare(rem, divisor) >= 0) { - return BigInt::dec(cx, quotient); - } - } else { - if (BigInt::absoluteCompare(rem, divisor) > 0) { - return BigInt::inc(cx, quotient); - } - } - return quotient; - } - case TemporalRoundingMode::HalfExpand: { - BigInt* rem = BigInt::add(cx, remainder, remainder); - if (!rem) { - return nullptr; - } - - if (BigInt::absoluteCompare(rem, divisor) >= 0) { - if (!dividend->isNegative()) { - return BigInt::inc(cx, quotient); - } - return BigInt::dec(cx, quotient); - } - return quotient; - } - case TemporalRoundingMode::HalfTrunc: { - BigInt* rem = BigInt::add(cx, remainder, remainder); - if (!rem) { - return nullptr; - } - - if (BigInt::absoluteCompare(rem, divisor) > 0) { - if (!dividend->isNegative()) { - return BigInt::inc(cx, quotient); - } - return BigInt::dec(cx, quotient); - } - return quotient; - } - case TemporalRoundingMode::HalfEven: { - BigInt* rem = BigInt::add(cx, remainder, remainder); - if (!rem) { - return nullptr; - } - - if (BigInt::absoluteCompare(rem, divisor) == 0) { - bool isOdd = !quotient->isZero() && (quotient->digit(0) & 1) == 1; - if (isOdd) { - if (!dividend->isNegative()) { - return BigInt::inc(cx, quotient); - } - return BigInt::dec(cx, quotient); - } - } - if (BigInt::absoluteCompare(rem, divisor) > 0) { - if (!dividend->isNegative()) { - return BigInt::inc(cx, quotient); - } - return BigInt::dec(cx, quotient); - } - return quotient; - } - } - - MOZ_CRASH("invalid rounding mode"); -} - -static BigInt* RoundNumberToIncrementSlow(JSContext* cx, Handle x, - int64_t divisor, int64_t increment, - TemporalRoundingMode roundingMode) { - // Steps 1-8. - Rooted rounded(cx, Divide(cx, x, divisor, roundingMode)); - if (!rounded) { - return nullptr; - } - - // We can skip the next step when |increment=1|. - if (increment == 1) { - return rounded; + if (x == Int128{0} || y == Int128{0}) { + return true; } - - // Step 9. - Rooted inc(cx, BigInt::createFromInt64(cx, increment)); - if (!inc) { - return nullptr; + if (x > Int128{0}) { + return y > Int128{0} ? x <= max / y : y >= min / x; } - return BigInt::mul(cx, rounded, inc); -} - -static BigInt* RoundNumberToIncrementSlow(JSContext* cx, Handle x, - int64_t increment, - TemporalRoundingMode roundingMode) { - return RoundNumberToIncrementSlow(cx, x, increment, increment, roundingMode); + return y > Int128{0} ? x >= min / y : y >= max / x; } +#endif /** * RoundNumberToIncrement ( x, increment, roundingMode ) */ -bool js::temporal::RoundNumberToIncrement(JSContext* cx, const Instant& x, - int64_t increment, - TemporalRoundingMode roundingMode, - Instant* result) { - MOZ_ASSERT(temporal::IsValidEpochInstant(x)); - MOZ_ASSERT(increment > 0); - MOZ_ASSERT(increment <= ToNanoseconds(TemporalUnit::Day)); - - // Fast path for the default case. - if (increment == 1) { - *result = x; - return true; - } +Int128 js::temporal::RoundNumberToIncrement(int64_t numerator, + int64_t denominator, + Increment increment, + TemporalRoundingMode roundingMode) { + MOZ_ASSERT(denominator > 0); + MOZ_ASSERT(Increment::min() <= increment && increment <= Increment::max()); // Dividing zero is always zero. - if (x == Instant{}) { - *result = x; - return true; + if (numerator == 0) { + return Int128{0}; + } + + // We don't have to adjust the divisor when |increment=1|. + if (increment == Increment{1}) { + // Steps 1-8 and implicit step 9. + return Int128{Divide(numerator, denominator, roundingMode)}; } // Fast-path when we can perform the whole computation with int64 values. - if (auto num = x.toNanoseconds(); MOZ_LIKELY(num.isValid())) { + auto divisor = mozilla::CheckedInt64(denominator) * increment.value(); + if (MOZ_LIKELY(divisor.isValid())) { + MOZ_ASSERT(divisor.value() > 0); + // Steps 1-8. - int64_t rounded = Divide(num.value(), increment, roundingMode); + int64_t rounded = Divide(numerator, divisor.value(), roundingMode); // Step 9. - mozilla::CheckedInt64 checked = rounded; - checked *= increment; - if (MOZ_LIKELY(checked.isValid())) { - *result = Instant::fromNanoseconds(checked.value()); - return true; + auto result = mozilla::CheckedInt64(rounded) * increment.value(); + if (MOZ_LIKELY(result.isValid())) { + return Int128{result.value()}; } } - Rooted bi(cx, ToEpochNanoseconds(cx, x)); - if (!bi) { - return false; - } - - auto* rounded = RoundNumberToIncrementSlow(cx, bi, increment, roundingMode); - if (!rounded) { - return false; - } - - *result = ToInstant(rounded); - return true; + // Int128 path on overflow. + return RoundNumberToIncrement(Int128{numerator}, Int128{denominator}, + increment, roundingMode); } /** * RoundNumberToIncrement ( x, increment, roundingMode ) */ -bool js::temporal::RoundNumberToIncrement(JSContext* cx, int64_t numerator, - TemporalUnit unit, - Increment increment, - TemporalRoundingMode roundingMode, - double* result) { - MOZ_ASSERT(unit >= TemporalUnit::Day); +Int128 js::temporal::RoundNumberToIncrement(const Int128& numerator, + const Int128& denominator, + Increment increment, + TemporalRoundingMode roundingMode) { + MOZ_ASSERT(denominator > Int128{0}); MOZ_ASSERT(Increment::min() <= increment && increment <= Increment::max()); - // Take the slow path when the increment is too large. - if (MOZ_UNLIKELY(increment > Increment{100'000})) { - Rooted bi(cx, BigInt::createFromInt64(cx, numerator)); - if (!bi) { - return false; - } + auto inc = Int128{increment.value()}; + MOZ_ASSERT(IsValidMul(denominator, inc), "unsupported overflow"); - Rooted denominator( - cx, BigInt::createFromInt64(cx, ToNanoseconds(unit))); - if (!denominator) { - return false; - } - - return RoundNumberToIncrement(cx, bi, denominator, increment, roundingMode, - result); - } - - int64_t divisor = ToNanoseconds(unit) * increment.value(); - MOZ_ASSERT(divisor > 0); - MOZ_ASSERT(divisor <= 8'640'000'000'000'000'000); - - // Division by one has no remainder. - if (divisor == 1) { - MOZ_ASSERT(increment == Increment{1}); - *result = double(numerator); - return true; - } + auto divisor = denominator * inc; + MOZ_ASSERT(divisor > Int128{0}); // Steps 1-8. - int64_t rounded = Divide(numerator, divisor, roundingMode); + auto rounded = Divide(numerator, divisor, roundingMode); // Step 9. - mozilla::CheckedInt64 checked = rounded; - checked *= increment.value(); - if (checked.isValid()) { - *result = double(checked.value()); - return true; - } - - Rooted bi(cx, BigInt::createFromInt64(cx, numerator)); - if (!bi) { - return false; - } - return RoundNumberToIncrement(cx, bi, unit, increment, roundingMode, result); + MOZ_ASSERT(IsValidMul(rounded, inc), "unsupported overflow"); + return rounded * inc; } /** * RoundNumberToIncrement ( x, increment, roundingMode ) */ -bool js::temporal::RoundNumberToIncrement( - JSContext* cx, Handle numerator, TemporalUnit unit, - Increment increment, TemporalRoundingMode roundingMode, double* result) { - MOZ_ASSERT(unit >= TemporalUnit::Day); - MOZ_ASSERT(Increment::min() <= increment && increment <= Increment::max()); +Int128 js::temporal::RoundNumberToIncrement(const Int128& x, + const Int128& increment, + TemporalRoundingMode roundingMode) { + MOZ_ASSERT(increment > Int128{0}); - // Take the slow path when the increment is too large. - if (MOZ_UNLIKELY(increment > Increment{100'000})) { - Rooted denominator( - cx, BigInt::createFromInt64(cx, ToNanoseconds(unit))); - if (!denominator) { - return false; - } - - return RoundNumberToIncrement(cx, numerator, denominator, increment, - roundingMode, result); - } - - int64_t divisor = ToNanoseconds(unit) * increment.value(); - MOZ_ASSERT(divisor > 0); - MOZ_ASSERT(divisor <= 8'640'000'000'000'000'000); - - // Division by one has no remainder. - if (divisor == 1) { - MOZ_ASSERT(increment == Increment{1}); - *result = BigInt::numberValue(numerator); - return true; - } - - // Dividing zero is always zero. - if (numerator->isZero()) { - *result = 0; - return true; - } - - // All callers are already in the slow path, so we don't need to fast-path the - // case when |x| can be represented by an int64 value. + // Steps 1-8. + auto rounded = Divide(x, increment, roundingMode); - // Steps 1-9. - auto* rounded = RoundNumberToIncrementSlow(cx, numerator, divisor, - increment.value(), roundingMode); - if (!rounded) { - return false; - } + // Step 9. + MOZ_ASSERT(IsValidMul(rounded, increment), "unsupported overflow"); + return rounded * increment; +} - *result = BigInt::numberValue(rounded); - return true; +template +static inline constexpr bool IsSafeInteger(const IntT& x) { + constexpr IntT MaxSafeInteger = IntT{int64_t(1) << 53}; + constexpr IntT MinSafeInteger = -MaxSafeInteger; + return MinSafeInteger < x && x < MaxSafeInteger; } /** - * RoundNumberToIncrement ( x, increment, roundingMode ) + * Return the real number value of the fraction |numerator / denominator|. + * + * As an optimization we multiply the remainder by 16 when computing the number + * of digits after the decimal point, i.e. we compute four instead of one bit of + * the fractional digits. The denominator is therefore required to not exceed + * 2**(N - log2(16)), where N is the number of non-sign bits in the mantissa. */ -bool js::temporal::RoundNumberToIncrement(JSContext* cx, int64_t numerator, - int64_t denominator, - Increment increment, - TemporalRoundingMode roundingMode, - double* result) { - MOZ_ASSERT(denominator > 0); - MOZ_ASSERT(Increment::min() <= increment && increment <= Increment::max()); - - // Dividing zero is always zero. - if (numerator == 0) { - *result = 0; - return true; - } - - // We don't have to adjust the divisor when |increment=1|. - if (increment == Increment{1}) { - int64_t divisor = denominator; - int64_t rounded = Divide(numerator, divisor, roundingMode); - - *result = double(rounded); - return true; - } - - auto divisor = mozilla::CheckedInt64(denominator) * increment.value(); - if (MOZ_LIKELY(divisor.isValid())) { - MOZ_ASSERT(divisor.value() > 0); - - // Steps 1-8. - int64_t rounded = Divide(numerator, divisor.value(), roundingMode); - - // Step 9. - auto adjusted = mozilla::CheckedInt64(rounded) * increment.value(); - if (MOZ_LIKELY(adjusted.isValid())) { - *result = double(adjusted.value()); - return true; +template +static double FractionToDoubleSlow(const T& numerator, const T& denominator) { + MOZ_ASSERT(denominator > T{0}, "expected positive denominator"); + MOZ_ASSERT(denominator <= (T{1} << (std::numeric_limits::digits - 4)), + "denominator too large"); + + auto absValue = [](const T& value) { + if constexpr (std::is_same_v) { + return value.abs(); + } else { + // NB: Not std::abs, because std::abs(INT64_MIN) is undefined behavior. + return mozilla::Abs(value); } - } + }; - // Slow path on overflow. + using UnsignedT = decltype(absValue(T{0})); + static_assert(!std::numeric_limits::is_signed); - Rooted bi(cx, BigInt::createFromInt64(cx, numerator)); - if (!bi) { - return false; - } + auto divrem = [](const UnsignedT& x, const UnsignedT& y) { + if constexpr (std::is_same_v) { + return x.divrem(y); + } else { + return std::pair{x / y, x % y}; + } + }; - Rooted denom(cx, BigInt::createFromInt64(cx, denominator)); - if (!denom) { - return false; + auto [quot, rem] = + divrem(absValue(numerator), static_cast(denominator)); + + // Simple case when no remainder is present. + if (rem == UnsignedT{0}) { + double sign = numerator < T{0} ? -1 : 1; + return sign * double(quot); + } + + using Double = mozilla::FloatingPoint; + + // Significand including the implicit one of IEEE-754 floating point numbers. + static constexpr uint32_t SignificandWidthWithImplicitOne = + Double::kSignificandWidth + 1; + + // Number of leading zeros for a correctly adjusted significand. + static constexpr uint32_t SignificandLeadingZeros = + 64 - SignificandWidthWithImplicitOne; + + // Exponent bias for an integral significand. (`Double::kExponentBias` is the + // bias for the binary fraction `1.xyz * 2**exp`. For an integral significand + // the significand width has to be added to the bias.) + static constexpr int32_t ExponentBias = + Double::kExponentBias + Double::kSignificandWidth; + + // Significand, possibly unnormalized. + uint64_t significand = 0; + + // Significand ignored msd bits. + uint32_t ignoredBits = 0; + + // Read quotient, from most to least significant digit. Stop when the + // significand got too large for double precision. + int32_t shift = std::numeric_limits::digits; + for (; shift != 0 && ignoredBits == 0; shift -= 4) { + uint64_t digit = uint64_t(quot >> (shift - 4)) & 0xf; + + significand = significand * 16 + digit; + ignoredBits = significand >> SignificandWidthWithImplicitOne; + } + + // Read remainder, from most to least significant digit. Stop when the + // remainder is zero or the significand got too large. + int32_t fractionDigit = 0; + for (; rem != UnsignedT{0} && ignoredBits == 0; fractionDigit++) { + auto [digit, next] = + divrem(rem * UnsignedT{16}, static_cast(denominator)); + rem = next; + + significand = significand * 16 + uint64_t(digit); + ignoredBits = significand >> SignificandWidthWithImplicitOne; + } + + // Unbiased exponent. (`shift` remaining bits in the quotient, minus the + // fractional digits.) + int32_t exponent = shift - (fractionDigit * 4); + + // Significand got too large and some bits are now ignored. Adjust the + // significand and exponent. + if (ignoredBits != 0) { + // significand + // ___________|__________ + // / \ + // [xxx················yyy| + // \_/ \_/ + // | | + // ignoredBits extraBits + // + // `ignoredBits` have to be shifted back into the 53 bits of the significand + // and `extraBits` has to be checked if the result has to be rounded up. + + // Number of ignored/extra bits in the significand. + uint32_t extraBitsCount = 32 - mozilla::CountLeadingZeroes32(ignoredBits); + MOZ_ASSERT(extraBitsCount > 0); + + // Extra bits in the significand. + uint32_t extraBits = uint32_t(significand) & ((1 << extraBitsCount) - 1); + + // Move the ignored bits into the proper significand position and adjust the + // exponent to reflect the now moved out extra bits. + significand >>= extraBitsCount; + exponent += extraBitsCount; + + MOZ_ASSERT((significand >> SignificandWidthWithImplicitOne) == 0, + "no excess bits in the significand"); + + // When the most significant digit in the extra bits is set, we may need to + // round the result. + uint32_t msdExtraBit = extraBits >> (extraBitsCount - 1); + if (msdExtraBit != 0) { + // Extra bits, excluding the most significant digit. + uint32_t extraBitExcludingMsdMask = (1 << (extraBitsCount - 1)) - 1; + + // Unprocessed bits in the quotient. + auto bitsBelowExtraBits = quot & ((UnsignedT{1} << shift) - UnsignedT{1}); + + // Round up if the extra bit's msd is set and either the significand is + // odd or any other bits below the extra bit's msd are non-zero. + // + // Bits below the extra bit's msd are: + // 1. The remaining bits of the extra bits. + // 2. Any bits below the extra bits. + // 3. Any rest of the remainder. + bool shouldRoundUp = (significand & 1) != 0 || + (extraBits & extraBitExcludingMsdMask) != 0 || + bitsBelowExtraBits != UnsignedT{0} || + rem != UnsignedT{0}; + if (shouldRoundUp) { + // Add one to the significand bits. + significand += 1; + + // If they overflow, the exponent must also be increased. + if ((significand >> SignificandWidthWithImplicitOne) != 0) { + exponent++; + significand >>= 1; + } + } + } } - return RoundNumberToIncrement(cx, bi, denom, increment, roundingMode, result); + MOZ_ASSERT(significand > 0, "significand is non-zero"); + MOZ_ASSERT((significand >> SignificandWidthWithImplicitOne) == 0, + "no excess bits in the significand"); + + // Move the significand into the correct position and adjust the exponent + // accordingly. + uint32_t significandZeros = mozilla::CountLeadingZeroes64(significand); + if (significandZeros < SignificandLeadingZeros) { + uint32_t shift = SignificandLeadingZeros - significandZeros; + significand >>= shift; + exponent += shift; + } else if (significandZeros > SignificandLeadingZeros) { + uint32_t shift = significandZeros - SignificandLeadingZeros; + significand <<= shift; + exponent -= shift; + } + + // Combine the individual bits of the double value and return it. + uint64_t signBit = uint64_t(numerator < T{0} ? 1 : 0) + << (Double::kExponentWidth + Double::kSignificandWidth); + uint64_t exponentBits = static_cast(exponent + ExponentBias) + << Double::kExponentShift; + uint64_t significandBits = significand & Double::kSignificandBits; + return mozilla::BitwiseCast(signBit | exponentBits | significandBits); } -/** - * RoundNumberToIncrement ( x, increment, roundingMode ) - */ -bool js::temporal::RoundNumberToIncrement( - JSContext* cx, Handle numerator, Handle denominator, - Increment increment, TemporalRoundingMode roundingMode, double* result) { - MOZ_ASSERT(!denominator->isNegative()); - MOZ_ASSERT(!denominator->isZero()); - MOZ_ASSERT(Increment::min() <= increment && increment <= Increment::max()); +double js::temporal::FractionToDouble(int64_t numerator, int64_t denominator) { + MOZ_ASSERT(denominator > 0); - // Dividing zero is always zero. - if (numerator->isZero()) { - *result = 0; - return true; + // Zero divided by any divisor is still zero. + if (numerator == 0) { + return 0; } - // We don't have to adjust the divisor when |increment=1|. - if (increment == Increment{1}) { - auto divisor = denominator; - - auto* rounded = Divide(cx, numerator, divisor, roundingMode); - if (!rounded) { - return false; - } - - *result = BigInt::numberValue(rounded); - return true; + // When both values can be represented as doubles, use double division to + // compute the exact result. The result is exact, because double division is + // guaranteed to return the exact result. + if (MOZ_LIKELY(::IsSafeInteger(numerator) && ::IsSafeInteger(denominator))) { + return double(numerator) / double(denominator); } - Rooted inc(cx, BigInt::createFromUint64(cx, increment.value())); - if (!inc) { - return false; + // Otherwise call into |FractionToDoubleSlow| to compute the exact result. + if (denominator <= + (int64_t(1) << (std::numeric_limits::digits - 4))) { + // Slightly faster, but still slow approach when |denominator| is small + // enough to allow computing on int64 values. + return FractionToDoubleSlow(numerator, denominator); } + return FractionToDoubleSlow(Int128{numerator}, Int128{denominator}); +} - Rooted divisor(cx, BigInt::mul(cx, denominator, inc)); - if (!divisor) { - return false; - } - MOZ_ASSERT(!divisor->isNegative()); - MOZ_ASSERT(!divisor->isZero()); +double js::temporal::FractionToDouble(const Int128& numerator, + const Int128& denominator) { + MOZ_ASSERT(denominator > Int128{0}); - // Steps 1-8. - Rooted rounded(cx, Divide(cx, numerator, divisor, roundingMode)); - if (!rounded) { - return false; + // Zero divided by any divisor is still zero. + if (numerator == Int128{0}) { + return 0; } - // Step 9. - auto* adjusted = BigInt::mul(cx, rounded, inc); - if (!adjusted) { - return false; + // When both values can be represented as doubles, use double division to + // compute the exact result. The result is exact, because double division is + // guaranteed to return the exact result. + if (MOZ_LIKELY(::IsSafeInteger(numerator) && ::IsSafeInteger(denominator))) { + return double(numerator) / double(denominator); } - *result = BigInt::numberValue(adjusted); - return true; + // Otherwise call into |FractionToDoubleSlow| to compute the exact result. + return FractionToDoubleSlow(numerator, denominator); } /** @@ -1404,17 +1192,14 @@ static JSObject* MaybeUnwrapIf(JSObject* object) { return nullptr; } -// FIXME: spec issue - "Reject" is exclusively used for Promise rejection. The -// existing `RejectPromise` abstract operation unconditionally rejects, whereas -// this operation conditionally rejects. -// https://github.com/tc39/proposal-temporal/issues/2534 - /** - * RejectTemporalLikeObject ( object ) + * IsPartialTemporalObject ( object ) */ -bool js::temporal::RejectTemporalLikeObject(JSContext* cx, - Handle object) { - // Step 1. +bool js::temporal::ThrowIfTemporalLikeObject(JSContext* cx, + Handle object) { + // Step 1. (Handled in caller) + + // Step 2. if (auto* unwrapped = MaybeUnwrapIf property(cx); - // Step 2. + // Step 3. if (!GetProperty(cx, object, object, cx->names().calendar, &property)) { return false; } - // Step 3. + // Step 4. if (!property.isUndefined()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_UNEXPECTED_PROPERTY, "calendar"); return false; } - // Step 4. + // Step 5. if (!GetProperty(cx, object, object, cx->names().timeZone, &property)) { return false; } - // Step 5. + // Step 6. if (!property.isUndefined()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_UNEXPECTED_PROPERTY, "timeZone"); return false; } - // Step 6. + // Step 7. return true; } @@ -1813,10 +1598,18 @@ static bool TemporalClassFinish(JSContext* cx, Handle temporal, }; // Add the constructor properties. - for (const auto& protoKey : - {JSProto_Calendar, JSProto_Duration, JSProto_Instant, JSProto_PlainDate, - JSProto_PlainDateTime, JSProto_PlainMonthDay, JSProto_PlainTime, - JSProto_PlainYearMonth, JSProto_TimeZone, JSProto_ZonedDateTime}) { + for (const auto& protoKey : { + JSProto_Calendar, + JSProto_Duration, + JSProto_Instant, + JSProto_PlainDate, + JSProto_PlainDateTime, + JSProto_PlainMonthDay, + JSProto_PlainTime, + JSProto_PlainYearMonth, + JSProto_TimeZone, + JSProto_ZonedDateTime, + }) { if (!defineProperty(protoKey, ClassName(protoKey, cx))) { return false; } -- cgit v1.2.3