Bug 1530324 - Part 6: Add JSOP_ASYNCRESOLVE to fulfill/reject an async function promise. r=arai
authorAndré Bargull <andre.bargull@gmail.com>
Tue, 26 Feb 2019 08:08:36 -0800
changeset 461676 b84fd1d91da2afcbd1d73bc16bc374fbe43c634c
parent 461675 48fb1e2b6e97f1f089c259d4f3209ee26a274e74
child 461677 dfcdd2084fea42de8d450614a26f196d12fe8106
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 6: Add JSOP_ASYNCRESOLVE to fulfill/reject an async function promise. r=arai
js/src/builtin/Promise.cpp
js/src/builtin/Promise.h
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/SharedContext.h
js/src/jit-test/tests/debug/Frame-onStep-async-02.js
js/src/jit-test/tests/debug/save-queue-resets-draining.js
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineCompiler.h
js/src/jit/IonBuilder.cpp
js/src/jit/VMFunctions.h
js/src/vm/AsyncFunction.cpp
js/src/vm/AsyncFunction.h
js/src/vm/BytecodeUtil.cpp
js/src/vm/Debugger.cpp
js/src/vm/Interpreter.cpp
js/src/vm/JSContext.cpp
js/src/vm/Opcodes.h
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -3511,24 +3511,20 @@ MOZ_MUST_USE PromiseObject* js::CreatePr
 
 bool js::IsPromiseForAsync(JSObject* promise) {
   return promise->is<PromiseObject>() &&
          PromiseHasAnyFlag(promise->as<PromiseObject>(), PROMISE_FLAG_ASYNC);
 }
 
 // ES2019 draft rev 7428c89bef626548084cd4e697a19ece7168f24c
 // 25.7.5.1 AsyncFunctionStart, steps 3.f-g.
