Bug 1315559 - Do not allocate onFulfilled/onRejected function for await. r=till
authorTooru Fujisawa <arai_a@mac.com>
Wed, 09 Nov 2016 03:27:50 +0900
changeset 351759 1f38bd73f5bd95cfd150e9552bd499c220f07816
parent 351758 d1710006b0936f2315997fcc39c092b72220e6eb
child 351760 38f5ec02b1d669c4e2894075da59d3e0d354aeb3
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-esr52@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstill
bugs1315559
milestone52.0a1
Bug 1315559 - Do not allocate onFulfilled/onRejected function for await. r=till
js/src/builtin/Promise.cpp
js/src/builtin/Promise.h
js/src/vm/AsyncFunction.cpp
js/src/vm/AsyncFunction.h
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -9,16 +9,17 @@
 
 #include "mozilla/Atomics.h"
 #include "mozilla/TimeStamp.h"
 
 #include "jscntxt.h"
 
 #include "gc/Heap.h"
 #include "js/Debug.h"
+#include "vm/AsyncFunction.h"
 #include "vm/SelfHosting.h"
 
 #include "jsobjinlines.h"
 
 #include "vm/NativeObject-inl.h"
 
 using namespace js;
 
@@ -27,16 +28,18 @@ MillisecondsSinceStartup()
 {
     auto now = mozilla::TimeStamp::Now();
     bool ignored;
     return (now - mozilla::TimeStamp::ProcessCreation(ignored)).ToMilliseconds();
 }
 
 #define PROMISE_HANDLER_IDENTITY 0
 #define PROMISE_HANDLER_THROWER  1
+#define PROMISE_HANDLER_AWAIT_FULFILLED 2
+#define PROMISE_HANDLER_AWAIT_REJECTED 3
 
 enum ResolutionMode {
     ResolveMode,
     RejectMode
 };
 
 enum ResolveFunctionSlots {
     ResolveFunctionSlot_Promise = 0,
@@ -620,16 +623,28 @@ static MOZ_MUST_USE PromiseObject* Creat
                                                                bool protoIsWrapped = false,
                                                                bool informDebugger = true);
 
 enum GetCapabilitiesExecutorSlots {
     GetCapabilitiesExecutorSlots_Resolve,
     GetCapabilitiesExecutorSlots_Reject
 };
 
