Bug 1331092 - Part 2: Implement Async Generator except yield*. r=shu
authorTooru Fujisawa <arai_a@mac.com>
Mon, 27 Mar 2017 23:20:17 +0900
changeset 398428 28bce039877f2be2a1a24318f59754cec62fd771
parent 398427 d1c1a6c478cb8e9430d5bdc5c84a8f009b1cbfb0
child 398429 dee4db84bae086b860d80c2134fb29aa5501cf28
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersshu
bugs1331092
milestone55.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 1331092 - Part 2: Implement Async Generator except yield*. r=shu
js/public/Class.h
js/src/builtin/AsyncIteration.js
js/src/builtin/Promise.cpp
js/src/builtin/Promise.h
js/src/frontend/BytecodeCompiler.cpp
js/src/frontend/BytecodeCompiler.h
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/Parser.cpp
js/src/js.msg
js/src/jsapi.cpp
js/src/jsfun.cpp
js/src/jsfun.h
js/src/jsiter.cpp
js/src/jsiter.h
js/src/moz.build
js/src/shell/js.cpp
js/src/tests/ecma_2017/AsyncFunctions/syntax.js
js/src/vm/AsyncIteration.cpp
js/src/vm/AsyncIteration.h
js/src/vm/CommonPropertyNames.h
js/src/vm/GlobalObject.h
js/src/vm/Interpreter.cpp
js/src/vm/Opcodes.h
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -847,17 +847,17 @@ struct JSClass {
 // with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was
 // previously allowed, but is now an ES5 violation and thus unsupported.
 //
 // JSCLASS_GLOBAL_APPLICATION_SLOTS is the number of slots reserved at
 // the beginning of every global object's slots for use by the
 // application.
 #define JSCLASS_GLOBAL_APPLICATION_SLOTS 5
 #define JSCLASS_GLOBAL_SLOT_COUNT                                             \
-    (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 41)
+    (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 45)
 #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n)                                    \
     (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
 #define JSCLASS_GLOBAL_FLAGS                                                  \
     JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(0)
 #define JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(clasp)                              \
   (((clasp)->flags & JSCLASS_IS_GLOBAL)                                       \
    && JSCLASS_RESERVED_SLOTS(clasp) >= JSCLASS_GLOBAL_SLOT_COUNT)
 
new file mode 100644
--- /dev/null
+++ b/js/src/builtin/AsyncIteration.js
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function AsyncIteratorIdentity() {
+    return this;
+}
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -7,20 +7,22 @@
 
 #include "builtin/Promise.h"
 
 #include "mozilla/Atomics.h"
 #include "mozilla/TimeStamp.h"
 
 #include "jscntxt.h"
 #include "jsexn.h"
+#include "jsiter.h"
 
 #include "gc/Heap.h"
 #include "js/Debug.h"
 #include "vm/AsyncFunction.h"
+#include "vm/AsyncIteration.h"
 
 #include "jsobjinlines.h"
 
 #include "vm/NativeObject-inl.h"
 
 using namespace js;
 
 static double
@@ -31,16 +33,26 @@ MillisecondsSinceStartup()
     return (now - mozilla::TimeStamp::ProcessCreation(ignored)).ToMilliseconds();
 }
 
 enum PromiseHandler {
     PromiseHandlerIdentity = 0,
     PromiseHandlerThrower,
     PromiseHandlerAsyncFunctionAwaitFulfilled,
     PromiseHandlerAsyncFunctionAwaitRejected,
+    PromiseHandlerAsyncGeneratorAwaitFulfilled,
+    PromiseHandlerAsyncGeneratorAwaitRejected,
+
+    // Async Iteration proposal 6.1.1.2.1.
+    // Async iterator handlers take the resolved value and create new iterator
+    // objects.  To do so it needs to forward whether the iterator is done. In
+    // spec, this is achieved via the [[Done]] internal slot. We enumerate both
+    // true and false cases here.
+    PromiseHandlerAsyncIteratorValueUnwrapDone,
+    PromiseHandlerAsyncIteratorValueUnwrapNotDone,
 };
 
 enum ResolutionMode {
     ResolveMode,
     RejectMode
 };
 
 enum ResolveFunctionSlots {
@@ -173,23 +185,25 @@ enum ReactionRecordSlots {
     ReactionRecordSlot_Promise = 0,
     ReactionRecordSlot_OnFulfilled,
     ReactionRecordSlot_OnRejected,
     ReactionRecordSlot_Resolve,
     ReactionRecordSlot_Reject,
     ReactionRecordSlot_IncumbentGlobalObject,
     ReactionRecordSlot_Flags,
     ReactionRecordSlot_HandlerArg,
+    ReactionRecordSlot_Generator,
     ReactionRecordSlots,
 };
 
 #define REACTION_FLAG_RESOLVED                  0x1
 #define REACTION_FLAG_FULFILLED                 0x2
 #define REACTION_FLAG_IGNORE_DEFAULT_RESOLUTION 0x4
 #define REACTION_FLAG_ASYNC_FUNCTION_AWAIT      0x8
+#define REACTION_FLAG_ASYNC_GENERATOR_AWAIT     0x10
 
 // ES2016, 25.4.1.2.
 class PromiseReactionRecord : public NativeObject
 {
   public:
     static const Class class_;
 
     JSObject* promise() { return getFixedSlot(ReactionRecordSlot_Promise).toObjectOrNull(); }
@@ -215,16 +229,32 @@ class PromiseReactionRecord : public Nat
         int32_t flags = this->flags();
         flags |= REACTION_FLAG_ASYNC_FUNCTION_AWAIT;
         setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags));
     }
     bool isAsyncFunctionAwait() {
         int32_t flags = this->flags();
         return flags & REACTION_FLAG_ASYNC_FUNCTION_AWAIT;
     }
+    void setIsAsyncGeneratorAwait(Handle<AsyncGeneratorObject*> asyncGenObj) {
+        int32_t flags = this->flags();
+        flags |= REACTION_FLAG_ASYNC_GENERATOR_AWAIT;
+        setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags));
+
+        setFixedSlot(ReactionRecordSlot_Generator, ObjectValue(*asyncGenObj));
+    }
+    bool isAsyncGeneratorAwait() {
+        int32_t flags = this->flags();
+        return flags & REACTION_FLAG_ASYNC_GENERATOR_AWAIT;
+    }
+    AsyncGeneratorObject* asyncGenerator() {
+        MOZ_ASSERT(isAsyncGeneratorAwait());
+        return &getFixedSlot(ReactionRecordSlot_Generator).toObject()
+                                                          .as<AsyncGeneratorObject>();
+    }
     Value handler() {
         MOZ_ASSERT(targetState() != JS::PromiseState::Pending);
         uint32_t slot = targetState() == JS::PromiseState::Fulfilled
                         ? ReactionRecordSlot_OnFulfilled
                         : ReactionRecordSlot_OnRejected;
         return getFixedSlot(slot);
     }
     Value handlerArg() {
@@ -839,16 +869,44 @@ AsyncFunctionAwaitPromiseReactionJob(JSC
         if (!AsyncFunctionAwaitedRejected(cx, resultPromise, generatorVal, argument))
             return false;
     }
 
     rval.setUndefined();
     return true;
 }
 
