Bug 1386534 - Use a C++ version of SpeciesConstructor when calling from C++. r=anba,rs=jonco
authorTill Schneidereit <till@tillschneidereit.net>
Tue, 01 Aug 2017 16:14:05 +0200
changeset 429411 6e7763254023277b9b5526166e7f34c6b1f1802c
parent 429410 0298cb04eb7a794501556f2dd8b64f27736d300a
child 429412 94b2ac8be5db8e0792675a133934ecf92648a450
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersanba, jonco
bugs1386534
milestone57.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 1386534 - Use a C++ version of SpeciesConstructor when calling from C++. r=anba,rs=jonco MozReview-Commit-ID: qyU0pqt0tV
js/src/builtin/Promise.cpp
js/src/builtin/Promise.js
js/src/jsapi.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/vm/TypedArrayObject.cpp
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -2336,17 +2336,28 @@ PromiseObject::unforgeableResolve(JSCont
 {
     RootedObject promiseCtor(cx, JS::GetPromiseConstructor(cx));
     if (!promiseCtor)
         return nullptr;
     RootedValue cVal(cx, ObjectValue(*promiseCtor));
     return CommonStaticResolveRejectImpl(cx, cVal, value, ResolveMode);
 }
 
-// ES2016, 25.4.4.6, implemented in Promise.js.
+/**
+ * ES2016, 25.4.4.6 get Promise [ @@species ]
+ */
+static bool
+Promise_static_species(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    // Step 1: Return the this value.
+    args.rval().set(args.thisv());
+    return true;
+}
 
 // ES2016, 25.4.5.1, implemented in Promise.js.
 
 static PromiseReactionRecord*
 NewReactionRecord(JSContext* cx, HandleObject resultPromise, HandleValue onFulfilled,
                   HandleValue onRejected, HandleObject resolve, HandleObject reject,
                   HandleObject incumbentGlobalObject)
 {
@@ -2368,16 +2379,22 @@ NewReactionRecord(JSContext* cx, HandleO
     reaction->setFixedSlot(ReactionRecordSlot_Resolve, ObjectOrNullValue(resolve));
     reaction->setFixedSlot(ReactionRecordSlot_Reject, ObjectOrNullValue(reject));
     reaction->setFixedSlot(ReactionRecordSlot_IncumbentGlobalObject,
                            ObjectOrNullValue(incumbentGlobalObject));
 
     return reaction;
 }
 
+static bool
+IsPromiseSpecies(JSContext* cx, JSFunction* species)
+{
+    return species->maybeNative() == Promise_static_species;
+}
+
 // ES2016, 25.4.5.3., steps 3-5.
 MOZ_MUST_USE bool
 js::OriginalPromiseThen(JSContext* cx, Handle<PromiseObject*> promise,
                         HandleValue onFulfilled, HandleValue onRejected,
                         MutableHandleObject dependent, bool createDependent)
 {
     RootedObject promiseObj(cx, promise);
     if (promise->compartment() != cx->compartment()) {
@@ -2386,20 +2403,19 @@ js::OriginalPromiseThen(JSContext* cx, H
     }
 
     RootedObject resultPromise(cx);
     RootedObject resolve(cx);
     RootedObject reject(cx);
 
     if (createDependent) {
         // Step 3.
-        RootedValue ctorVal(cx);
-        if (!SpeciesConstructor(cx, promiseObj, JSProto_Promise, &ctorVal))
+        RootedObject C(cx, SpeciesConstructor(cx, promiseObj, JSProto_Promise, IsPromiseSpecies));
+        if (!C)
             return false;
-        RootedObject C(cx, &ctorVal.toObject());
 
         // Step 4.
         if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, true))
             return false;
     }
 
     // Step 5.
     if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultPromise, resolve, reject))
