Bug 1316098 - Optimize out result object allocation for await/return in async function. r=till
authorTooru Fujisawa <arai_a@mac.com>
Fri, 03 Mar 2017 17:11:14 +0900
changeset 345785 37802af7d64b2721f92c547b2dbc076492c8be4a
parent 345784 cc40078f0ac62e96ba2b46aa4fa381c8f982b3c1
child 345786 a793136c90bc5d32f2c82aa7dea4bc300c4f1836
child 345888 5ed300efb2fa7afa52c2d60c4c02efa3e642748e
push id38286
push usercbook@mozilla.com
push dateFri, 03 Mar 2017 12:09:42 +0000
treeherderautoland@d67caca960aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstill
bugs1316098
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 1316098 - Optimize out result object allocation for await/return in async function. r=till
js/src/doc/Debugger/Conventions.md
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/SharedContext.h
js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async.js
js/src/js.msg
js/src/vm/AsyncFunction.cpp
js/src/vm/AsyncFunction.h
js/src/vm/Debugger.cpp
js/src/vm/Opcodes.h
--- a/js/src/doc/Debugger/Conventions.md
+++ b/js/src/doc/Debugger/Conventions.md
@@ -105,18 +105,18 @@ resumption value has one of the followin
     <i>Value</i> must be a debuggee value. (Most handler functions support
     this, except those whose descriptions say otherwise.) If the function
     was called as a constructor (that is, via a `new` expression), then
     <i>value</i> serves as the value returned by the function's body, not
     that produced by the `new` expression: if the value is not an object,
     the `new` expression returns the frame's `this` value. Similarly, if
     the function is the constructor for a subclass, then a non-object
     value may result in a TypeError.
