LibJS/Bytecode: Implement async generators

This commit is contained in:
Luke Wilde 2023-07-14 21:57:49 +01:00 committed by Linus Groh
parent d4e30710e7
commit d1cb78c411
Notes: sideshowbarker 2024-07-17 01:00:06 +09:00
9 changed files with 1290 additions and 71 deletions

View file

@ -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>();

View file

@ -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.
}
}

View file

@ -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;
};
}

View file

@ -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();
}
}

View file

@ -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_);
};
}

View file

@ -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();

View file

@ -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");
});
});

View file

@ -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");
});
});

View file

@ -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");
});
});