@@ -3034,21 +3050,20 @@ BlockOnPromise(JSContext* cx, HandleValu
 
     if (promiseObj && promiseObj->is<PromiseObject>() && IsNativeFunction(thenVal, Promise_then)) {
         // |promise| is an unwrapped Promise, and |then| is the original
         // |Promise.prototype.then|, inline it here.
         // 25.4.5.3., step 3.
         RootedObject PromiseCtor(cx);
         if (!GetBuiltinConstructor(cx, JSProto_Promise, &PromiseCtor))
             return false;
-        RootedValue PromiseCtorVal(cx, ObjectValue(*PromiseCtor));
-        RootedValue CVal(cx);
-        if (!SpeciesConstructor(cx, promiseObj, PromiseCtorVal, &CVal))
+
+        RootedObject C(cx, SpeciesConstructor(cx, PromiseCtor, JSProto_Promise, IsPromiseSpecies));
+        if (!C)
             return false;
-        RootedObject C(cx, &CVal.toObject());
 
         RootedObject resultPromise(cx, blockedPromise_);
         RootedObject resolveFun(cx);
         RootedObject rejectFun(cx);
 
         // By default, the blocked promise is added as an extra entry to the
         // rejected promises list.
         bool addToDependent = true;
@@ -3612,17 +3627,17 @@ static const JSFunctionSpec promise_stat
     JS_FN("all", Promise_static_all, 1, 0),
     JS_FN("race", Promise_static_race, 1, 0),
     JS_FN("reject", Promise_reject, 1, 0),
     JS_FN("resolve", Promise_static_resolve, 1, 0),
     JS_FS_END
 };
 
 static const JSPropertySpec promise_static_properties[] = {
-    JS_SELF_HOSTED_SYM_GET(species, "Promise_static_get_species", 0),
+    JS_SYM_GET(species, Promise_static_species, 0),
     JS_PS_END
 };
 
 static const ClassSpec PromiseObjectClassSpec = {
     GenericCreateConstructor<PromiseConstructor, 1, gc::AllocKind::FUNCTION>,
     CreatePromisePrototype,
     promise_static_methods,
     promise_static_properties,
--- a/js/src/builtin/Promise.js
+++ b/js/src/builtin/Promise.js
@@ -1,16 +1,9 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-// ES6, 25.4.4.6.
-function Promise_static_get_species() {
-    // Step 1.
-    return this;
-}
-_SetCanonicalName(Promise_static_get_species, "get [Symbol.species]");
-
 // ES6, 25.4.5.1.
 function Promise_catch(onRejected) {
     // Steps 1-2.
     return callContentFunction(this.then, this, undefined, onRejected);
 }
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -2273,16 +2273,19 @@ inline int CheckIsSetterOp(JSSetterOp op
  * them.
  */
 #define JS_PSG(name, getter, flags) \
     JS_PS_ACCESSOR_SPEC(name, JSNATIVE_WRAPPER(getter), JSNATIVE_WRAPPER(nullptr), flags, \
                         JSPROP_SHARED)
 #define JS_PSGS(name, getter, setter, flags) \
     JS_PS_ACCESSOR_SPEC(name, JSNATIVE_WRAPPER(getter), JSNATIVE_WRAPPER(setter), flags, \
                          JSPROP_SHARED)
+#define JS_SYM_GET(symbol, getter, flags) \
+    JS_PS_ACCESSOR_SPEC(reinterpret_cast<const char*>(uint32_t(::JS::SymbolCode::symbol) + 1), \
+                        JSNATIVE_WRAPPER(getter), JSNATIVE_WRAPPER(nullptr), flags, JSPROP_SHARED)
 #define JS_SELF_HOSTED_GET(name, getterName, flags) \
     JS_PS_ACCESSOR_SPEC(name, SELFHOSTED_WRAPPER(getterName), JSNATIVE_WRAPPER(nullptr), flags, \
                          JSPROP_SHARED | JSPROP_GETTER)
 #define JS_SELF_HOSTED_GETSET(name, getterName, setterName, flags) \
     JS_PS_ACCESSOR_SPEC(name, SELFHOSTED_WRAPPER(getterName), SELFHOSTED_WRAPPER(setterName), \
                          flags, JSPROP_SHARED | JSPROP_GETTER | JSPROP_SETTER)
 #define JS_SELF_HOSTED_SYM_GET(symbol, getterName, flags) \
     JS_PS_ACCESSOR_SPEC(reinterpret_cast<const char*>(uint32_t(::JS::SymbolCode::symbol) + 1), \
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -4051,44 +4051,87 @@ JSObject::constructorDisplayAtom(JSConte
 JSAtom*
 JSObject::maybeConstructorDisplayAtom() const
 {
     if (hasLazyGroup())
         return nullptr;
     return displayAtomFromObjectGroup(*group());
 }
 
-bool
-js::SpeciesConstructor(JSContext* cx, HandleObject obj, HandleValue defaultCtor, MutableHandleValue pctor)
+// ES 2016 7.3.20.
+MOZ_MUST_USE JSObject*
+js::SpeciesConstructor(JSContext* cx, HandleObject obj, HandleObject defaultCtor,
+                       bool (*isDefaultSpecies)(JSContext*, JSFunction*))
 {
-    HandlePropertyName shName = cx->names().SpeciesConstructor;
-    RootedValue func(cx);
-    if (!GlobalObject::getSelfHostedFunction(cx, cx->global(), shName, shName, 2, &func))
-        return false;
-
-    FixedInvokeArgs<2> args(cx);
-
-    args[0].setObject(*obj);
-    args[1].set(defaultCtor);
-
-    if (!Call(cx, func, UndefinedHandleValue, args, pctor))
-        return false;
-
-    pctor.set(args.rval());
-    return true;
+    // Step 1 (implicit).
+
+    // Fast-path for steps 2 - 8. Applies if all of the following conditions
+    // are met:
+    // - obj.constructor can be retrieved without side-effects.
+    // - obj.constructor[[@@species]] can be retrieved without side-effects.
+    // - obj.constructor[[@@species]] is the builtin's original @@species
+    //   getter.
+    RootedValue ctor(cx);
+    bool ctorGetSucceeded = GetPropertyPure(cx, obj, NameToId(cx->names().constructor),
+                                            ctor.address());
+    if (ctorGetSucceeded && ctor.isObject() && &ctor.toObject() == defaultCtor) {
+        RootedObject ctorObj(cx, &ctor.toObject());
+        RootedId speciesId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().species));
+        JSFunction* getter;
+        if (GetGetterPure(cx, ctorObj, speciesId, &getter) && getter &&
+            isDefaultSpecies(cx, getter))
+        {
+            return defaultCtor;
+        }
+    }
+
+    // Step 2.
+    if (!ctorGetSucceeded && !GetProperty(cx, obj, obj, cx->names().constructor, &ctor))
+        return nullptr;
+
+    // Step 3.
+    if (ctor.isUndefined())
+        return defaultCtor;
+
+    // Step 4.
+    if (!ctor.isObject()) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
+                                  "object's 'constructor' property");
+        return nullptr;
+    }
+
+    // Step 5.
+    RootedObject ctorObj(cx, &ctor.toObject());
+    RootedValue s(cx);
+    RootedId speciesId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().species));
+    if (!GetProperty(cx, ctorObj, ctor, speciesId, &s))
+        return nullptr;
+
+    // Step 6.
+    if (s.isNullOrUndefined())
+        return defaultCtor;
+
+    // Step 7.
+    if (IsConstructor(s))
+        return &s.toObject();
+
+    // Step 8.
+    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_CONSTRUCTOR,
+                              "[Symbol.species] property of object's constructor");
+    return nullptr;
 }
 