-    If the frame is a generator or async function, then <i>value</i> must
-    conform to the iterator protocol: it must be a non-proxy object of the form
+    If the frame is a generator function, then <i>value</i> must conform to the
+    iterator protocol: it must be a non-proxy object of the form
     <code>{ done: <i>boolean</i>, value: <i>v</i> }</code>, where
     both `done` and `value` are ordinary properties.
 
 <code>{ throw: <i>value</i> }</code>
 :   Throw <i>value</i> as an exception from the current bytecode
     instruction. <i>Value</i> must be a debuggee value.
 
 `null`
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -8350,17 +8350,17 @@ BytecodeEmitter::emitInitialYield(ParseN
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitYield(ParseNode* pn)
 {
     MOZ_ASSERT(sc->isFunctionBox());
-    MOZ_ASSERT(pn->getOp() == JSOP_YIELD || pn->getOp() == JSOP_AWAIT);
+    MOZ_ASSERT(pn->getOp() == JSOP_YIELD);
 
     bool needsIteratorResult = sc->asFunctionBox()->needsIteratorResult();
     if (needsIteratorResult) {
         if (!emitPrepareIteratorResult())
             return false;
     }
     if (pn->pn_kid) {
         if (!emitTree(pn->pn_kid))
@@ -8372,19 +8372,34 @@ BytecodeEmitter::emitYield(ParseNode* pn
     if (needsIteratorResult) {
         if (!emitFinishIteratorResult(false))
             return false;
     }
 
     if (!emitGetDotGenerator())
         return false;
 
-    if (!emitYieldOp(pn->getOp()))
-        return false;
-
+    if (!emitYieldOp(JSOP_YIELD))
+        return false;
+
+    return true;
+}
+
+bool
+BytecodeEmitter::emitAwait(ParseNode* pn)
+{
+    MOZ_ASSERT(sc->isFunctionBox());
+    MOZ_ASSERT(pn->getOp() == JSOP_AWAIT);
+
+    if (!emitTree(pn->pn_kid))
+        return false;
+    if (!emitGetDotGenerator())
+        return false;
+    if (!emitYieldOp(JSOP_AWAIT))
+        return false;
     return true;
 }
 
 bool
 BytecodeEmitter::emitYieldStar(ParseNode* iter)
 {
     MOZ_ASSERT(sc->isFunctionBox());
     MOZ_ASSERT(sc->asFunctionBox()->isStarGenerator());
@@ -10396,18 +10411,22 @@ BytecodeEmitter::emitTree(ParseNode* pn,
         break;
 
       case PNK_INITIALYIELD:
         if (!emitInitialYield(pn))
             return false;
         break;
 
       case PNK_YIELD:
+        if (!emitYield(pn))
+            return false;
+        break;
+
       case PNK_AWAIT:
-        if (!emitYield(pn))
+        if (!emitAwait(pn))
             return false;
         break;
 
       case PNK_STATEMENTLIST:
         if (!emitStatementList(pn))
             return false;
         break;
 
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -599,16 +599,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter
     MOZ_MUST_USE bool iteratorResultShape(unsigned* shape);
 
     MOZ_MUST_USE bool emitGetDotGenerator();
 
     MOZ_MUST_USE bool emitInitialYield(ParseNode* pn);
     MOZ_MUST_USE bool emitYield(ParseNode* pn);
     MOZ_MUST_USE bool emitYieldOp(JSOp op);
     MOZ_MUST_USE bool emitYieldStar(ParseNode* iter);
+    MOZ_MUST_USE bool emitAwait(ParseNode* pn);
 
     MOZ_MUST_USE bool emitPropLHS(ParseNode* pn);
     MOZ_MUST_USE bool emitPropOp(ParseNode* pn, JSOp op);
     MOZ_MUST_USE bool emitPropIncDec(ParseNode* pn);
 
     MOZ_MUST_USE bool emitAsyncWrapperLambda(unsigned index, bool isArrow);
     MOZ_MUST_USE bool emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow);
 
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -544,17 +544,17 @@ class FunctionBox : public ObjectBox, pu
 
     bool needsFinalYield() const {
         return isStarGenerator() || isLegacyGenerator() || isAsync();
     }
     bool needsDotGeneratorName() const {
         return isStarGenerator() || isLegacyGenerator() || isAsync();
     }
     bool needsIteratorResult() const {
-        return isStarGenerator() || isAsync();
+        return isStarGenerator();
     }
 
     bool isAsync() const { return asyncKind() == AsyncFunction; }
     bool isArrow() const { return function()->isArrow(); }
 
     bool hasRest() const { return hasRest_; }
     void setHasRest() {
         hasRest_ = true;
--- a/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async.js
+++ b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async.js
@@ -4,126 +4,31 @@ var g = newGlobal();
 var dbg = Debugger(g);
 
 g.eval(`
 async function f() {
     return e;
 }
 `);
 
-// To continue testing after uncaught exception, remember the exception and
-// return normal completeion.
-var currentFrame;
-var uncaughtException;
-dbg.uncaughtExceptionHook = function(e) {
-    uncaughtException = e;
-    return {
-        return: currentFrame.eval("({ done: true, value: 'uncaught' })").return
-    };
-};
-function testUncaughtException() {
-    uncaughtException = undefined;
-    var val = g.eval(`
-var val;
-f().then(v => { val = v });
-drainJobQueue();
-val;
-`);
-    assertEq(val, "uncaught");
-    assertEq(uncaughtException instanceof TypeError, true);
-}
-
 // Just continue
 dbg.onExceptionUnwind = function(frame) {
     return undefined;
 };
 g.eval(`
 var E;
 f().catch(e => { exc = e });
 drainJobQueue();
 assertEq(exc instanceof ReferenceError, true);
 `);
 
-// Should return object.
-dbg.onExceptionUnwind = function(frame) {
-    currentFrame = frame;
-    return {
-        return: "foo"
-    };
-};
-testUncaughtException();
-
-// The object should have `done` property and `value` property.
-dbg.onExceptionUnwind = function(frame) {
-    currentFrame = frame;
-    return {
-        return: frame.eval("({})").return
-    };
-};
-testUncaughtException();
-
-// The object should have `done` property.
-dbg.onExceptionUnwind = function(frame) {
-    currentFrame = frame;
-    return {
-        return: frame.eval("({ value: 10 })").return
-    };
-};
-testUncaughtException();
-
-// The object should have `value` property.
-dbg.onExceptionUnwind = function(frame) {
-    currentFrame = frame;
-    return {
-        return: frame.eval("({ done: true })").return
-    };
-};
-testUncaughtException();
-
-// `done` property should be a boolean value.
+// Return with resumption value.
 dbg.onExceptionUnwind = function(frame) {
-    currentFrame = frame;
     return {
-        return: frame.eval("({ done: 10, value: 10 })").return
-    };
-};
-testUncaughtException();
-
-// `done` property shouldn't be an accessor.
-dbg.onExceptionUnwind = function(frame) {
-    currentFrame = frame;
-    return {
-        return: frame.eval("({ get done() { return true; }, value: 10 })").return
-    };
-};
-testUncaughtException();
-
-// `value` property shouldn't be an accessor.
-dbg.onExceptionUnwind = function(frame) {
-    currentFrame = frame;
-    return {
-        return: frame.eval("({ done: true, get value() { return 10; } })").return
-    };
-};
-testUncaughtException();
-
-// The object shouldn't be a Proxy.
-dbg.onExceptionUnwind = function(frame) {
-    currentFrame = frame;
-    return {
-        return: frame.eval("new Proxy({ done: true, value: 10 }, {})").return
-    };
-};
-testUncaughtException();
-
-// Correct resumption value.
-dbg.onExceptionUnwind = function(frame) {
-    currentFrame = frame;
-    return {
-        return: frame.eval("({ done: true, value: 10 })").return
+        return: 10
     };
 };
 var val = g.eval(`
 var val;
 f().then(v => { val = v });
 drainJobQueue();
 val;
 `);
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -427,17 +427,16 @@ MSG_DEF(JSMSG_SC_DUP_TRANSFERABLE,     0
 MSG_DEF(JSMSG_SC_NOT_TRANSFERABLE,     0, JSEXN_TYPEERR, "invalid transferable array for structured clone")
 MSG_DEF(JSMSG_SC_UNSUPPORTED_TYPE,     0, JSEXN_TYPEERR, "unsupported type for structured data")
 MSG_DEF(JSMSG_SC_NOT_CLONABLE,         1, JSEXN_TYPEERR, "{0} cannot be cloned in this context")
 MSG_DEF(JSMSG_SC_SAB_TRANSFERABLE,     0, JSEXN_TYPEERR, "SharedArrayBuffer must not be in the transfer list")
 MSG_DEF(JSMSG_SC_SAB_DISABLED,         0, JSEXN_TYPEERR, "SharedArrayBuffer not cloned - shared memory disabled in receiver")
 
 // Debugger
 MSG_DEF(JSMSG_ASSIGN_FUNCTION_OR_NULL, 1, JSEXN_TYPEERR, "value assigned to {0} must be a function or null")
-MSG_DEF(JSMSG_DEBUG_BAD_AWAIT,         0, JSEXN_TYPEERR, "await expression received invalid value")
 MSG_DEF(JSMSG_DEBUG_BAD_LINE,          0, JSEXN_TYPEERR, "invalid line number")
 MSG_DEF(JSMSG_DEBUG_BAD_OFFSET,        0, JSEXN_TYPEERR, "invalid script offset")
 MSG_DEF(JSMSG_DEBUG_BAD_REFERENT,      2, JSEXN_TYPEERR, "{0} does not refer to {1}")
 MSG_DEF(JSMSG_DEBUG_BAD_RESUMPTION,    0, JSEXN_TYPEERR, "debugger resumption value must be undefined, {throw: val}, {return: val}, or null")
 MSG_DEF(JSMSG_DEBUG_BAD_YIELD,         0, JSEXN_TYPEERR, "generator yielded invalid value")
 MSG_DEF(JSMSG_DEBUG_CANT_DEBUG_GLOBAL, 0, JSEXN_TYPEERR, "passing non-debuggable global to addDebuggee")
 MSG_DEF(JSMSG_DEBUG_CCW_REQUIRED,      1, JSEXN_TYPEERR, "{0}: argument must be an object from a different compartment")
 MSG_DEF(JSMSG_DEBUG_COMPARTMENT_MISMATCH, 2, JSEXN_TYPEERR, "{0}: descriptor .{1} property is an object in a different compartment than the target object")
--- a/js/src/vm/AsyncFunction.cpp
+++ b/js/src/vm/AsyncFunction.cpp
@@ -169,32 +169,24 @@ AsyncFunctionResume(JSContext* cx, Handl
                     ResumeKind kind, HandleValue valueOrReason)
 {
     // Execution context switching is handled in generator.
     HandlePropertyName funName = kind == ResumeKind::Normal
                                  ? cx->names().StarGeneratorNext
                                  : cx->names().StarGeneratorThrow;
     FixedInvokeArgs<1> args(cx);
     args[0].set(valueOrReason);
-    RootedValue result(cx);
-    if (!CallSelfHostedFunction(cx, funName, generatorVal, args, &result))
+    RootedValue value(cx);
+    if (!CallSelfHostedFunction(cx, funName, generatorVal, args, &value))
         return AsyncFunctionThrown(cx, resultPromise);
 
-    RootedObject resultObj(cx, &result.toObject());
-    RootedValue doneVal(cx);
-    RootedValue value(cx);
-    if (!GetProperty(cx, resultObj, resultObj, cx->names().done, &doneVal))
-        return false;
-    if (!GetProperty(cx, resultObj, resultObj, cx->names().value, &value))
-        return false;
+    if (generatorVal.toObject().as<GeneratorObject>().isAfterAwait())
+        return AsyncFunctionAwait(cx, resultPromise, value);
 
-    if (doneVal.toBoolean())
-        return AsyncFunctionReturned(cx, resultPromise, value);
-
-    return AsyncFunctionAwait(cx, resultPromise, value);
+    return AsyncFunctionReturned(cx, resultPromise, value);
 }
 
 // Async Functions proposal 2.2 steps 3-8.
 static MOZ_MUST_USE bool
 AsyncFunctionStart(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue generatorVal)
 {
     return AsyncFunctionResume(cx, resultPromise, generatorVal, ResumeKind::Normal, UndefinedHandleValue);
 }
@@ -240,14 +232,8 @@ js::GetUnwrappedAsyncFunction(JSFunction
     return unwrapped;
 }
 
 bool
 js::IsWrappedAsyncFunction(JSFunction* fun)
 {
     return fun->maybeNative() == WrappedAsyncFunction;
 }
-
-MOZ_MUST_USE bool
-js::CheckAsyncResumptionValue(JSContext* cx, HandleValue v)
-{
-    return CheckStarGeneratorResumptionValue(cx, v);
-}
--- a/js/src/vm/AsyncFunction.h
+++ b/js/src/vm/AsyncFunction.h
@@ -30,14 +30,11 @@ WrapAsyncFunction(JSContext* cx, HandleF
 MOZ_MUST_USE bool
 AsyncFunctionAwaitedFulfilled(JSContext* cx, Handle<PromiseObject*> resultPromise,
                               HandleValue generatorVal, HandleValue value);
 
 MOZ_MUST_USE bool
 AsyncFunctionAwaitedRejected(JSContext* cx, Handle<PromiseObject*> resultPromise,
                              HandleValue generatorVal, HandleValue reason);
 
-MOZ_MUST_USE bool
-CheckAsyncResumptionValue(JSContext* cx, HandleValue v);
-
 } // namespace js
 
 #endif /* vm_AsyncFunction_h */
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -27,17 +27,16 @@
 #include "jit/BaselineDebugModeOSR.h"
 #include "jit/BaselineJIT.h"
 #include "js/Date.h"
 #include "js/GCAPI.h"
 #include "js/UbiNodeBreadthFirst.h"
 #include "js/Vector.h"
 #include "proxy/ScriptedProxyHandler.h"
 #include "vm/ArgumentsObject.h"
-#include "vm/AsyncFunction.h"
 #include "vm/DebuggerMemory.h"
 #include "vm/GeckoProfiler.h"
 #include "vm/GeneratorObject.h"
 #include "vm/TraceLogging.h"
 #include "vm/WrapperObject.h"
 #include "wasm/WasmInstance.h"
 
 #include "jsgcinlines.h"
@@ -1597,26 +1596,21 @@ ParseResumptionValue(JSContext* cx, Hand
     return ParseResumptionValueAsObject(cx, rval, statusp, vp);
 }
 
 static bool
 CheckResumptionValue(JSContext* cx, AbstractFramePtr frame, const Maybe<HandleValue>& maybeThisv,
                      JSTrapStatus status, MutableHandleValue vp)
 {
     if (status == JSTRAP_RETURN && frame && frame.isFunctionFrame()) {
-        // Don't let a { return: ... } resumption value make a generator or
-        // async function violate the iterator protocol. The return value from
+        // Don't let a { return: ... } resumption value make a generator
+        // function violate the iterator protocol. The return value from
         // such a frame must have the form { done: <bool>, value: <anything> }.
         RootedFunction callee(cx, frame.callee());
-        if (callee->isAsync()) {
-            if (!CheckAsyncResumptionValue(cx, vp)) {
-                JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_AWAIT);
-                return false;
-            }
-        } else if (callee->isStarGenerator() || callee->isAsync()) {
+        if (callee->isStarGenerator()) {
             if (!CheckStarGeneratorResumptionValue(cx, vp)) {
                 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_YIELD);
                 return false;
             }
         }
     }
 
     if (maybeThisv.isSome()) {
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -2131,22 +2131,22 @@ 1234567890123456789012345678901234567890
      *
      *   Category: Operators
      *   Type: Debugger
      *   Operands:
      *   Stack: =>
      */ \
     macro(JSOP_DEBUGAFTERYIELD,  208, "debugafteryield",  NULL,  1,  0,  0,  JOF_BYTE) \
     /*
-     * Pops the generator and the return value 'result', stops interpretation
-     * and returns 'result'. Pushes resolved value onto the stack.
+     * Pops the generator and the return value 'promise', stops interpretation
+     * and returns 'promise'. Pushes resolved value onto the stack.
      *   Category: Statements
      *   Type: Generator
      *   Operands: uint24_t yieldAndAwaitIndex
-     *   Stack: result, gen => resolved
+     *   Stack: promise, gen => resolved
      */ \
     macro(JSOP_AWAIT,         209, "await",        NULL,  4,  2,  1,  JOF_UINT24) \
     macro(JSOP_UNUSED210,     210, "unused210",    NULL,  1,  0,  0,  JOF_BYTE) \
     macro(JSOP_UNUSED211,     211, "unused211",    NULL,  1,  0,  0,  JOF_BYTE) \
     /*
      * Initializes generator frame, creates a generator and pushes it on the
      * stack.
      *   Category: Statements