Prechádzať zdrojové kódy

LibJS/Bytecode: Implement async generators

Luke Wilde 2 rokov pred
rodič
commit
d1cb78c411

+ 134 - 60
Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp

@@ -1587,6 +1587,78 @@ Bytecode::CodeGenerationErrorOr<void> ReturnStatement::generate_bytecode(Bytecod
     return {};
 }
 
+static void get_received_completion_type_and_value(Bytecode::Generator& generator, Bytecode::Register received_completion_register, Bytecode::Register received_completion_type_register, Bytecode::Register received_completion_value_register, Bytecode::IdentifierTableIndex type_identifier, Bytecode::IdentifierTableIndex value_identifier)
+{
+    // The accumulator is set to an object, for example: { "type": 1 (normal), value: 1337 }
+    generator.emit<Bytecode::Op::Store>(received_completion_register);
+
+    generator.emit_get_by_id(type_identifier);
+    generator.emit<Bytecode::Op::Store>(received_completion_type_register);
+
+    generator.emit<Bytecode::Op::Load>(received_completion_register);
+    generator.emit_get_by_id(value_identifier);
+    generator.emit<Bytecode::Op::Store>(received_completion_value_register);
+}
+
+static void generate_await(Bytecode::Generator& generator, Bytecode::Register received_completion_register, Bytecode::Register received_completion_type_register, Bytecode::Register received_completion_value_register, Bytecode::IdentifierTableIndex type_identifier, Bytecode::IdentifierTableIndex value_identifier);
+
+enum class AwaitBeforeYield {
+    No,
+    Yes,
+};
+
+static void generate_yield(Bytecode::Generator& generator, Bytecode::Label continuation_label, Bytecode::Register received_completion_register, Bytecode::Register received_completion_type_register, Bytecode::Register received_completion_value_register, Bytecode::IdentifierTableIndex type_identifier, Bytecode::IdentifierTableIndex value_identifier, AwaitBeforeYield await_before_yield)
+{
+    if (!generator.is_in_async_generator_function()) {
+        generator.emit<Bytecode::Op::Yield>(Bytecode::Label { continuation_label });
+        return;
+    }
+
+    if (await_before_yield == AwaitBeforeYield::Yes)
+        generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
+
+    auto& unwrap_yield_resumption_block = generator.make_block();
+    generator.emit<Bytecode::Op::Yield>(Bytecode::Label { unwrap_yield_resumption_block });
+    generator.switch_to_basic_block(unwrap_yield_resumption_block);
+    get_received_completion_type_and_value(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
+
+    // 27.6.3.7 AsyncGeneratorUnwrapYieldResumption ( resumptionValue ), https://tc39.es/ecma262/#sec-asyncgeneratorunwrapyieldresumption
+    // 1. If resumptionValue.[[Type]] is not return, return ? resumptionValue.
+    auto& load_completion_and_jump_to_continuation_label_block = generator.make_block();
+    auto& resumption_value_type_is_return_block = generator.make_block();
+    generator.emit<Bytecode::Op::LoadImmediate>(Value(to_underlying(Completion::Type::Return)));
+    generator.emit<Bytecode::Op::StrictlyInequals>(received_completion_type_register);
+    generator.emit<Bytecode::Op::JumpConditional>(
+        Bytecode::Label { load_completion_and_jump_to_continuation_label_block },
+        Bytecode::Label { resumption_value_type_is_return_block });
+
+    generator.switch_to_basic_block(resumption_value_type_is_return_block);
+
+    // 2. Let awaited be Completion(Await(resumptionValue.[[Value]])).
+    generator.emit<Bytecode::Op::Load>(received_completion_value_register);
+    generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
+
+    // 3. If awaited.[[Type]] is throw, return ? awaited.
+    auto& awaited_type_is_normal_block = generator.make_block();
+    generator.emit<Bytecode::Op::LoadImmediate>(Value(to_underlying(Completion::Type::Throw)));
+    generator.emit<Bytecode::Op::StrictlyEquals>(received_completion_type_register);
+    generator.emit<Bytecode::Op::JumpConditional>(
+        Bytecode::Label { load_completion_and_jump_to_continuation_label_block },
+        Bytecode::Label { awaited_type_is_normal_block });
+
+    // 4. Assert: awaited.[[Type]] is normal.
+    generator.switch_to_basic_block(awaited_type_is_normal_block);
+
+    // 5. Return Completion Record { [[Type]]: return, [[Value]]: awaited.[[Value]], [[Target]]: empty }.
+    generator.emit<Bytecode::Op::LoadImmediate>(Value(to_underlying(Completion::Type::Return)));
+    generator.emit<Bytecode::Op::PutById>(received_completion_register, type_identifier);
+    generator.emit<Bytecode::Op::Jump>(Bytecode::Label { load_completion_and_jump_to_continuation_label_block });
+
+    generator.switch_to_basic_block(load_completion_and_jump_to_continuation_label_block);
+    generator.emit<Bytecode::Op::Load>(received_completion_register);
+    generator.emit<Bytecode::Op::Jump>(continuation_label);
+}
+
 Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecode::Generator& generator) const
 {
     VERIFY(generator.is_in_generator_function());
@@ -1598,21 +1670,10 @@ Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecod
     auto type_identifier = generator.intern_identifier("type");
     auto value_identifier = generator.intern_identifier("value");
 
-    auto get_received_completion_type_and_value = [&]() {
-        // The accumulator is set to an object, for example: { "type": 1 (normal), value: 1337 }
-        generator.emit<Bytecode::Op::Store>(received_completion_register);
-
-        generator.emit_get_by_id(type_identifier);
-        generator.emit<Bytecode::Op::Store>(received_completion_type_register);
-
-        generator.emit<Bytecode::Op::Load>(received_completion_register);
-        generator.emit_get_by_id(value_identifier);
-        generator.emit<Bytecode::Op::Store>(received_completion_value_register);
-    };
-
     if (m_is_yield_from) {
         // 15.5.5 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-generator-function-definitions-runtime-semantics-evaluation
-        // FIXME: 1. Let generatorKind be GetGeneratorKind().
+        // 1. Let generatorKind be GetGeneratorKind().
+        // NOTE: is_in_async_generator_function differentiates the generator kind.
 
         // 2. Let exprRef be ? Evaluation of AssignmentExpression.
         // 3. Let value be ? GetValue(exprRef).
@@ -1620,9 +1681,9 @@ Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecod
         TRY(m_argument->generate_bytecode(generator));
 
         // 4. Let iteratorRecord be ? GetIterator(value, generatorKind).
-        // FIXME: Consider generatorKind.
         auto iterator_record_register = generator.allocate_register();
-        generator.emit<Bytecode::Op::GetIterator>();
+        auto iterator_hint = generator.is_in_async_generator_function() ? IteratorHint::Async : IteratorHint::Sync;
+        generator.emit<Bytecode::Op::GetIterator>(iterator_hint);
         generator.emit<Bytecode::Op::Store>(iterator_record_register);
 
         // 5. Let iterator be iteratorRecord.[[Iterator]].
@@ -1670,7 +1731,9 @@ Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecod
         generator.emit_with_extra_register_slots<Bytecode::Op::NewArray>(2, AK::Array { received_completion_value_register, received_completion_value_register });
         generator.emit<Bytecode::Op::CallWithArgumentArray>(Bytecode::Op::CallType::Call, next_method_register, iterator_register);
 
-        // FIXME: ii. If generatorKind is async, set innerResult to ? Await(innerResult).
+        // ii. If generatorKind is async, set innerResult to ? Await(innerResult).
+        if (generator.is_in_async_generator_function())
+            generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
 
         // iii. If innerResult is not an Object, throw a TypeError exception.
         generator.emit<Bytecode::Op::ThrowIfNotObject>();
@@ -1697,15 +1760,15 @@ Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecod
 
         generator.switch_to_basic_block(type_is_normal_not_done_block);
 
-        // FIXME: vi. If generatorKind is async, set received to Completion(AsyncGeneratorYield(? IteratorValue(innerResult))).
+        // vi. If generatorKind is async, set received to Completion(AsyncGeneratorYield(? IteratorValue(innerResult))).
         // vii. Else, set received to Completion(GeneratorYield(innerResult)).
-        // FIXME: Else,
         generator.emit<Bytecode::Op::Load>(inner_result_register);
 
         // FIXME: Yield currently only accepts a Value, not an object conforming to the IteratorResult interface, so we have to do an observable lookup of `value` here.
+        //        This only matters for non-async generators.
         generator.emit<Bytecode::Op::IteratorResultValue>();
 
-        generator.emit<Bytecode::Op::Yield>(Bytecode::Label { continuation_block });
+        generate_yield(generator, Bytecode::Label { continuation_block }, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier, AwaitBeforeYield::No);
 
         // b. Else if received.[[Type]] is throw, then
         generator.switch_to_basic_block(is_type_throw_block);
@@ -1742,7 +1805,9 @@ Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecod
         generator.emit_with_extra_register_slots<Bytecode::Op::NewArray>(2, AK::Array { received_completion_value_register, received_completion_value_register });
         generator.emit<Bytecode::Op::CallWithArgumentArray>(Bytecode::Op::CallType::Call, throw_method_register, iterator_register);
 
-        // FIXME: 2. If generatorKind is async, set innerResult to ? Await(innerResult).
+        // 2. If generatorKind is async, set innerResult to ? Await(innerResult).
+        if (generator.is_in_async_generator_function())
+            generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
 
         // 3. NOTE: Exceptions from the inner iterator throw method are propagated. Normal completions from an inner throw method are processed similarly to an inner next.
         // 4. If innerResult is not an Object, throw a TypeError exception.
@@ -1768,26 +1833,31 @@ Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecod
 
         generator.switch_to_basic_block(type_is_throw_not_done_block);
 
-        // FIXME: 7. If generatorKind is async, set received to Completion(AsyncGeneratorYield(? IteratorValue(innerResult))).
+        // 7. If generatorKind is async, set received to Completion(AsyncGeneratorYield(? IteratorValue(innerResult))).
         // 8. Else, set received to Completion(GeneratorYield(innerResult)).
-        // FIXME: Else,
         generator.emit<Bytecode::Op::Load>(inner_result_register);
 
         // FIXME: Yield currently only accepts a Value, not an object conforming to the IteratorResult interface, so we have to do an observable lookup of `value` here.
+        //        This only matters for non-async generators.
         generator.emit<Bytecode::Op::IteratorResultValue>();
 
-        generator.emit<Bytecode::Op::Yield>(Bytecode::Label { continuation_block });
+        generate_yield(generator, Bytecode::Label { continuation_block }, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier, AwaitBeforeYield::No);
 
         generator.switch_to_basic_block(throw_method_is_undefined_block);
 
         // 1. NOTE: If iterator does not have a throw method, this throw is going to terminate the yield* loop. But first we need to give iterator a chance to clean up.
 
         // 2. Let closeCompletion be Completion Record { [[Type]]: normal, [[Value]]: empty, [[Target]]: empty }.
-        // FIXME: 3. If generatorKind is async, perform ? AsyncIteratorClose(iteratorRecord, closeCompletion).
-        // 4. Else, perform ? IteratorClose(iteratorRecord, closeCompletion).
-        // FIXME: Else,
+        // 3. If generatorKind is async, perform ? AsyncIteratorClose(iteratorRecord, closeCompletion).
         generator.emit<Bytecode::Op::Load>(iterator_record_register);
-        generator.emit<Bytecode::Op::IteratorClose>(Completion::Type::Normal, Optional<Value> {});
+        if (generator.is_in_async_generator_function()) {
+            // FIXME: This performs `await` outside of the generator!
+            generator.emit<Bytecode::Op::AsyncIteratorClose>(Completion::Type::Normal, Optional<Value> {});
+        }
+        // 4. Else, perform ? IteratorClose(iteratorRecord, closeCompletion).
+        else {
+            generator.emit<Bytecode::Op::IteratorClose>(Completion::Type::Normal, Optional<Value> {});
+        }
 
         // 5. NOTE: The next step throws a TypeError to indicate that there was a yield* protocol violation: iterator does not have a throw method.
         // 6. Throw a TypeError exception.
@@ -1817,10 +1887,13 @@ Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecod
 
         generator.switch_to_basic_block(return_is_undefined_block);
 
-        // FIXME: 1. If generatorKind is async, set received.[[Value]] to ? Await(received.[[Value]]).
+        // 1. If generatorKind is async, set received.[[Value]] to ? Await(received.[[Value]]).
+        generator.emit<Bytecode::Op::Load>(received_completion_value_register);
+        if (generator.is_in_async_generator_function())
+            generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
+
         // 2. Return ? received.
         // NOTE: This will always be a return completion.
-        generator.emit<Bytecode::Op::Load>(received_completion_value_register);
         generator.perform_needed_unwinds<Bytecode::Op::Yield>();
         generator.emit<Bytecode::Op::Yield>(nullptr);
 
@@ -1830,7 +1903,9 @@ Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecod
         generator.emit_with_extra_register_slots<Bytecode::Op::NewArray>(2, AK::Array { received_completion_value_register, received_completion_value_register });
         generator.emit<Bytecode::Op::CallWithArgumentArray>(Bytecode::Op::CallType::Call, return_method_register, iterator_register);
 
-        // FIXME: v. If generatorKind is async, set innerReturnResult to ? Await(innerReturnResult).
+        // v. If generatorKind is async, set innerReturnResult to ? Await(innerReturnResult).
+        if (generator.is_in_async_generator_function())
+            generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
 
         // vi. If innerReturnResult is not an Object, throw a TypeError exception.
         generator.emit<Bytecode::Op::ThrowIfNotObject>();
@@ -1860,18 +1935,18 @@ Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecod
 
         generator.switch_to_basic_block(type_is_return_not_done_block);
 
-        // FIXME: ix. If generatorKind is async, set received to Completion(AsyncGeneratorYield(? IteratorValue(innerReturnResult))).
+        // ix. If generatorKind is async, set received to Completion(AsyncGeneratorYield(? IteratorValue(innerReturnResult))).
         // x. Else, set received to Completion(GeneratorYield(innerReturnResult)).
-        // FIXME: Else,
         generator.emit<Bytecode::Op::Load>(inner_return_result_register);
 
         // FIXME: Yield currently only accepts a Value, not an object conforming to the IteratorResult interface, so we have to do an observable lookup of `value` here.
+        //        This only matters for non-async generators.
         generator.emit<Bytecode::Op::IteratorResultValue>();
 
-        generator.emit<Bytecode::Op::Yield>(Bytecode::Label { continuation_block });
+        generate_yield(generator, Bytecode::Label { continuation_block }, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier, AwaitBeforeYield::No);
 
         generator.switch_to_basic_block(continuation_block);
-        get_received_completion_type_and_value();
+        get_received_completion_type_and_value(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
         generator.emit<Bytecode::Op::Jump>(Bytecode::Label { loop_block });
 
         generator.switch_to_basic_block(loop_end_block);
@@ -1884,9 +1959,9 @@ Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecod
         generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
 
     auto& continuation_block = generator.make_block();
-    generator.emit<Bytecode::Op::Yield>(Bytecode::Label { continuation_block });
+    generate_yield(generator, Bytecode::Label { continuation_block }, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier, AwaitBeforeYield::Yes);
     generator.switch_to_basic_block(continuation_block);
-    get_received_completion_type_and_value();
+    get_received_completion_type_and_value(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
 
     auto& normal_completion_continuation_block = generator.make_block();
     auto& throw_completion_continuation_block = generator.make_block();
@@ -2419,32 +2494,15 @@ Bytecode::CodeGenerationErrorOr<void> ThisExpression::generate_bytecode(Bytecode
     return {};
 }
 
-static void generate_await(Bytecode::Generator& generator)
+static void generate_await(Bytecode::Generator& generator, Bytecode::Register received_completion_register, Bytecode::Register received_completion_type_register, Bytecode::Register received_completion_value_register, Bytecode::IdentifierTableIndex type_identifier, Bytecode::IdentifierTableIndex value_identifier)
 {
     VERIFY(generator.is_in_async_function());
 
-    // Transform `await expr` to `yield expr`, see AsyncFunctionDriverWrapper
-    // For that we just need to copy most of the code from YieldExpression
-    auto received_completion_register = generator.allocate_register();
-    auto received_completion_type_register = generator.allocate_register();
-    auto received_completion_value_register = generator.allocate_register();
-
-    auto type_identifier = generator.intern_identifier("type");
-    auto value_identifier = generator.intern_identifier("value");
-
     auto& continuation_block = generator.make_block();
-    generator.emit<Bytecode::Op::Yield>(Bytecode::Label { continuation_block });
+    generator.emit<Bytecode::Op::Await>(Bytecode::Label { continuation_block });
     generator.switch_to_basic_block(continuation_block);
 
-    // The accumulator is set to an object, for example: { "type": 1 (normal), value: 1337 }
-    generator.emit<Bytecode::Op::Store>(received_completion_register);
-
-    generator.emit_get_by_id(type_identifier);
-    generator.emit<Bytecode::Op::Store>(received_completion_type_register);
-
-    generator.emit<Bytecode::Op::Load>(received_completion_register);
-    generator.emit_get_by_id(value_identifier);
-    generator.emit<Bytecode::Op::Store>(received_completion_value_register);
+    get_received_completion_type_and_value(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
 
     auto& normal_completion_continuation_block = generator.make_block();
     auto& throw_value_block = generator.make_block();
@@ -2455,7 +2513,7 @@ static void generate_await(Bytecode::Generator& generator)
         Bytecode::Label { normal_completion_continuation_block },
         Bytecode::Label { throw_value_block });
 
-    // Simplification: The only abrupt completion we receive from AsyncFunctionDriverWrapper is Type::Throw
+    // Simplification: The only abrupt completion we receive from AsyncFunctionDriverWrapper or AsyncGenerator is Type::Throw
     //                 So we do not need to account for the Type::Return path
     generator.switch_to_basic_block(throw_value_block);
     generator.emit<Bytecode::Op::Load>(received_completion_value_register);
@@ -2469,7 +2527,15 @@ static void generate_await(Bytecode::Generator& generator)
 Bytecode::CodeGenerationErrorOr<void> AwaitExpression::generate_bytecode(Bytecode::Generator& generator) const
 {
     TRY(m_argument->generate_bytecode(generator));
-    generate_await(generator);
+
+    auto received_completion_register = generator.allocate_register();
+    auto received_completion_type_register = generator.allocate_register();
+    auto received_completion_value_register = generator.allocate_register();
+
+    auto type_identifier = generator.intern_identifier("type");
+    auto value_identifier = generator.intern_identifier("value");
+
+    generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
     return {};
 }
 
@@ -2651,8 +2717,16 @@ static Bytecode::CodeGenerationErrorOr<void> for_in_of_body_evaluation(Bytecode:
     generator.emit<Bytecode::Op::IteratorNext>();
 
     // b. If iteratorKind is async, set nextResult to ? Await(nextResult).
-    if (iterator_kind == IteratorHint::Async)
-        generate_await(generator);
+    if (iterator_kind == IteratorHint::Async) {
+        auto received_completion_register = generator.allocate_register();
+        auto received_completion_type_register = generator.allocate_register();
+        auto received_completion_value_register = generator.allocate_register();
+
+        auto type_identifier = generator.intern_identifier("type");
+        auto value_identifier = generator.intern_identifier("value");
+
+        generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
+    }
 
     // c. If Type(nextResult) is not Object, throw a TypeError exception.
     generator.emit<Bytecode::Op::ThrowIfNotObject>();

+ 508 - 1
Userland/Libraries/LibJS/Runtime/AsyncGenerator.cpp

@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
+ * Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
@@ -7,12 +8,28 @@
 #include <LibJS/Runtime/AsyncGenerator.h>
 #include <LibJS/Runtime/AsyncGeneratorPrototype.h>
 #include <LibJS/Runtime/AsyncGeneratorRequest.h>
+#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
 #include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/PromiseConstructor.h>
 
 namespace JS {
 
-AsyncGenerator::AsyncGenerator(Object& prototype)
+ThrowCompletionOr<NonnullGCPtr<AsyncGenerator>> AsyncGenerator::create(Realm& realm, Value initial_value, ECMAScriptFunctionObject* generating_function, ExecutionContext execution_context, Bytecode::RegisterWindow frame)
+{
+    auto& vm = realm.vm();
+    // This is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png)
+    auto generating_function_prototype = TRY(generating_function->get(vm.names.prototype));
+    auto generating_function_prototype_object = TRY(generating_function_prototype.to_object(vm));
+    auto object = MUST_OR_THROW_OOM(realm.heap().allocate<AsyncGenerator>(realm, realm, generating_function_prototype_object, move(execution_context)));
+    object->m_generating_function = generating_function;
+    object->m_frame = move(frame);
+    object->m_previous_value = initial_value;
+    return object;
+}
+
+AsyncGenerator::AsyncGenerator(Realm&, Object& prototype, ExecutionContext context)
     : Object(ConstructWithPrototypeTag::Tag, prototype)
+    , m_async_generator_context(move(context))
 {
 }
 
@@ -24,6 +41,496 @@ void AsyncGenerator::visit_edges(Cell::Visitor& visitor)
             visitor.visit(*request.completion.value());
         visitor.visit(request.capability);
     }
+    m_async_generator_context.visit_edges(visitor);
+    visitor.visit(m_generating_function);
+    visitor.visit(m_previous_value);
+    if (m_frame.has_value())
+        m_frame->visit_edges(visitor);
+    visitor.visit(m_current_promise);
+}
+
+// 27.6.3.4 AsyncGeneratorEnqueue ( generator, completion, promiseCapability ), https://tc39.es/ecma262/#sec-asyncgeneratorenqueue
+void AsyncGenerator::async_generator_enqueue(Completion completion, NonnullGCPtr<PromiseCapability> promise_capability)
+{
+    // 1. Let request be AsyncGeneratorRequest { [[Completion]]: completion, [[Capability]]: promiseCapability }.
+    auto request = AsyncGeneratorRequest { .completion = move(completion), .capability = promise_capability };
+
+    // 2. Append request to generator.[[AsyncGeneratorQueue]].
+    m_async_generator_queue.append(move(request));
+
+    // 3. Return unused.
+}
+
+void AsyncGenerator::set_async_generator_state(Badge<AsyncGeneratorPrototype>, AsyncGenerator::State value)
+{
+    m_async_generator_state = value;
+}
+
+// 27.7.5.3 Await ( value ), https://tc39.es/ecma262/#await
+ThrowCompletionOr<void> AsyncGenerator::await(Value value)
+{
+    auto& vm = this->vm();
+    auto& realm = *vm.current_realm();
+
+    // 1. Let asyncContext be the running execution context.
+    auto& async_context = vm.running_execution_context();
+
+    // 2. Let promise be ? PromiseResolve(%Promise%, value).
+    auto* promise_object = TRY(promise_resolve(vm, realm.intrinsics().promise_constructor(), value));
+
+    // 3. Let fulfilledClosure be a new Abstract Closure with parameters (v) that captures asyncContext and performs the
+    //    following steps when called:
+    auto fulfilled_closure = [this, &async_context](VM& vm) -> ThrowCompletionOr<Value> {
+        auto value = vm.argument(0);
+
+        // a. Let prevContext be the running execution context.
+        auto& prev_context = vm.running_execution_context();
+
+        // FIXME: b. Suspend prevContext.
+
+        // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
+        TRY(vm.push_execution_context(async_context, {}));
+
+        // d. Resume the suspended evaluation of asyncContext using NormalCompletion(v) as the result of the operation that
+        //    suspended it.
+        execute(vm, normal_completion(value));
+
+        // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and
+        //    prevContext is the currently running execution context.
+        VERIFY(&vm.running_execution_context() == &prev_context);
+
+        // f. Return undefined.
+        return js_undefined();
+    };
+
+    // 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
+    auto on_fulfilled = NativeFunction::create(realm, move(fulfilled_closure), 1, "");
+
+    // 5. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures asyncContext and performs the
+    //    following steps when called:
+    auto rejected_closure = [this, &async_context](VM& vm) -> ThrowCompletionOr<Value> {
+        auto reason = vm.argument(0);
+
+        // a. Let prevContext be the running execution context.
+        auto& prev_context = vm.running_execution_context();
+
+        // FIXME: b. Suspend prevContext.
+
+        // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
+        TRY(vm.push_execution_context(async_context, {}));
+
+        // d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that
+        //    suspended it.
+        execute(vm, throw_completion(reason));
+
+        // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and
+        //    prevContext is the currently running execution context.
+        VERIFY(&vm.running_execution_context() == &prev_context);
+
+        // f. Return undefined.
+        return js_undefined();
+    };
+
+    // 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
+    auto on_rejected = NativeFunction::create(realm, move(rejected_closure), 1, "");
+
+    // 7. Perform PerformPromiseThen(promise, onFulfilled, onRejected).
+    m_current_promise = verify_cast<Promise>(promise_object);
+    m_current_promise->perform_then(on_fulfilled, on_rejected, {});
+
+    // 8. Remove asyncContext from the execution context stack and restore the execution context that is at the top of the
+    //    execution context stack as the running execution context.
+    vm.pop_execution_context();
+
+    // NOTE: None of these are necessary. 10-12 are handled by step d of the above lambdas.
+    // 9. Let callerContext be the running execution context.
+    // 10. Resume callerContext passing empty. If asyncContext is ever resumed again, let completion be the Completion Record with which it is resumed.
+    // 11. Assert: If control reaches here, then asyncContext is the running execution context again.
+    // 12. Return completion.
+    return {};
+}
+
+void AsyncGenerator::execute(VM& vm, Completion completion)
+{
+    while (true) {
+        // Loosely based on step 4 of https://tc39.es/ecma262/#sec-asyncgeneratorstart
+        VERIFY(completion.value().has_value());
+
+        auto generated_value = [](Value value) -> Value {
+            if (value.is_object())
+                return value.as_object().get_without_side_effects("result");
+            return value.is_empty() ? js_undefined() : value;
+        };
+
+        auto generated_continuation = [&](Value value) -> Bytecode::BasicBlock const* {
+            if (value.is_object()) {
+                auto number_value = value.as_object().get_without_side_effects("continuation");
+                return reinterpret_cast<Bytecode::BasicBlock const*>(static_cast<u64>(number_value.as_double()));
+            }
+            return nullptr;
+        };
+
+        auto generated_is_await = [](Value value) -> bool {
+            if (value.is_object())
+                return value.as_object().get_without_side_effects("isAwait").as_bool();
+            return false;
+        };
+
+        auto& realm = *vm.current_realm();
+        auto completion_object = Object::create(realm, nullptr);
+        completion_object->define_direct_property(vm.names.type, Value(to_underlying(completion.type())), default_attributes);
+        completion_object->define_direct_property(vm.names.value, completion.value().value(), default_attributes);
+
+        auto& bytecode_interpreter = vm.bytecode_interpreter();
+
+        auto const* next_block = generated_continuation(m_previous_value);
+
+        // We should never enter `execute` again after the generator is complete.
+        VERIFY(next_block);
+
+        VERIFY(!m_generating_function->bytecode_executable()->basic_blocks.find_if([next_block](auto& block) { return block == next_block; }).is_end());
+
+        Bytecode::RegisterWindow* frame = nullptr;
+        if (m_frame.has_value())
+            frame = &m_frame.value();
+
+        if (frame)
+            frame->registers[0] = completion_object;
+        else
+            bytecode_interpreter.accumulator() = completion_object;
+
+        auto next_result = bytecode_interpreter.run_and_return_frame(realm, *m_generating_function->bytecode_executable(), next_block, frame);
+
+        if (!m_frame.has_value())
+            m_frame = move(*next_result.frame);
+
+        auto result_value = move(next_result.value);
+        if (!result_value.is_throw_completion()) {
+            m_previous_value = result_value.release_value();
+            auto value = generated_value(m_previous_value);
+            bool is_await = generated_is_await(m_previous_value);
+
+            if (is_await) {
+                auto await_result = this->await(value);
+                if (await_result.is_throw_completion()) {
+                    completion = await_result.release_error();
+                    continue;
+                }
+                return;
+            }
+        }
+
+        bool done = result_value.is_throw_completion() || generated_continuation(m_previous_value) == nullptr;
+        if (!done) {
+            // 27.6.3.8 AsyncGeneratorYield ( value ), https://tc39.es/ecma262/#sec-asyncgeneratoryield
+            // 1. Let genContext be the running execution context.
+            // 2. Assert: genContext is the execution context of a generator.
+            // 3. Let generator be the value of the Generator component of genContext.
+            // 4. Assert: GetGeneratorKind() is async.
+            // NOTE: genContext is `m_async_generator_context`, generator is `this`.
+
+            // 5. Let completion be NormalCompletion(value).
+            auto value = generated_value(m_previous_value);
+            auto yield_completion = normal_completion(value);
+
+            // 6. Assert: The execution context stack has at least two elements.
+            VERIFY(vm.execution_context_stack().size() >= 2);
+
+            // 7. Let previousContext be the second to top element of the execution context stack.
+            auto& previous_context = vm.execution_context_stack().at(vm.execution_context_stack().size() - 2);
+
+            // 8. Let previousRealm be previousContext's Realm.
+            auto previous_realm = previous_context->realm;
+
+            // 9. Perform AsyncGeneratorCompleteStep(generator, completion, false, previousRealm).
+            complete_step(yield_completion, false, previous_realm.ptr());
+
+            // 10. Let queue be generator.[[AsyncGeneratorQueue]].
+            auto& queue = m_async_generator_queue;
+
+            // 11. If queue is not empty, then
+            if (!queue.is_empty()) {
+                // a. NOTE: Execution continues without suspending the generator.
+                // b. Let toYield be the first element of queue.
+                auto& to_yield = queue.first();
+
+                // c. Let resumptionValue be Completion(toYield.[[Completion]]).
+                completion = Completion(to_yield.completion);
+
+                // d. Return ? AsyncGeneratorUnwrapYieldResumption(resumptionValue).
+                // NOTE: AsyncGeneratorUnwrapYieldResumption is performed inside the continuation block inside the generator,
+                //       so we just need to enter the generator again.
+                continue;
+            }
+            // 12. Else,
+            else {
+                // a. Set generator.[[AsyncGeneratorState]] to suspendedYield.
+                m_async_generator_state = State::SuspendedYield;
+
+                // b. Remove genContext from the execution context stack and restore the execution context that is at the top of the
+                //    execution context stack as the running execution context.
+                vm.pop_execution_context();
+
+                // c. Let callerContext be the running execution context.
+                // d. Resume callerContext passing undefined. If genContext is ever resumed again, let resumptionValue be the Completion Record with which it is resumed.
+                // e. Assert: If control reaches here, then genContext is the running execution context again.
+                // f. Return ? AsyncGeneratorUnwrapYieldResumption(resumptionValue).
+                // NOTE: e-f are performed whenever someone calls `execute` again.
+                return;
+            }
+        }
+
+        // 27.6.3.2 AsyncGeneratorStart ( generator, generatorBody ), https://tc39.es/ecma262/#sec-asyncgeneratorstart
+        // 4.e. Assert: If we return here, the async generator either threw an exception or performed either an implicit or explicit return.
+        // 4.f. Remove acGenContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
+        vm.pop_execution_context();
+
+        // 4.g. Set acGenerator.[[AsyncGeneratorState]] to completed.
+        m_async_generator_state = State::Completed;
+
+        // 4.h. If result.[[Type]] is normal, set result to NormalCompletion(undefined).
+        // 4.i. If result.[[Type]] is return, set result to NormalCompletion(result.[[Value]]).
+        Completion result;
+        if (!result_value.is_throw_completion()) {
+            result = normal_completion(generated_value(m_previous_value));
+        } else {
+            result = result_value.release_error();
+        }
+
+        // 4.j. Perform AsyncGeneratorCompleteStep(acGenerator, result, true).
+        complete_step(result, true);
+
+        // 4.k. Perform AsyncGeneratorDrainQueue(acGenerator).
+        drain_queue();
+
+        // 4.l. Return undefined.
+        return;
+    }
+}
+
+// 27.6.3.6 AsyncGeneratorResume ( generator, completion ), https://tc39.es/ecma262/#sec-asyncgeneratorresume
+ThrowCompletionOr<void> AsyncGenerator::resume(VM& vm, Completion completion)
+{
+    // 1. Assert: generator.[[AsyncGeneratorState]] is either suspendedStart or suspendedYield.
+    VERIFY(m_async_generator_state == State::SuspendedStart || m_async_generator_state == State::SuspendedYield);
+
+    // 2. Let genContext be generator.[[AsyncGeneratorContext]].
+    auto& generator_context = m_async_generator_context;
+
+    // 3. Let callerContext be the running execution context.
+    auto const& caller_context = vm.running_execution_context();
+
+    // FIXME: 4. Suspend callerContext.
+
+    // 5. Set generator.[[AsyncGeneratorState]] to executing.
+    m_async_generator_state = State::Executing;
+
+    // 6. Push genContext onto the execution context stack; genContext is now the running execution context.
+    TRY(vm.push_execution_context(generator_context, {}));
+
+    // 7. Resume the suspended evaluation of genContext using completion as the result of the operation that suspended
+    //    it. Let result be the Completion Record returned by the resumed computation.
+    // 8. Assert: result is never an abrupt completion.
+    execute(vm, completion);
+
+    // 9. Assert: When we return here, genContext has already been removed from the execution context stack and
+    //    callerContext is the currently running execution context.
+    VERIFY(&vm.running_execution_context() == &caller_context);
+
+    // 10. Return unused.
+    return {};
+}
+
+// 27.6.3.9 AsyncGeneratorAwaitReturn ( generator ), https://tc39.es/ecma262/#sec-asyncgeneratorawaitreturn
+ThrowCompletionOr<void> AsyncGenerator::await_return()
+{
+    auto& vm = this->vm();
+    auto& realm = *vm.current_realm();
+
+    // 1. Let queue be generator.[[AsyncGeneratorQueue]].
+    auto& queue = m_async_generator_queue;
+
+    // 2. Assert: queue is not empty.
+    VERIFY(!queue.is_empty());
+
+    // 3. Let next be the first element of queue.
+    auto& next = m_async_generator_queue.first();
+
+    // 4. Let completion be Completion(next.[[Completion]]).
+    auto completion = Completion(next.completion);
+
+    // 5. Assert: completion.[[Type]] is return.
+    VERIFY(completion.type() == Completion::Type::Return);
+
+    // 6. Let promise be ? PromiseResolve(%Promise%, completion.[[Value]]).
+    auto* promise = TRY(promise_resolve(vm, realm.intrinsics().promise_constructor(), completion.value().value()));
+
+    // 7. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures generator and performs
+    //    the following steps when called:
+    auto fulfilled_closure = [this](VM& vm) -> ThrowCompletionOr<Value> {
+        // a. Set generator.[[AsyncGeneratorState]] to completed.
+        m_async_generator_state = State::Completed;
+
+        // b. Let result be NormalCompletion(value).
+        auto result = normal_completion(vm.argument(0));
+
+        // c. Perform AsyncGeneratorCompleteStep(generator, result, true).
+        complete_step(result, true);
+
+        // d. Perform AsyncGeneratorDrainQueue(generator).
+        drain_queue();
+
+        // e. Return undefined.
+        return js_undefined();
+    };
+
+    // 8. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
+    auto on_fulfilled = NativeFunction::create(realm, move(fulfilled_closure), 1, "");
+
+    // 9. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures generator and performs
+    //    the following steps when called:
+    auto rejected_closure = [this](VM& vm) -> ThrowCompletionOr<Value> {
+        // a. Set generator.[[AsyncGeneratorState]] to completed.
+        m_async_generator_state = State::Completed;
+
+        // b. Let result be ThrowCompletion(reason).
+        auto result = throw_completion(vm.argument(0));
+
+        // c. Perform AsyncGeneratorCompleteStep(generator, result, true).
+        complete_step(result, true);
+
+        // d. Perform AsyncGeneratorDrainQueue(generator).
+        drain_queue();
+
+        // e. Return undefined.
+        return js_undefined();
+    };
+
+    // 10. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
+    auto on_rejected = NativeFunction::create(realm, move(rejected_closure), 1, "");
+
+    // 11. Perform PerformPromiseThen(promise, onFulfilled, onRejected).
+    // NOTE: await_return should only be called when the generator is in SuspendedStart or Completed state,
+    //       so an await shouldn't be running currently, so it should be safe to overwrite m_current_promise.
+    m_current_promise = verify_cast<Promise>(promise);
+    m_current_promise->perform_then(on_fulfilled, on_rejected, {});
+
+    // 12. Return unused.
+    return {};
+}
+
+// 27.6.3.5 AsyncGeneratorCompleteStep ( generator, completion, done [ , realm ] ), https://tc39.es/ecma262/#sec-asyncgeneratorcompletestep
+void AsyncGenerator::complete_step(Completion completion, bool done, Realm* realm)
+{
+    auto& vm = this->vm();
+
+    // 1. Assert: generator.[[AsyncGeneratorQueue]] is not empty.
+    VERIFY(!m_async_generator_queue.is_empty());
+
+    // 2. Let next be the first element of generator.[[AsyncGeneratorQueue]].
+    // 3. Remove the first element from generator.[[AsyncGeneratorQueue]].
+    auto next = m_async_generator_queue.take_first();
+
+    // 4. Let promiseCapability be next.[[Capability]].
+    auto promise_capability = next.capability;
+
+    // 5. Let value be completion.[[Value]].
+    auto value = completion.value().value();
+
+    // 6. If completion.[[Type]] is throw, then
+    if (completion.type() == Completion::Type::Throw) {
+        // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « value »).
+        MUST(call(vm, *promise_capability->reject(), js_undefined(), value));
+    }
+    // 7. Else,
+    else {
+        // a. Assert: completion.[[Type]] is normal.
+        VERIFY(completion.type() == Completion::Type::Normal);
+
+        GCPtr<Object> iterator_result;
+
+        // b. If realm is present, then
+        if (realm) {
+            // i. Let oldRealm be the running execution context's Realm.
+            auto old_realm = vm.running_execution_context().realm;
+
+            // ii. Set the running execution context's Realm to realm.
+            vm.running_execution_context().realm = realm;
+
+            // iii. Let iteratorResult be CreateIterResultObject(value, done).
+            iterator_result = create_iterator_result_object(vm, value, done);
+
+            // iv. Set the running execution context's Realm to oldRealm.
+            vm.running_execution_context().realm = old_realm;
+        }
+        // c. Else,
+        else {
+            // i. Let iteratorResult be CreateIterResultObject(value, done).
+            iterator_result = create_iterator_result_object(vm, value, done);
+        }
+
+        VERIFY(iterator_result);
+
+        // d. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »).
+        MUST(call(vm, *promise_capability->resolve(), js_undefined(), iterator_result));
+    }
+
+    // 8. Return unused.
+}
+
+// 27.6.3.10 AsyncGeneratorDrainQueue ( generator ), https://tc39.es/ecma262/#sec-asyncgeneratordrainqueue
+void AsyncGenerator::drain_queue()
+{
+    // 1. Assert: generator.[[AsyncGeneratorState]] is completed.
+    VERIFY(m_async_generator_state == State::Completed);
+
+    // 2. Let queue be generator.[[AsyncGeneratorQueue]].
+    auto& queue = m_async_generator_queue;
+
+    // 3. If queue is empty, return unused.
+    if (queue.is_empty())
+        return;
+
+    // 4. Let done be false.
+    bool done = false;
+
+    // 5. Repeat, while done is false,
+    while (!done) {
+        // a. Let next be the first element of queue.
+        auto& next = m_async_generator_queue.first();
+
+        // b. Let completion be Completion(next.[[Completion]]).
+        auto completion = Completion(next.completion);
+
+        // c. If completion.[[Type]] is return, then
+        if (completion.type() == Completion::Type::Return) {
+            // i. Set generator.[[AsyncGeneratorState]] to awaiting-return.
+            m_async_generator_state = State::AwaitingReturn;
+
+            // ii. Perform ! AsyncGeneratorAwaitReturn(generator).
+            MUST(await_return());
+
+            // iii. Set done to true.
+            done = true;
+        }
+        // d. Else,
+        else {
+            // i. If completion.[[Type]] is normal, then
+            if (completion.type() == Completion::Type::Normal) {
+                // 1. Set completion to NormalCompletion(undefined).
+                completion = normal_completion(js_undefined());
+            }
+
+            // ii. Perform AsyncGeneratorCompleteStep(generator, completion, true).
+            complete_step(completion, true);
+
+            // iii. If queue is empty, set done to true.
+            if (queue.is_empty())
+                done = true;
+        }
+    }
+
+    // 6. Return unused.
 }
 
 }

+ 27 - 6
Userland/Libraries/LibJS/Runtime/AsyncGenerator.h

@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
+ * Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
@@ -7,6 +8,7 @@
 #pragma once
 
 #include <AK/Variant.h>
+#include <LibJS/Bytecode/Interpreter.h>
 #include <LibJS/Runtime/ExecutionContext.h>
 #include <LibJS/Runtime/Object.h>
 
@@ -25,21 +27,40 @@ public:
         Completed,
     };
 
+    static ThrowCompletionOr<NonnullGCPtr<AsyncGenerator>> create(Realm&, Value, ECMAScriptFunctionObject*, ExecutionContext, Bytecode::RegisterWindow);
+
     virtual ~AsyncGenerator() override = default;
 
+    void async_generator_enqueue(Completion, NonnullGCPtr<PromiseCapability>);
+    ThrowCompletionOr<void> resume(VM&, Completion completion);
+    ThrowCompletionOr<void> await_return();
+    void complete_step(Completion, bool done, Realm* realm = nullptr);
+    void drain_queue();
+
+    State async_generator_state() const { return m_async_generator_state; }
+    void set_async_generator_state(Badge<AsyncGeneratorPrototype>, State value);
+
+    Optional<String> const& generator_brand() const { return m_generator_brand; }
+
 private:
-    explicit AsyncGenerator(Object& prototype);
+    AsyncGenerator(Realm&, Object& prototype, ExecutionContext);
 
     virtual void visit_edges(Cell::Visitor&) override;
 
+    void execute(VM&, Completion completion);
+    ThrowCompletionOr<void> await(Value);
+
     // At the time of constructing an AsyncGenerator, we still need to point to an
     // execution context on the stack, but later need to 'adopt' it.
-    using ExecutionContextVariant = Variant<ExecutionContext, ExecutionContext*, Empty>;
+    State m_async_generator_state { State::SuspendedStart }; // [[AsyncGeneratorState]]
+    ExecutionContext m_async_generator_context;              // [[AsyncGeneratorContext]]
+    Vector<AsyncGeneratorRequest> m_async_generator_queue;   // [[AsyncGeneratorQueue]]
+    Optional<String> m_generator_brand;                      // [[GeneratorBrand]]
 
-    Optional<State> m_async_generator_state;               // [[AsyncGeneratorState]]
-    ExecutionContextVariant m_async_generator_context;     // [[AsyncGeneratorContext]]
-    Vector<AsyncGeneratorRequest> m_async_generator_queue; // [[AsyncGeneratorQueue]]
-    Optional<DeprecatedString> m_generator_brand;          // [[GeneratorBrand]]
+    GCPtr<ECMAScriptFunctionObject> m_generating_function;
+    Value m_previous_value;
+    Optional<Bytecode::RegisterWindow> m_frame;
+    GCPtr<Promise> m_current_promise;
 };
 
 }

+ 184 - 0
Userland/Libraries/LibJS/Runtime/AsyncGeneratorPrototype.cpp

@@ -1,10 +1,14 @@
 /*
  * Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org>
+ * Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
 
 #include <LibJS/Runtime/AsyncGeneratorPrototype.h>
+#include <LibJS/Runtime/IteratorOperations.h>
+#include <LibJS/Runtime/PromiseCapability.h>
+#include <LibJS/Runtime/PromiseConstructor.h>
 
 namespace JS {
 
@@ -18,6 +22,10 @@ ThrowCompletionOr<void> AsyncGeneratorPrototype::initialize(Realm& realm)
 {
     auto& vm = this->vm();
     MUST_OR_THROW_OOM(Base::initialize(realm));
+    u8 attr = Attribute::Writable | Attribute::Configurable;
+    define_native_function(realm, vm.names.next, next, 1, attr);
+    define_native_function(realm, vm.names.return_, return_, 1, attr);
+    define_native_function(realm, vm.names.throw_, throw_, 1, attr);
 
     // 27.6.1.5 AsyncGenerator.prototype [ @@toStringTag ], https://tc39.es/ecma262/#sec-asyncgenerator-prototype-tostringtag
     define_direct_property(vm.well_known_symbol_to_string_tag(), MUST_OR_THROW_OOM(PrimitiveString::create(vm, "AsyncGenerator"sv)), Attribute::Configurable);
@@ -25,4 +33,180 @@ ThrowCompletionOr<void> AsyncGeneratorPrototype::initialize(Realm& realm)
     return {};
 }
 
+// 27.6.3.3 AsyncGeneratorValidate ( generator, generatorBrand ), https://tc39.es/ecma262/#sec-asyncgeneratorvalidate
+static ThrowCompletionOr<NonnullGCPtr<AsyncGenerator>> async_generator_validate(VM& vm, Value generator, Optional<String> generator_brand)
+{
+    // 1. Perform ? RequireInternalSlot(generator, [[AsyncGeneratorContext]]).
+    // 2. Perform ? RequireInternalSlot(generator, [[AsyncGeneratorState]]).
+    // 3. Perform ? RequireInternalSlot(generator, [[AsyncGeneratorQueue]]).
+    if (!generator.is_object() || !is<AsyncGenerator>(generator.as_object()))
+        return vm.throw_completion<TypeError>(ErrorType::NotAnObjectOfType, "AsyncGenerator");
+
+    auto& async_generator = static_cast<AsyncGenerator&>(generator.as_object());
+
+    // 4. If generator.[[GeneratorBrand]] is not generatorBrand, throw a TypeError exception.
+    if (async_generator.generator_brand() != generator_brand)
+        return vm.throw_completion<TypeError>(ErrorType::GeneratorBrandMismatch, async_generator.generator_brand().value_or("emp"_short_string), generator_brand.value_or("emp"_short_string));
+
+    // 5. Return unused.
+    return async_generator;
+}
+
+// 27.6.1.2 AsyncGenerator.prototype.next ( value ), https://tc39.es/ecma262/#sec-asyncgenerator-prototype-next
+JS_DEFINE_NATIVE_FUNCTION(AsyncGeneratorPrototype::next)
+{
+    auto& realm = *vm.current_realm();
+
+    // 1. Let generator be the this value.
+    auto generator_this_value = vm.this_value();
+
+    // 2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
+    auto promise_capability = MUST(new_promise_capability(vm, realm.intrinsics().promise_constructor()));
+
+    // 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)).
+    // 4. IfAbruptRejectPromise(result, promiseCapability).
+    auto generator = TRY_OR_REJECT(vm, promise_capability, async_generator_validate(vm, generator_this_value, OptionalNone {}));
+
+    // 5. Let state be generator.[[AsyncGeneratorState]].
+    auto state = generator->async_generator_state();
+
+    // 6. If state is completed, then
+    if (state == AsyncGenerator::State::Completed) {
+        // a. Let iteratorResult be CreateIterResultObject(undefined, true).
+        auto iterator_result = create_iterator_result_object(vm, js_undefined(), true);
+
+        // b. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »).
+        MUST(call(vm, *promise_capability->resolve(), js_undefined(), iterator_result));
+
+        // c. Return promiseCapability.[[Promise]].
+        return promise_capability->promise();
+    }
+
+    // 7. Let completion be NormalCompletion(value).
+    auto completion = normal_completion(vm.argument(0));
+
+    // 8. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
+    generator->async_generator_enqueue(completion, promise_capability);
+
+    // 9. If state is either suspendedStart or suspendedYield, then
+    if (state == AsyncGenerator::State::SuspendedStart || state == AsyncGenerator::State::SuspendedYield) {
+        // a. Perform AsyncGeneratorResume(generator, completion).
+        TRY_OR_REJECT(vm, promise_capability, generator->resume(vm, completion));
+    }
+    // 10. Else,
+    else {
+        // a. Assert: state is either executing or awaiting-return.
+        VERIFY(state == AsyncGenerator::State::Executing || state == AsyncGenerator::State::AwaitingReturn);
+    }
+
+    // 11. Return promiseCapability.[[Promise]].
+    return promise_capability->promise();
+}
+
+// 27.6.1.3 AsyncGenerator.prototype.return ( value ), https://tc39.es/ecma262/#sec-asyncgenerator-prototype-return
+JS_DEFINE_NATIVE_FUNCTION(AsyncGeneratorPrototype::return_)
+{
+    auto& realm = *vm.current_realm();
+
+    // 1. Let generator be the this value.
+    auto generator_this_value = vm.this_value();
+
+    // 2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
+    auto promise_capability = MUST(new_promise_capability(vm, realm.intrinsics().promise_constructor()));
+
+    // 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)).
+    // 4. IfAbruptRejectPromise(result, promiseCapability).
+    auto generator = TRY_OR_REJECT(vm, promise_capability, async_generator_validate(vm, generator_this_value, OptionalNone {}));
+
+    // 5. Let completion be Completion Record { [[Type]]: return, [[Value]]: value, [[Target]]: empty }.
+    auto completion = Completion(Completion::Type::Return, vm.argument(0), {});
+
+    // 6. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
+    generator->async_generator_enqueue(completion, promise_capability);
+
+    // 7. Let state be generator.[[AsyncGeneratorState]].
+    auto state = generator->async_generator_state();
+
+    // 8. If state is either suspendedStart or completed, then
+    if (state == AsyncGenerator::State::SuspendedStart || state == AsyncGenerator::State::Completed) {
+        // a. Set generator.[[AsyncGeneratorState]] to awaiting-return.
+        generator->set_async_generator_state({}, AsyncGenerator::State::AwaitingReturn);
+
+        // b. Perform ! AsyncGeneratorAwaitReturn(generator).
+        MUST(generator->await_return());
+    }
+    // 9. Else if state is suspendedYield, then
+    else if (state == AsyncGenerator::State::SuspendedYield) {
+        // a. Perform AsyncGeneratorResume(generator, completion).
+        TRY_OR_REJECT(vm, promise_capability, generator->resume(vm, completion));
+    }
+    // 10. Else,
+    else {
+        // a. Assert: state is either executing or awaiting-return.
+        VERIFY(state == AsyncGenerator::State::Executing || state == AsyncGenerator::State::AwaitingReturn);
+    }
+
+    // 11. Return promiseCapability.[[Promise]].
+    return promise_capability->promise();
+}
+
+// 27.6.1.4 AsyncGenerator.prototype.throw ( exception ), https://tc39.es/ecma262/#sec-asyncgenerator-prototype-throw
+JS_DEFINE_NATIVE_FUNCTION(AsyncGeneratorPrototype::throw_)
+{
+    auto& realm = *vm.current_realm();
+
+    auto exception = vm.argument(0);
+
+    // 1. Let generator be the this value.
+    auto generator_this_value = vm.this_value();
+
+    // 2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
+    auto promise_capability = MUST(new_promise_capability(vm, realm.intrinsics().promise_constructor()));
+
+    // 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)).
+    // 4. IfAbruptRejectPromise(result, promiseCapability).
+    auto generator = TRY_OR_REJECT(vm, promise_capability, async_generator_validate(vm, generator_this_value, OptionalNone {}));
+
+    // 5. Let state be generator.[[AsyncGeneratorState]].
+    auto state = generator->async_generator_state();
+
+    // 6. If state is suspendedStart, then
+    if (state == AsyncGenerator::State::SuspendedStart) {
+        // a. Set generator.[[AsyncGeneratorState]] to completed.
+        generator->set_async_generator_state({}, AsyncGenerator::State::Completed);
+
+        // b. Set state to completed.
+        state = AsyncGenerator::State::Completed;
+    }
+
+    // 7. If state is completed, then
+    if (state == AsyncGenerator::State::Completed) {
+        // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « exception »).
+        MUST(call(vm, *promise_capability->reject(), js_undefined(), exception));
+
+        // b. Return promiseCapability.[[Promise]].
+        return promise_capability->promise();
+    }
+
+    // 8. Let completion be ThrowCompletion(exception).
+    auto completion = throw_completion(exception);
+
+    // 9. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
+    generator->async_generator_enqueue(completion, promise_capability);
+
+    // 10. If state is suspendedYield, then
+    if (state == AsyncGenerator::State::SuspendedYield) {
+        // a. Perform AsyncGeneratorResume(generator, completion).
+        TRY_OR_REJECT(vm, promise_capability, generator->resume(vm, completion));
+    }
+    // 11. Else,
+    else {
+        // a. Assert: state is either executing or awaiting-return.
+        VERIFY(state == AsyncGenerator::State::Executing || state == AsyncGenerator::State::AwaitingReturn);
+    }
+
+    // 12. Return promiseCapability.[[Promise]].
+    return promise_capability->promise();
+}
+
 }

+ 5 - 0
Userland/Libraries/LibJS/Runtime/AsyncGeneratorPrototype.h

@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
+ * Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
@@ -20,6 +21,10 @@ public:
 
 private:
     explicit AsyncGeneratorPrototype(Realm&);
+
+    JS_DECLARE_NATIVE_FUNCTION(next);
+    JS_DECLARE_NATIVE_FUNCTION(return_);
+    JS_DECLARE_NATIVE_FUNCTION(throw_);
 };
 
 }

+ 9 - 4
Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp

@@ -17,6 +17,7 @@
 #include <LibJS/Runtime/AbstractOperations.h>
 #include <LibJS/Runtime/Array.h>
 #include <LibJS/Runtime/AsyncFunctionDriverWrapper.h>
+#include <LibJS/Runtime/AsyncGenerator.h>
 #include <LibJS/Runtime/ECMAScriptFunctionObject.h>
 #include <LibJS/Runtime/Error.h>
 #include <LibJS/Runtime/ExecutionContext.h>
@@ -855,9 +856,6 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
     auto& vm = this->vm();
     auto& realm = *vm.current_realm();
 
-    if (m_kind == FunctionKind::AsyncGenerator)
-        return vm.throw_completion<InternalError>(ErrorType::NotImplemented, "Async Generator function execution");
-
     auto* bytecode_interpreter = vm.bytecode_interpreter_if_exists();
 
     // The bytecode interpreter can execute generator functions while the AST interpreter cannot.
@@ -867,7 +865,7 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
     // However, this does cause an awkward situation with features not supported in bytecode, where features that work outside of generators with AST
     // suddenly stop working inside of generators.
     // This is a stop gap until bytecode mode becomes the default.
-    if (m_kind == FunctionKind::Generator && !bytecode_interpreter) {
+    if ((m_kind == FunctionKind::Generator || m_kind == FunctionKind::AsyncGenerator) && !bytecode_interpreter) {
         bytecode_interpreter = &vm.bytecode_interpreter();
     }
 
@@ -920,6 +918,11 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
         if (m_kind == FunctionKind::Normal)
             return { Completion::Type::Return, result.value_or(js_undefined()), {} };
 
+        if (m_kind == FunctionKind::AsyncGenerator) {
+            auto async_generator_object = TRY(AsyncGenerator::create(realm, result, this, vm.running_execution_context().copy(), move(*result_and_frame.frame)));
+            return { Completion::Type::Return, async_generator_object, {} };
+        }
+
         auto generator_object = TRY(GeneratorObject::create(realm, result, this, vm.running_execution_context().copy(), move(*result_and_frame.frame)));
 
         // NOTE: Async functions are entirely transformed to generator functions, and wrapped in a custom driver that returns a promise
@@ -932,6 +935,8 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
     } else {
         if (m_kind == FunctionKind::Generator)
             return vm.throw_completion<InternalError>(ErrorType::NotImplemented, "Generator function execution in AST interpreter");
+        if (m_kind == FunctionKind::AsyncGenerator)
+            return vm.throw_completion<InternalError>(ErrorType::NotImplemented, "Async generator function execution in AST interpreter");
         OwnPtr<Interpreter> local_interpreter;
         Interpreter* ast_interpreter = vm.interpreter_if_exists();
 

+ 145 - 0
Userland/Libraries/LibJS/Tests/builtins/AsyncGenerator/AsyncGenerator.prototype.next.js

@@ -0,0 +1,145 @@
+describe("correct behaviour", () => {
+    async function* generatorFunction() {
+        yield 1;
+        await Promise.resolve(2);
+        const b = yield 3;
+        await Promise.resolve(b);
+        yield b + 1;
+        yield Promise.resolve(b + 2);
+        yield* [Promise.resolve(b + 3), Promise.resolve(b + 4), Promise.resolve(b + 5)];
+        return b + 6;
+    }
+
+    test("length is 1", () => {
+        expect(generatorFunction.prototype.next).toHaveLength(1);
+    });
+
+    const generator = generatorFunction();
+
+    function runGenerator(valueToPass, unwrapIteratorResult = true) {
+        let result = null;
+        test(`generator runs valueToPass=${valueToPass}`, () => {
+            const promise = generator.next(valueToPass);
+            promise
+                .then(value => {
+                    result = value;
+                })
+                .catch(e => {
+                    expect().fail(`Generator threw an unhandled exception: ${e}`);
+                });
+            runQueuedPromiseJobs();
+            expect(result).toBeInstanceOf(Object);
+            expect(Object.getPrototypeOf(result)).toBe(Object.prototype);
+            expect(Object.keys(result)).toEqual(["value", "done"]);
+        });
+        return unwrapIteratorResult ? result.value : result;
+    }
+
+    test("can yield", () => {
+        const firstRunResult = runGenerator("bad1");
+        expect(firstRunResult).toBe(1);
+    });
+
+    test("await does not yield", () => {
+        const secondRunResult = runGenerator("bad2");
+        expect(secondRunResult).toBe(3);
+    });
+
+    test("can pass values via yield", () => {
+        const thirdRunResult = runGenerator(4);
+        expect(thirdRunResult).toBe(5);
+    });
+
+    test("yield implicitly awaits", () => {
+        const fourthRunResult = runGenerator("bad3");
+        expect(fourthRunResult).toBe(6);
+
+        const fifthRunResult = runGenerator("bad4");
+        expect(fifthRunResult).toBe(7);
+
+        const sixthRunResult = runGenerator("bad5");
+        expect(sixthRunResult).toBe(8);
+
+        const seventhRunResult = runGenerator("bad6");
+        expect(seventhRunResult).toBe(9);
+    });
+
+    test("can return a value", () => {
+        const eighthRunResult = runGenerator("bad7", false);
+        expect(eighthRunResult.value).toBe(10);
+        expect(eighthRunResult.done).toBeTrue();
+    });
+
+    test("gets undefined in completed state", () => {
+        const ninethRunResult = runGenerator("bad8", false);
+        expect(ninethRunResult.value).toBeUndefined();
+        expect(ninethRunResult.done).toBeTrue();
+    });
+
+    async function* implicitReturnFunction() {
+        0xbbadbeef;
+    }
+
+    const implicitReturnGenerator = implicitReturnFunction();
+
+    test("gets undefined from implicit return", () => {
+        implicitReturnGenerator
+            .next("bad9")
+            .then(iteratorResult => {
+                expect(iteratorResult.value).toBeUndefined();
+                expect(iteratorResult.done).toBeTrue();
+            })
+            .catch(e => {
+                expect().fail(`Implicit await generator threw an unhandled exception: ${e}`);
+            });
+        runQueuedPromiseJobs();
+    });
+
+    async function* unhandledExceptionFunction() {
+        throw 1337;
+    }
+
+    const unhandledExceptionGenerator = unhandledExceptionFunction();
+
+    test("promise is rejected on unhandled exceptions", () => {
+        unhandledExceptionGenerator
+            .next("bad10")
+            .then(() => {
+                expect().fail(
+                    "Unhandled exception generator did NOT throw an unhandled exception."
+                );
+            })
+            .catch(e => {
+                expect(e).toBe(1337);
+            });
+        runQueuedPromiseJobs();
+    });
+
+    test("generator is complete after unhandled exception", () => {
+        unhandledExceptionGenerator
+            .next("bad11")
+            .then(iteratorResult => {
+                expect(iteratorResult.value).toBeUndefined();
+                expect(iteratorResult.done).toBeTrue();
+            })
+            .catch(e => {
+                expect().fail(
+                    "Unhandled exception generator threw an unhandled exception in Completed state."
+                );
+            });
+        runQueuedPromiseJobs();
+    });
+});
+
+describe("errors", () => {
+    test("this value must be an AsyncGenerator object", () => {
+        async function* generator() {}
+        let rejection = null;
+        generator.prototype.next.call("foo").catch(error => {
+            rejection = error;
+        });
+        runQueuedPromiseJobs();
+        expect(rejection).toBeInstanceOf(TypeError);
+        expect(rejection.message).toBe("Not an object of type AsyncGenerator");
+    });
+});

+ 146 - 0
Userland/Libraries/LibJS/Tests/builtins/AsyncGenerator/AsyncGenerator.prototype.return.js

@@ -0,0 +1,146 @@
+describe("correct behavior", () => {
+    async function* emptyGeneratorFunction() {}
+
+    test("length is 1", () => {
+        expect(emptyGeneratorFunction.prototype.return).toHaveLength(1);
+    });
+
+    const emptyGenerator = emptyGeneratorFunction();
+
+    test("return from SuspendedStart", () => {
+        emptyGenerator
+            .return(1337)
+            .then(result => {
+                expect(result.value).toBe(1337);
+                expect(result.done).toBeTrue();
+            })
+            .catch(e => {
+                expect().fail(`Generator threw an unhandled exception: ${e}`);
+            });
+        runQueuedPromiseJobs();
+    });
+
+    test("return from Completed", () => {
+        emptyGenerator
+            .return(123)
+            .then(result => {
+                expect(result.value).toBe(123);
+                expect(result.done).toBeTrue();
+            })
+            .catch(e => {
+                expect().fail(`Generator threw an unhandled exception: ${e}`);
+            });
+        runQueuedPromiseJobs();
+    });
+
+    async function* generatorTwo() {
+        yield 1337;
+        yield 123;
+    }
+
+    const generatorTwoIterator = generatorTwo();
+
+    test("return from SuspendedYield", () => {
+        generatorTwoIterator
+            .next("bad1")
+            .then(result => {
+                expect(result.value).toBe(1337);
+                expect(result.done).toBeFalse();
+            })
+            .catch(e => {
+                expect().fail(`Generator threw an unhandled exception: ${e}`);
+            });
+        runQueuedPromiseJobs();
+
+        generatorTwoIterator
+            .return(999)
+            .then(result => {
+                expect(result.value).toBe(999);
+                expect(result.done).toBeTrue();
+            })
+            .catch(e => {
+                expect().fail(`Generator threw an unhandled exception: ${e}`);
+            });
+        runQueuedPromiseJobs();
+
+        generatorTwoIterator
+            .next("bad2")
+            .then(result => {
+                expect(result.value).toBeUndefined();
+                expect(result.done).toBeTrue();
+            })
+            .catch(e => {
+                expect().fail(`Generator threw an unhandled exception: ${e}`);
+            });
+        runQueuedPromiseJobs();
+    });
+
+    async function* injectedCompletionGenerator() {
+        try {
+            yield 1;
+        } finally {
+            yield 2;
+        }
+    }
+
+    const injectedCompletionGeneratorObject = injectedCompletionGenerator();
+
+    test("return completion is injected into generator", () => {
+        injectedCompletionGeneratorObject
+            .next("bad1")
+            .then(result => {
+                expect(result.value).toBe(1);
+                expect(result.done).toBeFalse();
+            })
+            .catch(e => {
+                expect().fail(`Generator threw an unhandled exception: ${e}`);
+            });
+        runQueuedPromiseJobs();
+
+        injectedCompletionGeneratorObject
+            .return(3)
+            .then(result => {
+                expect(result.value).toBe(2);
+                expect(result.done).toBeFalse();
+            })
+            .catch(e => {
+                expect().fail(`Generator threw an unhandled exception: ${e}`);
+            });
+        runQueuedPromiseJobs();
+
+        injectedCompletionGeneratorObject
+            .next("bad3")
+            .then(result => {
+                expect(result.value).toBe(3);
+                expect(result.done).toBeTrue();
+            })
+            .catch(e => {
+                expect().fail(`Generator threw an unhandled exception: ${e}`);
+            });
+        runQueuedPromiseJobs();
+
+        injectedCompletionGeneratorObject
+            .next("bad4")
+            .then(result => {
+                expect(result.value).toBeUndefined();
+                expect(result.done).toBeTrue();
+            })
+            .catch(e => {
+                expect().fail(`Generator threw an unhandled exception: ${e}`);
+            });
+        runQueuedPromiseJobs();
+    });
+});
+
+describe("errors", () => {
+    test("this value must be an AsyncGenerator object", () => {
+        async function* generator() {}
+        let rejection = null;
+        generator.prototype.return.call("foo").catch(error => {
+            rejection = error;
+        });
+        runQueuedPromiseJobs();
+        expect(rejection).toBeInstanceOf(TypeError);
+        expect(rejection.message).toBe("Not an object of type AsyncGenerator");
+    });
+});

+ 132 - 0
Userland/Libraries/LibJS/Tests/builtins/AsyncGenerator/AsyncGenerator.prototype.throw.js

@@ -0,0 +1,132 @@
+describe("correct behavior", () => {
+    async function* emptyGeneratorFunction() {}
+
+    test("length is 1", () => {
+        expect(emptyGeneratorFunction.prototype.throw).toHaveLength(1);
+    });
+
+    const emptyGenerator = emptyGeneratorFunction();
+
+    test("throw from SuspendedStart", () => {
+        emptyGenerator
+            .throw(1337)
+            .then(() => {
+                expect().fail("Generator did NOT throw an unhandled exception.");
+            })
+            .catch(e => {
+                expect(e).toBe(1337);
+            });
+        runQueuedPromiseJobs();
+    });
+
+    test("throw from Completed", () => {
+        emptyGenerator
+            .throw(123)
+            .then(() => {
+                expect().fail("Generator did NOT throw an unhandled exception.");
+            })
+            .catch(e => {
+                expect(e).toBe(123);
+            });
+        runQueuedPromiseJobs();
+    });
+
+    async function* generatorTwo() {
+        yield 1337;
+        yield 123;
+    }
+
+    const generatorTwoIterator = generatorTwo();
+
+    test("throw from SuspendedYield", () => {
+        generatorTwoIterator
+            .next("bad1")
+            .then(result => {
+                expect(result.value).toBe(1337);
+                expect(result.done).toBeFalse();
+            })
+            .catch(e => {
+                expect().fail(`Generator threw an unhandled exception: ${e}`);
+            });
+        runQueuedPromiseJobs();
+
+        generatorTwoIterator
+            .throw(999)
+            .then(() => {
+                expect().fail("Generator did NOT throw an unhandled exception.");
+            })
+            .catch(e => {
+                expect(e).toBe(999);
+            });
+        runQueuedPromiseJobs();
+
+        generatorTwoIterator
+            .next("bad2")
+            .then(result => {
+                expect(result.value).toBeUndefined();
+                expect(result.done).toBeTrue();
+            })
+            .catch(e => {
+                expect().fail(`Generator threw an unhandled exception: ${e}`);
+            });
+        runQueuedPromiseJobs();
+    });
+
+    async function* injectedCompletionGenerator() {
+        try {
+            yield 1;
+        } catch (e) {
+            yield e;
+        }
+    }
+
+    const injectedCompletionGeneratorObject = injectedCompletionGenerator();
+
+    test("throw completion is injected into generator", () => {
+        injectedCompletionGeneratorObject
+            .next("bad1")
+            .then(result => {
+                expect(result.value).toBe(1);
+                expect(result.done).toBeFalse();
+            })
+            .catch(e => {
+                expect().fail(`Generator threw an unhandled exception: ${e}`);
+            });
+        runQueuedPromiseJobs();
+
+        injectedCompletionGeneratorObject
+            .throw(9999)
+            .then(result => {
+                expect(result.value).toBe(9999);
+                expect(result.done).toBeFalse();
+            })
+            .catch(e => {
+                expect().fail(`Generator threw an unhandled exception: ${e}`);
+            });
+        runQueuedPromiseJobs();
+
+        injectedCompletionGeneratorObject
+            .next("bad2")
+            .then(result => {
+                expect(result.value).toBeUndefined();
+                expect(result.done).toBeTrue();
+            })
+            .catch(e => {
+                expect().fail(`Generator threw an unhandled exception: ${e}`);
+            });
+        runQueuedPromiseJobs();
+    });
+});
+
+describe("errors", () => {
+    test("this value must be an AsyncGenerator object", () => {
+        async function* generator() {}
+        let rejection = null;
+        generator.prototype.throw.call("foo").catch(error => {
+            rejection = error;
+        });
+        runQueuedPromiseJobs();
+        expect(rejection).toBeInstanceOf(TypeError);
+        expect(rejection.message).toBe("Not an object of type AsyncGenerator");
+    });
+});