-bool
+MOZ_MUST_USE JSObject*
 js::SpeciesConstructor(JSContext* cx, HandleObject obj, JSProtoKey ctorKey,
-                       MutableHandleValue pctor)
+                       bool (*isDefaultSpecies)(JSContext*, JSFunction*))
 {
     if (!GlobalObject::ensureConstructor(cx, cx->global(), ctorKey))
-        return false;
-    RootedValue defaultCtor(cx, cx->global()->getConstructor(ctorKey));
-    return SpeciesConstructor(cx, obj, defaultCtor, pctor);
+        return nullptr;
+    RootedObject defaultCtor(cx, &cx->global()->getConstructor(ctorKey).toObject());
+    return SpeciesConstructor(cx, obj, defaultCtor, isDefaultSpecies);
 }
 
 bool
 js::Unbox(JSContext* cx, HandleObject obj, MutableHandleValue vp)
 {
     if (MOZ_UNLIKELY(obj->is<ProxyObject>()))
         return Proxy::boxedValue_unbox(cx, obj, vp);
 
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -1436,21 +1436,23 @@ FreezeObject(JSContext* cx, HandleObject
 
 /*
  * ES6 rev 29 (6 Dec 2014) 7.3.14. Code shared by Object.isSealed and
  * Object.isFrozen.
  */
 extern bool
 TestIntegrityLevel(JSContext* cx, HandleObject obj, IntegrityLevel level, bool* resultp);
 
-extern bool
-SpeciesConstructor(JSContext* cx, HandleObject obj, HandleValue defaultCtor, MutableHandleValue pctor);
+extern MOZ_MUST_USE JSObject*
+SpeciesConstructor(JSContext* cx, HandleObject obj, HandleObject defaultCtor,
+                   bool (*isDefaultSpecies)(JSContext*, JSFunction*));
 
-extern bool
-SpeciesConstructor(JSContext* cx, HandleObject obj, JSProtoKey ctorKey, MutableHandleValue pctor);
+extern MOZ_MUST_USE JSObject*
+SpeciesConstructor(JSContext* cx, HandleObject obj, JSProtoKey ctorKey,
+                   bool (*isDefaultSpecies)(JSContext*, JSFunction*));
 
 extern bool
 GetObjectFromIncumbentGlobal(JSContext* cx, MutableHandleObject obj);
 
 
 #ifdef DEBUG
 inline bool
 IsObjectValueInCompartment(const Value& v, JSCompartment* comp)
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -1062,98 +1062,57 @@ TypedArrayObjectTemplate<T>::AllocateArr
     // ES 2016 draft Mar 25, 2016 24.1.1.1 steps 1 (remaining part), 2-6.
     if (!maybeCreateArrayBuffer(cx, count, unit, proto, buffer))
         return false;
 
     return true;
 }
 
 static bool
-IsArrayBufferConstructor(const Value& v)
-{
-    return v.isObject() &&
-           v.toObject().is<JSFunction>() &&
-           v.toObject().as<JSFunction>().isNative() &&
-           v.toObject().as<JSFunction>().native() == ArrayBufferObject::class_constructor;
-}
-
-static bool
-IsArrayBufferSpecies(JSContext* cx, HandleObject origBuffer)
+IsArrayBufferSpecies(JSContext* cx, JSFunction* species)
 {
-    RootedValue ctor(cx);
-    if (!GetPropertyPure(cx, origBuffer, NameToId(cx->names().constructor), ctor.address()))
-        return false;
-
-    if (!IsArrayBufferConstructor(ctor))
-        return false;
-
-    RootedObject ctorObj(cx, &ctor.toObject());
-    RootedId speciesId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().species));
-    JSFunction* getter;
-    if (!GetGetterPure(cx, ctorObj, speciesId, &getter))
-        return false;
-
-    if (!getter)
-        return false;
-
-    return IsSelfHostedFunctionWithName(getter, cx->names().ArrayBufferSpecies);
+    return IsSelfHostedFunctionWithName(species, cx->names().ArrayBufferSpecies);
 }
 
