// 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-promise.h"
#include "src/builtins/builtins-constructor.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/objects-inl.h"

namespace v8 {
namespace internal {

typedef compiler::Node Node;
typedef CodeStubAssembler::ParameterMode ParameterMode;
typedef compiler::CodeAssemblerState CodeAssemblerState;

Node* PromiseBuiltinsAssembler::AllocateJSPromise(Node* context) {
  Node* const native_context = LoadNativeContext(context);
  Node* const promise_fun =
      LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
  Node* const initial_map =
      LoadObjectField(promise_fun, JSFunction::kPrototypeOrInitialMapOffset);
  Node* const instance = AllocateJSObjectFromMap(initial_map);
  return instance;
}

void PromiseBuiltinsAssembler::PromiseInit(Node* promise) {
  StoreObjectField(promise, JSPromise::kStatusOffset,
                   SmiConstant(v8::Promise::kPending));
  StoreObjectField(promise, JSPromise::kFlagsOffset, SmiConstant(0));
}

Node* PromiseBuiltinsAssembler::AllocateAndInitJSPromise(Node* context) {
  return AllocateAndInitJSPromise(context, UndefinedConstant());
}

Node* PromiseBuiltinsAssembler::AllocateAndInitJSPromise(Node* context,
                                                         Node* parent) {
  Node* const instance = AllocateJSPromise(context);
  PromiseInit(instance);

  Label out(this);
  GotoIfNot(IsPromiseHookEnabledOrDebugIsActive(), &out);
  CallRuntime(Runtime::kPromiseHookInit, context, instance, parent);
  Goto(&out);

  Bind(&out);
  return instance;
}

Node* PromiseBuiltinsAssembler::AllocateAndSetJSPromise(Node* context,
                                                        Node* status,
                                                        Node* result) {
  CSA_ASSERT(this, TaggedIsSmi(status));

  Node* const instance = AllocateJSPromise(context);

  StoreObjectFieldNoWriteBarrier(instance, JSPromise::kStatusOffset, status);
  StoreObjectFieldNoWriteBarrier(instance, JSPromise::kResultOffset, result);
  StoreObjectFieldNoWriteBarrier(instance, JSPromise::kFlagsOffset,
                                 SmiConstant(0));

  Label out(this);
  GotoIfNot(IsPromiseHookEnabledOrDebugIsActive(), &out);
  CallRuntime(Runtime::kPromiseHookInit, context, instance,
              UndefinedConstant());
  Goto(&out);

  Bind(&out);
  return instance;
}

std::pair<Node*, Node*>
PromiseBuiltinsAssembler::CreatePromiseResolvingFunctions(
    Node* promise, Node* debug_event, Node* native_context) {
  Node* const promise_context = CreatePromiseResolvingFunctionsContext(
      promise, debug_event, native_context);
  Node* const map = LoadContextElement(
      native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
  Node* const resolve_info =
      LoadContextElement(native_context, Context::PROMISE_RESOLVE_SHARED_FUN);
  Node* const resolve =
      AllocateFunctionWithMapAndContext(map, resolve_info, promise_context);
  Node* const reject_info =
      LoadContextElement(native_context, Context::PROMISE_REJECT_SHARED_FUN);
  Node* const reject =
      AllocateFunctionWithMapAndContext(map, reject_info, promise_context);
  return std::make_pair(resolve, reject);
}

Node* PromiseBuiltinsAssembler::NewPromiseCapability(Node* context,
                                                     Node* constructor,
                                                     Node* debug_event) {
  if (debug_event == nullptr) {
    debug_event = TrueConstant();
  }

  Node* native_context = LoadNativeContext(context);

  Node* map = LoadRoot(Heap::kJSPromiseCapabilityMapRootIndex);
  Node* capability = AllocateJSObjectFromMap(map);

  StoreObjectFieldNoWriteBarrier(
      capability, JSPromiseCapability::kPromiseOffset, UndefinedConstant());
  StoreObjectFieldNoWriteBarrier(
      capability, JSPromiseCapability::kResolveOffset, UndefinedConstant());
  StoreObjectFieldNoWriteBarrier(capability, JSPromiseCapability::kRejectOffset,
                                 UndefinedConstant());

  Variable var_result(this, MachineRepresentation::kTagged);
  var_result.Bind(capability);

  Label if_builtin_promise(this), if_custom_promise(this, Label::kDeferred),
      out(this);
  Branch(WordEqual(constructor,
                   LoadContextElement(native_context,
                                      Context::PROMISE_FUNCTION_INDEX)),
         &if_builtin_promise, &if_custom_promise);

  Bind(&if_builtin_promise);
  {
    Node* promise = AllocateJSPromise(context);
    PromiseInit(promise);
    StoreObjectFieldNoWriteBarrier(
        capability, JSPromiseCapability::kPromiseOffset, promise);

    Node* resolve = nullptr;
    Node* reject = nullptr;

    std::tie(resolve, reject) =
        CreatePromiseResolvingFunctions(promise, debug_event, native_context);
    StoreObjectField(capability, JSPromiseCapability::kResolveOffset, resolve);
    StoreObjectField(capability, JSPromiseCapability::kRejectOffset, reject);

    GotoIfNot(IsPromiseHookEnabledOrDebugIsActive(), &out);
    CallRuntime(Runtime::kPromiseHookInit, context, promise,
                UndefinedConstant());
    Goto(&out);
  }

  Bind(&if_custom_promise);
  {
    Label if_notcallable(this, Label::kDeferred);
    Node* executor_context =
        CreatePromiseGetCapabilitiesExecutorContext(capability, native_context);
    Node* executor_info = LoadContextElement(
        native_context, Context::PROMISE_GET_CAPABILITIES_EXECUTOR_SHARED_FUN);
    Node* function_map = LoadContextElement(
        native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
    Node* executor = AllocateFunctionWithMapAndContext(
        function_map, executor_info, executor_context);

    Node* promise = ConstructJS(CodeFactory::Construct(isolate()), context,
                                constructor, executor);

    Node* resolve =
        LoadObjectField(capability, JSPromiseCapability::kResolveOffset);
    GotoIf(TaggedIsSmi(resolve), &if_notcallable);
    GotoIfNot(IsCallableMap(LoadMap(resolve)), &if_notcallable);

    Node* reject =
        LoadObjectField(capability, JSPromiseCapability::kRejectOffset);
    GotoIf(TaggedIsSmi(reject), &if_notcallable);
    GotoIfNot(IsCallableMap(LoadMap(reject)), &if_notcallable);

    StoreObjectField(capability, JSPromiseCapability::kPromiseOffset, promise);

    Goto(&out);

    Bind(&if_notcallable);
    Node* message = SmiConstant(MessageTemplate::kPromiseNonCallable);
    StoreObjectField(capability, JSPromiseCapability::kPromiseOffset,
                     UndefinedConstant());
    StoreObjectField(capability, JSPromiseCapability::kResolveOffset,
                     UndefinedConstant());
    StoreObjectField(capability, JSPromiseCapability::kRejectOffset,
                     UndefinedConstant());
    CallRuntime(Runtime::kThrowTypeError, context, message);
    Unreachable();
  }

  Bind(&out);
  return var_result.value();
}

Node* PromiseBuiltinsAssembler::CreatePromiseContext(Node* native_context,
                                                     int slots) {
  DCHECK_GE(slots, Context::MIN_CONTEXT_SLOTS);

  Node* const context = Allocate(FixedArray::SizeFor(slots));
  StoreMapNoWriteBarrier(context, Heap::kFunctionContextMapRootIndex);
  StoreObjectFieldNoWriteBarrier(context, FixedArray::kLengthOffset,
                                 SmiConstant(slots));

  Node* const empty_fn =
      LoadContextElement(native_context, Context::CLOSURE_INDEX);
  StoreContextElementNoWriteBarrier(context, Context::CLOSURE_INDEX, empty_fn);
  StoreContextElementNoWriteBarrier(context, Context::PREVIOUS_INDEX,
                                    UndefinedConstant());
  StoreContextElementNoWriteBarrier(context, Context::EXTENSION_INDEX,
                                    TheHoleConstant());
  StoreContextElementNoWriteBarrier(context, Context::NATIVE_CONTEXT_INDEX,
                                    native_context);
  return context;
}

Node* PromiseBuiltinsAssembler::CreatePromiseResolvingFunctionsContext(
    Node* promise, Node* debug_event, Node* native_context) {
  Node* const context =
      CreatePromiseContext(native_context, kPromiseContextLength);
  StoreContextElementNoWriteBarrier(context, kAlreadyVisitedSlot,
                                    SmiConstant(0));
  StoreContextElementNoWriteBarrier(context, kPromiseSlot, promise);
  StoreContextElementNoWriteBarrier(context, kDebugEventSlot, debug_event);
  return context;
}

Node* PromiseBuiltinsAssembler::CreatePromiseGetCapabilitiesExecutorContext(
    Node* promise_capability, Node* native_context) {
  int kContextLength = kCapabilitiesContextLength;
  Node* context = CreatePromiseContext(native_context, kContextLength);
  StoreContextElementNoWriteBarrier(context, kCapabilitySlot,
                                    promise_capability);
  return context;
}

Node* PromiseBuiltinsAssembler::ThrowIfNotJSReceiver(
    Node* context, Node* value, MessageTemplate::Template msg_template,
    const char* method_name) {
  Label out(this), throw_exception(this, Label::kDeferred);
  Variable var_value_map(this, MachineRepresentation::kTagged);

  GotoIf(TaggedIsSmi(value), &throw_exception);

  // Load the instance type of the {value}.
  var_value_map.Bind(LoadMap(value));
  Node* const value_instance_type = LoadMapInstanceType(var_value_map.value());

  Branch(IsJSReceiverInstanceType(value_instance_type), &out, &throw_exception);

  // The {value} is not a compatible receiver for this method.
  Bind(&throw_exception);
  {
    Node* const method =
        method_name == nullptr
            ? UndefinedConstant()
            : HeapConstant(
                  isolate()->factory()->NewStringFromAsciiChecked(method_name));
    Node* const message_id = SmiConstant(msg_template);
    CallRuntime(Runtime::kThrowTypeError, context, message_id, method);
    Unreachable();
  }

  Bind(&out);
  return var_value_map.value();
}

Node* PromiseBuiltinsAssembler::PromiseHasHandler(Node* promise) {
  Node* const flags = LoadObjectField(promise, JSPromise::kFlagsOffset);
  return IsSetWord(SmiUntag(flags), 1 << JSPromise::kHasHandlerBit);
}

void PromiseBuiltinsAssembler::PromiseSetHasHandler(Node* promise) {
  Node* const flags = LoadObjectField(promise, JSPromise::kFlagsOffset);
  Node* const new_flags =
      SmiOr(flags, SmiConstant(1 << JSPromise::kHasHandlerBit));
  StoreObjectFieldNoWriteBarrier(promise, JSPromise::kFlagsOffset, new_flags);
}

void PromiseBuiltinsAssembler::PromiseSetHandledHint(Node* promise) {
  Node* const flags = LoadObjectField(promise, JSPromise::kFlagsOffset);
  Node* const new_flags =
      SmiOr(flags, SmiConstant(1 << JSPromise::kHandledHintBit));
  StoreObjectFieldNoWriteBarrier(promise, JSPromise::kFlagsOffset, new_flags);
}

Node* PromiseBuiltinsAssembler::SpeciesConstructor(Node* context, Node* object,
                                                   Node* default_constructor) {
  Isolate* isolate = this->isolate();
  Variable var_result(this, MachineRepresentation::kTagged);
  var_result.Bind(default_constructor);

  // 2. Let C be ? Get(O, "constructor").
  Node* const constructor_str =
      HeapConstant(isolate->factory()->constructor_string());
  Callable getproperty_callable = CodeFactory::GetProperty(isolate);
  Node* const constructor =
      CallStub(getproperty_callable, context, object, constructor_str);

  // 3. If C is undefined, return defaultConstructor.
  Label out(this);
  GotoIf(IsUndefined(constructor), &out);

  // 4. If Type(C) is not Object, throw a TypeError exception.
  ThrowIfNotJSReceiver(context, constructor,
                       MessageTemplate::kConstructorNotReceiver);

  // 5. Let S be ? Get(C, @@species).
  Node* const species_symbol =
      HeapConstant(isolate->factory()->species_symbol());
  Node* const species =
      CallStub(getproperty_callable, context, constructor, species_symbol);

  // 6. If S is either undefined or null, return defaultConstructor.
  GotoIf(IsUndefined(species), &out);
  GotoIf(WordEqual(species, NullConstant()), &out);

  // 7. If IsConstructor(S) is true, return S.
  Label throw_error(this);
  Node* species_bitfield = LoadMapBitField(LoadMap(species));
  GotoIfNot(Word32Equal(Word32And(species_bitfield,
                                  Int32Constant((1 << Map::kIsConstructor))),
                        Int32Constant(1 << Map::kIsConstructor)),
            &throw_error);
  var_result.Bind(species);
  Goto(&out);

  // 8. Throw a TypeError exception.
  Bind(&throw_error);
  {
    Node* const message_id =
        SmiConstant(MessageTemplate::kSpeciesNotConstructor);
    CallRuntime(Runtime::kThrowTypeError, context, message_id);
    Unreachable();
  }

  Bind(&out);
  return var_result.value();
}

void PromiseBuiltinsAssembler::AppendPromiseCallback(int offset, Node* promise,
                                                     Node* value) {
  Node* elements = LoadObjectField(promise, offset);
  Node* length = LoadFixedArrayBaseLength(elements);
  CodeStubAssembler::ParameterMode mode = OptimalParameterMode();
  length = TaggedToParameter(length, mode);

  Node* delta = IntPtrOrSmiConstant(1, mode);
  Node* new_capacity = IntPtrOrSmiAdd(length, delta, mode);

  const ElementsKind kind = FAST_ELEMENTS;
  const WriteBarrierMode barrier_mode = UPDATE_WRITE_BARRIER;
  const CodeStubAssembler::AllocationFlags flags =
      CodeStubAssembler::kAllowLargeObjectAllocation;
  int additional_offset = 0;

  Node* new_elements = AllocateFixedArray(kind, new_capacity, mode, flags);

  CopyFixedArrayElements(kind, elements, new_elements, length, barrier_mode,
                         mode);
  StoreFixedArrayElement(new_elements, length, value, barrier_mode,
                         additional_offset, mode);

  StoreObjectField(promise, offset, new_elements);
}

Node* PromiseBuiltinsAssembler::InternalPromiseThen(Node* context,
                                                    Node* promise,
                                                    Node* on_resolve,
                                                    Node* on_reject) {
  Isolate* isolate = this->isolate();

  // 2. If IsPromise(promise) is false, throw a TypeError exception.
  ThrowIfNotInstanceType(context, promise, JS_PROMISE_TYPE,
                         "Promise.prototype.then");

  Node* const native_context = LoadNativeContext(context);
  Node* const promise_fun =
      LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);

  // 3. Let C be ? SpeciesConstructor(promise, %Promise%).
  Node* constructor = SpeciesConstructor(context, promise, promise_fun);

  // 4. Let resultCapability be ? NewPromiseCapability(C).
  Callable call_callable = CodeFactory::Call(isolate);
  Label fast_promise_capability(this), promise_capability(this),
      perform_promise_then(this);
  Variable var_deferred_promise(this, MachineRepresentation::kTagged),
      var_deferred_on_resolve(this, MachineRepresentation::kTagged),
      var_deferred_on_reject(this, MachineRepresentation::kTagged);

  Branch(WordEqual(promise_fun, constructor), &fast_promise_capability,
         &promise_capability);

  Bind(&fast_promise_capability);
  {
    Node* const deferred_promise = AllocateAndInitJSPromise(context, promise);
    var_deferred_promise.Bind(deferred_promise);
    var_deferred_on_resolve.Bind(UndefinedConstant());
    var_deferred_on_reject.Bind(UndefinedConstant());
    Goto(&perform_promise_then);
  }

  Bind(&promise_capability);
  {
    Node* const capability = NewPromiseCapability(context, constructor);
    var_deferred_promise.Bind(
        LoadObjectField(capability, JSPromiseCapability::kPromiseOffset));
    var_deferred_on_resolve.Bind(
        LoadObjectField(capability, JSPromiseCapability::kResolveOffset));
    var_deferred_on_reject.Bind(
        LoadObjectField(capability, JSPromiseCapability::kRejectOffset));
    Goto(&perform_promise_then);
  }

  // 5. Return PerformPromiseThen(promise, onFulfilled, onRejected,
  //    resultCapability).
  Bind(&perform_promise_then);
  Node* const result = InternalPerformPromiseThen(
      context, promise, on_resolve, on_reject, var_deferred_promise.value(),
      var_deferred_on_resolve.value(), var_deferred_on_reject.value());
  return result;
}

Node* PromiseBuiltinsAssembler::InternalPerformPromiseThen(
    Node* context, Node* promise, Node* on_resolve, Node* on_reject,
    Node* deferred_promise, Node* deferred_on_resolve,
    Node* deferred_on_reject) {

  Variable var_on_resolve(this, MachineRepresentation::kTagged),
      var_on_reject(this, MachineRepresentation::kTagged);

  var_on_resolve.Bind(on_resolve);
  var_on_reject.Bind(on_reject);

  Label out(this), if_onresolvenotcallable(this), onrejectcheck(this),
      append_callbacks(this);
  GotoIf(TaggedIsSmi(on_resolve), &if_onresolvenotcallable);

  Isolate* isolate = this->isolate();
  Node* const on_resolve_map = LoadMap(on_resolve);
  Branch(IsCallableMap(on_resolve_map), &onrejectcheck,
         &if_onresolvenotcallable);

  Bind(&if_onresolvenotcallable);
  {
    Node* const default_resolve_handler_symbol = HeapConstant(
        isolate->factory()->promise_default_resolve_handler_symbol());
    var_on_resolve.Bind(default_resolve_handler_symbol);
    Goto(&onrejectcheck);
  }

  Bind(&onrejectcheck);
  {
    Label if_onrejectnotcallable(this);
    GotoIf(TaggedIsSmi(on_reject), &if_onrejectnotcallable);

    Node* const on_reject_map = LoadMap(on_reject);
    Branch(IsCallableMap(on_reject_map), &append_callbacks,
           &if_onrejectnotcallable);

    Bind(&if_onrejectnotcallable);
    {
      Node* const default_reject_handler_symbol = HeapConstant(
          isolate->factory()->promise_default_reject_handler_symbol());
      var_on_reject.Bind(default_reject_handler_symbol);
      Goto(&append_callbacks);
    }
  }

  Bind(&append_callbacks);
  {
    Label fulfilled_check(this);
    Node* const status = LoadObjectField(promise, JSPromise::kStatusOffset);
    GotoIfNot(SmiEqual(status, SmiConstant(v8::Promise::kPending)),
              &fulfilled_check);

    Node* const existing_deferred_promise =
        LoadObjectField(promise, JSPromise::kDeferredPromiseOffset);

    Label if_noexistingcallbacks(this), if_existingcallbacks(this);
    Branch(IsUndefined(existing_deferred_promise), &if_noexistingcallbacks,
           &if_existingcallbacks);

    Bind(&if_noexistingcallbacks);
    {
      // Store callbacks directly in the slots.
      StoreObjectField(promise, JSPromise::kDeferredPromiseOffset,
                       deferred_promise);
      StoreObjectField(promise, JSPromise::kDeferredOnResolveOffset,
                       deferred_on_resolve);
      StoreObjectField(promise, JSPromise::kDeferredOnRejectOffset,
                       deferred_on_reject);
      StoreObjectField(promise, JSPromise::kFulfillReactionsOffset,
                       var_on_resolve.value());
      StoreObjectField(promise, JSPromise::kRejectReactionsOffset,
                       var_on_reject.value());
      Goto(&out);
    }

    Bind(&if_existingcallbacks);
    {
      Label if_singlecallback(this), if_multiplecallbacks(this);
      BranchIfJSObject(existing_deferred_promise, &if_singlecallback,
                       &if_multiplecallbacks);

      Bind(&if_singlecallback);
      {
        // Create new FixedArrays to store callbacks, and migrate
        // existing callbacks.
        Node* const deferred_promise_arr =
            AllocateFixedArray(FAST_ELEMENTS, IntPtrConstant(2));
        StoreFixedArrayElement(deferred_promise_arr, 0,
                               existing_deferred_promise);
        StoreFixedArrayElement(deferred_promise_arr, 1, deferred_promise);

        Node* const deferred_on_resolve_arr =
            AllocateFixedArray(FAST_ELEMENTS, IntPtrConstant(2));
        StoreFixedArrayElement(
            deferred_on_resolve_arr, 0,
            LoadObjectField(promise, JSPromise::kDeferredOnResolveOffset));
        StoreFixedArrayElement(deferred_on_resolve_arr, 1, deferred_on_resolve);

        Node* const deferred_on_reject_arr =
            AllocateFixedArray(FAST_ELEMENTS, IntPtrConstant(2));
        StoreFixedArrayElement(
            deferred_on_reject_arr, 0,
            LoadObjectField(promise, JSPromise::kDeferredOnRejectOffset));
        StoreFixedArrayElement(deferred_on_reject_arr, 1, deferred_on_reject);

        Node* const fulfill_reactions =
            AllocateFixedArray(FAST_ELEMENTS, IntPtrConstant(2));
        StoreFixedArrayElement(
            fulfill_reactions, 0,
            LoadObjectField(promise, JSPromise::kFulfillReactionsOffset));
        StoreFixedArrayElement(fulfill_reactions, 1, var_on_resolve.value());

        Node* const reject_reactions =
            AllocateFixedArray(FAST_ELEMENTS, IntPtrConstant(2));
        StoreFixedArrayElement(
            reject_reactions, 0,
            LoadObjectField(promise, JSPromise::kRejectReactionsOffset));
        StoreFixedArrayElement(reject_reactions, 1, var_on_reject.value());

        // Store new FixedArrays in promise.
        StoreObjectField(promise, JSPromise::kDeferredPromiseOffset,
                         deferred_promise_arr);
        StoreObjectField(promise, JSPromise::kDeferredOnResolveOffset,
                         deferred_on_resolve_arr);
        StoreObjectField(promise, JSPromise::kDeferredOnRejectOffset,
                         deferred_on_reject_arr);
        StoreObjectField(promise, JSPromise::kFulfillReactionsOffset,
                         fulfill_reactions);
        StoreObjectField(promise, JSPromise::kRejectReactionsOffset,
                         reject_reactions);
        Goto(&out);
      }

      Bind(&if_multiplecallbacks);
      {
        AppendPromiseCallback(JSPromise::kDeferredPromiseOffset, promise,
                              deferred_promise);
        AppendPromiseCallback(JSPromise::kDeferredOnResolveOffset, promise,
                              deferred_on_resolve);
        AppendPromiseCallback(JSPromise::kDeferredOnRejectOffset, promise,
                              deferred_on_reject);
        AppendPromiseCallback(JSPromise::kFulfillReactionsOffset, promise,
                              var_on_resolve.value());
        AppendPromiseCallback(JSPromise::kRejectReactionsOffset, promise,
                              var_on_reject.value());
        Goto(&out);
      }
    }

    Bind(&fulfilled_check);
    {
      Label reject(this);
      Node* const result = LoadObjectField(promise, JSPromise::kResultOffset);
      GotoIfNot(WordEqual(status, SmiConstant(v8::Promise::kFulfilled)),
                &reject);

      Node* info = AllocatePromiseReactionJobInfo(
          result, var_on_resolve.value(), deferred_promise, deferred_on_resolve,
          deferred_on_reject, context);
      // TODO(gsathya): Move this to TF
      CallRuntime(Runtime::kEnqueuePromiseReactionJob, context, info);
      Goto(&out);

      Bind(&reject);
      {
        Node* const has_handler = PromiseHasHandler(promise);
        Label enqueue(this);

        // TODO(gsathya): Fold these runtime calls and move to TF.
        GotoIf(has_handler, &enqueue);
        CallRuntime(Runtime::kPromiseRevokeReject, context, promise);
        Goto(&enqueue);

        Bind(&enqueue);
        {
          Node* info = AllocatePromiseReactionJobInfo(
              result, var_on_reject.value(), deferred_promise,
              deferred_on_resolve, deferred_on_reject, context);
          // TODO(gsathya): Move this to TF
          CallRuntime(Runtime::kEnqueuePromiseReactionJob, context, info);
          Goto(&out);
        }
      }
    }
  }

  Bind(&out);
  PromiseSetHasHandler(promise);
  return deferred_promise;
}

// Promise fast path implementations rely on unmodified JSPromise instances.
// We use a fairly coarse granularity for this and simply check whether both
// the promise itself is unmodified (i.e. its map has not changed) and its
// prototype is unmodified.
// TODO(gsathya): Refactor this out to prevent code dupe with builtins-regexp
void PromiseBuiltinsAssembler::BranchIfFastPath(Node* context, Node* promise,
                                                Label* if_isunmodified,
                                                Label* if_ismodified) {
  Node* const native_context = LoadNativeContext(context);
  Node* const promise_fun =
      LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
  BranchIfFastPath(native_context, promise_fun, promise, if_isunmodified,
                   if_ismodified);
}

void PromiseBuiltinsAssembler::BranchIfFastPath(Node* native_context,
                                                Node* promise_fun,
                                                Node* promise,
                                                Label* if_isunmodified,
                                                Label* if_ismodified) {
  CSA_ASSERT(this, IsNativeContext(native_context));
  CSA_ASSERT(this,
             WordEqual(promise_fun,
                       LoadContextElement(native_context,
                                          Context::PROMISE_FUNCTION_INDEX)));

  Node* const map = LoadMap(promise);
  Node* const initial_map =
      LoadObjectField(promise_fun, JSFunction::kPrototypeOrInitialMapOffset);
  Node* const has_initialmap = WordEqual(map, initial_map);

  GotoIfNot(has_initialmap, if_ismodified);

  Node* const initial_proto_initial_map =
      LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_MAP_INDEX);
  Node* const proto_map = LoadMap(LoadMapPrototype(map));
  Node* const proto_has_initialmap =
      WordEqual(proto_map, initial_proto_initial_map);

  Branch(proto_has_initialmap, if_isunmodified, if_ismodified);
}

Node* PromiseBuiltinsAssembler::AllocatePromiseResolveThenableJobInfo(
    Node* thenable, Node* then, Node* resolve, Node* reject, Node* context) {
  Node* const info = Allocate(PromiseResolveThenableJobInfo::kSize);
  StoreMapNoWriteBarrier(info,
                         Heap::kPromiseResolveThenableJobInfoMapRootIndex);
  StoreObjectFieldNoWriteBarrier(
      info, PromiseResolveThenableJobInfo::kThenableOffset, thenable);
  StoreObjectFieldNoWriteBarrier(
      info, PromiseResolveThenableJobInfo::kThenOffset, then);
  StoreObjectFieldNoWriteBarrier(
      info, PromiseResolveThenableJobInfo::kResolveOffset, resolve);
  StoreObjectFieldNoWriteBarrier(
      info, PromiseResolveThenableJobInfo::kRejectOffset, reject);
  StoreObjectFieldNoWriteBarrier(
      info, PromiseResolveThenableJobInfo::kContextOffset, context);
  return info;
}

void PromiseBuiltinsAssembler::InternalResolvePromise(Node* context,
                                                      Node* promise,
                                                      Node* result) {
  Isolate* isolate = this->isolate();

  Variable var_reason(this, MachineRepresentation::kTagged),
      var_then(this, MachineRepresentation::kTagged);

  Label do_enqueue(this), fulfill(this), if_cycle(this, Label::kDeferred),
      if_rejectpromise(this, Label::kDeferred), out(this);

  Label cycle_check(this);
  GotoIfNot(IsPromiseHookEnabledOrDebugIsActive(), &cycle_check);
  CallRuntime(Runtime::kPromiseHookResolve, context, promise);
  Goto(&cycle_check);

  Bind(&cycle_check);
  // 6. If SameValue(resolution, promise) is true, then
  GotoIf(SameValue(promise, result, context), &if_cycle);

  // 7. If Type(resolution) is not Object, then
  GotoIf(TaggedIsSmi(result), &fulfill);
  GotoIfNot(IsJSReceiver(result), &fulfill);

  Label if_nativepromise(this), if_notnativepromise(this, Label::kDeferred);
  Node* const native_context = LoadNativeContext(context);
  Node* const promise_fun =
      LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
  BranchIfFastPath(native_context, promise_fun, result, &if_nativepromise,
                   &if_notnativepromise);

  // Resolution is a native promise and if it's already resolved or
  // rejected, shortcircuit the resolution procedure by directly
  // reusing the value from the promise.
  Bind(&if_nativepromise);
  {
    Node* const thenable_status =
        LoadObjectField(result, JSPromise::kStatusOffset);
    Node* const thenable_value =
        LoadObjectField(result, JSPromise::kResultOffset);

    Label if_isnotpending(this);
    GotoIfNot(SmiEqual(SmiConstant(v8::Promise::kPending), thenable_status),
              &if_isnotpending);

    // TODO(gsathya): Use a marker here instead of the actual then
    // callback, and check for the marker in PromiseResolveThenableJob
    // and perform PromiseThen.
    Node* const then =
        LoadContextElement(native_context, Context::PROMISE_THEN_INDEX);
    var_then.Bind(then);
    Goto(&do_enqueue);

    Bind(&if_isnotpending);
    {
      Label if_fulfilled(this), if_rejected(this);
      Branch(SmiEqual(SmiConstant(v8::Promise::kFulfilled), thenable_status),
             &if_fulfilled, &if_rejected);

      Bind(&if_fulfilled);
      {
        PromiseFulfill(context, promise, thenable_value,
                       v8::Promise::kFulfilled);
        PromiseSetHasHandler(promise);
        Goto(&out);
      }

      Bind(&if_rejected);
      {
        Label reject(this);
        Node* const has_handler = PromiseHasHandler(result);

        // Promise has already been rejected, but had no handler.
        // Revoke previously triggered reject event.
        GotoIf(has_handler, &reject);
        CallRuntime(Runtime::kPromiseRevokeReject, context, result);
        Goto(&reject);

        Bind(&reject);
        // Don't cause a debug event as this case is forwarding a rejection
        InternalPromiseReject(context, promise, thenable_value, false);
        PromiseSetHasHandler(result);
        Goto(&out);
      }
    }
  }

  Bind(&if_notnativepromise);
  {
    // 8. Let then be Get(resolution, "then").
    Node* const then_str = HeapConstant(isolate->factory()->then_string());
    Callable getproperty_callable = CodeFactory::GetProperty(isolate);
    Node* const then =
        CallStub(getproperty_callable, context, result, then_str);

    // 9. If then is an abrupt completion, then
    GotoIfException(then, &if_rejectpromise, &var_reason);

    // 11. If IsCallable(thenAction) is false, then
    GotoIf(TaggedIsSmi(then), &fulfill);
    Node* const then_map = LoadMap(then);
    GotoIfNot(IsCallableMap(then_map), &fulfill);
    var_then.Bind(then);
    Goto(&do_enqueue);
  }

  Bind(&do_enqueue);
  {
    // TODO(gsathya): Add fast path for native promises with unmodified
    // PromiseThen (which don't need these resolving functions, but
    // instead can just call resolve/reject directly).
    Node* resolve = nullptr;
    Node* reject = nullptr;
    std::tie(resolve, reject) = CreatePromiseResolvingFunctions(
        promise, FalseConstant(), native_context);

    Node* const info = AllocatePromiseResolveThenableJobInfo(
        result, var_then.value(), resolve, reject, context);

    Label enqueue(this);
    GotoIfNot(IsDebugActive(), &enqueue);

    GotoIf(TaggedIsSmi(result), &enqueue);
    GotoIfNot(HasInstanceType(result, JS_PROMISE_TYPE), &enqueue);

    // Mark the dependency of the new promise on the resolution
    Node* const key =
        HeapConstant(isolate->factory()->promise_handled_by_symbol());
    CallRuntime(Runtime::kSetProperty, context, result, key, promise,
                SmiConstant(STRICT));
    Goto(&enqueue);

    // 12. Perform EnqueueJob("PromiseJobs",
    // PromiseResolveThenableJob, « promise, resolution, thenAction»).
    Bind(&enqueue);
    // TODO(gsathya): Move this to TF
    CallRuntime(Runtime::kEnqueuePromiseResolveThenableJob, context, info);
    Goto(&out);
  }

  // 7.b Return FulfillPromise(promise, resolution).
  Bind(&fulfill);
  {
    PromiseFulfill(context, promise, result, v8::Promise::kFulfilled);
    Goto(&out);
  }

  Bind(&if_cycle);
  {
    // 6.a Let selfResolutionError be a newly created TypeError object.
    Node* const message_id = SmiConstant(MessageTemplate::kPromiseCyclic);
    Node* const error =
        CallRuntime(Runtime::kNewTypeError, context, message_id, result);
    var_reason.Bind(error);

    // 6.b Return RejectPromise(promise, selfResolutionError).
    Goto(&if_rejectpromise);
  }

  // 9.a Return RejectPromise(promise, then.[[Value]]).
  Bind(&if_rejectpromise);
  {
    InternalPromiseReject(context, promise, var_reason.value(), true);
    Goto(&out);
  }

  Bind(&out);
}

void PromiseBuiltinsAssembler::PromiseFulfill(
    Node* context, Node* promise, Node* result,
    v8::Promise::PromiseState status) {
  Label do_promisereset(this), debug_async_event_enqueue_recurring(this);

  Node* const status_smi = SmiConstant(static_cast<int>(status));
  Node* const deferred_promise =
      LoadObjectField(promise, JSPromise::kDeferredPromiseOffset);

  GotoIf(IsUndefined(deferred_promise), &debug_async_event_enqueue_recurring);

  Node* const tasks =
      status == v8::Promise::kFulfilled
          ? LoadObjectField(promise, JSPromise::kFulfillReactionsOffset)
          : LoadObjectField(promise, JSPromise::kRejectReactionsOffset);

  Node* const deferred_on_resolve =
      LoadObjectField(promise, JSPromise::kDeferredOnResolveOffset);
  Node* const deferred_on_reject =
      LoadObjectField(promise, JSPromise::kDeferredOnRejectOffset);

  Node* const info = AllocatePromiseReactionJobInfo(
      result, tasks, deferred_promise, deferred_on_resolve, deferred_on_reject,
      context);

  CallRuntime(Runtime::kEnqueuePromiseReactionJob, context, info);
  Goto(&debug_async_event_enqueue_recurring);

  Bind(&debug_async_event_enqueue_recurring);
  {
    GotoIfNot(IsDebugActive(), &do_promisereset);
    CallRuntime(Runtime::kDebugAsyncEventEnqueueRecurring, context, promise,
                status_smi);
    Goto(&do_promisereset);
  }

  Bind(&do_promisereset);
  {
    StoreObjectField(promise, JSPromise::kStatusOffset, status_smi);
    StoreObjectField(promise, JSPromise::kResultOffset, result);
    StoreObjectFieldRoot(promise, JSPromise::kDeferredPromiseOffset,
                         Heap::kUndefinedValueRootIndex);
    StoreObjectFieldRoot(promise, JSPromise::kDeferredOnResolveOffset,
                         Heap::kUndefinedValueRootIndex);
    StoreObjectFieldRoot(promise, JSPromise::kDeferredOnRejectOffset,
                         Heap::kUndefinedValueRootIndex);
    StoreObjectFieldRoot(promise, JSPromise::kFulfillReactionsOffset,
                         Heap::kUndefinedValueRootIndex);
    StoreObjectFieldRoot(promise, JSPromise::kRejectReactionsOffset,
                         Heap::kUndefinedValueRootIndex);
  }
}

void PromiseBuiltinsAssembler::BranchIfAccessCheckFailed(
    Node* context, Node* native_context, Node* promise_constructor,
    Node* executor, Label* if_noaccess) {
  Variable var_executor(this, MachineRepresentation::kTagged);
  var_executor.Bind(executor);
  Label has_access(this), call_runtime(this, Label::kDeferred);

  // If executor is a bound function, load the bound function until we've
  // reached an actual function.
  Label found_function(this), loop_over_bound_function(this, &var_executor);
  Goto(&loop_over_bound_function);
  Bind(&loop_over_bound_function);
  {
    Node* executor_type = LoadInstanceType(var_executor.value());
    GotoIf(InstanceTypeEqual(executor_type, JS_FUNCTION_TYPE), &found_function);
    GotoIfNot(InstanceTypeEqual(executor_type, JS_BOUND_FUNCTION_TYPE),
              &call_runtime);
    var_executor.Bind(LoadObjectField(
        var_executor.value(), JSBoundFunction::kBoundTargetFunctionOffset));
    Goto(&loop_over_bound_function);
  }

  // Load the context from the function and compare it to the Promise
  // constructor's context. If they match, everything is fine, otherwise, bail
  // out to the runtime.
  Bind(&found_function);
  {
    Node* function_context =
        LoadObjectField(var_executor.value(), JSFunction::kContextOffset);
    Node* native_function_context = LoadNativeContext(function_context);
    Branch(WordEqual(native_context, native_function_context), &has_access,
           &call_runtime);
  }

  Bind(&call_runtime);
  {
    Branch(WordEqual(CallRuntime(Runtime::kAllowDynamicFunction, context,
                                 promise_constructor),
                     BooleanConstant(true)),
           &has_access, if_noaccess);
  }

  Bind(&has_access);
}

void PromiseBuiltinsAssembler::InternalPromiseReject(Node* context,
                                                     Node* promise, Node* value,
                                                     Node* debug_event) {
  Label out(this);
  GotoIfNot(IsDebugActive(), &out);
  GotoIfNot(WordEqual(TrueConstant(), debug_event), &out);
  CallRuntime(Runtime::kDebugPromiseReject, context, promise, value);
  Goto(&out);

  Bind(&out);
  InternalPromiseReject(context, promise, value, false);
}

// This duplicates a lot of logic from PromiseRejectEvent in
// runtime-promise.cc
void PromiseBuiltinsAssembler::InternalPromiseReject(Node* context,
                                                     Node* promise, Node* value,
                                                     bool debug_event) {
  Label fulfill(this), report_unhandledpromise(this), run_promise_hook(this);

  if (debug_event) {
    GotoIfNot(IsDebugActive(), &run_promise_hook);
    CallRuntime(Runtime::kDebugPromiseReject, context, promise, value);
    Goto(&run_promise_hook);
  } else {
    Goto(&run_promise_hook);
  }

  Bind(&run_promise_hook);
  {
    GotoIfNot(IsPromiseHookEnabledOrDebugIsActive(), &report_unhandledpromise);
    CallRuntime(Runtime::kPromiseHookResolve, context, promise);
    Goto(&report_unhandledpromise);
  }

  Bind(&report_unhandledpromise);
  {
    GotoIf(PromiseHasHandler(promise), &fulfill);
    CallRuntime(Runtime::kReportPromiseReject, context, promise, value);
    Goto(&fulfill);
  }

  Bind(&fulfill);
  PromiseFulfill(context, promise, value, v8::Promise::kRejected);
}

// ES#sec-promise-reject-functions
// Promise Reject Functions
TF_BUILTIN(PromiseRejectClosure, PromiseBuiltinsAssembler) {
  Node* const value = Parameter(1);
  Node* const context = Parameter(4);

  Label out(this);

  // 3. Let alreadyResolved be F.[[AlreadyResolved]].
  int has_already_visited_slot = kAlreadyVisitedSlot;

  Node* const has_already_visited =
      LoadContextElement(context, has_already_visited_slot);

  // 4. If alreadyResolved.[[Value]] is true, return undefined.
  GotoIf(SmiEqual(has_already_visited, SmiConstant(1)), &out);

  // 5.Set alreadyResolved.[[Value]] to true.
  StoreContextElementNoWriteBarrier(context, has_already_visited_slot,
                                    SmiConstant(1));

  // 2. Let promise be F.[[Promise]].
  Node* const promise =
      LoadContextElement(context, IntPtrConstant(kPromiseSlot));
  Node* const debug_event =
      LoadContextElement(context, IntPtrConstant(kDebugEventSlot));

  InternalPromiseReject(context, promise, value, debug_event);
  Return(UndefinedConstant());

  Bind(&out);
  Return(UndefinedConstant());
}

TF_BUILTIN(PromiseConstructor, PromiseBuiltinsAssembler) {
  Node* const executor = Parameter(1);
  Node* const new_target = Parameter(2);
  Node* const context = Parameter(4);
  Isolate* isolate = this->isolate();

  Label if_targetisundefined(this, Label::kDeferred);

  GotoIf(IsUndefined(new_target), &if_targetisundefined);

  Label if_notcallable(this, Label::kDeferred);

  GotoIf(TaggedIsSmi(executor), &if_notcallable);

  Node* const executor_map = LoadMap(executor);
  GotoIfNot(IsCallableMap(executor_map), &if_notcallable);

  Node* const native_context = LoadNativeContext(context);
  Node* const promise_fun =
      LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
  Node* const is_debug_active = IsDebugActive();
  Label if_targetisnotmodified(this),
      if_targetismodified(this, Label::kDeferred), run_executor(this),
      debug_push(this), if_noaccess(this, Label::kDeferred);

  BranchIfAccessCheckFailed(context, native_context, promise_fun, executor,
                            &if_noaccess);

  Branch(WordEqual(promise_fun, new_target), &if_targetisnotmodified,
         &if_targetismodified);

  Variable var_result(this, MachineRepresentation::kTagged),
      var_reject_call(this, MachineRepresentation::kTagged),
      var_reason(this, MachineRepresentation::kTagged);

  Bind(&if_targetisnotmodified);
  {
    Node* const instance = AllocateAndInitJSPromise(context);
    var_result.Bind(instance);
    Goto(&debug_push);
  }

  Bind(&if_targetismodified);
  {
    ConstructorBuiltinsAssembler constructor_assembler(this->state());
    Node* const instance = constructor_assembler.EmitFastNewObject(
        context, promise_fun, new_target);
    PromiseInit(instance);
    var_result.Bind(instance);

    GotoIfNot(IsPromiseHookEnabledOrDebugIsActive(), &debug_push);
    CallRuntime(Runtime::kPromiseHookInit, context, instance,
                UndefinedConstant());
    Goto(&debug_push);
  }

  Bind(&debug_push);
  {
    GotoIfNot(is_debug_active, &run_executor);
    CallRuntime(Runtime::kDebugPushPromise, context, var_result.value());
    Goto(&run_executor);
  }

  Bind(&run_executor);
  {
    Label out(this), if_rejectpromise(this), debug_pop(this, Label::kDeferred);

    Node *resolve, *reject;
    std::tie(resolve, reject) = CreatePromiseResolvingFunctions(
        var_result.value(), TrueConstant(), native_context);
    Callable call_callable = CodeFactory::Call(isolate);

    Node* const maybe_exception = CallJS(call_callable, context, executor,
                                         UndefinedConstant(), resolve, reject);

    GotoIfException(maybe_exception, &if_rejectpromise, &var_reason);
    Branch(is_debug_active, &debug_pop, &out);

    Bind(&if_rejectpromise);
    {
      Callable call_callable = CodeFactory::Call(isolate);
      CallJS(call_callable, context, reject, UndefinedConstant(),
             var_reason.value());
      Branch(is_debug_active, &debug_pop, &out);
    }

    Bind(&debug_pop);
    {
      CallRuntime(Runtime::kDebugPopPromise, context);
      Goto(&out);
    }
    Bind(&out);
    Return(var_result.value());
  }

  // 1. If NewTarget is undefined, throw a TypeError exception.
  Bind(&if_targetisundefined);
  {
    Node* const message_id = SmiConstant(MessageTemplate::kNotAPromise);
    CallRuntime(Runtime::kThrowTypeError, context, message_id, new_target);
    Unreachable();
  }

  // 2. If IsCallable(executor) is false, throw a TypeError exception.
  Bind(&if_notcallable);
  {
    Node* const message_id =
        SmiConstant(MessageTemplate::kResolverNotAFunction);
    CallRuntime(Runtime::kThrowTypeError, context, message_id, executor);
    Unreachable();
  }

  // Silently fail if the stack looks fishy.
  Bind(&if_noaccess);
  {
    Node* const counter_id =
        SmiConstant(v8::Isolate::kPromiseConstructorReturnedUndefined);
    CallRuntime(Runtime::kIncrementUseCounter, context, counter_id);
    Return(UndefinedConstant());
  }
}

TF_BUILTIN(PromiseInternalConstructor, PromiseBuiltinsAssembler) {
  Node* const parent = Parameter(1);
  Node* const context = Parameter(4);
  Return(AllocateAndInitJSPromise(context, parent));
}

TF_BUILTIN(IsPromise, PromiseBuiltinsAssembler) {
  Node* const maybe_promise = Parameter(1);
  Label if_notpromise(this, Label::kDeferred);

  GotoIf(TaggedIsSmi(maybe_promise), &if_notpromise);

  Node* const result =
      SelectBooleanConstant(HasInstanceType(maybe_promise, JS_PROMISE_TYPE));
  Return(result);

  Bind(&if_notpromise);
  Return(FalseConstant());
}

// ES#sec-promise.prototype.then
// Promise.prototype.catch ( onFulfilled, onRejected )
TF_BUILTIN(PromiseThen, PromiseBuiltinsAssembler) {
  // 1. Let promise be the this value.
  Node* const promise = Parameter(0);
  Node* const on_resolve = Parameter(1);
  Node* const on_reject = Parameter(2);
  Node* const context = Parameter(5);

  Node* const result =
      InternalPromiseThen(context, promise, on_resolve, on_reject);
  Return(result);
}

// ES#sec-promise-resolve-functions
// Promise Resolve Functions
TF_BUILTIN(PromiseResolveClosure, PromiseBuiltinsAssembler) {
  Node* const value = Parameter(1);
  Node* const context = Parameter(4);

  Label out(this);

  // 3. Let alreadyResolved be F.[[AlreadyResolved]].
  int has_already_visited_slot = kAlreadyVisitedSlot;

  Node* const has_already_visited =
      LoadContextElement(context, has_already_visited_slot);

  // 4. If alreadyResolved.[[Value]] is true, return undefined.
  GotoIf(SmiEqual(has_already_visited, SmiConstant(1)), &out);

  // 5.Set alreadyResolved.[[Value]] to true.
  StoreContextElementNoWriteBarrier(context, has_already_visited_slot,
                                    SmiConstant(1));

  // 2. Let promise be F.[[Promise]].
  Node* const promise =
      LoadContextElement(context, IntPtrConstant(kPromiseSlot));

  InternalResolvePromise(context, promise, value);
  Return(UndefinedConstant());

  Bind(&out);
  Return(UndefinedConstant());
}

TF_BUILTIN(ResolvePromise, PromiseBuiltinsAssembler) {
  Node* const promise = Parameter(1);
  Node* const result = Parameter(2);
  Node* const context = Parameter(5);

  InternalResolvePromise(context, promise, result);
  Return(UndefinedConstant());
}

TF_BUILTIN(PromiseHandleReject, PromiseBuiltinsAssembler) {
  typedef PromiseHandleRejectDescriptor Descriptor;

  Node* const promise = Parameter(Descriptor::kPromise);
  Node* const on_reject = Parameter(Descriptor::kOnReject);
  Node* const exception = Parameter(Descriptor::kException);
  Node* const context = Parameter(Descriptor::kContext);

  Callable call_callable = CodeFactory::Call(isolate());
  Variable var_unused(this, MachineRepresentation::kTagged);

  Label if_internalhandler(this), if_customhandler(this, Label::kDeferred);
  Branch(IsUndefined(on_reject), &if_internalhandler, &if_customhandler);

  Bind(&if_internalhandler);
  {
    InternalPromiseReject(context, promise, exception, false);
    Return(UndefinedConstant());
  }

  Bind(&if_customhandler);
  {
    CallJS(call_callable, context, on_reject, UndefinedConstant(), exception);
    Return(UndefinedConstant());
  }
}

TF_BUILTIN(PromiseHandle, PromiseBuiltinsAssembler) {
  Node* const value = Parameter(1);
  Node* const handler = Parameter(2);
  Node* const deferred_promise = Parameter(3);
  Node* const deferred_on_resolve = Parameter(4);
  Node* const deferred_on_reject = Parameter(5);
  Node* const context = Parameter(8);
  Isolate* isolate = this->isolate();

  Variable var_reason(this, MachineRepresentation::kTagged);

  Node* const is_debug_active = IsDebugActive();
  Label run_handler(this), if_rejectpromise(this), promisehook_before(this),
      promisehook_after(this), debug_pop(this);

  GotoIfNot(is_debug_active, &promisehook_before);
  CallRuntime(Runtime::kDebugPushPromise, context, deferred_promise);
  Goto(&promisehook_before);

  Bind(&promisehook_before);
  {
    GotoIfNot(IsPromiseHookEnabledOrDebugIsActive(), &run_handler);
    CallRuntime(Runtime::kPromiseHookBefore, context, deferred_promise);
    Goto(&run_handler);
  }

  Bind(&run_handler);
  {
    Label if_defaulthandler(this), if_callablehandler(this),
        if_internalhandler(this), if_customhandler(this, Label::kDeferred);
    Variable var_result(this, MachineRepresentation::kTagged);

    Branch(IsSymbol(handler), &if_defaulthandler, &if_callablehandler);

    Bind(&if_defaulthandler);
    {
      Label if_resolve(this), if_reject(this);
      Node* const default_resolve_handler_symbol = HeapConstant(
          isolate->factory()->promise_default_resolve_handler_symbol());
      Branch(WordEqual(default_resolve_handler_symbol, handler), &if_resolve,
             &if_reject);

      Bind(&if_resolve);
      {
        var_result.Bind(value);
        Branch(IsUndefined(deferred_on_resolve), &if_internalhandler,
               &if_customhandler);
      }

      Bind(&if_reject);
      {
        var_reason.Bind(value);
        Goto(&if_rejectpromise);
      }
    }

    Bind(&if_callablehandler);
    {
      Callable call_callable = CodeFactory::Call(isolate);
      Node* const result =
          CallJS(call_callable, context, handler, UndefinedConstant(), value);
      var_result.Bind(result);
      GotoIfException(result, &if_rejectpromise, &var_reason);
      Branch(IsUndefined(deferred_on_resolve), &if_internalhandler,
             &if_customhandler);
    }

    Bind(&if_internalhandler);
    InternalResolvePromise(context, deferred_promise, var_result.value());
    Goto(&promisehook_after);

    Bind(&if_customhandler);
    {
      Callable call_callable = CodeFactory::Call(isolate);
      Node* const maybe_exception =
          CallJS(call_callable, context, deferred_on_resolve,
                 UndefinedConstant(), var_result.value());
      GotoIfException(maybe_exception, &if_rejectpromise, &var_reason);
      Goto(&promisehook_after);
    }
  }

  Bind(&if_rejectpromise);
  {
    Callable promise_handle_reject = CodeFactory::PromiseHandleReject(isolate);
    CallStub(promise_handle_reject, context, deferred_promise,
             deferred_on_reject, var_reason.value());
    Goto(&promisehook_after);
  }

  Bind(&promisehook_after);
  {
    GotoIfNot(IsPromiseHookEnabledOrDebugIsActive(), &debug_pop);
    CallRuntime(Runtime::kPromiseHookAfter, context, deferred_promise);
    Goto(&debug_pop);
  }

  Bind(&debug_pop);
  {
    Label out(this);

    GotoIfNot(is_debug_active, &out);
    CallRuntime(Runtime::kDebugPopPromise, context);
    Goto(&out);

    Bind(&out);
    Return(UndefinedConstant());
  }
}

// ES#sec-promise.prototype.catch
// Promise.prototype.catch ( onRejected )
TF_BUILTIN(PromiseCatch, PromiseBuiltinsAssembler) {
  // 1. Let promise be the this value.
  Node* const promise = Parameter(0);
  Node* const on_resolve = UndefinedConstant();
  Node* const on_reject = Parameter(1);
  Node* const context = Parameter(4);

  Label if_internalthen(this), if_customthen(this, Label::kDeferred);
  GotoIf(TaggedIsSmi(promise), &if_customthen);
  BranchIfFastPath(context, promise, &if_internalthen, &if_customthen);

  Bind(&if_internalthen);
  {
    Node* const result =
        InternalPromiseThen(context, promise, on_resolve, on_reject);
    Return(result);
  }

  Bind(&if_customthen);
  {
    Isolate* isolate = this->isolate();
    Node* const then_str = HeapConstant(isolate->factory()->then_string());
    Callable getproperty_callable = CodeFactory::GetProperty(isolate);
    Node* const then =
        CallStub(getproperty_callable, context, promise, then_str);
    Callable call_callable = CodeFactory::Call(isolate);
    Node* const result =
        CallJS(call_callable, context, then, promise, on_resolve, on_reject);
    Return(result);
  }
}

TF_BUILTIN(PromiseResolve, PromiseBuiltinsAssembler) {
  //  1. Let C be the this value.
  Node* receiver = Parameter(0);
  Node* value = Parameter(1);
  Node* context = Parameter(4);
  Isolate* isolate = this->isolate();

  // 2. If Type(C) is not Object, throw a TypeError exception.
  ThrowIfNotJSReceiver(context, receiver, MessageTemplate::kCalledOnNonObject,
                       "PromiseResolve");

  Label if_valueisnativepromise(this), if_valueisnotnativepromise(this),
      if_valueisnotpromise(this);

  // 3.If IsPromise(x) is true, then
  GotoIf(TaggedIsSmi(value), &if_valueisnotpromise);

  // This shortcircuits the constructor lookups.
  GotoIfNot(HasInstanceType(value, JS_PROMISE_TYPE), &if_valueisnotpromise);

  // This adds a fast path as non-subclassed native promises don't have
  // an observable constructor lookup.
  Node* const native_context = LoadNativeContext(context);
  Node* const promise_fun =
      LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
  BranchIfFastPath(native_context, promise_fun, value, &if_valueisnativepromise,
                   &if_valueisnotnativepromise);

  Bind(&if_valueisnativepromise);
  {
    GotoIfNot(WordEqual(promise_fun, receiver), &if_valueisnotnativepromise);
    Return(value);
  }

  // At this point, value or/and receiver are not native promises, but
  // they could be of the same subclass.
  Bind(&if_valueisnotnativepromise);
  {
    // 3.a Let xConstructor be ? Get(x, "constructor").
    // The constructor lookup is observable.
    Node* const constructor_str =
        HeapConstant(isolate->factory()->constructor_string());
    Callable getproperty_callable = CodeFactory::GetProperty(isolate);
    Node* const constructor =
        CallStub(getproperty_callable, context, value, constructor_str);

    // 3.b If SameValue(xConstructor, C) is true, return x.
    GotoIfNot(SameValue(constructor, receiver, context), &if_valueisnotpromise);

    Return(value);
  }

  Bind(&if_valueisnotpromise);
  {
    Label if_nativepromise(this), if_notnativepromise(this);
    BranchIfFastPath(context, receiver, &if_nativepromise,
                     &if_notnativepromise);

    // This adds a fast path for native promises that don't need to
    // create NewPromiseCapability.
    Bind(&if_nativepromise);
    {
      Label do_resolve(this);

      Node* const result = AllocateAndInitJSPromise(context);
      InternalResolvePromise(context, result, value);
      Return(result);
    }

    Bind(&if_notnativepromise);
    {
      // 4. Let promiseCapability be ? NewPromiseCapability(C).
      Node* const capability = NewPromiseCapability(context, receiver);

      // 5. Perform ? Call(promiseCapability.[[Resolve]], undefined, « x »).
      Callable call_callable = CodeFactory::Call(isolate);
      Node* const resolve =
          LoadObjectField(capability, JSPromiseCapability::kResolveOffset);
      CallJS(call_callable, context, resolve, UndefinedConstant(), value);

      // 6. Return promiseCapability.[[Promise]].
      Node* const result =
          LoadObjectField(capability, JSPromiseCapability::kPromiseOffset);
      Return(result);
    }
  }
}

TF_BUILTIN(PromiseGetCapabilitiesExecutor, PromiseBuiltinsAssembler) {
  Node* const resolve = Parameter(1);
  Node* const reject = Parameter(2);
  Node* const context = Parameter(5);

  Node* const capability = LoadContextElement(context, kCapabilitySlot);

  Label if_alreadyinvoked(this, Label::kDeferred);
  GotoIf(WordNotEqual(
             LoadObjectField(capability, JSPromiseCapability::kResolveOffset),
             UndefinedConstant()),
         &if_alreadyinvoked);
  GotoIf(WordNotEqual(
             LoadObjectField(capability, JSPromiseCapability::kRejectOffset),
             UndefinedConstant()),
         &if_alreadyinvoked);

  StoreObjectField(capability, JSPromiseCapability::kResolveOffset, resolve);
  StoreObjectField(capability, JSPromiseCapability::kRejectOffset, reject);

  Return(UndefinedConstant());

  Bind(&if_alreadyinvoked);
  Node* message = SmiConstant(MessageTemplate::kPromiseExecutorAlreadyInvoked);
  CallRuntime(Runtime::kThrowTypeError, context, message);
  Unreachable();
}

TF_BUILTIN(NewPromiseCapability, PromiseBuiltinsAssembler) {
  Node* constructor = Parameter(1);
  Node* debug_event = Parameter(2);
  Node* context = Parameter(5);

  CSA_ASSERT_JS_ARGC_EQ(this, 2);

  Return(NewPromiseCapability(context, constructor, debug_event));
}

TF_BUILTIN(PromiseReject, PromiseBuiltinsAssembler) {
  // 1. Let C be the this value.
  Node* const receiver = Parameter(0);
  Node* const reason = Parameter(1);
  Node* const context = Parameter(4);

  // 2. If Type(C) is not Object, throw a TypeError exception.
  ThrowIfNotJSReceiver(context, receiver, MessageTemplate::kCalledOnNonObject,
                       "PromiseReject");

  Label if_nativepromise(this), if_custompromise(this, Label::kDeferred);
  Node* const native_context = LoadNativeContext(context);
  Node* const promise_fun =
      LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
  Branch(WordEqual(promise_fun, receiver), &if_nativepromise,
         &if_custompromise);

  Bind(&if_nativepromise);
  {
    Node* const promise = AllocateAndSetJSPromise(
        context, SmiConstant(v8::Promise::kRejected), reason);
    CallRuntime(Runtime::kPromiseRejectEventFromStack, context, promise,
                reason);
    Return(promise);
  }

  Bind(&if_custompromise);
  {
    // 3. Let promiseCapability be ? NewPromiseCapability(C).
    Node* const capability = NewPromiseCapability(context, receiver);

    // 4. Perform ? Call(promiseCapability.[[Reject]], undefined, « r »).
    Node* const reject =
        LoadObjectField(capability, JSPromiseCapability::kRejectOffset);
    Callable call_callable = CodeFactory::Call(isolate());
    CallJS(call_callable, context, reject, UndefinedConstant(), reason);

    // 5. Return promiseCapability.[[Promise]].
    Node* const promise =
        LoadObjectField(capability, JSPromiseCapability::kPromiseOffset);
    Return(promise);
  }
}

TF_BUILTIN(InternalPromiseReject, PromiseBuiltinsAssembler) {
  Node* const promise = Parameter(1);
  Node* const reason = Parameter(2);
  Node* const debug_event = Parameter(3);
  Node* const context = Parameter(6);

  InternalPromiseReject(context, promise, reason, debug_event);
  Return(UndefinedConstant());
}

Node* PromiseBuiltinsAssembler::CreatePromiseFinallyContext(
    Node* on_finally, Node* native_context) {
  Node* const context =
      CreatePromiseContext(native_context, kOnFinallyContextLength);
  StoreContextElementNoWriteBarrier(context, kOnFinallySlot, on_finally);
  return context;
}

std::pair<Node*, Node*> PromiseBuiltinsAssembler::CreatePromiseFinallyFunctions(
    Node* on_finally, Node* native_context) {
  Node* const promise_context =
      CreatePromiseFinallyContext(on_finally, native_context);
  Node* const map = LoadContextElement(
      native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
  Node* const then_finally_info = LoadContextElement(
      native_context, Context::PROMISE_THEN_FINALLY_SHARED_FUN);
  Node* const then_finally = AllocateFunctionWithMapAndContext(
      map, then_finally_info, promise_context);
  Node* const catch_finally_info = LoadContextElement(
      native_context, Context::PROMISE_CATCH_FINALLY_SHARED_FUN);
  Node* const catch_finally = AllocateFunctionWithMapAndContext(
      map, catch_finally_info, promise_context);
  return std::make_pair(then_finally, catch_finally);
}

TF_BUILTIN(PromiseValueThunkFinally, PromiseBuiltinsAssembler) {
  Node* const context = Parameter(3);

  Node* const value = LoadContextElement(context, kOnFinallySlot);
  Return(value);
}

Node* PromiseBuiltinsAssembler::CreateValueThunkFunctionContext(
    Node* value, Node* native_context) {
  Node* const context =
      CreatePromiseContext(native_context, kOnFinallyContextLength);
  StoreContextElementNoWriteBarrier(context, kOnFinallySlot, value);
  return context;
}

Node* PromiseBuiltinsAssembler::CreateValueThunkFunction(Node* value,
                                                         Node* native_context) {
  Node* const value_thunk_context =
      CreateValueThunkFunctionContext(value, native_context);
  Node* const map = LoadContextElement(
      native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
  Node* const value_thunk_info = LoadContextElement(
      native_context, Context::PROMISE_VALUE_THUNK_FINALLY_SHARED_FUN);
  Node* const value_thunk = AllocateFunctionWithMapAndContext(
      map, value_thunk_info, value_thunk_context);
  return value_thunk;
}

TF_BUILTIN(PromiseThenFinally, PromiseBuiltinsAssembler) {
  CSA_ASSERT_JS_ARGC_EQ(this, 1);

  Node* const value = Parameter(1);
  Node* const context = Parameter(4);

  Node* const on_finally = LoadContextElement(context, kOnFinallySlot);

  // 2.a Let result be ?  Call(onFinally, undefined).
  Callable call_callable = CodeFactory::Call(isolate());
  Node* result =
      CallJS(call_callable, context, on_finally, UndefinedConstant());

  // 2.b Let promise be !  PromiseResolve( %Promise%, result).
  Node* const promise = AllocateAndInitJSPromise(context);
  InternalResolvePromise(context, promise, result);

  // 2.c Let valueThunk be equivalent to a function that returns value.
  Node* native_context = LoadNativeContext(context);
  Node* const value_thunk = CreateValueThunkFunction(value, native_context);

  // 2.d Let promiseCapability be !  NewPromiseCapability( %Promise%).
  Node* const promise_capability = AllocateAndInitJSPromise(context, promise);

  // 2.e Return PerformPromiseThen(promise, valueThunk, undefined,
  // promiseCapability).
  InternalPerformPromiseThen(context, promise, value_thunk, UndefinedConstant(),
                             promise_capability, UndefinedConstant(),
                             UndefinedConstant());
  Return(promise_capability);
}

TF_BUILTIN(PromiseThrowerFinally, PromiseBuiltinsAssembler) {
  Node* const context = Parameter(3);

  Node* const reason = LoadContextElement(context, kOnFinallySlot);
  CallRuntime(Runtime::kThrow, context, reason);
  Unreachable();
}

Node* PromiseBuiltinsAssembler::CreateThrowerFunctionContext(
    Node* reason, Node* native_context) {
  Node* const context =
      CreatePromiseContext(native_context, kOnFinallyContextLength);
  StoreContextElementNoWriteBarrier(context, kOnFinallySlot, reason);
  return context;
}

Node* PromiseBuiltinsAssembler::CreateThrowerFunction(Node* reason,
                                                      Node* native_context) {
  Node* const thrower_context =
      CreateThrowerFunctionContext(reason, native_context);
  Node* const map = LoadContextElement(
      native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
  Node* const thrower_info = LoadContextElement(
      native_context, Context::PROMISE_THROWER_FINALLY_SHARED_FUN);
  Node* const thrower =
      AllocateFunctionWithMapAndContext(map, thrower_info, thrower_context);
  return thrower;
}

TF_BUILTIN(PromiseCatchFinally, PromiseBuiltinsAssembler) {
  CSA_ASSERT_JS_ARGC_EQ(this, 1);

  Node* const reason = Parameter(1);
  Node* const context = Parameter(4);

  Node* const on_finally = LoadContextElement(context, kOnFinallySlot);

  // 2.a Let result be ?  Call(onFinally, undefined).
  Callable call_callable = CodeFactory::Call(isolate());
  Node* result =
      CallJS(call_callable, context, on_finally, UndefinedConstant());

  // 2.b Let promise be !  PromiseResolve( %Promise%, result).
  Node* const promise = AllocateAndInitJSPromise(context);
  InternalResolvePromise(context, promise, result);

  // 2.c Let thrower be equivalent to a function that throws reason.
  Node* native_context = LoadNativeContext(context);
  Node* const thrower = CreateThrowerFunction(reason, native_context);

  // 2.d Let promiseCapability be !  NewPromiseCapability( %Promise%).
  Node* const promise_capability = AllocateAndInitJSPromise(context, promise);

  // 2.e Return PerformPromiseThen(promise, thrower, undefined,
  // promiseCapability).
  InternalPerformPromiseThen(context, promise, thrower, UndefinedConstant(),
                             promise_capability, UndefinedConstant(),
                             UndefinedConstant());
  Return(promise_capability);
}

TF_BUILTIN(PromiseFinally, PromiseBuiltinsAssembler) {
  CSA_ASSERT_JS_ARGC_EQ(this, 1);

  // 1.  Let promise be the this value.
  Node* const promise = Parameter(0);
  Node* const on_finally = Parameter(1);
  Node* const context = Parameter(4);

  // 2. If IsPromise(promise) is false, throw a TypeError exception.
  ThrowIfNotInstanceType(context, promise, JS_PROMISE_TYPE,
                         "Promise.prototype.finally");

  Variable var_then_finally(this, MachineRepresentation::kTagged),
      var_catch_finally(this, MachineRepresentation::kTagged);

  Label if_notcallable(this, Label::kDeferred), perform_finally(this);

  // 3. Let thenFinally be !  CreateThenFinally(onFinally).
  // 4. Let catchFinally be !  CreateCatchFinally(onFinally).
  GotoIf(TaggedIsSmi(on_finally), &if_notcallable);
  Node* const on_finally_map = LoadMap(on_finally);
  GotoIfNot(IsCallableMap(on_finally_map), &if_notcallable);

  Node* const native_context = LoadNativeContext(context);
  Node* then_finally = nullptr;
  Node* catch_finally = nullptr;
  std::tie(then_finally, catch_finally) =
      CreatePromiseFinallyFunctions(on_finally, native_context);
  var_then_finally.Bind(then_finally);
  var_catch_finally.Bind(catch_finally);
  Goto(&perform_finally);

  Bind(&if_notcallable);
  {
    var_then_finally.Bind(on_finally);
    var_catch_finally.Bind(on_finally);
    Goto(&perform_finally);
  }

  // 5. Return PerformPromiseThen(promise, valueThunk, undefined,
  // promiseCapability).
  Bind(&perform_finally);
  Label if_nativepromise(this), if_custompromise(this, Label::kDeferred);
  BranchIfFastPath(context, promise, &if_nativepromise, &if_custompromise);

  Bind(&if_nativepromise);
  {
    Node* deferred_promise = AllocateAndInitJSPromise(context, promise);
    InternalPerformPromiseThen(context, promise, var_then_finally.value(),
                               var_catch_finally.value(), deferred_promise,
                               UndefinedConstant(), UndefinedConstant());
    Return(deferred_promise);
  }

  Bind(&if_custompromise);
  {
    Isolate* isolate = this->isolate();
    Node* const then_str = HeapConstant(isolate->factory()->then_string());
    Callable getproperty_callable = CodeFactory::GetProperty(isolate);
    Node* const then =
        CallStub(getproperty_callable, context, promise, then_str);
    Callable call_callable = CodeFactory::Call(isolate);
    // 5. Return ?  Invoke(promise, "then", « thenFinally, catchFinally »).
    Node* const result =
        CallJS(call_callable, context, then, promise, var_then_finally.value(),
               var_catch_finally.value());
    Return(result);
  }
}

}  // namespace internal
}  // namespace v8