// 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/code-factory.h"
#include "src/code-stub-assembler.h"
#include "src/frames-inl.h"
#include "src/objects/js-generator.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) {
  Node* generator = Parameter(Descriptor::kGenerator);
  Node* value = Parameter(Descriptor::kAwaited);
  Node* context = Parameter(Descriptor::kContext);

  CSA_SLOW_ASSERT(this, TaggedIsAsyncGenerator(generator));

  Node* const request = LoadFirstAsyncGeneratorRequestFromQueue(generator);
  CSA_ASSERT(this, IsNotUndefined(request));

  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;

  SetGeneratorAwaiting(generator);
  Await(context, generator, 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) {
  typedef AsyncGeneratorResumeNextDescriptor Descriptor;
  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)));

  // If this assertion fails, the `value` component was not Awaited as it should
  // have been, per https://github.com/tc39/proposal-async-iteration/pull/102/.
  CSA_SLOW_ASSERT(this, TaggedDoesntHaveInstanceType(value, JS_PROMISE_TYPE));

  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) {
  typedef AsyncGeneratorRejectDescriptor Descriptor;
  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