-static bool
+static JSObject*
 GetSpeciesConstructor(JSContext* cx, HandleObject obj, bool isWrapped,
-                      SpeciesConstructorOverride override, MutableHandleValue ctor)
+                      SpeciesConstructorOverride override)
 {
     if (!GlobalObject::ensureConstructor(cx, cx->global(), JSProto_ArrayBuffer))
-        return false;
-    RootedValue defaultCtor(cx, cx->global()->getConstructor(JSProto_ArrayBuffer));
+        return nullptr;
+    RootedObject defaultCtor(cx, &cx->global()->getConstructor(JSProto_ArrayBuffer).toObject());
 
     // Use the current global's ArrayBuffer if the override is set.
-    if (override == SpeciesConstructorOverride::ArrayBuffer) {
-        ctor.set(defaultCtor);
-        return true;
-    }
-
-    if (!isWrapped) {
-        // As an optimization, avoid calling into self-hosted code if |obj|'s
-        // constructor is the built-in ArrayBuffer and the constructor's
-        // species property is the original ArrayBuffer[@@species] function.
-        if (IsArrayBufferSpecies(cx, obj))
-            ctor.set(defaultCtor);
-        else if (!SpeciesConstructor(cx, obj, defaultCtor, ctor))
-            return false;
-
-        return true;
-    }
+    if (override == SpeciesConstructorOverride::ArrayBuffer)
+        return defaultCtor;
 
     RootedObject wrappedObj(cx, obj);
-    if (!cx->compartment()->wrap(cx, &wrappedObj))
-        return false;
+    if (isWrapped && !cx->compartment()->wrap(cx, &wrappedObj))
+        return nullptr;
 
-    if (!SpeciesConstructor(cx, wrappedObj, defaultCtor, ctor))
-        return false;
-
-    return true;
+    return SpeciesConstructor(cx, wrappedObj, defaultCtor, IsArrayBufferSpecies);
 }
 
 // ES 2017 draft rev 8633ffd9394b203b8876bb23cb79aff13eb07310 24.1.1.4.
 template<typename T>
 /* static */ bool
 TypedArrayObjectTemplate<T>::CloneArrayBufferNoCopy(JSContext* cx,
                                                     Handle<ArrayBufferObjectMaybeShared*> srcBuffer,
                                                     bool isWrapped, uint32_t srcByteOffset,
                                                     uint32_t srcLength,
                                                     SpeciesConstructorOverride override,
                                                     MutableHandle<ArrayBufferObject*> buffer)
 {
     // Step 1 (skipped).
 
     // Step 2.a.
-    RootedValue cloneCtor(cx);
-    if (!GetSpeciesConstructor(cx, srcBuffer, isWrapped, override, &cloneCtor))
+    JSObject* ctorObj = GetSpeciesConstructor(cx, srcBuffer, isWrapped, override);
+    if (!ctorObj)
         return false;
+    RootedValue cloneCtor(cx, ObjectValue(*ctorObj));
 
     // Step 2.b.
     if (srcBuffer->isDetached()) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
         return false;
     }
 
     // Steps 3-4 (skipped).
@@ -1261,19 +1220,20 @@ TypedArrayObjectTemplate<T>::fromTypedAr
         // Steps 16.b-c.
         if (!CloneArrayBufferNoCopy(cx, srcData, isWrapped, srcByteOffset, srcLength, override,
                                     &buffer))
         {
             return nullptr;
         }
     } else {
         // Steps 17.a-b.
-        RootedValue bufferCtor(cx);
-        if (!GetSpeciesConstructor(cx, srcData, isWrapped, override, &bufferCtor))
+        JSObject* ctorObj = GetSpeciesConstructor(cx, srcData, isWrapped, override);
+        if (!ctorObj)
             return nullptr;
+        RootedValue bufferCtor(cx, ObjectValue(*ctorObj));
 
         // Steps 14-15, 17.c.
         if (!AllocateArrayBuffer(cx, bufferCtor, elementLength, BYTES_PER_ELEMENT, &buffer))
             return nullptr;
 
         // Step 17.d.
         if (srcArray->hasDetachedBuffer()) {
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);