tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Temporal.cpp (44601B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "builtin/temporal/Temporal.h"
      8 
      9 #include "mozilla/Casting.h"
     10 #ifdef DEBUG
     11 #  include "mozilla/CheckedInt.h"
     12 #endif
     13 #include "mozilla/FloatingPoint.h"
     14 #include "mozilla/Likely.h"
     15 #include "mozilla/MathAlgorithms.h"
     16 
     17 #include <algorithm>
     18 #include <cmath>
     19 #include <cstdlib>
     20 #include <iterator>
     21 #include <limits>
     22 #include <stdint.h>
     23 #include <string_view>
     24 #include <utility>
     25 
     26 #include "jsnum.h"
     27 #include "jspubtd.h"
     28 #include "NamespaceImports.h"
     29 
     30 #include "builtin/temporal/PlainDate.h"
     31 #include "builtin/temporal/PlainDateTime.h"
     32 #include "builtin/temporal/PlainMonthDay.h"
     33 #include "builtin/temporal/PlainTime.h"
     34 #include "builtin/temporal/PlainYearMonth.h"
     35 #include "builtin/temporal/TemporalRoundingMode.h"
     36 #include "builtin/temporal/TemporalUnit.h"
     37 #include "builtin/temporal/ZonedDateTime.h"
     38 #include "gc/Barrier.h"
     39 #include "js/Class.h"
     40 #include "js/Conversions.h"
     41 #include "js/ErrorReport.h"
     42 #include "js/friend/ErrorMessages.h"
     43 #include "js/Id.h"
     44 #include "js/Printer.h"
     45 #include "js/PropertyDescriptor.h"
     46 #include "js/PropertySpec.h"
     47 #include "js/RootingAPI.h"
     48 #include "js/String.h"
     49 #include "js/Value.h"
     50 #include "vm/BytecodeUtil.h"
     51 #include "vm/GlobalObject.h"
     52 #include "vm/Int128.h"
     53 #include "vm/JSAtomState.h"
     54 #include "vm/JSAtomUtils.h"
     55 #include "vm/JSContext.h"
     56 #include "vm/JSObject.h"
     57 #include "vm/ObjectOperations.h"
     58 #include "vm/Realm.h"
     59 #include "vm/StringType.h"
     60 
     61 #include "vm/JSObject-inl.h"
     62 #include "vm/ObjectOperations-inl.h"
     63 
     64 using namespace js;
     65 using namespace js::temporal;
     66 
     67 /**
     68 * GetOption ( options, property, type, values, default )
     69 *
     70 * GetOption specialization when `type=string`. Default value handling must
     71 * happen in the caller, so we don't provide the `default` parameter here.
     72 */
     73 static bool GetStringOption(JSContext* cx, Handle<JSObject*> options,
     74                            Handle<PropertyName*> property,
     75                            MutableHandle<JSString*> string) {
     76  // Step 1.
     77  Rooted<Value> value(cx);
     78  if (!GetProperty(cx, options, options, property, &value)) {
     79    return false;
     80  }
     81 
     82  // Step 2. (Caller should fill in the fallback.)
     83  if (value.isUndefined()) {
     84    return true;
     85  }
     86 
     87  // Steps 3-4. (Not applicable when type=string)
     88 
     89  // Step 5.
     90  string.set(JS::ToString(cx, value));
     91  if (!string) {
     92    return false;
     93  }
     94 
     95  // Step 6. (Not applicable in our implementation)
     96 
     97  // Step 7.
     98  return true;
     99 }
    100 
    101 /**
    102 * GetRoundingIncrementOption ( normalizedOptions, dividend, inclusive )
    103 */
    104 bool js::temporal::GetRoundingIncrementOption(JSContext* cx,
    105                                              Handle<JSObject*> options,
    106                                              Increment* increment) {
    107  // Step 1.
    108  Rooted<Value> value(cx);
    109  if (!GetProperty(cx, options, options, cx->names().roundingIncrement,
    110                   &value)) {
    111    return false;
    112  }
    113 
    114  // Step 2.
    115  if (value.isUndefined()) {
    116    *increment = Increment{1};
    117    return true;
    118  }
    119 
    120  // Step 3.
    121  double number;
    122  if (!ToIntegerWithTruncation(cx, value, "roundingIncrement", &number)) {
    123    return false;
    124  }
    125 
    126  // Step 4.
    127  if (number < 1 || number > 1'000'000'000) {
    128    ToCStringBuf cbuf;
    129    const char* numStr = NumberToCString(&cbuf, number);
    130 
    131    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    132                              JSMSG_INVALID_OPTION_VALUE, "roundingIncrement",
    133                              numStr);
    134    return false;
    135  }
    136 
    137  // Step 5.
    138  *increment = Increment{uint32_t(number)};
    139  return true;
    140 }
    141 
    142 /**
    143 * ValidateTemporalRoundingIncrement ( increment, dividend, inclusive )
    144 */
    145 bool js::temporal::ValidateTemporalRoundingIncrement(JSContext* cx,
    146                                                     Increment increment,
    147                                                     int64_t dividend,
    148                                                     bool inclusive) {
    149  MOZ_ASSERT(dividend > 0);
    150  MOZ_ASSERT_IF(!inclusive, dividend > 1);
    151 
    152  // Steps 1-2.
    153  int64_t maximum = inclusive ? dividend : dividend - 1;
    154 
    155  // Steps 3-4.
    156  if (increment.value() > maximum || dividend % increment.value() != 0) {
    157    Int32ToCStringBuf cbuf;
    158    const char* numStr = Int32ToCString(&cbuf, int32_t(increment.value()));
    159 
    160    // TODO: Better error message could be helpful.
    161    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    162                              JSMSG_INVALID_OPTION_VALUE, "roundingIncrement",
    163                              numStr);
    164    return false;
    165  }
    166 
    167  // Step 5.
    168  return true;
    169 }
    170 
    171 static Handle<PropertyName*> ToPropertyName(JSContext* cx,
    172                                            TemporalUnitKey key) {
    173  switch (key) {
    174    case TemporalUnitKey::SmallestUnit:
    175      return cx->names().smallestUnit;
    176    case TemporalUnitKey::LargestUnit:
    177      return cx->names().largestUnit;
    178    case TemporalUnitKey::Unit:
    179      return cx->names().unit;
    180  }
    181  MOZ_CRASH("invalid temporal unit group");
    182 }
    183 
    184 static const char* ToCString(TemporalUnitKey key) {
    185  switch (key) {
    186    case TemporalUnitKey::SmallestUnit:
    187      return "smallestUnit";
    188    case TemporalUnitKey::LargestUnit:
    189      return "largestUnit";
    190    case TemporalUnitKey::Unit:
    191      return "unit";
    192  }
    193  MOZ_CRASH("invalid temporal unit group");
    194 }
    195 
    196 static bool ToTemporalUnit(JSContext* cx, JSLinearString* str,
    197                           TemporalUnitKey key, TemporalUnit* unit) {
    198  struct UnitMap {
    199    std::string_view name;
    200    TemporalUnit unit;
    201  };
    202 
    203  static constexpr UnitMap mapping[] = {
    204      {"auto", TemporalUnit::Auto},
    205      {"year", TemporalUnit::Year},
    206      {"years", TemporalUnit::Year},
    207      {"month", TemporalUnit::Month},
    208      {"months", TemporalUnit::Month},
    209      {"week", TemporalUnit::Week},
    210      {"weeks", TemporalUnit::Week},
    211      {"day", TemporalUnit::Day},
    212      {"days", TemporalUnit::Day},
    213      {"hour", TemporalUnit::Hour},
    214      {"hours", TemporalUnit::Hour},
    215      {"minute", TemporalUnit::Minute},
    216      {"minutes", TemporalUnit::Minute},
    217      {"second", TemporalUnit::Second},
    218      {"seconds", TemporalUnit::Second},
    219      {"millisecond", TemporalUnit::Millisecond},
    220      {"milliseconds", TemporalUnit::Millisecond},
    221      {"microsecond", TemporalUnit::Microsecond},
    222      {"microseconds", TemporalUnit::Microsecond},
    223      {"nanosecond", TemporalUnit::Nanosecond},
    224      {"nanoseconds", TemporalUnit::Nanosecond},
    225  };
    226 
    227  // Compute the length of the longest name.
    228  constexpr size_t maxNameLength =
    229      std::max_element(std::begin(mapping), std::end(mapping),
    230                       [](const auto& x, const auto& y) {
    231                         return x.name.length() < y.name.length();
    232                       })
    233          ->name.length();
    234 
    235  // Twenty StringEqualsLiteral calls for each possible combination seems a bit
    236  // expensive, so let's instead copy the input name into a char array and rely
    237  // on the compiler to generate optimized code for the comparisons.
    238 
    239  size_t length = str->length();
    240  if (length <= maxNameLength && StringIsAscii(str)) {
    241    char chars[maxNameLength] = {};
    242    JS::LossyCopyLinearStringChars(chars, str, length);
    243 
    244    for (const auto& m : mapping) {
    245      if (m.name == std::string_view(chars, length)) {
    246        *unit = m.unit;
    247        return true;
    248      }
    249    }
    250  }
    251 
    252  if (auto chars = QuoteString(cx, str, '"')) {
    253    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
    254                             JSMSG_INVALID_OPTION_VALUE, ToCString(key),
    255                             chars.get());
    256  }
    257  return false;
    258 }
    259 
    260 static std::pair<TemporalUnit, TemporalUnit> AllowedValues(
    261    TemporalUnitGroup unitGroup) {
    262  switch (unitGroup) {
    263    case TemporalUnitGroup::Date:
    264      return {TemporalUnit::Year, TemporalUnit::Day};
    265    case TemporalUnitGroup::Time:
    266      return {TemporalUnit::Hour, TemporalUnit::Nanosecond};
    267    case TemporalUnitGroup::DateTime:
    268      return {TemporalUnit::Year, TemporalUnit::Nanosecond};
    269    case TemporalUnitGroup::DayTime:
    270      return {TemporalUnit::Day, TemporalUnit::Nanosecond};
    271  }
    272  MOZ_CRASH("invalid temporal unit group");
    273 }
    274 
    275 /**
    276 * GetTemporalUnitValuedOption ( options, key, default )
    277 */
    278 bool js::temporal::GetTemporalUnitValuedOption(JSContext* cx,
    279                                               Handle<JSObject*> options,
    280                                               TemporalUnitKey key,
    281                                               TemporalUnit* unit) {
    282  // Steps 1-5. (Not applicable in our implementation.)
    283 
    284  // Step 6.
    285  Rooted<JSString*> value(cx);
    286  if (!GetStringOption(cx, options, ToPropertyName(cx, key), &value)) {
    287    return false;
    288  }
    289 
    290  // Step 7.
    291  //
    292  // Caller should fill in the fallback.
    293  if (!value) {
    294    return true;
    295  }
    296 
    297  // Steps 8-9.
    298  auto* linear = value->ensureLinear(cx);
    299  if (!linear) {
    300    return false;
    301  }
    302  return ToTemporalUnit(cx, linear, key, unit);
    303 }
    304 
    305 /**
    306 * GetTemporalUnitValuedOption ( options, key, default )
    307 */
    308 bool js::temporal::GetTemporalUnitValuedOption(JSContext* cx,
    309                                               Handle<JSString*> value,
    310                                               TemporalUnitKey key,
    311                                               TemporalUnit* unit) {
    312  // Steps 1-7. (Not applicable in our implementation.)
    313 
    314  // Steps 8-9.
    315  auto* linear = value->ensureLinear(cx);
    316  if (!linear) {
    317    return false;
    318  }
    319  return ToTemporalUnit(cx, linear, key, unit);
    320 }
    321 
    322 /**
    323 * ValidateTemporalUnitValue ( value, unitGroup [ , extraValues ] )
    324 */
    325 bool js::temporal::ValidateTemporalUnitValue(JSContext* cx, TemporalUnitKey key,
    326                                             TemporalUnit unit,
    327                                             TemporalUnitGroup unitGroup) {
    328  // Step 1.
    329  if (unit == TemporalUnit::Unset) {
    330    return true;
    331  }
    332 
    333  // Step 2. (Partial)
    334  if (key == TemporalUnitKey::LargestUnit && unit == TemporalUnit::Auto) {
    335    return true;
    336  }
    337 
    338  // Steps 2-5.
    339  auto allowedValues = AllowedValues(unitGroup);
    340  if (allowedValues.first <= unit && unit <= allowedValues.second) {
    341    return true;
    342  }
    343 
    344  // Step 6.
    345  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    346                            JSMSG_INVALID_OPTION_VALUE, ToCString(key),
    347                            TemporalUnitToString(unit));
    348  return false;
    349 }
    350 
    351 /**
    352 * GetRoundingModeOption ( normalizedOptions, fallback )
    353 */
    354 bool js::temporal::GetRoundingModeOption(JSContext* cx,
    355                                         Handle<JSObject*> options,
    356                                         TemporalRoundingMode* mode) {
    357  // Steps 1-2.
    358  Rooted<JSString*> string(cx);
    359  if (!GetStringOption(cx, options, cx->names().roundingMode, &string)) {
    360    return false;
    361  }
    362 
    363  // Caller should fill in the fallback.
    364  if (!string) {
    365    return true;
    366  }
    367 
    368  JSLinearString* linear = string->ensureLinear(cx);
    369  if (!linear) {
    370    return false;
    371  }
    372 
    373  if (StringEqualsLiteral(linear, "ceil")) {
    374    *mode = TemporalRoundingMode::Ceil;
    375  } else if (StringEqualsLiteral(linear, "floor")) {
    376    *mode = TemporalRoundingMode::Floor;
    377  } else if (StringEqualsLiteral(linear, "expand")) {
    378    *mode = TemporalRoundingMode::Expand;
    379  } else if (StringEqualsLiteral(linear, "trunc")) {
    380    *mode = TemporalRoundingMode::Trunc;
    381  } else if (StringEqualsLiteral(linear, "halfCeil")) {
    382    *mode = TemporalRoundingMode::HalfCeil;
    383  } else if (StringEqualsLiteral(linear, "halfFloor")) {
    384    *mode = TemporalRoundingMode::HalfFloor;
    385  } else if (StringEqualsLiteral(linear, "halfExpand")) {
    386    *mode = TemporalRoundingMode::HalfExpand;
    387  } else if (StringEqualsLiteral(linear, "halfTrunc")) {
    388    *mode = TemporalRoundingMode::HalfTrunc;
    389  } else if (StringEqualsLiteral(linear, "halfEven")) {
    390    *mode = TemporalRoundingMode::HalfEven;
    391  } else {
    392    if (auto chars = QuoteString(cx, linear, '"')) {
    393      JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
    394                               JSMSG_INVALID_OPTION_VALUE, "roundingMode",
    395                               chars.get());
    396    }
    397    return false;
    398  }
    399  return true;
    400 }
    401 
    402 #ifdef DEBUG
    403 template <typename T>
    404 static bool IsValidMul(const T& x, const T& y) {
    405  return (mozilla::CheckedInt<T>(x) * y).isValid();
    406 }
    407 
    408 // Copied from mozilla::CheckedInt.
    409 template <>
    410 bool IsValidMul<Int128>(const Int128& x, const Int128& y) {
    411  static constexpr auto min = Int128{1} << 127;
    412  static constexpr auto max = ~min;
    413 
    414  if (x == Int128{0} || y == Int128{0}) {
    415    return true;
    416  }
    417  if (x > Int128{0}) {
    418    return y > Int128{0} ? x <= max / y : y >= min / x;
    419  }
    420  return y > Int128{0} ? x >= min / y : y >= max / x;
    421 }
    422 #endif
    423 
    424 /**
    425 * RoundNumberToIncrement ( x, increment, roundingMode )
    426 */
    427 Int128 js::temporal::RoundNumberToIncrement(const Int128& numerator,
    428                                            int64_t denominator,
    429                                            Increment increment,
    430                                            TemporalRoundingMode roundingMode) {
    431  MOZ_ASSERT(denominator > 0);
    432  MOZ_ASSERT(Increment::min() <= increment && increment <= Increment::max());
    433 
    434  auto inc = Int128{increment.value()};
    435  MOZ_ASSERT(IsValidMul(Int128{denominator}, inc), "unsupported overflow");
    436 
    437  auto divisor = Int128{denominator} * inc;
    438  MOZ_ASSERT(divisor > Int128{0});
    439 
    440  // Steps 1-8.
    441  auto rounded = Divide(numerator, divisor, roundingMode);
    442 
    443  // Step 9.
    444  MOZ_ASSERT(IsValidMul(rounded, inc), "unsupported overflow");
    445  return rounded * inc;
    446 }
    447 
    448 /**
    449 * RoundNumberToIncrement ( x, increment, roundingMode )
    450 */
    451 int64_t js::temporal::RoundNumberToIncrement(
    452    int64_t x, int64_t increment, TemporalRoundingMode roundingMode) {
    453  MOZ_ASSERT(increment > 0);
    454 
    455  // Steps 1-8.
    456  int64_t rounded = Divide(x, increment, roundingMode);
    457 
    458  // Step 9.
    459  MOZ_ASSERT(IsValidMul(rounded, increment), "unsupported overflow");
    460  return rounded * increment;
    461 }
    462 
    463 /**
    464 * RoundNumberToIncrement ( x, increment, roundingMode )
    465 */
    466 Int128 js::temporal::RoundNumberToIncrement(const Int128& x,
    467                                            const Int128& increment,
    468                                            TemporalRoundingMode roundingMode) {
    469  MOZ_ASSERT(increment > Int128{0});
    470 
    471  // Steps 1-8.
    472  auto rounded = Divide(x, increment, roundingMode);
    473 
    474  // Step 9.
    475  MOZ_ASSERT(IsValidMul(rounded, increment), "unsupported overflow");
    476  return rounded * increment;
    477 }
    478 
    479 template <typename IntT>
    480 static inline constexpr bool IsSafeInteger(const IntT& x) {
    481  constexpr IntT MaxSafeInteger = IntT{int64_t(1) << 53};
    482  constexpr IntT MinSafeInteger = -MaxSafeInteger;
    483  return MinSafeInteger < x && x < MaxSafeInteger;
    484 }
    485 
    486 /**
    487 * Return the real number value of the fraction |numerator / denominator|.
    488 *
    489 * As an optimization we multiply the remainder by 16 when computing the number
    490 * of digits after the decimal point, i.e. we compute four instead of one bit of
    491 * the fractional digits. The denominator is therefore required to not exceed
    492 * 2**(N - log2(16)), where N is the number of non-sign bits in the mantissa.
    493 */
    494 template <typename T>
    495 static double FractionToDoubleSlow(const T& numerator, const T& denominator) {
    496  MOZ_ASSERT(denominator > T{0}, "expected positive denominator");
    497  MOZ_ASSERT(denominator <= (T{1} << (std::numeric_limits<T>::digits - 4)),
    498             "denominator too large");
    499 
    500  auto absValue = [](const T& value) {
    501    if constexpr (std::is_same_v<T, Int128>) {
    502      return value.abs();
    503    } else {
    504      // NB: Not std::abs, because std::abs(INT64_MIN) is undefined behavior.
    505      return mozilla::Abs(value);
    506    }
    507  };
    508 
    509  using UnsignedT = decltype(absValue(T{0}));
    510  static_assert(!std::numeric_limits<UnsignedT>::is_signed);
    511 
    512  auto divrem = [](const UnsignedT& x, const UnsignedT& y) {
    513    if constexpr (std::is_same_v<T, Int128>) {
    514      return x.divrem(y);
    515    } else {
    516      return std::pair{x / y, x % y};
    517    }
    518  };
    519 
    520  auto [quot, rem] =
    521      divrem(absValue(numerator), static_cast<UnsignedT>(denominator));
    522 
    523  // Simple case when no remainder is present.
    524  if (rem == UnsignedT{0}) {
    525    double sign = numerator < T{0} ? -1 : 1;
    526    return sign * double(quot);
    527  }
    528 
    529  using Double = mozilla::FloatingPoint<double>;
    530 
    531  // Significand including the implicit one of IEEE-754 floating point numbers.
    532  static constexpr uint32_t SignificandWidthWithImplicitOne =
    533      Double::kSignificandWidth + 1;
    534 
    535  // Number of leading zeros for a correctly adjusted significand.
    536  static constexpr uint32_t SignificandLeadingZeros =
    537      64 - SignificandWidthWithImplicitOne;
    538 
    539  // Exponent bias for an integral significand. (`Double::kExponentBias` is the
    540  // bias for the binary fraction `1.xyz * 2**exp`. For an integral significand
    541  // the significand width has to be added to the bias.)
    542  static constexpr int32_t ExponentBias =
    543      Double::kExponentBias + Double::kSignificandWidth;
    544 
    545  // Significand, possibly unnormalized.
    546  uint64_t significand = 0;
    547 
    548  // Significand ignored msd bits.
    549  uint32_t ignoredBits = 0;
    550 
    551  // Read quotient, from most to least significant digit. Stop when the
    552  // significand got too large for double precision.
    553  int32_t shift = std::numeric_limits<UnsignedT>::digits;
    554  for (; shift != 0 && ignoredBits == 0; shift -= 4) {
    555    uint64_t digit = uint64_t(quot >> (shift - 4)) & 0xf;
    556 
    557    significand = significand * 16 + digit;
    558    ignoredBits = significand >> SignificandWidthWithImplicitOne;
    559  }
    560 
    561  // Read remainder, from most to least significant digit. Stop when the
    562  // remainder is zero or the significand got too large.
    563  int32_t fractionDigit = 0;
    564  for (; rem != UnsignedT{0} && ignoredBits == 0; fractionDigit++) {
    565    auto [digit, next] =
    566        divrem(rem * UnsignedT{16}, static_cast<UnsignedT>(denominator));
    567    rem = next;
    568 
    569    significand = significand * 16 + uint64_t(digit);
    570    ignoredBits = significand >> SignificandWidthWithImplicitOne;
    571  }
    572 
    573  // Unbiased exponent. (`shift` remaining bits in the quotient, minus the
    574  // fractional digits.)
    575  int32_t exponent = shift - (fractionDigit * 4);
    576 
    577  // Significand got too large and some bits are now ignored. Adjust the
    578  // significand and exponent.
    579  if (ignoredBits != 0) {
    580    //        significand
    581    //  ___________|__________
    582    // /                      |
    583    // [xxx················yyy|
    584    //  \_/                \_/
    585    //   |                  |
    586    // ignoredBits       extraBits
    587    //
    588    // `ignoredBits` have to be shifted back into the 53 bits of the significand
    589    // and `extraBits` has to be checked if the result has to be rounded up.
    590 
    591    // Number of ignored/extra bits in the significand.
    592    uint32_t extraBitsCount = 32 - mozilla::CountLeadingZeroes32(ignoredBits);
    593    MOZ_ASSERT(extraBitsCount > 0);
    594 
    595    // Extra bits in the significand.
    596    uint32_t extraBits = uint32_t(significand) & ((1 << extraBitsCount) - 1);
    597 
    598    // Move the ignored bits into the proper significand position and adjust the
    599    // exponent to reflect the now moved out extra bits.
    600    significand >>= extraBitsCount;
    601    exponent += extraBitsCount;
    602 
    603    MOZ_ASSERT((significand >> SignificandWidthWithImplicitOne) == 0,
    604               "no excess bits in the significand");
    605 
    606    // When the most significant digit in the extra bits is set, we may need to
    607    // round the result.
    608    uint32_t msdExtraBit = extraBits >> (extraBitsCount - 1);
    609    if (msdExtraBit != 0) {
    610      // Extra bits, excluding the most significant digit.
    611      uint32_t extraBitExcludingMsdMask = (1 << (extraBitsCount - 1)) - 1;
    612 
    613      // Unprocessed bits in the quotient.
    614      auto bitsBelowExtraBits = quot & ((UnsignedT{1} << shift) - UnsignedT{1});
    615 
    616      // Round up if the extra bit's msd is set and either the significand is
    617      // odd or any other bits below the extra bit's msd are non-zero.
    618      //
    619      // Bits below the extra bit's msd are:
    620      // 1. The remaining bits of the extra bits.
    621      // 2. Any bits below the extra bits.
    622      // 3. Any rest of the remainder.
    623      bool shouldRoundUp = (significand & 1) != 0 ||
    624                           (extraBits & extraBitExcludingMsdMask) != 0 ||
    625                           bitsBelowExtraBits != UnsignedT{0} ||
    626                           rem != UnsignedT{0};
    627      if (shouldRoundUp) {
    628        // Add one to the significand bits.
    629        significand += 1;
    630 
    631        // If they overflow, the exponent must also be increased.
    632        if ((significand >> SignificandWidthWithImplicitOne) != 0) {
    633          exponent++;
    634          significand >>= 1;
    635        }
    636      }
    637    }
    638  }
    639 
    640  MOZ_ASSERT(significand > 0, "significand is non-zero");
    641  MOZ_ASSERT((significand >> SignificandWidthWithImplicitOne) == 0,
    642             "no excess bits in the significand");
    643 
    644  // Move the significand into the correct position and adjust the exponent
    645  // accordingly.
    646  uint32_t significandZeros = mozilla::CountLeadingZeroes64(significand);
    647  if (significandZeros < SignificandLeadingZeros) {
    648    uint32_t shift = SignificandLeadingZeros - significandZeros;
    649    significand >>= shift;
    650    exponent += shift;
    651  } else if (significandZeros > SignificandLeadingZeros) {
    652    uint32_t shift = significandZeros - SignificandLeadingZeros;
    653    significand <<= shift;
    654    exponent -= shift;
    655  }
    656 
    657  // Combine the individual bits of the double value and return it.
    658  uint64_t signBit = uint64_t(numerator < T{0} ? 1 : 0)
    659                     << (Double::kExponentWidth + Double::kSignificandWidth);
    660  uint64_t exponentBits = static_cast<uint64_t>(exponent + ExponentBias)
    661                          << Double::kExponentShift;
    662  uint64_t significandBits = significand & Double::kSignificandBits;
    663  return mozilla::BitwiseCast<double>(signBit | exponentBits | significandBits);
    664 }
    665 
    666 double js::temporal::FractionToDouble(int64_t numerator, int64_t denominator) {
    667  MOZ_ASSERT(denominator > 0);
    668 
    669  // Zero divided by any divisor is still zero.
    670  if (numerator == 0) {
    671    return 0;
    672  }
    673 
    674  // When both values can be represented as doubles, use double division to
    675  // compute the exact result. The result is exact, because double division is
    676  // guaranteed to return the exact result.
    677  if (MOZ_LIKELY(::IsSafeInteger(numerator) && ::IsSafeInteger(denominator))) {
    678    return double(numerator) / double(denominator);
    679  }
    680 
    681  // Otherwise call into |FractionToDoubleSlow| to compute the exact result.
    682  if (denominator <=
    683      (int64_t(1) << (std::numeric_limits<int64_t>::digits - 4))) {
    684    // Slightly faster, but still slow approach when |denominator| is small
    685    // enough to allow computing on int64 values.
    686    return FractionToDoubleSlow(numerator, denominator);
    687  }
    688  return FractionToDoubleSlow(Int128{numerator}, Int128{denominator});
    689 }
    690 
    691 double js::temporal::FractionToDouble(const Int128& numerator,
    692                                      const Int128& denominator) {
    693  MOZ_ASSERT(denominator > Int128{0});
    694 
    695  // Zero divided by any divisor is still zero.
    696  if (numerator == Int128{0}) {
    697    return 0;
    698  }
    699 
    700  // When both values can be represented as doubles, use double division to
    701  // compute the exact result. The result is exact, because double division is
    702  // guaranteed to return the exact result.
    703  if (MOZ_LIKELY(::IsSafeInteger(numerator) && ::IsSafeInteger(denominator))) {
    704    return double(numerator) / double(denominator);
    705  }
    706 
    707  // Otherwise call into |FractionToDoubleSlow| to compute the exact result.
    708  return FractionToDoubleSlow(numerator, denominator);
    709 }
    710 
    711 /**
    712 * GetTemporalShowCalendarNameOption ( normalizedOptions )
    713 */
    714 bool js::temporal::GetTemporalShowCalendarNameOption(JSContext* cx,
    715                                                     Handle<JSObject*> options,
    716                                                     ShowCalendar* result) {
    717  // Step 1.
    718  Rooted<JSString*> calendarName(cx);
    719  if (!GetStringOption(cx, options, cx->names().calendarName, &calendarName)) {
    720    return false;
    721  }
    722 
    723  // Caller should fill in the fallback.
    724  if (!calendarName) {
    725    return true;
    726  }
    727 
    728  JSLinearString* linear = calendarName->ensureLinear(cx);
    729  if (!linear) {
    730    return false;
    731  }
    732 
    733  if (StringEqualsLiteral(linear, "auto")) {
    734    *result = ShowCalendar::Auto;
    735  } else if (StringEqualsLiteral(linear, "always")) {
    736    *result = ShowCalendar::Always;
    737  } else if (StringEqualsLiteral(linear, "never")) {
    738    *result = ShowCalendar::Never;
    739  } else if (StringEqualsLiteral(linear, "critical")) {
    740    *result = ShowCalendar::Critical;
    741  } else {
    742    if (auto chars = QuoteString(cx, linear, '"')) {
    743      JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
    744                               JSMSG_INVALID_OPTION_VALUE, "calendarName",
    745                               chars.get());
    746    }
    747    return false;
    748  }
    749  return true;
    750 }
    751 
    752 /**
    753 * GetTemporalFractionalSecondDigitsOption ( normalizedOptions )
    754 */
    755 bool js::temporal::GetTemporalFractionalSecondDigitsOption(
    756    JSContext* cx, Handle<JSObject*> options, Precision* precision) {
    757  // Step 1.
    758  Rooted<Value> digitsValue(cx);
    759  if (!GetProperty(cx, options, options, cx->names().fractionalSecondDigits,
    760                   &digitsValue)) {
    761    return false;
    762  }
    763 
    764  // Step 2.
    765  if (digitsValue.isUndefined()) {
    766    *precision = Precision::Auto();
    767    return true;
    768  }
    769 
    770  // Step 3.
    771  if (!digitsValue.isNumber()) {
    772    // Step 3.a.
    773    JSString* string = JS::ToString(cx, digitsValue);
    774    if (!string) {
    775      return false;
    776    }
    777 
    778    JSLinearString* linear = string->ensureLinear(cx);
    779    if (!linear) {
    780      return false;
    781    }
    782 
    783    if (!StringEqualsLiteral(linear, "auto")) {
    784      if (auto chars = QuoteString(cx, linear, '"')) {
    785        JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
    786                                 JSMSG_INVALID_OPTION_VALUE,
    787                                 "fractionalSecondDigits", chars.get());
    788      }
    789      return false;
    790    }
    791 
    792    // Step 3.b.
    793    *precision = Precision::Auto();
    794    return true;
    795  }
    796 
    797  // Step 4.
    798  double digitCount = digitsValue.toNumber();
    799  if (!std::isfinite(digitCount)) {
    800    ToCStringBuf cbuf;
    801    const char* numStr = NumberToCString(&cbuf, digitCount);
    802 
    803    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    804                              JSMSG_INVALID_OPTION_VALUE,
    805                              "fractionalSecondDigits", numStr);
    806    return false;
    807  }
    808 
    809  // Step 5.
    810  digitCount = std::floor(digitCount);
    811 
    812  // Step 6.
    813  if (digitCount < 0 || digitCount > 9) {
    814    ToCStringBuf cbuf;
    815    const char* numStr = NumberToCString(&cbuf, digitCount);
    816 
    817    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    818                              JSMSG_INVALID_OPTION_VALUE,
    819                              "fractionalSecondDigits", numStr);
    820    return false;
    821  }
    822 
    823  // Step 7.
    824  *precision = Precision{uint8_t(digitCount)};
    825  return true;
    826 }
    827 
    828 /**
    829 * ToSecondsStringPrecisionRecord ( smallestUnit, fractionalDigitCount )
    830 */
    831 SecondsStringPrecision js::temporal::ToSecondsStringPrecision(
    832    TemporalUnit smallestUnit, Precision fractionalDigitCount) {
    833  MOZ_ASSERT(smallestUnit == TemporalUnit::Unset ||
    834             smallestUnit >= TemporalUnit::Minute);
    835  MOZ_ASSERT(fractionalDigitCount == Precision::Auto() ||
    836             fractionalDigitCount.value() <= 9);
    837 
    838  // Steps 1-5.
    839  switch (smallestUnit) {
    840    // Step 1.
    841    case TemporalUnit::Minute:
    842      return {Precision::Minute(), TemporalUnit::Minute, Increment{1}};
    843 
    844    // Step 2.
    845    case TemporalUnit::Second:
    846      return {Precision{0}, TemporalUnit::Second, Increment{1}};
    847 
    848    // Step 3.
    849    case TemporalUnit::Millisecond:
    850      return {Precision{3}, TemporalUnit::Millisecond, Increment{1}};
    851 
    852    // Step 4.
    853    case TemporalUnit::Microsecond:
    854      return {Precision{6}, TemporalUnit::Microsecond, Increment{1}};
    855 
    856    // Step 5.
    857    case TemporalUnit::Nanosecond:
    858      return {Precision{9}, TemporalUnit::Nanosecond, Increment{1}};
    859 
    860    case TemporalUnit::Unset:
    861      break;
    862 
    863    case TemporalUnit::Auto:
    864    case TemporalUnit::Year:
    865    case TemporalUnit::Month:
    866    case TemporalUnit::Week:
    867    case TemporalUnit::Day:
    868    case TemporalUnit::Hour:
    869      MOZ_CRASH("Unexpected temporal unit");
    870  }
    871 
    872  // Step 6. (Not applicable in our implementation.)
    873 
    874  // Step 7.
    875  if (fractionalDigitCount == Precision::Auto()) {
    876    return {Precision::Auto(), TemporalUnit::Nanosecond, Increment{1}};
    877  }
    878 
    879  static constexpr Increment increments[] = {
    880      Increment{1},
    881      Increment{10},
    882      Increment{100},
    883  };
    884 
    885  uint8_t digitCount = fractionalDigitCount.value();
    886 
    887  // Step 8.
    888  if (digitCount == 0) {
    889    return {Precision{0}, TemporalUnit::Second, Increment{1}};
    890  }
    891 
    892  // Step 9.
    893  if (digitCount <= 3) {
    894    return {fractionalDigitCount, TemporalUnit::Millisecond,
    895            increments[3 - digitCount]};
    896  }
    897 
    898  // Step 10.
    899  if (digitCount <= 6) {
    900    return {fractionalDigitCount, TemporalUnit::Microsecond,
    901            increments[6 - digitCount]};
    902  }
    903 
    904  // Step 11.
    905  MOZ_ASSERT(digitCount <= 9);
    906 
    907  // Step 12.
    908  return {fractionalDigitCount, TemporalUnit::Nanosecond,
    909          increments[9 - digitCount]};
    910 }
    911 
    912 /**
    913 * GetTemporalOverflowOption ( normalizedOptions )
    914 */
    915 bool js::temporal::GetTemporalOverflowOption(JSContext* cx,
    916                                             Handle<JSObject*> options,
    917                                             TemporalOverflow* result) {
    918  // Step 1.
    919  Rooted<JSString*> overflow(cx);
    920  if (!GetStringOption(cx, options, cx->names().overflow, &overflow)) {
    921    return false;
    922  }
    923 
    924  // Caller should fill in the fallback.
    925  if (!overflow) {
    926    return true;
    927  }
    928 
    929  JSLinearString* linear = overflow->ensureLinear(cx);
    930  if (!linear) {
    931    return false;
    932  }
    933 
    934  if (StringEqualsLiteral(linear, "constrain")) {
    935    *result = TemporalOverflow::Constrain;
    936  } else if (StringEqualsLiteral(linear, "reject")) {
    937    *result = TemporalOverflow::Reject;
    938  } else {
    939    if (auto chars = QuoteString(cx, linear, '"')) {
    940      JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
    941                               JSMSG_INVALID_OPTION_VALUE, "overflow",
    942                               chars.get());
    943    }
    944    return false;
    945  }
    946  return true;
    947 }
    948 
    949 /**
    950 * GetTemporalDisambiguationOption ( options )
    951 */
    952 bool js::temporal::GetTemporalDisambiguationOption(
    953    JSContext* cx, Handle<JSObject*> options,
    954    TemporalDisambiguation* disambiguation) {
    955  // Step 1. (Not applicable)
    956 
    957  // Step 2.
    958  Rooted<JSString*> string(cx);
    959  if (!GetStringOption(cx, options, cx->names().disambiguation, &string)) {
    960    return false;
    961  }
    962 
    963  // Caller should fill in the fallback.
    964  if (!string) {
    965    return true;
    966  }
    967 
    968  JSLinearString* linear = string->ensureLinear(cx);
    969  if (!linear) {
    970    return false;
    971  }
    972 
    973  if (StringEqualsLiteral(linear, "compatible")) {
    974    *disambiguation = TemporalDisambiguation::Compatible;
    975  } else if (StringEqualsLiteral(linear, "earlier")) {
    976    *disambiguation = TemporalDisambiguation::Earlier;
    977  } else if (StringEqualsLiteral(linear, "later")) {
    978    *disambiguation = TemporalDisambiguation::Later;
    979  } else if (StringEqualsLiteral(linear, "reject")) {
    980    *disambiguation = TemporalDisambiguation::Reject;
    981  } else {
    982    if (auto chars = QuoteString(cx, linear, '"')) {
    983      JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
    984                               JSMSG_INVALID_OPTION_VALUE, "disambiguation",
    985                               chars.get());
    986    }
    987    return false;
    988  }
    989  return true;
    990 }
    991 
    992 /**
    993 * GetTemporalOffsetOption ( options, fallback )
    994 */
    995 bool js::temporal::GetTemporalOffsetOption(JSContext* cx,
    996                                           Handle<JSObject*> options,
    997                                           TemporalOffset* offset) {
    998  // Step 1. (Not applicable in our implementation.)
    999 
   1000  // Step 2.
   1001  Rooted<JSString*> string(cx);
   1002  if (!GetStringOption(cx, options, cx->names().offset, &string)) {
   1003    return false;
   1004  }
   1005 
   1006  // Caller should fill in the fallback.
   1007  if (!string) {
   1008    return true;
   1009  }
   1010 
   1011  JSLinearString* linear = string->ensureLinear(cx);
   1012  if (!linear) {
   1013    return false;
   1014  }
   1015 
   1016  if (StringEqualsLiteral(linear, "prefer")) {
   1017    *offset = TemporalOffset::Prefer;
   1018  } else if (StringEqualsLiteral(linear, "use")) {
   1019    *offset = TemporalOffset::Use;
   1020  } else if (StringEqualsLiteral(linear, "ignore")) {
   1021    *offset = TemporalOffset::Ignore;
   1022  } else if (StringEqualsLiteral(linear, "reject")) {
   1023    *offset = TemporalOffset::Reject;
   1024  } else {
   1025    if (auto chars = QuoteString(cx, linear, '"')) {
   1026      JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
   1027                               JSMSG_INVALID_OPTION_VALUE, "offset",
   1028                               chars.get());
   1029    }
   1030    return false;
   1031  }
   1032  return true;
   1033 }
   1034 
   1035 /**
   1036 * GetTemporalShowTimeZoneNameOption ( normalizedOptions )
   1037 */
   1038 bool js::temporal::GetTemporalShowTimeZoneNameOption(JSContext* cx,
   1039                                                     Handle<JSObject*> options,
   1040                                                     ShowTimeZoneName* result) {
   1041  // Step 1.
   1042  Rooted<JSString*> timeZoneName(cx);
   1043  if (!GetStringOption(cx, options, cx->names().timeZoneName, &timeZoneName)) {
   1044    return false;
   1045  }
   1046 
   1047  // Caller should fill in the fallback.
   1048  if (!timeZoneName) {
   1049    return true;
   1050  }
   1051 
   1052  JSLinearString* linear = timeZoneName->ensureLinear(cx);
   1053  if (!linear) {
   1054    return false;
   1055  }
   1056 
   1057  if (StringEqualsLiteral(linear, "auto")) {
   1058    *result = ShowTimeZoneName::Auto;
   1059  } else if (StringEqualsLiteral(linear, "never")) {
   1060    *result = ShowTimeZoneName::Never;
   1061  } else if (StringEqualsLiteral(linear, "critical")) {
   1062    *result = ShowTimeZoneName::Critical;
   1063  } else {
   1064    if (auto chars = QuoteString(cx, linear, '"')) {
   1065      JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
   1066                               JSMSG_INVALID_OPTION_VALUE, "timeZoneName",
   1067                               chars.get());
   1068    }
   1069    return false;
   1070  }
   1071  return true;
   1072 }
   1073 
   1074 /**
   1075 * GetTemporalShowOffsetOption ( normalizedOptions )
   1076 */
   1077 bool js::temporal::GetTemporalShowOffsetOption(JSContext* cx,
   1078                                               Handle<JSObject*> options,
   1079                                               ShowOffset* result) {
   1080  // Step 1.
   1081  Rooted<JSString*> offset(cx);
   1082  if (!GetStringOption(cx, options, cx->names().offset, &offset)) {
   1083    return false;
   1084  }
   1085 
   1086  // Caller should fill in the fallback.
   1087  if (!offset) {
   1088    return true;
   1089  }
   1090 
   1091  JSLinearString* linear = offset->ensureLinear(cx);
   1092  if (!linear) {
   1093    return false;
   1094  }
   1095 
   1096  if (StringEqualsLiteral(linear, "auto")) {
   1097    *result = ShowOffset::Auto;
   1098  } else if (StringEqualsLiteral(linear, "never")) {
   1099    *result = ShowOffset::Never;
   1100  } else {
   1101    if (auto chars = QuoteString(cx, linear, '"')) {
   1102      JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
   1103                               JSMSG_INVALID_OPTION_VALUE, "offset",
   1104                               chars.get());
   1105    }
   1106    return false;
   1107  }
   1108  return true;
   1109 }
   1110 
   1111 /**
   1112 * GetDirectionOption ( options )
   1113 */
   1114 bool js::temporal::GetDirectionOption(JSContext* cx,
   1115                                      Handle<JSString*> direction,
   1116                                      Direction* result) {
   1117  JSLinearString* linear = direction->ensureLinear(cx);
   1118  if (!linear) {
   1119    return false;
   1120  }
   1121 
   1122  if (StringEqualsLiteral(linear, "next")) {
   1123    *result = Direction::Next;
   1124  } else if (StringEqualsLiteral(linear, "previous")) {
   1125    *result = Direction::Previous;
   1126  } else {
   1127    if (auto chars = QuoteString(cx, linear, '"')) {
   1128      JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
   1129                               JSMSG_INVALID_OPTION_VALUE, "direction",
   1130                               chars.get());
   1131    }
   1132    return false;
   1133  }
   1134  return true;
   1135 }
   1136 
   1137 /**
   1138 * GetDirectionOption ( options )
   1139 */
   1140 bool js::temporal::GetDirectionOption(JSContext* cx, Handle<JSObject*> options,
   1141                                      Direction* result) {
   1142  // Step 1.
   1143  Rooted<JSString*> direction(cx);
   1144  if (!GetStringOption(cx, options, cx->names().direction, &direction)) {
   1145    return false;
   1146  }
   1147 
   1148  if (!direction) {
   1149    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1150                              JSMSG_TEMPORAL_MISSING_OPTION, "direction");
   1151    return false;
   1152  }
   1153 
   1154  return GetDirectionOption(cx, direction, result);
   1155 }
   1156 
   1157 template <typename T, typename... Ts>
   1158 static JSObject* MaybeUnwrapIf(JSObject* object) {
   1159  if (auto* unwrapped = object->maybeUnwrapIf<T>()) {
   1160    return unwrapped;
   1161  }
   1162  if constexpr (sizeof...(Ts) > 0) {
   1163    return MaybeUnwrapIf<Ts...>(object);
   1164  }
   1165  return nullptr;
   1166 }
   1167 
   1168 /**
   1169 * IsPartialTemporalObject ( object )
   1170 */
   1171 bool js::temporal::ThrowIfTemporalLikeObject(JSContext* cx,
   1172                                             Handle<JSObject*> object) {
   1173  // Step 1. (Handled in caller)
   1174 
   1175  // Step 2.
   1176  if (auto* unwrapped =
   1177          MaybeUnwrapIf<PlainDateObject, PlainDateTimeObject,
   1178                        PlainMonthDayObject, PlainTimeObject,
   1179                        PlainYearMonthObject, ZonedDateTimeObject>(object)) {
   1180    Rooted<Value> value(cx, ObjectValue(*object));
   1181    ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, value,
   1182                     nullptr, unwrapped->getClass()->name);
   1183    return false;
   1184  }
   1185 
   1186  Rooted<Value> property(cx);
   1187 
   1188  // Step 3.
   1189  if (!GetProperty(cx, object, object, cx->names().calendar, &property)) {
   1190    return false;
   1191  }
   1192 
   1193  // Step 4.
   1194  if (!property.isUndefined()) {
   1195    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1196                              JSMSG_TEMPORAL_UNEXPECTED_PROPERTY, "calendar");
   1197    return false;
   1198  }
   1199 
   1200  // Step 5.
   1201  if (!GetProperty(cx, object, object, cx->names().timeZone, &property)) {
   1202    return false;
   1203  }
   1204 
   1205  // Step 6.
   1206  if (!property.isUndefined()) {
   1207    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1208                              JSMSG_TEMPORAL_UNEXPECTED_PROPERTY, "timeZone");
   1209    return false;
   1210  }
   1211 
   1212  // Step 7.
   1213  return true;
   1214 }
   1215 
   1216 /**
   1217 * ToPositiveIntegerWithTruncation ( argument )
   1218 */
   1219 bool js::temporal::ToPositiveIntegerWithTruncation(JSContext* cx,
   1220                                                   Handle<Value> value,
   1221                                                   const char* name,
   1222                                                   double* result) {
   1223  // Step 1.
   1224  double number;
   1225  if (!ToIntegerWithTruncation(cx, value, name, &number)) {
   1226    return false;
   1227  }
   1228 
   1229  // Step 2.
   1230  if (number <= 0) {
   1231    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1232                              JSMSG_TEMPORAL_INVALID_NUMBER, name);
   1233    return false;
   1234  }
   1235 
   1236  // Step 3.
   1237  *result = number;
   1238  return true;
   1239 }
   1240 
   1241 /**
   1242 * ToIntegerWithTruncation ( argument )
   1243 */
   1244 bool js::temporal::ToIntegerWithTruncation(JSContext* cx, Handle<Value> value,
   1245                                           const char* name, double* result) {
   1246  // Step 1.
   1247  double number;
   1248  if (!JS::ToNumber(cx, value, &number)) {
   1249    return false;
   1250  }
   1251 
   1252  // Step 2.
   1253  if (!std::isfinite(number)) {
   1254    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1255                              JSMSG_TEMPORAL_INVALID_INTEGER, name);
   1256    return false;
   1257  }
   1258 
   1259  // Step 3.
   1260  *result = std::trunc(number) + (+0.0);  // Add zero to convert -0 to +0.
   1261  return true;
   1262 }
   1263 
   1264 /**
   1265 * GetDifferenceSettings ( operation, options, unitGroup, disallowedUnits,
   1266 * fallbackSmallestUnit, smallestLargestDefaultUnit )
   1267 */
   1268 bool js::temporal::GetDifferenceSettings(
   1269    JSContext* cx, TemporalDifference operation, Handle<JSObject*> options,
   1270    TemporalUnitGroup unitGroup, TemporalUnit smallestAllowedUnit,
   1271    TemporalUnit fallbackSmallestUnit, TemporalUnit smallestLargestDefaultUnit,
   1272    DifferenceSettings* result) {
   1273  MOZ_ASSERT(TemporalUnit::Auto < fallbackSmallestUnit &&
   1274             fallbackSmallestUnit <= smallestAllowedUnit);
   1275  MOZ_ASSERT(TemporalUnit::Auto < smallestLargestDefaultUnit &&
   1276             smallestLargestDefaultUnit <= smallestAllowedUnit);
   1277 
   1278  // Steps 1-2.
   1279  auto largestUnit = TemporalUnit::Auto;
   1280  if (!GetTemporalUnitValuedOption(cx, options, TemporalUnitKey::LargestUnit,
   1281                                   &largestUnit)) {
   1282    return false;
   1283  }
   1284 
   1285  // Step 3.
   1286  auto roundingIncrement = Increment{1};
   1287  if (!GetRoundingIncrementOption(cx, options, &roundingIncrement)) {
   1288    return false;
   1289  }
   1290 
   1291  // Step 4.
   1292  auto roundingMode = TemporalRoundingMode::Trunc;
   1293  if (!GetRoundingModeOption(cx, options, &roundingMode)) {
   1294    return false;
   1295  }
   1296 
   1297  // Step 5.
   1298  auto smallestUnit = fallbackSmallestUnit;
   1299  if (!GetTemporalUnitValuedOption(cx, options, TemporalUnitKey::SmallestUnit,
   1300                                   &smallestUnit)) {
   1301    return false;
   1302  }
   1303 
   1304  // Step 6.
   1305  if (!ValidateTemporalUnitValue(cx, TemporalUnitKey::LargestUnit, largestUnit,
   1306                                 unitGroup)) {
   1307    return false;
   1308  }
   1309 
   1310  // Step 7. (Not applicable in our implementation.)
   1311  MOZ_ASSERT(largestUnit != TemporalUnit::Unset);
   1312 
   1313  // Step 8.
   1314  if (largestUnit > smallestAllowedUnit) {
   1315    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1316                              JSMSG_TEMPORAL_INVALID_UNIT_OPTION,
   1317                              TemporalUnitToString(largestUnit), "largestUnit");
   1318    return false;
   1319  }
   1320 
   1321  // Step 9.
   1322  if (!ValidateTemporalUnitValue(cx, TemporalUnitKey::SmallestUnit,
   1323                                 smallestUnit, unitGroup)) {
   1324    return false;
   1325  }
   1326 
   1327  // Step 10. (Not applicable in our implementation.)
   1328  MOZ_ASSERT(smallestUnit != TemporalUnit::Unset);
   1329 
   1330  // Step 11.
   1331  if (smallestUnit > smallestAllowedUnit) {
   1332    JS_ReportErrorNumberASCII(
   1333        cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_INVALID_UNIT_OPTION,
   1334        TemporalUnitToString(smallestUnit), "smallestUnit");
   1335    return false;
   1336  }
   1337 
   1338  // Step 12. (Inlined call to LargerOfTwoTemporalUnits)
   1339  auto defaultLargestUnit = std::min(smallestLargestDefaultUnit, smallestUnit);
   1340 
   1341  // Step 13.
   1342  if (largestUnit == TemporalUnit::Auto) {
   1343    largestUnit = defaultLargestUnit;
   1344  }
   1345 
   1346  // Step 14.
   1347  if (largestUnit > smallestUnit) {
   1348    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1349                              JSMSG_TEMPORAL_INVALID_UNIT_RANGE);
   1350    return false;
   1351  }
   1352 
   1353  // Steps 15-16.
   1354  if (smallestUnit > TemporalUnit::Day) {
   1355    // Step 15.
   1356    auto maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit);
   1357 
   1358    // Step 16.
   1359    if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum,
   1360                                           false)) {
   1361      return false;
   1362    }
   1363  }
   1364 
   1365  // Step 17.
   1366  if (operation == TemporalDifference::Since) {
   1367    roundingMode = NegateRoundingMode(roundingMode);
   1368  }
   1369 
   1370  // Step 18.
   1371  *result = {smallestUnit, largestUnit, roundingMode, roundingIncrement};
   1372  return true;
   1373 }
   1374 
   1375 static JSObject* CreateTemporalObject(JSContext* cx, JSProtoKey key) {
   1376  Rooted<JSObject*> proto(cx, &cx->global()->getObjectPrototype());
   1377 
   1378  // The |Temporal| object is just a plain object with some "static" data
   1379  // properties and some constructor properties.
   1380  return NewTenuredObjectWithGivenProto<TemporalObject>(cx, proto);
   1381 }
   1382 
   1383 /**
   1384 * Initializes the Temporal Object and its standard built-in properties.
   1385 */
   1386 static bool TemporalClassFinish(JSContext* cx, Handle<JSObject*> temporal,
   1387                                Handle<JSObject*> proto) {
   1388  Rooted<PropertyKey> ctorId(cx);
   1389  Rooted<Value> ctorValue(cx);
   1390  auto defineProperty = [&](JSProtoKey protoKey, Handle<PropertyName*> name) {
   1391    JSObject* ctor = GlobalObject::getOrCreateConstructor(cx, protoKey);
   1392    if (!ctor) {
   1393      return false;
   1394    }
   1395 
   1396    ctorId = NameToId(name);
   1397    ctorValue.setObject(*ctor);
   1398    return DefineDataProperty(cx, temporal, ctorId, ctorValue, 0);
   1399  };
   1400 
   1401  // Add the constructor properties.
   1402  for (const auto& protoKey : {
   1403           JSProto_Duration,
   1404           JSProto_Instant,
   1405           JSProto_PlainDate,
   1406           JSProto_PlainDateTime,
   1407           JSProto_PlainMonthDay,
   1408           JSProto_PlainTime,
   1409           JSProto_PlainYearMonth,
   1410           JSProto_ZonedDateTime,
   1411       }) {
   1412    if (!defineProperty(protoKey, ClassName(protoKey, cx))) {
   1413      return false;
   1414    }
   1415  }
   1416 
   1417  // ClassName(JSProto_TemporalNow) returns "TemporalNow", so we need to handle
   1418  // it separately.
   1419  if (!defineProperty(JSProto_TemporalNow, cx->names().Now)) {
   1420    return false;
   1421  }
   1422 
   1423  return true;
   1424 }
   1425 
   1426 const JSClass TemporalObject::class_ = {
   1427    "Temporal",
   1428    JSCLASS_HAS_CACHED_PROTO(JSProto_Temporal),
   1429    JS_NULL_CLASS_OPS,
   1430    &TemporalObject::classSpec_,
   1431 };
   1432 
   1433 static const JSPropertySpec Temporal_properties[] = {
   1434    JS_STRING_SYM_PS(toStringTag, "Temporal", JSPROP_READONLY),
   1435    JS_PS_END,
   1436 };
   1437 
   1438 const ClassSpec TemporalObject::classSpec_ = {
   1439    CreateTemporalObject, nullptr, nullptr,
   1440    Temporal_properties,  nullptr, nullptr,
   1441    TemporalClassFinish,
   1442 };