Bug 1530324 - Part 7: Remove initial-yield for async functions. r=arai
authorAndré Bargull <andre.bargull@gmail.com>
Tue, 26 Feb 2019 08:33:30 -0800
changeset 461677 dfcdd2084fea42de8d450614a26f196d12fe8106
parent 461676 b84fd1d91da2afcbd1d73bc16bc374fbe43c634c
child 461678 55b6a8c4e0154ac41f710bf1f3f5627c68ce8d42
push id35626
push usercsabou@mozilla.com
push dateThu, 28 Feb 2019 11:31:08 +0000
treeherdermozilla-central@2ea0c1db7e60 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersarai
bugs1530324
milestone67.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 1530324 - Part 7: Remove initial-yield for async functions. r=arai
js/src/builtin/Promise.cpp
js/src/builtin/Promise.h
js/src/frontend/BinASTParserPerTokenizer.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/Parser.cpp
js/src/jit-test/tests/debug/onEnterFrame-async-01.js
js/src/jit-test/tests/debug/onEnterFrame-async-resumption-01.js
js/src/jit-test/tests/debug/onEnterFrame-async-resumption-02.js
js/src/jit-test/tests/debug/onEnterFrame-async-resumption-03.js
js/src/jit-test/tests/debug/onEnterFrame-async-resumption-04.js
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineCompiler.h
js/src/jit/IonBuilder.cpp
js/src/tests/non262/async-functions/ErrorStack.js
js/src/vm/AsyncFunction.cpp
js/src/vm/BytecodeUtil.cpp
js/src/vm/Debugger.cpp
js/src/vm/Interpreter.cpp
js/src/vm/Opcodes.h
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -3564,30 +3564,33 @@ static MOZ_MUST_USE bool InternalAwait(J
   // Step 6.
   extraStep(reaction);
 
   // Step 9.
   return PerformPromiseThenWithReaction(cx, promise, reaction);
 }
 
 // ES 2018 draft 25.5.5.3 steps 2-10.
