Bug 1328386 - Part 6: Implement legacy constructor semantics for Intl.NumberFormat per ECMA-402, 4th edition. r=Waldo
authorAndré Bargull <andre.bargull@gmail.com>
Mon, 23 Jan 2017 08:33:43 -0800
changeset 331048 91080dad0ff8d9211948e470500a12cbb3bcf515
parent 331047 985a0c1baa892278e078fb4d796fc87a730aaeb4
child 331049 60adc4589abc3de850e379496a143dc6dc208cac
push id86156
push userryanvm@gmail.com
push dateWed, 25 Jan 2017 22:49:02 +0000
treeherdermozilla-inbound@f35fe2367a4d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo
bugs1328386
milestone54.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1328386 - Part 6: Implement legacy constructor semantics for Intl.NumberFormat per ECMA-402, 4th edition. r=Waldo
js/public/Class.h
js/src/builtin/Intl.cpp
js/src/builtin/Intl.js
js/src/jit/InlinableNatives.h
js/src/jit/MCallOptimize.cpp
js/src/tests/Intl/NumberFormat/call.js
js/src/tests/Intl/NumberFormat/unwrapping.js
js/src/tests/jstests.list
js/src/vm/GlobalObject.h
js/src/vm/SelfHosting.cpp
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -847,17 +847,17 @@ struct JSClass {
 // with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was
 // previously allowed, but is now an ES5 violation and thus unsupported.
 //
 // JSCLASS_GLOBAL_APPLICATION_SLOTS is the number of slots reserved at
 // the beginning of every global object's slots for use by the
 // application.
 #define JSCLASS_GLOBAL_APPLICATION_SLOTS 5
 #define JSCLASS_GLOBAL_SLOT_COUNT                                             \
-    (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 39)
+    (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 40)
 #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n)                                    \
     (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
 #define JSCLASS_GLOBAL_FLAGS                                                  \
     JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(0)
 #define JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(clasp)                              \
   (((clasp)->flags & JSCLASS_IS_GLOBAL)                                       \
    && JSCLASS_RESERVED_SLOTS(clasp) >= JSCLASS_GLOBAL_SLOT_COUNT)
 
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -801,17 +801,42 @@ IntlInitialize(JSContext* cx, HandleObje
     FixedInvokeArgs<3> args(cx);
 
     args[0].setObject(*obj);
     args[1].set(locales);
     args[2].set(options);
 
     RootedValue thisv(cx, NullValue());
     RootedValue ignored(cx);
-    return js::CallSelfHostedFunction(cx, initializer, thisv, args, &ignored);
+    if (!js::CallSelfHostedFunction(cx, initializer, thisv, args, &ignored))
+        return false;
+
+    MOZ_ASSERT(ignored.isUndefined(),
+               "Unexpected return value from non-legacy Intl object initializer");
+    return true;
+}
+
+static bool
+LegacyIntlInitialize(JSContext* cx, HandleObject obj, Handle<PropertyName*> initializer,
+                     HandleValue thisValue, HandleValue locales, HandleValue options,
+                     MutableHandleValue result)
+{
+    FixedInvokeArgs<4> args(cx);
+
+    args[0].setObject(*obj);
+    args[1].set(thisValue);
+    args[2].set(locales);
+    args[3].set(options);
+
+    RootedValue thisv(cx, NullValue());
+    if (!js::CallSelfHostedFunction(cx, initializer, thisv, args, result))
+        return false;
+
+    MOZ_ASSERT(result.isObject(), "Legacy Intl object initializer must return an object");
+    return true;
 }
 
 static bool
 CreateDefaultOptions(JSContext* cx, MutableHandleValue defaultOptions)
 {
     RootedObject options(cx, NewObjectWithGivenProto<PlainObject>(cx, nullptr));
     if (!options)
         return false;
@@ -1421,74 +1446,43 @@ static const JSPropertySpec numberFormat
 /**
  * 11.2.1 Intl.NumberFormat([ locales [, options]])
  *
  * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
  */
 static bool
 NumberFormat(JSContext* cx, const CallArgs& args, bool construct)
 {
-    RootedObject obj(cx);
-
-    // We're following ECMA-402 1st Edition when NumberFormat is called
-    // because of backward compatibility issues.
-    // See https://github.com/tc39/ecma402/issues/57
-    if (!construct) {
-        // ES Intl 1st ed., 11.1.2.1 step 3
-        JSObject* intl = GlobalObject::getOrCreateIntlObject(cx, cx->global());
-        if (!intl)
+    // Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).
+
+    // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
+    RootedObject proto(cx);
+    if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
+        return false;
+
+    if (!proto) {
+        proto = GlobalObject::getOrCreateNumberFormatPrototype(cx, cx->global());
+        if (!proto)
             return false;
-        RootedValue self(cx, args.thisv());
-        if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
-            // ES Intl 1st ed., 11.1.2.1 step 4
-            obj = ToObject(cx, self);
-            if (!obj)
-                return false;
-
-            // ES Intl 1st ed., 11.1.2.1 step 5
-            bool extensible;
-            if (!IsExtensible(cx, obj, &extensible))
-                return false;
-            if (!extensible)
-                return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
-        } else {
-            // ES Intl 1st ed., 11.1.2.1 step 3.a
-            construct = true;
-        }
     }
 
-    if (construct) {
-        // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
-        RootedObject proto(cx);
-        if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
-            return false;
-
-        if (!proto) {
-            proto = GlobalObject::getOrCreateNumberFormatPrototype(cx, cx->global());
-            if (!proto)
-                return false;
-        }
-
-        obj = NewObjectWithGivenProto<NumberFormatObject>(cx, proto);
-        if (!obj)
-            return false;
-
-        obj->as<NumberFormatObject>().setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT,
-                                                      PrivateValue(nullptr));
-    }
-
+    Rooted<NumberFormatObject*> numberFormat(cx);
+    numberFormat = NewObjectWithGivenProto<NumberFormatObject>(cx, proto);
+    if (!numberFormat)
+        return false;
+
+    numberFormat->setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nullptr));
+
+    RootedValue thisValue(cx, construct ? ObjectValue(*numberFormat) : args.thisv());
     RootedValue locales(cx, args.get(0));
     RootedValue options(cx, args.get(1));
 
     // Step 3.
-    if (!IntlInitialize(cx, obj, cx->names().InitializeNumberFormat, locales, options))
-        return false;
-
-    args.rval().setObject(*obj);
-    return true;
+    return LegacyIntlInitialize(cx, numberFormat, cx->names().InitializeNumberFormat, thisValue,
+                                locales, options, args.rval());
 }
 
 static bool
 NumberFormat(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return NumberFormat(cx, args, args.isConstructing());
 }
