diff options
Diffstat (limited to 'js/src/builtin/temporal/Instant.cpp')
-rw-r--r-- | js/src/builtin/temporal/Instant.cpp | 403 |
1 files changed, 96 insertions, 307 deletions
diff --git a/js/src/builtin/temporal/Instant.cpp b/js/src/builtin/temporal/Instant.cpp index 78fc15f313..8e38f3ad51 100644 --- a/js/src/builtin/temporal/Instant.cpp +++ b/js/src/builtin/temporal/Instant.cpp @@ -7,6 +7,7 @@ #include "builtin/temporal/Instant.h" #include "mozilla/Assertions.h" +#include "mozilla/Casting.h" #include "mozilla/CheckedInt.h" #include "mozilla/FloatingPoint.h" #include "mozilla/Maybe.h" @@ -164,43 +165,18 @@ bool js::temporal::IsValidEpochInstant(const Instant& instant) { return Instant::min() <= instant && instant <= Instant::max(); } -static constexpr auto NanosecondsMaxInstantSpan() { - static_assert(BigInt::DigitBits == 64 || BigInt::DigitBits == 32); - - // ±8.64 × 10^21 is the nanoseconds from epoch limit. - // 2 × 8.64 × 10^21 is 172_80000_00000_00000_00000 or 0x3a8_c02c5ea2_de000000. - // Return the BigInt digits of that number for fast BigInt comparisons. - if constexpr (BigInt::DigitBits == 64) { - return std::array{ - BigInt::Digit(0x3a8), - BigInt::Digit(0xc02c'5ea2'de00'0000), - }; - } else { - return std::array{ - BigInt::Digit(0x3a8), - BigInt::Digit(0xc02c'5ea2), - BigInt::Digit(0xde00'0000), - }; - } -} - +#ifdef DEBUG /** * Validates a nanoseconds amount is at most as large as the difference * between two valid nanoseconds from the epoch instants. - * - * Useful when we want to ensure a BigInt doesn't exceed a certain limit. */ -bool js::temporal::IsValidInstantSpan(const BigInt* nanoseconds) { - static constexpr auto spanLimit = NanosecondsMaxInstantSpan(); - return AbsoluteValueIsLessOrEqual<spanLimit>(nanoseconds); -} - bool js::temporal::IsValidInstantSpan(const InstantSpan& span) { MOZ_ASSERT(0 <= span.nanoseconds && span.nanoseconds <= 999'999'999); // Steps 1-3. return InstantSpan::min() <= span && span <= InstantSpan::max(); } +#endif /** * Return the BigInt as a 96-bit integer. The BigInt digits must not consist of @@ -258,14 +234,6 @@ Instant js::temporal::ToInstant(const BigInt* epochNanoseconds) { return {seconds, nanos}; } -InstantSpan js::temporal::ToInstantSpan(const BigInt* nanoseconds) { - MOZ_ASSERT(IsValidInstantSpan(nanoseconds)); - - auto [seconds, nanos] = - ToInt96(nanoseconds) / ToNanoseconds(TemporalUnit::Second); - return {seconds, nanos}; -} - static BigInt* CreateBigInt(JSContext* cx, const std::array<uint32_t, 3>& digits, bool negative) { @@ -300,9 +268,7 @@ static BigInt* CreateBigInt(JSContext* cx, } } -static BigInt* ToEpochBigInt(JSContext* cx, const InstantSpan& instant) { - MOZ_ASSERT(IsValidInstantSpan(instant)); - +static auto ToBigIntDigits(uint64_t seconds, uint32_t nanoseconds) { // Multiplies two uint32_t values and returns the lower 32-bits. The higher // 32-bits are stored in |high|. auto digitMul = [](uint32_t a, uint32_t b, uint32_t* high) { @@ -321,20 +287,6 @@ static BigInt* ToEpochBigInt(JSContext* cx, const InstantSpan& instant) { constexpr uint32_t secToNanos = ToNanoseconds(TemporalUnit::Second); - uint64_t seconds = std::abs(instant.seconds); - uint32_t nanoseconds = instant.nanoseconds; - - // Negative nanoseconds are represented as the difference to 1'000'000'000. - // Convert these back to their absolute value and adjust the seconds part - // accordingly. - // - // For example the nanoseconds from the epoch value |-1n| is represented as - // the instant {seconds: -1, nanoseconds: 999'999'999}. - if (instant.seconds < 0 && nanoseconds != 0) { - nanoseconds = secToNanos - nanoseconds; - seconds -= 1; - } - // uint32_t digits stored in the same order as BigInt digits, i.e. the least // significant digit is stored at index zero. std::array<uint32_t, 2> multiplicand = {uint32_t(seconds), @@ -362,93 +314,34 @@ static BigInt* ToEpochBigInt(JSContext* cx, const InstantSpan& instant) { MOZ_ASSERT(newCarry == 0); } - return CreateBigInt(cx, accumulator, instant.seconds < 0); -} - -BigInt* js::temporal::ToEpochNanoseconds(JSContext* cx, - const Instant& instant) { - MOZ_ASSERT(IsValidEpochInstant(instant)); - return ::ToEpochBigInt(cx, InstantSpan{instant.seconds, instant.nanoseconds}); -} - -BigInt* js::temporal::ToEpochNanoseconds(JSContext* cx, - const InstantSpan& instant) { - MOZ_ASSERT(IsValidInstantSpan(instant)); - return ::ToEpochBigInt(cx, instant); -} - -/** - * Return an Instant for the input nanoseconds if the input is less-or-equal to - * the maximum instant span. Otherwise returns nothing. - */ -static mozilla::Maybe<InstantSpan> NanosecondsToInstantSpan( - double nanoseconds) { - MOZ_ASSERT(IsInteger(nanoseconds)); - - if (auto int96 = Int96::fromInteger(nanoseconds)) { - constexpr auto maximum = Int96{InstantSpan::max().toSeconds()} * - ToNanoseconds(TemporalUnit::Second); - - // Accept if the value is less-or-equal to the maximum instant span. - if (int96->abs() <= maximum) { - // Split into seconds and nanoseconds. - auto [seconds, nanos] = *int96 / ToNanoseconds(TemporalUnit::Second); - - auto result = InstantSpan{seconds, nanos}; - MOZ_ASSERT(IsValidInstantSpan(result)); - return mozilla::Some(result); - } - } - return mozilla::Nothing(); + return accumulator; } -/** - * Return an Instant for the input microseconds if the input is less-or-equal to - * the maximum instant span. Otherwise returns nothing. - */ -static mozilla::Maybe<InstantSpan> MicrosecondsToInstantSpan( - double microseconds) { - MOZ_ASSERT(IsInteger(microseconds)); +template <typename T> +static BigInt* ToBigInt(JSContext* cx, + const SecondsAndNanoseconds<T>& secondsAndNanoseconds) { + uint64_t seconds = std::abs(secondsAndNanoseconds.seconds); + uint32_t nanoseconds = secondsAndNanoseconds.nanoseconds; - constexpr int64_t spanLimit = InstantSpan::max().toSeconds(); - constexpr int64_t secToMicros = ToNanoseconds(TemporalUnit::Second) / - ToNanoseconds(TemporalUnit::Microsecond); - constexpr int32_t microToNanos = ToNanoseconds(TemporalUnit::Microsecond); - - // Fast path for the common case. - if (microseconds == 0) { - return mozilla::Some(InstantSpan{}); - } - - // Reject if the value is larger than the maximum instant span. - if (std::abs(microseconds) > double(spanLimit) * double(secToMicros)) { - return mozilla::Nothing(); + // Negative nanoseconds are represented as the difference to 1'000'000'000. + // Convert these back to their absolute value and adjust the seconds part + // accordingly. + // + // For example the nanoseconds from the epoch value |-1n| is represented as + // the instant {seconds: -1, nanoseconds: 999'999'999}. + if (secondsAndNanoseconds.seconds < 0 && nanoseconds != 0) { + nanoseconds = ToNanoseconds(TemporalUnit::Second) - nanoseconds; + seconds -= 1; } - // |spanLimit| in microseconds is below UINT64_MAX, so we can use uint64 in - // the following computations. - static_assert(double(spanLimit) * double(secToMicros) <= double(UINT64_MAX)); - - // Use the absolute value and convert it then into uint64_t. - uint64_t absMicros = uint64_t(std::abs(microseconds)); - - // Seconds and remainder are small enough to fit into int64_t resp. int32_t. - int64_t seconds = absMicros / uint64_t(secToMicros); - int32_t remainder = absMicros % uint64_t(secToMicros); - - // Correct the sign of |seconds| and |remainder|, and then constrain - // |remainder| to the range [0, 999'999]. - if (microseconds < 0) { - seconds *= -1; - if (remainder != 0) { - seconds -= 1; - remainder = secToMicros - remainder; - } - } + auto digits = ToBigIntDigits(seconds, nanoseconds); + return CreateBigInt(cx, digits, secondsAndNanoseconds.seconds < 0); +} - InstantSpan result = {seconds, remainder * microToNanos}; - MOZ_ASSERT(IsValidInstantSpan(result)); - return mozilla::Some(result); +BigInt* js::temporal::ToEpochNanoseconds(JSContext* cx, + const Instant& instant) { + MOZ_ASSERT(IsValidEpochInstant(instant)); + return ::ToBigInt(cx, instant); } /** @@ -456,7 +349,7 @@ static mozilla::Maybe<InstantSpan> MicrosecondsToInstantSpan( * microsecond, nanosecond [ , offsetNanoseconds ] ) */ Instant js::temporal::GetUTCEpochNanoseconds(const PlainDateTime& dateTime) { - auto& [date, time] = dateTime; + const auto& [date, time] = dateTime; // Step 1. MOZ_ASSERT(IsValidISODateTime(dateTime)); @@ -579,13 +472,13 @@ Wrapped<InstantObject*> js::temporal::ToTemporalInstant(JSContext* cx, } } - // Steps 1.b-d and 3-6 + // Steps 1.b-d and 3-7 Instant epochNanoseconds; if (!ToTemporalInstant(cx, item, &epochNanoseconds)) { return nullptr; } - // Step 7. + // Step 8. return CreateTemporalInstant(cx, epochNanoseconds); } @@ -635,25 +528,25 @@ bool js::temporal::ToTemporalInstant(JSContext* cx, Handle<Value> item, } MOZ_ASSERT(std::abs(offset) < ToNanoseconds(TemporalUnit::Day)); - // Step 6. (Reordered) + // Steps 5-6. (Reordered) if (!ISODateTimeWithinLimits(dateTime)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_INSTANT_INVALID); return false; } - // Step 5. + // Step 4. auto epochNanoseconds = GetUTCEpochNanoseconds(dateTime, InstantSpan::fromNanoseconds(offset)); - // Step 6. + // Step 7. if (!IsValidEpochInstant(epochNanoseconds)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_INSTANT_INVALID); return false; } - // Step 7. + // Step 8. *result = epochNanoseconds; return true; } @@ -663,181 +556,75 @@ bool js::temporal::ToTemporalInstant(JSContext* cx, Handle<Value> item, * microseconds, nanoseconds ) */ bool js::temporal::AddInstant(JSContext* cx, const Instant& instant, - const Duration& duration, Instant* result) { + const NormalizedTimeDuration& duration, + Instant* result) { MOZ_ASSERT(IsValidEpochInstant(instant)); - MOZ_ASSERT(IsValidDuration(duration)); - MOZ_ASSERT(duration.years == 0); - MOZ_ASSERT(duration.months == 0); - MOZ_ASSERT(duration.weeks == 0); - MOZ_ASSERT(duration.days == 0); - - do { - auto nanoseconds = NanosecondsToInstantSpan(duration.nanoseconds); - if (!nanoseconds) { - break; - } - MOZ_ASSERT(IsValidInstantSpan(*nanoseconds)); - - auto microseconds = MicrosecondsToInstantSpan(duration.microseconds); - if (!microseconds) { - break; - } - MOZ_ASSERT(IsValidInstantSpan(*microseconds)); - - // Overflows for millis/seconds/minutes/hours always result in an invalid - // instant. - - int64_t milliseconds; - if (!mozilla::NumberEqualsInt64(duration.milliseconds, &milliseconds)) { - break; - } + MOZ_ASSERT(IsValidNormalizedTimeDuration(duration)); - int64_t seconds; - if (!mozilla::NumberEqualsInt64(duration.seconds, &seconds)) { - break; - } + // Step 1. (Inlined AddNormalizedTimeDurationToEpochNanoseconds) + auto r = instant + duration.to<InstantSpan>(); - int64_t minutes; - if (!mozilla::NumberEqualsInt64(duration.minutes, &minutes)) { - break; - } - - int64_t hours; - if (!mozilla::NumberEqualsInt64(duration.hours, &hours)) { - break; - } - - // Compute the overall amount of milliseconds to add. - mozilla::CheckedInt64 millis = hours; - millis *= 60; - millis += minutes; - millis *= 60; - millis += seconds; - millis *= 1000; - millis += milliseconds; - if (!millis.isValid()) { - break; - } - - auto milli = InstantSpan::fromMilliseconds(millis.value()); - if (!IsValidInstantSpan(milli)) { - break; - } - - // Compute the overall instant span. - auto span = milli + *microseconds + *nanoseconds; - if (!IsValidInstantSpan(span)) { - break; - } - - *result = instant + span; - if (IsValidEpochInstant(*result)) { - return true; - } - } while (false); + // Step 2. + if (!IsValidEpochInstant(r)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_INSTANT_INVALID); + return false; + } - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, - JSMSG_TEMPORAL_INSTANT_INVALID); - return false; + // Step 3. + *result = r; + return true; } /** - * DifferenceInstant ( ns1, ns2, roundingIncrement, smallestUnit, largestUnit, - * roundingMode ) + * DifferenceInstant ( ns1, ns2, roundingIncrement, smallestUnit, roundingMode ) */ -bool js::temporal::DifferenceInstant(JSContext* cx, const Instant& ns1, - const Instant& ns2, - Increment roundingIncrement, - TemporalUnit smallestUnit, - TemporalUnit largestUnit, - TemporalRoundingMode roundingMode, - Duration* result) { +NormalizedTimeDuration js::temporal::DifferenceInstant( + const Instant& ns1, const Instant& ns2, Increment roundingIncrement, + TemporalUnit smallestUnit, TemporalRoundingMode roundingMode) { MOZ_ASSERT(IsValidEpochInstant(ns1)); MOZ_ASSERT(IsValidEpochInstant(ns2)); - MOZ_ASSERT(largestUnit > TemporalUnit::Day); - MOZ_ASSERT(largestUnit <= smallestUnit); + MOZ_ASSERT(smallestUnit > TemporalUnit::Day); MOZ_ASSERT(roundingIncrement <= MaximumTemporalDurationRoundingIncrement(smallestUnit)); // Step 1. - auto diff = ns2 - ns1; - MOZ_ASSERT(IsValidInstantSpan(diff)); - - // Negative nanoseconds are represented as the difference to 1'000'000'000. - auto [seconds, nanoseconds] = diff; - if (seconds < 0 && nanoseconds != 0) { - seconds += 1; - nanoseconds -= ToNanoseconds(TemporalUnit::Second); - } - - // Steps 2-5. - Duration duration = { - 0, - 0, - 0, - 0, - 0, - 0, - double(seconds), - double((nanoseconds / 1000'000) % 1000), - double((nanoseconds / 1000) % 1000), - double(nanoseconds % 1000), - }; - MOZ_ASSERT(IsValidDuration(duration)); + auto diff = NormalizedTimeDurationFromEpochNanosecondsDifference(ns2, ns1); + MOZ_ASSERT(IsValidInstantSpan(diff.to<InstantSpan>())); - // Step 6. + // Step 2. if (smallestUnit == TemporalUnit::Nanosecond && roundingIncrement == Increment{1}) { - TimeDuration balanced; - if (!BalanceTimeDuration(cx, duration, largestUnit, &balanced)) { - return false; - } - MOZ_ASSERT(balanced.days == 0); - - *result = balanced.toDuration().time(); - return true; - } - - // Steps 7-8. - Duration roundResult; - if (!temporal::RoundDuration(cx, duration, roundingIncrement, smallestUnit, - roundingMode, &roundResult)) { - return false; - } - - // Step 9. - MOZ_ASSERT(roundResult.days == 0); - - // Step 10. - TimeDuration balanced; - if (!BalanceTimeDuration(cx, roundResult, largestUnit, &balanced)) { - return false; + return diff; } - MOZ_ASSERT(balanced.days == 0); - *result = balanced.toDuration().time(); - return true; + // Steps 3-4. + return RoundDuration(diff, roundingIncrement, smallestUnit, roundingMode); } /** * RoundNumberToIncrementAsIfPositive ( x, increment, roundingMode ) */ -static bool RoundNumberToIncrementAsIfPositive( - JSContext* cx, const Instant& x, int64_t increment, - TemporalRoundingMode roundingMode, Instant* result) { +static Instant RoundNumberToIncrementAsIfPositive( + const Instant& x, int64_t increment, TemporalRoundingMode roundingMode) { + MOZ_ASSERT(IsValidEpochInstant(x)); + MOZ_ASSERT(increment > 0); + MOZ_ASSERT(increment <= ToNanoseconds(TemporalUnit::Day)); + // This operation is equivalent to adjusting the rounding mode through // |ToPositiveRoundingMode| and then calling |RoundNumberToIncrement|. - return RoundNumberToIncrement(cx, x, increment, - ToPositiveRoundingMode(roundingMode), result); + auto rounded = RoundNumberToIncrement(x.toNanoseconds(), Int128{increment}, + ToPositiveRoundingMode(roundingMode)); + return Instant::fromNanoseconds(rounded); } /** * RoundTemporalInstant ( ns, increment, unit, roundingMode ) */ -bool js::temporal::RoundTemporalInstant(JSContext* cx, const Instant& ns, - Increment increment, TemporalUnit unit, - TemporalRoundingMode roundingMode, - Instant* result) { +Instant js::temporal::RoundTemporalInstant(const Instant& ns, + Increment increment, + TemporalUnit unit, + TemporalRoundingMode roundingMode) { MOZ_ASSERT(IsValidEpochInstant(ns)); MOZ_ASSERT(increment >= Increment::min()); MOZ_ASSERT(uint64_t(increment.value()) <= ToNanoseconds(TemporalUnit::Day)); @@ -851,7 +638,7 @@ bool js::temporal::RoundTemporalInstant(JSContext* cx, const Instant& ns, // Step 7. return RoundNumberToIncrementAsIfPositive( - cx, ns, increment.value() * toNanoseconds, roundingMode, result); + ns, increment.value() * toNanoseconds, roundingMode); } /** @@ -903,19 +690,23 @@ static bool DifferenceTemporalInstant(JSContext* cx, } // Step 5. - Duration difference; - if (!DifferenceInstant(cx, instant, other, settings.roundingIncrement, - settings.smallestUnit, settings.largestUnit, - settings.roundingMode, &difference)) { + auto difference = + DifferenceInstant(instant, other, settings.roundingIncrement, + settings.smallestUnit, settings.roundingMode); + + // Step 6. + TimeDuration balanced; + if (!BalanceTimeDuration(cx, difference, settings.largestUnit, &balanced)) { return false; } - // Step 6. + // Step 7. + auto duration = balanced.toDuration(); if (operation == TemporalDifference::Since) { - difference = difference.negate(); + duration = duration.negate(); } - auto* obj = CreateTemporalDuration(cx, difference); + auto* obj = CreateTemporalDuration(cx, duration); if (!obj) { return false; } @@ -959,13 +750,15 @@ static bool AddDurationToOrSubtractDurationFromInstant( if (operation == InstantDuration::Subtract) { duration = duration.negate(); } + auto timeDuration = NormalizeTimeDuration(duration); + // Step 8. Instant ns; - if (!AddInstant(cx, epochNanoseconds, duration, &ns)) { + if (!AddInstant(cx, epochNanoseconds, timeDuration, &ns)) { return false; } - // Step 8. + // Step 9. auto* result = CreateTemporalInstant(cx, ns); if (!result) { return false; @@ -1063,7 +856,8 @@ static bool Instant_fromEpochSeconds(JSContext* cx, unsigned argc, Value* vp) { } // Step 5. - auto* result = CreateTemporalInstant(cx, Instant::fromSeconds(epochSeconds)); + int64_t seconds = mozilla::AssertedCast<int64_t>(epochSeconds); + auto* result = CreateTemporalInstant(cx, Instant::fromSeconds(seconds)); if (!result) { return false; } @@ -1106,8 +900,9 @@ static bool Instant_fromEpochMilliseconds(JSContext* cx, unsigned argc, } // Step 5. + int64_t milliseconds = mozilla::AssertedCast<int64_t>(epochMilliseconds); auto* result = - CreateTemporalInstant(cx, Instant::fromMilliseconds(epochMilliseconds)); + CreateTemporalInstant(cx, Instant::fromMilliseconds(milliseconds)); if (!result) { return false; } @@ -1413,7 +1208,7 @@ static bool Instant_round(JSContext* cx, const CallArgs& args) { } // Steps 10-15. - uint64_t maximum = UnitsPerDay(smallestUnit); + int64_t maximum = UnitsPerDay(smallestUnit); // Step 16. if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum, @@ -1423,11 +1218,8 @@ static bool Instant_round(JSContext* cx, const CallArgs& args) { } // Step 17. - Instant roundedNs; - if (!RoundTemporalInstant(cx, instant, roundingIncrement, smallestUnit, - roundingMode, &roundedNs)) { - return false; - } + auto roundedNs = RoundTemporalInstant(instant, roundingIncrement, + smallestUnit, roundingMode); // Step 18. auto* result = CreateTemporalInstant(cx, roundedNs); @@ -1535,11 +1327,8 @@ static bool Instant_toString(JSContext* cx, const CallArgs& args) { } // Step 12. - Instant ns; - if (!RoundTemporalInstant(cx, instant, precision.increment, precision.unit, - roundingMode, &ns)) { - return false; - } + auto ns = RoundTemporalInstant(instant, precision.increment, precision.unit, + roundingMode); // Step 13. Rooted<InstantObject*> roundedInstant(cx, CreateTemporalInstant(cx, ns)); |