-MOZ_MUST_USE bool js::AsyncFunctionAwait(
+MOZ_MUST_USE JSObject* js::AsyncFunctionAwait(
     JSContext* cx, Handle<AsyncFunctionGeneratorObject*> genObj,
     HandleValue value) {
   // Steps 4-5.
   RootedValue onFulfilled(
       cx, Int32Value(PromiseHandlerAsyncFunctionAwaitedFulfilled));
   RootedValue onRejected(
       cx, Int32Value(PromiseHandlerAsyncFunctionAwaitedRejected));
 
   // Steps 2-3, 6-10.
   auto extra = [&](Handle<PromiseReactionRecord*> reaction) {
     reaction->setIsAsyncFunction(genObj);
   };
-  return InternalAwait(cx, value, nullptr, onFulfilled, onRejected, extra);
+  if (!InternalAwait(cx, value, nullptr, onFulfilled, onRejected, extra)) {
+    return nullptr;
+  }
+  return genObj->promise();
 }
 
 // Async Iteration proposal 4.1 Await steps 2-9.
 MOZ_MUST_USE bool js::AsyncGeneratorAwait(
     JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
     HandleValue value) {
   // Steps 4-5.
   RootedValue onFulfilled(
@@ -4820,23 +4823,36 @@ static MOZ_MUST_USE bool IsTopMostAsyncF
   if (iter.done()) {
     return false;
   }
   if (!iter.calleeTemplate()) {
     return false;
   }
   MOZ_ASSERT(iter.calleeTemplate()->isAsync());
 
+#ifdef DEBUG
+  bool isGenerator = iter.calleeTemplate()->isGenerator();
+#endif
+
   ++iter;
 
   // The parent frame should be the `next` function of the generator that is
   // internally called in AsyncFunctionResume resp. AsyncGeneratorResume.
   if (iter.done()) {
     return false;
   }
+  // The initial call into an async function can happen from top-level code, so
+  // the parent frame isn't required to be a function frame. Contrary to that,
+  // the parent frame for an async generator function is always a function
+  // frame, because async generators can't directly fall through to an `await`
+  // expression from their initial call.
+  if (!iter.isFunctionFrame()) {
+    MOZ_ASSERT(!isGenerator);
+    return false;
+  }
   if (!iter.calleeTemplate()) {
     return false;
   }
 
   if (!IsSelfHostedFunctionWithName(iter.calleeTemplate(),
                                     cx->names().AsyncFunctionNext) &&
       !IsSelfHostedFunctionWithName(iter.calleeTemplate(),
                                     cx->names().AsyncGeneratorNext)) {
--- a/js/src/builtin/Promise.h
+++ b/js/src/builtin/Promise.h
@@ -248,17 +248,19 @@ class AsyncFunctionGeneratorObject;
 MOZ_MUST_USE bool AsyncFunctionReturned(JSContext* cx,
                                         Handle<PromiseObject*> resultPromise,
                                         HandleValue value);
 
 MOZ_MUST_USE bool AsyncFunctionThrown(JSContext* cx,
                                       Handle<PromiseObject*> resultPromise,
                                       HandleValue reason);
 
-MOZ_MUST_USE bool AsyncFunctionAwait(
+// Start awaiting `value` in an async function (, but doesn't suspend the
+// async function's execution!). Returns the async function's result promise.
+MOZ_MUST_USE JSObject* AsyncFunctionAwait(
     JSContext* cx, Handle<AsyncFunctionGeneratorObject*> genObj,
     HandleValue value);
 
 // If the await operation can be skipped and the resolution value for `val` can
 // be acquired, stored the resolved value to `resolved` and `true` to
 // `*canSkip`.  Otherwise, stores `false` to `*canSkip`.
 MOZ_MUST_USE bool TrySkipAwait(JSContext* cx, HandleValue val, bool* canSkip,
                                MutableHandleValue resolved);
--- a/js/src/frontend/BinASTParserPerTokenizer.cpp
+++ b/js/src/frontend/BinASTParserPerTokenizer.cpp
@@ -335,23 +335,25 @@ JS::Result<FunctionNode*> BinASTParserPe
 
   if (funbox->needsDotGeneratorName()) {
     BINJS_TRY(pc_->declareDotGeneratorName());
 
     HandlePropertyName dotGenerator = cx_->names().dotGenerator;
     BINJS_TRY(usedNames_.noteUse(cx_, dotGenerator, pc_->scriptId(),
                                  pc_->innermostScope()->id()));
 
-    BINJS_TRY_DECL(
-        dotGen, handler_.newName(dotGenerator,
-                                 tokenizer_->pos(tokenizer_->offset()), cx_));
+    if (funbox->isGenerator()) {
+      BINJS_TRY_DECL(
+          dotGen, handler_.newName(dotGenerator,
+                                   tokenizer_->pos(tokenizer_->offset()), cx_));
 
-    ListNode* stmtList =
-        &body->as<LexicalScopeNode>().scopeBody()->as<ListNode>();
-    BINJS_TRY(handler_.prependInitialYield(stmtList, dotGen));
+      ListNode* stmtList =
+          &body->as<LexicalScopeNode>().scopeBody()->as<ListNode>();
+      BINJS_TRY(handler_.prependInitialYield(stmtList, dotGen));
+    }
   }
 
   const bool canSkipLazyClosedOverBindings = false;
   BINJS_TRY(pc_->declareFunctionArgumentsObject(usedNames_,
                                                 canSkipLazyClosedOverBindings));
   BINJS_TRY(
       pc_->declareFunctionThis(usedNames_, canSkipLazyClosedOverBindings));
 
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -6384,18 +6384,29 @@ bool BytecodeEmitter::emitAwaitInScope(E
   }
 
   InternalIfEmitter ifCanSkip(this);
   if (!ifCanSkip.emitThen()) {
     //              [stack] VALUE_OR_RESOLVED
     return false;
   }
 
+  if (sc->asFunctionBox()->needsPromiseResult()) {
+    if (!emitGetDotGeneratorInScope(currentScope)) {
+      //            [stack] VALUE GENERATOR
+      return false;
+    }
+    if (!emit1(JSOP_ASYNCAWAIT)) {
+      //            [stack] PROMISE
+      return false;
+    }
+  }
+
   if (!emitGetDotGeneratorInScope(currentScope)) {
-    //              [stack] VALUE GENERATOR
+    //              [stack] VALUE|PROMISE GENERATOR
     return false;
   }
   if (!emitYieldOp(JSOP_AWAIT)) {
     //              [stack] RESOLVED
     return false;
   }
 
   if (!ifCanSkip.emitEnd()) {
@@ -8489,16 +8500,28 @@ bool BytecodeEmitter::emitFunctionFormal
 bool BytecodeEmitter::emitFunctionFormalParameters(ListNode* paramsBody) {
   ParseNode* funBody = paramsBody->last();
   FunctionBox* funbox = sc->asFunctionBox();
   EmitterScope* funScope = innermostEmitterScope();
 
   bool hasParameterExprs = funbox->hasParameterExprs;
   bool hasRest = funbox->hasRest();
 
+  // Parameters can't reuse the reject try-catch block from the function body,
+  // because the body may have pushed an additional var-environment. This
+  // messes up scope resolution for the |.generator| variable, because we'd
+  // need different hops to reach |.generator| depending on whether the error
+  // was thrown from the parameters or the function body.
+  Maybe<TryEmitter> rejectTryCatch;
+  if (hasParameterExprs && funbox->needsPromiseResult()) {
+    if (!emitAsyncFunctionRejectPrologue(rejectTryCatch)) {
+      return false;
+    }
+  }
+
   uint16_t argSlot = 0;
   for (ParseNode* arg = paramsBody->head(); arg != funBody;
        arg = arg->pn_next, argSlot++) {
     ParseNode* bindingElement = arg;
     ParseNode* initializer = nullptr;
     if (arg->isKind(ParseNodeKind::AssignExpr)) {
       bindingElement = arg->as<AssignmentNode>().left();
       initializer = arg->as<AssignmentNode>().right();
@@ -8600,16 +8623,22 @@ bool BytecodeEmitter::emitFunctionFormal
 
     if (paramExprVarScope) {
       if (!paramExprVarScope->leave(this)) {
         return false;
       }
     }
   }
 
+  if (rejectTryCatch) {
+    if (!emitAsyncFunctionRejectEpilogue(*rejectTryCatch)) {
+      return false;
+    }
+  }
+
   return true;
 }
 
 bool BytecodeEmitter::emitInitializeFunctionSpecialNames() {
   FunctionBox* funbox = sc->asFunctionBox();
 
   auto emitInitializeFunctionSpecialName =
       [](BytecodeEmitter* bce, HandlePropertyName name, JSOp op) {
@@ -8647,16 +8676,24 @@ bool BytecodeEmitter::emitInitializeFunc
   // arrow function).
   if (funbox->hasThisBinding()) {
     if (!emitInitializeFunctionSpecialName(this, cx->names().dotThis,
                                            JSOP_FUNCTIONTHIS)) {
       return false;
     }
   }
 
+  // Do nothing if the function doesn't implicitly return a promise result.
+  if (funbox->needsPromiseResult()) {
+    if (!emitInitializeFunctionSpecialName(this, cx->names().dotGenerator,
+                                           JSOP_GENERATOR)) {
+      return false;
+    }
+  }
+
   return true;
 }
 
 bool BytecodeEmitter::emitFunctionBody(ParseNode* funBody) {
   FunctionBox* funbox = sc->asFunctionBox();
 
   Maybe<TryEmitter> rejectTryCatch;
   if (funbox->needsPromiseResult()) {
@@ -8788,53 +8825,16 @@ bool BytecodeEmitter::emitAsyncFunctionR
   if (!emit1(JSOP_EXCEPTION)) {
     //              [stack] EXC
     return false;
   }
   if (!emitGetDotGeneratorInInnermostScope()) {
     //              [stack] EXC GEN
     return false;
   }
-
-  // TODO: Remove when .generator is created outside of try-catch.
-  if (!emit1(JSOP_DUP)) {
-    //              [stack] EXC GEN GEN
-    return false;
-  }
-  if (!emit1(JSOP_UNDEFINED)) {
-    //              [stack] EXC GEN GEN UNDEF
-    return false;
-  }
-  if (!emit1(JSOP_STRICTEQ)) {
-    //              [stack] EXC GEN EQ
-    return false;
-  }
-
-  InternalIfEmitter ifGeneratorIsUndef(this);
-  if (!ifGeneratorIsUndef.emitThen()) {
-    //              [stack] EXC GEN
-    return false;
-  }
-
-  if (!emit1(JSOP_POP)) {
-    //              [stack] EXC
-    return false;
-  }
-  if (!emit1(JSOP_THROW)) {
-    //              [stack]
-    return false;
-  }
-
-  this->stackDepth += 2;  // Fixup stack depth.
-
-  if (!ifGeneratorIsUndef.emitEnd()) {
-    //              [stack] EXC GEN
-    return false;
-  }
-
   if (!emit2(JSOP_ASYNCRESOLVE, uint8_t(AsyncFunctionResolveKind::Reject))) {
     //              [stack] PROMISE
     return false;
   }
   if (!emit1(JSOP_SETRVAL)) {
     //              [stack]
     return false;
   }
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -1892,22 +1892,24 @@ GeneralParser<ParseHandler, Unit>::funct
   MOZ_ASSERT_IF(pc_->isGenerator(), kind != FunctionSyntaxKind::Arrow);
   MOZ_ASSERT_IF(pc_->isGenerator(), type == StatementListBody);
 
   if (pc_->needsDotGeneratorName()) {
     MOZ_ASSERT_IF(!pc_->isAsync(), type == StatementListBody);
     if (!pc_->declareDotGeneratorName()) {
       return null();
     }
-    NameNodeType generator = newDotGeneratorName();
-    if (!generator) {
-      return null();
-    }
-    if (!handler_.prependInitialYield(handler_.asList(body), generator)) {
-      return null();
+    if (pc_->isGenerator()) {
+      NameNodeType generator = newDotGeneratorName();
+      if (!generator) {
+        return null();
+      }
+      if (!handler_.prependInitialYield(handler_.asList(body), generator)) {
+        return null();
+      }
     }
   }
 
   // Declare the 'arguments' and 'this' bindings if necessary before
   // finishing up the scope so these special bindings get marked as closed
   // over if necessary. Arrow functions don't have these bindings.
   if (kind != FunctionSyntaxKind::Arrow) {
     bool canSkipLazyClosedOverBindings =
--- a/js/src/jit-test/tests/debug/onEnterFrame-async-01.js
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-01.js
@@ -22,12 +22,11 @@ dbg.onEnterFrame = frame => {
         frame.nickname = nicknames.shift() || "FAIL";
     log += "(" + frame.nickname;
     frame.onPop = completion => { log += ")"; };
 };
 
 g.job();
 drainJobQueue();
 assertEq(log,
-         "(job)(job(t5)(t5)(t3)(t3))" +
+         "(job(t5)(t3))" +
          "(t5)(t3)".repeat(3) + "(job)" +
          "(t5)(t5)(job)");
-
--- a/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-01.js
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-01.js
@@ -9,30 +9,27 @@ g.eval(`async function f(x) { await x; r
 
 let dbg = new Debugger;
 let gw = dbg.addDebuggee(g);
 let hits = 0;
 let resumption = undefined;
 dbg.onEnterFrame = frame => {
     if (frame.type == "call" && frame.callee.name === "f") {
         frame.onPop = completion => {
-            assertEq(completion.return, resumption.return);
+            assertEq(completion.return.isPromise, true);
             hits++;
         };
 
-        // Don't tell anyone, but if we force-return a generator object here,
-        // the robots will still detect it and throw an error. No protection
-        // against Skynet, for us poor humans!
+        // If we force-return a generator object here, the caller will receive
+        // a promise object resolved with that generator.
         resumption = frame.eval(`(function* f2() { hit2 = true; })()`);
         assertEq(resumption.return.class, "Generator");
         return resumption;
     }
 };
 
-let error;
-try {
-    g.f(0);
-} catch (e) {
-    error = e;
-}
+let p = g.f(0);
 assertEq(hits, 1);
-assertEq(error instanceof g.Error, true);
 assertEq(g.hit2, false);
+let pw = gw.makeDebuggeeValue(p);
+assertEq(pw.isPromise, true);
+assertEq(pw.promiseState, "fulfilled");
+assertEq(pw.promiseValue, resumption.return);
--- a/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-02.js
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-02.js
@@ -9,21 +9,20 @@ g.eval(`
     var err = new TypeError("object too hairy");
 `);
 
 let dbg = new Debugger;
 let gw = dbg.addDebuggee(g);
 let errw = gw.makeDebuggeeValue(g.err);
 
 // Repeat the test for each onEnterFrame event.
-// It fires up to three times:
+// It fires up to two times:
 // - when the async function g.f is called;
-// - when we enter it to run to `await 1`;
 // - when we resume after the await to run to the end.
-for (let when = 0; when < 3; when++) {
+for (let when = 0; when < 2; when++) {
     let hits = 0;
     dbg.onEnterFrame = frame => {
         return hits++ < when ? undefined : {throw: errw};
     };
 
     let result = undefined;
     g.f()
         .then(value => { result = {returned: value}; })
--- a/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-03.js
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-03.js
@@ -21,10 +21,9 @@ function test(when) {
     drainJobQueue();
     assertEq(finished, true);
     assertEq(hits, when + 1);
     assertEq(result, "exit");
 }
 
 // onEnterFrame with hits==0 is not a resume point; {return:} behaves differently there
 // (see onEnterFrame-async-resumption-02.js).
-test(1);  // force return from first resume point, immediately after the initial suspend
-test(2);  // force return from second resume point, immediately after the await instruction
+test(1);  // force return from first resume point, immediately after the await instruction
--- a/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-04.js
+++ b/js/src/jit-test/tests/debug/onEnterFrame-async-resumption-04.js
@@ -12,19 +12,19 @@ g.eval(`
         }
     }
 `)
 
 let dbg = new Debugger(g);
 dbg.onEnterFrame = frame => {
     if (!("hits" in frame)) {
         frame.hits = 1;
-    } else if (++frame.hits == 3) {
-        // First two hits happen when g.af() is called;
-        // third hit is resuming at the `await` inside the try block.
+    } else if (++frame.hits === 2) {
+        // First hit happens when g.af() is called;
+        // second hit is resuming at the `await` inside the try block.
         return {throw: "fit"};
     }
 };
 
 let p = g.af();
 let hits = 0;
 p.then(value => {
     result = value;
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -4692,16 +4692,43 @@ bool BaselineCodeGen<Handler>::emit_JSOP
   masm.bind(&done);
 
   frame.pop();
   frame.push(R0);
   frame.push(R1);
   return true;
 }
 
+typedef JSObject* (*AsyncFunctionAwaitFn)(JSContext*,
+                                          Handle<AsyncFunctionGeneratorObject*>,
+                                          HandleValue);
+static const VMFunction AsyncFunctionAwaitInfo =
+    FunctionInfo<AsyncFunctionAwaitFn>(js::AsyncFunctionAwait,
+                                       "AsyncFunctionAwait");
+
+template <typename Handler>
+bool BaselineCodeGen<Handler>::emit_JSOP_ASYNCAWAIT() {
+  frame.syncStack(0);
+  masm.loadValue(frame.addressOfStackValue(-2), R1);
+  masm.unboxObject(frame.addressOfStackValue(-1), R0.scratchReg());
+
+  prepareVMCall();
+  pushArg(R1);
+  pushArg(R0.scratchReg());
+
+  if (!callVM(AsyncFunctionAwaitInfo)) {
+    return false;
+  }
+
+  masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
+  frame.popn(2);
+  frame.push(R0);
+  return true;
+}
+
 typedef JSObject* (*AsyncFunctionResolveFn)(
     JSContext*, Handle<AsyncFunctionGeneratorObject*>, HandleValue,
     AsyncFunctionResolveKind);
 static const VMFunction AsyncFunctionResolveInfo =
     FunctionInfo<AsyncFunctionResolveFn>(js::AsyncFunctionResolve,
                                          "AsyncFunctionResolve");
 
 template <typename Handler>
@@ -6075,17 +6102,16 @@ MethodStatus BaselineCompiler::emitBody(
       return Method_Error;
     }
 
     switch (op) {
       // ===== NOT Yet Implemented =====
       case JSOP_FORCEINTERPRETER:
         // Intentionally not implemented.
       case JSOP_UNUSED71:
-      case JSOP_UNUSED151:
       case JSOP_LIMIT:
         // === !! WARNING WARNING WARNING !! ===
         // DO NOT add new ops to this list! All bytecode ops MUST have Baseline
         // support. Follow-up bugs are not acceptable.
         JitSpew(JitSpew_BaselineAbort, "Unhandled op: %s", CodeName[op]);
         return Method_CantCompile;
 
 #define EMIT_OP(OP)                                            \
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -204,16 +204,17 @@ namespace jit {
   _(JSOP_GENERATOR)             \
   _(JSOP_INITIALYIELD)          \
   _(JSOP_YIELD)                 \
   _(JSOP_AWAIT)                 \
   _(JSOP_TRYSKIPAWAIT)          \
   _(JSOP_DEBUGAFTERYIELD)       \
   _(JSOP_FINALYIELDRVAL)        \
   _(JSOP_RESUME)                \
+  _(JSOP_ASYNCAWAIT)            \
   _(JSOP_ASYNCRESOLVE)          \
   _(JSOP_CALLEE)                \
   _(JSOP_ENVCALLEE)             \
   _(JSOP_SUPERBASE)             \
   _(JSOP_SUPERFUN)              \
   _(JSOP_GETRVAL)               \
   _(JSOP_SETRVAL)               \
   _(JSOP_RETRVAL)               \
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -2505,16 +2505,17 @@ AbortReasonOr<Ok> IonBuilder::inspectOpc
     case JSOP_INITIALYIELD:
     case JSOP_YIELD:
     case JSOP_FINALYIELDRVAL:
     case JSOP_RESUME:
     case JSOP_DEBUGAFTERYIELD:
     case JSOP_AWAIT:
     case JSOP_TRYSKIPAWAIT:
     case JSOP_GENERATOR:
+    case JSOP_ASYNCAWAIT:
     case JSOP_ASYNCRESOLVE:
 
     // Misc
     case JSOP_DELNAME:
     case JSOP_FINALLY:
     case JSOP_GETRVAL:
     case JSOP_GOSUB:
     case JSOP_RETSUB:
@@ -2525,17 +2526,16 @@ AbortReasonOr<Ok> IonBuilder::inspectOpc
       // operation in the optimizing compiler?
       break;
 
     case JSOP_FORCEINTERPRETER:
       // Intentionally not implemented.
       break;
 
     case JSOP_UNUSED71:
-    case JSOP_UNUSED151:
     case JSOP_LIMIT:
       break;
   }
 
   // Track a simpler message, since the actionable abort message is a
   // static string, and the internal opcode name isn't an actionable
   // thing anyways.
   trackActionableAbort("Unsupported bytecode");
--- a/js/src/tests/non262/async-functions/ErrorStack.js
+++ b/js/src/tests/non262/async-functions/ErrorStack.js
@@ -5,29 +5,29 @@ var summary = "Error.stack should provid
 
 print(BUGNUMBER + ": " + summary);
 
 let COOKIE = "C0F5DBB89807";
 
 async function thrower() {
     let stack = new Error().stack; // line 11
     assertEq(/^thrower@.+ErrorStack.js:11/m.test(stack), true, toMessage(stack));
-    assertEq(/^async\*inner@.+ErrorStack.js:38/m.test(stack), true, toMessage(stack));
+    assertEq(/^inner@.+ErrorStack.js:38/m.test(stack), true, toMessage(stack));
     assertEq(/^async\*middle@.+ErrorStack.js:58/m.test(stack), true, toMessage(stack));
     assertEq(/^async\*outer@.+ErrorStack.js:78/m.test(stack), true, toMessage(stack));
     assertEq(/^async\*@.+ErrorStack.js:82/m.test(stack), true, toMessage(stack));
 
     throw new Error(COOKIE); // line 18
 }
 
 async function inner() {
     let stack = new Error().stack; // line 22
     assertEq(/thrower@.+ErrorStack.js/m.test(stack), false, toMessage(stack));
     assertEq(/^inner@.+ErrorStack.js:22/m.test(stack), true, toMessage(stack));
-    assertEq(/^async\*middle@.+ErrorStack.js:58/m.test(stack), true, toMessage(stack));
+    assertEq(/^middle@.+ErrorStack.js:58/m.test(stack), true, toMessage(stack));
     assertEq(/^async\*outer@.+ErrorStack.js:78/m.test(stack), true, toMessage(stack));
     assertEq(/^async\*@.+ErrorStack.js:82/m.test(stack), true, toMessage(stack));
 
     await Promise.resolve(100);
 
     stack = new Error().stack; // line 31
     assertEq(/thrower@.+ErrorStack.js/m.test(stack), false, toMessage(stack));
     assertEq(/^inner@.+ErrorStack.js:31/m.test(stack), true, toMessage(stack));
@@ -38,17 +38,17 @@ async function inner() {
     await thrower(); // line 38
 }
 
 async function middle() {
     let stack = new Error().stack; // line 42
     assertEq(/thrower@.+ErrorStack.js/m.test(stack), false, toMessage(stack));
     assertEq(/inner@.+ErrorStack.js/m.test(stack), false, toMessage(stack));
     assertEq(/^middle@.+ErrorStack.js:42/m.test(stack), true, toMessage(stack));
-    assertEq(/^async\*outer@.+ErrorStack.js:78/m.test(stack), true, toMessage(stack));
+    assertEq(/^outer@.+ErrorStack.js:78/m.test(stack), true, toMessage(stack));
     assertEq(/^async\*@.+ErrorStack.js:82/m.test(stack), true, toMessage(stack));
 
     await Promise.resolve(1000);
 
     stack = new Error().stack; // line 51
     assertEq(/thrower@.+ErrorStack.js/m.test(stack), false, toMessage(stack));
     assertEq(/inner@.+ErrorStack.js/m.test(stack), false, toMessage(stack));
     assertEq(/^middle@.+ErrorStack.js:51/m.test(stack), true, toMessage(stack));
@@ -59,17 +59,17 @@ async function middle() {
 }
 
 async function outer() {
     let stack = new Error().stack; // line 62
     assertEq(/thrower@.+ErrorStack.js/m.test(stack), false, toMessage(stack));
     assertEq(/inner@.+ErrorStack.js/m.test(stack), false, toMessage(stack));
     assertEq(/middle@.+ErrorStack.js/m.test(stack), false, toMessage(stack));
     assertEq(/^outer@.+ErrorStack.js:62/m.test(stack), true, toMessage(stack));
-    assertEq(/^async\*@.+ErrorStack.js:82/m.test(stack), true, toMessage(stack));
+    assertEq(/^@.+ErrorStack.js:82/m.test(stack), true, toMessage(stack));
 
     await Promise.resolve(10000);
 
     stack = new Error().stack; // line 71
     assertEq(/thrower@.+ErrorStack.js/m.test(stack), false, toMessage(stack));
     assertEq(/inner@.+ErrorStack.js/m.test(stack), false, toMessage(stack));
     assertEq(/middle@.+ErrorStack.js/m.test(stack), false, toMessage(stack));
     assertEq(/^outer@.+ErrorStack.js:71/m.test(stack), true, toMessage(stack));
@@ -83,17 +83,17 @@ try {
     assertEq(true, false);
 } catch (e) {
     // Re-throw the exception to log the assertion failure properly.
     if (!e.message.includes(COOKIE))
         throw e;
 
     let stack = e.stack;
     assertEq(/^thrower@.+ErrorStack.js:18/m.test(stack), true, toMessage(stack));
-    assertEq(/^async\*inner@.+ErrorStack.js:38/m.test(stack), true, toMessage(stack));
+    assertEq(/^inner@.+ErrorStack.js:38/m.test(stack), true, toMessage(stack));
     assertEq(/^async\*middle@.+ErrorStack.js:58/m.test(stack), true, toMessage(stack));
     assertEq(/^async\*outer@.+ErrorStack.js:78/m.test(stack), true, toMessage(stack));
     assertEq(/^async\*@.+ErrorStack.js:82/m.test(stack), true, toMessage(stack));
 }
 
 function toMessage(stack) {
     // Provide the stack string in the error message for debugging.
     return `[stack: ${stack.replace(/\n/g, "\\n")}]`;
--- a/js/src/vm/AsyncFunction.cpp
+++ b/js/src/vm/AsyncFunction.cpp
@@ -56,19 +56,16 @@ using mozilla::Maybe;
   }
 
   global->setReservedSlot(ASYNC_FUNCTION, ObjectValue(*asyncFunction));
   global->setReservedSlot(ASYNC_FUNCTION_PROTO,
                           ObjectValue(*asyncFunctionProto));
   return true;
 }
 
-static MOZ_MUST_USE bool AsyncFunctionStart(
-    JSContext* cx, Handle<AsyncFunctionGeneratorObject*> generator);
-
 #define UNWRAPPED_ASYNC_WRAPPED_SLOT 1
 #define WRAPPED_ASYNC_UNWRAPPED_SLOT 0
 
 // Async Functions proposal 1.1.8 and 1.2.14.
 static bool WrappedAsyncFunction(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
 
   RootedValue unwrappedVal(cx);
@@ -77,36 +74,17 @@ static bool WrappedAsyncFunction(JSConte
 
   // Step 2.
   // Also does a part of 2.2 steps 1-2.
   InvokeArgs args2(cx);
   if (!FillArgumentsFromArraylike(cx, args2, args)) {
     return false;
   }
 
-  RootedValue generatorVal(cx);
-  if (Call(cx, unwrappedVal, args.thisv(), args2, &generatorVal)) {
-    // Handle the case when the debugger force-returned an unexpected value.
-    if (!generatorVal.isObject() ||
-        !generatorVal.toObject().is<AsyncFunctionGeneratorObject>()) {
-      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
-                                JSMSG_UNEXPECTED_TYPE, "return value",
-                                JS::InformalValueTypeName(generatorVal));
-      return false;
-    }
-
-    // Step 3.
-    Rooted<AsyncFunctionGeneratorObject*> generator(
-        cx, &generatorVal.toObject().as<AsyncFunctionGeneratorObject>());
-    if (!AsyncFunctionStart(cx, generator)) {
-      return false;
-    }
-
-    // Step 5.
-    args.rval().setObject(*generator->promise());
+  if (Call(cx, unwrappedVal, args.thisv(), args2, args.rval())) {
     return true;
   }
 
   if (!cx->isExceptionPending()) {
     return false;
   }
 
   // Steps 1, 4.
@@ -198,53 +176,42 @@ static bool AsyncFunctionResume(JSContex
     stack = allocationSite->as<SavedFrame>().getParent();
     if (stack) {
       asyncStack.emplace(
           cx, stack, "async",
           JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::EXPLICIT);
     }
   }
 
-  // This case can only happen when the debugger force-returned the same
-  // generator object for two async function calls. It doesn't matter how we
-  // handle it, as long as we don't crash.
-  if (generator->isClosed() || !generator->isSuspended()) {
-    return true;
-  }
+  MOZ_ASSERT(!generator->isClosed(),
+             "closed generator when resuming async function");
+  MOZ_ASSERT(generator->isSuspended(),
+             "non-suspended generator when resuming async function");
 
   // Execution context switching is handled in generator.
   HandlePropertyName funName = kind == ResumeKind::Normal
                                    ? cx->names().AsyncFunctionNext
                                    : cx->names().AsyncFunctionThrow;
   FixedInvokeArgs<1> args(cx);
   args[0].set(valueOrReason);
   RootedValue generatorOrValue(cx, ObjectValue(*generator));
   if (!CallSelfHostedFunction(cx, funName, generatorOrValue, args,
                               &generatorOrValue)) {
     if (!generator->isClosed()) {
       generator->setClosed();
     }
     return false;
   }
 
-  if (generator->isClosed()) {
-    MOZ_ASSERT(generatorOrValue.isObject());
-    MOZ_ASSERT(&generatorOrValue.toObject() == resultPromise);
-    return true;
-  }
+  MOZ_ASSERT_IF(generator->isClosed(), generatorOrValue.isObject());
+  MOZ_ASSERT_IF(generator->isClosed(),
+                &generatorOrValue.toObject() == resultPromise);
+  MOZ_ASSERT_IF(!generator->isClosed(), generator->isAfterAwait());
 
-  MOZ_ASSERT(generator->isAfterAwait());
-  return AsyncFunctionAwait(cx, generator, generatorOrValue);
-}
-
-// Async Functions proposal 2.2 steps 3-8.
-static MOZ_MUST_USE bool AsyncFunctionStart(
-    JSContext* cx, Handle<AsyncFunctionGeneratorObject*> generator) {
-  return AsyncFunctionResume(cx, generator, ResumeKind::Normal,
-                             UndefinedHandleValue);
+  return true;
 }
 
 // Async Functions proposal 2.3 steps 1-8.
 // Implemented in js/src/builtin/Promise.cpp
 
 // Async Functions proposal 2.4.
 MOZ_MUST_USE bool js::AsyncFunctionAwaitedFulfilled(
     JSContext* cx, Handle<AsyncFunctionGeneratorObject*> generator,
@@ -316,10 +283,13 @@ AsyncFunctionGeneratorObject* AsyncFunct
   }
 
   auto* obj = NewBuiltinClassInstance<AsyncFunctionGeneratorObject>(cx);
   if (!obj) {
     return nullptr;
   }
   obj->initFixedSlot(PROMISE_SLOT, ObjectValue(*resultPromise));
 
+  // Starts in the running state.
+  obj->setResumeIndex(AbstractGeneratorObject::RESUME_INDEX_RUNNING);
+
   return obj;
 }
--- a/js/src/vm/BytecodeUtil.cpp
+++ b/js/src/vm/BytecodeUtil.cpp
@@ -2108,16 +2108,17 @@ bool ExpressionDecompiler::decompilePC(j
 
       case JSOP_AWAIT:
       case JSOP_YIELD:
         // Printing "yield SOMETHING" is confusing since the operand doesn't
         // match to the syntax, since the stack operand for "yield 10" is
         // the result object, not 10.
         return write("RVAL");
 
+      case JSOP_ASYNCAWAIT:
       case JSOP_ASYNCRESOLVE:
         return write("PROMISE");
 
       default:
         break;
     }
     return write("<unknown>");
   }
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -1593,33 +1593,42 @@ static bool CheckResumptionValue(JSConte
   }
   return true;
 }
 
 static void AdjustGeneratorResumptionValue(JSContext* cx,
                                            AbstractFramePtr frame,
                                            ResumeMode& resumeMode,
                                            MutableHandleValue vp) {
-  if (resumeMode != ResumeMode::Return || !frame || !frame.isFunctionFrame()) {
+  if (resumeMode != ResumeMode::Return && resumeMode != ResumeMode::Throw) {
+    return;
+  }
+  if (!frame || !frame.isFunctionFrame()) {
     return;
   }
 
   // To propagate out of memory in debuggee code.
   auto getAndClearExceptionThenThrow = [&]() {
     MOZ_ALWAYS_TRUE(cx->getPendingException(vp));
     cx->clearPendingException();
     resumeMode = ResumeMode::Throw;
   };
 
   // Treat `{return: <value>}` like a `return` statement. Simulate what the
   // debuggee would do for an ordinary `return` statement--using a few bytecode
   // instructions--and it's simpler to do the work manually than to count on
   // that bytecode sequence existing in the debuggee, somehow jump to it, and
   // then avoid re-entering the debugger from it.
+  // Similarly treat `{throw: <value>}` like a `throw` statement.
   if (frame.callee()->isGenerator()) {
+    // Throw doesn't require any special processing for (async) generators.
+    if (resumeMode == ResumeMode::Throw) {
+      return;
+    }
+
     // For (async) generators, that means doing the work below.
     Rooted<AbstractGeneratorObject*> genObj(
         cx, GetGeneratorObjectForFrame(cx, frame));
     if (genObj) {
       // 1.  `return <value>` creates and returns a new object,
       //     `{value: <value>, done: true}`.
       if (!genObj->isBeforeInitialYield()) {
         JSObject* pair = CreateIterResultObject(cx, vp, true);
@@ -1636,36 +1645,52 @@ static void AdjustGeneratorResumptionVal
       // We're before the initial yield. Carry on with the forced return.
       // The debuggee will see a call to a generator returning the
       // non-generator value *vp.
     }
   } else if (frame.callee()->isAsync()) {
     // For async functions, that means doing the work below.
     if (AbstractGeneratorObject* genObj =
             GetGeneratorObjectForFrame(cx, frame)) {
+      // Throw doesn't require any special processing for async functions when
+      // the internal generator object is already present.
+      if (resumeMode == ResumeMode::Throw) {
+        return;
+      }
+
       Rooted<AsyncFunctionGeneratorObject*> asyncGenObj(
           cx, &genObj->as<AsyncFunctionGeneratorObject>());
 
       // 1.  `return <value>` fulfills and returns the async function's promise.
-      if (!asyncGenObj->isBeforeInitialYield()) {
-        JSObject* promise = AsyncFunctionResolve(
-            cx, asyncGenObj, vp, AsyncFunctionResolveKind::Fulfill);
-        if (!promise) {
-          getAndClearExceptionThenThrow();
-          return;
-        }
-        vp.setObject(*promise);
-      }
+      JSObject* promise = AsyncFunctionResolve(
+          cx, asyncGenObj, vp, AsyncFunctionResolveKind::Fulfill);
+      if (!promise) {
+        getAndClearExceptionThenThrow();
+        return;
+      }
+      vp.setObject(*promise);
 
       // 2.  The generator must be closed.
       asyncGenObj->setClosed();
     } else {
-      // We're before the initial yield. Carry on with the forced return.
-      // The debuggee will see a call to an async function returning the
-      // non-promise value *vp.
+      // We're before entering the actual function code.
+
+      // 1.  `throw <value>` creates a promise rejected with the value *vp.
+      // 1.  `return <value>` creates a promise resolved with the value *vp.
+      JSObject* promise = resumeMode == ResumeMode::Throw
+                              ? PromiseObject::unforgeableReject(cx, vp)
+                              : PromiseObject::unforgeableResolve(cx, vp);
+      if (!promise) {
+        getAndClearExceptionThenThrow();
+        return;
+      }
+      vp.setObject(*promise);
+
+      // 2.  Return normally in both cases.
+      resumeMode = ResumeMode::Return;
     }
   }
 }
 
 ResumeMode Debugger::reportUncaughtException(Maybe<AutoRealm>& ar) {
   JSContext* cx = ar->context();
 
   // Uncaught exceptions arise from Debugger code, and so we must already be
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1941,17 +1941,16 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
       DISPATCH_TO(op);
     }
 
     /* Various 1-byte no-ops. */
     CASE(JSOP_NOP)
     CASE(JSOP_NOP_DESTRUCTURING)
     CASE(JSOP_TRY_DESTRUCTURING)
     CASE(JSOP_UNUSED71)
-    CASE(JSOP_UNUSED151)
     CASE(JSOP_TRY)
     CASE(JSOP_CONDSWITCH) {
       MOZ_ASSERT(CodeSpec[*REGS.pc].length == 1);
       ADVANCE_AND_DISPATCH(1);
     }
 
     CASE(JSOP_JUMPTARGET)
     CASE(JSOP_LOOPHEAD) {
@@ -2109,17 +2108,21 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
         if (MOZ_LIKELY(interpReturnOK)) {
           TypeScript::Monitor(cx, script, REGS.pc, REGS.sp[-1]);
 
           ADVANCE_AND_DISPATCH(JSOP_CALL_LENGTH);
         }
 
         goto error;
       } else {
-        MOZ_ASSERT(REGS.stackDepth() == 0);
+        // Stack should be empty for the outer frame, unless we executed the
+        // first |await| expression in an async function.
+        MOZ_ASSERT(
+            REGS.stackDepth() == 0 ||
+            (*REGS.pc == JSOP_AWAIT && !REGS.fp()->isResumedGenerator()));
       }
       goto exit;
     }
 
     CASE(JSOP_DEFAULT) {
       REGS.sp--;
       /* FALL THROUGH */
     }
@@ -3619,23 +3622,39 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
         REGS.sp[-1] = resolved;
         PUSH_BOOLEAN(true);
       } else {
         PUSH_BOOLEAN(false);
       }
     }
     END_CASE(JSOP_TRYSKIPAWAIT)
 
+    CASE(JSOP_ASYNCAWAIT) {
+      MOZ_ASSERT(REGS.stackDepth() >= 2);
+      ReservedRooted<JSObject*> gen(&rootObject1, &REGS.sp[-1].toObject());
+      ReservedRooted<Value> value(&rootValue0, REGS.sp[-2]);
+      JSObject* promise =
+          AsyncFunctionAwait(cx, gen.as<AsyncFunctionGeneratorObject>(), value);
+      if (!promise) {
+        goto error;
+      }
+
+      REGS.sp--;
+      REGS.sp[-1].setObject(*promise);
+    }
+    END_CASE(JSOP_ASYNCAWAIT)
+
     CASE(JSOP_ASYNCRESOLVE) {
       MOZ_ASSERT(REGS.stackDepth() >= 2);
       auto resolveKind = AsyncFunctionResolveKind(GET_UINT8(REGS.pc));
       ReservedRooted<JSObject*> gen(&rootObject1, &REGS.sp[-1].toObject());
-      ReservedRooted<Value> reason(&rootValue0, REGS.sp[-2]);
-      JSObject* promise = AsyncFunctionResolve(
-          cx, gen.as<AsyncFunctionGeneratorObject>(), reason, resolveKind);
+      ReservedRooted<Value> valueOrReason(&rootValue0, REGS.sp[-2]);
+      JSObject* promise =
+          AsyncFunctionResolve(cx, gen.as<AsyncFunctionGeneratorObject>(),
+                               valueOrReason, resolveKind);
       if (!promise) {
         goto error;
       }
 
       REGS.sp--;
       REGS.sp[-1].setObject(*promise);
     }
     END_CASE(JSOP_ASYNCRESOLVE)
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -1619,18 +1619,27 @@
      *
      *   Category: Operators
      *   Type: Arithmetic Operators
      *   Operands:
      *   Stack: lval, rval => (lval ** rval)
      */ \
     MACRO(JSOP_POW, 150, "pow", "**", 1, 2, 1, JOF_BYTE|JOF_IC) \
     /*
+     * Pops the top two values 'value' and 'gen' from the stack, then starts
+     * "awaiting" for 'value' to be resolved, which will then resume the
+     * execution of 'gen'. Pushes the async function promise on the stack, so
+     * that it'll be returned to the caller on the very first "await".
+     *
+     *   Category: Statements
+     *   Type: Generator
+     *   Operands:
+     *   Stack: value, gen => promise
      */ \
-    MACRO(JSOP_UNUSED151, 151, "unused151", NULL, 1, 0, 0, JOF_BYTE) \
+    MACRO(JSOP_ASYNCAWAIT, 151, "async-await", NULL, 1, 2, 1, JOF_BYTE) \
     /*
      * Pops the top of stack value as 'rval', sets the return value in stack
      * frame as 'rval'.
      *
      *   Category: Statements
      *   Type: Function
      *   Operands:
      *   Stack: rval =>