@@ -1518,17 +1512,18 @@ NumberFormatObject::finalize(FreeOp* fop
         obj->as<NumberFormatObject>().getReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT);
     if (!slot.isUndefined()) {
         if (UNumberFormat* nf = static_cast<UNumberFormat*>(slot.toPrivate()))
             unum_close(nf);
     }
 }
 
 static JSObject*
-CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
+CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global,
+                            MutableHandleObject constructor)
 {
     RootedFunction ctor(cx);
     ctor = GlobalObject::createConstructor(cx, &NumberFormat, cx->names().NumberFormat, 0);
     if (!ctor)
         return nullptr;
 
     RootedNativeObject proto(cx);
     proto = GlobalObject::createBlankPrototype<NumberFormatObject>(cx, global);
@@ -1569,27 +1564,30 @@ CreateNumberFormatPrototype(JSContext* c
     }
 #endif // defined(ICU_UNUM_HAS_FORMATDOUBLEFORFIELDS)
 
     RootedValue options(cx);
     if (!CreateDefaultOptions(cx, &options))
         return nullptr;
 
     // 11.2.1 and 11.3
-    if (!IntlInitialize(cx, proto, cx->names().InitializeNumberFormat, UndefinedHandleValue,
-                        options))
+    RootedValue thisOrResult(cx, ObjectValue(*proto));
+    if (!LegacyIntlInitialize(cx, proto, cx->names().InitializeNumberFormat, thisOrResult,
+                              UndefinedHandleValue, options, &thisOrResult))
     {
         return nullptr;
     }
+    MOZ_ASSERT(&thisOrResult.toObject() == proto);
 
     // 8.1
     RootedValue ctorValue(cx, ObjectValue(*ctor));
     if (!DefineProperty(cx, Intl, cx->names().NumberFormat, ctorValue, nullptr, nullptr, 0))
         return nullptr;
 
+    constructor.set(ctor);
     return proto;
 }
 
 bool
 js::intl_NumberFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 0);
@@ -1708,17 +1706,17 @@ NewUNumberFormatForPluralRules(JSContext
 }
 
 
 /**
  * Returns a new UNumberFormat with the locale and number formatting options
  * of the given NumberFormat.
  */
 static UNumberFormat*
-NewUNumberFormat(JSContext* cx, HandleObject numberFormat)
+NewUNumberFormat(JSContext* cx, Handle<NumberFormatObject*> numberFormat)
 {
     RootedValue value(cx);
 
     RootedObject internals(cx, GetInternals(cx, numberFormat));
     if (!internals)
        return nullptr;
 
     if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
@@ -2321,66 +2319,40 @@ bool
 js::intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 3);
     MOZ_ASSERT(args[0].isObject());
     MOZ_ASSERT(args[1].isNumber());
     MOZ_ASSERT(args[2].isBoolean());
 
-    RootedObject numberFormat(cx, &args[0].toObject());
-
-    // Obtain a UNumberFormat object, cached if possible.
-    bool isNumberFormatInstance = numberFormat->is<NumberFormatObject>();
-    UNumberFormat* nf;
-    if (isNumberFormatInstance) {
-        void* priv =
-            numberFormat->as<NumberFormatObject>().getReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT)
-                                                  .toPrivate();
-        nf = static_cast<UNumberFormat*>(priv);
-        if (!nf) {
-            nf = NewUNumberFormat(cx, numberFormat);
-            if (!nf)
-                return false;
-            numberFormat->as<NumberFormatObject>().setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT,
-                                                                   PrivateValue(nf));
-        }
-    } else {
-        // There's no good place to cache the ICU number format for an object
-        // that has been initialized as a NumberFormat but is not a
-        // NumberFormat instance. One possibility might be to add a
-        // NumberFormat instance as an internal property to each such object.
+    Rooted<NumberFormatObject*> numberFormat(cx, &args[0].toObject().as<NumberFormatObject>());
+
+    // Obtain a cached UNumberFormat object.
+    void* priv =
+        numberFormat->getReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT).toPrivate();
+    UNumberFormat* nf = static_cast<UNumberFormat*>(priv);
+    if (!nf) {
         nf = NewUNumberFormat(cx, numberFormat);
         if (!nf)
             return false;
+        numberFormat->setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT, PrivateValue(nf));
     }
 
     // Use the UNumberFormat to actually format the number.
