Chromium Code Reviews| 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..4a309c571c6cd0d31490d0f4210f4e60a1d25076 |
| --- /dev/null |
| +++ b/src/builtins/builtins-async-generator.cc |
| @@ -0,0 +1,477 @@ |
| +// 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, |
| + WordEqual(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( |
| + request, AsyncGeneratorRequest::kAwaitedPromiseOffset))); |
| + |
| + Node* const iter_result = AllocateJSIteratorResult(context, value, false); |
|
jgruber
2017/01/13 13:34:39
I just recently landed CreateIterResultObject, per
caitp
2017/01/13 14:36:04
Sgtm
caitp
2017/01/17 19:23:11
Done.
|
| + |
| + InternalResolvePromise(context, promise, iter_result); |
| + |
| + 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 is resides in AsyncGeneratorResumeNext. |
|
jgruber
2017/01/13 13:34:42
Nit: is resides
|
| + 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(WordEqual(var_request.value(), UndefinedConstant()), &done, &loop); |
| + Bind(&loop); |
| + { |
| + AsyncGeneratorResumeNextStep(context, generator, var_request.value(), |
| + var_continuation.value(), &done); |
| + |
| + var_continuation.Bind(LoadObjectField( |
| + generator, JSAsyncGeneratorObject::kContinuationOffset)); |
| + var_request.Bind( |
| + LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset)); |
| + |
| + // Continue loop if there is another request queued up. |
| + Branch(WordEqual(var_request.value(), UndefinedConstant()), &done, &loop); |
| + } |
| + |
| + Bind(&done); |
| +} |
| + |
| +void AsyncBuiltinsAssembler::AsyncGeneratorResumeNextStep(Node* context, |
| + Node* generator, |
| + Node* request, |
| + Node* continuation, |
| + Label* exit_loop) { |
| + Variable var_return(this, MachineRepresentation::kTagged); |
| + var_return.Bind(UndefinedConstant()); |
| + CSA_SLOW_ASSERT(this, HasInstanceType(request, ASYNC_GENERATOR_REQUEST_TYPE)); |
| + |
| + Label start(this), loop_next(this); |
| + |
| + // Stop consuming the AsyncGeneratorRequest queue and resuming the generator |
| + // if an Await is in progress. |
| + Node* awaited = |
| + LoadObjectField(request, AsyncGeneratorRequest::kAwaitedPromiseOffset); |
| + Branch(IsUndefined(awaited), &start, exit_loop); |
| + |
| + Bind(&start); |
| + |
| + // 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" |
| + GotoIf(WordNotEqual(continuation, SmiConstant(0)), &check_completed); |
| + StoreObjectField(generator, JSAsyncGeneratorObject::kContinuationOffset, |
|
jgruber
2017/01/13 13:34:36
I guess we could use the NoWriteBarrier variant he
caitp
2017/01/17 19:23:11
Done.
|
| + SmiConstant(JSAsyncGeneratorObject::kGeneratorClosed)); |
| + Goto(&if_completed); |
| + |
| + Bind(&check_completed); |
| + Branch(WordEqual(continuation, |
|
jgruber
2017/01/13 13:34:38
SmiEqual here and at other smi comparisons.
caitp
2017/01/17 19:23:11
Done.
|
| + 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( |
| + WordEqual(resume_mode, SmiConstant(JSAsyncGeneratorObject::kReturn)), |
| + &if_return, &if_throw); |
| + } |
| + } |
| + |
| + Bind(&check_ifcompleted); |
| + { |
| + // Else if state is "completed", then return ! |
| + // AsyncGeneratorResolve(generator, undefined, true). |
| + Branch(WordEqual(continuation, |
| + SmiConstant(JSAsyncGeneratorObject::kGeneratorClosed)), |
| + &if_return, &resume_generator); |
| + } |
| + |
| + Bind(&resume_generator); |
| + { |
| + CSA_ASSERT(this, SmiAboveOrEqual(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 = |
| + AllocateJSIteratorResult(context, var_return.value(), true); |
| + 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); |
| + StoreObjectFieldNoWriteBarrier(request, |
| + AsyncGeneratorRequest::kAwaitedPromiseOffset, |
| + UndefinedConstant()); |
| + 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 request = |
| + LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset); |
| + CSA_SLOW_ASSERT(this, HasInstanceType(request, ASYNC_GENERATOR_REQUEST_TYPE)); |
| + |
| + Node* const promise = |
| + LoadObjectField(request, AsyncGeneratorRequest::kAwaitedPromiseOffset); |
| + CSA_SLOW_ASSERT(this, HasInstanceType(promise, JS_PROMISE_TYPE)); |
| + CSA_SLOW_ASSERT( |
| + this, WordNotEqual(LoadObjectField(promise, JSPromise::kStatusOffset), |
| + SmiConstant(v8::Promise::kPending))); |
| + |
| + StoreObjectField(request, AsyncGeneratorRequest::kAwaitedPromiseOffset, |
|
jgruber
2017/01/13 13:34:36
NoWriteBarrier
caitp
2017/01/13 14:36:04
Acknowledged.
caitp
2017/01/17 19:23:11
and done
|
| + UndefinedConstant()); |
| + |
| + Node* const continuation = |
| + LoadObjectField(generator, JSAsyncGeneratorObject::kContinuationOffset); |
| + |
| + GotoUnless(SmiAboveOrEqual(continuation, SmiConstant(0)), &done); |
|
jgruber
2017/01/13 13:34:35
Isn't an unsigned '>= 0' comparison always true?
caitp
2017/01/13 14:36:04
If the generator is executing or closed, no.
jgruber
2017/01/13 15:46:40
My point was that there is no way to make SmiAbove
caitp
2017/01/17 19:23:11
I understand now, per offline discussion. Changed
|
| + |
| + CallStub(CodeFactory::ResumeGenerator(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 reject_index = Context::ASYNC_GENERATOR_AWAIT_REJECT_SHARED_FUN; |
| + STATIC_ASSERT((Context::ASYNC_GENERATOR_AWAIT_REJECT_SHARED_FUN + 1) == |
| + Context::ASYNC_GENERATOR_AWAIT_RESOLVE_SHARED_FUN); |
| + |
| + Node* promise = Await(context, generator, value, outer_promise, |
| + closure_context, reject_index, is_catchable); |
| + |
| + CSA_SLOW_ASSERT(this, |
| + IsUndefined(LoadObjectField( |
| + request, AsyncGeneratorRequest::kAwaitedPromiseOffset))); |
| + StoreObjectField(request, AsyncGeneratorRequest::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 |