+static MOZ_MUST_USE PromiseObject*
+CreatePromiseObjectWithDefaultResolution(JSContext* cx)
+{
+    Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx));
+    if (!promise)
+        return nullptr;
+
+    AddPromiseFlags(*promise, PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION |
+                    PROMISE_FLAG_DEFAULT_REJECT_FUNCTION);
+    return promise;
+}
+
 // ES2016, 25.4.1.5.
 static MOZ_MUST_USE bool
 NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise,
                      MutableHandleObject resolve, MutableHandleObject reject,
                      bool canOmitResolutionFunctions)
 {
     RootedValue cVal(cx, ObjectValue(*C));
 
@@ -644,22 +659,19 @@ NewPromiseCapability(JSContext* cx, Hand
     // creating and calling the executor function and instead return a Promise
     // marked as having default resolve/reject functions.
     //
     // This can't be used in Promise.all and Promise.race because we have to
     // pass the reject (and resolve, in the race case) function to thenables
     // in the list passed to all/race, which (potentially) means exposing them
     // to content.
     if (canOmitResolutionFunctions && IsNativeFunction(cVal, PromiseConstructor)) {
-        promise.set(CreatePromiseObjectInternal(cx));
+        promise.set(CreatePromiseObjectWithDefaultResolution(cx));
         if (!promise)
             return false;
-        AddPromiseFlags(promise->as<PromiseObject>(), PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION |
-                                                      PROMISE_FLAG_DEFAULT_REJECT_FUNCTION);
-
         return true;
     }
 
     // Step 3 (omitted).
 
     // Step 4.
     RootedAtom funName(cx, cx->names().empty);
     RootedFunction executor(cx, NewNativeFunction(cx, GetCapabilitiesExecutor, 2, funName,
@@ -843,21 +855,42 @@ PromiseReactionJob(JSContext* cx, unsign
 
     // Steps 4-6.
     if (handlerVal.isNumber()) {
         int32_t handlerNum = int32_t(handlerVal.toNumber());
 
         // Step 4.
         if (handlerNum == PROMISE_HANDLER_IDENTITY) {
             handlerResult = argument;
-        } else {
+        } else if (handlerNum == PROMISE_HANDLER_THROWER) {
             // Step 5.
-            MOZ_ASSERT(handlerNum == PROMISE_HANDLER_THROWER);
             resolutionMode = RejectMode;
             handlerResult = argument;
+        } else {
+            MOZ_ASSERT(handlerNum == PROMISE_HANDLER_AWAIT_FULFILLED
+                       || handlerNum == PROMISE_HANDLER_AWAIT_REJECTED);
+
+            Rooted<PromiseObject*> promiseObj(cx, &reaction->promise()->as<PromiseObject>());
+            MOZ_ASSERT(PromiseHasAnyFlag(*promiseObj, PROMISE_FLAG_AWAIT));
+
+            RootedValue generatorVal(cx, promiseObj->getFixedSlot(PromiseSlot_AwaitGenerator));
+
+            // Step 6.
+            // Optimized for await.
+            bool result;
+            if (handlerNum == PROMISE_HANDLER_AWAIT_FULFILLED)
+                result = AsyncFunctionAwaitedFulfilled(cx, argument, generatorVal, &handlerResult);
+            else
+                result = AsyncFunctionAwaitedRejected(cx, argument, generatorVal, &handlerResult);
+
+            if (!result) {
+                resolutionMode = RejectMode;
+                if (!cx->isExceptionPending() || !GetAndClearException(cx, &handlerResult))
+                    return false;
+            }
         }
     } else {
         // Step 6.
         FixedInvokeArgs<1> args2(cx);
         args2[0].set(argument);
         if (!Call(cx, handlerVal, UndefinedHandleValue, args2, &handlerResult)) {
             resolutionMode = RejectMode;
             // Not much we can do about uncatchable exceptions, so just bail
@@ -2050,16 +2083,63 @@ js::OriginalPromiseThen(JSContext* cx, H
 
     // Step 5.
     if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultPromise, resolve, reject))
         return nullptr;
 
     return resultPromise;
 }
 
+static MOZ_MUST_USE bool PerformPromiseThenImpl(JSContext* cx, Handle<PromiseObject*> promise,
+                                                HandleValue onFulfilled,
+                                                HandleValue onRejected,
+                                                HandleObject resultPromise,
+                                                HandleObject resolve,
+                                                HandleObject reject);
+
+// Async Functions proposal 2.3 steps 2-8.
+// Implemented here instead of js/src/builtin/AsyncFunction.cpp
+// to call Promise internal functions
+bool
+js::AsyncFunctionAwait(JSContext* cx, HandleValue generatorVal, HandleValue value,
+                       MutableHandleValue rval)
+{
+    // Step 2.
+    Rooted<PromiseObject*> promise(cx, CreatePromiseObjectWithDefaultResolution(cx));
+    if (!promise)
+        return false;
+
+    // Steps 3.
+    if (!ResolvePromiseInternal(cx, promise, value))
+        return false;
+
+    // Steps 4-5.
+    RootedValue onFulfilled(cx, Int32Value(PROMISE_HANDLER_AWAIT_FULFILLED));
+    RootedValue onRejected(cx, Int32Value(PROMISE_HANDLER_AWAIT_REJECTED));
+
+    // Step 7.
+    Rooted<PromiseObject*> resultPromise(cx, CreatePromiseObjectWithDefaultResolution(cx));
+    if (!resultPromise)
+        return false;
+
+    // Step 6 (reordered).
+    AddPromiseFlags(*resultPromise, PROMISE_FLAG_AWAIT);
+    resultPromise->setFixedSlot(PromiseSlot_AwaitGenerator, generatorVal);
+
+    // Step 8.
+    if (!PerformPromiseThenImpl(cx, promise, onFulfilled, onRejected, resultPromise,
+                                nullptr, nullptr))
+    {
+        return false;
+    }
+
+    rval.setObject(*resultPromise);
+    return true;
+}
+
 // ES2016, 25.4.5.3.
 bool
 js::Promise_then(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
     RootedValue promiseVal(cx, args.thisv());
@@ -2116,16 +2196,25 @@ PerformPromiseThen(JSContext* cx, Handle
     if (!IsCallable(onFulfilled))
         onFulfilled = Int32Value(PROMISE_HANDLER_IDENTITY);
 
     // Step 4.
     RootedValue onRejected(cx, onRejected_);
     if (!IsCallable(onRejected))
         onRejected = Int32Value(PROMISE_HANDLER_THROWER);
 
+    return PerformPromiseThenImpl(cx, promise, onFulfilled, onRejected, resultPromise,
+                                  resolve, reject);
+}
+
+static MOZ_MUST_USE bool
+PerformPromiseThenImpl(JSContext* cx, Handle<PromiseObject*> promise, HandleValue onFulfilled,
+                       HandleValue onRejected, HandleObject resultPromise,
+                       HandleObject resolve, HandleObject reject)
+{
     RootedObject incumbentGlobal(cx);
     if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobal))
         return false;
 
     // Step 7.
     Rooted<PromiseReactionRecord*> reaction(cx, NewReactionRecord(cx, resultPromise,
                                                                   onFulfilled, onRejected,
                                                                   resolve, reject,
@@ -2440,16 +2529,17 @@ PromiseObject::dependentPromises(JSConte
     }
 
     return true;
 }
 
 bool
 PromiseObject::resolve(JSContext* cx, HandleValue resolutionValue)
 {
+    MOZ_ASSERT(!PromiseHasAnyFlag(*this, PROMISE_FLAG_AWAIT));
     if (state() != JS::PromiseState::Pending)
         return true;
 
     RootedObject resolveFun(cx, GetResolveFunctionFromPromise(this));
     RootedValue funVal(cx, ObjectValue(*resolveFun));
 
     // For xray'd Promises, the resolve fun may have been created in another
     // compartment. For the call below to work in that case, wrap the
@@ -2462,16 +2552,17 @@ PromiseObject::resolve(JSContext* cx, Ha
 
     RootedValue dummy(cx);
     return Call(cx, funVal, UndefinedHandleValue, args, &dummy);
 }
 
 bool
 PromiseObject::reject(JSContext* cx, HandleValue rejectionValue)
 {
+    MOZ_ASSERT(!PromiseHasAnyFlag(*this, PROMISE_FLAG_AWAIT));
     if (state() != JS::PromiseState::Pending)
         return true;
 
     RootedValue funVal(cx, this->getFixedSlot(PromiseSlot_RejectFunction));
     MOZ_ASSERT(IsCallable(funVal));
 
     FixedInvokeArgs<1> args(cx);
     args[0].set(rejectionValue);
--- a/js/src/builtin/Promise.h
+++ b/js/src/builtin/Promise.h
@@ -11,30 +11,32 @@
 #include "vm/NativeObject.h"
 
 namespace js {
 
 enum PromiseSlots {
     PromiseSlot_Flags = 0,
     PromiseSlot_ReactionsOrResult,
     PromiseSlot_RejectFunction,
+    PromiseSlot_AwaitGenerator = PromiseSlot_RejectFunction,
     PromiseSlot_AllocationSite,
     PromiseSlot_ResolutionSite,
     PromiseSlot_AllocationTime,
     PromiseSlot_ResolutionTime,
     PromiseSlot_Id,
     PromiseSlots,
 };
 
 #define PROMISE_FLAG_RESOLVED  0x1
 #define PROMISE_FLAG_FULFILLED 0x2
 #define PROMISE_FLAG_HANDLED   0x4
 #define PROMISE_FLAG_REPORTED  0x8
 #define PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION 0x10
 #define PROMISE_FLAG_DEFAULT_REJECT_FUNCTION  0x20
+#define PROMISE_FLAG_AWAIT     0x40
 
 class AutoSetNewObjectMetadata;
 
 class PromiseObject : public NativeObject
 {
   public:
     static const unsigned RESERVED_SLOTS = PromiseSlots;
     static const Class class_;
@@ -112,16 +114,20 @@ EnqueuePromiseReactions(JSContext* cx, H
 
 MOZ_MUST_USE JSObject*
 GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises);
 
 MOZ_MUST_USE JSObject*
 OriginalPromiseThen(JSContext* cx, Handle<PromiseObject*> promise, HandleValue onFulfilled,
                     HandleValue onRejected);
 
+bool
+AsyncFunctionAwait(JSContext* cx, HandleValue generatorVal, HandleValue value,
+                   MutableHandleValue rval);
+
 /**
  * A PromiseTask represents a task that can be dispatched to a helper thread
  * (via StartPromiseTask), executed (by implementing PromiseTask::execute()),
  * and then resolved back on the original JSContext owner thread.
  * Because it contains a PersistentRooted, a PromiseTask will only be destroyed
  * on the JSContext's owner thread.
  */
 class PromiseTask : public JS::AsyncTask
--- a/js/src/vm/AsyncFunction.cpp
+++ b/js/src/vm/AsyncFunction.cpp
@@ -156,19 +156,16 @@ AsyncFunctionReturned(JSContext* cx, Han
     if (!resolveObj)
         return false;
 
     // Step 3.g.
     rval.setObject(*resolveObj);
     return true;
 }
 
-static bool AsyncFunctionAwait(JSContext* cx, HandleValue generatorVal, HandleValue value,
-                               MutableHandleValue rval);
-
 enum class ResumeKind {
     Normal,
     Throw
 };
 
 // Async Functions proposal 2.2 steps 3-8, 2.4 steps 2-7, 2.5 steps 2-7.
 static bool
 AsyncFunctionResume(JSContext* cx, HandleValue generatorVal, ResumeKind kind,
@@ -200,98 +197,39 @@ AsyncFunctionResume(JSContext* cx, Handl
 
 // Async Functions proposal 2.2 steps 3-8.
 static bool
 AsyncFunctionStart(JSContext* cx, HandleValue generatorVal, MutableHandleValue rval)
 {
     return AsyncFunctionResume(cx, generatorVal, ResumeKind::Normal, UndefinedHandleValue, rval);
 }
 
-#define AWAITED_FUNC_GENERATOR_SLOT 0
-
-static bool AsyncFunctionAwaitedFulfilled(JSContext* cx, unsigned argc, Value* vp);
-static bool AsyncFunctionAwaitedRejected(JSContext* cx, unsigned argc, Value* vp);
+// Async Functions proposal 2.3 steps 1-8.
+// Implemented in js/src/builtin/Promise.cpp
 
-// Async Functions proposal 2.3 steps 1-8.
-static bool
-AsyncFunctionAwait(JSContext* cx, HandleValue generatorVal, HandleValue value,
-                   MutableHandleValue rval)
+// Async Functions proposal 2.4.
+bool
+js::AsyncFunctionAwaitedFulfilled(JSContext* cx, HandleValue value, HandleValue generatorVal,
+                                  MutableHandleValue rval)
 {
     // Step 1 (implicit).
 
-    // Steps 2-3.
-    RootedObject resolveObj(cx, PromiseObject::unforgeableResolve(cx, value));
-    if (!resolveObj)
-        return false;
-
-    Rooted<PromiseObject*> resolvePromise(cx, resolveObj.as<PromiseObject>());
-
-    // Step 4.
-    RootedAtom funName(cx, cx->names().empty);
-    RootedFunction onFulfilled(cx, NewNativeFunction(cx, AsyncFunctionAwaitedFulfilled, 1,
-                                                      funName, gc::AllocKind::FUNCTION_EXTENDED,
-                                                      GenericObject));
-    if (!onFulfilled)
-        return false;
-
-    // Step 5.
-    RootedFunction onRejected(cx, NewNativeFunction(cx, AsyncFunctionAwaitedRejected, 1,
-                                                    funName, gc::AllocKind::FUNCTION_EXTENDED,
-                                                    GenericObject));
-    if (!onRejected)
-        return false;
-
-    // Step 6.
-    onFulfilled->setExtendedSlot(AWAITED_FUNC_GENERATOR_SLOT, generatorVal);
-    onRejected->setExtendedSlot(AWAITED_FUNC_GENERATOR_SLOT, generatorVal);
-
-    // Step 8.
-    RootedValue onFulfilledVal(cx, ObjectValue(*onFulfilled));
-    RootedValue onRejectedVal(cx, ObjectValue(*onRejected));
-    RootedObject resultPromise(cx, OriginalPromiseThen(cx, resolvePromise, onFulfilledVal,
-                                                       onRejectedVal));
-    if (!resultPromise)
-        return false;
-
-    rval.setObject(*resultPromise);
-    return true;
-}
-
-// Async Functions proposal 2.4.
-static bool
-AsyncFunctionAwaitedFulfilled(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-    MOZ_ASSERT(args.length() == 1);
-
-    RootedFunction F(cx, &args.callee().as<JSFunction>());
-    RootedValue value(cx, args[0]);
-
-    // Step 1.
-    RootedValue generatorVal(cx, F->getExtendedSlot(AWAITED_FUNC_GENERATOR_SLOT));
-
     // Steps 2-7.
-    return AsyncFunctionResume(cx, generatorVal, ResumeKind::Normal, value, args.rval());
+    return AsyncFunctionResume(cx, generatorVal, ResumeKind::Normal, value, rval);
 }
 
 // Async Functions proposal 2.5.
-static bool
-AsyncFunctionAwaitedRejected(JSContext* cx, unsigned argc, Value* vp)
+bool
+js::AsyncFunctionAwaitedRejected(JSContext* cx, HandleValue reason, HandleValue generatorVal,
+                                 MutableHandleValue rval)
 {
-    CallArgs args = CallArgsFromVp(argc, vp);
-    MOZ_ASSERT(args.length() == 1);
-
-    RootedFunction F(cx, &args.callee().as<JSFunction>());
-    RootedValue reason(cx, args[0]);
-
-    // Step 1.
-    RootedValue generatorVal(cx, F->getExtendedSlot(AWAITED_FUNC_GENERATOR_SLOT));
+    // Step 1 (implicit).
 
     // Step 2-7.
-    return AsyncFunctionResume(cx, generatorVal, ResumeKind::Throw, reason, args.rval());
+    return AsyncFunctionResume(cx, generatorVal, ResumeKind::Throw, reason, rval);
 }
 
 JSFunction*
 js::GetWrappedAsyncFunction(JSFunction* unwrapped)
 {
     MOZ_ASSERT(unwrapped->isAsync());
     return &unwrapped->getExtendedSlot(UNWRAPPED_ASYNC_WRAPPED_SLOT).toObject().as<JSFunction>();
 }
--- a/js/src/vm/AsyncFunction.h
+++ b/js/src/vm/AsyncFunction.h
@@ -19,11 +19,19 @@ JSFunction*
 GetUnwrappedAsyncFunction(JSFunction* wrapped);
 
 bool
 IsWrappedAsyncFunction(JSFunction* fun);
 
 JSObject*
 WrapAsyncFunction(JSContext* cx, HandleFunction unwrapped);
 
+bool
+AsyncFunctionAwaitedFulfilled(JSContext* cx, HandleValue value, HandleValue generatorVal,
+                              MutableHandleValue rval);
+
+bool
+AsyncFunctionAwaitedRejected(JSContext* cx, HandleValue reason, HandleValue generatorVal,
+                             MutableHandleValue rval);
+
 } // namespace js
 
 #endif /* vm_AsyncFunction_h */