| // Copyright 2017 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-gen.h" |
| #include "src/builtins/builtins-utils-gen.h" |
| #include "src/builtins/builtins.h" |
| #include "src/codegen/code-factory.h" |
| #include "src/codegen/code-stub-assembler.h" |
| #include "src/execution/frames-inl.h" |
| #include "src/objects/js-generator.h" |
| #include "src/objects/js-promise.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| using compiler::Node; |
| |
| namespace { |
| |
| class AsyncGeneratorBuiltinsAssembler : public AsyncBuiltinsAssembler { |
| public: |
| explicit AsyncGeneratorBuiltinsAssembler(CodeAssemblerState* state) |
| : AsyncBuiltinsAssembler(state) {} |
| |
| inline Node* TaggedIsAsyncGenerator(Node* tagged_object) { |
| TNode<BoolT> if_notsmi = TaggedIsNotSmi(tagged_object); |
| return Select<BoolT>(if_notsmi, |
| [=] { |
| return HasInstanceType( |
| tagged_object, JS_ASYNC_GENERATOR_OBJECT_TYPE); |
| }, |
| [=] { return if_notsmi; }); |
| } |
| inline Node* LoadGeneratorState(Node* const generator) { |
| return LoadObjectField(generator, JSGeneratorObject::kContinuationOffset); |
| } |
| |
| inline TNode<BoolT> IsGeneratorStateClosed(SloppyTNode<Smi> const state) { |
| return SmiEqual(state, SmiConstant(JSGeneratorObject::kGeneratorClosed)); |
| } |
| inline TNode<BoolT> IsGeneratorClosed(Node* const generator) { |
| return IsGeneratorStateClosed(LoadGeneratorState(generator)); |
| } |
| |
| inline TNode<BoolT> IsGeneratorStateSuspended(SloppyTNode<Smi> const state) { |
| return SmiGreaterThanOrEqual(state, SmiConstant(0)); |
| } |
| |
| inline TNode<BoolT> IsGeneratorSuspended(Node* const generator) { |
| return IsGeneratorStateSuspended(LoadGeneratorState(generator)); |
| } |
| |
| inline TNode<BoolT> IsGeneratorStateSuspendedAtStart( |
| SloppyTNode<Smi> const state) { |
| return SmiEqual(state, SmiConstant(0)); |
| } |
| |
| inline TNode<BoolT> IsGeneratorStateNotExecuting( |
| SloppyTNode<Smi> const state) { |
| return SmiNotEqual(state, |
| SmiConstant(JSGeneratorObject::kGeneratorExecuting)); |
| } |
| inline TNode<BoolT> IsGeneratorNotExecuting(Node* const generator) { |
| return IsGeneratorStateNotExecuting(LoadGeneratorState(generator)); |
| } |
| |
| inline TNode<BoolT> IsGeneratorAwaiting(Node* const generator) { |
| TNode<Object> is_generator_awaiting = |
| LoadObjectField(generator, JSAsyncGeneratorObject::kIsAwaitingOffset); |
| return WordEqual(is_generator_awaiting, SmiConstant(1)); |
| } |
| |
| inline void SetGeneratorAwaiting(Node* const generator) { |
| CSA_ASSERT(this, Word32BinaryNot(IsGeneratorAwaiting(generator))); |
| StoreObjectFieldNoWriteBarrier( |
| generator, JSAsyncGeneratorObject::kIsAwaitingOffset, SmiConstant(1)); |
| CSA_ASSERT(this, IsGeneratorAwaiting(generator)); |
| } |
| |
| inline void SetGeneratorNotAwaiting(Node* const generator) { |
| CSA_ASSERT(this, IsGeneratorAwaiting(generator)); |
| StoreObjectFieldNoWriteBarrier( |
| generator, JSAsyncGeneratorObject::kIsAwaitingOffset, SmiConstant(0)); |
| CSA_ASSERT(this, Word32BinaryNot(IsGeneratorAwaiting(generator))); |
| } |
| |
| inline void CloseGenerator(Node* const generator) { |
| StoreObjectFieldNoWriteBarrier( |
| generator, JSGeneratorObject::kContinuationOffset, |
| SmiConstant(JSGeneratorObject::kGeneratorClosed)); |
| } |
| |
| inline Node* IsFastJSIterResult(Node* const value, Node* const context) { |
| CSA_ASSERT(this, TaggedIsNotSmi(value)); |
| Node* const native_context = LoadNativeContext(context); |
| return WordEqual( |
| LoadMap(value), |
| LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX)); |
| } |
| |
| inline Node* LoadFirstAsyncGeneratorRequestFromQueue(Node* const generator) { |
| return LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset); |
| } |
| |
| inline Node* LoadResumeTypeFromAsyncGeneratorRequest(Node* const request) { |
| return LoadObjectField(request, AsyncGeneratorRequest::kResumeModeOffset); |
| } |
| |
| inline Node* LoadPromiseFromAsyncGeneratorRequest(Node* const request) { |
| return LoadObjectField(request, AsyncGeneratorRequest::kPromiseOffset); |
| } |
| |
| inline Node* LoadValueFromAsyncGeneratorRequest(Node* const request) { |
| return LoadObjectField(request, AsyncGeneratorRequest::kValueOffset); |
| } |
| |
| inline TNode<BoolT> IsAbruptResumeType(SloppyTNode<Smi> const resume_type) { |
| return SmiNotEqual(resume_type, SmiConstant(JSGeneratorObject::kNext)); |
| } |
| |
| void AsyncGeneratorEnqueue(CodeStubArguments* args, Node* context, |
| Node* generator, Node* value, |
| JSAsyncGeneratorObject::ResumeMode resume_mode, |
| const char* method_name); |
| |
| Node* TakeFirstAsyncGeneratorRequestFromQueue(Node* generator); |
| Node* TakeFirstAsyncGeneratorRequestFromQueueIfPresent(Node* generator, |
| Label* if_not_present); |
| void AddAsyncGeneratorRequestToQueue(Node* generator, Node* request); |
| |
| Node* AllocateAsyncGeneratorRequest( |
| JSAsyncGeneratorObject::ResumeMode resume_mode, Node* resume_value, |
| Node* promise); |
| |
| // Shared implementation of the catchable and uncatchable variations of Await |
| // for AsyncGenerators. |
| template <typename Descriptor> |
| void AsyncGeneratorAwait(bool is_catchable); |
| void AsyncGeneratorAwaitResumeClosure( |
| Node* context, Node* value, |
| JSAsyncGeneratorObject::ResumeMode resume_mode); |
| }; |
| |
| // Shared implementation for the 3 Async Iterator protocol methods of Async |
| // Generators. |
| void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorEnqueue( |
| CodeStubArguments* args, 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); |
| |
| Branch(TaggedIsAsyncGenerator(generator), &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. |
| TNode<Smi> continuation = CAST(LoadGeneratorState(generator)); |
| |
| GotoIf(SmiEqual(continuation, |
| SmiConstant(JSAsyncGeneratorObject::kGeneratorExecuting)), |
| &done); |
| |
| CallBuiltin(Builtins::kAsyncGeneratorResumeNext, context, generator); |
| |
| Goto(&done); |
| BIND(&done); |
| args->PopAndReturn(promise); |
| } |
| |
| BIND(&if_receiverisincompatible); |
| { |
| Node* const error = |
| MakeTypeError(MessageTemplate::kIncompatibleMethodReceiver, context, |
| StringConstant(method_name), generator); |
| |
| CallBuiltin(Builtins::kRejectPromise, context, promise, error, |
| TrueConstant()); |
| args->PopAndReturn(promise); |
| } |
| } |
| |
| Node* AsyncGeneratorBuiltinsAssembler::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, RootIndex::kAsyncGeneratorRequestMap); |
| StoreObjectFieldNoWriteBarrier(request, AsyncGeneratorRequest::kNextOffset, |
| UndefinedConstant()); |
| StoreObjectFieldNoWriteBarrier(request, |
| AsyncGeneratorRequest::kResumeModeOffset, |
| SmiConstant(resume_mode)); |
| StoreObjectFieldNoWriteBarrier(request, AsyncGeneratorRequest::kValueOffset, |
| resume_value); |
| StoreObjectFieldNoWriteBarrier(request, AsyncGeneratorRequest::kPromiseOffset, |
| promise); |
| StoreObjectFieldRoot(request, AsyncGeneratorRequest::kNextOffset, |
| RootIndex::kUndefinedValue); |
| return request; |
| } |
| |
| void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorAwaitResumeClosure( |
| Node* context, Node* value, |
| JSAsyncGeneratorObject::ResumeMode resume_mode) { |
| Node* const generator = LoadContextElement(context, Context::EXTENSION_INDEX); |
| CSA_SLOW_ASSERT(this, TaggedIsAsyncGenerator(generator)); |
| |
| SetGeneratorNotAwaiting(generator); |
| |
| CSA_SLOW_ASSERT(this, IsGeneratorSuspended(generator)); |
| |
| // Remember the {resume_mode} for the {generator}. |
| StoreObjectFieldNoWriteBarrier(generator, |
| JSGeneratorObject::kResumeModeOffset, |
| SmiConstant(resume_mode)); |
| |
| CallStub(CodeFactory::ResumeGenerator(isolate()), context, value, generator); |
| |
| TailCallBuiltin(Builtins::kAsyncGeneratorResumeNext, context, generator); |
| } |
| |
| template <typename Descriptor> |
| void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorAwait(bool is_catchable) { |
| TNode<JSAsyncGeneratorObject> async_generator_object = |
| CAST(Parameter(Descriptor::kAsyncGeneratorObject)); |
| TNode<Object> value = CAST(Parameter(Descriptor::kValue)); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| |
| TNode<AsyncGeneratorRequest> request = |
| CAST(LoadFirstAsyncGeneratorRequestFromQueue(async_generator_object)); |
| TNode<JSPromise> outer_promise = LoadObjectField<JSPromise>( |
| 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; |
| |
| SetGeneratorAwaiting(async_generator_object); |
| Await(context, async_generator_object, value, outer_promise, resolve_index, |
| reject_index, is_catchable); |
| Return(UndefinedConstant()); |
| } |
| |
| void AsyncGeneratorBuiltinsAssembler::AddAsyncGeneratorRequestToQueue( |
| Node* generator, Node* request) { |
| VARIABLE(var_current, 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* AsyncGeneratorBuiltinsAssembler::TakeFirstAsyncGeneratorRequestFromQueue( |
| Node* generator) { |
| // Removes and returns the first AsyncGeneratorRequest from a |
| // JSAsyncGeneratorObject's queue. Asserts that the queue is not empty. |
| CSA_ASSERT(this, TaggedIsAsyncGenerator(generator)); |
| Node* request = |
| LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset); |
| CSA_ASSERT(this, IsNotUndefined(request)); |
| |
| Node* next = LoadObjectField(request, AsyncGeneratorRequest::kNextOffset); |
| |
| StoreObjectField(generator, JSAsyncGeneratorObject::kQueueOffset, next); |
| return request; |
| } |
| } // namespace |
| |
| // https://tc39.github.io/proposal-async-iteration/ |
| // Section #sec-asyncgenerator-prototype-next |
| TF_BUILTIN(AsyncGeneratorPrototypeNext, AsyncGeneratorBuiltinsAssembler) { |
| const int kValueArg = 0; |
| |
| Node* argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| CodeStubArguments args(this, argc); |
| |
| Node* generator = args.GetReceiver(); |
| Node* value = args.GetOptionalArgumentValue(kValueArg); |
| Node* context = Parameter(Descriptor::kContext); |
| |
| AsyncGeneratorEnqueue(&args, context, generator, value, |
| JSAsyncGeneratorObject::kNext, |
| "[AsyncGenerator].prototype.next"); |
| } |
| |
| // https://tc39.github.io/proposal-async-iteration/ |
| // Section #sec-asyncgenerator-prototype-return |
| TF_BUILTIN(AsyncGeneratorPrototypeReturn, AsyncGeneratorBuiltinsAssembler) { |
| const int kValueArg = 0; |
| |
| Node* argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| CodeStubArguments args(this, argc); |
| |
| Node* generator = args.GetReceiver(); |
| Node* value = args.GetOptionalArgumentValue(kValueArg); |
| Node* context = Parameter(Descriptor::kContext); |
| |
| AsyncGeneratorEnqueue(&args, context, generator, value, |
| JSAsyncGeneratorObject::kReturn, |
| "[AsyncGenerator].prototype.return"); |
| } |
| |
| // https://tc39.github.io/proposal-async-iteration/ |
| // Section #sec-asyncgenerator-prototype-throw |
| TF_BUILTIN(AsyncGeneratorPrototypeThrow, AsyncGeneratorBuiltinsAssembler) { |
| const int kValueArg = 0; |
| |
| Node* argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| CodeStubArguments args(this, argc); |
| |
| Node* generator = args.GetReceiver(); |
| Node* value = args.GetOptionalArgumentValue(kValueArg); |
| Node* context = Parameter(Descriptor::kContext); |
| |
| AsyncGeneratorEnqueue(&args, context, generator, value, |
| JSAsyncGeneratorObject::kThrow, |
| "[AsyncGenerator].prototype.throw"); |
| } |
| |
| TF_BUILTIN(AsyncGeneratorAwaitResolveClosure, AsyncGeneratorBuiltinsAssembler) { |
| Node* value = Parameter(Descriptor::kValue); |
| Node* context = Parameter(Descriptor::kContext); |
| AsyncGeneratorAwaitResumeClosure(context, value, |
| JSAsyncGeneratorObject::kNext); |
| } |
| |
| TF_BUILTIN(AsyncGeneratorAwaitRejectClosure, AsyncGeneratorBuiltinsAssembler) { |
| Node* value = Parameter(Descriptor::kValue); |
| Node* context = Parameter(Descriptor::kContext); |
| AsyncGeneratorAwaitResumeClosure(context, value, |
| JSAsyncGeneratorObject::kThrow); |
| } |
| |
| TF_BUILTIN(AsyncGeneratorAwaitUncaught, AsyncGeneratorBuiltinsAssembler) { |
| const bool kIsCatchable = false; |
| AsyncGeneratorAwait<Descriptor>(kIsCatchable); |
| } |
| |
| TF_BUILTIN(AsyncGeneratorAwaitCaught, AsyncGeneratorBuiltinsAssembler) { |
| const bool kIsCatchable = true; |
| AsyncGeneratorAwait<Descriptor>(kIsCatchable); |
| } |
| |
| TF_BUILTIN(AsyncGeneratorResumeNext, AsyncGeneratorBuiltinsAssembler) { |
| using Descriptor = AsyncGeneratorResumeNextDescriptor; |
| Node* const generator = Parameter(Descriptor::kGenerator); |
| Node* const context = Parameter(Descriptor::kContext); |
| |
| // The penultimate step of proposal-async-iteration/#sec-asyncgeneratorresolve |
| // and proposal-async-iteration/#sec-asyncgeneratorreject both recursively |
| // invoke AsyncGeneratorResumeNext() again. |
| // |
| // This implementation does not implement this recursively, but instead |
| // performs a loop in AsyncGeneratorResumeNext, which continues as long as |
| // there is an AsyncGeneratorRequest in the queue, and as long as the |
| // generator is not suspended due to an AwaitExpression. |
| VARIABLE(var_state, MachineRepresentation::kTaggedSigned, |
| LoadGeneratorState(generator)); |
| VARIABLE(var_next, MachineRepresentation::kTagged, |
| LoadFirstAsyncGeneratorRequestFromQueue(generator)); |
| Variable* loop_variables[] = {&var_state, &var_next}; |
| Label start(this, 2, loop_variables); |
| Goto(&start); |
| BIND(&start); |
| |
| CSA_ASSERT(this, IsGeneratorNotExecuting(generator)); |
| |
| // Stop resuming if suspended for Await. |
| ReturnIf(IsGeneratorAwaiting(generator), UndefinedConstant()); |
| |
| // Stop resuming if request queue is empty. |
| ReturnIf(IsUndefined(var_next.value()), UndefinedConstant()); |
| |
| Node* const next = var_next.value(); |
| TNode<Smi> const resume_type = |
| CAST(LoadResumeTypeFromAsyncGeneratorRequest(next)); |
| |
| Label if_abrupt(this), if_normal(this), resume_generator(this); |
| Branch(IsAbruptResumeType(resume_type), &if_abrupt, &if_normal); |
| BIND(&if_abrupt); |
| { |
| Label settle_promise(this), if_return(this), if_throw(this); |
| GotoIfNot(IsGeneratorStateSuspendedAtStart(var_state.value()), |
| &settle_promise); |
| CloseGenerator(generator); |
| var_state.Bind(SmiConstant(JSGeneratorObject::kGeneratorClosed)); |
| Goto(&settle_promise); |
| |
| BIND(&settle_promise); |
| Node* next_value = LoadValueFromAsyncGeneratorRequest(next); |
| Branch(SmiEqual(resume_type, SmiConstant(JSGeneratorObject::kReturn)), |
| &if_return, &if_throw); |
| |
| BIND(&if_return); |
| // For "return" completions, await the sent value. If the Await succeeds, |
| // and the generator is not closed, resume the generator with a "return" |
| // completion to allow `finally` blocks to be evaluated. Otherwise, perform |
| // AsyncGeneratorResolve(awaitedValue, true). If the await fails and the |
| // generator is not closed, resume the generator with a "throw" completion. |
| // If the generator was closed, perform AsyncGeneratorReject(thrownValue). |
| // In all cases, the last step is to call AsyncGeneratorResumeNext. |
| Node* is_caught = CallRuntime(Runtime::kAsyncGeneratorHasCatchHandlerForPC, |
| context, generator); |
| TailCallBuiltin(Builtins::kAsyncGeneratorReturn, context, generator, |
| next_value, is_caught); |
| |
| BIND(&if_throw); |
| GotoIfNot(IsGeneratorStateClosed(var_state.value()), &resume_generator); |
| CallBuiltin(Builtins::kAsyncGeneratorReject, context, generator, |
| next_value); |
| var_next.Bind(LoadFirstAsyncGeneratorRequestFromQueue(generator)); |
| Goto(&start); |
| } |
| |
| BIND(&if_normal); |
| { |
| GotoIfNot(IsGeneratorStateClosed(var_state.value()), &resume_generator); |
| CallBuiltin(Builtins::kAsyncGeneratorResolve, context, generator, |
| UndefinedConstant(), TrueConstant()); |
| var_state.Bind(LoadGeneratorState(generator)); |
| var_next.Bind(LoadFirstAsyncGeneratorRequestFromQueue(generator)); |
| Goto(&start); |
| } |
| |
| BIND(&resume_generator); |
| { |
| // Remember the {resume_type} for the {generator}. |
| StoreObjectFieldNoWriteBarrier( |
| generator, JSGeneratorObject::kResumeModeOffset, resume_type); |
| CallStub(CodeFactory::ResumeGenerator(isolate()), context, |
| LoadValueFromAsyncGeneratorRequest(next), generator); |
| var_state.Bind(LoadGeneratorState(generator)); |
| var_next.Bind(LoadFirstAsyncGeneratorRequestFromQueue(generator)); |
| Goto(&start); |
| } |
| } |
| |
| TF_BUILTIN(AsyncGeneratorResolve, AsyncGeneratorBuiltinsAssembler) { |
| Node* const generator = Parameter(Descriptor::kGenerator); |
| Node* const value = Parameter(Descriptor::kValue); |
| Node* const done = Parameter(Descriptor::kDone); |
| Node* const context = Parameter(Descriptor::kContext); |
| |
| CSA_SLOW_ASSERT(this, TaggedIsAsyncGenerator(generator)); |
| CSA_ASSERT(this, Word32BinaryNot(IsGeneratorAwaiting(generator))); |
| |
| // This operation should be called only when the `value` parameter has been |
| // Await-ed. Typically, this means `value` is not a JSPromise value. However, |
| // it may be a JSPromise value whose "then" method has been overridden to a |
| // non-callable value. This can't be checked with assertions due to being |
| // observable, but keep it in mind. |
| |
| Node* const next = TakeFirstAsyncGeneratorRequestFromQueue(generator); |
| Node* const promise = LoadPromiseFromAsyncGeneratorRequest(next); |
| |
| // Let iteratorResult be CreateIterResultObject(value, done). |
| Node* const iter_result = Allocate(JSIteratorResult::kSize); |
| { |
| Node* map = LoadContextElement(LoadNativeContext(context), |
| Context::ITERATOR_RESULT_MAP_INDEX); |
| StoreMapNoWriteBarrier(iter_result, map); |
| StoreObjectFieldRoot(iter_result, JSIteratorResult::kPropertiesOrHashOffset, |
| RootIndex::kEmptyFixedArray); |
| StoreObjectFieldRoot(iter_result, JSIteratorResult::kElementsOffset, |
| RootIndex::kEmptyFixedArray); |
| StoreObjectFieldNoWriteBarrier(iter_result, JSIteratorResult::kValueOffset, |
| value); |
| StoreObjectFieldNoWriteBarrier(iter_result, JSIteratorResult::kDoneOffset, |
| done); |
| } |
| |
| // We know that {iter_result} itself doesn't have any "then" property (a |
| // freshly allocated IterResultObject only has "value" and "done" properties) |
| // and we also know that the [[Prototype]] of {iter_result} is the intrinsic |
| // %ObjectPrototype%. So we can skip the [[Resolve]] logic here completely |
| // and directly call into the FulfillPromise operation if we can prove |
| // that the %ObjectPrototype% also doesn't have any "then" property. This |
| // is guarded by the Promise#then() protector. |
| // If the PromiseHooks are enabled, we cannot take the shortcut here, since |
| // the "promiseResolve" hook would not be fired otherwise. |
| Label if_fast(this), if_slow(this, Label::kDeferred), return_promise(this); |
| GotoIfForceSlowPath(&if_slow); |
| GotoIf(IsPromiseHookEnabled(), &if_slow); |
| Branch(IsPromiseThenProtectorCellInvalid(), &if_slow, &if_fast); |
| |
| BIND(&if_fast); |
| { |
| // Skip the "then" on {iter_result} and directly fulfill the {promise} |
| // with the {iter_result}. |
| CallBuiltin(Builtins::kFulfillPromise, context, promise, iter_result); |
| Goto(&return_promise); |
| } |
| |
| BIND(&if_slow); |
| { |
| // Perform Call(promiseCapability.[[Resolve]], undefined, «iteratorResult»). |
| CallBuiltin(Builtins::kResolvePromise, context, promise, iter_result); |
| Goto(&return_promise); |
| } |
| |
| // Per spec, AsyncGeneratorResolve() returns undefined. However, for the |
| // benefit of %TraceExit(), return the Promise. |
| BIND(&return_promise); |
| Return(promise); |
| } |
| |
| TF_BUILTIN(AsyncGeneratorReject, AsyncGeneratorBuiltinsAssembler) { |
| using Descriptor = AsyncGeneratorRejectDescriptor; |
| Node* const generator = Parameter(Descriptor::kGenerator); |
| Node* const value = Parameter(Descriptor::kValue); |
| Node* const context = Parameter(Descriptor::kContext); |
| |
| Node* const next = TakeFirstAsyncGeneratorRequestFromQueue(generator); |
| Node* const promise = LoadPromiseFromAsyncGeneratorRequest(next); |
| |
| Return(CallBuiltin(Builtins::kRejectPromise, context, promise, value, |
| TrueConstant())); |
| } |
| |
| TF_BUILTIN(AsyncGeneratorYield, AsyncGeneratorBuiltinsAssembler) { |
| Node* const generator = Parameter(Descriptor::kGenerator); |
| Node* const value = Parameter(Descriptor::kValue); |
| Node* const is_caught = Parameter(Descriptor::kIsCaught); |
| Node* const context = Parameter(Descriptor::kContext); |
| |
| Node* const request = LoadFirstAsyncGeneratorRequestFromQueue(generator); |
| Node* const outer_promise = LoadPromiseFromAsyncGeneratorRequest(request); |
| |
| const int on_resolve = Context::ASYNC_GENERATOR_YIELD_RESOLVE_SHARED_FUN; |
| const int on_reject = Context::ASYNC_GENERATOR_AWAIT_REJECT_SHARED_FUN; |
| |
| SetGeneratorAwaiting(generator); |
| Await(context, generator, value, outer_promise, on_resolve, on_reject, |
| is_caught); |
| Return(UndefinedConstant()); |
| } |
| |
| TF_BUILTIN(AsyncGeneratorYieldResolveClosure, AsyncGeneratorBuiltinsAssembler) { |
| Node* const context = Parameter(Descriptor::kContext); |
| Node* const value = Parameter(Descriptor::kValue); |
| Node* const generator = LoadContextElement(context, Context::EXTENSION_INDEX); |
| |
| SetGeneratorNotAwaiting(generator); |
| |
| // Per proposal-async-iteration/#sec-asyncgeneratoryield step 9 |
| // Return ! AsyncGeneratorResolve(_F_.[[Generator]], _value_, *false*). |
| CallBuiltin(Builtins::kAsyncGeneratorResolve, context, generator, value, |
| FalseConstant()); |
| |
| TailCallBuiltin(Builtins::kAsyncGeneratorResumeNext, context, generator); |
| } |
| |
| TF_BUILTIN(AsyncGeneratorReturn, AsyncGeneratorBuiltinsAssembler) { |
| // AsyncGeneratorReturn is called when resuming requests with "return" resume |
| // modes. It is similar to AsyncGeneratorAwait(), but selects different |
| // resolve/reject closures depending on whether or not the generator is marked |
| // as closed. |
| // |
| // In particular, non-closed generators will resume the generator with either |
| // "return" or "throw" resume modes, allowing finally blocks or catch blocks |
| // to be evaluated, as if the `await` were performed within the body of the |
| // generator. (per proposal-async-iteration/#sec-asyncgeneratoryield step 8.b) |
| // |
| // Closed generators do not resume the generator in the resolve/reject |
| // closures, but instead simply perform AsyncGeneratorResolve or |
| // AsyncGeneratorReject with the awaited value |
| // (per proposal-async-iteration/#sec-asyncgeneratorresumenext step 10.b.i) |
| // |
| // In all cases, the final step is to jump back to AsyncGeneratorResumeNext. |
| Node* const generator = Parameter(Descriptor::kGenerator); |
| Node* const value = Parameter(Descriptor::kValue); |
| Node* const is_caught = Parameter(Descriptor::kIsCaught); |
| Node* const req = LoadFirstAsyncGeneratorRequestFromQueue(generator); |
| CSA_ASSERT(this, IsNotUndefined(req)); |
| |
| Label perform_await(this); |
| VARIABLE(var_on_resolve, MachineType::PointerRepresentation(), |
| IntPtrConstant( |
| Context::ASYNC_GENERATOR_RETURN_CLOSED_RESOLVE_SHARED_FUN)); |
| VARIABLE( |
| var_on_reject, MachineType::PointerRepresentation(), |
| IntPtrConstant(Context::ASYNC_GENERATOR_RETURN_CLOSED_REJECT_SHARED_FUN)); |
| |
| Node* const state = LoadGeneratorState(generator); |
| GotoIf(IsGeneratorStateClosed(state), &perform_await); |
| var_on_resolve.Bind( |
| IntPtrConstant(Context::ASYNC_GENERATOR_RETURN_RESOLVE_SHARED_FUN)); |
| var_on_reject.Bind( |
| IntPtrConstant(Context::ASYNC_GENERATOR_AWAIT_REJECT_SHARED_FUN)); |
| Goto(&perform_await); |
| |
| BIND(&perform_await); |
| |
| SetGeneratorAwaiting(generator); |
| Node* const context = Parameter(Descriptor::kContext); |
| Node* const outer_promise = LoadPromiseFromAsyncGeneratorRequest(req); |
| Await(context, generator, value, outer_promise, var_on_resolve.value(), |
| var_on_reject.value(), is_caught); |
| |
| Return(UndefinedConstant()); |
| } |
| |
| // On-resolve closure for Await in AsyncGeneratorReturn |
| // Resume the generator with "return" resume_mode, and finally perform |
| // AsyncGeneratorResumeNext. Per |
| // proposal-async-iteration/#sec-asyncgeneratoryield step 8.e |
| TF_BUILTIN(AsyncGeneratorReturnResolveClosure, |
| AsyncGeneratorBuiltinsAssembler) { |
| Node* const context = Parameter(Descriptor::kContext); |
| Node* const value = Parameter(Descriptor::kValue); |
| AsyncGeneratorAwaitResumeClosure(context, value, JSGeneratorObject::kReturn); |
| } |
| |
| // On-resolve closure for Await in AsyncGeneratorReturn |
| // Perform AsyncGeneratorResolve({awaited_value}, true) and finally perform |
| // AsyncGeneratorResumeNext. |
| TF_BUILTIN(AsyncGeneratorReturnClosedResolveClosure, |
| AsyncGeneratorBuiltinsAssembler) { |
| Node* const context = Parameter(Descriptor::kContext); |
| Node* const value = Parameter(Descriptor::kValue); |
| Node* const generator = LoadContextElement(context, Context::EXTENSION_INDEX); |
| |
| SetGeneratorNotAwaiting(generator); |
| |
| // https://tc39.github.io/proposal-async-iteration/ |
| // #async-generator-resume-next-return-processor-fulfilled step 2: |
| // Return ! AsyncGeneratorResolve(_F_.[[Generator]], _value_, *true*). |
| CallBuiltin(Builtins::kAsyncGeneratorResolve, context, generator, value, |
| TrueConstant()); |
| |
| TailCallBuiltin(Builtins::kAsyncGeneratorResumeNext, context, generator); |
| } |
| |
| TF_BUILTIN(AsyncGeneratorReturnClosedRejectClosure, |
| AsyncGeneratorBuiltinsAssembler) { |
| Node* const context = Parameter(Descriptor::kContext); |
| Node* const value = Parameter(Descriptor::kValue); |
| Node* const generator = LoadContextElement(context, Context::EXTENSION_INDEX); |
| |
| SetGeneratorNotAwaiting(generator); |
| |
| // https://tc39.github.io/proposal-async-iteration/ |
| // #async-generator-resume-next-return-processor-rejected step 2: |
| // Return ! AsyncGeneratorReject(_F_.[[Generator]], _reason_). |
| CallBuiltin(Builtins::kAsyncGeneratorReject, context, generator, value); |
| |
| TailCallBuiltin(Builtins::kAsyncGeneratorResumeNext, context, generator); |
| } |
| |
| } // namespace internal |
| } // namespace v8 |