-    double d = args[1].toNumber();
-    RootedValue result(cx);
-
-    bool success;
 #if defined(ICU_UNUM_HAS_FORMATDOUBLEFORFIELDS)
     if (args[2].toBoolean()) {
-        success = intl_FormatNumberToParts(cx, nf, d, &result);
-    } else
+        return intl_FormatNumberToParts(cx, nf, args[1].toNumber(), args.rval());
+    }
+#else
+    MOZ_ASSERT(!args[2].toBoolean(),
+               "shouldn't be doing formatToParts without an ICU that "
+               "supports it");
 #endif // defined(ICU_UNUM_HAS_FORMATDOUBLEFORFIELDS)
-    {
-        MOZ_ASSERT(!args[2].toBoolean(),
-                   "shouldn't be doing formatToParts without an ICU that "
-                   "supports it");
-        success = intl_FormatNumber(cx, nf, d, &result);
-    }
-
-    if (!isNumberFormatInstance)
-        unum_close(nf);
-    if (!success)
-        return false;
-    args.rval().set(result);
-    return true;
+    return intl_FormatNumber(cx, nf, args[1].toNumber(), args.rval());
 }
 
 
 /******************** DateTimeFormat ********************/
 
 const ClassOps DateTimeFormatObject::classOps_ = {
     nullptr, /* addProperty */
     nullptr, /* delProperty */
@@ -4370,17 +4342,18 @@ GlobalObject::initIntlObject(JSContext* 
     // Add the constructor properties, computing and returning the relevant
     // prototype objects needed below.
     RootedObject collatorProto(cx, CreateCollatorPrototype(cx, intl, global));
     if (!collatorProto)
         return false;
     RootedObject dateTimeFormatProto(cx, CreateDateTimeFormatPrototype(cx, intl, global));
     if (!dateTimeFormatProto)
         return false;
-    RootedObject numberFormatProto(cx, CreateNumberFormatPrototype(cx, intl, global));
+    RootedObject numberFormatProto(cx), numberFormat(cx);
+    numberFormatProto = CreateNumberFormatPrototype(cx, intl, global, &numberFormat);
     if (!numberFormatProto)
         return false;
 
     // The |Intl| object is fully set up now, so define the global property.
     RootedValue intlValue(cx, ObjectValue(*intl));
     if (!DefineProperty(cx, global, cx->names().Intl, intlValue, nullptr, nullptr,
                         JSPROP_RESOLVING))
     {
@@ -4393,16 +4366,17 @@ GlobalObject::initIntlObject(JSContext* 
     // Cache the various prototypes, for use in creating instances of these
     // objects with the proper [[Prototype]] as "the original value of
     // |Intl.Collator.prototype|" and similar.  For builtin classes like
     // |String.prototype| we have |JSProto_*| that enables
     // |getPrototype(JSProto_*)|, but that has global-object-property-related
     // baggage we don't need or want, so we use one-off reserved slots.
     global->setReservedSlot(COLLATOR_PROTO, ObjectValue(*collatorProto));
     global->setReservedSlot(DATE_TIME_FORMAT_PROTO, ObjectValue(*dateTimeFormatProto));
+    global->setReservedSlot(NUMBER_FORMAT, ObjectValue(*numberFormat));
     global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*numberFormatProto));
 
     // Also cache |Intl| to implement spec language that conditions behavior
     // based on values being equal to "the standard built-in |Intl| object".
     // Use |setConstructor| to correspond with |JSProto_Intl|.
     //
     // XXX We should possibly do a one-off reserved slot like above.
     global->setConstructor(JSProto_Intl, ObjectValue(*intl));
--- a/js/src/builtin/Intl.js
+++ b/js/src/builtin/Intl.js
@@ -1847,34 +1847,55 @@ function resolveNumberFormatInternals(la
 
     // The caller is responsible for associating |internalProps| with the right
     // object using |setInternalProperties|.
     return internalProps;
 }
 
 
 /**
- * Returns an object containing the NumberFormat internal properties of |obj|,
- * or throws a TypeError if |obj| isn't NumberFormat-initialized.
+ * Returns an object containing the NumberFormat internal properties of |obj|.
  */
 function getNumberFormatInternals(obj, methodName) {
+    assert(IsObject(obj), "getNumberFormatInternals called with non-object");
+    assert(IsNumberFormat(obj), "getNumberFormatInternals called with non-NumberFormat");
+
     var internals = getIntlObjectInternals(obj, "NumberFormat", methodName);
     assert(internals.type === "NumberFormat", "bad type escaped getIntlObjectInternals");
 
     // If internal properties have already been computed, use them.
     var internalProps = maybeInternalProperties(internals);
     if (internalProps)
         return internalProps;
 
     // Otherwise it's time to fully create them.
     internalProps = resolveNumberFormatInternals(internals.lazyData);
     setInternalProperties(internals, internalProps);
     return internalProps;
 }
 
+
+/**
+ * UnwrapNumberFormat(nf)
+ */
+function UnwrapNumberFormat(nf, methodName) {
+    // Step 1.
+    if ((!IsObject(nf) || !IsNumberFormat(nf)) && nf instanceof GetNumberFormatConstructor()) {
+        nf = nf[intlFallbackSymbol()];
+    }
+
+    // Step 2.
+    if (!IsObject(nf) || !IsNumberFormat(nf))
+        ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "NumberFormat", methodName, "NumberFormat");
+
+    // Step 3.
+    return nf;
+}
+
+
 /**
  * Applies digit options used for number formatting onto the intl object.
  *
  * Spec: ECMAScript Internationalization API Specification, 11.1.1.
  */
 function SetNumberFormatDigitOptions(lazyData, options, mnfdDefault, mxfdDefault) {
     // We skip Step 1 because we set the properties on a lazyData object.
 
@@ -1915,22 +1936,23 @@ function SetNumberFormatDigitOptions(laz
  * This method is complicated a moderate bit by its implementing initialization
  * as a *lazy* concept.  Everything that must happen now, does -- but we defer
  * all the work we can until the object is actually used as a NumberFormat.
  * This later work occurs in |resolveNumberFormatInternals|; steps not noted
  * here occur there.
  *
  * Spec: ECMAScript Internationalization API Specification, 11.1.1.
  */
-function InitializeNumberFormat(numberFormat, locales, options) {
-    assert(IsObject(numberFormat), "InitializeNumberFormat");
-
-    // Step 1.
-    if (isInitializedIntlObject(numberFormat))
-        ThrowTypeError(JSMSG_INTL_OBJECT_REINITED);
+function InitializeNumberFormat(numberFormat, thisValue, locales, options) {
+    assert(IsObject(numberFormat), "InitializeNumberFormat called with non-object");
+    assert(IsNumberFormat(numberFormat), "InitializeNumberFormat called with non-NumberFormat");
+
+    // Steps 1-2 (These steps are no longer required and should be removed
+    // from the spec; https://github.com/tc39/ecma402/issues/115).
+    assert(!isInitializedIntlObject(numberFormat), "numberFormat mustn't be initialized");
 
     // Step 2.
     var internals = initializeIntlObject(numberFormat);
 
     // Lazy NumberFormat data has the following structure:
     //
     //   {
     //     requestedLocales: List of locales,
@@ -2026,16 +2048,28 @@ function InitializeNumberFormat(numberFo
     var g = GetOption(options, "useGrouping", "boolean", undefined, true);
     lazyNumberFormatData.useGrouping = g;
 
     // Steps 35-36.
     //
     // We've done everything that must be done now: mark the lazy data as fully
     // computed and install it.
     setLazyData(internals, "NumberFormat", lazyNumberFormatData);
+
+    if (numberFormat !== thisValue && thisValue instanceof GetNumberFormatConstructor()) {
+        if (!IsObject(thisValue))
+            ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof thisValue);
+
+        _DefineDataProperty(thisValue, intlFallbackSymbol(), numberFormat,
+                            ATTR_NONENUMERABLE | ATTR_NONCONFIGURABLE | ATTR_NONWRITABLE);
+
+        return thisValue;
+    }
+
+    return numberFormat;
 }
 
 
 /**
  * Returns the number of decimal digits to be used for the given currency.
  *
  * Spec: ECMAScript Internationalization API Specification, 11.1.1.
  */
@@ -2122,57 +2156,62 @@ function numberFormatFormatToBind(value)
 /**
  * Returns a function bound to this NumberFormat that returns a String value
  * representing the result of calling ToNumber(value) according to the
  * effective locale and the formatting options of this NumberFormat.
  *
  * Spec: ECMAScript Internationalization API Specification, 11.3.2.
  */
 function Intl_NumberFormat_format_get() {
-    // Check "this NumberFormat object" per introduction of section 11.3.
-    var internals = getNumberFormatInternals(this, "format");
-
-    // Step 1.
+    // Steps 1-3.
+    var nf = UnwrapNumberFormat(this, "format");
+
+    var internals = getNumberFormatInternals(nf, "format");
+
+    // Step 4.
     if (internals.boundFormat === undefined) {
-        // Step 1.a.
+        // Step 4.a.
         var F = numberFormatFormatToBind;
 
-        // Step 1.b-d.
-        var bf = callFunction(FunctionBind, F, this);
+        // Steps 4.b-d.
+        var bf = callFunction(FunctionBind, F, nf);
         internals.boundFormat = bf;
     }
-    // Step 2.
+
+    // Step 5.
     return internals.boundFormat;
 }
 _SetCanonicalName(Intl_NumberFormat_format_get, "get format");
 
 
 function Intl_NumberFormat_formatToParts(value) {
-    // Step 1.
-    var nf = this;
-
-    // Steps 2-3.
+    // Steps 1-3.
+    var nf = UnwrapNumberFormat(this, "formatToParts");
+
+    // Ensure the NumberFormat internals are resolved.
     getNumberFormatInternals(nf, "formatToParts");
 
     // Step 4.
     var x = ToNumber(value);
 
     // Step 5.
     return intl_FormatNumber(nf, x, /* formatToParts = */ true);
 }
 
 
 /**
  * Returns the resolved options for a NumberFormat object.
  *
  * Spec: ECMAScript Internationalization API Specification, 11.3.3 and 11.4.
  */
 function Intl_NumberFormat_resolvedOptions() {
-    // Check "this NumberFormat object" per introduction of section 11.3.
-    var internals = getNumberFormatInternals(this, "resolvedOptions");
+    // Invoke |UnwrapNumberFormat| per introduction of section 11.3.
+    var nf = UnwrapNumberFormat(this, "resolvedOptions");
+
+    var internals = getNumberFormatInternals(nf, "resolvedOptions");
 
     var result = {
         locale: internals.locale,
         numberingSystem: internals.numberingSystem,
         style: internals.style,
         minimumIntegerDigits: internals.minimumIntegerDigits,
         minimumFractionDigits: internals.minimumFractionDigits,
         maximumFractionDigits: internals.maximumFractionDigits,
--- a/js/src/jit/InlinableNatives.h
+++ b/js/src/jit/InlinableNatives.h
@@ -24,16 +24,17 @@
     _(AtomicsAdd)                   \
     _(AtomicsSub)                   \
     _(AtomicsAnd)                   \
     _(AtomicsOr)                    \
     _(AtomicsXor)                   \
     _(AtomicsIsLockFree)            \
                                     \
     _(IntlIsCollator)               \
+    _(IntlIsNumberFormat)           \
     _(IntlIsPluralRules)            \
                                     \
     _(MathAbs)                      \
     _(MathFloor)                    \
     _(MathCeil)                     \
     _(MathRound)                    \
     _(MathClz32)                    \
     _(MathSqrt)                     \
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -106,16 +106,18 @@ IonBuilder::inlineNativeCall(CallInfo& c
       case InlinableNative::AtomicsXor:
         return inlineAtomicsBinop(callInfo, inlNative);
       case InlinableNative::AtomicsIsLockFree:
         return inlineAtomicsIsLockFree(callInfo);
 
       // Intl natives.
       case InlinableNative::IntlIsCollator:
         return inlineHasClass(callInfo, &CollatorObject::class_);
+      case InlinableNative::IntlIsNumberFormat:
+        return inlineHasClass(callInfo, &NumberFormatObject::class_);
       case InlinableNative::IntlIsPluralRules:
         return inlineHasClass(callInfo, &PluralRulesObject::class_);
 
       // Math natives.
       case InlinableNative::MathAbs:
         return inlineMathAbs(callInfo);
       case InlinableNative::MathFloor:
         return inlineMathFloor(callInfo);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/NumberFormat/call.js
@@ -0,0 +1,167 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+function IsConstructor(o) {
+  try {
+    new (new Proxy(o, {construct: () => ({})}));
+    return true;
+  } catch (e) {
+    return false;
+  }
+}
+
+function IsObject(o) {
+    return Object(o) === o;
+}
+
+function thisValues() {
+    const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsConstructor);
+
+    return [
+        // Primitive values.
+        ...[undefined, null, true, "abc", Symbol(), 123],
+
+        // Object values.
+        ...[{}, [], /(?:)/, function(){}, new Proxy({}, {})],
+
+        // Intl objects.
+        ...[].concat(...intlConstructors.map(ctor => [
+            // Instance of an Intl constructor.
+            new ctor(),
+
+            // Instance of a subclassed Intl constructor.
+            new class extends ctor {},
+
+            // Object inheriting from an Intl constructor prototype.
+            Object.create(ctor.prototype),
+
+            // Intl object not inheriting from its default prototype.
+            Object.setPrototypeOf(new ctor(), Object.prototype),
+        ])),
+    ];
+}
+
+const intlFallbackSymbol = Object.getOwnPropertySymbols(Intl.NumberFormat.call(Object.create(Intl.NumberFormat.prototype)))[0];
+
+// Invoking [[Call]] for Intl.NumberFormat returns a new instance unless called
+// with an instance inheriting from Intl.NumberFormat.prototype.
+for (let thisValue of thisValues()) {
+    let obj = Intl.NumberFormat.call(thisValue);
+
+    if (!Intl.NumberFormat.prototype.isPrototypeOf(thisValue)) {
+        assertEq(Object.is(obj, thisValue), false);
+        assertEq(obj instanceof Intl.NumberFormat, true);
+        if (IsObject(thisValue))
+            assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+    } else {
+        assertEq(Object.is(obj, thisValue), true);
+        assertEq(obj instanceof Intl.NumberFormat, true);
+        assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+    }
+}
+
+// Intl.NumberFormat uses the legacy Intl constructor compromise semantics.
+// - Test when InstanceofOperator(thisValue, %NumberFormat%) returns true.
+for (let thisValue of thisValues()) {
+    let hasInstanceCalled = false;
+    Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, {
+        value() {
+            assertEq(hasInstanceCalled, false);
+            hasInstanceCalled = true;
+            return true;
+        }, configurable: true
+    });
+    if (!IsObject(thisValue)) {
+        // A TypeError is thrown when Intl.NumberFormat tries to install the
+        // [[FallbackSymbol]] property on |thisValue|.
+        assertThrowsInstanceOf(() => Intl.NumberFormat.call(thisValue), TypeError);
+        delete Intl.NumberFormat[Symbol.hasInstance];
+    } else {
+        let obj = Intl.NumberFormat.call(thisValue);
+        delete Intl.NumberFormat[Symbol.hasInstance];
+        assertEq(Object.is(obj, thisValue), true);
+        assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+    }
+    assertEq(hasInstanceCalled, true);
+}
+// - Test when InstanceofOperator(thisValue, %NumberFormat%) returns false.
+for (let thisValue of thisValues()) {
+    let hasInstanceCalled = false;
+    Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, {
+        value() {
+            assertEq(hasInstanceCalled, false);
+            hasInstanceCalled = true;
+            return false;
+        }, configurable: true
+    });
+    let obj = Intl.NumberFormat.call(thisValue);
+    delete Intl.NumberFormat[Symbol.hasInstance];
+    assertEq(Object.is(obj, thisValue), false);
+    assertEq(obj instanceof Intl.NumberFormat, true);
+    if (IsObject(thisValue))
+        assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+    assertEq(hasInstanceCalled, true);
+}
+
+// Throws an error when attempting to install [[FallbackSymbol]] twice.
+{
+    let thisValue = Object.create(Intl.NumberFormat.prototype);
+    assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+
+    assertEq(Intl.NumberFormat.call(thisValue), thisValue);
+    assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+
+    assertThrowsInstanceOf(() => Intl.NumberFormat.call(thisValue), TypeError);
+    assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+}
+
+// Throws an error when the thisValue is non-extensible.
+{
+    let thisValue = Object.create(Intl.NumberFormat.prototype);
+    Object.preventExtensions(thisValue);
+
+    assertThrowsInstanceOf(() => Intl.NumberFormat.call(thisValue), TypeError);
+    assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+}
+
+// [[FallbackSymbol]] is installed as a frozen property holding an Intl.NumberFormat instance.
+{
+    let thisValue = Object.create(Intl.NumberFormat.prototype);
+    Intl.NumberFormat.call(thisValue);
+
+    let desc = Object.getOwnPropertyDescriptor(thisValue, intlFallbackSymbol);
+    assertEq(desc !== undefined, true);
+    assertEq(desc.writable, false);
+    assertEq(desc.enumerable, false);
+    assertEq(desc.configurable, false);
+    assertEq(desc.value instanceof Intl.NumberFormat, true);
+}
+
+// Ensure [[FallbackSymbol]] is installed last by changing the [[Prototype]]
+// during initialization.
+{
+    let thisValue = {};
+    let options = {
+        get useGrouping() {
+            Object.setPrototypeOf(thisValue, Intl.NumberFormat.prototype);
+            return false;
+        }
+    };
+    let obj = Intl.NumberFormat.call(thisValue, undefined, options);
+    assertEq(Object.is(obj, thisValue), true);
+    assertEqArray(Object.getOwnPropertySymbols(thisValue), [intlFallbackSymbol]);
+}
+{
+    let thisValue = Object.create(Intl.NumberFormat.prototype);
+    let options = {
+        get useGrouping() {
+            Object.setPrototypeOf(thisValue, Object.prototype);
+            return false;
+        }
+    };
+    let obj = Intl.NumberFormat.call(thisValue, undefined, options);
+    assertEq(Object.is(obj, thisValue), false);
+    assertEqArray(Object.getOwnPropertySymbols(thisValue), []);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/NumberFormat/unwrapping.js
@@ -0,0 +1,226 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Test UnwrapNumberFormat operation.
+
+const numberFormatFunctions = [];
+numberFormatFunctions.push(Intl.NumberFormat.prototype.resolvedOptions);
+numberFormatFunctions.push(Object.getOwnPropertyDescriptor(Intl.NumberFormat.prototype, "format").get);
+// "formatToParts" isn't yet enabled by default.
+if ("formatToParts" in Intl.NumberFormat.prototype)
+    numberFormatFunctions.push(Intl.NumberFormat.prototype.formatToParts);
+
+function IsConstructor(o) {
+  try {
+    new (new Proxy(o, {construct: () => ({})}));
+    return true;
+  } catch (e) {
+    return false;
+  }
+}
+
+function IsObject(o) {
+    return Object(o) === o;
+}
+
+function intlObjects(ctor) {
+    return [
+        // Instance of an Intl constructor.
+        new ctor(),
+
+        // Instance of a subclassed Intl constructor.
+        new class extends ctor {},
+
+        // Intl object not inheriting from its default prototype.
+        Object.setPrototypeOf(new ctor(), Object.prototype),
+    ];
+}
+
+function thisValues(C) {
+    const intlConstructors = Object.getOwnPropertyNames(Intl).map(name => Intl[name]).filter(IsConstructor);
+
+    return [
+        // Primitive values.
+        ...[undefined, null, true, "abc", Symbol(), 123],
+
+        // Object values.
+        ...[{}, [], /(?:)/, function(){}, new Proxy({}, {})],
+
+        // Intl objects.
+        ...[].concat(...intlConstructors.filter(ctor => ctor !== C).map(intlObjects)),
+
+        // Object inheriting from an Intl constructor prototype.
+        ...intlConstructors.map(ctor => Object.create(ctor.prototype)),
+    ];
+}
+
+const intlFallbackSymbol = Object.getOwnPropertySymbols(Intl.NumberFormat.call(Object.create(Intl.NumberFormat.prototype)))[0];
+
+// Test Intl.NumberFormat.prototype methods.
+for (let numberFormatFunction of numberFormatFunctions) {
+    // Test a TypeError is thrown when the this-value isn't an initialized
+    // Intl.NumberFormat instance.
+    for (let thisValue of thisValues(Intl.NumberFormat)) {
+        assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError);
+    }
+
+    // And test no error is thrown for initialized Intl.NumberFormat instances.
+    for (let thisValue of intlObjects(Intl.NumberFormat)) {
+        numberFormatFunction.call(thisValue);
+    }
+
+    // Manually add [[FallbackSymbol]] to objects and then repeat the tests from above.
+    for (let thisValue of thisValues(Intl.NumberFormat)) {
+        assertThrowsInstanceOf(() => numberFormatFunction.call({
+            __proto__: Intl.NumberFormat.prototype,
+            [intlFallbackSymbol]: thisValue,
+        }), TypeError);
+    }
+
+    for (let thisValue of intlObjects(Intl.NumberFormat)) {
+        numberFormatFunction.call({
+            __proto__: Intl.NumberFormat.prototype,
+            [intlFallbackSymbol]: thisValue,
+        });
+    }
+
+    // Ensure [[FallbackSymbol]] isn't retrieved for Intl.NumberFormat instances.
+    for (let thisValue of intlObjects(Intl.NumberFormat)) {
+        Object.defineProperty(thisValue, intlFallbackSymbol, {
+            get() { assertEq(false, true); }
+        });
+        numberFormatFunction.call(thisValue);
+    }
+
+    // Ensure [[FallbackSymbol]] is only retrieved for objects inheriting from Intl.NumberFormat.prototype.
+    for (let thisValue of thisValues(Intl.NumberFormat)) {
+        if (!IsObject(thisValue) || Intl.NumberFormat.prototype.isPrototypeOf(thisValue))
+            continue;
+        Object.defineProperty(thisValue, intlFallbackSymbol, {
+            get() { assertEq(false, true); }
+        });
+        assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError);
+    }
+
+    // Repeat the test from above, but also change Intl.NumberFormat[@@hasInstance]
+    // so it always returns |null|.
+    for (let thisValue of thisValues(Intl.NumberFormat)) {
+        let hasInstanceCalled = false, symbolGetterCalled = false;
+        Object.defineProperty(Intl.NumberFormat, Symbol.hasInstance, {
+            value() {
+                assertEq(hasInstanceCalled, false);
+                hasInstanceCalled = true;
+                return true;
+            }, configurable: true
+        });
+        let isUndefinedOrNull = thisValue !== undefined || thisValue !== null;
+        let symbolHolder;
+        if (!isUndefinedOrNull) {
+            symbolHolder = IsObject(thisValue) ? thisValue : Object.getPrototypeOf(thisValue);
+            Object.defineProperty(symbolHolder, intlFallbackSymbol, {
+                get() {
+                    assertEq(symbolGetterCalled, false);
+                    symbolGetterCalled = true;
+                    return null;
+                }, configurable: true
+            });
+        }
+
+        assertThrowsInstanceOf(() => numberFormatFunction.call(thisValue), TypeError);
+
+        delete Intl.NumberFormat[Symbol.hasInstance];
+        if (!isUndefinedOrNull && !IsObject(thisValue))
+            delete symbolHolder[intlFallbackSymbol];
+
+        assertEq(hasInstanceCalled, true);
+        assertEq(symbolGetterCalled, !isUndefinedOrNull);
+    }
+}
+
+// Test format() returns the correct result for objects initialized as Intl.NumberFormat instances.
+{
+    // An actual Intl.NumberFormat instance.
+    let numberFormat = new Intl.NumberFormat();
+
+    // An object initialized as a NumberFormat instance.
+    let thisValue = Object.create(Intl.NumberFormat.prototype);
+    Intl.NumberFormat.call(thisValue);
+
+    // Object with [[FallbackSymbol]] set to NumberFormat instance.
+    let fakeObj = {
+        __proto__: Intl.NumberFormat.prototype,
+        [intlFallbackSymbol]: numberFormat,
+    };
+
+    for (let number of [0, 1, 1.5, Infinity, NaN]) {
+        let expected = numberFormat.format(number);
+        assertEq(thisValue.format(number), expected);
+        assertEq(thisValue[intlFallbackSymbol].format(number), expected);
+        assertEq(fakeObj.format(number), expected);
+    }
+}
+
+// Test formatToParts() returns the correct result for objects initialized as Intl.NumberFormat instances.
+if ("formatToParts" in Intl.NumberFormat.prototype) {
+    // An actual Intl.NumberFormat instance.
+    let numberFormat = new Intl.NumberFormat();
+
+    // An object initialized as a NumberFormat instance.
+    let thisValue = Object.create(Intl.NumberFormat.prototype);
+    Intl.NumberFormat.call(thisValue);
+
+    // Object with [[FallbackSymbol]] set to NumberFormat instance.
+    let fakeObj = {
+        __proto__: Intl.NumberFormat.prototype,
+        [intlFallbackSymbol]: numberFormat,
+    };
+
+    function assertEqParts(actual, expected) {
+        assertEq(actual.length, expected.length, "parts count mismatch");
+        for (var i = 0; i < expected.length; i++) {
+            assertEq(actual[i].type, expected[i].type, "type mismatch at " + i);
+            assertEq(actual[i].value, expected[i].value, "value mismatch at " + i);
+        }
+    }
+
+    for (let number of [0, 1, 1.5, Infinity, NaN]) {
+        let expected = numberFormat.formatToParts(number);
+        assertEqParts(thisValue.formatToParts(number), expected);
+        assertEqParts(thisValue[intlFallbackSymbol].formatToParts(number), expected);
+        assertEqParts(fakeObj.formatToParts(number), expected);
+    }
+}
+
+// Test resolvedOptions() returns the same results.
+{
+    // An actual Intl.NumberFormat instance.
+    let numberFormat = new Intl.NumberFormat();
+
+    // An object initialized as a NumberFormat instance.
+    let thisValue = Object.create(Intl.NumberFormat.prototype);
+    Intl.NumberFormat.call(thisValue);
+
+    // Object with [[FallbackSymbol]] set to NumberFormat instance.
+    let fakeObj = {
+        __proto__: Intl.NumberFormat.prototype,
+        [intlFallbackSymbol]: numberFormat,
+    };
+
+    function assertEqOptions(actual, expected) {
+        actual = Object.entries(actual);
+        expected = Object.entries(expected);
+
+        assertEq(actual.length, expected.length, "options count mismatch");
+        for (var i = 0; i < expected.length; i++) {
+            assertEq(actual[i][0], expected[i][0], "key mismatch at " + i);
+            assertEq(actual[i][1], expected[i][1], "value mismatch at " + i);
+        }
+    }
+
+    let expected = numberFormat.resolvedOptions();
+    assertEqOptions(thisValue.resolvedOptions(), expected);
+    assertEqOptions(thisValue[intlFallbackSymbol].resolvedOptions(), expected);
+    assertEqOptions(fakeObj.resolvedOptions(), expected);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -55,16 +55,17 @@ skip script test262/ch10/10.6/10.6-14-b-
 skip script test262/ch10/10.6/10.6-14-b-4-s.js
 skip script test262/ch10/10.6/10.6-13-b-1-s.js
 skip script test262/ch10/10.6/10.6-13-b-2-s.js
 
 # ES2017 Intl legacy constructor semantics changes made these tests invalid
 # (bug 1328386).
 skip script test262/intl402/ch10/10.1/10.1.1_1.js
 skip script test262/intl402/ch10/10.1/10.1.2_a.js
+skip script test262/intl402/ch11/11.1/11.1.1_1.js
 
 #######################################################################
 # Tests disabled due to jstest limitations wrt imported test262 tests #
 #######################################################################
 
 # These tests are disabled because jstest doesn't understand @negative (without
 # a pattern) yet.
 skip script test262/ch07/7.2/S7.2_A5_T1.js
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -96,16 +96,17 @@ class GlobalObject : public NativeObject
         STAR_GENERATOR_OBJECT_PROTO,
         STAR_GENERATOR_FUNCTION_PROTO,
         STAR_GENERATOR_FUNCTION,
         ASYNC_FUNCTION_PROTO,
         ASYNC_FUNCTION,
         MAP_ITERATOR_PROTO,
         SET_ITERATOR_PROTO,
         COLLATOR_PROTO,
+        NUMBER_FORMAT,
         NUMBER_FORMAT_PROTO,
         DATE_TIME_FORMAT_PROTO,
         PLURAL_RULES_PROTO,
         MODULE_PROTO,
         IMPORT_ENTRY_PROTO,
         EXPORT_ENTRY_PROTO,
         REGEXP_STATICS,
         WARNED_ONCE_FLAGS,
@@ -478,16 +479,22 @@ class GlobalObject : public NativeObject
         return &getPrototype(JSProto_Iterator).toObject();
     }
 
     static JSObject*
     getOrCreateCollatorPrototype(JSContext* cx, Handle<GlobalObject*> global) {
         return getOrCreateObject(cx, global, COLLATOR_PROTO, initIntlObject);
     }
 
+    static JSFunction*
+    getOrCreateNumberFormatConstructor(JSContext* cx, Handle<GlobalObject*> global) {
+        JSObject* obj = getOrCreateObject(cx, global, NUMBER_FORMAT, initIntlObject);
+        return obj ? &obj->as<JSFunction>() : nullptr;
+    }
+
     static JSObject*
     getOrCreateNumberFormatPrototype(JSContext* cx, Handle<GlobalObject*> global) {
         return getOrCreateObject(cx, global, NUMBER_FORMAT_PROTO, initIntlObject);
     }
 
     static JSObject*
     getOrCreateDateTimeFormatPrototype(JSContext* cx, Handle<GlobalObject*> global) {
         return getOrCreateObject(cx, global, DATE_TIME_FORMAT_PROTO, initIntlObject);
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -1851,16 +1851,33 @@ intrinsic_RuntimeDefaultLocale(JSContext
     RootedString jslocale(cx, JS_NewStringCopyZ(cx, locale));
     if (!jslocale)
         return false;
 
     args.rval().setString(jslocale);
     return true;
 }
 
+using GetOrCreateIntlConstructor = JSFunction* (*)(JSContext*, Handle<GlobalObject*>);
+
+template <GetOrCreateIntlConstructor getOrCreateIntlConstructor>
+static bool
+intrinsic_GetBuiltinIntlConstructor(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 0);
+
+    JSFunction* constructor = getOrCreateIntlConstructor(cx, cx->global());
+    if (!constructor)
+        return false;
+
+    args.rval().setObject(*constructor);
+    return true;
+}
+
 static bool
 intrinsic_AddContentTelemetry(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 2);
 
     int id = args[0].toInt32();
     MOZ_ASSERT(id < JS_TELEMETRY_END);
@@ -2480,19 +2497,25 @@ static const JSFunctionSpec intrinsic_fu
     JS_FN("intl_patternForSkeleton", intl_patternForSkeleton, 2,0),
     JS_FN("intl_PluralRules_availableLocales", intl_PluralRules_availableLocales, 0,0),
     JS_FN("intl_GetPluralCategories", intl_GetPluralCategories, 2, 0),
     JS_FN("intl_SelectPluralRule", intl_SelectPluralRule, 2,0),
 
     JS_INLINABLE_FN("IsCollator",
                     intrinsic_IsInstanceOfBuiltin<CollatorObject>, 1,0,
                     IntlIsCollator),
+    JS_INLINABLE_FN("IsNumberFormat",
+                    intrinsic_IsInstanceOfBuiltin<NumberFormatObject>, 1,0,
+                    IntlIsNumberFormat),
     JS_INLINABLE_FN("IsPluralRules",
                     intrinsic_IsInstanceOfBuiltin<PluralRulesObject>, 1,0,
                     IntlIsPluralRules),
+    JS_FN("GetNumberFormatConstructor",
+          intrinsic_GetBuiltinIntlConstructor<GlobalObject::getOrCreateNumberFormatConstructor>,
+          0,0),
 
     JS_INLINABLE_FN("IsRegExpObject",
                     intrinsic_IsInstanceOfBuiltin<RegExpObject>, 1,0,
                     IsRegExpObject),
     JS_FN("CallRegExpMethodIfWrapped",
           CallNonGenericSelfhostedMethod<Is<RegExpObject>>, 2,0),
     JS_INLINABLE_FN("RegExpMatcher", RegExpMatcher, 4,0,
                     RegExpMatcher),