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 |