+static MOZ_MUST_USE bool
+AsyncGeneratorAwaitPromiseReactionJob(JSContext* cx, Handle<PromiseReactionRecord*> reaction,
+                                      MutableHandleValue rval)
+{
+    MOZ_ASSERT(reaction->isAsyncGeneratorAwait());
+
+    RootedValue handlerVal(cx, reaction->handler());
+    RootedValue argument(cx, reaction->handlerArg());
+    Rooted<AsyncGeneratorObject*> asyncGenObj(cx, reaction->asyncGenerator());
+
+    int32_t handlerNum = int32_t(handlerVal.toNumber());
+    MOZ_ASSERT(handlerNum == PromiseHandlerAsyncGeneratorAwaitFulfilled ||
+               handlerNum == PromiseHandlerAsyncGeneratorAwaitRejected);
+
+    // Await's handlers don't return a value, nor throw exception.
+    // They fail only on OOM.
+    if (handlerNum == PromiseHandlerAsyncGeneratorAwaitFulfilled) {
+        if (!AsyncGeneratorAwaitedFulfilled(cx, asyncGenObj, argument))
+            return false;
+    } else {
+        if (!AsyncGeneratorAwaitedRejected(cx, asyncGenObj, argument))
+            return false;
+    }
+
+    rval.setUndefined();
+    return true;
+}
+
 // ES2016, 25.4.2.1.
 /**
  * Callback triggering the fulfill/reject reaction for a resolved Promise,
  * to be invoked by the embedding during its processing of the Promise job
  * queue.
  *
  * See http://www.ecma-international.org/ecma-262/7.0/index.html#sec-jobs-and-job-queues
  *
@@ -877,37 +935,49 @@ PromiseReactionJob(JSContext* cx, unsign
         reactionObj = UncheckedUnwrap(reactionObj);
         ac.emplace(cx, reactionObj);
     }
 
     // Steps 1-2.
     Rooted<PromiseReactionRecord*> reaction(cx, &reactionObj->as<PromiseReactionRecord>());
     if (reaction->isAsyncFunctionAwait())
         return AsyncFunctionAwaitPromiseReactionJob(cx, reaction, args.rval());
+    if (reaction->isAsyncGeneratorAwait())
+        return AsyncGeneratorAwaitPromiseReactionJob(cx, reaction, args.rval());
 
     // Step 3.
     RootedValue handlerVal(cx, reaction->handler());
 
     RootedValue argument(cx, reaction->handlerArg());
 
     RootedValue handlerResult(cx);
     ResolutionMode resolutionMode = ResolveMode;
 
     // Steps 4-6.
     if (handlerVal.isNumber()) {
         int32_t handlerNum = int32_t(handlerVal.toNumber());
 
         // Step 4.
         if (handlerNum == PromiseHandlerIdentity) {
             handlerResult = argument;
-        } else {
+        } else if (handlerNum == PromiseHandlerThrower) {
             // Step 5.
-            MOZ_ASSERT(handlerNum == PromiseHandlerThrower);
             resolutionMode = RejectMode;
             handlerResult = argument;
+        } else {
+            MOZ_ASSERT(handlerNum == PromiseHandlerAsyncIteratorValueUnwrapDone ||
+                       handlerNum == PromiseHandlerAsyncIteratorValueUnwrapNotDone);
+
+            bool done = handlerNum == PromiseHandlerAsyncIteratorValueUnwrapDone;
+            // Async Iteration proposal 6.1.1.2.1 step 1.
+            RootedObject resultObj(cx, CreateIterResultObject(cx, argument, done));
+            if (!resultObj)
+                return false;
+
+            handlerResult = ObjectValue(*resultObj);
         }
     } else {
         // Step 6.
         FixedInvokeArgs<1> args2(cx);
         args2[0].set(argument);
         if (!Call(cx, handlerVal, UndefinedHandleValue, args2, &handlerResult)) {
             resolutionMode = RejectMode;
             if (!MaybeGetAndClearException(cx, &handlerResult))
@@ -2186,16 +2256,195 @@ js::AsyncFunctionAwait(JSContext* cx, Ha
         return false;
 
     reaction->setIsAsyncFunctionAwait();
 
     // Step 8.
     return PerformPromiseThenWithReaction(cx, promise, reaction);
 }
 
+// Async Iteration proposal 5.1 steps 2-9.
+MOZ_MUST_USE bool
+js::AsyncGeneratorAwait(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+                        HandleValue value)
+{
+    // Step 2.
+    Rooted<PromiseObject*> promise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx));
+    if (!promise)
+        return false;
+
+    // Steps 3.
+    if (!ResolvePromiseInternal(cx, promise, value))
+        return false;
+
+    // Steps 4-5.
+    RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAsyncGeneratorAwaitFulfilled));
+    RootedValue onRejected(cx, Int32Value(PromiseHandlerAsyncGeneratorAwaitRejected));
+
+    RootedObject incumbentGlobal(cx);
+    if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobal))
+        return false;
+
+    // Step 6 (skipped).
+
+    // Steps 7-8.
+    Rooted<PromiseReactionRecord*> reaction(cx, NewReactionRecord(cx, nullptr,
+                                                                  onFulfilled, onRejected,
+                                                                  nullptr, nullptr,
+                                                                  incumbentGlobal));
+    if (!reaction)
+        return false;
+
+    reaction->setIsAsyncGeneratorAwait(asyncGenObj);
+
+    // Step 9.
+    return PerformPromiseThenWithReaction(cx, promise, reaction);
+}
+
+// Async Iteration proposal 6.4.3.3.
+MOZ_MUST_USE bool
+js::AsyncGeneratorResolve(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+                          HandleValue value, bool done)
+{
+    // Step 1 (implicit).
+
+    // Steps 2-3.
+    MOZ_ASSERT(!asyncGenObj->isQueueEmpty());
+
+    // Step 4.
+    Rooted<AsyncGeneratorRequest*> request(
+        cx, AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj));
+    if (!request)
+        return false;
+
+    // Step 5.
+    RootedObject resultPromise(cx, request->promise());
+
+    // Step 6.
+    Rooted<PromiseObject*> promise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx));
+    if (!promise)
+        return false;
+
+    // Step 7.
+    if (!ResolvePromiseInternal(cx, promise, value))
+        return false;
+
+    // Steps 8-9.
+    RootedValue onFulfilled(cx, Int32Value(done
+                                           ? PromiseHandlerAsyncIteratorValueUnwrapDone
+                                           : PromiseHandlerAsyncIteratorValueUnwrapNotDone));
+
+    RootedObject incumbentGlobal(cx);
+    if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobal))
+        return false;
+
+    // Step 10.
+    Rooted<PromiseReactionRecord*> reaction(cx, NewReactionRecord(cx, resultPromise, onFulfilled,
+                                                                  UndefinedHandleValue,
+                                                                  nullptr, nullptr,
+                                                                  incumbentGlobal));
+    if (!reaction)
+        return false;
+
+    if (!PerformPromiseThenWithReaction(cx, promise, reaction))
+        return false;
+
+    // Step 11.
+    if (!AsyncGeneratorResumeNext(cx, asyncGenObj))
+        return false;
+
+    // Step 12.
+    return true;
+}
+
+// Async Iteration proposal 6.4.3.4.
+MOZ_MUST_USE bool
+js::AsyncGeneratorReject(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+                         HandleValue exception)
+{
+    // Step 1 (implicit).
+
+    // Steps 2-3.
+    MOZ_ASSERT(!asyncGenObj->isQueueEmpty());
+
+    // Step 4.
+    Rooted<AsyncGeneratorRequest*> request(
+        cx, AsyncGeneratorObject::dequeueRequest(cx, asyncGenObj));
+    if (!request)
+        return false;
+
+    // Step 5.
+    RootedObject resultPromise(cx, request->promise());
+
+    // Step 6.
+    if (!RejectMaybeWrappedPromise(cx, resultPromise, exception))
+        return false;
+
+    // Step 7.
+    if (!AsyncGeneratorResumeNext(cx, asyncGenObj))
+        return false;
+
+    // Step 8.
+    return true;
+}
+
+// Async Iteration proposal 6.4.3.6.
+MOZ_MUST_USE bool
+js::AsyncGeneratorEnqueue(JSContext* cx, HandleValue asyncGenVal,
+                          CompletionKind completionKind, HandleValue completionValue,
+                          MutableHandleValue result)
+{
+    // Step 1 (implicit).
+
+    // Step 2.
+    RootedObject resultPromise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx));
+    if (!resultPromise)
+        return false;
+
+    // Step 3.
+    if (!asyncGenVal.isObject() || !asyncGenVal.toObject().is<AsyncGeneratorObject>()) {
+        // Step 3.a.
+        RootedValue badGeneratorError(cx);
+        if (!GetTypeError(cx, JSMSG_NOT_AN_ASYNC_GENERATOR, &badGeneratorError))
+            return false;
+
+        // Step 3.b.
+        if (!RejectMaybeWrappedPromise(cx, resultPromise, badGeneratorError))
+            return false;
+
+        // Step 3.c.
+        result.setObject(*resultPromise);
+        return true;
+    }
+
+    Rooted<AsyncGeneratorObject*> asyncGenObj(
+        cx, &asyncGenVal.toObject().as<AsyncGeneratorObject>());
+
+    // Step 5 (reordered).
+    Rooted<AsyncGeneratorRequest*> request(
+        cx, AsyncGeneratorRequest::create(cx, completionKind, completionValue, resultPromise));
+    if (!request)
+        return false;
+
+    // Steps 4, 6.
+    if (!AsyncGeneratorObject::enqueueRequest(cx, asyncGenObj, request))
+        return false;
+
+    // Step 7.
+    if (!asyncGenObj->isExecuting()) {
+        // Step 8.
+        if (!AsyncGeneratorResumeNext(cx, asyncGenObj))
+            return false;
+    }
+
+    // Step 9.
+    result.setObject(*resultPromise);
+    return true;
+}
+
 // ES2016, 25.4.5.3.
 bool
 js::Promise_then(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
     RootedValue promiseVal(cx, args.thisv());
--- a/js/src/builtin/Promise.h
+++ b/js/src/builtin/Promise.h
@@ -130,16 +130,33 @@ MOZ_MUST_USE bool
 AsyncFunctionReturned(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue value);
 
 MOZ_MUST_USE bool
 AsyncFunctionThrown(JSContext* cx, Handle<PromiseObject*> resultPromise);
 
 MOZ_MUST_USE bool
 AsyncFunctionAwait(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue value);
 
+class AsyncGeneratorObject;
+
+MOZ_MUST_USE bool
+AsyncGeneratorAwait(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, HandleValue value);
+
+MOZ_MUST_USE bool
+AsyncGeneratorResolve(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+                      HandleValue value, bool done);
+
+MOZ_MUST_USE bool
+AsyncGeneratorReject(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+                     HandleValue exception);
+
+MOZ_MUST_USE bool
+AsyncGeneratorEnqueue(JSContext* cx, HandleValue asyncGenVal, CompletionKind completionKind,
+                      HandleValue completionValue, MutableHandleValue result);
+
 /**
  * A PromiseTask represents a task that can be dispatched to a helper thread
  * (via StartPromiseTask), executed (by implementing PromiseTask::execute()),
  * and then resolved back on the original JSContext owner thread.
  * Because it contains a PersistentRooted, a PromiseTask will only be destroyed
  * on the JSContext's owner thread.
  */
 class PromiseTask : public JS::AsyncTask
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -773,8 +773,20 @@ frontend::CompileStandaloneAsyncFunction
                                          JS::SourceBufferHolder& srcBuf,
                                          const Maybe<uint32_t>& parameterListEnd)
 {
     RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
 
     BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, emptyGlobalScope);
     return compiler.compileStandaloneFunction(fun, NotGenerator, AsyncFunction, parameterListEnd);
 }
