Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(906)

Unified Diff: src/builtins/builtins-async-generator.cc

Issue 2622833002: WIP [esnext] implement async iteration proposal (Closed)
Patch Set: simplify AsyncIteratorValueUnwrap Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « src/builtins/builtins-async.cc ('k') | src/builtins/builtins-async-iterator.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « src/builtins/builtins-async.cc ('k') | src/builtins/builtins-async-iterator.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698