-MOZ_MUST_USE bool js::AsyncFunctionThrown(
-    JSContext* cx, Handle<PromiseObject*> resultPromise) {
-  RootedValue exc(cx);
-  if (!MaybeGetAndClearException(cx, &exc)) {
-    return false;
-  }
-
-  return RejectPromiseInternal(cx, resultPromise, exc);
+MOZ_MUST_USE bool js::AsyncFunctionThrown(JSContext* cx,
+                                          Handle<PromiseObject*> resultPromise,
+                                          HandleValue reason) {
+  return RejectPromiseInternal(cx, resultPromise, reason);
 }
 
 // ES2019 draft rev 7428c89bef626548084cd4e697a19ece7168f24c
 // 25.7.5.1 AsyncFunctionStart, steps 3.d-e, 3.g.
 MOZ_MUST_USE bool js::AsyncFunctionReturned(
     JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue value) {
   return ResolvePromiseInternal(cx, resultPromise, value);
 }
--- a/js/src/builtin/Promise.h
+++ b/js/src/builtin/Promise.h
@@ -245,17 +245,18 @@ MOZ_MUST_USE bool IsPromiseForAsync(JSOb
 
 class AsyncFunctionGeneratorObject;
 
 MOZ_MUST_USE bool AsyncFunctionReturned(JSContext* cx,
                                         Handle<PromiseObject*> resultPromise,
                                         HandleValue value);
 
 MOZ_MUST_USE bool AsyncFunctionThrown(JSContext* cx,
-                                      Handle<PromiseObject*> resultPromise);
+                                      Handle<PromiseObject*> resultPromise,
+                                      HandleValue reason);
 
 MOZ_MUST_USE bool 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`.
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -45,16 +45,17 @@
 #include "frontend/ParseNode.h"
 #include "frontend/Parser.h"
 #include "frontend/PropOpEmitter.h"
 #include "frontend/SwitchEmitter.h"
 #include "frontend/TDZCheckCache.h"
 #include "frontend/TryEmitter.h"
 #include "frontend/WhileEmitter.h"
 #include "js/CompileOptions.h"
+#include "vm/AsyncFunction.h"
 #include "vm/BytecodeUtil.h"
 #include "vm/Debugger.h"
 #include "vm/GeneratorObject.h"
 #include "vm/JSAtom.h"
 #include "vm/JSContext.h"
 #include "vm/JSFunction.h"
 #include "vm/JSScript.h"
 #include "vm/Stack.h"
@@ -6239,16 +6240,38 @@ bool BytecodeEmitter::emitReturn(UnaryNo
     return false;
   }
 
   if (needsFinalYield) {
     // We know that .generator is on the function scope, as we just exited
     // all nested scopes.
     NameLocation loc = *locationOfNameBoundInFunctionScope(
         cx->names().dotGenerator, varEmitterScope);
+
+    // Resolve the return value before emitting the final yield.
+    if (sc->asFunctionBox()->needsPromiseResult()) {
+      if (!emit1(JSOP_GETRVAL)) {
+        //          [stack] RVAL
+        return false;
+      }
+      if (!emitGetNameAtLocation(cx->names().dotGenerator, loc)) {
+        //          [stack] RVAL GEN
+        return false;
+      }
+      if (!emit2(JSOP_ASYNCRESOLVE,
+                 uint8_t(AsyncFunctionResolveKind::Fulfill))) {
+        //          [stack] PROMISE
+        return false;
+      }
+      if (!emit1(JSOP_SETRVAL)) {
+        //          [stack]
+        return false;
+      }
+    }
+
     if (!emitGetNameAtLocation(cx->names().dotGenerator, loc)) {
       return false;
     }
     if (!emitYieldOp(JSOP_FINALYIELDRVAL)) {
       return false;
     }
   } else if (isDerivedClassConstructor) {
     MOZ_ASSERT(code()[top] == JSOP_SETRVAL);
@@ -8630,16 +8653,23 @@ bool BytecodeEmitter::emitInitializeFunc
   }
 
   return true;
 }
 
 bool BytecodeEmitter::emitFunctionBody(ParseNode* funBody) {
   FunctionBox* funbox = sc->asFunctionBox();
 
+  Maybe<TryEmitter> rejectTryCatch;
+  if (funbox->needsPromiseResult()) {
+    if (!emitAsyncFunctionRejectPrologue(rejectTryCatch)) {
+      return false;
+    }
+  }
+
   if (funbox->function()->kind() ==
       JSFunction::FunctionKind::ClassConstructor) {
     if (!emitInitializeInstanceFields()) {
       return false;
     }
   }
 
   if (!emitTree(funBody)) {
@@ -8660,16 +8690,29 @@ bool BytecodeEmitter::emitFunctionBody(P
     }
 
     if (needsIteratorResult) {
       if (!emitFinishIteratorResult(true)) {
         return false;
       }
     }
 
+    if (funbox->needsPromiseResult()) {
+      if (!emitGetDotGeneratorInInnermostScope()) {
+        //          [stack] RVAL GEN
+        return false;
+      }
+
+      if (!emit2(JSOP_ASYNCRESOLVE,
+                 uint8_t(AsyncFunctionResolveKind::Fulfill))) {
+        //          [stack] PROMISE
+        return false;
+      }
+    }
+
     if (!emit1(JSOP_SETRVAL)) {
       return false;
     }
 
     if (!emitGetDotGeneratorInInnermostScope()) {
       return false;
     }
 
@@ -8694,16 +8737,22 @@ bool BytecodeEmitter::emitFunctionBody(P
   }
 
   if (funbox->isDerivedClassConstructor()) {
     if (!emitCheckDerivedClassConstructorReturn()) {
       return false;
     }
   }
 
+  if (rejectTryCatch) {
+    if (!emitAsyncFunctionRejectEpilogue(*rejectTryCatch)) {
+      return false;
+    }
+  }
+
   return true;
 }
 
 bool BytecodeEmitter::emitLexicalInitialization(NameNode* name) {
   return emitLexicalInitialization(name->name());
 }
 
 bool BytecodeEmitter::emitLexicalInitialization(JSAtom* name) {
@@ -8719,16 +8768,93 @@ bool BytecodeEmitter::emitLexicalInitial
 
   if (!noe.emitAssignment()) {
     return false;
   }
 
   return true;
 }
 
+bool BytecodeEmitter::emitAsyncFunctionRejectPrologue(
+    Maybe<TryEmitter>& tryCatch) {
+  tryCatch.emplace(this, TryEmitter::Kind::TryCatch,
+                   TryEmitter::ControlKind::NonSyntactic);
+  return tryCatch->emitTry();
+}
+
+bool BytecodeEmitter::emitAsyncFunctionRejectEpilogue(TryEmitter& tryCatch) {
+  if (!tryCatch.emitCatch()) {
+    return false;
+  }
+
+  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;
+  }
+  if (!emitGetDotGeneratorInInnermostScope()) {
+    //              [stack] GEN
+    return false;
+  }
+  if (!emit1(JSOP_FINALYIELDRVAL)) {
+    //              [stack]
+    return false;
+  }
+
+  return tryCatch.emitEnd();
+}
+
 static MOZ_ALWAYS_INLINE FunctionNode* FindConstructor(JSContext* cx,
                                                        ListNode* classMethods) {
   for (ParseNode* mn : classMethods->contents()) {
     if (mn->is<ClassMethod>()) {
       ClassMethod& method = mn->as<ClassMethod>();
       ParseNode& methodName = method.name();
       if (!method.isStatic() &&
           (methodName.isKind(ParseNodeKind::ObjectPropertyName) ||
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -99,16 +99,17 @@ typedef Vector<jsbytecode, 64> BytecodeV
 typedef Vector<jssrcnote, 64> SrcNotesVector;
 
 class CallOrNewEmitter;
 class ElemOpEmitter;
 class EmitterScope;
 class NestableControl;
 class PropertyEmitter;
 class TDZCheckCache;
+class TryEmitter;
 
 struct MOZ_STACK_CLASS BytecodeEmitter {
   // Context shared between parsing and bytecode generation.
   SharedContext* const sc = nullptr;
 
   JSContext* const cx = nullptr;
 
   // Enclosing function or global context.
@@ -846,16 +847,22 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
 
   MOZ_MUST_USE bool emitFunctionFormalParametersAndBody(ListNode* paramsBody);
   MOZ_MUST_USE bool emitFunctionFormalParameters(ListNode* paramsBody);
   MOZ_MUST_USE bool emitInitializeFunctionSpecialNames();
   MOZ_MUST_USE bool emitFunctionBody(ParseNode* funBody);
   MOZ_MUST_USE bool emitLexicalInitialization(NameNode* name);
   MOZ_MUST_USE bool emitLexicalInitialization(JSAtom* name);
 
+  // Async functions have implicit try-catch blocks to convert exceptions
+  // into promise rejections.
+  MOZ_MUST_USE bool emitAsyncFunctionRejectPrologue(
+      mozilla::Maybe<TryEmitter>& tryCatch);
+  MOZ_MUST_USE bool emitAsyncFunctionRejectEpilogue(TryEmitter& tryCatch);
+
   // Emit bytecode for the spread operator.
   //
   // emitSpread expects the current index (I) of the array, the array itself
   // and the iterator to be on the stack in that order (iterator on the bottom).
   // It will pop the iterator and I, then iterate over the iterator by calling
   // |.next()| and put the results into the I-th element of array with
   // incrementing I, then push the result I (it will be original I +
   // iteration count). The stack after iteration will look like |ARRAY INDEX|.
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -472,16 +472,17 @@ class FunctionBox : public ObjectBox, pu
   FunctionAsyncKind asyncKind() const {
     return isAsync() ? FunctionAsyncKind::AsyncFunction
                      : FunctionAsyncKind::SyncFunction;
   }
 
   bool needsFinalYield() const { return isGenerator() || isAsync(); }
   bool needsDotGeneratorName() const { return isGenerator() || isAsync(); }
   bool needsIteratorResult() const { return isGenerator(); }
+  bool needsPromiseResult() const { return isAsync() && !isGenerator(); }
 
   bool isArrow() const { return function()->isArrow(); }
 
   bool hasRest() const { return hasRest_; }
   void setHasRest() { hasRest_ = true; }
 
   bool hasExprBody() const { return hasExprBody_; }
   void setHasExprBody() {
--- a/js/src/jit-test/tests/debug/Frame-onStep-async-02.js
+++ b/js/src/jit-test/tests/debug/Frame-onStep-async-02.js
@@ -63,17 +63,19 @@ dbg.onEnterFrame = frame => {
             previousLine = line;
         }
     };
 
     frame.onPop = completion => {
         // Popping the frame. But async function frames are popped multiple
         // times: for the "initial suspend", at each await, and on return. The
         // debugger offers no easy way to distinguish them (bug 1470558).
-        if (typeof completion.return === "string") {
+        // For now there's an "await" property, but bug 1470558 may come up
+        // with a different solution, so don't rely on it!
+        if (!completion.await) {
             // Returning (not awaiting or at initial suspend).
             assertEq(asyncStack.pop(), frame);
             log += ")";
         }
     };
 };
 
 // Run.
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/save-queue-resets-draining.js
@@ -0,0 +1,18 @@
+// The draining state is reset when saving the job queue.
+
+let g = newGlobal({newCompartment: true});
+
+let dbg = new Debugger();
+let gw = dbg.addDebuggee(g);
+
+dbg.onDebuggerStatement = frame => {
+  // Enqueue a new job from within the debugger while executing another job
+  // from outside of the debugger.
+  enqueueJob(function() {});
+};
+
+g.eval(`
+  enqueueJob(function() {
+    debugger;
+  });
+`);
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -4692,16 +4692,44 @@ bool BaselineCodeGen<Handler>::emit_JSOP
   masm.bind(&done);
 
   frame.pop();
   frame.push(R0);
   frame.push(R1);
   return true;
 }
 
+typedef JSObject* (*AsyncFunctionResolveFn)(
+    JSContext*, Handle<AsyncFunctionGeneratorObject*>, HandleValue,
+    AsyncFunctionResolveKind);
+static const VMFunction AsyncFunctionResolveInfo =
+    FunctionInfo<AsyncFunctionResolveFn>(js::AsyncFunctionResolve,
+                                         "AsyncFunctionResolve");
+
+template <typename Handler>
+bool BaselineCodeGen<Handler>::emit_JSOP_ASYNCRESOLVE() {
+  frame.syncStack(0);
+  masm.loadValue(frame.addressOfStackValue(-2), R1);
+  masm.unboxObject(frame.addressOfStackValue(-1), R0.scratchReg());
+
+  prepareVMCall();
+  pushUint8BytecodeOperandArg();
+  pushArg(R1);
+  pushArg(R0.scratchReg());
+
+  if (!callVM(AsyncFunctionResolveInfo)) {
+    return false;
+  }
+
+  masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
+  frame.popn(2);
+  frame.push(R0);
+  return true;
+}
+
 typedef bool (*ThrowObjectCoercibleFn)(JSContext*, HandleValue);
 static const VMFunction ThrowObjectCoercibleInfo =
     FunctionInfo<ThrowObjectCoercibleFn>(ThrowObjectCoercible,
                                          "ThrowObjectCoercible");
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_CHECKOBJCOERCIBLE() {
   frame.syncStack(0);
@@ -6048,17 +6076,16 @@ MethodStatus BaselineCompiler::emitBody(
     }
 
     switch (op) {
       // ===== NOT Yet Implemented =====
       case JSOP_FORCEINTERPRETER:
         // Intentionally not implemented.
       case JSOP_UNUSED71:
       case JSOP_UNUSED151:
-      case JSOP_UNUSED192:
       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_ASYNCRESOLVE)          \
   _(JSOP_CALLEE)                \
   _(JSOP_ENVCALLEE)             \
   _(JSOP_SUPERBASE)             \
   _(JSOP_SUPERFUN)              \
   _(JSOP_GETRVAL)               \
   _(JSOP_SETRVAL)               \
   _(JSOP_RETRVAL)               \
   _(JSOP_RETURN)                \
--- 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_ASYNCRESOLVE:
 
     // Misc
     case JSOP_DELNAME:
     case JSOP_FINALLY:
     case JSOP_GETRVAL:
     case JSOP_GOSUB:
     case JSOP_RETSUB:
     case JSOP_SETINTRINSIC:
@@ -2525,17 +2526,16 @@ AbortReasonOr<Ok> IonBuilder::inspectOpc
       break;
 
     case JSOP_FORCEINTERPRETER:
       // Intentionally not implemented.
       break;
 
     case JSOP_UNUSED71:
     case JSOP_UNUSED151:
-    case JSOP_UNUSED192:
     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/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -17,16 +17,17 @@
 #include "vm/Interpreter.h"
 
 namespace js {
 
 class NamedLambdaObject;
 class WithScope;
 class InlineTypedObject;
 class AbstractGeneratorObject;
+class AsyncFunctionGeneratorObject;
 class RegExpObject;
 class TypedArrayObject;
 
 namespace gc {
 
 struct Cell;
 
 }
@@ -467,16 +468,20 @@ template <>
 struct TypeToDataType<Handle<ArrayObject*> > {
   static const DataType result = Type_Handle;
 };
 template <>
 struct TypeToDataType<Handle<AbstractGeneratorObject*> > {
   static const DataType result = Type_Handle;
 };
 template <>
+struct TypeToDataType<Handle<AsyncFunctionGeneratorObject*> > {
+  static const DataType result = Type_Handle;
+};
+template <>
 struct TypeToDataType<Handle<PlainObject*> > {
   static const DataType result = Type_Handle;
 };
 template <>
 struct TypeToDataType<Handle<WithScope*> > {
   static const DataType result = Type_Handle;
 };
 template <>
@@ -551,16 +556,22 @@ struct TypeToArgProperties<Handle<ArrayO
       TypeToArgProperties<ArrayObject*>::result | VMFunction::ByRef;
 };
 template <>
 struct TypeToArgProperties<Handle<AbstractGeneratorObject*> > {
   static const uint32_t result =
       TypeToArgProperties<AbstractGeneratorObject*>::result | VMFunction::ByRef;
 };
 template <>
+struct TypeToArgProperties<Handle<AsyncFunctionGeneratorObject*> > {
+  static const uint32_t result =
+      TypeToArgProperties<AsyncFunctionGeneratorObject*>::result |
+      VMFunction::ByRef;
+};
+template <>
 struct TypeToArgProperties<Handle<PlainObject*> > {
   static const uint32_t result =
       TypeToArgProperties<PlainObject*>::result | VMFunction::ByRef;
 };
 template <>
 struct TypeToArgProperties<Handle<RegExpObject*> > {
   static const uint32_t result =
       TypeToArgProperties<RegExpObject*>::result | VMFunction::ByRef;
@@ -679,16 +690,20 @@ template <>
 struct TypeToRootType<Handle<ArrayObject*> > {
   static const uint32_t result = VMFunction::RootObject;
 };
 template <>
 struct TypeToRootType<Handle<AbstractGeneratorObject*> > {
   static const uint32_t result = VMFunction::RootObject;
 };
 template <>
+struct TypeToRootType<Handle<AsyncFunctionGeneratorObject*> > {
+  static const uint32_t result = VMFunction::RootObject;
+};
+template <>
 struct TypeToRootType<Handle<PlainObject*> > {
   static const uint32_t result = VMFunction::RootObject;
 };
 template <>
 struct TypeToRootType<Handle<RegExpObject*> > {
   static const uint32_t result = VMFunction::RootObject;
 };
 template <>
--- a/js/src/vm/AsyncFunction.cpp
+++ b/js/src/vm/AsyncFunction.cpp
@@ -217,24 +217,27 @@ static bool AsyncFunctionResume(JSContex
   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 AsyncFunctionThrown(cx, resultPromise);
+    return false;
   }
 
-  if (generator->isAfterAwait()) {
-    return AsyncFunctionAwait(cx, generator, generatorOrValue);
+  if (generator->isClosed()) {
+    MOZ_ASSERT(generatorOrValue.isObject());
+    MOZ_ASSERT(&generatorOrValue.toObject() == resultPromise);
+    return true;
   }
 
-  return AsyncFunctionReturned(cx, resultPromise, generatorOrValue);
+  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);
 }
@@ -257,16 +260,32 @@ MOZ_MUST_USE bool js::AsyncFunctionAwait
     JSContext* cx, Handle<AsyncFunctionGeneratorObject*> generator,
     HandleValue reason) {
   // Step 1 (implicit).
 
   // Step 2-7.
   return AsyncFunctionResume(cx, generator, ResumeKind::Throw, reason);
 }
 
+JSObject* js::AsyncFunctionResolve(
+    JSContext* cx, Handle<AsyncFunctionGeneratorObject*> generator,
+    HandleValue valueOrReason, AsyncFunctionResolveKind resolveKind) {
+  Rooted<PromiseObject*> promise(cx, generator->promise());
+  if (resolveKind == AsyncFunctionResolveKind::Fulfill) {
+    if (!AsyncFunctionReturned(cx, promise, valueOrReason)) {
+      return nullptr;
+    }
+  } else {
+    if (!AsyncFunctionThrown(cx, promise, valueOrReason)) {
+      return nullptr;
+    }
+  }
+  return promise;
+}
+
 JSFunction* js::GetWrappedAsyncFunction(JSFunction* unwrapped) {
   MOZ_ASSERT(unwrapped->isAsync());
   return &unwrapped->getExtendedSlot(UNWRAPPED_ASYNC_WRAPPED_SLOT)
               .toObject()
               .as<JSFunction>();
 }
 
 JSFunction* js::GetUnwrappedAsyncFunction(JSFunction* wrapped) {
--- a/js/src/vm/AsyncFunction.h
+++ b/js/src/vm/AsyncFunction.h
@@ -54,16 +54,25 @@ class AsyncFunctionGeneratorObject;
 MOZ_MUST_USE bool AsyncFunctionAwaitedFulfilled(
     JSContext* cx, Handle<AsyncFunctionGeneratorObject*> generator,
     HandleValue value);
 
 MOZ_MUST_USE bool AsyncFunctionAwaitedRejected(
     JSContext* cx, Handle<AsyncFunctionGeneratorObject*> generator,
     HandleValue reason);
 
+enum class AsyncFunctionResolveKind { Fulfill, Reject };
+
+// Resolve the async function's promise object with the given value and then
+// return the promise object.
+JSObject* AsyncFunctionResolve(JSContext* cx,
+                               Handle<AsyncFunctionGeneratorObject*> generator,
+                               HandleValue valueOrReason,
+                               AsyncFunctionResolveKind resolveKind);
+
 class AsyncFunctionGeneratorObject : public AbstractGeneratorObject {
  public:
   enum {
     PROMISE_SLOT = AbstractGeneratorObject::RESERVED_SLOTS,
 
     RESERVED_SLOTS
   };
 
--- a/js/src/vm/BytecodeUtil.cpp
+++ b/js/src/vm/BytecodeUtil.cpp
@@ -2108,16 +2108,19 @@ 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_ASYNCRESOLVE:
+        return write("PROMISE");
+
       default:
         break;
     }
     return write("<unknown>");
   }
 #endif /* DEBUG */
 
   return write("(intermediate value)");
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -1081,19 +1081,19 @@ class MOZ_RAII AutoSetGeneratorRunning {
         // Call the onPop handler.
         ResumeMode nextResumeMode = resumeMode;
         RootedValue nextValue(cx, wrappedValue);
         bool success;
         {
           AutoSetGeneratorRunning asgr(cx, genObj);
           success = handler->onPop(cx, frameobj, nextResumeMode, &nextValue);
         }
-        adjqi.runJobs();
         nextResumeMode = dbg->processParsedHandlerResult(
             ar, frame, pc, success, nextResumeMode, &nextValue);
+        adjqi.runJobs();
 
         // At this point, we are back in the debuggee compartment, and
         // any error has been wrapped up as a completion value.
         MOZ_ASSERT(cx->compartment() == debuggeeGlobal->compartment());
         MOZ_ASSERT(!cx->isExceptionPending());
 
         // ResumeMode::Continue means "make no change".
         if (nextResumeMode != ResumeMode::Continue) {
@@ -1593,48 +1593,80 @@ static bool CheckResumptionValue(JSConte
   }
   return true;
 }
 
 static void AdjustGeneratorResumptionValue(JSContext* cx,
                                            AbstractFramePtr frame,
                                            ResumeMode& resumeMode,
                                            MutableHandleValue vp) {
-  if (resumeMode == ResumeMode::Return && frame && frame.isFunctionFrame() &&
-      frame.callee()->isGenerator()) {
-    // Treat `{return: <value>}` like a `return` statement. For generators,
-    // that means doing the work below. It's only 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.
+  if (resumeMode != ResumeMode::Return || !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.
+  if (frame.callee()->isGenerator()) {
+    // 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);
         if (!pair) {
-          // Out of memory in debuggee code. Arrange for this to propagate.
-          MOZ_ALWAYS_TRUE(cx->getPendingException(vp));
-          cx->clearPendingException();
-          resumeMode = ResumeMode::Throw;
+          getAndClearExceptionThenThrow();
           return;
         }
         vp.setObject(*pair);
       }
 
       // 2.  The generator must be closed.
       genObj->setClosed();
     } else {
       // 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)) {
+      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);
+      }
+
+      // 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.
+    }
   }
 }
 
 ResumeMode Debugger::reportUncaughtException(Maybe<AutoRealm>& ar) {
   JSContext* cx = ar->context();
 
   // Uncaught exceptions arise from Debugger code, and so we must already be
   // in an NX section.
@@ -2206,19 +2238,20 @@ void Debugger::slowPathOnNewWasmInstance
         RootedValue scriptFrame(cx);
         if (!dbg->getFrame(cx, iter, &scriptFrame)) {
           return dbg->reportUncaughtException(ar);
         }
         RootedValue rv(cx);
         Rooted<JSObject*> handler(cx, bp->handler);
         bool ok = CallMethodIfPresent(cx, handler, "hit", 1,
                                       scriptFrame.address(), &rv);
-        adjqi.runJobs();
         ResumeMode resumeMode = dbg->processHandlerResult(
             ar, ok, rv, iter.abstractFramePtr(), iter.pc(), vp);
+        adjqi.runJobs();
+
         if (resumeMode != ResumeMode::Continue) {
           savedExc.drop();
           return resumeMode;
         }
 
         // Calling JS code invalidates site. Reload it.
         if (isJS) {
           site = iter.script()->getBreakpointSite(pc);
@@ -2309,19 +2342,20 @@ void Debugger::slowPathOnNewWasmInstance
       Debugger* dbg = Debugger::fromChildJSObject(frame);
       EnterDebuggeeNoExecute nx(cx, *dbg, adjqi);
 
       Maybe<AutoRealm> ar;
       ar.emplace(cx, dbg->object);
 
       ResumeMode resumeMode = ResumeMode::Continue;
       bool success = handler->onStep(cx, frame, resumeMode, vp);
-      adjqi.runJobs();
       resumeMode = dbg->processParsedHandlerResult(
           ar, iter.abstractFramePtr(), iter.pc(), success, resumeMode, vp);
+      adjqi.runJobs();
+
       if (resumeMode != ResumeMode::Continue) {
         savedExc.drop();
         return resumeMode;
       }
     }
   }
 
   vp.setUndefined();
@@ -8668,21 +8702,41 @@ void ScriptedOnPopHandler::trace(JSTrace
   TraceEdge(tracer, &object_, "OnStepHandlerFunction.object");
 }
 
 bool ScriptedOnPopHandler::onPop(JSContext* cx, HandleDebuggerFrame frame,
                                  ResumeMode& resumeMode,
                                  MutableHandleValue vp) {
   Debugger* dbg = frame->owner();
 
+  // Make it possible to distinguish 'return' from 'await' completions.
+  // Bug 1470558 will investigate a more robust solution.
+  bool isAfterAwait = false;
+  AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
+  if (resumeMode == ResumeMode::Return && referent &&
+      referent.isFunctionFrame() && referent.callee()->isAsync() &&
+      !referent.callee()->isGenerator()) {
+    AutoRealm ar(cx, referent.callee());
+    if (auto* genObj = GetGeneratorObjectForFrame(cx, referent)) {
+      isAfterAwait = !genObj->isClosed() && genObj->isRunning();
+    }
+  }
+
   RootedValue completion(cx);
   if (!dbg->newCompletionValue(cx, resumeMode, vp, &completion)) {
     return false;
   }
 
+  if (isAfterAwait) {
+    RootedObject obj(cx, &completion.toObject());
+    if (!DefineDataProperty(cx, obj, cx->names().await, TrueHandleValue)) {
+      return false;
+    }
+  }
+
   RootedValue fval(cx, ObjectValue(*object_));
   RootedValue rval(cx);
   if (!js::Call(cx, fval, frame, completion, &rval)) {
     return false;
   }
 
   return ParseResumptionValue(cx, rval, resumeMode, vp);
 };
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1942,17 +1942,16 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
     }
 
     /* Various 1-byte no-ops. */
     CASE(JSOP_NOP)
     CASE(JSOP_NOP_DESTRUCTURING)
     CASE(JSOP_TRY_DESTRUCTURING)
     CASE(JSOP_UNUSED71)
     CASE(JSOP_UNUSED151)
-    CASE(JSOP_UNUSED192)
     CASE(JSOP_TRY)
     CASE(JSOP_CONDSWITCH) {
       MOZ_ASSERT(CodeSpec[*REGS.pc].length == 1);
       ADVANCE_AND_DISPATCH(1);
     }
 
     CASE(JSOP_JUMPTARGET)
     CASE(JSOP_LOOPHEAD) {
@@ -3620,16 +3619,32 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
         REGS.sp[-1] = resolved;
         PUSH_BOOLEAN(true);
       } else {
         PUSH_BOOLEAN(false);
       }
     }
     END_CASE(JSOP_TRYSKIPAWAIT)
 
+    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);
+      if (!promise) {
+        goto error;
+      }
+
+      REGS.sp--;
+      REGS.sp[-1].setObject(*promise);
+    }
+    END_CASE(JSOP_ASYNCRESOLVE)
+
     CASE(JSOP_SETFUNNAME) {
       MOZ_ASSERT(REGS.stackDepth() >= 2);
       FunctionPrefixKind prefixKind = FunctionPrefixKind(GET_UINT8(REGS.pc));
       ReservedRooted<Value> name(&rootValue0, REGS.sp[-1]);
       ReservedRooted<JSFunction*> fun(&rootFunction0,
                                       &REGS.sp[-2].toObject().as<JSFunction>());
       if (!SetFunctionName(cx, fun, name, prefixKind)) {
         goto error;
--- a/js/src/vm/JSContext.cpp
+++ b/js/src/vm/JSContext.cpp
@@ -1149,43 +1149,47 @@ JSObject* InternalJobQueue::maybeFront()
     return nullptr;
   }
 
   return queue.get().front();
 }
 
 class js::InternalJobQueue::SavedQueue : public JobQueue::SavedJobQueue {
  public:
-  SavedQueue(JSContext* cx, Queue&& saved)
-      : cx(cx), saved(cx, std::move(saved)) {
+  SavedQueue(JSContext* cx, Queue&& saved, bool draining)
+      : cx(cx), saved(cx, std::move(saved)), draining_(draining) {
     MOZ_ASSERT(cx->internalJobQueue.ref());
   }
 
   ~SavedQueue() {
     MOZ_ASSERT(cx->internalJobQueue.ref());
     cx->internalJobQueue->queue = std::move(saved.get());
+    cx->internalJobQueue->draining_ = draining_;
   }
 
  private:
   JSContext* cx;
   PersistentRooted<Queue> saved;
+  bool draining_;
 };
 
 js::UniquePtr<JS::JobQueue::SavedJobQueue> InternalJobQueue::saveJobQueue(
     JSContext* cx) {
-  auto saved = js::MakeUnique<SavedQueue>(cx, std::move(queue.get()));
+  auto saved =
+      js::MakeUnique<SavedQueue>(cx, std::move(queue.get()), draining_);
   if (!saved) {
     // When MakeUnique's allocation fails, the SavedQueue constructor is never
     // called, so this->queue is still initialized. (The move doesn't occur
     // until the constructor gets called.)
     ReportOutOfMemory(cx);
     return nullptr;
   }
 
   queue = Queue(SystemAllocPolicy());
+  draining_ = false;
   return saved;
 }
 
 JS::Error JSContext::reportedError;
 JS::OOM JSContext::reportedOOM;
 
 mozilla::GenericErrorResult<OOM&> JSContext::alreadyReportedOOM() {
 #ifdef DEBUG
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -2062,18 +2062,27 @@
      *
      *   Category: Variables and Scopes
      *   Type: This
      *   Operands:
      *   Stack: this => this
      */ \
     MACRO(JSOP_CHECKTHISREINIT, 191, "checkthisreinit", NULL, 1, 1, 1, JOF_BYTE) \
     /*
+     * Pops the top two values 'valueOrReason' and 'gen' from the stack, then
+     * pushes the promise resolved with 'valueOrReason'. `gen` must be the
+     * internal generator object created in async functions. The pushed promise
+     * is the async function's result promise, which is stored in `gen`.
+     *
+     *   Category: Statements
+     *   Type: Generator
+     *   Operands: uint8_t fulfillOrReject
+     *   Stack: valueOrReason, gen => promise
      */ \
-    MACRO(JSOP_UNUSED192, 192, "unused192", NULL, 1, 0, 0, JOF_BYTE) \
+    MACRO(JSOP_ASYNCRESOLVE, 192, "async-resolve", NULL, 2, 2, 1, JOF_UINT8) \
     /*
      * Pops the top two values on the stack as 'propval' and 'obj', pushes
      * 'propval' property of 'obj' onto the stack.
      *
      * Like JSOP_GETELEM but for call context.
      *
      *   Category: Literals
      *   Type: Object