+
+bool
+frontend::CompileStandaloneAsyncGenerator(JSContext* cx, MutableHandleFunction fun,
+                                          const ReadOnlyCompileOptions& options,
+                                          JS::SourceBufferHolder& srcBuf,
+                                          const Maybe<uint32_t>& parameterListEnd)
+{
+    RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
+
+    BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, emptyGlobalScope);
+    return compiler.compileStandaloneFunction(fun, StarGenerator, AsyncFunction, parameterListEnd);
+}
--- a/js/src/frontend/BytecodeCompiler.h
+++ b/js/src/frontend/BytecodeCompiler.h
@@ -85,16 +85,22 @@ CompileStandaloneGenerator(JSContext* cx
 
 MOZ_MUST_USE bool
 CompileStandaloneAsyncFunction(JSContext* cx, MutableHandleFunction fun,
                                const ReadOnlyCompileOptions& options,
                                JS::SourceBufferHolder& srcBuf,
                                const mozilla::Maybe<uint32_t>& parameterListEnd);
 
 MOZ_MUST_USE bool
+CompileStandaloneAsyncGenerator(JSContext* cx, MutableHandleFunction fun,
+                                const ReadOnlyCompileOptions& options,
+                                JS::SourceBufferHolder& srcBuf,
+                                const mozilla::Maybe<uint32_t>& parameterListEnd);
+
+MOZ_MUST_USE bool
 CompileAsyncFunctionBody(JSContext* cx, MutableHandleFunction fun,
                          const ReadOnlyCompileOptions& options,
                          Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf);
 
 ScriptSourceObject*
 CreateScriptSourceObject(JSContext* cx, const ReadOnlyCompileOptions& options,
                          const mozilla::Maybe<uint32_t>& parameterListEnd = mozilla::Nothing());
 
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -7836,17 +7836,18 @@ BytecodeEmitter::emitFunction(ParseNode*
     unsigned index = objectList.add(pn->pn_funbox);
 
     /* Non-hoisted functions simply emit their respective op. */
     if (!pn->functionIsHoisted()) {
         /* JSOP_LAMBDA_ARROW is always preceded by a new.target */
         MOZ_ASSERT(fun->isArrow() == (pn->getOp() == JSOP_LAMBDA_ARROW));
         if (funbox->isAsync()) {
             MOZ_ASSERT(!needsProto);
-            return emitAsyncWrapper(index, funbox->needsHomeObject(), fun->isArrow());
+            return emitAsyncWrapper(index, funbox->needsHomeObject(), fun->isArrow(),
+                                    fun->isStarGenerator());
         }
 
         if (fun->isArrow()) {
             if (sc->allowNewTarget()) {
                 if (!emit1(JSOP_NEWTARGET))
                     return false;
             } else {
                 if (!emit1(JSOP_NULL))
@@ -7891,37 +7892,42 @@ BytecodeEmitter::emitFunction(ParseNode*
             RootedModuleObject module(cx, sc->asModuleContext()->module());
             if (!module->noteFunctionDeclaration(cx, name, fun))
                 return false;
         } else {
             MOZ_ASSERT(sc->isGlobalContext() || sc->isEvalContext());
             MOZ_ASSERT(pn->getOp() == JSOP_NOP);
             switchToPrologue();
             if (funbox->isAsync()) {
-                if (!emitAsyncWrapper(index, fun->isMethod(), fun->isArrow()))
+                if (!emitAsyncWrapper(index, fun->isMethod(), fun->isArrow(),
+                                      fun->isStarGenerator()))
+                {
                     return false;
+                }
             } else {
                 if (!emitIndex32(JSOP_LAMBDA, index))
                     return false;
             }
             if (!emit1(JSOP_DEFFUN))
                 return false;
             if (!updateSourceCoordNotes(pn->pn_pos.begin))
                 return false;
             switchToMain();
         }
     } else {
         // For functions nested within functions and blocks, make a lambda and
         // initialize the binding name of the function in the current scope.
 
         bool isAsync = funbox->isAsync();
-        auto emitLambda = [index, isAsync](BytecodeEmitter* bce, const NameLocation&, bool) {
+        bool isStarGenerator = funbox->isStarGenerator();
+        auto emitLambda = [index, isAsync, isStarGenerator](BytecodeEmitter* bce,
+                                                            const NameLocation&, bool) {
             if (isAsync) {
                 return bce->emitAsyncWrapper(index, /* needsHomeObject = */ false,
-                                             /* isArrow = */ false);
+                                             /* isArrow = */ false, isStarGenerator);
             }
             return bce->emitIndexOp(JSOP_LAMBDA, index);
         };
 
         if (!emitInitializeName(name, emitLambda))
             return false;
         if (!emit1(JSOP_POP))
             return false;
@@ -7946,17 +7952,18 @@ BytecodeEmitter::emitAsyncWrapperLambda(
         if (!emitIndex32(JSOP_LAMBDA, index))
             return false;
     }
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow)
+BytecodeEmitter::emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow,
+                                  bool isStarGenerator)
 {
     // needsHomeObject can be true for propertyList for extended class.
     // In that case push both unwrapped and wrapped function, in order to
     // initialize home object of unwrapped function, and set wrapped function
     // as a property.
     //
     //   lambda       // unwrapped
     //   dup          // unwrapped unwrapped
@@ -7978,18 +7985,23 @@ BytecodeEmitter::emitAsyncWrapper(unsign
     //
     // needsHomeObject is false for other cases, push wrapped function only.
     if (!emitAsyncWrapperLambda(index, isArrow))
         return false;
     if (needsHomeObject) {
         if (!emit1(JSOP_DUP))
             return false;
     }
-    if (!emit1(JSOP_TOASYNC))
-        return false;
+    if (isStarGenerator) {
+        if (!emit1(JSOP_TOASYNCGEN))
+            return false;
+    } else {
+        if (!emit1(JSOP_TOASYNC))
+            return false;
+    }
     return true;
 }
 
 bool
 BytecodeEmitter::emitDo(ParseNode* pn)
 {
     /* Emit an annotated nop so IonBuilder can recognize the 'do' loop. */
     unsigned noteIndex;
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -614,17 +614,18 @@ struct MOZ_STACK_CLASS BytecodeEmitter
     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);
+    MOZ_MUST_USE bool emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow,
+                                       bool isStarGenerator);
 
     MOZ_MUST_USE bool emitComputedPropertyName(ParseNode* computedPropName);
 
     // Emit bytecode to put operands for a JSOP_GETELEM/CALLELEM/SETELEM/DELELEM
     // opcode onto the stack in the right order. In the case of SETELEM, the
     // value to be assigned must already be pushed.
     enum class EmitElemOption { Get, Set, Call, IncDec, CompoundAssign, Ref };
     MOZ_MUST_USE bool emitElemOperands(ParseNode* pn, EmitElemOption opts);
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -3748,20 +3748,22 @@ Parser<ParseHandler>::functionStmt(uint3
     }
 
     TokenKind tt;
     if (!tokenStream.getToken(&tt))
         return null();
 
     GeneratorKind generatorKind = NotGenerator;
     if (tt == TOK_MUL) {
+#ifdef RELEASE_OR_BETA
         if (asyncKind != SyncFunction) {
             error(JSMSG_ASYNC_GENERATOR);
             return null();
         }
+#endif
         generatorKind = StarGenerator;
         if (!tokenStream.getToken(&tt))
             return null();
     }
 
     RootedPropertyName name(context);
     if (TokenKindIsPossibleIdentifier(tt)) {
         name = bindingIdentifier(yieldHandling);
@@ -3820,20 +3822,22 @@ Parser<ParseHandler>::functionExpr(uint3
 
     AutoAwaitIsKeyword<ParseHandler> awaitIsKeyword(this, asyncKind == AsyncFunction);
     GeneratorKind generatorKind = NotGenerator;
     TokenKind tt;
     if (!tokenStream.getToken(&tt))
         return null();
 
     if (tt == TOK_MUL) {
+#ifdef RELEASE_OR_BETA
         if (asyncKind != SyncFunction) {
             error(JSMSG_ASYNC_GENERATOR);
             return null();
         }
+#endif
         generatorKind = StarGenerator;
         if (!tokenStream.getToken(&tt))
             return null();
     }
 
     YieldHandling yieldHandling = GetYieldHandling(generatorKind);
 
     RootedPropertyName name(context);
@@ -9283,21 +9287,16 @@ Parser<ParseHandler>::propertyName(Yield
     TokenKind ltok;
     if (!tokenStream.getToken(&ltok))
         return null();
 
     MOZ_ASSERT(ltok != TOK_RC, "caller should have handled TOK_RC");
 
     bool isGenerator = false;
     bool isAsync = false;
-    if (ltok == TOK_MUL) {
-        isGenerator = true;
-        if (!tokenStream.getToken(&ltok))
-            return null();
-    }
 
     if (ltok == TOK_ASYNC) {
         // AsyncMethod[Yield, Await]:
         //   async [no LineTerminator here] PropertyName[?Yield, ?Await] ...
         //
         // PropertyName:
         //   LiteralPropertyName
         //   ComputedPropertyName[?Yield, ?Await]
@@ -9316,19 +9315,26 @@ Parser<ParseHandler>::propertyName(Yield
             TokenKindIsPossibleIdentifierName(tt))
         {
             isAsync = true;
             tokenStream.consumeKnownToken(tt);
             ltok = tt;
         }
     }
 
-    if (isAsync && isGenerator) {
-        error(JSMSG_ASYNC_GENERATOR);
-        return null();
+    if (ltok == TOK_MUL) {
+#ifdef RELEASE_OR_BETA
+        if (isAsync) {
+            error(JSMSG_ASYNC_GENERATOR);
+            return null();
+        }
+#endif
+        isGenerator = true;
+        if (!tokenStream.getToken(&ltok))
+            return null();
     }
 
     propAtom.set(nullptr);
     Node propName;
     switch (ltok) {
       case TOK_NUMBER:
         propAtom.set(DoubleToAtom(context, tokenStream.currentToken().number()));
         if (!propAtom.get())
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -586,8 +586,11 @@ MSG_DEF(JSMSG_CANNOT_RESOLVE_PROMISE_WIT
 MSG_DEF(JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY, 0, JSEXN_TYPEERR, "GetCapabilitiesExecutor function already invoked with non-undefined values.")
 MSG_DEF(JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE,    0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the resolve function.")
 MSG_DEF(JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE,     0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the reject function.")
 MSG_DEF(JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON,0, JSEXN_INTERNALERR, "Promise rejection value is a non-unwrappable cross-compartment wrapper.")
 
 // Iterator
 MSG_DEF(JSMSG_RETURN_NOT_CALLABLE,     0, JSEXN_TYPEERR, "property 'return' of iterator is not callable")
 MSG_DEF(JSMSG_ITERATOR_NO_THROW,       0, JSEXN_TYPEERR, "iterator does not have a 'throw' method")
+
+// Async Iteration
+MSG_DEF(JSMSG_NOT_AN_ASYNC_GENERATOR,  0, JSEXN_TYPEERR, "Not an async generator")
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -66,16 +66,17 @@
 #include "js/Conversions.h"
 #include "js/Date.h"
 #include "js/Initialization.h"
 #include "js/Proxy.h"
 #include "js/SliceBudget.h"
 #include "js/StructuredClone.h"
 #include "js/Utility.h"
 #include "vm/AsyncFunction.h"
+#include "vm/AsyncIteration.h"
 #include "vm/DateObject.h"
 #include "vm/Debugger.h"
 #include "vm/EnvironmentObject.h"
 #include "vm/ErrorObject.h"
 #include "vm/HelperThreads.h"
 #include "vm/Interpreter.h"
 #include "vm/RegExpStatics.h"
 #include "vm/Runtime.h"
@@ -3610,16 +3611,21 @@ CloneFunctionObject(JSContext* cx, Handl
         return nullptr;
     }
 
     if (IsWrappedAsyncFunction(fun)) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CLONE_OBJECT);
         return nullptr;
     }
 
+    if (IsWrappedAsyncGenerator(fun)) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CLONE_OBJECT);
+        return nullptr;
+    }
+
     if (CanReuseScriptForClone(cx->compartment(), fun, env)) {
         // If the script is to be reused, either the script can already handle
         // non-syntactic scopes, or there is only the standard global lexical
         // scope.
 #ifdef DEBUG
         // Fail here if we OOM during debug asserting.
         // CloneFunctionReuseScript will delazify the script anyways, so we
         // are not creating an extra failure condition for DEBUG builds.
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -36,16 +36,17 @@
 #include "gc/Marking.h"
 #include "gc/Policy.h"
 #include "jit/InlinableNatives.h"
 #include "jit/Ion.h"
 #include "jit/JitFrameIterator.h"
 #include "js/CallNonGenericMethod.h"
 #include "js/Proxy.h"
 #include "vm/AsyncFunction.h"
+#include "vm/AsyncIteration.h"
 #include "vm/Debugger.h"
 #include "vm/GlobalObject.h"
 #include "vm/Interpreter.h"
 #include "vm/SelfHosting.h"
 #include "vm/Shape.h"
 #include "vm/SharedImmutableStringsCache.h"
 #include "vm/StringBuffer.h"
 #include "vm/WrapperObject.h"
@@ -301,16 +302,18 @@ CallerGetterImpl(JSContext* cx, const Ca
         if (!callerObj) {
             args.rval().setNull();
             return true;
         }
 
         JSFunction* callerFun = &callerObj->as<JSFunction>();
         if (IsWrappedAsyncFunction(callerFun))
             callerFun = GetUnwrappedAsyncFunction(callerFun);
+        else if (IsWrappedAsyncGenerator(callerFun))
+            callerFun = GetUnwrappedAsyncGenerator(callerFun);
         MOZ_ASSERT(!callerFun->isBuiltin(), "non-builtin iterator returned a builtin?");
 
         if (callerFun->strict()) {
             JS_ReportErrorFlagsAndNumberASCII(cx, JSREPORT_ERROR, GetErrorMessage, nullptr,
                                               JSMSG_CALLER_IS_STRICT);
             return false;
         }
     }
@@ -397,49 +400,53 @@ static const JSPropertySpec function_pro
     JS_PSGS("arguments", ArgumentsGetter, ArgumentsSetter, 0),
     JS_PSGS("caller", CallerGetter, CallerSetter, 0),
     JS_PS_END
 };
 
 static bool
 ResolveInterpretedFunctionPrototype(JSContext* cx, HandleFunction fun, HandleId id)
 {
-    MOZ_ASSERT(fun->isInterpreted() || fun->isAsmJSNative());
+    bool isAsyncGenerator = IsWrappedAsyncGenerator(fun);
+
+    MOZ_ASSERT_IF(!isAsyncGenerator, fun->isInterpreted() || fun->isAsmJSNative());
     MOZ_ASSERT(id == NameToId(cx->names().prototype));
 
     // Assert that fun is not a compiler-created function object, which
     // must never leak to script or embedding code and then be mutated.
     // Also assert that fun is not bound, per the ES5 15.3.4.5 ref above.
-    MOZ_ASSERT(!IsInternalFunctionObject(*fun));
+    MOZ_ASSERT_IF(!isAsyncGenerator, !IsInternalFunctionObject(*fun));
     MOZ_ASSERT(!fun->isBoundFunction());
 
     // Make the prototype object an instance of Object with the same parent as
     // the function object itself, unless the function is an ES6 generator.  In
     // that case, per the 15 July 2013 ES6 draft, section 15.19.3, its parent is
     // the GeneratorObjectPrototype singleton.
     bool isStarGenerator = fun->isStarGenerator();
     Rooted<GlobalObject*> global(cx, &fun->global());
     RootedObject objProto(cx);
-    if (isStarGenerator)
+    if (isAsyncGenerator)
+        objProto = GlobalObject::getOrCreateAsyncGeneratorPrototype(cx, global);
+    else if (isStarGenerator)
         objProto = GlobalObject::getOrCreateStarGeneratorObjectPrototype(cx, global);
     else
         objProto = GlobalObject::getOrCreateObjectPrototype(cx, global);
     if (!objProto)
         return false;
 
     RootedPlainObject proto(cx, NewObjectWithGivenProto<PlainObject>(cx, objProto,
                                                                      SingletonObject));
     if (!proto)
         return false;
 
     // Per ES5 13.2 the prototype's .constructor property is configurable,
     // non-enumerable, and writable.  However, per the 15 July 2013 ES6 draft,
     // section 15.19.3, the .prototype of a generator function does not link
     // back with a .constructor.
-    if (!isStarGenerator) {
+    if (!isStarGenerator && !isAsyncGenerator) {
         RootedValue objVal(cx, ObjectValue(*fun));
         if (!DefineProperty(cx, proto, cx->names().constructor, objVal, nullptr, nullptr, 0))
             return false;
     }
 
     // Per ES5 15.3.5.2 a user-defined function's .prototype property is
     // initially non-configurable, non-enumerable, and writable.
     RootedValue protoVal(cx, ObjectValue(*proto));
@@ -479,21 +486,23 @@ fun_resolve(JSContext* cx, HandleObject 
          * Generators are not constructors, but they have a .prototype property anyway,
          * according to errata to ES6. See bug 1191486.
          *
          * Thus all of the following don't get a .prototype property:
          * - Methods (that are not class-constructors or generators)
          * - Arrow functions
          * - Function.prototype
          */
-        if (fun->isBuiltin())
-            return true;
-        if (!fun->isConstructor()) {
-            if (!fun->isStarGenerator() && !fun->isLegacyGenerator() && !fun->isAsync())
+        if (!IsWrappedAsyncGenerator(fun)) {
+            if (fun->isBuiltin())
                 return true;
+            if (!fun->isConstructor()) {
+                if (!fun->isStarGenerator() && !fun->isLegacyGenerator() && !fun->isAsync())
+                    return true;
+            }
         }
 
         if (!ResolveInterpretedFunctionPrototype(cx, fun, id))
             return false;
 
         *resolvedp = true;
         return true;
     }
@@ -980,16 +989,20 @@ js::FunctionToString(JSContext* cx, Hand
         return AsmJSModuleToString(cx, fun, !prettyPrint);
     if (IsAsmJSFunction(fun))
         return AsmJSFunctionToString(cx, fun);
 
     if (IsWrappedAsyncFunction(fun)) {
         RootedFunction unwrapped(cx, GetUnwrappedAsyncFunction(fun));
         return FunctionToString(cx, unwrapped, prettyPrint);
     }
+    if (IsWrappedAsyncGenerator(fun)) {
+        RootedFunction unwrapped(cx, GetUnwrappedAsyncGenerator(fun));
+        return FunctionToString(cx, unwrapped, prettyPrint);
+    }
 
     StringBuffer out(cx);
     RootedScript script(cx);
 
     if (fun->hasScript()) {
         script = fun->nonLazyScript();
         if (script->isGeneratorExp()) {
             if (!out.append("function genexp() {") ||
@@ -1632,20 +1645,24 @@ FunctionConstructor(JSContext* cx, const
     const char* filename;
     unsigned lineno;
     bool mutedErrors;
     uint32_t pcOffset;
     DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset,
                                          &mutedErrors);
 
     const char* introductionType = "Function";
-    if (isAsync)
-        introductionType = "AsyncFunction";
-    else if (generatorKind != NotGenerator)
+    if (isAsync) {
+        if (isStarGenerator)
+            introductionType = "AsyncGenerator";
+        else
+            introductionType = "AsyncFunction";
+    } else if (generatorKind != NotGenerator) {
         introductionType = "GeneratorFunction";
+    }
 
     const char* introducerFilename = filename;
     if (maybeScript && maybeScript->scriptSource()->introducerFilename())
         introducerFilename = maybeScript->scriptSource()->introducerFilename();
 
     CompileOptions options(cx);
     options.setMutedErrors(mutedErrors)
            .setFileAndLine(filename, 1)
@@ -1762,22 +1779,30 @@ FunctionConstructor(JSContext* cx, const
         return false;
 
     mozilla::Range<const char16_t> chars = stableChars.twoByteRange();
     SourceBufferHolder::Ownership ownership = stableChars.maybeGiveOwnershipToCaller()
                                               ? SourceBufferHolder::GiveOwnership
                                               : SourceBufferHolder::NoOwnership;
     bool ok;
     SourceBufferHolder srcBuf(chars.begin().get(), chars.length(), ownership);
-    if (isAsync)
-        ok = frontend::CompileStandaloneAsyncFunction(cx, &fun, options, srcBuf, parameterListEnd);
-    else if (isStarGenerator)
-        ok = frontend::CompileStandaloneGenerator(cx, &fun, options, srcBuf, parameterListEnd);
-    else
-        ok = frontend::CompileStandaloneFunction(cx, &fun, options, srcBuf, parameterListEnd);
+    if (isAsync) {
+        if (isStarGenerator) {
+            ok = frontend::CompileStandaloneAsyncGenerator(cx, &fun, options, srcBuf,
+                                                           parameterListEnd);
+        } else {
+            ok = frontend::CompileStandaloneAsyncFunction(cx, &fun, options, srcBuf,
+                                                          parameterListEnd);
+        }
+    } else {
+        if (isStarGenerator)
+            ok = frontend::CompileStandaloneGenerator(cx, &fun, options, srcBuf, parameterListEnd);
+        else
+            ok = frontend::CompileStandaloneFunction(cx, &fun, options, srcBuf, parameterListEnd);
+    }
 
     // Step 33.
     args.rval().setObject(*fun);
     return ok;
 }
 
 bool
 js::Function(JSContext* cx, unsigned argc, Value* vp)
@@ -1793,17 +1818,17 @@ js::Generator(JSContext* cx, unsigned ar
     return FunctionConstructor(cx, args, StarGenerator, SyncFunction);
 }
 
 bool
 js::AsyncFunctionConstructor(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    // Save the callee before its reset in FunctionConstructor().
+    // Save the callee before it's reset in FunctionConstructor().
     RootedObject newTarget(cx);
     if (args.isConstructing())
         newTarget = &args.newTarget().toObject();
     else
         newTarget = &args.callee();
 
     if (!FunctionConstructor(cx, args, NotGenerator, AsyncFunction))
         return false;
@@ -1826,16 +1851,50 @@ js::AsyncFunctionConstructor(JSContext* 
     if (!wrapped)
         return false;
 
     args.rval().setObject(*wrapped);
     return true;
 }
 
 bool
+js::AsyncGeneratorConstructor(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    // Save the callee before its reset in FunctionConstructor().
+    RootedObject newTarget(cx);
+    if (args.isConstructing())
+        newTarget = &args.newTarget().toObject();
+    else
+        newTarget = &args.callee();
+
+    if (!FunctionConstructor(cx, args, StarGenerator, AsyncFunction))
+        return false;
+
+    RootedObject proto(cx);
+    if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
+        return false;
+
+    if (!proto) {
+        proto = GlobalObject::getOrCreateAsyncGenerator(cx, cx->global());
+        if (!proto)
+            return false;
+    }
+
+    RootedFunction unwrapped(cx, &args.rval().toObject().as<JSFunction>());
+    RootedObject wrapped(cx, WrapAsyncGeneratorWithProto(cx, unwrapped, proto));
+    if (!wrapped)
+        return false;
+
+    args.rval().setObject(*wrapped);
+    return true;
+}
+
+bool
 JSFunction::isBuiltinFunctionConstructor()
 {
     return maybeNative() == Function || maybeNative() == Generator;
 }
 
 bool
 JSFunction::needsExtraBodyVarEnvironment() const
 {
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -642,16 +642,19 @@ extern bool
 Function(JSContext* cx, unsigned argc, Value* vp);
 
 extern bool
 Generator(JSContext* cx, unsigned argc, Value* vp);
 
 extern bool
 AsyncFunctionConstructor(JSContext* cx, unsigned argc, Value* vp);
 
+extern bool
+AsyncGeneratorConstructor(JSContext* cx, unsigned argc, Value* vp);
+
 // Allocate a new function backed by a JSNative.  Note that by default this
 // creates a singleton object.
 extern JSFunction*
 NewNativeFunction(JSContext* cx, JSNative native, unsigned nargs, HandleAtom atom,
                   gc::AllocKind allocKind = gc::AllocKind::FUNCTION,
                   NewObjectKind newKind = SingletonObject);
 
 // Allocate a new constructor backed by a JSNative.  Note that by default this
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -933,38 +933,40 @@ JSObject*
 js::GetIteratorObject(JSContext* cx, HandleObject obj, uint32_t flags)
 {
     RootedObject iterator(cx);
     if (!GetIterator(cx, obj, flags, &iterator))
         return nullptr;
     return iterator;
 }
 
+// ES 2017 draft 7.4.7.
 JSObject*
-js::CreateItrResultObject(JSContext* cx, HandleValue value, bool done)
+js::CreateIterResultObject(JSContext* cx, HandleValue value, bool done)
 {
-    // FIXME: We can cache the iterator result object shape somewhere.
-    AssertHeapIsIdle();
+    // Step 1 (implicit).
 
-    RootedObject proto(cx, GlobalObject::getOrCreateObjectPrototype(cx, cx->global()));
-    if (!proto)
+    // Step 2.
+    RootedObject resultObj(cx, NewBuiltinClassInstance<PlainObject>(cx));
+    if (!resultObj)
         return nullptr;
 
-    RootedPlainObject obj(cx, NewObjectWithGivenProto<PlainObject>(cx, proto));
-    if (!obj)
+    // Step 3.
+    if (!DefineProperty(cx, resultObj, cx->names().value, value))
         return nullptr;
 
-    if (!DefineProperty(cx, obj, cx->names().value, value))
+    // Step 4.
+    if (!DefineProperty(cx, resultObj, cx->names().done,
+                        done ? TrueHandleValue : FalseHandleValue))
+    {
         return nullptr;
+    }
 
-    RootedValue doneBool(cx, BooleanValue(done));
-    if (!DefineProperty(cx, obj, cx->names().done, doneBool))
-        return nullptr;
-
-    return obj;
+    // Step 5.
+    return resultObj;
 }
 
 bool
 js::ThrowStopIteration(JSContext* cx)
 {
     MOZ_ASSERT(!JS_IsExceptionPending(cx));
 
     // StopIteration isn't a constructor, but it's stored in GlobalObject
--- a/js/src/jsiter.h
+++ b/js/src/jsiter.h
@@ -205,20 +205,20 @@ SuppressDeletedElement(JSContext* cx, Ha
 extern bool
 IteratorMore(JSContext* cx, HandleObject iterobj, MutableHandleValue rval);
 
 extern bool
 ThrowStopIteration(JSContext* cx);
 
 /*
  * Create an object of the form { value: VALUE, done: DONE }.
- * ES6 draft from 2013-09-05, section 25.4.3.4.
+ * ES 2017 draft 7.4.7.
  */
 extern JSObject*
-CreateItrResultObject(JSContext* cx, HandleValue value, bool done);
+CreateIterResultObject(JSContext* cx, HandleValue value, bool done);
 
 extern JSObject*
 InitLegacyIteratorClass(JSContext* cx, HandleObject obj);
 
 extern JSObject*
 InitStopIterationClass(JSContext* cx, HandleObject obj);
 
 } /* namespace js */
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -306,16 +306,17 @@ UNIFIED_SOURCES += [
     'proxy/ScriptedProxyHandler.cpp',
     'proxy/SecurityWrapper.cpp',
     'proxy/Wrapper.cpp',
     'threading/Mutex.cpp',
     'threading/ProtectedData.cpp',
     'vm/ArgumentsObject.cpp',
     'vm/ArrayBufferObject.cpp',
     'vm/AsyncFunction.cpp',
+    'vm/AsyncIteration.cpp',
     'vm/Caches.cpp',
     'vm/CallNonGenericMethod.cpp',
     'vm/CharacterEncoding.cpp',
     'vm/CodeCoverage.cpp',
     'vm/Compression.cpp',
     'vm/DateTime.cpp',
     'vm/Debugger.cpp',
     'vm/DebuggerMemory.cpp',
@@ -748,16 +749,17 @@ GENERATED_FILES += [('selfhosted.out.h',
 selfhosted = GENERATED_FILES[('selfhosted.out.h', 'selfhosted.js')]
 selfhosted.script = 'builtin/embedjs.py:generate_selfhosted'
 selfhosted.inputs = [
     'js.msg',
     'builtin/TypedObjectConstants.h',
     'builtin/SelfHostingDefines.h',
     'builtin/Utilities.js',
     'builtin/Array.js',
+    'builtin/AsyncIteration.js',
     'builtin/Classes.js',
     'builtin/Date.js',
     'builtin/Error.js',
     'builtin/Function.js',
     'builtin/Generator.js',
     'builtin/Intl.js',
     'builtin/IntlCurrency.js',
     'builtin/IntlData.js',
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -81,16 +81,17 @@
 #include "shell/jsoptparse.h"
 #include "shell/jsshell.h"
 #include "shell/OSObject.h"
 #include "threading/ConditionVariable.h"
 #include "threading/LockGuard.h"
 #include "threading/Thread.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/AsyncFunction.h"
+#include "vm/AsyncIteration.h"
 #include "vm/Compression.h"
 #include "vm/Debugger.h"
 #include "vm/HelperThreads.h"
 #include "vm/Monitor.h"
 #include "vm/MutexIDs.h"
 #include "vm/Shape.h"
 #include "vm/SharedArrayObject.h"
 #include "vm/StringBuffer.h"
@@ -2390,16 +2391,18 @@ ValueToScript(JSContext* cx, HandleValue
             fun = &target->as<JSFunction>();
         else
             break;
     }
 
     // Get unwrapped async function.
     if (IsWrappedAsyncFunction(fun))
         fun = GetUnwrappedAsyncFunction(fun);
+    if (IsWrappedAsyncGenerator(fun))
+        fun = GetUnwrappedAsyncGenerator(fun);
 
     if (!fun->isInterpreted()) {
         JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_SCRIPTS_ONLY);
         return nullptr;
     }
 
     JSScript* script = JSFunction::getOrCreateScript(cx, fun);
     if (!script)
--- a/js/src/tests/ecma_2017/AsyncFunctions/syntax.js
+++ b/js/src/tests/ecma_2017/AsyncFunctions/syntax.js
@@ -4,19 +4,16 @@ var summary = "async/await syntax";
 print(BUGNUMBER + ": " + summary);
 
 if (typeof Reflect !== "undefined" && Reflect.parse) {
     assertEq(Reflect.parse("function a() {}").body[0].async, false);
     assertEq(Reflect.parse("function* a() {}").body[0].async, false);
     assertEq(Reflect.parse("async function a() {}").body[0].async, true);
     assertEq(Reflect.parse("() => {}").body[0].async, undefined);
 
-    // Async generators are not allowed (with regards to spec)
-    assertThrows(() => Reflect.parse("async function* a() {}"), SyntaxError);
-
     // No line terminator after async
     assertEq(Reflect.parse("async\nfunction a(){}").body[0].expression.name, "async");
 
     // Async function expressions
     assertEq(Reflect.parse("(async function() {})()").body[0].expression.callee.async, true);
     assertEq(Reflect.parse("var k = async function() {}").body[0].declarations[0].init.async, true);
     assertEq(Reflect.parse("var nmd = async function named() {}").body[0].declarations[0].init.id.name, "named");
 
new file mode 100644
--- /dev/null
+++ b/js/src/vm/AsyncIteration.cpp
@@ -0,0 +1,564 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "vm/AsyncIteration.h"
+
+#include "jsarray.h"
+#include "jscompartment.h"
+
+#include "builtin/Promise.h"
+#include "vm/GeneratorObject.h"
+#include "vm/GlobalObject.h"
+#include "vm/Interpreter.h"
+#include "vm/SelfHosting.h"
+
+#include "jscntxtinlines.h"
+#include "jsobjinlines.h"
+
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+using namespace js::gc;
+
+#define UNWRAPPED_ASYNC_WRAPPED_SLOT 1
+#define WRAPPED_ASYNC_UNWRAPPED_SLOT 0
+
+// Async Iteration proposal 2.3.10 Runtime Semantics: EvaluateBody.
+static bool
+WrappedAsyncGenerator(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    RootedFunction wrapped(cx, &args.callee().as<JSFunction>());
+    RootedValue unwrappedVal(cx, wrapped->getExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT));
+    RootedFunction unwrapped(cx, &unwrappedVal.toObject().as<JSFunction>());
+    RootedValue thisValue(cx, args.thisv());
+
+    // Step 1.
+    RootedValue generatorVal(cx);
+    InvokeArgs args2(cx);
+    if (!args2.init(cx, argc))
+        return false;
+    for (size_t i = 0, len = argc; i < len; i++)
+        args2[i].set(args[i]);
+    if (!Call(cx, unwrappedVal, thisValue, args2, &generatorVal))
+        return false;
+
+    // Step 2.
+    Rooted<AsyncGeneratorObject*> asyncGenObj(
+        cx, AsyncGeneratorObject::create(cx, wrapped, generatorVal));
+    if (!asyncGenObj)
+        return false;
+
+    // Step 3 (skipped).
+    // Done in AsyncGeneratorObject::create and generator.
+
+    // Step 4.
+    args.rval().setObject(*asyncGenObj);
+    return true;
+}
+
+JSObject*
+js::WrapAsyncGeneratorWithProto(JSContext* cx, HandleFunction unwrapped, HandleObject proto)
+{
+    MOZ_ASSERT(unwrapped->isAsync());
+    MOZ_ASSERT(proto, "We need an explicit prototype to avoid the default"
+                      "%FunctionPrototype% fallback in NewFunctionWithProto().");
+
+    // Create a new function with AsyncGeneratorPrototype, reusing the name and
+    // the length of `unwrapped`.
+
+    RootedAtom funName(cx, unwrapped->explicitName());
+    uint16_t length;
+    if (!JSFunction::getLength(cx, unwrapped, &length))
+        return nullptr;
+
+    RootedFunction wrapped(cx, NewFunctionWithProto(cx, WrappedAsyncGenerator, length,
+                                                    JSFunction::NATIVE_FUN, nullptr,
+                                                    funName, proto,
+                                                    AllocKind::FUNCTION_EXTENDED,
+                                                    TenuredObject));
+    if (!wrapped)
+        return nullptr;
+
+    if (unwrapped->hasCompileTimeName())
+        wrapped->setCompileTimeName(unwrapped->compileTimeName());
+
+    // Link them to each other to make GetWrappedAsyncGenerator and
+    // GetUnwrappedAsyncGenerator work.
+    unwrapped->setExtendedSlot(UNWRAPPED_ASYNC_WRAPPED_SLOT, ObjectValue(*wrapped));
+    wrapped->setExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT, ObjectValue(*unwrapped));
+
+    return wrapped;
+}
+
+JSObject*
+js::WrapAsyncGenerator(JSContext* cx, HandleFunction unwrapped)
+{
+    RootedObject proto(cx, GlobalObject::getOrCreateAsyncGenerator(cx, cx->global()));
+    if (!proto)
+        return nullptr;
+
+    return WrapAsyncGeneratorWithProto(cx, unwrapped, proto);
+}
+
+bool
+js::IsWrappedAsyncGenerator(JSFunction* fun)
+{
+    return fun->maybeNative() == WrappedAsyncGenerator;
+}
+
+JSFunction*
+js::GetWrappedAsyncGenerator(JSFunction* unwrapped)
+{
+    MOZ_ASSERT(unwrapped->isAsync());
+    return &unwrapped->getExtendedSlot(UNWRAPPED_ASYNC_WRAPPED_SLOT).toObject().as<JSFunction>();
+}
+
+JSFunction*
+js::GetUnwrappedAsyncGenerator(JSFunction* wrapped)
+{
+    MOZ_ASSERT(IsWrappedAsyncGenerator(wrapped));
+    JSFunction* unwrapped = &wrapped->getExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT)
+                            .toObject().as<JSFunction>();
+    MOZ_ASSERT(unwrapped->isAsync());
+    return unwrapped;
+}
+
+static MOZ_MUST_USE bool
+AsyncGeneratorResume(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+                     CompletionKind completionKind, HandleValue argument);
+
+// Async Iteration proposal 5.1.1 Await Fulfilled Functions.
+MOZ_MUST_USE bool
+js::AsyncGeneratorAwaitedFulfilled(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+                                   HandleValue value)
+{
+    return AsyncGeneratorResume(cx, asyncGenObj, CompletionKind::Normal, value);
+}
+
+// Async Iteration proposal 5.1.2 Await Rejected Functions.
+MOZ_MUST_USE bool
+js::AsyncGeneratorAwaitedRejected(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+                                  HandleValue reason)
+{
+    return AsyncGeneratorResume(cx, asyncGenObj, CompletionKind::Throw, reason);
+}
+
+// Async Iteration proposal 6.4.1.2 AsyncGenerator.prototype.next.
+static bool
+AsyncGeneratorNext(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    // Steps 1-3.
+    return AsyncGeneratorEnqueue(cx, args.thisv(), CompletionKind::Normal, args.get(0),
+                                 args.rval());
+}
+
+// Async Iteration proposal 6.4.1.3 AsyncGenerator.prototype.return.
+static bool
+AsyncGeneratorReturn(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    // Steps 1-3.
+    return AsyncGeneratorEnqueue(cx, args.thisv(), CompletionKind::Return, args.get(0),
+                                 args.rval());
+}
+
+// Async Iteration proposal 6.4.1.4 AsyncGenerator.prototype.throw.
+static bool
+AsyncGeneratorThrow(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    // Steps 1-3.
+    return AsyncGeneratorEnqueue(cx, args.thisv(), CompletionKind::Throw, args.get(0),
+                                 args.rval());
+}
+
+const Class AsyncGeneratorObject::class_ = {
+    "AsyncGenerator",
+    JSCLASS_HAS_RESERVED_SLOTS(AsyncGeneratorObject::Slots)
+};
+
+// ES 2017 draft 9.1.13.
+template <typename ProtoGetter>
+static JSObject*
+OrdinaryCreateFromConstructor(JSContext* cx, HandleFunction fun,
+                              ProtoGetter protoGetter, const Class* clasp)
+{
+    // Step 1 (skipped).
+
+    // Step 2.
+    RootedValue protoVal(cx);
+    if (!GetProperty(cx, fun, fun, cx->names().prototype, &protoVal))
+        return nullptr;
+
+    RootedObject proto(cx, protoVal.isObject() ? &protoVal.toObject() : nullptr);
+    if (!proto) {
+        proto = protoGetter(cx, cx->global());
+        if (!proto)
+            return nullptr;
+    }
+
+    // Step 3.
+    return NewNativeObjectWithGivenProto(cx, clasp, proto);
+}
+
+/* static */ AsyncGeneratorObject*
+AsyncGeneratorObject::create(JSContext* cx, HandleFunction asyncGen, HandleValue generatorVal)
+{
+    MOZ_ASSERT(generatorVal.isObject());
+    MOZ_ASSERT(generatorVal.toObject().is<GeneratorObject>());
+
+    RootedObject obj(
+        cx, OrdinaryCreateFromConstructor(cx, asyncGen,
+                                          GlobalObject::getOrCreateAsyncGeneratorPrototype,
+                                          &class_));
+    if (!obj)
+        return nullptr;
+
+    Handle<AsyncGeneratorObject*> asyncGenObj = obj.as<AsyncGeneratorObject>();
+
+    // Async Iteration proposal 6.4.3.2 AsyncGeneratorStart.
+    // Step 6.
+    asyncGenObj->setGenerator(generatorVal);
+
+    // Step 7.
+    asyncGenObj->setSuspendedStart();
+
+    // Step 8.
+    asyncGenObj->clearSingleQueueRequest();
+
+    return asyncGenObj;
+}
+
+/* static */ MOZ_MUST_USE bool
+AsyncGeneratorObject::enqueueRequest(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+                                     Handle<AsyncGeneratorRequest*> request)
+{
+    if (asyncGenObj->isSingleQueue()) {
+        if (asyncGenObj->isSingleQueueEmpty()) {
+            asyncGenObj->setSingleQueueRequest(request);
+            return true;
+        }
+
+        RootedArrayObject queue(cx, NewDenseEmptyArray(cx));
+        if (!queue)
+            return false;
+
+        if (!NewbornArrayPush(cx, queue, ObjectValue(*asyncGenObj->singleQueueRequest())))
+            return false;
+        if (!NewbornArrayPush(cx, queue, ObjectValue(*request)))
+            return false;
+
+        asyncGenObj->setQueue(queue);
+        return true;
+    }
+
+    RootedArrayObject queue(cx, asyncGenObj->queue());
+
+    FixedInvokeArgs<1> args(cx);
+    args[0].setObject(*request);
+    args.setThis(ObjectValue(*queue));
+    return CallJSNative(cx, array_push, args);
+}
+
+/* static */ AsyncGeneratorRequest*
+AsyncGeneratorObject::dequeueRequest(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj)
+{
+    if (asyncGenObj->isSingleQueue()) {
+        AsyncGeneratorRequest* request = asyncGenObj->singleQueueRequest();
+        asyncGenObj->clearSingleQueueRequest();
+        return request;
+    }
+
+    RootedArrayObject queue(cx, asyncGenObj->queue());
+
+    FixedInvokeArgs<0> args(cx);
+    args.setThis(ObjectValue(*queue));
+    if (!CallJSNative(cx, array_shift, args))
+        return nullptr;
+
+    return &args.rval().toObject().as<AsyncGeneratorRequest>();
+}
+
+/* static */ AsyncGeneratorRequest*
+AsyncGeneratorObject::peekRequest(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj)
+{
+    if (asyncGenObj->isSingleQueue())
+        return asyncGenObj->singleQueueRequest();
+
+    RootedArrayObject queue(cx, asyncGenObj->queue());
+
+    RootedValue requestVal(cx);
+    if (!GetElement(cx, queue, queue, 0, &requestVal))
+        return nullptr;
+
+    return &requestVal.toObject().as<AsyncGeneratorRequest>();
+}
+
+const Class AsyncGeneratorRequest::class_ = {
+    "AsyncGeneratorRequest",
+    JSCLASS_HAS_RESERVED_SLOTS(AsyncGeneratorRequest::Slots)
+};
+
+// Async Iteration proposal 6.4.3.1.
+/* static */ AsyncGeneratorRequest*
+AsyncGeneratorRequest::create(JSContext* cx, CompletionKind completionKind_,
+                              HandleValue completionValue_, HandleObject promise_)
+{
+    RootedObject obj(cx, NewNativeObjectWithGivenProto(cx, &class_, nullptr));
+    if (!obj)
+        return nullptr;
+
+    Handle<AsyncGeneratorRequest*> request = obj.as<AsyncGeneratorRequest>();
+    request->setCompletionKind(completionKind_);
+    request->setCompletionValue(completionValue_);
+    request->setPromise(promise_);
+    return request;
+}
+
+// Async Iteration proposal 6.4.3.2 steps 5.d-g.
+static MOZ_MUST_USE bool
+AsyncGeneratorReturned(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+                       HandleValue value)
+{
+    // Step 5.d.
+    asyncGenObj->setCompleted();
+
+    // Step 5.e (done in bytecode).
+    // Step 5.f.i (implicit).
+
+    // Step 5.g.
+    return AsyncGeneratorResolve(cx, asyncGenObj, value, true);
+}
+
+// Async Iteration proposal 6.4.3.2 steps 5.d, f.
+static MOZ_MUST_USE bool
+AsyncGeneratorThrown(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj)
+{
+    // Step 5.d.
+    asyncGenObj->setCompleted();
+
+    // Not much we can do about uncatchable exceptions, so just bail.
+    if (!cx->isExceptionPending())
+        return false;
+
+    // Step 5.f.i.
+    RootedValue value(cx);
+    if (!GetAndClearException(cx, &value))
+        return false;
+
+    // Step 5.f.ii.
+    return AsyncGeneratorReject(cx, asyncGenObj, value);
+}
+
+// Async Iteration proposal 6.4.3.5.
+MOZ_MUST_USE bool
+js::AsyncGeneratorResumeNext(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj)
+{
+    // Step 1 (implicit).
+
+    // Steps 2-3.
+    MOZ_ASSERT(!asyncGenObj->isExecuting());
+
+    // Steps 4-5.
+    if (asyncGenObj->isQueueEmpty())
+        return true;
+
+    // Steps 6-7.
+    Rooted<AsyncGeneratorRequest*> request(
+        cx, AsyncGeneratorObject::peekRequest(cx, asyncGenObj));
+    if (!request)
+        return false;
+
+    // Step 8.
+    CompletionKind completionKind = request->completionKind();
+
+    // Step 9.
+    if (completionKind != CompletionKind::Normal) {
+        // Step 9.a.
+        if (asyncGenObj->isSuspendedStart())
+            asyncGenObj->setCompleted();
+
+        // Step 9.b.
+        if (asyncGenObj->isCompleted()) {
+            // Step 9.b.i.
+            RootedValue value(cx, request->completionValue());
+            if (completionKind == CompletionKind::Return)
+                return AsyncGeneratorResolve(cx, asyncGenObj, value, true);
+            // Step 9.b.ii.
+            return AsyncGeneratorReject(cx, asyncGenObj, value);
+        }
+    } else if (asyncGenObj->isCompleted()) {
+        // Step 10.
+        return AsyncGeneratorResolve(cx, asyncGenObj, UndefinedHandleValue, true);
+    }
+
+    // Step 11.
+    MOZ_ASSERT(asyncGenObj->isSuspendedStart() || asyncGenObj->isSuspendedYield());
+
+    // Step 15 (reordered).
+    asyncGenObj->setExecuting();
+
+    RootedValue argument(cx, request->completionValue());
+
+    // Steps 12-14, 16-20.
+    return AsyncGeneratorResume(cx, asyncGenObj, completionKind, argument);
+}
+
+// Async Iteration proposal 6.2.1.2 (partially).
+// Most steps are done in generator.
+static MOZ_MUST_USE bool
+AsyncGeneratorYield(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+                    HandleValue value, bool done)
+{
+    // Step 6.
+    asyncGenObj->setSuspendedYield();
+
+    // Step 10.c.
+    return AsyncGeneratorResolve(cx, asyncGenObj, value, done);
+}
+
+// Async Iteration proposal 6.4.3.5 steps 12-14, 16-20.
+// Async Iteration proposal 6.2.1.2 step 10.
+// Async Iteration proposal 6.4.3.2 step 5.f-g.
+// Async Iteration proposal 5.1 steps 2-9.
+// Execution context switching is handled in generator.
+static MOZ_MUST_USE bool
+AsyncGeneratorResume(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+                     CompletionKind completionKind, HandleValue argument)
+{
+    RootedValue generatorVal(cx, asyncGenObj->generatorVal());
+
+    // 6.4.3.5 steps 12-14, 16-20.
+    HandlePropertyName funName = completionKind == CompletionKind::Normal
+                                 ? cx->names().StarGeneratorNext
+                                 : completionKind == CompletionKind::Throw
+                                 ? cx->names().StarGeneratorThrow
+                                 : cx->names().StarGeneratorReturn;
+    FixedInvokeArgs<1> args(cx);
+    args[0].set(argument);
+    RootedValue result(cx);
+    if (!CallSelfHostedFunction(cx, funName, generatorVal, args, &result)) {
+        // 6.4.3.2 step 5.d, f.
+        return AsyncGeneratorThrown(cx, asyncGenObj);
+    }
+
+    // The following code corresponds to the following 3 cases:
+    //   * yield
+    //   * await
+    //   * return
+    // For await and return, property access is done on an internal result
+    // object and it's not observable.
+    // For yield, it's done on an user-provided result object, and it's
+    // observable, so perform in that order in all cases.
+
+    RootedObject resultObj(cx, &result.toObject());
+    RootedValue value(cx);
+    RootedValue doneVal(cx);
+
+    // 6.2.1.2 step 10.a.
+    if (!GetProperty(cx, resultObj, resultObj, cx->names().value, &value))
+        return false;
+
+    // 6.2.1.2 step 10.b.
+    if (!GetProperty(cx, resultObj, resultObj, cx->names().done, &doneVal))
+        return false;
+
+    // 6.2.1.2 step 10.c.
+    if (asyncGenObj->generatorObj()->isAfterYield())
+        return AsyncGeneratorYield(cx, asyncGenObj, value, ToBoolean(doneVal));
+
+    // 6.4.3.2 step 5.d-g.
+    if (ToBoolean(doneVal)) {
+        MOZ_ASSERT(!asyncGenObj->generatorObj()->isAfterAwait());
+        return AsyncGeneratorReturned(cx, asyncGenObj, value);
+    }
+
+    MOZ_ASSERT(asyncGenObj->generatorObj()->isAfterAwait());
+
+    // 5.1 steps 2-9.
+    return AsyncGeneratorAwait(cx, asyncGenObj, value);
+}
+
+static const JSFunctionSpec async_iterator_proto_methods[] = {
+    JS_SELF_HOSTED_SYM_FN(asyncIterator, "AsyncIteratorIdentity", 0, 0),
+    JS_FS_END
+};
+
+static const JSFunctionSpec async_generator_methods[] = {
+    JS_FN("next", AsyncGeneratorNext, 1, 0),
+    JS_FN("throw", AsyncGeneratorThrow, 1, 0),
+    JS_FN("return", AsyncGeneratorReturn, 1, 0),
+    JS_FS_END
+};
+
+/* static */ MOZ_MUST_USE bool
+GlobalObject::initAsyncGenerators(JSContext* cx, Handle<GlobalObject*> global)
+{
+    if (global->getReservedSlot(ASYNC_ITERATOR_PROTO).isObject())
+        return true;
+
+    // Async Iteration proposal 6.1.2 %AsyncIteratorPrototype%.
+    RootedObject asyncIterProto(cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global));
+    if (!asyncIterProto)
+        return false;
+    if (!DefinePropertiesAndFunctions(cx, asyncIterProto, nullptr, async_iterator_proto_methods))
+        return false;
+
+    // Async Iteration proposal 6.4.1 %AsyncGeneratorPrototype%.
+    RootedObject asyncGenProto(
+        cx, GlobalObject::createBlankPrototypeInheriting(cx, global, &PlainObject::class_,
+                                                         asyncIterProto));
+    if (!asyncGenProto)
+        return false;
+    if (!DefinePropertiesAndFunctions(cx, asyncGenProto, nullptr, async_generator_methods) ||
+        !DefineToStringTag(cx, asyncGenProto, cx->names().AsyncGenerator))
+    {
+        return false;
+    }
+
+    // Async Iteration proposal 6.3.3 %AsyncGenerator%.
+    RootedObject asyncGenerator(cx, NewSingletonObjectWithFunctionPrototype(cx, global));
+    if (!asyncGenerator)
+        return false;
+    if (!JSObject::setDelegate(cx, asyncGenerator))
+        return false;
+    if (!LinkConstructorAndPrototype(cx, asyncGenerator, asyncGenProto, JSPROP_READONLY,
+                                     JSPROP_READONLY) ||
+        !DefineToStringTag(cx, asyncGenerator, cx->names().AsyncGeneratorFunction))
+    {
+        return false;
+    }
+
+    RootedValue function(cx, global->getConstructor(JSProto_Function));
+    if (!function.toObjectOrNull())
+        return false;
+    RootedObject proto(cx, &function.toObject());
+    RootedAtom name(cx, cx->names().AsyncGeneratorFunction);
+
+    // Async Iteration proposal 6.3.2 %AsyncGeneratorFunction%.
+    RootedObject asyncGenFunction(
+        cx, NewFunctionWithProto(cx, AsyncGeneratorConstructor, 1, JSFunction::NATIVE_CTOR,
+                                 nullptr, name, proto, gc::AllocKind::FUNCTION, SingletonObject));
+    if (!asyncGenFunction)
+        return false;
+    if (!LinkConstructorAndPrototype(cx, asyncGenFunction, asyncGenerator,
+                                     JSPROP_PERMANENT | JSPROP_READONLY, JSPROP_READONLY))
+    {
+        return false;
+    }
+
+    global->setReservedSlot(ASYNC_ITERATOR_PROTO, ObjectValue(*asyncIterProto));
+    global->setReservedSlot(ASYNC_GENERATOR, ObjectValue(*asyncGenerator));
+    global->setReservedSlot(ASYNC_GENERATOR_FUNCTION, ObjectValue(*asyncGenFunction));
+    global->setReservedSlot(ASYNC_GENERATOR_PROTO, ObjectValue(*asyncGenProto));
+    return true;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/vm/AsyncIteration.h
@@ -0,0 +1,204 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef vm_AsyncIteration_h
+#define vm_AsyncIteration_h
+
+#include "jscntxt.h"
+#include "jsobj.h"
+
+#include "builtin/Promise.h"
+#include "vm/GeneratorObject.h"
+
+namespace js {
+
+// Async generator consists of 2 functions, |wrapped| and |unwrapped|.
+// |unwrapped| is a generator function compiled from async generator script,
+// |await| behaves just like |yield| there.  |unwrapped| isn't exposed to user
+// script.
+// |wrapped| is a native function that is the value of async generator.
+
+JSObject*
+WrapAsyncGeneratorWithProto(JSContext* cx, HandleFunction unwrapped, HandleObject proto);
+
+JSObject*
+WrapAsyncGenerator(JSContext* cx, HandleFunction unwrapped);
+
+bool
+IsWrappedAsyncGenerator(JSFunction* fun);
+
+JSFunction*
+GetWrappedAsyncGenerator(JSFunction* unwrapped);
+
+JSFunction*
+GetUnwrappedAsyncGenerator(JSFunction* wrapped);
+
+MOZ_MUST_USE bool
+AsyncGeneratorAwaitedFulfilled(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+                              HandleValue value);
+
+MOZ_MUST_USE bool
+AsyncGeneratorAwaitedRejected(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+                             HandleValue reason);
+
+class AsyncGeneratorRequest : public NativeObject
+{
+  private:
+    enum AsyncGeneratorRequestSlots {
+        Slot_CompletionKind = 0,
+        Slot_CompletionValue,
+        Slot_Promise,
+        Slots,
+    };
+
+    void setCompletionKind(CompletionKind completionKind_) {
+        setFixedSlot(Slot_CompletionKind,
+                     Int32Value(static_cast<int32_t>(completionKind_)));
+    }
+    void setCompletionValue(HandleValue completionValue_) {
+        setFixedSlot(Slot_CompletionValue, completionValue_);
+    }
+    void setPromise(HandleObject promise_) {
+        setFixedSlot(Slot_Promise, ObjectValue(*promise_));
+    }
+
+  public:
+    static const Class class_;
+
+    static AsyncGeneratorRequest*
+    create(JSContext* cx, CompletionKind completionKind, HandleValue completionValue,
+           HandleObject promise);
+
+    CompletionKind completionKind() const {
+        return static_cast<CompletionKind>(getFixedSlot(Slot_CompletionKind).toInt32());
+    }
+    JS::Value completionValue() const {
+        return getFixedSlot(Slot_CompletionValue);
+    }
+    JSObject* promise() const {
+        return &getFixedSlot(Slot_Promise).toObject();
+    }
+};
+
+class AsyncGeneratorObject : public NativeObject
+{
+  private:
+    enum AsyncGeneratorObjectSlots {
+        Slot_State = 0,
+        Slot_Generator,
+        Slot_QueueOrRequest,
+        Slots
+    };
+
+    enum State {
+        State_SuspendedStart,
+        State_SuspendedYield,
+        State_Executing,
+        State_Completed
+    };
+
+    State state() const {
+        return static_cast<State>(getFixedSlot(Slot_State).toInt32());
+    }
+    void setState(State state_) {
+        setFixedSlot(Slot_State, Int32Value(state_));
+    }
+
+    void setGenerator(const Value& value) {
+        setFixedSlot(Slot_Generator, value);
+    }
+
+    // Queue is implemented in 2 ways.  If only one request is queued ever,
+    // request is stored directly to the slot.  Once 2 requests are queued, an
+    // array is created and requests are pushed into it, and the array is
+    // stored to the slot.
+
+    bool isSingleQueue() const {
+        return getFixedSlot(Slot_QueueOrRequest).isNull() ||
+               getFixedSlot(Slot_QueueOrRequest).toObject().is<AsyncGeneratorRequest>();
+    }
+    bool isSingleQueueEmpty() const {
+        return getFixedSlot(Slot_QueueOrRequest).isNull();
+    }
+    void setSingleQueueRequest(AsyncGeneratorRequest* request) {
+        setFixedSlot(Slot_QueueOrRequest, ObjectValue(*request));
+    }
+    void clearSingleQueueRequest() {
+        setFixedSlot(Slot_QueueOrRequest, NullHandleValue);
+    }
+    AsyncGeneratorRequest* singleQueueRequest() const {
+        return &getFixedSlot(Slot_QueueOrRequest).toObject().as<AsyncGeneratorRequest>();
+    }
+
+    ArrayObject* queue() const {
+        return &getFixedSlot(Slot_QueueOrRequest).toObject().as<ArrayObject>();
+    }
+    void setQueue(JSObject* queue_) {
+        setFixedSlot(Slot_QueueOrRequest, ObjectValue(*queue_));
+    }
+
+  public:
+    static const Class class_;
+
+    static AsyncGeneratorObject*
+    create(JSContext* cx, HandleFunction asyncGen, HandleValue generatorVal);
+
+    bool isSuspendedStart() const {
+        return state() == State_SuspendedStart;
+    }
+    bool isSuspendedYield() const {
+        return state() == State_SuspendedYield;
+    }
+    bool isExecuting() const {
+        return state() == State_Executing;
+    }
+    bool isCompleted() const {
+        return state() == State_Completed;
+    }
+
+    void setSuspendedStart() {
+        setState(State_SuspendedStart);
+    }
+    void setSuspendedYield() {
+        setState(State_SuspendedYield);
+    }
+    void setExecuting() {
+        setState(State_Executing);
+    }
+    void setCompleted() {
+        setState(State_Completed);
+    }
+
+    JS::Value generatorVal() const {
+        return getFixedSlot(Slot_Generator);
+    }
+    GeneratorObject* generatorObj() const {
+        return &getFixedSlot(Slot_Generator).toObject().as<GeneratorObject>();
+    }
+
+    static MOZ_MUST_USE bool
+    enqueueRequest(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
+                   Handle<AsyncGeneratorRequest*> request);
+
+    static AsyncGeneratorRequest*
+    dequeueRequest(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj);
+
+    static AsyncGeneratorRequest*
+    peekRequest(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj);
+
+    bool isQueueEmpty() const {
+        if (isSingleQueue())
+            return isSingleQueueEmpty();
+        return queue()->length() == 0;
+    }
+};
+
+MOZ_MUST_USE bool
+AsyncGeneratorResumeNext(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj);
+
+} // namespace js
+
+#endif /* vm_AsyncIteration_h */
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -26,16 +26,18 @@
     macro(ArraySpeciesCreate, ArraySpeciesCreate, "ArraySpeciesCreate") \
     macro(ArrayToLocaleString, ArrayToLocaleString, "ArrayToLocaleString") \
     macro(ArrayType, ArrayType, "ArrayType") \
     macro(ArrayValues, ArrayValues, "ArrayValues") \
     macro(ArrayValuesAt, ArrayValuesAt, "ArrayValuesAt") \
     macro(as, as, "as") \
     macro(Async, Async, "Async") \
     macro(AsyncFunction, AsyncFunction, "AsyncFunction") \
+    macro(AsyncGenerator, AsyncGenerator, "AsyncGenerator") \
+    macro(AsyncGeneratorFunction, AsyncGeneratorFunction, "AsyncGeneratorFunction") \
     macro(AsyncWrapped, AsyncWrapped, "AsyncWrapped") \
     macro(async, async, "async") \
     macro(await, await, "await") \
     macro(Bool8x16, Bool8x16, "Bool8x16") \
     macro(Bool16x8, Bool16x8, "Bool16x8") \
     macro(Bool32x4, Bool32x4, "Bool32x4") \
     macro(Bool64x2, Bool64x2, "Bool64x2") \
     macro(boundWithSpace, boundWithSpace, "bound ") \
@@ -314,16 +316,17 @@
     macro(shape, shape, "shape") \
     macro(size, size, "size") \
     macro(source, source, "source") \
     macro(SpeciesConstructor, SpeciesConstructor, "SpeciesConstructor") \
     macro(stack, stack, "stack") \
     macro(star, star, "*") \
     macro(starDefaultStar, starDefaultStar, "*default*") \
     macro(StarGeneratorNext, StarGeneratorNext, "StarGeneratorNext") \
+    macro(StarGeneratorReturn, StarGeneratorReturn, "StarGeneratorReturn") \
     macro(StarGeneratorThrow, StarGeneratorThrow, "StarGeneratorThrow") \
     macro(startTimestamp, startTimestamp, "startTimestamp") \
     macro(state, state, "state") \
     macro(static, static_, "static") \
     macro(std_Function_apply, std_Function_apply, "std_Function_apply") \
     macro(sticky, sticky, "sticky") \
     macro(StringIterator, StringIterator, "String Iterator") \
     macro(strings, strings, "strings") \
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -93,16 +93,20 @@ class GlobalObject : public NativeObject
         ARRAY_ITERATOR_PROTO,
         STRING_ITERATOR_PROTO,
         LEGACY_GENERATOR_OBJECT_PROTO,
         STAR_GENERATOR_OBJECT_PROTO,
         STAR_GENERATOR_FUNCTION_PROTO,
         STAR_GENERATOR_FUNCTION,
         ASYNC_FUNCTION_PROTO,
         ASYNC_FUNCTION,
+        ASYNC_ITERATOR_PROTO,
+        ASYNC_GENERATOR,
+        ASYNC_GENERATOR_FUNCTION,
+        ASYNC_GENERATOR_PROTO,
         MAP_ITERATOR_PROTO,
         SET_ITERATOR_PROTO,
         COLLATOR_PROTO,
         NUMBER_FORMAT,
         NUMBER_FORMAT_PROTO,
         DATE_TIME_FORMAT,
         DATE_TIME_FORMAT_PROTO,
         PLURAL_RULES_PROTO,
@@ -624,16 +628,40 @@ class GlobalObject : public NativeObject
                                                    initAsyncFunction));
     }
 
     static JSObject*
     getOrCreateAsyncFunction(JSContext* cx, Handle<GlobalObject*> global) {
         return getOrCreateObject(cx, global, ASYNC_FUNCTION, initAsyncFunction);
     }
 
+    static NativeObject*
+    getOrCreateAsyncIteratorPrototype(JSContext* cx, Handle<GlobalObject*> global)
+    {
+        return MaybeNativeObject(getOrCreateObject(cx, global, ASYNC_ITERATOR_PROTO,
+                                                   initAsyncGenerators));
+    }
+
+    static NativeObject*
+    getOrCreateAsyncGenerator(JSContext* cx, Handle<GlobalObject*> global) {
+        return MaybeNativeObject(getOrCreateObject(cx, global, ASYNC_GENERATOR,
+                                                   initAsyncGenerators));
+    }
+
+    static JSObject*
+    getOrCreateAsyncGeneratorFunction(JSContext* cx, Handle<GlobalObject*> global) {
+        return getOrCreateObject(cx, global, ASYNC_GENERATOR_FUNCTION, initAsyncGenerators);
+    }
+
+    static NativeObject*
+    getOrCreateAsyncGeneratorPrototype(JSContext* cx, Handle<GlobalObject*> global) {
+        return MaybeNativeObject(getOrCreateObject(cx, global, ASYNC_GENERATOR_PROTO,
+                                                   initAsyncGenerators));
+    }
+
     static JSObject*
     getOrCreateMapIteratorPrototype(JSContext* cx, Handle<GlobalObject*> global) {
         return getOrCreateObject(cx, global, MAP_ITERATOR_PROTO, initMapIteratorProto);
     }
 
     static JSObject*
     getOrCreateSetIteratorPrototype(JSContext* cx, Handle<GlobalObject*> global) {
         return getOrCreateObject(cx, global, SET_ITERATOR_PROTO, initSetIteratorProto);
@@ -770,16 +798,18 @@ class GlobalObject : public NativeObject
     static bool initStringIteratorProto(JSContext* cx, Handle<GlobalObject*> global);
 
     // Implemented in vm/GeneratorObject.cpp.
     static bool initLegacyGeneratorProto(JSContext* cx, Handle<GlobalObject*> global);
     static bool initStarGenerators(JSContext* cx, Handle<GlobalObject*> global);
 
     static bool initAsyncFunction(JSContext* cx, Handle<GlobalObject*> global);
 
+    static bool initAsyncGenerators(JSContext* cx, Handle<GlobalObject*> global);
+
     // Implemented in builtin/MapObject.cpp.
     static bool initMapIteratorProto(JSContext* cx, Handle<GlobalObject*> global);
     static bool initSetIteratorProto(JSContext* cx, Handle<GlobalObject*> global);
 
     // Implemented in Intl.cpp.
     static bool initIntlObject(JSContext* cx, Handle<GlobalObject*> global);
     static bool addPluralRulesConstructor(JSContext* cx, HandleObject intl);
 
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -34,16 +34,17 @@
 #include "jsstr.h"
 
 #include "builtin/Eval.h"
 #include "jit/AtomicOperations.h"
 #include "jit/BaselineJIT.h"
 #include "jit/Ion.h"
 #include "jit/IonAnalysis.h"
 #include "vm/AsyncFunction.h"
+#include "vm/AsyncIteration.h"
 #include "vm/Debugger.h"
 #include "vm/GeneratorObject.h"
 #include "vm/Opcodes.h"
 #include "vm/Scope.h"
 #include "vm/Shape.h"
 #include "vm/Stopwatch.h"
 #include "vm/TraceLogging.h"
 
@@ -1917,17 +1918,16 @@ CASE(EnableInterruptsPseudoOpcode)
     /* Commence executing the actual opcode. */
     SANITY_CHECKS();
     DISPATCH_TO(op);
 }
 
 /* Various 1-byte no-ops. */
 CASE(JSOP_NOP)
 CASE(JSOP_NOP_DESTRUCTURING)
-CASE(JSOP_UNUSED192)
 CASE(JSOP_UNUSED210)
 CASE(JSOP_UNUSED211)
 CASE(JSOP_UNUSED220)
 CASE(JSOP_UNUSED221)
 CASE(JSOP_UNUSED222)
 CASE(JSOP_UNUSED223)
 CASE(JSOP_CONDSWITCH)
 {
@@ -3561,16 +3561,28 @@ CASE(JSOP_TOASYNC)
     JSObject* wrapped = WrapAsyncFunction(cx, unwrapped);
     if (!wrapped)
         goto error;
 
     REGS.sp[-1].setObject(*wrapped);
 }
 END_CASE(JSOP_TOASYNC)
 
+CASE(JSOP_TOASYNCGEN)
+{
+    ReservedRooted<JSFunction*> unwrapped(&rootFunction0,
+                                          &REGS.sp[-1].toObject().as<JSFunction>());
+    JSObject* wrapped = WrapAsyncGenerator(cx, unwrapped);
+    if (!wrapped)
+        goto error;
+
+    REGS.sp[-1].setObject(*wrapped);
+}
+END_CASE(JSOP_TOASYNCGEN)
+
 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 (!SetFunctionNameIfNoOwnName(cx, fun, name, prefixKind))
         goto error;
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -1962,17 +1962,25 @@ 1234567890123456789012345678901234567890
      * Throw an exception if the value on top of the stack is not the TDZ
      * MagicValue. Used in derived class constructors.
      *   Category: Variables and Scopes
      *   Type: This
      *   Operands:
      *   Stack: this => this
      */ \
     macro(JSOP_CHECKTHISREINIT,191,"checkthisreinit",NULL,1,  1,  1,  JOF_BYTE) \
-    macro(JSOP_UNUSED192,     192,"unused192",   NULL,    1,  0,  0,  JOF_BYTE) \
+    /*
+     * Pops the top of stack value as 'unwrapped', converts it to async
+     * generator 'wrapped', and pushes 'wrapped' back on the stack.
+     *   Category: Statements
+     *   Type: Generator
+     *   Operands:
+     *   Stack: unwrapped => wrapped
+     */ \
+    macro(JSOP_TOASYNCGEN,    192, "toasyncgen", NULL,    1,  1,  1, JOF_BYTE) \
     \
     /*
      * 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