| Index: src/builtins/builtins-async-generator.cc
|
| diff --git a/src/builtins/builtins-async-generator.cc b/src/builtins/builtins-async-generator.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..2bb8b79ff8260a6836703a5966fdd1e36ad10e23
|
| --- /dev/null
|
| +++ b/src/builtins/builtins-async-generator.cc
|
| @@ -0,0 +1,503 @@
|
| +// Copyright 2016 the V8 project authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "src/builtins/builtins-async.h"
|
| +#include "src/builtins/builtins-utils.h"
|
| +#include "src/builtins/builtins.h"
|
| +#include "src/code-factory.h"
|
| +#include "src/code-stub-assembler.h"
|
| +#include "src/frames-inl.h"
|
| +
|
| +namespace v8 {
|
| +namespace internal {
|
| +
|
| +namespace {
|
| +
|
| +// Describe fields of Context associated with AsyncGeneratorAwait resume
|
| +// closures.
|
| +class AwaitContext {
|
| + public:
|
| + enum Fields { kGeneratorSlot = Context::MIN_CONTEXT_SLOTS, kLength };
|
| +};
|
| +
|
| +} // anonymous namespace
|
| +
|
| +// https://tc39.github.io/proposal-async-iteration/
|
| +// Section #sec-asyncgenerator-prototype-next
|
| +TF_BUILTIN(AsyncGeneratorPrototypeNext, AsyncBuiltinsAssembler) {
|
| + Node* const generator = Parameter(0);
|
| + Node* const value = Parameter(1);
|
| + Node* const context = Parameter(4);
|
| + AsyncGeneratorEnqueue(context, generator, value,
|
| + JSAsyncGeneratorObject::kNext,
|
| + "[AsyncGenerator].prototype.next");
|
| +}
|
| +
|
| +// https://tc39.github.io/proposal-async-iteration/
|
| +// Section #sec-asyncgenerator-prototype-return
|
| +TF_BUILTIN(AsyncGeneratorPrototypeReturn, AsyncBuiltinsAssembler) {
|
| + Node* generator = Parameter(0);
|
| + Node* value = Parameter(1);
|
| + Node* context = Parameter(4);
|
| + AsyncGeneratorEnqueue(context, generator, value,
|
| + JSAsyncGeneratorObject::kReturn,
|
| + "[AsyncGenerator].prototype.return");
|
| +}
|
| +
|
| +// https://tc39.github.io/proposal-async-iteration/
|
| +// Section #sec-asyncgenerator-prototype-throw
|
| +TF_BUILTIN(AsyncGeneratorPrototypeThrow, AsyncBuiltinsAssembler) {
|
| + Node* generator = Parameter(0);
|
| + Node* value = Parameter(1);
|
| + Node* context = Parameter(4);
|
| + AsyncGeneratorEnqueue(context, generator, value,
|
| + JSAsyncGeneratorObject::kThrow,
|
| + "[AsyncGenerator].prototype.throw");
|
| +}
|
| +
|
| +TF_BUILTIN(AsyncGeneratorYield, AsyncBuiltinsAssembler) {
|
| + Node* const generator = Parameter(0);
|
| + Node* const value = Parameter(1);
|
| + Node* const context = Parameter(4);
|
| +
|
| + CSA_ASSERT_JS_ARGC_EQ(this, 1);
|
| + CSA_SLOW_ASSERT(this,
|
| + HasInstanceType(generator, JS_ASYNC_GENERATOR_OBJECT_TYPE));
|
| +
|
| + Node* request = TakeFirstAsyncGeneratorRequestFromQueue(generator);
|
| +
|
| + Node* const promise =
|
| + LoadObjectField(request, AsyncGeneratorRequest::kPromiseOffset);
|
| +
|
| + // The request Promise must still be pending at this point.
|
| + CSA_SLOW_ASSERT(this,
|
| + SmiEqual(LoadObjectField(promise, JSPromise::kStatusOffset),
|
| + SmiConstant(v8::Promise::kPending)));
|
| +
|
| + // If a yield expression is reached, we must not be awaiting any value.
|
| + CSA_SLOW_ASSERT(
|
| + this, IsUndefined(LoadObjectField(
|
| + generator, JSAsyncGeneratorObject::kAwaitedPromiseOffset)));
|
| +
|
| + Node* const iter_result =
|
| + CallStub(CodeFactory::CreateIterResultObject(isolate()), context, value,
|
| + FalseConstant());
|
| +
|
| + InternalResolvePromise(context, promise, iter_result);
|
| +
|
| + AsyncGeneratorResumeNext(context, generator);
|
| +
|
| + Return(UndefinedConstant());
|
| +}
|
| +
|
| +TF_BUILTIN(AsyncGeneratorRawYield, AsyncBuiltinsAssembler) {
|
| + Node* const generator = Parameter(0);
|
| + Node* const value = Parameter(1);
|
| + Node* const context = Parameter(4);
|
| +
|
| + CSA_ASSERT_JS_ARGC_EQ(this, 1);
|
| + CSA_SLOW_ASSERT(this,
|
| + HasInstanceType(generator, JS_ASYNC_GENERATOR_OBJECT_TYPE));
|
| +
|
| + Node* request = TakeFirstAsyncGeneratorRequestFromQueue(generator);
|
| +
|
| + Node* const promise =
|
| + LoadObjectField(request, AsyncGeneratorRequest::kPromiseOffset);
|
| +
|
| + // The request Promise must still be pending at this point.
|
| + CSA_SLOW_ASSERT(this,
|
| + SmiEqual(LoadObjectField(promise, JSPromise::kStatusOffset),
|
| + SmiConstant(v8::Promise::kPending)));
|
| +
|
| + // If a yield expression is reached, we must not be awaiting any value.
|
| + CSA_SLOW_ASSERT(
|
| + this, IsUndefined(LoadObjectField(
|
| + generator, JSAsyncGeneratorObject::kAwaitedPromiseOffset)));
|
| +
|
| + InternalResolvePromise(context, promise, value);
|
| +
|
| + AsyncGeneratorResumeNext(context, generator);
|
| +
|
| + Return(UndefinedConstant());
|
| +}
|
| +
|
| +TF_BUILTIN(AsyncGeneratorAwaitResolveClosure, AsyncBuiltinsAssembler) {
|
| + Node* value = Parameter(1);
|
| + Node* context = Parameter(4);
|
| +
|
| + AsyncGeneratorAwaitResumeClosure(context, value,
|
| + JSAsyncGeneratorObject::kNext);
|
| + Return(UndefinedConstant());
|
| +}
|
| +
|
| +TF_BUILTIN(AsyncGeneratorAwaitRejectClosure, AsyncBuiltinsAssembler) {
|
| + Node* value = Parameter(1);
|
| + Node* context = Parameter(4);
|
| +
|
| + AsyncGeneratorAwaitResumeClosure(context, value,
|
| + JSAsyncGeneratorObject::kThrow);
|
| + Return(UndefinedConstant());
|
| +}
|
| +
|
| +TF_BUILTIN(AsyncGeneratorAwaitUncaught, AsyncBuiltinsAssembler) {
|
| + const bool kIsCatchable = false;
|
| + AsyncGeneratorAwait(kIsCatchable);
|
| +}
|
| +
|
| +TF_BUILTIN(AsyncGeneratorAwaitCaught, AsyncBuiltinsAssembler) {
|
| + const bool kIsCatchable = true;
|
| + AsyncGeneratorAwait(kIsCatchable);
|
| +}
|
| +
|
| +// Shared implementation for the 3 Async Iterator protocol methods of Async
|
| +// Generators.
|
| +void AsyncBuiltinsAssembler::AsyncGeneratorEnqueue(
|
| + Node* context, Node* generator, Node* value,
|
| + JSAsyncGeneratorObject::ResumeMode resume_mode, const char* method_name) {
|
| + // AsyncGeneratorEnqueue produces a new Promise, and appends it to the list
|
| + // of async generator requests to be executed. If the generator is not
|
| + // presently executing, then this method will loop through, processing each
|
| + // request from front to back.
|
| + // This loop resides in AsyncGeneratorResumeNext.
|
| + Node* promise = AllocateAndInitJSPromise(context);
|
| +
|
| + Label enqueue(this), if_receiverisincompatible(this, Label::kDeferred);
|
| +
|
| + GotoIf(TaggedIsSmi(generator), &if_receiverisincompatible);
|
| + Branch(HasInstanceType(generator, JS_ASYNC_GENERATOR_OBJECT_TYPE), &enqueue,
|
| + &if_receiverisincompatible);
|
| +
|
| + Bind(&enqueue);
|
| + {
|
| + Label done(this);
|
| + Node* const req =
|
| + AllocateAsyncGeneratorRequest(resume_mode, value, promise);
|
| +
|
| + AddAsyncGeneratorRequestToQueue(generator, req);
|
| +
|
| + // Let state be generator.[[AsyncGeneratorState]]
|
| + // If state is not "executing", then
|
| + // Perform AsyncGeneratorResumeNext(Generator)
|
| + // Check if the {receiver} is running or already closed.
|
| + Node* continuation =
|
| + LoadObjectField(generator, JSAsyncGeneratorObject::kContinuationOffset);
|
| +
|
| + GotoIf(SmiEqual(continuation,
|
| + SmiConstant(JSAsyncGeneratorObject::kGeneratorExecuting)),
|
| + &done);
|
| +
|
| + AsyncGeneratorResumeNext(context, generator, continuation);
|
| +
|
| + Goto(&done);
|
| + Bind(&done);
|
| + Return(promise);
|
| + }
|
| +
|
| + Bind(&if_receiverisincompatible);
|
| + {
|
| + Node* native_context = LoadNativeContext(context);
|
| + Node* make_type_error =
|
| + LoadContextElement(native_context, Context::MAKE_TYPE_ERROR_INDEX);
|
| + Handle<String> method =
|
| + factory()->NewStringFromAsciiChecked(method_name, TENURED);
|
| + Node* error =
|
| + CallJS(CodeFactory::Call(isolate()), context, make_type_error,
|
| + UndefinedConstant(),
|
| + SmiConstant(MessageTemplate::kIncompatibleMethodReceiver),
|
| + HeapConstant(method), generator);
|
| +
|
| + CallRuntime(Runtime::kPromiseReject, context, promise, error,
|
| + TrueConstant());
|
| + Return(promise);
|
| + }
|
| +}
|
| +
|
| +void AsyncBuiltinsAssembler::AsyncGeneratorResumeNext(Node* context,
|
| + Node* generator,
|
| + Node* continuation) {
|
| + // Loop through each queued request, from first to last, and resuming the
|
| + // generator. Will not resume generator if resumed with an abrupt completion
|
| + // such as via the .throw() or .return() methods, or if an Await is
|
| + // in progress.
|
| + Variable var_request(this, MachineRepresentation::kTagged);
|
| + Variable var_continuation(this, MachineRepresentation::kTaggedSigned);
|
| + var_continuation.Bind(continuation);
|
| +
|
| + Variable* loop_vars[] = {&var_request, &var_continuation};
|
| + Label loop(this, arraysize(loop_vars),
|
| + reinterpret_cast<Variable **>(loop_vars)),
|
| + done(this);
|
| +
|
| + var_request.Bind(
|
| + LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset));
|
| + Branch(IsUndefined(var_request.value()), &done, &loop);
|
| +
|
| + Bind(&loop);
|
| + {
|
| + // Stop consuming the AsyncGeneratorRequest queue and resuming the generator
|
| + // if an Await is in progress.
|
| + Node* awaited = LoadObjectField(
|
| + generator, JSAsyncGeneratorObject::kAwaitedPromiseOffset);
|
| + GotoUnless(IsUndefined(awaited), &done);
|
| +
|
| + AsyncGeneratorResumeNextStep(context, generator, var_request.value(),
|
| + var_continuation.value());
|
| +
|
| + var_continuation.Bind(LoadObjectField(
|
| + generator, JSAsyncGeneratorObject::kContinuationOffset));
|
| + var_request.Bind(
|
| + LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset));
|
| +
|
| + // Continue loop if there is another request queued up.
|
| + Branch(IsUndefined(var_request.value()), &done, &loop);
|
| + }
|
| +
|
| + Bind(&done);
|
| +}
|
| +
|
| +void AsyncBuiltinsAssembler::AsyncGeneratorResumeNextStep(Node* context,
|
| + Node* generator,
|
| + Node* request,
|
| + Node* continuation) {
|
| + Variable var_return(this, MachineRepresentation::kTagged);
|
| + var_return.Bind(UndefinedConstant());
|
| + CSA_SLOW_ASSERT(this, HasInstanceType(request, ASYNC_GENERATOR_REQUEST_TYPE));
|
| +
|
| + Label loop_next(this);
|
| +
|
| + // Let completion be next.[[Completion]]
|
| + Node* resume_mode =
|
| + LoadObjectField(request, AsyncGeneratorRequest::kResumeModeOffset);
|
| + Node* value = LoadObjectField(request, AsyncGeneratorRequest::kValueOffset);
|
| +
|
| + // If completion is an abrupt completion, then
|
| + Label if_abruptcompletion(this), check_ifcompleted(this),
|
| + resume_generator(this), if_return(this, &var_return), if_throw(this);
|
| + Branch(SmiEqual(resume_mode, SmiConstant(JSAsyncGeneratorObject::kNext)),
|
| + &check_ifcompleted, &if_abruptcompletion);
|
| +
|
| + Bind(&if_abruptcompletion);
|
| + {
|
| + Label check_completed(this), if_completed(this);
|
| +
|
| + // If state is "suspendedStart", then
|
| + // Set generator.[[AsyncGeneratorState]] to "completed"
|
| + // Set state to "completed"
|
| + GotoUnless(SmiEqual(continuation, SmiConstant(0)), &check_completed);
|
| + StoreObjectFieldNoWriteBarrier(
|
| + generator, JSAsyncGeneratorObject::kContinuationOffset,
|
| + SmiConstant(JSAsyncGeneratorObject::kGeneratorClosed));
|
| + Goto(&if_completed);
|
| +
|
| + Bind(&check_completed);
|
| + Branch(SmiEqual(continuation,
|
| + SmiConstant(JSAsyncGeneratorObject::kGeneratorClosed)),
|
| + &if_completed, &resume_generator);
|
| +
|
| + Bind(&if_completed);
|
| + {
|
| + // If state is "completed", then
|
| + // If completion.[[Type]] is return, then return
|
| + // ! AsyncGeneratorResolve(generator, completion.[[Value]], true).
|
| + // Else, return ! AsyncGeneratorReject(generator,
|
| + // completion.[[Value]])
|
| + var_return.Bind(value);
|
| + Branch(
|
| + SmiEqual(resume_mode, SmiConstant(JSAsyncGeneratorObject::kReturn)),
|
| + &if_return, &if_throw);
|
| + }
|
| + }
|
| +
|
| + Bind(&check_ifcompleted);
|
| + {
|
| + // Else if state is "completed", then return !
|
| + // AsyncGeneratorResolve(generator, undefined, true).
|
| + Branch(SmiEqual(continuation,
|
| + SmiConstant(JSAsyncGeneratorObject::kGeneratorClosed)),
|
| + &if_return, &resume_generator);
|
| + }
|
| +
|
| + Bind(&resume_generator);
|
| + {
|
| + CSA_ASSERT(this, SmiGreaterThanOrEqual(continuation, SmiConstant(0)));
|
| + Node* result = CallStub(CodeFactory::ResumeGenerator(isolate()), context,
|
| + value, generator, resume_mode);
|
| + Goto(&loop_next);
|
| + }
|
| +
|
| + Bind(&if_return);
|
| + {
|
| + Node* const request = TakeFirstAsyncGeneratorRequestFromQueue(generator);
|
| + Node* const promise =
|
| + LoadObjectField(request, AsyncGeneratorRequest::kPromiseOffset);
|
| + Node* const result =
|
| + CallStub(CodeFactory::CreateIterResultObject(isolate()), context,
|
| + var_return.value(), TrueConstant());
|
| + PromiseFulfill(context, promise, result, v8::Promise::kFulfilled);
|
| + Goto(&loop_next);
|
| + }
|
| +
|
| + Bind(&if_throw);
|
| + {
|
| + Node* request = TakeFirstAsyncGeneratorRequestFromQueue(generator);
|
| +
|
| + // Let promiseCapability be next.[[Capability]].
|
| + Node* promise =
|
| + LoadObjectField(request, AsyncGeneratorRequest::kPromiseOffset);
|
| +
|
| + // Perform ! Call(promiseCapability.[[Reject]], undefined, « exception »).
|
| + CallRuntime(Runtime::kPromiseReject, context, promise, value,
|
| + TrueConstant());
|
| + Goto(&loop_next);
|
| + }
|
| +
|
| + Bind(&loop_next);
|
| +}
|
| +
|
| +Node* AsyncBuiltinsAssembler::AllocateAsyncGeneratorRequest(
|
| + JSAsyncGeneratorObject::ResumeMode resume_mode, Node* resume_value,
|
| + Node* promise) {
|
| + CSA_SLOW_ASSERT(this, HasInstanceType(promise, JS_PROMISE_TYPE));
|
| + Node* request = Allocate(AsyncGeneratorRequest::kSize);
|
| + StoreMapNoWriteBarrier(request, Heap::kAsyncGeneratorRequestMapRootIndex);
|
| + StoreObjectFieldNoWriteBarrier(request, AsyncGeneratorRequest::kNextOffset,
|
| + UndefinedConstant());
|
| + StoreObjectFieldNoWriteBarrier(request,
|
| + AsyncGeneratorRequest::kResumeModeOffset,
|
| + SmiConstant(resume_mode));
|
| + StoreObjectFieldNoWriteBarrier(request, AsyncGeneratorRequest::kValueOffset,
|
| + resume_value);
|
| + StoreObjectFieldNoWriteBarrier(request, AsyncGeneratorRequest::kPromiseOffset,
|
| + promise);
|
| + return request;
|
| +}
|
| +
|
| +void AsyncBuiltinsAssembler::AsyncGeneratorAwaitResumeClosure(
|
| + Node* context, Node* value,
|
| + JSAsyncGeneratorObject::ResumeMode resume_mode) {
|
| + Label done(this);
|
| + Node* const generator =
|
| + LoadContextElement(context, AwaitContext::kGeneratorSlot);
|
| + CSA_SLOW_ASSERT(this,
|
| + HasInstanceType(generator, JS_ASYNC_GENERATOR_OBJECT_TYPE));
|
| +
|
| + Node* const promise =
|
| + LoadObjectField(generator, JSAsyncGeneratorObject::kAwaitedPromiseOffset);
|
| + CSA_SLOW_ASSERT(this, HasInstanceType(promise, JS_PROMISE_TYPE));
|
| + CSA_SLOW_ASSERT(this,
|
| + SmiAbove(LoadObjectField(promise, JSPromise::kStatusOffset),
|
| + SmiConstant(v8::Promise::kPending)));
|
| +
|
| + StoreObjectFieldNoWriteBarrier(generator,
|
| + JSAsyncGeneratorObject::kAwaitedPromiseOffset,
|
| + UndefinedConstant());
|
| +
|
| + Node* const continuation =
|
| + LoadObjectField(generator, JSAsyncGeneratorObject::kContinuationOffset);
|
| +
|
| + GotoUnless(SmiGreaterThanOrEqual(continuation, SmiConstant(0)), &done);
|
| + CallStub(CodeFactory::ResumeAwaitedGenerator(isolate()), context, value,
|
| + generator, SmiConstant(resume_mode));
|
| +
|
| + Goto(&done);
|
| + Bind(&done);
|
| + AsyncGeneratorResumeNext(context, generator);
|
| +}
|
| +
|
| +void AsyncBuiltinsAssembler::AsyncGeneratorAwait(bool is_catchable) {
|
| + Node* generator = Parameter(1);
|
| + Node* value = Parameter(2);
|
| + Node* context = Parameter(5);
|
| +
|
| + CSA_ASSERT_JS_ARGC_EQ(this, 2);
|
| +
|
| + CSA_SLOW_ASSERT(this,
|
| + HasInstanceType(generator, JS_ASYNC_GENERATOR_OBJECT_TYPE));
|
| +
|
| + Node* const request =
|
| + LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset);
|
| + CSA_SLOW_ASSERT(this, WordNotEqual(request, UndefinedConstant()));
|
| +
|
| + NodeGenerator1 closure_context = [&](Node* native_context) -> Node* {
|
| + Node* const context =
|
| + CreatePromiseContext(native_context, AwaitContext::kLength);
|
| + StoreContextElementNoWriteBarrier(context, AwaitContext::kGeneratorSlot,
|
| + generator);
|
| + return context;
|
| + };
|
| +
|
| + Node* outer_promise =
|
| + LoadObjectField(request, AsyncGeneratorRequest::kPromiseOffset);
|
| +
|
| + const int resolve_index = Context::ASYNC_GENERATOR_AWAIT_RESOLVE_SHARED_FUN;
|
| + const int reject_index = Context::ASYNC_GENERATOR_AWAIT_REJECT_SHARED_FUN;
|
| +
|
| + Node* promise =
|
| + Await(context, generator, value, outer_promise, closure_context,
|
| + resolve_index, reject_index, is_catchable);
|
| +
|
| + CSA_SLOW_ASSERT(
|
| + this, IsUndefined(LoadObjectField(
|
| + generator, JSAsyncGeneratorObject::kAwaitedPromiseOffset)));
|
| + StoreObjectField(generator, JSAsyncGeneratorObject::kAwaitedPromiseOffset,
|
| + promise);
|
| + Return(UndefinedConstant());
|
| +}
|
| +
|
| +void AsyncBuiltinsAssembler::AddAsyncGeneratorRequestToQueue(Node* generator,
|
| + Node* request) {
|
| + Variable var_current(this, MachineRepresentation::kTagged);
|
| + Label empty(this), loop(this, &var_current), done(this);
|
| +
|
| + var_current.Bind(
|
| + LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset));
|
| + Branch(IsUndefined(var_current.value()), &empty, &loop);
|
| +
|
| + Bind(&empty);
|
| + {
|
| + StoreObjectField(generator, JSAsyncGeneratorObject::kQueueOffset, request);
|
| + Goto(&done);
|
| + }
|
| +
|
| + Bind(&loop);
|
| + {
|
| + Label loop_next(this), next_empty(this);
|
| + Node* current = var_current.value();
|
| + Node* next = LoadObjectField(current, AsyncGeneratorRequest::kNextOffset);
|
| +
|
| + Branch(IsUndefined(next), &next_empty, &loop_next);
|
| + Bind(&next_empty);
|
| + {
|
| + StoreObjectField(current, AsyncGeneratorRequest::kNextOffset, request);
|
| + Goto(&done);
|
| + }
|
| +
|
| + Bind(&loop_next);
|
| + {
|
| + var_current.Bind(next);
|
| + Goto(&loop);
|
| + }
|
| + }
|
| + Bind(&done);
|
| +}
|
| +
|
| +Node* AsyncBuiltinsAssembler::TakeFirstAsyncGeneratorRequestFromQueue(
|
| + Node* generator) {
|
| + // Removes and returns the first AsyncGeneratorRequest from a
|
| + // JSAsyncGeneratorObject's queue. Asserts that the queue is not empty.
|
| + CSA_SLOW_ASSERT(this,
|
| + HasInstanceType(generator, JS_ASYNC_GENERATOR_OBJECT_TYPE));
|
| +
|
| + Node* request =
|
| + LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset);
|
| + CSA_SLOW_ASSERT(this, WordNotEqual(request, UndefinedConstant()));
|
| +
|
| + Node* next = LoadObjectField(request, AsyncGeneratorRequest::kNextOffset);
|
| +
|
| + StoreObjectField(generator, JSAsyncGeneratorObject::kQueueOffset, next);
|
| + return request;
|
| +}
|
| +
|
| +} // namespace internal
|
| +} // namespace v8
|
|
|