// 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-iterator-gen.h"
#include "src/builtins/builtins-string-gen.h"
#include "src/builtins/builtins-typed-array-gen.h"
#include "src/builtins/builtins-utils-gen.h"
#include "src/builtins/builtins.h"
#include "src/code-stub-assembler.h"
#include "src/frame-constants.h"
#include "src/heap/factory-inl.h"

#include "src/builtins/builtins-array-gen.h"

namespace v8 {
namespace internal {

using Node = compiler::Node;

ArrayBuiltinsAssembler::ArrayBuiltinsAssembler(
    compiler::CodeAssemblerState* state)
    : BaseBuiltinsFromDSLAssembler(state),
      k_(this, MachineRepresentation::kTagged),
      a_(this, MachineRepresentation::kTagged),
      to_(this, MachineRepresentation::kTagged, SmiConstant(0)),
      fully_spec_compliant_(this, {&k_, &a_, &to_}) {}

void ArrayBuiltinsAssembler::FindResultGenerator() {
  a_.Bind(UndefinedConstant());
}

Node* ArrayBuiltinsAssembler::FindProcessor(Node* k_value, Node* k) {
  Node* value = CallJS(CodeFactory::Call(isolate()), context(), callbackfn(),
                       this_arg(), k_value, k, o());
  Label false_continue(this), return_true(this);
  BranchIfToBooleanIsTrue(value, &return_true, &false_continue);
  BIND(&return_true);
  ReturnFromBuiltin(k_value);
  BIND(&false_continue);
  return a();
  }

  void ArrayBuiltinsAssembler::FindIndexResultGenerator() {
    a_.Bind(SmiConstant(-1));
  }

  Node* ArrayBuiltinsAssembler::FindIndexProcessor(Node* k_value, Node* k) {
    Node* value = CallJS(CodeFactory::Call(isolate()), context(), callbackfn(),
                         this_arg(), k_value, k, o());
    Label false_continue(this), return_true(this);
    BranchIfToBooleanIsTrue(value, &return_true, &false_continue);
    BIND(&return_true);
    ReturnFromBuiltin(k);
    BIND(&false_continue);
    return a();
  }

  void ArrayBuiltinsAssembler::ForEachResultGenerator() {
    a_.Bind(UndefinedConstant());
  }

  Node* ArrayBuiltinsAssembler::ForEachProcessor(Node* k_value, Node* k) {
    CallJS(CodeFactory::Call(isolate()), context(), callbackfn(), this_arg(),
           k_value, k, o());
    return a();
  }

  void ArrayBuiltinsAssembler::SomeResultGenerator() {
    a_.Bind(FalseConstant());
  }

  Node* ArrayBuiltinsAssembler::SomeProcessor(Node* k_value, Node* k) {
    Node* value = CallJS(CodeFactory::Call(isolate()), context(), callbackfn(),
                         this_arg(), k_value, k, o());
    Label false_continue(this), return_true(this);
    BranchIfToBooleanIsTrue(value, &return_true, &false_continue);
    BIND(&return_true);
    ReturnFromBuiltin(TrueConstant());
    BIND(&false_continue);
    return a();
  }

  void ArrayBuiltinsAssembler::EveryResultGenerator() {
    a_.Bind(TrueConstant());
  }

  Node* ArrayBuiltinsAssembler::EveryProcessor(Node* k_value, Node* k) {
    Node* value = CallJS(CodeFactory::Call(isolate()), context(), callbackfn(),
                         this_arg(), k_value, k, o());
    Label true_continue(this), return_false(this);
    BranchIfToBooleanIsTrue(value, &true_continue, &return_false);
    BIND(&return_false);
    ReturnFromBuiltin(FalseConstant());
    BIND(&true_continue);
    return a();
  }

  void ArrayBuiltinsAssembler::ReduceResultGenerator() {
    return a_.Bind(this_arg());
  }

  Node* ArrayBuiltinsAssembler::ReduceProcessor(Node* k_value, Node* k) {
    VARIABLE(result, MachineRepresentation::kTagged);
    Label done(this, {&result}), initial(this);
    GotoIf(WordEqual(a(), TheHoleConstant()), &initial);
    result.Bind(CallJS(CodeFactory::Call(isolate()), context(), callbackfn(),
                       UndefinedConstant(), a(), k_value, k, o()));
    Goto(&done);

    BIND(&initial);
    result.Bind(k_value);
    Goto(&done);

    BIND(&done);
    return result.value();
  }

  void ArrayBuiltinsAssembler::ReducePostLoopAction() {
    Label ok(this);
    GotoIf(WordNotEqual(a(), TheHoleConstant()), &ok);
    ThrowTypeError(context(), MessageTemplate::kReduceNoInitial);
    BIND(&ok);
  }

  void ArrayBuiltinsAssembler::FilterResultGenerator() {
    // 7. Let A be ArraySpeciesCreate(O, 0).
    // This version of ArraySpeciesCreate will create with the correct
    // ElementsKind in the fast case.
    GenerateArraySpeciesCreate();
  }

  Node* ArrayBuiltinsAssembler::FilterProcessor(Node* k_value, Node* k) {
    // ii. Let selected be ToBoolean(? Call(callbackfn, T, kValue, k, O)).
    Node* selected = CallJS(CodeFactory::Call(isolate()), context(),
                            callbackfn(), this_arg(), k_value, k, o());
    Label true_continue(this, &to_), false_continue(this);
    BranchIfToBooleanIsTrue(selected, &true_continue, &false_continue);
    BIND(&true_continue);
    // iii. If selected is true, then...
    {
      Label after_work(this, &to_);
      Node* kind = nullptr;

      // If a() is a JSArray, we can have a fast path.
      Label fast(this);
      Label runtime(this);
      Label object_push_pre(this), object_push(this), double_push(this);
      BranchIfFastJSArray(a(), context(), &fast, &runtime);

      BIND(&fast);
      {
        GotoIf(WordNotEqual(LoadJSArrayLength(a()), to_.value()), &runtime);
        kind = EnsureArrayPushable(LoadMap(a()), &runtime);
        GotoIf(IsElementsKindGreaterThan(kind, HOLEY_SMI_ELEMENTS),
               &object_push_pre);

        BuildAppendJSArray(HOLEY_SMI_ELEMENTS, a(), k_value, &runtime);
        Goto(&after_work);
      }

      BIND(&object_push_pre);
      {
        Branch(IsElementsKindGreaterThan(kind, HOLEY_ELEMENTS), &double_push,
               &object_push);
      }

      BIND(&object_push);
      {
        BuildAppendJSArray(HOLEY_ELEMENTS, a(), k_value, &runtime);
        Goto(&after_work);
      }

      BIND(&double_push);
      {
        BuildAppendJSArray(HOLEY_DOUBLE_ELEMENTS, a(), k_value, &runtime);
        Goto(&after_work);
      }

      BIND(&runtime);
      {
        // 1. Perform ? CreateDataPropertyOrThrow(A, ToString(to), kValue).
        CallRuntime(Runtime::kCreateDataProperty, context(), a(), to_.value(),
                    k_value);
        Goto(&after_work);
      }

      BIND(&after_work);
      {
        // 2. Increase to by 1.
        to_.Bind(NumberInc(to_.value()));
        Goto(&false_continue);
      }
    }
    BIND(&false_continue);
    return a();
  }

  void ArrayBuiltinsAssembler::MapResultGenerator() {
    GenerateArraySpeciesCreate(len_);
  }

  void ArrayBuiltinsAssembler::TypedArrayMapResultGenerator() {
    // 6. Let A be ? TypedArraySpeciesCreate(O, len).
    TNode<JSTypedArray> original_array = CAST(o());
    TNode<Smi> length = CAST(len_);
    const char* method_name = "%TypedArray%.prototype.map";

    TypedArrayBuiltinsAssembler typedarray_asm(state());
    TNode<JSTypedArray> a = typedarray_asm.SpeciesCreateByLength(
        context(), original_array, length, method_name);
    // In the Spec and our current implementation, the length check is already
    // performed in TypedArraySpeciesCreate.
    CSA_ASSERT(
        this,
        SmiLessThanOrEqual(
            CAST(len_), CAST(LoadObjectField(a, JSTypedArray::kLengthOffset))));
    fast_typed_array_target_ =
        Word32Equal(LoadInstanceType(LoadElements(original_array)),
                    LoadInstanceType(LoadElements(a)));
    a_.Bind(a);
  }

  Node* ArrayBuiltinsAssembler::SpecCompliantMapProcessor(Node* k_value,
                                                          Node* k) {
    //  i. Let kValue be ? Get(O, Pk). Performed by the caller of
    //  SpecCompliantMapProcessor.
    // ii. Let mapped_value be ? Call(callbackfn, T, kValue, k, O).
    Node* mapped_value = CallJS(CodeFactory::Call(isolate()), context(),
                                callbackfn(), this_arg(), k_value, k, o());

    // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mapped_value).
    CallRuntime(Runtime::kCreateDataProperty, context(), a(), k, mapped_value);
    return a();
  }

  Node* ArrayBuiltinsAssembler::FastMapProcessor(Node* k_value, Node* k) {
    //  i. Let kValue be ? Get(O, Pk). Performed by the caller of
    //  FastMapProcessor.
    // ii. Let mapped_value be ? Call(callbackfn, T, kValue, k, O).
    Node* mapped_value = CallJS(CodeFactory::Call(isolate()), context(),
                                callbackfn(), this_arg(), k_value, k, o());

    // mode is SMI_PARAMETERS because k has tagged representation.
    ParameterMode mode = SMI_PARAMETERS;
    Label runtime(this), finished(this);
    Label transition_pre(this), transition_smi_fast(this),
        transition_smi_double(this);
    Label array_not_smi(this), array_fast(this), array_double(this);

    Node* kind = LoadMapElementsKind(LoadMap(a()));
    Node* elements = LoadElements(a());
    GotoIf(IsElementsKindGreaterThan(kind, HOLEY_SMI_ELEMENTS), &array_not_smi);
    TryStoreArrayElement(HOLEY_SMI_ELEMENTS, mode, &transition_pre, elements, k,
                         mapped_value);
    Goto(&finished);

    BIND(&transition_pre);
    {
      // array is smi. Value is either tagged or a heap number.
      CSA_ASSERT(this, TaggedIsNotSmi(mapped_value));
      GotoIf(IsHeapNumberMap(LoadMap(mapped_value)), &transition_smi_double);
      Goto(&transition_smi_fast);
    }

    BIND(&array_not_smi);
    {
      Branch(IsElementsKindGreaterThan(kind, HOLEY_ELEMENTS), &array_double,
             &array_fast);
    }

    BIND(&transition_smi_fast);
    {
      // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mapped_value).
      Node* const native_context = LoadNativeContext(context());
      Node* const fast_map = LoadContextElement(
          native_context, Context::JS_ARRAY_HOLEY_ELEMENTS_MAP_INDEX);

      // Since this transition is only a map change, just do it right here.
      // Since a() doesn't have an allocation site, it's safe to do the
      // map store directly, otherwise I'd call TransitionElementsKind().
      StoreMap(a(), fast_map);
      Goto(&array_fast);
    }

    BIND(&array_fast);
    {
      TryStoreArrayElement(HOLEY_ELEMENTS, mode, &runtime, elements, k,
                           mapped_value);
      Goto(&finished);
    }

    BIND(&transition_smi_double);
    {
      // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mapped_value).
      Node* const native_context = LoadNativeContext(context());
      Node* const double_map = LoadContextElement(
          native_context, Context::JS_ARRAY_HOLEY_DOUBLE_ELEMENTS_MAP_INDEX);
      CallStub(CodeFactory::TransitionElementsKind(
                   isolate(), HOLEY_SMI_ELEMENTS, HOLEY_DOUBLE_ELEMENTS, true),
               context(), a(), double_map);
      Goto(&array_double);
    }

    BIND(&array_double);
    {
      // TODO(mvstanton): If we use a variable for elements and bind it
      // appropriately, we can avoid an extra load of elements by binding the
      // value only after a transition from smi to double.
      elements = LoadElements(a());
      // If the mapped_value isn't a number, this will bail out to the runtime
      // to make the transition.
      TryStoreArrayElement(HOLEY_DOUBLE_ELEMENTS, mode, &runtime, elements, k,
                           mapped_value);
      Goto(&finished);
    }

    BIND(&runtime);
    {
      // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mapped_value).
      CallRuntime(Runtime::kCreateDataProperty, context(), a(), k,
                  mapped_value);
      Goto(&finished);
    }

    BIND(&finished);
    return a();
  }

  // See tc39.github.io/ecma262/#sec-%typedarray%.prototype.map.
  Node* ArrayBuiltinsAssembler::TypedArrayMapProcessor(Node* k_value, Node* k) {
    // 8. c. Let mapped_value be ? Call(callbackfn, T, « kValue, k, O »).
    Node* mapped_value = CallJS(CodeFactory::Call(isolate()), context(),
                                callbackfn(), this_arg(), k_value, k, o());
    Label fast(this), slow(this), done(this), detached(this, Label::kDeferred);

    // 8. d. Perform ? Set(A, Pk, mapped_value, true).
    // Since we know that A is a TypedArray, this always ends up in
    // #sec-integer-indexed-exotic-objects-set-p-v-receiver and then
    // tc39.github.io/ecma262/#sec-integerindexedelementset .
    Branch(fast_typed_array_target_, &fast, &slow);

    BIND(&fast);
    // #sec-integerindexedelementset
    // 5. If arrayTypeName is "BigUint64Array" or "BigInt64Array", let
    // numValue be ? ToBigInt(v).
    // 6. Otherwise, let numValue be ? ToNumber(value).
    Node* num_value;
    if (source_elements_kind_ == BIGINT64_ELEMENTS ||
        source_elements_kind_ == BIGUINT64_ELEMENTS) {
      num_value = ToBigInt(context(), mapped_value);
    } else {
      num_value = ToNumber_Inline(context(), mapped_value);
    }
    // The only way how this can bailout is because of a detached buffer.
    EmitElementStore(a(), k, num_value, false, source_elements_kind_,
                     KeyedAccessStoreMode::STANDARD_STORE, &detached,
                     context());
    Goto(&done);

    BIND(&slow);
    CallRuntime(Runtime::kSetProperty, context(), a(), k, mapped_value,
                SmiConstant(LanguageMode::kStrict));
    Goto(&done);

    BIND(&detached);
    // tc39.github.io/ecma262/#sec-integerindexedelementset
    // 8. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
    ThrowTypeError(context_, MessageTemplate::kDetachedOperation, name_);

    BIND(&done);
    return a();
  }

  void ArrayBuiltinsAssembler::NullPostLoopAction() {}

  void ArrayBuiltinsAssembler::ReturnFromBuiltin(Node* value) {
    if (argc_ == nullptr) {
      Return(value);
    } else {
      // argc_ doesn't include the receiver, so it has to be added back in
      // manually.
      PopAndReturn(IntPtrAdd(argc_, IntPtrConstant(1)), value);
    }
  }

  void ArrayBuiltinsAssembler::InitIteratingArrayBuiltinBody(
      TNode<Context> context, TNode<Object> receiver, Node* callbackfn,
      Node* this_arg, TNode<IntPtrT> argc) {
    context_ = context;
    receiver_ = receiver;
    callbackfn_ = callbackfn;
    this_arg_ = this_arg;
    argc_ = argc;
  }

  void ArrayBuiltinsAssembler::GenerateIteratingArrayBuiltinBody(
      const char* name, const BuiltinResultGenerator& generator,
      const CallResultProcessor& processor, const PostLoopAction& action,
      const Callable& slow_case_continuation,
      MissingPropertyMode missing_property_mode, ForEachDirection direction) {
    Label non_array(this), array_changes(this, {&k_, &a_, &to_});

    // TODO(danno): Seriously? Do we really need to throw the exact error
    // message on null and undefined so that the webkit tests pass?
    Label throw_null_undefined_exception(this, Label::kDeferred);
    GotoIf(IsNullOrUndefined(receiver()), &throw_null_undefined_exception);

    // By the book: taken directly from the ECMAScript 2015 specification

    // 1. Let O be ToObject(this value).
    // 2. ReturnIfAbrupt(O)
    o_ = ToObject(context(), receiver());

    // 3. Let len be ToLength(Get(O, "length")).
    // 4. ReturnIfAbrupt(len).
    TVARIABLE(Number, merged_length);
    Label has_length(this, &merged_length), not_js_array(this);
    GotoIf(DoesntHaveInstanceType(o(), JS_ARRAY_TYPE), &not_js_array);
    merged_length = LoadJSArrayLength(CAST(o()));
    Goto(&has_length);
    BIND(&not_js_array);
    Node* len_property =
        GetProperty(context(), o(), isolate()->factory()->length_string());
    merged_length = ToLength_Inline(context(), len_property);
    Goto(&has_length);
    BIND(&has_length);
    len_ = merged_length.value();

    // 5. If IsCallable(callbackfn) is false, throw a TypeError exception.
    Label type_exception(this, Label::kDeferred);
    Label done(this);
    GotoIf(TaggedIsSmi(callbackfn()), &type_exception);
    Branch(IsCallableMap(LoadMap(callbackfn())), &done, &type_exception);

    BIND(&throw_null_undefined_exception);
    ThrowTypeError(context(), MessageTemplate::kCalledOnNullOrUndefined, name);

    BIND(&type_exception);
    ThrowTypeError(context(), MessageTemplate::kCalledNonCallable,
                   callbackfn());

    BIND(&done);

    // 6. If thisArg was supplied, let T be thisArg; else let T be undefined.
    // [Already done by the arguments adapter]

    if (direction == ForEachDirection::kForward) {
      // 7. Let k be 0.
      k_.Bind(SmiConstant(0));
    } else {
      k_.Bind(NumberDec(len()));
    }

    generator(this);

    HandleFastElements(processor, action, &fully_spec_compliant_, direction,
                       missing_property_mode);

    BIND(&fully_spec_compliant_);

    Node* result =
        CallStub(slow_case_continuation, context(), receiver(), callbackfn(),
                 this_arg(), a_.value(), o(), k_.value(), len(), to_.value());
    ReturnFromBuiltin(result);
  }

  void ArrayBuiltinsAssembler::InitIteratingArrayBuiltinLoopContinuation(
      TNode<Context> context, TNode<Object> receiver, Node* callbackfn,
      Node* this_arg, Node* a, TNode<JSReceiver> o, Node* initial_k,
      TNode<Number> len, Node* to) {
    context_ = context;
    this_arg_ = this_arg;
    callbackfn_ = callbackfn;
    a_.Bind(a);
    k_.Bind(initial_k);
    o_ = o;
    len_ = len;
    to_.Bind(to);
  }

  void ArrayBuiltinsAssembler::GenerateIteratingTypedArrayBuiltinBody(
      const char* name, const BuiltinResultGenerator& generator,
      const CallResultProcessor& processor, const PostLoopAction& action,
      ForEachDirection direction) {
    name_ = name;

    // ValidateTypedArray: tc39.github.io/ecma262/#sec-validatetypedarray

    Label throw_not_typed_array(this, Label::kDeferred),
        throw_detached(this, Label::kDeferred);

    GotoIf(TaggedIsSmi(receiver_), &throw_not_typed_array);
    GotoIfNot(HasInstanceType(CAST(receiver_), JS_TYPED_ARRAY_TYPE),
              &throw_not_typed_array);

    TNode<JSTypedArray> typed_array = CAST(receiver_);
    o_ = typed_array;

    Node* array_buffer =
        LoadObjectField(typed_array, JSTypedArray::kBufferOffset);
    GotoIf(IsDetachedBuffer(array_buffer), &throw_detached);

    len_ = LoadObjectField<Smi>(typed_array, JSTypedArray::kLengthOffset);

    Label throw_not_callable(this, Label::kDeferred);
    Label distinguish_types(this);
    GotoIf(TaggedIsSmi(callbackfn_), &throw_not_callable);
    Branch(IsCallableMap(LoadMap(callbackfn_)), &distinguish_types,
           &throw_not_callable);

    BIND(&throw_not_typed_array);
    ThrowTypeError(context_, MessageTemplate::kNotTypedArray);

    BIND(&throw_detached);
    ThrowTypeError(context_, MessageTemplate::kDetachedOperation, name_);

    BIND(&throw_not_callable);
    ThrowTypeError(context_, MessageTemplate::kCalledNonCallable, callbackfn_);

    Label unexpected_instance_type(this);
    BIND(&unexpected_instance_type);
    Unreachable();

    std::vector<int32_t> instance_types = {
#define INSTANCE_TYPE(Type, type, TYPE, ctype, size) FIXED_##TYPE##_ARRAY_TYPE,
        TYPED_ARRAYS(INSTANCE_TYPE)
#undef INSTANCE_TYPE
    };
    std::vector<Label> labels;
    for (size_t i = 0; i < instance_types.size(); ++i) {
      labels.push_back(Label(this));
    }
    std::vector<Label*> label_ptrs;
    for (Label& label : labels) {
      label_ptrs.push_back(&label);
    }

    BIND(&distinguish_types);

    generator(this);

    if (direction == ForEachDirection::kForward) {
      k_.Bind(SmiConstant(0));
    } else {
      k_.Bind(NumberDec(len()));
    }
    Node* instance_type = LoadInstanceType(LoadElements(typed_array));
    Switch(instance_type, &unexpected_instance_type, instance_types.data(),
           label_ptrs.data(), labels.size());

    for (size_t i = 0; i < labels.size(); ++i) {
      BIND(&labels[i]);
      Label done(this);
      source_elements_kind_ = ElementsKindForInstanceType(
          static_cast<InstanceType>(instance_types[i]));
      // TODO(tebbi): Silently cancelling the loop on buffer detachment is a
      // spec violation. Should go to &throw_detached and throw a TypeError
      // instead.
      VisitAllTypedArrayElements(array_buffer, processor, &done, direction,
                                 typed_array);
      Goto(&done);
      // No exception, return success
      BIND(&done);
      action(this);
      ReturnFromBuiltin(a_.value());
    }
  }

  void ArrayBuiltinsAssembler::GenerateIteratingArrayBuiltinLoopContinuation(
      const CallResultProcessor& processor, const PostLoopAction& action,
      MissingPropertyMode missing_property_mode, ForEachDirection direction) {
    Label loop(this, {&k_, &a_, &to_});
    Label after_loop(this);
    Goto(&loop);
    BIND(&loop);
    {
      if (direction == ForEachDirection::kForward) {
        // 8. Repeat, while k < len
        GotoIfNumberGreaterThanOrEqual(k(), len_, &after_loop);
      } else {
        // OR
        // 10. Repeat, while k >= 0
        GotoIfNumberGreaterThanOrEqual(SmiConstant(-1), k(), &after_loop);
      }

      Label done_element(this, &to_);
      // a. Let Pk be ToString(k).
      // We never have to perform a ToString conversion as the above guards
      // guarantee that we have a positive {k} which also is a valid array
      // index in the range [0, 2^32-1).
      CSA_ASSERT(this, IsNumberArrayIndex(k()));

      if (missing_property_mode == MissingPropertyMode::kSkip) {
        // b. Let kPresent be HasProperty(O, Pk).
        // c. ReturnIfAbrupt(kPresent).
        TNode<Oddball> k_present =
            HasProperty(o(), k(), context(), kHasProperty);

        // d. If kPresent is true, then
        GotoIf(IsFalse(k_present), &done_element);
      }

      // i. Let kValue be Get(O, Pk).
      // ii. ReturnIfAbrupt(kValue).
      Node* k_value = GetProperty(context(), o(), k());

      // iii. Let funcResult be Call(callbackfn, T, «kValue, k, O»).
      // iv. ReturnIfAbrupt(funcResult).
      a_.Bind(processor(this, k_value, k()));
      Goto(&done_element);

      BIND(&done_element);

      if (direction == ForEachDirection::kForward) {
        // e. Increase k by 1.
        k_.Bind(NumberInc(k()));
      } else {
        // e. Decrease k by 1.
        k_.Bind(NumberDec(k()));
      }
      Goto(&loop);
    }
    BIND(&after_loop);

    action(this);
    Return(a_.value());
  }

  ElementsKind ArrayBuiltinsAssembler::ElementsKindForInstanceType(
      InstanceType type) {
    switch (type) {
#define INSTANCE_TYPE_TO_ELEMENTS_KIND(Type, type, TYPE, ctype, size) \
  case FIXED_##TYPE##_ARRAY_TYPE:                                     \
    return TYPE##_ELEMENTS;

      TYPED_ARRAYS(INSTANCE_TYPE_TO_ELEMENTS_KIND)
#undef INSTANCE_TYPE_TO_ELEMENTS_KIND

      default:
        UNREACHABLE();
    }
  }

  void ArrayBuiltinsAssembler::VisitAllTypedArrayElements(
      Node* array_buffer, const CallResultProcessor& processor, Label* detached,
      ForEachDirection direction, TNode<JSTypedArray> typed_array) {
    VariableList list({&a_, &k_, &to_}, zone());

    FastLoopBody body = [&](Node* index) {
      GotoIf(IsDetachedBuffer(array_buffer), detached);
      Node* elements = LoadElements(typed_array);
      Node* base_ptr =
          LoadObjectField(elements, FixedTypedArrayBase::kBasePointerOffset);
      Node* external_ptr =
          LoadObjectField(elements, FixedTypedArrayBase::kExternalPointerOffset,
                          MachineType::Pointer());
      Node* data_ptr = IntPtrAdd(BitcastTaggedToWord(base_ptr), external_ptr);
      Node* value = LoadFixedTypedArrayElementAsTagged(
          data_ptr, index, source_elements_kind_, SMI_PARAMETERS);
      k_.Bind(index);
      a_.Bind(processor(this, value, index));
    };
    Node* start = SmiConstant(0);
    Node* end = len_;
    IndexAdvanceMode advance_mode = IndexAdvanceMode::kPost;
    int incr = 1;
    if (direction == ForEachDirection::kReverse) {
      std::swap(start, end);
      advance_mode = IndexAdvanceMode::kPre;
      incr = -1;
    }
    BuildFastLoop(list, start, end, body, incr, ParameterMode::SMI_PARAMETERS,
                  advance_mode);
  }

  void ArrayBuiltinsAssembler::VisitAllFastElementsOneKind(
      ElementsKind kind, const CallResultProcessor& processor,
      Label* array_changed, ParameterMode mode, ForEachDirection direction,
      MissingPropertyMode missing_property_mode, TNode<Smi> length) {
    Comment("begin VisitAllFastElementsOneKind");
    // We only use this kind of processing if the no-elements protector is
    // in place at the start. We'll continue checking during array iteration.
    CSA_ASSERT(this, Word32BinaryNot(IsNoElementsProtectorCellInvalid()));
    VARIABLE(original_map, MachineRepresentation::kTagged);
    original_map.Bind(LoadMap(o()));
    VariableList list({&original_map, &a_, &k_, &to_}, zone());
    Node* start = IntPtrOrSmiConstant(0, mode);
    Node* end = TaggedToParameter(length, mode);
    IndexAdvanceMode advance_mode = direction == ForEachDirection::kReverse
                                        ? IndexAdvanceMode::kPre
                                        : IndexAdvanceMode::kPost;
    if (direction == ForEachDirection::kReverse) std::swap(start, end);
    BuildFastLoop(
        list, start, end,
        [=, &original_map](Node* index) {
          k_.Bind(ParameterToTagged(index, mode));
          Label one_element_done(this), hole_element(this),
              process_element(this);

          // Check if o's map has changed during the callback. If so, we have to
          // fall back to the slower spec implementation for the rest of the
          // iteration.
          Node* o_map = LoadMap(o());
          GotoIf(WordNotEqual(o_map, original_map.value()), array_changed);

          TNode<JSArray> o_array = CAST(o());
          // Check if o's length has changed during the callback and if the
          // index is now out of range of the new length.
          GotoIf(SmiGreaterThanOrEqual(CAST(k_.value()),
                                       CAST(LoadJSArrayLength(o_array))),
                 array_changed);

          // Re-load the elements array. If may have been resized.
          Node* elements = LoadElements(o_array);

          // Fast case: load the element directly from the elements FixedArray
          // and call the callback if the element is not the hole.
          DCHECK(kind == PACKED_ELEMENTS || kind == PACKED_DOUBLE_ELEMENTS);
          int base_size = kind == PACKED_ELEMENTS
                              ? FixedArray::kHeaderSize
                              : (FixedArray::kHeaderSize - kHeapObjectTag);
          Node* offset = ElementOffsetFromIndex(index, kind, mode, base_size);
          VARIABLE(value, MachineRepresentation::kTagged);
          if (kind == PACKED_ELEMENTS) {
            value.Bind(LoadObjectField(elements, offset));
            GotoIf(WordEqual(value.value(), TheHoleConstant()), &hole_element);
          } else {
            Node* double_value =
                LoadDoubleWithHoleCheck(elements, offset, &hole_element);
            value.Bind(AllocateHeapNumberWithValue(double_value));
          }
          Goto(&process_element);

          BIND(&hole_element);
          if (missing_property_mode == MissingPropertyMode::kSkip) {
            // The NoElementsProtectorCell could go invalid during callbacks.
            Branch(IsNoElementsProtectorCellInvalid(), array_changed,
                   &one_element_done);
          } else {
            value.Bind(UndefinedConstant());
            Goto(&process_element);
          }
          BIND(&process_element);
          {
            a_.Bind(processor(this, value.value(), k()));
            Goto(&one_element_done);
          }
          BIND(&one_element_done);
        },
        1, mode, advance_mode);
    Comment("end VisitAllFastElementsOneKind");
  }

  void ArrayBuiltinsAssembler::HandleFastElements(
      const CallResultProcessor& processor, const PostLoopAction& action,
      Label* slow, ForEachDirection direction,
      MissingPropertyMode missing_property_mode) {
    Label switch_on_elements_kind(this), fast_elements(this),
        maybe_double_elements(this), fast_double_elements(this);

    Comment("begin HandleFastElements");
    // Non-smi lengths must use the slow path.
    GotoIf(TaggedIsNotSmi(len()), slow);

    BranchIfFastJSArray(o(), context(),
                        &switch_on_elements_kind, slow);

    BIND(&switch_on_elements_kind);
    TNode<Smi> smi_len = CAST(len());
    // Select by ElementsKind
    Node* o_map = LoadMap(o());
    Node* bit_field2 = LoadMapBitField2(o_map);
    Node* kind = DecodeWord32<Map::ElementsKindBits>(bit_field2);
    Branch(IsElementsKindGreaterThan(kind, HOLEY_ELEMENTS),
           &maybe_double_elements, &fast_elements);

    ParameterMode mode = OptimalParameterMode();
    BIND(&fast_elements);
    {
      VisitAllFastElementsOneKind(PACKED_ELEMENTS, processor, slow, mode,
                                  direction, missing_property_mode, smi_len);

      action(this);

      // No exception, return success
      ReturnFromBuiltin(a_.value());
    }

    BIND(&maybe_double_elements);
    Branch(IsElementsKindGreaterThan(kind, HOLEY_DOUBLE_ELEMENTS), slow,
           &fast_double_elements);

    BIND(&fast_double_elements);
    {
      VisitAllFastElementsOneKind(PACKED_DOUBLE_ELEMENTS, processor, slow, mode,
                                  direction, missing_property_mode, smi_len);

      action(this);

      // No exception, return success
      ReturnFromBuiltin(a_.value());
    }
  }

  // Perform ArraySpeciesCreate (ES6 #sec-arrayspeciescreate).
  // This version is specialized to create a zero length array
  // of the elements kind of the input array.
  void ArrayBuiltinsAssembler::GenerateArraySpeciesCreate() {
    Label runtime(this, Label::kDeferred), done(this);

    TNode<Smi> len = SmiConstant(0);
    TNode<Map> original_map = LoadMap(o());
    GotoIfNot(
        InstanceTypeEqual(LoadMapInstanceType(original_map), JS_ARRAY_TYPE),
        &runtime);

    GotoIfNot(IsPrototypeInitialArrayPrototype(context(), original_map),
              &runtime);

    Node* species_protector = ArraySpeciesProtectorConstant();
    Node* value =
        LoadObjectField(species_protector, PropertyCell::kValueOffset);
    TNode<Smi> const protector_invalid =
        SmiConstant(Isolate::kProtectorInvalid);
    GotoIf(WordEqual(value, protector_invalid), &runtime);

    // Respect the ElementsKind of the input array.
    TNode<Int32T> elements_kind = LoadMapElementsKind(original_map);
    GotoIfNot(IsFastElementsKind(elements_kind), &runtime);
    TNode<Context> native_context = LoadNativeContext(context());
    TNode<Map> array_map =
        LoadJSArrayElementsMap(elements_kind, native_context);
    TNode<JSArray> array =
        CAST(AllocateJSArray(GetInitialFastElementsKind(), array_map, len, len,
                             nullptr, CodeStubAssembler::SMI_PARAMETERS));
    a_.Bind(array);

    Goto(&done);

    BIND(&runtime);
    {
      // 5. Let A be ? ArraySpeciesCreate(O, len).
      Node* constructor =
          CallRuntime(Runtime::kArraySpeciesConstructor, context(), o());
      a_.Bind(ConstructJS(CodeFactory::Construct(isolate()), context(),
                          constructor, len));
      Goto(&fully_spec_compliant_);
    }

    BIND(&done);
  }

  // Perform ArraySpeciesCreate (ES6 #sec-arrayspeciescreate).
  void ArrayBuiltinsAssembler::GenerateArraySpeciesCreate(TNode<Number> len) {
    Label runtime(this, Label::kDeferred), done(this);

    Node* const original_map = LoadMap(o());
    GotoIfNot(
        InstanceTypeEqual(LoadMapInstanceType(original_map), JS_ARRAY_TYPE),
        &runtime);

    GotoIfNot(IsPrototypeInitialArrayPrototype(context(), original_map),
              &runtime);

    Node* species_protector = ArraySpeciesProtectorConstant();
    Node* value =
        LoadObjectField(species_protector, PropertyCell::kValueOffset);
    Node* const protector_invalid = SmiConstant(Isolate::kProtectorInvalid);
    GotoIf(WordEqual(value, protector_invalid), &runtime);

    GotoIfNot(TaggedIsPositiveSmi(len), &runtime);
    GotoIf(
        SmiAbove(CAST(len), SmiConstant(JSArray::kInitialMaxFastElementArray)),
        &runtime);

    // We need to be conservative and start with holey because the builtins
    // that create output arrays aren't guaranteed to be called for every
    // element in the input array (maybe the callback deletes an element).
    const ElementsKind elements_kind =
        GetHoleyElementsKind(GetInitialFastElementsKind());
    TNode<Context> native_context = LoadNativeContext(context());
    TNode<Map> array_map =
        LoadJSArrayElementsMap(elements_kind, native_context);
    a_.Bind(AllocateJSArray(PACKED_SMI_ELEMENTS, array_map, len, len, nullptr,
                            CodeStubAssembler::SMI_PARAMETERS));

    Goto(&done);

    BIND(&runtime);
    {
      // 5. Let A be ? ArraySpeciesCreate(O, len).
      Node* constructor =
          CallRuntime(Runtime::kArraySpeciesConstructor, context(), o());
      a_.Bind(ConstructJS(CodeFactory::Construct(isolate()), context(),
                          constructor, len));
      Goto(&fully_spec_compliant_);
    }

    BIND(&done);
  }

TF_BUILTIN(ArrayPrototypePop, CodeStubAssembler) {
  TNode<Int32T> argc =
      UncheckedCast<Int32T>(Parameter(BuiltinDescriptor::kArgumentsCount));
  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
  CSA_ASSERT(this, IsUndefined(Parameter(BuiltinDescriptor::kNewTarget)));

  CodeStubArguments args(this, ChangeInt32ToIntPtr(argc));
  TNode<Object> receiver = args.GetReceiver();

  Label runtime(this, Label::kDeferred);
  Label fast(this);

  // Only pop in this stub if
  // 1) the array has fast elements
  // 2) the length is writable,
  // 3) the elements backing store isn't copy-on-write,
  // 4) we aren't supposed to shrink the backing store.

  // 1) Check that the array has fast elements.
  BranchIfFastJSArray(receiver, context, &fast, &runtime);

  BIND(&fast);
  {
    TNode<JSArray> array_receiver = CAST(receiver);
    CSA_ASSERT(this, TaggedIsPositiveSmi(LoadJSArrayLength(array_receiver)));
    Node* length =
        LoadAndUntagObjectField(array_receiver, JSArray::kLengthOffset);
    Label return_undefined(this), fast_elements(this);
    GotoIf(IntPtrEqual(length, IntPtrConstant(0)), &return_undefined);

    // 2) Ensure that the length is writable.
    EnsureArrayLengthWritable(LoadMap(array_receiver), &runtime);

    // 3) Check that the elements backing store isn't copy-on-write.
    Node* elements = LoadElements(array_receiver);
    GotoIf(WordEqual(LoadMap(elements),
                     LoadRoot(Heap::kFixedCOWArrayMapRootIndex)),
           &runtime);

    Node* new_length = IntPtrSub(length, IntPtrConstant(1));

    // 4) Check that we're not supposed to shrink the backing store, as
    //    implemented in elements.cc:ElementsAccessorBase::SetLengthImpl.
    Node* capacity = SmiUntag(LoadFixedArrayBaseLength(elements));
    GotoIf(IntPtrLessThan(
               IntPtrAdd(IntPtrAdd(new_length, new_length),
                         IntPtrConstant(JSObject::kMinAddedElementsCapacity)),
               capacity),
           &runtime);

    StoreObjectFieldNoWriteBarrier(array_receiver, JSArray::kLengthOffset,
                                   SmiTag(new_length));

    Node* elements_kind = LoadMapElementsKind(LoadMap(array_receiver));
    GotoIf(Int32LessThanOrEqual(elements_kind,
                                Int32Constant(TERMINAL_FAST_ELEMENTS_KIND)),
           &fast_elements);

    Node* value = LoadFixedDoubleArrayElement(
        elements, new_length, MachineType::Float64(), 0, INTPTR_PARAMETERS,
        &return_undefined);

    int32_t header_size = FixedDoubleArray::kHeaderSize - kHeapObjectTag;
    Node* offset = ElementOffsetFromIndex(new_length, HOLEY_DOUBLE_ELEMENTS,
                                          INTPTR_PARAMETERS, header_size);
    if (Is64()) {
      Node* double_hole = Int64Constant(kHoleNanInt64);
      StoreNoWriteBarrier(MachineRepresentation::kWord64, elements, offset,
                          double_hole);
    } else {
      STATIC_ASSERT(kHoleNanLower32 == kHoleNanUpper32);
      Node* double_hole = Int32Constant(kHoleNanLower32);
      StoreNoWriteBarrier(MachineRepresentation::kWord32, elements, offset,
                          double_hole);
      StoreNoWriteBarrier(MachineRepresentation::kWord32, elements,
                          IntPtrAdd(offset, IntPtrConstant(kPointerSize)),
                          double_hole);
    }
    args.PopAndReturn(AllocateHeapNumberWithValue(value));

    BIND(&fast_elements);
    {
      Node* value = LoadFixedArrayElement(elements, new_length);
      StoreFixedArrayElement(elements, new_length, TheHoleConstant());
      GotoIf(WordEqual(value, TheHoleConstant()), &return_undefined);
      args.PopAndReturn(value);
    }

    BIND(&return_undefined);
    { args.PopAndReturn(UndefinedConstant()); }
  }

  BIND(&runtime);
  {
    Node* target = LoadFromFrame(StandardFrameConstants::kFunctionOffset,
                                 MachineType::TaggedPointer());
    TailCallStub(CodeFactory::ArrayPop(isolate()), context, target,
                 UndefinedConstant(), argc);
  }
}

TF_BUILTIN(ArrayPrototypePush, CodeStubAssembler) {
  TVARIABLE(IntPtrT, arg_index);
  Label default_label(this, &arg_index);
  Label smi_transition(this);
  Label object_push_pre(this);
  Label object_push(this, &arg_index);
  Label double_push(this, &arg_index);
  Label double_transition(this);
  Label runtime(this, Label::kDeferred);

  // TODO(ishell): use constants from Descriptor once the JSFunction linkage
  // arguments are reordered.
  TNode<Int32T> argc =
      UncheckedCast<Int32T>(Parameter(BuiltinDescriptor::kArgumentsCount));
  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
  CSA_ASSERT(this, IsUndefined(Parameter(BuiltinDescriptor::kNewTarget)));

  CodeStubArguments args(this, ChangeInt32ToIntPtr(argc));
  TNode<Object> receiver = args.GetReceiver();
  TNode<JSArray> array_receiver;
  Node* kind = nullptr;

  Label fast(this);
  BranchIfFastJSArray(receiver, context, &fast, &runtime);

  BIND(&fast);
  {
    array_receiver = CAST(receiver);
    arg_index = IntPtrConstant(0);
    kind = EnsureArrayPushable(LoadMap(array_receiver), &runtime);
    GotoIf(IsElementsKindGreaterThan(kind, HOLEY_SMI_ELEMENTS),
           &object_push_pre);

    Node* new_length = BuildAppendJSArray(PACKED_SMI_ELEMENTS, array_receiver,
                                          &args, &arg_index, &smi_transition);
    args.PopAndReturn(new_length);
  }

  // If the argument is not a smi, then use a heavyweight SetProperty to
  // transition the array for only the single next element. If the argument is
  // a smi, the failure is due to some other reason and we should fall back on
  // the most generic implementation for the rest of the array.
  BIND(&smi_transition);
  {
    Node* arg = args.AtIndex(arg_index.value());
    GotoIf(TaggedIsSmi(arg), &default_label);
    Node* length = LoadJSArrayLength(array_receiver);
    // TODO(danno): Use the KeyedStoreGeneric stub here when possible,
    // calling into the runtime to do the elements transition is overkill.
    CallRuntime(Runtime::kSetProperty, context, array_receiver, length, arg,
                SmiConstant(LanguageMode::kStrict));
    Increment(&arg_index);
    // The runtime SetProperty call could have converted the array to dictionary
    // mode, which must be detected to abort the fast-path.
    Node* map = LoadMap(array_receiver);
    Node* bit_field2 = LoadMapBitField2(map);
    Node* kind = DecodeWord32<Map::ElementsKindBits>(bit_field2);
    GotoIf(Word32Equal(kind, Int32Constant(DICTIONARY_ELEMENTS)),
           &default_label);

    GotoIfNotNumber(arg, &object_push);
    Goto(&double_push);
  }

  BIND(&object_push_pre);
  {
    Branch(IsElementsKindGreaterThan(kind, HOLEY_ELEMENTS), &double_push,
           &object_push);
  }

  BIND(&object_push);
  {
    Node* new_length = BuildAppendJSArray(PACKED_ELEMENTS, array_receiver,
                                          &args, &arg_index, &default_label);
    args.PopAndReturn(new_length);
  }

  BIND(&double_push);
  {
    Node* new_length =
        BuildAppendJSArray(PACKED_DOUBLE_ELEMENTS, array_receiver, &args,
                           &arg_index, &double_transition);
    args.PopAndReturn(new_length);
  }

  // If the argument is not a double, then use a heavyweight SetProperty to
  // transition the array for only the single next element. If the argument is
  // a double, the failure is due to some other reason and we should fall back
  // on the most generic implementation for the rest of the array.
  BIND(&double_transition);
  {
    Node* arg = args.AtIndex(arg_index.value());
    GotoIfNumber(arg, &default_label);
    Node* length = LoadJSArrayLength(array_receiver);
    // TODO(danno): Use the KeyedStoreGeneric stub here when possible,
    // calling into the runtime to do the elements transition is overkill.
    CallRuntime(Runtime::kSetProperty, context, array_receiver, length, arg,
                SmiConstant(LanguageMode::kStrict));
    Increment(&arg_index);
    // The runtime SetProperty call could have converted the array to dictionary
    // mode, which must be detected to abort the fast-path.
    Node* map = LoadMap(array_receiver);
    Node* bit_field2 = LoadMapBitField2(map);
    Node* kind = DecodeWord32<Map::ElementsKindBits>(bit_field2);
    GotoIf(Word32Equal(kind, Int32Constant(DICTIONARY_ELEMENTS)),
           &default_label);
    Goto(&object_push);
  }

  // Fallback that stores un-processed arguments using the full, heavyweight
  // SetProperty machinery.
  BIND(&default_label);
  {
    args.ForEach(
        [this, array_receiver, context](Node* arg) {
          Node* length = LoadJSArrayLength(array_receiver);
          CallRuntime(Runtime::kSetProperty, context, array_receiver, length,
                      arg, SmiConstant(LanguageMode::kStrict));
        },
        arg_index.value());
    args.PopAndReturn(LoadJSArrayLength(array_receiver));
  }

  BIND(&runtime);
  {
    Node* target = LoadFromFrame(StandardFrameConstants::kFunctionOffset,
                                 MachineType::TaggedPointer());
    TailCallStub(CodeFactory::ArrayPush(isolate()), context, target,
                 UndefinedConstant(), argc);
  }
}

class ArrayPrototypeSliceCodeStubAssembler : public CodeStubAssembler {
 public:
  explicit ArrayPrototypeSliceCodeStubAssembler(
      compiler::CodeAssemblerState* state)
      : CodeStubAssembler(state) {}

  Node* HandleFastSlice(TNode<Context> context, Node* array, Node* from,
                        Node* count, Label* slow) {
    VARIABLE(result, MachineRepresentation::kTagged);
    Label done(this);

    GotoIf(TaggedIsNotSmi(from), slow);
    GotoIf(TaggedIsNotSmi(count), slow);

    Label try_fast_arguments(this), try_simple_slice(this);

    Node* map = LoadMap(array);
    GotoIfNot(IsJSArrayMap(map), &try_fast_arguments);

    // Check prototype chain if receiver does not have packed elements
    GotoIfNot(IsPrototypeInitialArrayPrototype(context, map), slow);

    GotoIf(IsNoElementsProtectorCellInvalid(), slow);

    GotoIf(IsArraySpeciesProtectorCellInvalid(), slow);

    // Bailout if receiver has slow elements.
    Node* elements_kind = LoadMapElementsKind(map);
    GotoIfNot(IsFastElementsKind(elements_kind), &try_simple_slice);

    // Make sure that the length hasn't been changed by side-effect.
    Node* array_length = LoadJSArrayLength(array);
    GotoIf(TaggedIsNotSmi(array_length), slow);
    GotoIf(SmiAbove(SmiAdd(CAST(from), CAST(count)), CAST(array_length)), slow);

    CSA_ASSERT(this, SmiGreaterThanOrEqual(CAST(from), SmiConstant(0)));

    result.Bind(CallStub(CodeFactory::ExtractFastJSArray(isolate()), context,
                         array, from, count));
    Goto(&done);

    BIND(&try_fast_arguments);

    Node* const native_context = LoadNativeContext(context);
    Node* const fast_aliasted_arguments_map = LoadContextElement(
        native_context, Context::FAST_ALIASED_ARGUMENTS_MAP_INDEX);
    GotoIf(WordNotEqual(map, fast_aliasted_arguments_map), &try_simple_slice);

    Node* sloppy_elements = LoadElements(array);
    TNode<Smi> sloppy_elements_length =
        LoadFixedArrayBaseLength(sloppy_elements);
    TNode<Smi> parameter_map_length =
        SmiSub(sloppy_elements_length,
               SmiConstant(SloppyArgumentsElements::kParameterMapStart));
    VARIABLE(index_out, MachineType::PointerRepresentation());

    int max_fast_elements =
        (kMaxRegularHeapObjectSize - FixedArray::kHeaderSize - JSArray::kSize -
         AllocationMemento::kSize) /
        kPointerSize;
    GotoIf(SmiAboveOrEqual(CAST(count), SmiConstant(max_fast_elements)),
           &try_simple_slice);

    GotoIf(SmiLessThan(CAST(from), SmiConstant(0)), slow);

    TNode<Smi> end = SmiAdd(CAST(from), CAST(count));

    Node* unmapped_elements = LoadFixedArrayElement(
        sloppy_elements, SloppyArgumentsElements::kArgumentsIndex);
    TNode<Smi> unmapped_elements_length =
        LoadFixedArrayBaseLength(unmapped_elements);

    GotoIf(SmiAbove(end, unmapped_elements_length), slow);

    Node* array_map = LoadJSArrayElementsMap(HOLEY_ELEMENTS, native_context);
    result.Bind(AllocateJSArray(HOLEY_ELEMENTS, array_map, count, count,
                                nullptr, SMI_PARAMETERS));

    index_out.Bind(IntPtrConstant(0));
    Node* result_elements = LoadElements(result.value());
    TNode<Smi> from_mapped = SmiMin(parameter_map_length, CAST(from));
    TNode<Smi> to = SmiMin(parameter_map_length, end);
    Node* arguments_context = LoadFixedArrayElement(
        sloppy_elements, SloppyArgumentsElements::kContextIndex);
    VariableList var_list({&index_out}, zone());
    BuildFastLoop(
        var_list, from_mapped, to,
        [this, result_elements, arguments_context, sloppy_elements,
         unmapped_elements, &index_out](Node* current) {
          Node* context_index = LoadFixedArrayElement(
              sloppy_elements, current,
              kPointerSize * SloppyArgumentsElements::kParameterMapStart,
              SMI_PARAMETERS);
          Label is_the_hole(this), done(this);
          GotoIf(IsTheHole(context_index), &is_the_hole);
          Node* mapped_argument =
              LoadContextElement(arguments_context, SmiUntag(context_index));
          StoreFixedArrayElement(result_elements, index_out.value(),
                                 mapped_argument, SKIP_WRITE_BARRIER);
          Goto(&done);
          BIND(&is_the_hole);
          Node* argument = LoadFixedArrayElement(unmapped_elements, current, 0,
                                                 SMI_PARAMETERS);
          StoreFixedArrayElement(result_elements, index_out.value(), argument,
                                 SKIP_WRITE_BARRIER);
          Goto(&done);
          BIND(&done);
          index_out.Bind(IntPtrAdd(index_out.value(), IntPtrConstant(1)));
        },
        1, SMI_PARAMETERS, IndexAdvanceMode::kPost);

    TNode<Smi> unmapped_from =
        SmiMin(SmiMax(parameter_map_length, CAST(from)), end);

    BuildFastLoop(
        var_list, unmapped_from, end,
        [this, unmapped_elements, result_elements, &index_out](Node* current) {
          Node* argument = LoadFixedArrayElement(unmapped_elements, current, 0,
                                                 SMI_PARAMETERS);
          StoreFixedArrayElement(result_elements, index_out.value(), argument,
                                 SKIP_WRITE_BARRIER);
          index_out.Bind(IntPtrAdd(index_out.value(), IntPtrConstant(1)));
        },
        1, SMI_PARAMETERS, IndexAdvanceMode::kPost);

    Goto(&done);

    BIND(&try_simple_slice);
    Node* simple_result = CallRuntime(Runtime::kTrySliceSimpleNonFastElements,
                                      context, array, from, count);
    GotoIfNumber(simple_result, slow);
    result.Bind(simple_result);

    Goto(&done);

    BIND(&done);
    return result.value();
  }

  void CopyOneElement(TNode<Context> context, Node* o, Node* a, Node* p_k,
                      Variable& n) {
    // b. Let kPresent be HasProperty(O, Pk).
    // c. ReturnIfAbrupt(kPresent).
    TNode<Oddball> k_present = HasProperty(o, p_k, context, kHasProperty);

    // d. If kPresent is true, then
    Label done_element(this);
    GotoIf(IsFalse(k_present), &done_element);

    // i. Let kValue be Get(O, Pk).
    // ii. ReturnIfAbrupt(kValue).
    Node* k_value = GetProperty(context, o, p_k);

    // iii. Let status be CreateDataPropertyOrThrow(A, ToString(n), kValue).
    // iv. ReturnIfAbrupt(status).
    CallRuntime(Runtime::kCreateDataProperty, context, a, n.value(), k_value);

    Goto(&done_element);
    BIND(&done_element);
  }
};

TF_BUILTIN(ArrayPrototypeSlice, ArrayPrototypeSliceCodeStubAssembler) {
  Node* const argc =
      ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
  Label slow(this, Label::kDeferred), fast_elements_kind(this);

  CodeStubArguments args(this, argc);
  TNode<Object> receiver = args.GetReceiver();

  TVARIABLE(JSReceiver, o);
  VARIABLE(len, MachineRepresentation::kTagged);
  Label length_done(this), generic_length(this), check_arguments_length(this),
      load_arguments_length(this);

  GotoIf(TaggedIsSmi(receiver), &generic_length);
  GotoIfNot(IsJSArray(CAST(receiver)), &check_arguments_length);

  TNode<JSArray> array_receiver = CAST(receiver);
  o = array_receiver;
  len.Bind(LoadJSArrayLength(array_receiver));

  // Check for the array clone case. There can be no arguments to slice, the
  // array prototype chain must be intact and have no elements, the array has to
  // have fast elements.
  GotoIf(WordNotEqual(argc, IntPtrConstant(0)), &length_done);

  Label clone(this);
  BranchIfFastJSArrayForCopy(receiver, context, &clone, &length_done);
  BIND(&clone);

  args.PopAndReturn(
      CallStub(CodeFactory::CloneFastJSArray(isolate()), context, receiver));

  BIND(&check_arguments_length);

  Node* map = LoadMap(array_receiver);
  Node* native_context = LoadNativeContext(context);
  GotoIfContextElementEqual(map, native_context,
                            Context::FAST_ALIASED_ARGUMENTS_MAP_INDEX,
                            &load_arguments_length);
  GotoIfContextElementEqual(map, native_context,
                            Context::SLOW_ALIASED_ARGUMENTS_MAP_INDEX,
                            &load_arguments_length);
  GotoIfContextElementEqual(map, native_context,
                            Context::STRICT_ARGUMENTS_MAP_INDEX,
                            &load_arguments_length);
  GotoIfContextElementEqual(map, native_context,
                            Context::SLOPPY_ARGUMENTS_MAP_INDEX,
                            &load_arguments_length);

  Goto(&generic_length);

  BIND(&load_arguments_length);
  Node* arguments_length =
      LoadObjectField(array_receiver, JSArgumentsObject::kLengthOffset);
  GotoIf(TaggedIsNotSmi(arguments_length), &generic_length);
  o = CAST(receiver);
  len.Bind(arguments_length);
  Goto(&length_done);

  BIND(&generic_length);
  // 1. Let O be ToObject(this value).
  // 2. ReturnIfAbrupt(O).
  o = ToObject(context, receiver);

  // 3. Let len be ToLength(Get(O, "length")).
  // 4. ReturnIfAbrupt(len).
  len.Bind(ToLength_Inline(
      context,
      GetProperty(context, o.value(), isolate()->factory()->length_string())));
  Goto(&length_done);

  BIND(&length_done);

  // 5. Let relativeStart be ToInteger(start).
  // 6. ReturnIfAbrupt(relativeStart).
  TNode<Object> arg0 = args.GetOptionalArgumentValue(0, SmiConstant(0));
  Node* relative_start = ToInteger_Inline(context, arg0);

  // 7. If relativeStart < 0, let k be max((len + relativeStart),0);
  //    else let k be min(relativeStart, len.value()).
  VARIABLE(k, MachineRepresentation::kTagged);
  Label relative_start_positive(this), relative_start_done(this);
  GotoIfNumberGreaterThanOrEqual(relative_start, SmiConstant(0),
                                 &relative_start_positive);
  k.Bind(NumberMax(NumberAdd(len.value(), relative_start), NumberConstant(0)));
  Goto(&relative_start_done);
  BIND(&relative_start_positive);
  k.Bind(NumberMin(relative_start, len.value()));
  Goto(&relative_start_done);
  BIND(&relative_start_done);

  // 8. If end is undefined, let relativeEnd be len;
  //    else let relativeEnd be ToInteger(end).
  // 9. ReturnIfAbrupt(relativeEnd).
  TNode<Object> end = args.GetOptionalArgumentValue(1, UndefinedConstant());
  Label end_undefined(this), end_done(this);
  VARIABLE(relative_end, MachineRepresentation::kTagged);
  GotoIf(WordEqual(end, UndefinedConstant()), &end_undefined);
  relative_end.Bind(ToInteger_Inline(context, end));
  Goto(&end_done);
  BIND(&end_undefined);
  relative_end.Bind(len.value());
  Goto(&end_done);
  BIND(&end_done);

  // 10. If relativeEnd < 0, let final be max((len + relativeEnd),0);
  //     else let final be min(relativeEnd, len).
  VARIABLE(final, MachineRepresentation::kTagged);
  Label relative_end_positive(this), relative_end_done(this);
  GotoIfNumberGreaterThanOrEqual(relative_end.value(), NumberConstant(0),
                                 &relative_end_positive);
  final.Bind(NumberMax(NumberAdd(len.value(), relative_end.value()),
                       NumberConstant(0)));
  Goto(&relative_end_done);
  BIND(&relative_end_positive);
  final.Bind(NumberMin(relative_end.value(), len.value()));
  Goto(&relative_end_done);
  BIND(&relative_end_done);

  // 11. Let count be max(final – k, 0).
  Node* count =
      NumberMax(NumberSub(final.value(), k.value()), NumberConstant(0));

  // Handle FAST_ELEMENTS
  Label non_fast(this);
  Node* fast_result =
      HandleFastSlice(context, o.value(), k.value(), count, &non_fast);
  args.PopAndReturn(fast_result);

  // 12. Let A be ArraySpeciesCreate(O, count).
  // 13. ReturnIfAbrupt(A).
  BIND(&non_fast);

  Node* constructor =
      CallRuntime(Runtime::kArraySpeciesConstructor, context, o.value());
  Node* a = ConstructJS(CodeFactory::Construct(isolate()), context, constructor,
                        count);

  // 14. Let n be 0.
  VARIABLE(n, MachineRepresentation::kTagged);
  n.Bind(SmiConstant(0));

  Label loop(this, {&k, &n});
  Label after_loop(this);
  Goto(&loop);
  BIND(&loop);
  {
    // 15. Repeat, while k < final
    GotoIfNumberGreaterThanOrEqual(k.value(), final.value(), &after_loop);

    Node* p_k = k.value();  //  ToString(context, k.value()) is no-op

    CopyOneElement(context, o.value(), a, p_k, n);

    // e. Increase k by 1.
    k.Bind(NumberInc(k.value()));

    // f. Increase n by 1.
    n.Bind(NumberInc(n.value()));

    Goto(&loop);
  }

  BIND(&after_loop);

  // 16. Let setStatus be Set(A, "length", n, true).
  // 17. ReturnIfAbrupt(setStatus).
  CallRuntime(Runtime::kSetProperty, context, a,
              HeapConstant(isolate()->factory()->length_string()), n.value(),
              SmiConstant(static_cast<int>(LanguageMode::kStrict)));

  args.PopAndReturn(a);
}

TF_BUILTIN(ArrayPrototypeShift, CodeStubAssembler) {
  TNode<Int32T> argc =
      UncheckedCast<Int32T>(Parameter(BuiltinDescriptor::kArgumentsCount));
  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
  CSA_ASSERT(this, IsUndefined(Parameter(BuiltinDescriptor::kNewTarget)));

  CodeStubArguments args(this, ChangeInt32ToIntPtr(argc));
  TNode<Object> receiver = args.GetReceiver();

  Label runtime(this, Label::kDeferred);
  Label fast(this);

  // Only shift in this stub if
  // 1) the array has fast elements
  // 2) the length is writable,
  // 3) the elements backing store isn't copy-on-write,
  // 4) we aren't supposed to shrink the backing store,
  // 5) we aren't supposed to left-trim the backing store.

  // 1) Check that the array has fast elements.
  BranchIfFastJSArray(receiver, context, &fast, &runtime);

  BIND(&fast);
  {
    TNode<JSArray> array_receiver = CAST(receiver);
    CSA_ASSERT(this, TaggedIsPositiveSmi(LoadJSArrayLength(array_receiver)));
    Node* length =
        LoadAndUntagObjectField(array_receiver, JSArray::kLengthOffset);
    Label return_undefined(this), fast_elements_tagged(this),
        fast_elements_smi(this);
    GotoIf(IntPtrEqual(length, IntPtrConstant(0)), &return_undefined);

    // 2) Ensure that the length is writable.
    EnsureArrayLengthWritable(LoadMap(array_receiver), &runtime);

    // 3) Check that the elements backing store isn't copy-on-write.
    Node* elements = LoadElements(array_receiver);
    GotoIf(WordEqual(LoadMap(elements),
                     LoadRoot(Heap::kFixedCOWArrayMapRootIndex)),
           &runtime);

    Node* new_length = IntPtrSub(length, IntPtrConstant(1));

    // 4) Check that we're not supposed to right-trim the backing store, as
    //    implemented in elements.cc:ElementsAccessorBase::SetLengthImpl.
    Node* capacity = SmiUntag(LoadFixedArrayBaseLength(elements));
    GotoIf(IntPtrLessThan(
               IntPtrAdd(IntPtrAdd(new_length, new_length),
                         IntPtrConstant(JSObject::kMinAddedElementsCapacity)),
               capacity),
           &runtime);

    // 5) Check that we're not supposed to left-trim the backing store, as
    //    implemented in elements.cc:FastElementsAccessor::MoveElements.
    GotoIf(IntPtrGreaterThan(new_length,
                             IntPtrConstant(JSArray::kMaxCopyElements)),
           &runtime);

    StoreObjectFieldNoWriteBarrier(array_receiver, JSArray::kLengthOffset,
                                   SmiTag(new_length));

    Node* elements_kind = LoadMapElementsKind(LoadMap(array_receiver));
    GotoIf(
        Int32LessThanOrEqual(elements_kind, Int32Constant(HOLEY_SMI_ELEMENTS)),
        &fast_elements_smi);
    GotoIf(Int32LessThanOrEqual(elements_kind, Int32Constant(HOLEY_ELEMENTS)),
           &fast_elements_tagged);

    // Fast double elements kind:
    {
      CSA_ASSERT(this,
                 Int32LessThanOrEqual(elements_kind,
                                      Int32Constant(HOLEY_DOUBLE_ELEMENTS)));

      VARIABLE(result, MachineRepresentation::kTagged, UndefinedConstant());

      Label move_elements(this);
      result.Bind(AllocateHeapNumberWithValue(LoadFixedDoubleArrayElement(
          elements, IntPtrConstant(0), MachineType::Float64(), 0,
          INTPTR_PARAMETERS, &move_elements)));
      Goto(&move_elements);
      BIND(&move_elements);

      int32_t header_size = FixedDoubleArray::kHeaderSize - kHeapObjectTag;
      Node* memmove =
          ExternalConstant(ExternalReference::libc_memmove_function());
      Node* start = IntPtrAdd(
          BitcastTaggedToWord(elements),
          ElementOffsetFromIndex(IntPtrConstant(0), HOLEY_DOUBLE_ELEMENTS,
                                 INTPTR_PARAMETERS, header_size));
      CallCFunction3(MachineType::AnyTagged(), MachineType::Pointer(),
                     MachineType::Pointer(), MachineType::UintPtr(), memmove,
                     start, IntPtrAdd(start, IntPtrConstant(kDoubleSize)),
                     IntPtrMul(new_length, IntPtrConstant(kDoubleSize)));
      Node* offset = ElementOffsetFromIndex(new_length, HOLEY_DOUBLE_ELEMENTS,
                                            INTPTR_PARAMETERS, header_size);
      if (Is64()) {
        Node* double_hole = Int64Constant(kHoleNanInt64);
        StoreNoWriteBarrier(MachineRepresentation::kWord64, elements, offset,
                            double_hole);
      } else {
        STATIC_ASSERT(kHoleNanLower32 == kHoleNanUpper32);
        Node* double_hole = Int32Constant(kHoleNanLower32);
        StoreNoWriteBarrier(MachineRepresentation::kWord32, elements, offset,
                            double_hole);
        StoreNoWriteBarrier(MachineRepresentation::kWord32, elements,
                            IntPtrAdd(offset, IntPtrConstant(kPointerSize)),
                            double_hole);
      }
      args.PopAndReturn(result.value());
    }

    BIND(&fast_elements_tagged);
    {
      Node* value = LoadFixedArrayElement(elements, 0);
      BuildFastLoop(IntPtrConstant(0), new_length,
                    [&](Node* index) {
                      StoreFixedArrayElement(
                          elements, index,
                          LoadFixedArrayElement(
                              elements, IntPtrAdd(index, IntPtrConstant(1))));
                    },
                    1, ParameterMode::INTPTR_PARAMETERS,
                    IndexAdvanceMode::kPost);
      StoreFixedArrayElement(elements, new_length, TheHoleConstant());
      GotoIf(WordEqual(value, TheHoleConstant()), &return_undefined);
      args.PopAndReturn(value);
    }

    BIND(&fast_elements_smi);
    {
      Node* value = LoadFixedArrayElement(elements, 0);
      BuildFastLoop(IntPtrConstant(0), new_length,
                    [&](Node* index) {
                      StoreFixedArrayElement(
                          elements, index,
                          LoadFixedArrayElement(
                              elements, IntPtrAdd(index, IntPtrConstant(1))),
                          SKIP_WRITE_BARRIER);
                    },
                    1, ParameterMode::INTPTR_PARAMETERS,
                    IndexAdvanceMode::kPost);
      StoreFixedArrayElement(elements, new_length, TheHoleConstant());
      GotoIf(WordEqual(value, TheHoleConstant()), &return_undefined);
      args.PopAndReturn(value);
    }

    BIND(&return_undefined);
    { args.PopAndReturn(UndefinedConstant()); }
  }

  BIND(&runtime);
  {
    Node* target = LoadFromFrame(StandardFrameConstants::kFunctionOffset,
                                 MachineType::TaggedPointer());
    TailCallStub(CodeFactory::ArrayShift(isolate()), context, target,
                 UndefinedConstant(), argc);
  }
}

TF_BUILTIN(ExtractFastJSArray, ArrayBuiltinsAssembler) {
  ParameterMode mode = OptimalParameterMode();
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  Node* array = Parameter(Descriptor::kSource);
  Node* begin = TaggedToParameter(Parameter(Descriptor::kBegin), mode);
  Node* count = TaggedToParameter(Parameter(Descriptor::kCount), mode);

  CSA_ASSERT(this, IsJSArray(array));
  CSA_ASSERT(this, Word32BinaryNot(IsNoElementsProtectorCellInvalid()));

  Return(ExtractFastJSArray(context, array, begin, count, mode));
}

TF_BUILTIN(CloneFastJSArray, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  Node* array = Parameter(Descriptor::kSource);

  CSA_ASSERT(this, IsJSArray(array));
  CSA_ASSERT(this, Word32BinaryNot(IsNoElementsProtectorCellInvalid()));

  ParameterMode mode = OptimalParameterMode();
  Return(CloneFastJSArray(context, array, mode));
}

TF_BUILTIN(ArrayFindLoopContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* this_arg = Parameter(Descriptor::kThisArg);
  Node* array = Parameter(Descriptor::kArray);
  TNode<JSReceiver> object = CAST(Parameter(Descriptor::kObject));
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));
  Node* to = Parameter(Descriptor::kTo);

  InitIteratingArrayBuiltinLoopContinuation(context, receiver, callbackfn,
                                            this_arg, array, object, initial_k,
                                            len, to);

  GenerateIteratingArrayBuiltinLoopContinuation(
      &ArrayBuiltinsAssembler::FindProcessor,
      &ArrayBuiltinsAssembler::NullPostLoopAction,
      MissingPropertyMode::kUseUndefined, ForEachDirection::kForward);
}

// Continuation that is called after an eager deoptimization from TF (ex. the
// array changes during iteration).
TF_BUILTIN(ArrayFindLoopEagerDeoptContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* this_arg = Parameter(Descriptor::kThisArg);
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));

  Return(CallBuiltin(Builtins::kArrayFindLoopContinuation, context, receiver,
                     callbackfn, this_arg, UndefinedConstant(), receiver,
                     initial_k, len, UndefinedConstant()));
}

// Continuation that is called after a lazy deoptimization from TF (ex. the
// callback function is no longer callable).
TF_BUILTIN(ArrayFindLoopLazyDeoptContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* this_arg = Parameter(Descriptor::kThisArg);
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));

  Return(CallBuiltin(Builtins::kArrayFindLoopContinuation, context, receiver,
                     callbackfn, this_arg, UndefinedConstant(), receiver,
                     initial_k, len, UndefinedConstant()));
}

// Continuation that is called after a lazy deoptimization from TF that happens
// right after the callback and it's returned value must be handled before
// iteration continues.
TF_BUILTIN(ArrayFindLoopAfterCallbackLazyDeoptContinuation,
           ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* this_arg = Parameter(Descriptor::kThisArg);
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));
  Node* found_value = Parameter(Descriptor::kFoundValue);
  Node* is_found = Parameter(Descriptor::kIsFound);

  // This custom lazy deopt point is right after the callback. find() needs
  // to pick up at the next step, which is returning the element if the callback
  // value is truthy.  Otherwise, continue the search by calling the
  // continuation.
  Label if_true(this), if_false(this);
  BranchIfToBooleanIsTrue(is_found, &if_true, &if_false);
  BIND(&if_true);
  Return(found_value);
  BIND(&if_false);
  Return(CallBuiltin(Builtins::kArrayFindLoopContinuation, context, receiver,
                     callbackfn, this_arg, UndefinedConstant(), receiver,
                     initial_k, len, UndefinedConstant()));
}

// ES #sec-get-%typedarray%.prototype.find
TF_BUILTIN(ArrayPrototypeFind, ArrayBuiltinsAssembler) {
  TNode<IntPtrT> argc =
      ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
  CodeStubArguments args(this, argc);
  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
  TNode<Object> receiver = args.GetReceiver();
  Node* callbackfn = args.GetOptionalArgumentValue(0);
  Node* this_arg = args.GetOptionalArgumentValue(1);

  InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc);

  GenerateIteratingArrayBuiltinBody(
      "Array.prototype.find", &ArrayBuiltinsAssembler::FindResultGenerator,
      &ArrayBuiltinsAssembler::FindProcessor,
      &ArrayBuiltinsAssembler::NullPostLoopAction,
      Builtins::CallableFor(isolate(), Builtins::kArrayFindLoopContinuation),
      MissingPropertyMode::kUseUndefined, ForEachDirection::kForward);
}

TF_BUILTIN(ArrayFindIndexLoopContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* this_arg = Parameter(Descriptor::kThisArg);
  Node* array = Parameter(Descriptor::kArray);
  TNode<JSReceiver> object = CAST(Parameter(Descriptor::kObject));
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));
  Node* to = Parameter(Descriptor::kTo);

  InitIteratingArrayBuiltinLoopContinuation(context, receiver, callbackfn,
                                            this_arg, array, object, initial_k,
                                            len, to);

  GenerateIteratingArrayBuiltinLoopContinuation(
      &ArrayBuiltinsAssembler::FindIndexProcessor,
      &ArrayBuiltinsAssembler::NullPostLoopAction,
      MissingPropertyMode::kUseUndefined, ForEachDirection::kForward);
}

TF_BUILTIN(ArrayFindIndexLoopEagerDeoptContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* this_arg = Parameter(Descriptor::kThisArg);
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));

  Return(CallBuiltin(Builtins::kArrayFindIndexLoopContinuation, context,
                     receiver, callbackfn, this_arg, SmiConstant(-1), receiver,
                     initial_k, len, UndefinedConstant()));
}

TF_BUILTIN(ArrayFindIndexLoopLazyDeoptContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* this_arg = Parameter(Descriptor::kThisArg);
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));

  Return(CallBuiltin(Builtins::kArrayFindIndexLoopContinuation, context,
                     receiver, callbackfn, this_arg, SmiConstant(-1), receiver,
                     initial_k, len, UndefinedConstant()));
}

TF_BUILTIN(ArrayFindIndexLoopAfterCallbackLazyDeoptContinuation,
           ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* this_arg = Parameter(Descriptor::kThisArg);
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));
  Node* found_value = Parameter(Descriptor::kFoundValue);
  Node* is_found = Parameter(Descriptor::kIsFound);

  // This custom lazy deopt point is right after the callback. find() needs
  // to pick up at the next step, which is returning the element if the callback
  // value is truthy.  Otherwise, continue the search by calling the
  // continuation.
  Label if_true(this), if_false(this);
  BranchIfToBooleanIsTrue(is_found, &if_true, &if_false);
  BIND(&if_true);
  Return(found_value);
  BIND(&if_false);
  Return(CallBuiltin(Builtins::kArrayFindIndexLoopContinuation, context,
                     receiver, callbackfn, this_arg, SmiConstant(-1), receiver,
                     initial_k, len, UndefinedConstant()));
}

// ES #sec-get-%typedarray%.prototype.findIndex
TF_BUILTIN(ArrayPrototypeFindIndex, ArrayBuiltinsAssembler) {
  TNode<IntPtrT> argc =
      ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
  CodeStubArguments args(this, argc);
  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
  TNode<Object> receiver = args.GetReceiver();
  Node* callbackfn = args.GetOptionalArgumentValue(0);
  Node* this_arg = args.GetOptionalArgumentValue(1);

  InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc);

  GenerateIteratingArrayBuiltinBody(
      "Array.prototype.findIndex",
      &ArrayBuiltinsAssembler::FindIndexResultGenerator,
      &ArrayBuiltinsAssembler::FindIndexProcessor,
      &ArrayBuiltinsAssembler::NullPostLoopAction,
      Builtins::CallableFor(isolate(),
                            Builtins::kArrayFindIndexLoopContinuation),
      MissingPropertyMode::kUseUndefined, ForEachDirection::kForward);
}

class ArrayPopulatorAssembler : public CodeStubAssembler {
 public:
  explicit ArrayPopulatorAssembler(compiler::CodeAssemblerState* state)
      : CodeStubAssembler(state) {}

  TNode<Object> ConstructArrayLike(TNode<Context> context,
                                   TNode<Object> receiver) {
    TVARIABLE(Object, array);
    Label is_constructor(this), is_not_constructor(this), done(this);
    GotoIf(TaggedIsSmi(receiver), &is_not_constructor);
    Branch(IsConstructor(CAST(receiver)), &is_constructor, &is_not_constructor);

    BIND(&is_constructor);
    {
      array = CAST(
          ConstructJS(CodeFactory::Construct(isolate()), context, receiver));
      Goto(&done);
    }

    BIND(&is_not_constructor);
    {
      Label allocate_js_array(this);

      TNode<Map> array_map = CAST(LoadContextElement(
          context, Context::JS_ARRAY_PACKED_SMI_ELEMENTS_MAP_INDEX));

      array = CAST(AllocateJSArray(PACKED_SMI_ELEMENTS, array_map,
                                   SmiConstant(0), SmiConstant(0), nullptr,
                                   ParameterMode::SMI_PARAMETERS));
      Goto(&done);
    }

    BIND(&done);
    return array.value();
  }

  TNode<Object> ConstructArrayLike(TNode<Context> context,
                                   TNode<Object> receiver,
                                   TNode<Number> length) {
    TVARIABLE(Object, array);
    Label is_constructor(this), is_not_constructor(this), done(this);
    CSA_ASSERT(this, IsNumberNormalized(length));
    GotoIf(TaggedIsSmi(receiver), &is_not_constructor);
    Branch(IsConstructor(CAST(receiver)), &is_constructor, &is_not_constructor);

    BIND(&is_constructor);
    {
      array = CAST(ConstructJS(CodeFactory::Construct(isolate()), context,
                               receiver, length));
      Goto(&done);
    }

    BIND(&is_not_constructor);
    {
      Label allocate_js_array(this);

      Label next(this), runtime(this, Label::kDeferred);
      TNode<Smi> limit = SmiConstant(JSArray::kInitialMaxFastElementArray);
      CSA_ASSERT_BRANCH(this, [=](Label* ok, Label* not_ok) {
        BranchIfNumberRelationalComparison(Operation::kGreaterThanOrEqual,
                                           length, SmiConstant(0), ok, not_ok);
      });
      // This check also transitively covers the case where length is too big
      // to be representable by a SMI and so is not usable with
      // AllocateJSArray.
      BranchIfNumberRelationalComparison(Operation::kGreaterThanOrEqual, length,
                                         limit, &runtime, &next);

      BIND(&runtime);
      {
        TNode<Context> native_context = LoadNativeContext(context);
        TNode<JSFunction> array_function = CAST(
            LoadContextElement(native_context, Context::ARRAY_FUNCTION_INDEX));
        array = CallRuntime(Runtime::kNewArray, context, array_function, length,
                            array_function, UndefinedConstant());
        Goto(&done);
      }

      BIND(&next);
      CSA_ASSERT(this, TaggedIsSmi(length));

      TNode<Map> array_map = CAST(LoadContextElement(
          context, Context::JS_ARRAY_PACKED_SMI_ELEMENTS_MAP_INDEX));

      // TODO(delphick): Consider using
      // AllocateUninitializedJSArrayWithElements to avoid initializing an
      // array and then writing over it.
      array = CAST(AllocateJSArray(PACKED_SMI_ELEMENTS, array_map, length,
                                   SmiConstant(0), nullptr,
                                   ParameterMode::SMI_PARAMETERS));
      Goto(&done);
    }

    BIND(&done);
    return array.value();
  }

  void GenerateSetLength(TNode<Context> context, TNode<Object> array,
                         TNode<Number> length) {
    Label fast(this), runtime(this), done(this);
    // There's no need to set the length, if
    // 1) the array is a fast JS array and
    // 2) the new length is equal to the old length.
    // as the set is not observable. Otherwise fall back to the run-time.

    // 1) Check that the array has fast elements.
    // TODO(delphick): Consider changing this since it does an an unnecessary
    // check for SMIs.
    // TODO(delphick): Also we could hoist this to after the array construction
    // and copy the args into array in the same way as the Array constructor.
    BranchIfFastJSArray(array, context, &fast, &runtime);

    BIND(&fast);
    {
      TNode<JSArray> fast_array = CAST(array);

      TNode<Smi> length_smi = CAST(length);
      TNode<Smi> old_length = LoadFastJSArrayLength(fast_array);
      CSA_ASSERT(this, TaggedIsPositiveSmi(old_length));

      // 2) If the created array's length matches the required length, then
      //    there's nothing else to do. Otherwise use the runtime to set the
      //    property as that will insert holes into excess elements or shrink
      //    the backing store as appropriate.
      Branch(SmiNotEqual(length_smi, old_length), &runtime, &done);
    }

    BIND(&runtime);
    {
      CallRuntime(Runtime::kSetProperty, context, static_cast<Node*>(array),
                  CodeStubAssembler::LengthStringConstant(), length,
                  SmiConstant(LanguageMode::kStrict));
      Goto(&done);
    }

    BIND(&done);
  }
};

// ES #sec-array.from
TF_BUILTIN(ArrayFrom, ArrayPopulatorAssembler) {
  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
  TNode<Int32T> argc =
      UncheckedCast<Int32T>(Parameter(BuiltinDescriptor::kArgumentsCount));

  CodeStubArguments args(this, ChangeInt32ToIntPtr(argc));

  TNode<Object> map_function = args.GetOptionalArgumentValue(1);

  // If map_function is not undefined, then ensure it's callable else throw.
  {
    Label no_error(this), error(this);
    GotoIf(IsUndefined(map_function), &no_error);
    GotoIf(TaggedIsSmi(map_function), &error);
    Branch(IsCallable(CAST(map_function)), &no_error, &error);

    BIND(&error);
    ThrowTypeError(context, MessageTemplate::kCalledNonCallable, map_function);

    BIND(&no_error);
  }

  Label iterable(this), not_iterable(this), finished(this), if_exception(this);

  TNode<Object> this_arg = args.GetOptionalArgumentValue(2);
  TNode<Object> items = args.GetOptionalArgumentValue(0);
  // The spec doesn't require ToObject to be called directly on the iterable
  // branch, but it's part of GetMethod that is in the spec.
  TNode<JSReceiver> array_like = ToObject(context, items);

  TVARIABLE(Object, array);
  TVARIABLE(Number, length);

  // Determine whether items[Symbol.iterator] is defined:
  IteratorBuiltinsAssembler iterator_assembler(state());
  Node* iterator_method =
      iterator_assembler.GetIteratorMethod(context, array_like);
  Branch(IsNullOrUndefined(iterator_method), &not_iterable, &iterable);

  BIND(&iterable);
  {
    TVARIABLE(Number, index, SmiConstant(0));
    TVARIABLE(Object, var_exception);
    Label loop(this, &index), loop_done(this),
        on_exception(this, Label::kDeferred),
        index_overflow(this, Label::kDeferred);

    // Check that the method is callable.
    {
      Label get_method_not_callable(this, Label::kDeferred), next(this);
      GotoIf(TaggedIsSmi(iterator_method), &get_method_not_callable);
      GotoIfNot(IsCallable(CAST(iterator_method)), &get_method_not_callable);
      Goto(&next);

      BIND(&get_method_not_callable);
      ThrowTypeError(context, MessageTemplate::kCalledNonCallable,
                     iterator_method);

      BIND(&next);
    }

    // Construct the output array with empty length.
    array = ConstructArrayLike(context, args.GetReceiver());

    // Actually get the iterator and throw if the iterator method does not yield
    // one.
    IteratorRecord iterator_record =
        iterator_assembler.GetIterator(context, items, iterator_method);

    TNode<Context> native_context = LoadNativeContext(context);
    TNode<Object> fast_iterator_result_map =
        LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX);

    Goto(&loop);

    BIND(&loop);
    {
      // Loop while iterator is not done.
      TNode<Object> next = CAST(iterator_assembler.IteratorStep(
          context, iterator_record, &loop_done, fast_iterator_result_map));
      TVARIABLE(Object, value,
                CAST(iterator_assembler.IteratorValue(
                    context, next, fast_iterator_result_map)));

      // If a map_function is supplied then call it (using this_arg as
      // receiver), on the value returned from the iterator. Exceptions are
      // caught so the iterator can be closed.
      {
        Label next(this);
        GotoIf(IsUndefined(map_function), &next);

        CSA_ASSERT(this, IsCallable(CAST(map_function)));
        Node* v = CallJS(CodeFactory::Call(isolate()), context, map_function,
                         this_arg, value.value(), index.value());
        GotoIfException(v, &on_exception, &var_exception);
        value = CAST(v);
        Goto(&next);
        BIND(&next);
      }

      // Store the result in the output object (catching any exceptions so the
      // iterator can be closed).
      Node* define_status =
          CallRuntime(Runtime::kCreateDataProperty, context, array.value(),
                      index.value(), value.value());
      GotoIfException(define_status, &on_exception, &var_exception);

      index = NumberInc(index.value());

      // The spec requires that we throw an exception if index reaches 2^53-1,
      // but an empty loop would take >100 days to do this many iterations. To
      // actually run for that long would require an iterator that never set
      // done to true and a target array which somehow never ran out of memory,
      // e.g. a proxy that discarded the values. Ignoring this case just means
      // we would repeatedly call CreateDataProperty with index = 2^53.
      CSA_ASSERT_BRANCH(this, [&](Label* ok, Label* not_ok) {
        BranchIfNumberRelationalComparison(Operation::kLessThan, index.value(),
                                           NumberConstant(kMaxSafeInteger), ok,
                                           not_ok);
      });
      Goto(&loop);
    }

    BIND(&loop_done);
    {
      length = index;
      Goto(&finished);
    }

    BIND(&on_exception);
    {
      // Close the iterator, rethrowing either the passed exception or
      // exceptions thrown during the close.
      iterator_assembler.IteratorCloseOnException(context, iterator_record,
                                                  &var_exception);
    }
  }

  BIND(&not_iterable);
  {
    // Treat array_like as an array and try to get its length.
    length = ToLength_Inline(
        context, GetProperty(context, array_like, factory()->length_string()));

    // Construct an array using the receiver as constructor with the same length
    // as the input array.
    array = ConstructArrayLike(context, args.GetReceiver(), length.value());

    TVARIABLE(Number, index, SmiConstant(0));

    // TODO(ishell): remove <Object, Object>
    GotoIf(WordEqual<Object, Object>(length.value(), SmiConstant(0)),
           &finished);

    // Loop from 0 to length-1.
    {
      Label loop(this, &index);
      Goto(&loop);
      BIND(&loop);
      TVARIABLE(Object, value);

      value = GetProperty(context, array_like, index.value());

      // If a map_function is supplied then call it (using this_arg as
      // receiver), on the value retrieved from the array.
      {
        Label next(this);
        GotoIf(IsUndefined(map_function), &next);

        CSA_ASSERT(this, IsCallable(CAST(map_function)));
        value = CAST(CallJS(CodeFactory::Call(isolate()), context, map_function,
                            this_arg, value.value(), index.value()));
        Goto(&next);
        BIND(&next);
      }

      // Store the result in the output object.
      CallRuntime(Runtime::kCreateDataProperty, context, array.value(),
                  index.value(), value.value());
      index = NumberInc(index.value());
      BranchIfNumberRelationalComparison(Operation::kLessThan, index.value(),
                                         length.value(), &loop, &finished);
    }
  }

  BIND(&finished);

  // Finally set the length on the output and return it.
  GenerateSetLength(context, array.value(), length.value());
  args.PopAndReturn(array.value());
}

// ES #sec-array.of
TF_BUILTIN(ArrayOf, ArrayPopulatorAssembler) {
  TNode<Int32T> argc =
      UncheckedCast<Int32T>(Parameter(BuiltinDescriptor::kArgumentsCount));
  TNode<Smi> length = SmiFromInt32(argc);

  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));

  CodeStubArguments args(this, length, nullptr, ParameterMode::SMI_PARAMETERS);

  TNode<Object> array = ConstructArrayLike(context, args.GetReceiver(), length);

  // TODO(delphick): Avoid using CreateDataProperty on the fast path.
  BuildFastLoop(SmiConstant(0), length,
                [=](Node* index) {
                  CallRuntime(
                      Runtime::kCreateDataProperty, context,
                      static_cast<Node*>(array), index,
                      args.AtIndex(index, ParameterMode::SMI_PARAMETERS));
                },
                1, ParameterMode::SMI_PARAMETERS, IndexAdvanceMode::kPost);

  GenerateSetLength(context, array, length);
  args.PopAndReturn(array);
}

// ES #sec-get-%typedarray%.prototype.find
TF_BUILTIN(TypedArrayPrototypeFind, ArrayBuiltinsAssembler) {
  TNode<IntPtrT> argc =
      ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
  CodeStubArguments args(this, argc);
  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
  TNode<Object> receiver = args.GetReceiver();
  Node* callbackfn = args.GetOptionalArgumentValue(0);
  Node* this_arg = args.GetOptionalArgumentValue(1);

  InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc);

  GenerateIteratingTypedArrayBuiltinBody(
      "%TypedArray%.prototype.find",
      &ArrayBuiltinsAssembler::FindResultGenerator,
      &ArrayBuiltinsAssembler::FindProcessor,
      &ArrayBuiltinsAssembler::NullPostLoopAction);
}

// ES #sec-get-%typedarray%.prototype.findIndex
TF_BUILTIN(TypedArrayPrototypeFindIndex, ArrayBuiltinsAssembler) {
  TNode<IntPtrT> argc =
      ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
  CodeStubArguments args(this, argc);
  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
  TNode<Object> receiver = args.GetReceiver();
  Node* callbackfn = args.GetOptionalArgumentValue(0);
  Node* this_arg = args.GetOptionalArgumentValue(1);

  InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc);

  GenerateIteratingTypedArrayBuiltinBody(
      "%TypedArray%.prototype.findIndex",
      &ArrayBuiltinsAssembler::FindIndexResultGenerator,
      &ArrayBuiltinsAssembler::FindIndexProcessor,
      &ArrayBuiltinsAssembler::NullPostLoopAction);
}

TF_BUILTIN(TypedArrayPrototypeForEach, ArrayBuiltinsAssembler) {
  TNode<IntPtrT> argc =
      ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
  CodeStubArguments args(this, argc);
  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
  TNode<Object> receiver = args.GetReceiver();
  Node* callbackfn = args.GetOptionalArgumentValue(0);
  Node* this_arg = args.GetOptionalArgumentValue(1);

  InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc);

  GenerateIteratingTypedArrayBuiltinBody(
      "%TypedArray%.prototype.forEach",
      &ArrayBuiltinsAssembler::ForEachResultGenerator,
      &ArrayBuiltinsAssembler::ForEachProcessor,
      &ArrayBuiltinsAssembler::NullPostLoopAction);
}

TF_BUILTIN(ArraySomeLoopLazyDeoptContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* this_arg = Parameter(Descriptor::kThisArg);
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));
  Node* result = Parameter(Descriptor::kResult);

  // This custom lazy deopt point is right after the callback. every() needs
  // to pick up at the next step, which is either continuing to the next
  // array element or returning false if {result} is false.
  Label true_continue(this), false_continue(this);

  // iii. If selected is true, then...
  BranchIfToBooleanIsTrue(result, &true_continue, &false_continue);
  BIND(&true_continue);
  { Return(TrueConstant()); }
  BIND(&false_continue);
  {
    // Increment k.
    initial_k = NumberInc(initial_k);

    Return(CallBuiltin(Builtins::kArraySomeLoopContinuation, context, receiver,
                       callbackfn, this_arg, FalseConstant(), receiver,
                       initial_k, len, UndefinedConstant()));
  }
}

TF_BUILTIN(ArraySomeLoopEagerDeoptContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* this_arg = Parameter(Descriptor::kThisArg);
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));

  Return(CallBuiltin(Builtins::kArraySomeLoopContinuation, context, receiver,
                     callbackfn, this_arg, FalseConstant(), receiver, initial_k,
                     len, UndefinedConstant()));
}

TF_BUILTIN(ArraySomeLoopContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* this_arg = Parameter(Descriptor::kThisArg);
  Node* array = Parameter(Descriptor::kArray);
  TNode<JSReceiver> object = CAST(Parameter(Descriptor::kObject));
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));
  Node* to = Parameter(Descriptor::kTo);

  InitIteratingArrayBuiltinLoopContinuation(context, receiver, callbackfn,
                                            this_arg, array, object, initial_k,
                                            len, to);

  GenerateIteratingArrayBuiltinLoopContinuation(
      &ArrayBuiltinsAssembler::SomeProcessor,
      &ArrayBuiltinsAssembler::NullPostLoopAction, MissingPropertyMode::kSkip);
}

TF_BUILTIN(ArraySome, ArrayBuiltinsAssembler) {
  TNode<IntPtrT> argc =
      ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
  CodeStubArguments args(this, argc);
  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
  TNode<Object> receiver = args.GetReceiver();
  Node* callbackfn = args.GetOptionalArgumentValue(0);
  Node* this_arg = args.GetOptionalArgumentValue(1);

  InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc);

  GenerateIteratingArrayBuiltinBody(
      "Array.prototype.some", &ArrayBuiltinsAssembler::SomeResultGenerator,
      &ArrayBuiltinsAssembler::SomeProcessor,
      &ArrayBuiltinsAssembler::NullPostLoopAction,
      Builtins::CallableFor(isolate(), Builtins::kArraySomeLoopContinuation),
      MissingPropertyMode::kSkip);
}

TF_BUILTIN(TypedArrayPrototypeSome, ArrayBuiltinsAssembler) {
  TNode<IntPtrT> argc =
      ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
  CodeStubArguments args(this, argc);
  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
  TNode<Object> receiver = args.GetReceiver();
  Node* callbackfn = args.GetOptionalArgumentValue(0);
  Node* this_arg = args.GetOptionalArgumentValue(1);

  InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc);

  GenerateIteratingTypedArrayBuiltinBody(
      "%TypedArray%.prototype.some",
      &ArrayBuiltinsAssembler::SomeResultGenerator,
      &ArrayBuiltinsAssembler::SomeProcessor,
      &ArrayBuiltinsAssembler::NullPostLoopAction);
}

TF_BUILTIN(ArrayEveryLoopLazyDeoptContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* this_arg = Parameter(Descriptor::kThisArg);
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));
  Node* result = Parameter(Descriptor::kResult);

  // This custom lazy deopt point is right after the callback. every() needs
  // to pick up at the next step, which is either continuing to the next
  // array element or returning false if {result} is false.
  Label true_continue(this), false_continue(this);

  // iii. If selected is true, then...
  BranchIfToBooleanIsTrue(result, &true_continue, &false_continue);
  BIND(&true_continue);
  {
    // Increment k.
    initial_k = NumberInc(initial_k);

    Return(CallBuiltin(Builtins::kArrayEveryLoopContinuation, context, receiver,
                       callbackfn, this_arg, TrueConstant(), receiver,
                       initial_k, len, UndefinedConstant()));
  }
  BIND(&false_continue);
  { Return(FalseConstant()); }
}

TF_BUILTIN(ArrayEveryLoopEagerDeoptContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* this_arg = Parameter(Descriptor::kThisArg);
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));

  Return(CallBuiltin(Builtins::kArrayEveryLoopContinuation, context, receiver,
                     callbackfn, this_arg, TrueConstant(), receiver, initial_k,
                     len, UndefinedConstant()));
}

TF_BUILTIN(ArrayEveryLoopContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* this_arg = Parameter(Descriptor::kThisArg);
  Node* array = Parameter(Descriptor::kArray);
  TNode<JSReceiver> object = CAST(Parameter(Descriptor::kObject));
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));
  Node* to = Parameter(Descriptor::kTo);

  InitIteratingArrayBuiltinLoopContinuation(context, receiver, callbackfn,
                                            this_arg, array, object, initial_k,
                                            len, to);

  GenerateIteratingArrayBuiltinLoopContinuation(
      &ArrayBuiltinsAssembler::EveryProcessor,
      &ArrayBuiltinsAssembler::NullPostLoopAction, MissingPropertyMode::kSkip);
}

TF_BUILTIN(ArrayEvery, ArrayBuiltinsAssembler) {
  TNode<IntPtrT> argc =
      ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
  CodeStubArguments args(this, argc);
  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
  TNode<Object> receiver = args.GetReceiver();
  Node* callbackfn = args.GetOptionalArgumentValue(0);
  Node* this_arg = args.GetOptionalArgumentValue(1);

  InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc);

  GenerateIteratingArrayBuiltinBody(
      "Array.prototype.every", &ArrayBuiltinsAssembler::EveryResultGenerator,
      &ArrayBuiltinsAssembler::EveryProcessor,
      &ArrayBuiltinsAssembler::NullPostLoopAction,
      Builtins::CallableFor(isolate(), Builtins::kArrayEveryLoopContinuation),
      MissingPropertyMode::kSkip);
}

TF_BUILTIN(TypedArrayPrototypeEvery, ArrayBuiltinsAssembler) {
  TNode<IntPtrT> argc =
      ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
  CodeStubArguments args(this, argc);
  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
  TNode<Object> receiver = args.GetReceiver();
  Node* callbackfn = args.GetOptionalArgumentValue(0);
  Node* this_arg = args.GetOptionalArgumentValue(1);

  InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc);

  GenerateIteratingTypedArrayBuiltinBody(
      "%TypedArray%.prototype.every",
      &ArrayBuiltinsAssembler::EveryResultGenerator,
      &ArrayBuiltinsAssembler::EveryProcessor,
      &ArrayBuiltinsAssembler::NullPostLoopAction);
}

TF_BUILTIN(ArrayReduceLoopContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* this_arg = Parameter(Descriptor::kThisArg);
  Node* accumulator = Parameter(Descriptor::kAccumulator);
  TNode<JSReceiver> object = CAST(Parameter(Descriptor::kObject));
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));
  Node* to = Parameter(Descriptor::kTo);

  InitIteratingArrayBuiltinLoopContinuation(context, receiver, callbackfn,
                                            this_arg, accumulator, object,
                                            initial_k, len, to);

  GenerateIteratingArrayBuiltinLoopContinuation(
      &ArrayBuiltinsAssembler::ReduceProcessor,
      &ArrayBuiltinsAssembler::ReducePostLoopAction,
      MissingPropertyMode::kSkip);
}

TF_BUILTIN(ArrayReducePreLoopEagerDeoptContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));

  // Simulate starting the loop at 0, but ensuring that the accumulator is
  // the hole. The continuation stub will search for the initial non-hole
  // element, rightly throwing an exception if not found.
  Return(CallBuiltin(Builtins::kArrayReduceLoopContinuation, context, receiver,
                     callbackfn, UndefinedConstant(), TheHoleConstant(),
                     receiver, SmiConstant(0), len, UndefinedConstant()));
}

TF_BUILTIN(ArrayReduceLoopEagerDeoptContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* accumulator = Parameter(Descriptor::kAccumulator);
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));

  Return(CallBuiltin(Builtins::kArrayReduceLoopContinuation, context, receiver,
                     callbackfn, UndefinedConstant(), accumulator, receiver,
                     initial_k, len, UndefinedConstant()));
}

TF_BUILTIN(ArrayReduceLoopLazyDeoptContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));
  Node* result = Parameter(Descriptor::kResult);

  Return(CallBuiltin(Builtins::kArrayReduceLoopContinuation, context, receiver,
                     callbackfn, UndefinedConstant(), result, receiver,
                     initial_k, len, UndefinedConstant()));
}

TF_BUILTIN(ArrayReduce, ArrayBuiltinsAssembler) {
  TNode<IntPtrT> argc =
      ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
  CodeStubArguments args(this, argc);
  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
  TNode<Object> receiver = args.GetReceiver();
  Node* callbackfn = args.GetOptionalArgumentValue(0);
  Node* initial_value = args.GetOptionalArgumentValue(1, TheHoleConstant());

  InitIteratingArrayBuiltinBody(context, receiver, callbackfn, initial_value,
                                argc);

  GenerateIteratingArrayBuiltinBody(
      "Array.prototype.reduce", &ArrayBuiltinsAssembler::ReduceResultGenerator,
      &ArrayBuiltinsAssembler::ReduceProcessor,
      &ArrayBuiltinsAssembler::ReducePostLoopAction,
      Builtins::CallableFor(isolate(), Builtins::kArrayReduceLoopContinuation),
      MissingPropertyMode::kSkip);
}

TF_BUILTIN(TypedArrayPrototypeReduce, ArrayBuiltinsAssembler) {
  TNode<IntPtrT> argc =
      ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
  CodeStubArguments args(this, argc);
  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
  TNode<Object> receiver = args.GetReceiver();
  Node* callbackfn = args.GetOptionalArgumentValue(0);
  Node* initial_value = args.GetOptionalArgumentValue(1, TheHoleConstant());

  InitIteratingArrayBuiltinBody(context, receiver, callbackfn, initial_value,
                                argc);

  GenerateIteratingTypedArrayBuiltinBody(
      "%TypedArray%.prototype.reduce",
      &ArrayBuiltinsAssembler::ReduceResultGenerator,
      &ArrayBuiltinsAssembler::ReduceProcessor,
      &ArrayBuiltinsAssembler::ReducePostLoopAction);
}

TF_BUILTIN(ArrayReduceRightLoopContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* this_arg = Parameter(Descriptor::kThisArg);
  Node* accumulator = Parameter(Descriptor::kAccumulator);
  TNode<JSReceiver> object = CAST(Parameter(Descriptor::kObject));
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));
  Node* to = Parameter(Descriptor::kTo);

  InitIteratingArrayBuiltinLoopContinuation(context, receiver, callbackfn,
                                            this_arg, accumulator, object,
                                            initial_k, len, to);

  GenerateIteratingArrayBuiltinLoopContinuation(
      &ArrayBuiltinsAssembler::ReduceProcessor,
      &ArrayBuiltinsAssembler::ReducePostLoopAction, MissingPropertyMode::kSkip,
      ForEachDirection::kReverse);
}

TF_BUILTIN(ArrayReduceRightPreLoopEagerDeoptContinuation,
           ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  TNode<Smi> len = CAST(Parameter(Descriptor::kLength));

  // Simulate starting the loop at 0, but ensuring that the accumulator is
  // the hole. The continuation stub will search for the initial non-hole
  // element, rightly throwing an exception if not found.
  Return(CallBuiltin(Builtins::kArrayReduceRightLoopContinuation, context,
                     receiver, callbackfn, UndefinedConstant(),
                     TheHoleConstant(), receiver, SmiSub(len, SmiConstant(1)),
                     len, UndefinedConstant()));
}

TF_BUILTIN(ArrayReduceRightLoopEagerDeoptContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* accumulator = Parameter(Descriptor::kAccumulator);
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));

  Return(CallBuiltin(Builtins::kArrayReduceRightLoopContinuation, context,
                     receiver, callbackfn, UndefinedConstant(), accumulator,
                     receiver, initial_k, len, UndefinedConstant()));
}

TF_BUILTIN(ArrayReduceRightLoopLazyDeoptContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));
  Node* result = Parameter(Descriptor::kResult);

  Return(CallBuiltin(Builtins::kArrayReduceRightLoopContinuation, context,
                     receiver, callbackfn, UndefinedConstant(), result,
                     receiver, initial_k, len, UndefinedConstant()));
}

TF_BUILTIN(ArrayReduceRight, ArrayBuiltinsAssembler) {
  TNode<IntPtrT> argc =
      ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
  CodeStubArguments args(this, argc);
  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
  TNode<Object> receiver = args.GetReceiver();
  Node* callbackfn = args.GetOptionalArgumentValue(0);
  Node* initial_value = args.GetOptionalArgumentValue(1, TheHoleConstant());

  InitIteratingArrayBuiltinBody(context, receiver, callbackfn, initial_value,
                                argc);

  GenerateIteratingArrayBuiltinBody(
      "Array.prototype.reduceRight",
      &ArrayBuiltinsAssembler::ReduceResultGenerator,
      &ArrayBuiltinsAssembler::ReduceProcessor,
      &ArrayBuiltinsAssembler::ReducePostLoopAction,
      Builtins::CallableFor(isolate(),
                            Builtins::kArrayReduceRightLoopContinuation),
      MissingPropertyMode::kSkip, ForEachDirection::kReverse);
}

TF_BUILTIN(TypedArrayPrototypeReduceRight, ArrayBuiltinsAssembler) {
  TNode<IntPtrT> argc =
      ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
  CodeStubArguments args(this, argc);
  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
  TNode<Object> receiver = args.GetReceiver();
  Node* callbackfn = args.GetOptionalArgumentValue(0);
  Node* initial_value = args.GetOptionalArgumentValue(1, TheHoleConstant());

  InitIteratingArrayBuiltinBody(context, receiver, callbackfn, initial_value,
                                argc);

  GenerateIteratingTypedArrayBuiltinBody(
      "%TypedArray%.prototype.reduceRight",
      &ArrayBuiltinsAssembler::ReduceResultGenerator,
      &ArrayBuiltinsAssembler::ReduceProcessor,
      &ArrayBuiltinsAssembler::ReducePostLoopAction,
      ForEachDirection::kReverse);
}

TF_BUILTIN(ArrayFilterLoopContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* this_arg = Parameter(Descriptor::kThisArg);
  Node* array = Parameter(Descriptor::kArray);
  TNode<JSReceiver> object = CAST(Parameter(Descriptor::kObject));
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));
  Node* to = Parameter(Descriptor::kTo);

  InitIteratingArrayBuiltinLoopContinuation(context, receiver, callbackfn,
                                            this_arg, array, object, initial_k,
                                            len, to);

  GenerateIteratingArrayBuiltinLoopContinuation(
      &ArrayBuiltinsAssembler::FilterProcessor,
      &ArrayBuiltinsAssembler::NullPostLoopAction, MissingPropertyMode::kSkip);
}

TF_BUILTIN(ArrayFilterLoopEagerDeoptContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* this_arg = Parameter(Descriptor::kThisArg);
  Node* array = Parameter(Descriptor::kArray);
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));
  Node* to = Parameter(Descriptor::kTo);

  Return(CallBuiltin(Builtins::kArrayFilterLoopContinuation, context, receiver,
                     callbackfn, this_arg, array, receiver, initial_k, len,
                     to));
}

TF_BUILTIN(ArrayFilterLoopLazyDeoptContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* this_arg = Parameter(Descriptor::kThisArg);
  Node* array = Parameter(Descriptor::kArray);
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));
  Node* value_k = Parameter(Descriptor::kValueK);
  Node* result = Parameter(Descriptor::kResult);

  VARIABLE(to, MachineRepresentation::kTagged, Parameter(Descriptor::kTo));

  // This custom lazy deopt point is right after the callback. filter() needs
  // to pick up at the next step, which is setting the callback result in
  // the output array. After incrementing k and to, we can glide into the loop
  // continuation builtin.

  Label true_continue(this, &to), false_continue(this);

  // iii. If selected is true, then...
  BranchIfToBooleanIsTrue(result, &true_continue, &false_continue);
  BIND(&true_continue);
  {
    // 1. Perform ? CreateDataPropertyOrThrow(A, ToString(to), kValue).
    CallRuntime(Runtime::kCreateDataProperty, context, array, to.value(),
                value_k);
    // 2. Increase to by 1.
    to.Bind(NumberInc(to.value()));
    Goto(&false_continue);
  }
  BIND(&false_continue);

  // Increment k.
  initial_k = NumberInc(initial_k);

  Return(CallBuiltin(Builtins::kArrayFilterLoopContinuation, context, receiver,
                     callbackfn, this_arg, array, receiver, initial_k, len,
                     to.value()));
}

TF_BUILTIN(ArrayFilter, ArrayBuiltinsAssembler) {
  TNode<IntPtrT> argc =
      ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
  CodeStubArguments args(this, argc);
  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
  TNode<Object> receiver = args.GetReceiver();
  Node* callbackfn = args.GetOptionalArgumentValue(0);
  Node* this_arg = args.GetOptionalArgumentValue(1);

  InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc);

  GenerateIteratingArrayBuiltinBody(
      "Array.prototype.filter", &ArrayBuiltinsAssembler::FilterResultGenerator,
      &ArrayBuiltinsAssembler::FilterProcessor,
      &ArrayBuiltinsAssembler::NullPostLoopAction,
      Builtins::CallableFor(isolate(), Builtins::kArrayFilterLoopContinuation),
      MissingPropertyMode::kSkip);
}

TF_BUILTIN(ArrayMapLoopContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* this_arg = Parameter(Descriptor::kThisArg);
  Node* array = Parameter(Descriptor::kArray);
  TNode<JSReceiver> object = CAST(Parameter(Descriptor::kObject));
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));
  Node* to = Parameter(Descriptor::kTo);

  InitIteratingArrayBuiltinLoopContinuation(context, receiver, callbackfn,
                                            this_arg, array, object, initial_k,
                                            len, to);

  GenerateIteratingArrayBuiltinLoopContinuation(
      &ArrayBuiltinsAssembler::SpecCompliantMapProcessor,
      &ArrayBuiltinsAssembler::NullPostLoopAction, MissingPropertyMode::kSkip);
}

TF_BUILTIN(ArrayMapLoopEagerDeoptContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* this_arg = Parameter(Descriptor::kThisArg);
  Node* array = Parameter(Descriptor::kArray);
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));

  Return(CallBuiltin(Builtins::kArrayMapLoopContinuation, context, receiver,
                     callbackfn, this_arg, array, receiver, initial_k, len,
                     UndefinedConstant()));
}

TF_BUILTIN(ArrayMapLoopLazyDeoptContinuation, ArrayBuiltinsAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Node* callbackfn = Parameter(Descriptor::kCallbackFn);
  Node* this_arg = Parameter(Descriptor::kThisArg);
  Node* array = Parameter(Descriptor::kArray);
  Node* initial_k = Parameter(Descriptor::kInitialK);
  TNode<Number> len = CAST(Parameter(Descriptor::kLength));
  Node* result = Parameter(Descriptor::kResult);

  // This custom lazy deopt point is right after the callback. map() needs
  // to pick up at the next step, which is setting the callback result in
  // the output array. After incrementing k, we can glide into the loop
  // continuation builtin.

  // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
  CallRuntime(Runtime::kCreateDataProperty, context, array, initial_k, result);
  // Then we have to increment k before going on.
  initial_k = NumberInc(initial_k);

  Return(CallBuiltin(Builtins::kArrayMapLoopContinuation, context, receiver,
                     callbackfn, this_arg, array, receiver, initial_k, len,
                     UndefinedConstant()));
}

TF_BUILTIN(ArrayMap, ArrayBuiltinsAssembler) {
  TNode<IntPtrT> argc =
      ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
  CodeStubArguments args(this, argc);
  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
  TNode<Object> receiver = args.GetReceiver();
  Node* callbackfn = args.GetOptionalArgumentValue(0);
  Node* this_arg = args.GetOptionalArgumentValue(1);

  InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc);

  GenerateIteratingArrayBuiltinBody(
      "Array.prototype.map", &ArrayBuiltinsAssembler::MapResultGenerator,
      &ArrayBuiltinsAssembler::FastMapProcessor,
      &ArrayBuiltinsAssembler::NullPostLoopAction,
      Builtins::CallableFor(isolate(), Builtins::kArrayMapLoopContinuation),
      MissingPropertyMode::kSkip);
}

TF_BUILTIN(TypedArrayPrototypeMap, ArrayBuiltinsAssembler) {
  TNode<IntPtrT> argc =
      ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
  CodeStubArguments args(this, argc);
  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
  TNode<Object> receiver = args.GetReceiver();
  Node* callbackfn = args.GetOptionalArgumentValue(0);
  Node* this_arg = args.GetOptionalArgumentValue(1);

  InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc);

  GenerateIteratingTypedArrayBuiltinBody(
      "%TypedArray%.prototype.map",
      &ArrayBuiltinsAssembler::TypedArrayMapResultGenerator,
      &ArrayBuiltinsAssembler::TypedArrayMapProcessor,
      &ArrayBuiltinsAssembler::NullPostLoopAction);
}

TF_BUILTIN(ArrayIsArray, CodeStubAssembler) {
  TNode<Object> object = CAST(Parameter(Descriptor::kArg));
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));

  Label call_runtime(this), return_true(this), return_false(this);

  GotoIf(TaggedIsSmi(object), &return_false);
  TNode<Int32T> instance_type = LoadInstanceType(CAST(object));

  GotoIf(InstanceTypeEqual(instance_type, JS_ARRAY_TYPE), &return_true);

  // TODO(verwaest): Handle proxies in-place.
  Branch(InstanceTypeEqual(instance_type, JS_PROXY_TYPE), &call_runtime,
         &return_false);

  BIND(&return_true);
  Return(TrueConstant());

  BIND(&return_false);
  Return(FalseConstant());

  BIND(&call_runtime);
  Return(CallRuntime(Runtime::kArrayIsArray, context, object));
}

class ArrayIncludesIndexofAssembler : public CodeStubAssembler {
 public:
  explicit ArrayIncludesIndexofAssembler(compiler::CodeAssemblerState* state)
      : CodeStubAssembler(state) {}

  enum SearchVariant { kIncludes, kIndexOf };

  void Generate(SearchVariant variant);
  void GenerateSmiOrObject(SearchVariant variant, Node* context, Node* elements,
                           Node* search_element, Node* array_length,
                           Node* from_index);
  void GeneratePackedDoubles(SearchVariant variant, Node* elements,
                             Node* search_element, Node* array_length,
                             Node* from_index);
  void GenerateHoleyDoubles(SearchVariant variant, Node* elements,
                            Node* search_element, Node* array_length,
                            Node* from_index);
};

void ArrayIncludesIndexofAssembler::Generate(SearchVariant variant) {
  const int kSearchElementArg = 0;
  const int kFromIndexArg = 1;

  TNode<IntPtrT> argc =
      ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
  CodeStubArguments args(this, argc);

  TNode<Object> receiver = args.GetReceiver();
  TNode<Object> search_element =
      args.GetOptionalArgumentValue(kSearchElementArg);
  TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));

  Node* intptr_zero = IntPtrConstant(0);

  Label init_index(this), return_not_found(this), call_runtime(this);

  // Take slow path if not a JSArray, if retrieving elements requires
  // traversing prototype, or if access checks are required.
  BranchIfFastJSArray(receiver, context, &init_index, &call_runtime);

  BIND(&init_index);
  VARIABLE(index_var, MachineType::PointerRepresentation(), intptr_zero);
  TNode<JSArray> array = CAST(receiver);

  // JSArray length is always a positive Smi for fast arrays.
  CSA_ASSERT(this, TaggedIsPositiveSmi(LoadJSArrayLength(array)));
  Node* array_length = LoadFastJSArrayLength(array);
  Node* array_length_untagged = SmiUntag(array_length);

  {
    // Initialize fromIndex.
    Label is_smi(this), is_nonsmi(this), done(this);

    // If no fromIndex was passed, default to 0.
    GotoIf(IntPtrLessThanOrEqual(argc, IntPtrConstant(kFromIndexArg)), &done);

    Node* start_from = args.AtIndex(kFromIndexArg);
    // Handle Smis and undefined here and everything else in runtime.
    // We must be very careful with side effects from the ToInteger conversion,
    // as the side effects might render previously checked assumptions about
    // the receiver being a fast JSArray and its length invalid.
    Branch(TaggedIsSmi(start_from), &is_smi, &is_nonsmi);

    BIND(&is_nonsmi);
    {
      GotoIfNot(IsUndefined(start_from), &call_runtime);
      Goto(&done);
    }
    BIND(&is_smi);
    {
      Node* intptr_start_from = SmiUntag(start_from);
      index_var.Bind(intptr_start_from);

      GotoIf(IntPtrGreaterThanOrEqual(index_var.value(), intptr_zero), &done);
      // The fromIndex is negative: add it to the array's length.
      index_var.Bind(IntPtrAdd(array_length_untagged, index_var.value()));
      // Clamp negative results at zero.
      GotoIf(IntPtrGreaterThanOrEqual(index_var.value(), intptr_zero), &done);
      index_var.Bind(intptr_zero);
      Goto(&done);
    }
    BIND(&done);
  }

  // Fail early if startIndex >= array.length.
  GotoIf(IntPtrGreaterThanOrEqual(index_var.value(), array_length_untagged),
         &return_not_found);

  Label if_smiorobjects(this), if_packed_doubles(this), if_holey_doubles(this);

  Node* elements_kind = LoadMapElementsKind(LoadMap(array));
  Node* elements = LoadElements(array);
  STATIC_ASSERT(PACKED_SMI_ELEMENTS == 0);
  STATIC_ASSERT(HOLEY_SMI_ELEMENTS == 1);
  STATIC_ASSERT(PACKED_ELEMENTS == 2);
  STATIC_ASSERT(HOLEY_ELEMENTS == 3);
  GotoIf(Uint32LessThanOrEqual(elements_kind, Int32Constant(HOLEY_ELEMENTS)),
         &if_smiorobjects);
  GotoIf(Word32Equal(elements_kind, Int32Constant(PACKED_DOUBLE_ELEMENTS)),
         &if_packed_doubles);
  GotoIf(Word32Equal(elements_kind, Int32Constant(HOLEY_DOUBLE_ELEMENTS)),
         &if_holey_doubles);
  Goto(&return_not_found);

  BIND(&if_smiorobjects);
  {
    Callable callable =
        (variant == kIncludes)
            ? Builtins::CallableFor(isolate(),
                                    Builtins::kArrayIncludesSmiOrObject)
            : Builtins::CallableFor(isolate(),
                                    Builtins::kArrayIndexOfSmiOrObject);
    Node* result = CallStub(callable, context, elements, search_element,
                            array_length, SmiTag(index_var.value()));
    args.PopAndReturn(result);
  }

  BIND(&if_packed_doubles);
  {
    Callable callable =
        (variant == kIncludes)
            ? Builtins::CallableFor(isolate(),
                                    Builtins::kArrayIncludesPackedDoubles)
            : Builtins::CallableFor(isolate(),
                                    Builtins::kArrayIndexOfPackedDoubles);
    Node* result = CallStub(callable, context, elements, search_element,
                            array_length, SmiTag(index_var.value()));
    args.PopAndReturn(result);
  }

  BIND(&if_holey_doubles);
  {
    Callable callable =
        (variant == kIncludes)
            ? Builtins::CallableFor(isolate(),
                                    Builtins::kArrayIncludesHoleyDoubles)
            : Builtins::CallableFor(isolate(),
                                    Builtins::kArrayIndexOfHoleyDoubles);
    Node* result = CallStub(callable, context, elements, search_element,
                            array_length, SmiTag(index_var.value()));
    args.PopAndReturn(result);
  }

  BIND(&return_not_found);
  if (variant == kIncludes) {
    args.PopAndReturn(FalseConstant());
  } else {
    args.PopAndReturn(NumberConstant(-1));
  }

  BIND(&call_runtime);
  {
    Node* start_from =
        args.GetOptionalArgumentValue(kFromIndexArg, UndefinedConstant());
    Runtime::FunctionId function = variant == kIncludes
                                       ? Runtime::kArrayIncludes_Slow
                                       : Runtime::kArrayIndexOf;
    args.PopAndReturn(
        CallRuntime(function, context, array, search_element, start_from));
  }
}

void ArrayIncludesIndexofAssembler::GenerateSmiOrObject(
    SearchVariant variant, Node* context, Node* elements, Node* search_element,
    Node* array_length, Node* from_index) {
  VARIABLE(index_var, MachineType::PointerRepresentation(),
           SmiUntag(from_index));
  VARIABLE(search_num, MachineRepresentation::kFloat64);
  Node* array_length_untagged = SmiUntag(array_length);

  Label ident_loop(this, &index_var), heap_num_loop(this, &search_num),
      string_loop(this), bigint_loop(this, &index_var),
      undef_loop(this, &index_var), not_smi(this), not_heap_num(this),
      return_found(this), return_not_found(this);

  GotoIfNot(TaggedIsSmi(search_element), &not_smi);
  search_num.Bind(SmiToFloat64(search_element));
  Goto(&heap_num_loop);

  BIND(&not_smi);
  if (variant == kIncludes) {
    GotoIf(IsUndefined(search_element), &undef_loop);
  }
  Node* map = LoadMap(search_element);
  GotoIfNot(IsHeapNumberMap(map), &not_heap_num);
  search_num.Bind(LoadHeapNumberValue(search_element));
  Goto(&heap_num_loop);

  BIND(&not_heap_num);
  Node* search_type = LoadMapInstanceType(map);
  GotoIf(IsStringInstanceType(search_type), &string_loop);
  GotoIf(IsBigIntInstanceType(search_type), &bigint_loop);
  Goto(&ident_loop);

  BIND(&ident_loop);
  {
    GotoIfNot(UintPtrLessThan(index_var.value(), array_length_untagged),
              &return_not_found);
    Node* element_k = LoadFixedArrayElement(elements, index_var.value());
    GotoIf(WordEqual(element_k, search_element), &return_found);

    Increment(&index_var);
    Goto(&ident_loop);
  }

  if (variant == kIncludes) {
    BIND(&undef_loop);

    GotoIfNot(UintPtrLessThan(index_var.value(), array_length_untagged),
              &return_not_found);
    Node* element_k = LoadFixedArrayElement(elements, index_var.value());
    GotoIf(IsUndefined(element_k), &return_found);
    GotoIf(IsTheHole(element_k), &return_found);

    Increment(&index_var);
    Goto(&undef_loop);
  }

  BIND(&heap_num_loop);
  {
    Label nan_loop(this, &index_var), not_nan_loop(this, &index_var);
    Label* nan_handling = variant == kIncludes ? &nan_loop : &return_not_found;
    BranchIfFloat64IsNaN(search_num.value(), nan_handling, &not_nan_loop);

    BIND(&not_nan_loop);
    {
      Label continue_loop(this), not_smi(this);
      GotoIfNot(UintPtrLessThan(index_var.value(), array_length_untagged),
                &return_not_found);
      Node* element_k = LoadFixedArrayElement(elements, index_var.value());
      GotoIfNot(TaggedIsSmi(element_k), &not_smi);
      Branch(Float64Equal(search_num.value(), SmiToFloat64(element_k)),
             &return_found, &continue_loop);

      BIND(&not_smi);
      GotoIfNot(IsHeapNumber(element_k), &continue_loop);
      Branch(Float64Equal(search_num.value(), LoadHeapNumberValue(element_k)),
             &return_found, &continue_loop);

      BIND(&continue_loop);
      Increment(&index_var);
      Goto(&not_nan_loop);
    }

    // Array.p.includes uses SameValueZero comparisons, where NaN == NaN.
    if (variant == kIncludes) {
      BIND(&nan_loop);
      Label continue_loop(this);
      GotoIfNot(UintPtrLessThan(index_var.value(), array_length_untagged),
                &return_not_found);
      Node* element_k = LoadFixedArrayElement(elements, index_var.value());
      GotoIf(TaggedIsSmi(element_k), &continue_loop);
      GotoIfNot(IsHeapNumber(CAST(element_k)), &continue_loop);
      BranchIfFloat64IsNaN(LoadHeapNumberValue(element_k), &return_found,
                           &continue_loop);

      BIND(&continue_loop);
      Increment(&index_var);
      Goto(&nan_loop);
    }
  }

  BIND(&string_loop);
  {
    TNode<String> search_element_string = CAST(search_element);
    Label continue_loop(this), next_iteration(this, &index_var),
        slow_compare(this), runtime(this, Label::kDeferred);
    TNode<IntPtrT> search_length =
        LoadStringLengthAsWord(search_element_string);
    Goto(&next_iteration);
    BIND(&next_iteration);
    GotoIfNot(UintPtrLessThan(index_var.value(), array_length_untagged),
              &return_not_found);
    Node* element_k = LoadFixedArrayElement(elements, index_var.value());
    GotoIf(TaggedIsSmi(element_k), &continue_loop);
    GotoIf(WordEqual(search_element_string, element_k), &return_found);
    Node* element_k_type = LoadInstanceType(element_k);
    GotoIfNot(IsStringInstanceType(element_k_type), &continue_loop);
    Branch(WordEqual(search_length, LoadStringLengthAsWord(element_k)),
           &slow_compare, &continue_loop);

    BIND(&slow_compare);
    StringBuiltinsAssembler string_asm(state());
    string_asm.StringEqual_Core(context, search_element_string, search_type,
                                element_k, element_k_type, search_length,
                                &return_found, &continue_loop, &runtime);
    BIND(&runtime);
    TNode<Object> result = CallRuntime(Runtime::kStringEqual, context,
                                       search_element_string, element_k);
    Branch(WordEqual(result, TrueConstant()), &return_found, &continue_loop);

    BIND(&continue_loop);
    Increment(&index_var);
    Goto(&next_iteration);
  }

  BIND(&bigint_loop);
  {
    GotoIfNot(UintPtrLessThan(index_var.value(), array_length_untagged),
              &return_not_found);

    Node* element_k = LoadFixedArrayElement(elements, index_var.value());
    Label continue_loop(this);
    GotoIf(TaggedIsSmi(element_k), &continue_loop);
    GotoIfNot(IsBigInt(CAST(element_k)), &continue_loop);
    TNode<Object> result = CallRuntime(Runtime::kBigIntEqualToBigInt, context,
                                       search_element, element_k);
    Branch(WordEqual(result, TrueConstant()), &return_found, &continue_loop);

    BIND(&continue_loop);
    Increment(&index_var);
    Goto(&bigint_loop);
  }
  BIND(&return_found);
  if (variant == kIncludes) {
    Return(TrueConstant());
  } else {
    Return(SmiTag(index_var.value()));
  }

  BIND(&return_not_found);
  if (variant == kIncludes) {
    Return(FalseConstant());
  } else {
    Return(NumberConstant(-1));
  }
}

void ArrayIncludesIndexofAssembler::GeneratePackedDoubles(SearchVariant variant,
                                                          Node* elements,
                                                          Node* search_element,
                                                          Node* array_length,
                                                          Node* from_index) {
  VARIABLE(index_var, MachineType::PointerRepresentation(),
           SmiUntag(from_index));
  Node* array_length_untagged = SmiUntag(array_length);

  Label nan_loop(this, &index_var), not_nan_loop(this, &index_var),
      hole_loop(this, &index_var), search_notnan(this), return_found(this),
      return_not_found(this);
  VARIABLE(search_num, MachineRepresentation::kFloat64);
  search_num.Bind(Float64Constant(0));

  GotoIfNot(TaggedIsSmi(search_element), &search_notnan);
  search_num.Bind(SmiToFloat64(search_element));
  Goto(&not_nan_loop);

  BIND(&search_notnan);
  GotoIfNot(IsHeapNumber(search_element), &return_not_found);

  search_num.Bind(LoadHeapNumberValue(search_element));

  Label* nan_handling = variant == kIncludes ? &nan_loop : &return_not_found;
  BranchIfFloat64IsNaN(search_num.value(), nan_handling, &not_nan_loop);

  BIND(&not_nan_loop);
  {
    Label continue_loop(this);
    GotoIfNot(UintPtrLessThan(index_var.value(), array_length_untagged),
              &return_not_found);
    Node* element_k = LoadFixedDoubleArrayElement(elements, index_var.value(),
                                                  MachineType::Float64());
    Branch(Float64Equal(element_k, search_num.value()), &return_found,
           &continue_loop);
    BIND(&continue_loop);
    Increment(&index_var);
    Goto(&not_nan_loop);
  }

  // Array.p.includes uses SameValueZero comparisons, where NaN == NaN.
  if (variant == kIncludes) {
    BIND(&nan_loop);
    Label continue_loop(this);
    GotoIfNot(UintPtrLessThan(index_var.value(), array_length_untagged),
              &return_not_found);
    Node* element_k = LoadFixedDoubleArrayElement(elements, index_var.value(),
                                                  MachineType::Float64());
    BranchIfFloat64IsNaN(element_k, &return_found, &continue_loop);
    BIND(&continue_loop);
    Increment(&index_var);
    Goto(&nan_loop);
  }

  BIND(&return_found);
  if (variant == kIncludes) {
    Return(TrueConstant());
  } else {
    Return(SmiTag(index_var.value()));
  }

  BIND(&return_not_found);
  if (variant == kIncludes) {
    Return(FalseConstant());
  } else {
    Return(NumberConstant(-1));
  }
}

void ArrayIncludesIndexofAssembler::GenerateHoleyDoubles(SearchVariant variant,
                                                         Node* elements,
                                                         Node* search_element,
                                                         Node* array_length,
                                                         Node* from_index) {
  VARIABLE(index_var, MachineType::PointerRepresentation(),
           SmiUntag(from_index));
  Node* array_length_untagged = SmiUntag(array_length);

  Label nan_loop(this, &index_var), not_nan_loop(this, &index_var),
      hole_loop(this, &index_var), search_notnan(this), return_found(this),
      return_not_found(this);
  VARIABLE(search_num, MachineRepresentation::kFloat64);
  search_num.Bind(Float64Constant(0));

  GotoIfNot(TaggedIsSmi(search_element), &search_notnan);
  search_num.Bind(SmiToFloat64(search_element));
  Goto(&not_nan_loop);

  BIND(&search_notnan);
  if (variant == kIncludes) {
    GotoIf(IsUndefined(search_element), &hole_loop);
  }
  GotoIfNot(IsHeapNumber(search_element), &return_not_found);

  search_num.Bind(LoadHeapNumberValue(search_element));

  Label* nan_handling = variant == kIncludes ? &nan_loop : &return_not_found;
  BranchIfFloat64IsNaN(search_num.value(), nan_handling, &not_nan_loop);

  BIND(&not_nan_loop);
  {
    Label continue_loop(this);
    GotoIfNot(UintPtrLessThan(index_var.value(), array_length_untagged),
              &return_not_found);

    // No need for hole checking here; the following Float64Equal will
    // return 'not equal' for holes anyway.
    Node* element_k = LoadFixedDoubleArrayElement(elements, index_var.value(),
                                                  MachineType::Float64());

    Branch(Float64Equal(element_k, search_num.value()), &return_found,
           &continue_loop);
    BIND(&continue_loop);
    Increment(&index_var);
    Goto(&not_nan_loop);
  }

  // Array.p.includes uses SameValueZero comparisons, where NaN == NaN.
  if (variant == kIncludes) {
    BIND(&nan_loop);
    Label continue_loop(this);
    GotoIfNot(UintPtrLessThan(index_var.value(), array_length_untagged),
              &return_not_found);

    // Load double value or continue if it's the hole NaN.
    Node* element_k = LoadFixedDoubleArrayElement(
        elements, index_var.value(), MachineType::Float64(), 0,
        INTPTR_PARAMETERS, &continue_loop);

    BranchIfFloat64IsNaN(element_k, &return_found, &continue_loop);
    BIND(&continue_loop);
    Increment(&index_var);
    Goto(&nan_loop);
  }

  // Array.p.includes treats the hole as undefined.
  if (variant == kIncludes) {
    BIND(&hole_loop);
    GotoIfNot(UintPtrLessThan(index_var.value(), array_length_untagged),
              &return_not_found);

    // Check if the element is a double hole, but don't load it.
    LoadFixedDoubleArrayElement(elements, index_var.value(),
                                MachineType::None(), 0, INTPTR_PARAMETERS,
                                &return_found);

    Increment(&index_var);
    Goto(&hole_loop);
  }

  BIND(&return_found);
  if (variant == kIncludes) {
    Return(TrueConstant());
  } else {
    Return(SmiTag(index_var.value()));
  }

  BIND(&return_not_found);
  if (variant == kIncludes) {
    Return(FalseConstant());
  } else {
    Return(NumberConstant(-1));
  }
}

TF_BUILTIN(ArrayIncludes, ArrayIncludesIndexofAssembler) {
  Generate(kIncludes);
}

TF_BUILTIN(ArrayIncludesSmiOrObject, ArrayIncludesIndexofAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* elements = Parameter(Descriptor::kElements);
  Node* search_element = Parameter(Descriptor::kSearchElement);
  Node* array_length = Parameter(Descriptor::kLength);
  Node* from_index = Parameter(Descriptor::kFromIndex);

  GenerateSmiOrObject(kIncludes, context, elements, search_element,
                      array_length, from_index);
}

TF_BUILTIN(ArrayIncludesPackedDoubles, ArrayIncludesIndexofAssembler) {
  Node* elements = Parameter(Descriptor::kElements);
  Node* search_element = Parameter(Descriptor::kSearchElement);
  Node* array_length = Parameter(Descriptor::kLength);
  Node* from_index = Parameter(Descriptor::kFromIndex);

  GeneratePackedDoubles(kIncludes, elements, search_element, array_length,
                        from_index);
}

TF_BUILTIN(ArrayIncludesHoleyDoubles, ArrayIncludesIndexofAssembler) {
  Node* elements = Parameter(Descriptor::kElements);
  Node* search_element = Parameter(Descriptor::kSearchElement);
  Node* array_length = Parameter(Descriptor::kLength);
  Node* from_index = Parameter(Descriptor::kFromIndex);

  GenerateHoleyDoubles(kIncludes, elements, search_element, array_length,
                       from_index);
}

TF_BUILTIN(ArrayIndexOf, ArrayIncludesIndexofAssembler) { Generate(kIndexOf); }

TF_BUILTIN(ArrayIndexOfSmiOrObject, ArrayIncludesIndexofAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* elements = Parameter(Descriptor::kElements);
  Node* search_element = Parameter(Descriptor::kSearchElement);
  Node* array_length = Parameter(Descriptor::kLength);
  Node* from_index = Parameter(Descriptor::kFromIndex);

  GenerateSmiOrObject(kIndexOf, context, elements, search_element, array_length,
                      from_index);
}

TF_BUILTIN(ArrayIndexOfPackedDoubles, ArrayIncludesIndexofAssembler) {
  Node* elements = Parameter(Descriptor::kElements);
  Node* search_element = Parameter(Descriptor::kSearchElement);
  Node* array_length = Parameter(Descriptor::kLength);
  Node* from_index = Parameter(Descriptor::kFromIndex);

  GeneratePackedDoubles(kIndexOf, elements, search_element, array_length,
                        from_index);
}

TF_BUILTIN(ArrayIndexOfHoleyDoubles, ArrayIncludesIndexofAssembler) {
  Node* elements = Parameter(Descriptor::kElements);
  Node* search_element = Parameter(Descriptor::kSearchElement);
  Node* array_length = Parameter(Descriptor::kLength);
  Node* from_index = Parameter(Descriptor::kFromIndex);

  GenerateHoleyDoubles(kIndexOf, elements, search_element, array_length,
                       from_index);
}

// ES #sec-array.prototype.values
TF_BUILTIN(ArrayPrototypeValues, CodeStubAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Return(CreateArrayIterator(context, ToObject(context, receiver),
                             IterationKind::kValues));
}

// ES #sec-array.prototype.entries
TF_BUILTIN(ArrayPrototypeEntries, CodeStubAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Return(CreateArrayIterator(context, ToObject(context, receiver),
                             IterationKind::kEntries));
}

// ES #sec-array.prototype.keys
TF_BUILTIN(ArrayPrototypeKeys, CodeStubAssembler) {
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver));
  Return(CreateArrayIterator(context, ToObject(context, receiver),
                             IterationKind::kKeys));
}

// ES #sec-%arrayiteratorprototype%.next
TF_BUILTIN(ArrayIteratorPrototypeNext, CodeStubAssembler) {
  const char* method_name = "Array Iterator.prototype.next";

  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
  Node* iterator = Parameter(Descriptor::kReceiver);

  VARIABLE(var_value, MachineRepresentation::kTagged);
  VARIABLE(var_done, MachineRepresentation::kTagged);

  // Required, or else `throw_bad_receiver` fails a DCHECK due to these
  // variables not being bound along all paths, despite not being used.
  var_done.Bind(TrueConstant());
  var_value.Bind(UndefinedConstant());

  Label throw_bad_receiver(this, Label::kDeferred);
  Label set_done(this);
  Label allocate_entry_if_needed(this);
  Label allocate_iterator_result(this);

  // If O does not have all of the internal slots of an Array Iterator Instance
  // (22.1.5.3), throw a TypeError exception
  GotoIf(TaggedIsSmi(iterator), &throw_bad_receiver);
  GotoIfNot(IsJSArrayIterator(CAST(iterator)), &throw_bad_receiver);

  // Let a be O.[[IteratedObject]].
  Node* array =
      LoadObjectField(iterator, JSArrayIterator::kIteratedObjectOffset);

  // Let index be O.[[ArrayIteratorNextIndex]].
  Node* index = LoadObjectField(iterator, JSArrayIterator::kNextIndexOffset);
  Node* array_map = LoadMap(array);

  Label if_detached(this, Label::kDeferred);

  Label if_typedarray(this), if_other(this, Label::kDeferred), if_array(this),
      if_generic(this, Label::kDeferred);

  Node* array_type = LoadInstanceType(array);
  GotoIf(InstanceTypeEqual(array_type, JS_ARRAY_TYPE), &if_array);
  Branch(InstanceTypeEqual(array_type, JS_TYPED_ARRAY_TYPE), &if_typedarray,
         &if_other);

  BIND(&if_array);
  {
    // We can only handle fast elements here.
    Node* elements_kind = LoadMapElementsKind(array_map);
    GotoIfNot(IsFastElementsKind(elements_kind), &if_other);

    TNode<Smi> length = CAST(LoadJSArrayLength(array));

    GotoIfNot(SmiBelow(CAST(index), length), &set_done);

    var_value.Bind(index);
    TNode<Smi> one = SmiConstant(1);
    StoreObjectFieldNoWriteBarrier(iterator, JSArrayIterator::kNextIndexOffset,
                                   SmiAdd(CAST(index), one));
    var_done.Bind(FalseConstant());

    GotoIf(Word32Equal(LoadAndUntagToWord32ObjectField(
                           iterator, JSArrayIterator::kKindOffset),
                       Int32Constant(static_cast<int>(IterationKind::kKeys))),
           &allocate_iterator_result);

    Node* elements = LoadElements(array);
    Label if_packed(this), if_holey(this), if_packed_double(this),
        if_holey_double(this), if_unknown_kind(this, Label::kDeferred);
    int32_t kinds[] = {// Handled by if_packed.
                       PACKED_SMI_ELEMENTS, PACKED_ELEMENTS,
                       // Handled by if_holey.
                       HOLEY_SMI_ELEMENTS, HOLEY_ELEMENTS,
                       // Handled by if_packed_double.
                       PACKED_DOUBLE_ELEMENTS,
                       // Handled by if_holey_double.
                       HOLEY_DOUBLE_ELEMENTS};
    Label* labels[] = {// PACKED_{SMI,}_ELEMENTS
                       &if_packed, &if_packed,
                       // HOLEY_{SMI,}_ELEMENTS
                       &if_holey, &if_holey,
                       // PACKED_DOUBLE_ELEMENTS
                       &if_packed_double,
                       // HOLEY_DOUBLE_ELEMENTS
                       &if_holey_double};
    Switch(elements_kind, &if_unknown_kind, kinds, labels, arraysize(kinds));

    BIND(&if_packed);
    {
      var_value.Bind(LoadFixedArrayElement(elements, index, 0, SMI_PARAMETERS));
      Goto(&allocate_entry_if_needed);
    }

    BIND(&if_holey);
    {
      Node* element = LoadFixedArrayElement(elements, index, 0, SMI_PARAMETERS);
      var_value.Bind(element);
      GotoIfNot(WordEqual(element, TheHoleConstant()),
                &allocate_entry_if_needed);
      GotoIf(IsNoElementsProtectorCellInvalid(), &if_generic);
      var_value.Bind(UndefinedConstant());
      Goto(&allocate_entry_if_needed);
    }

    BIND(&if_packed_double);
    {
      Node* value = LoadFixedDoubleArrayElement(
          elements, index, MachineType::Float64(), 0, SMI_PARAMETERS);
      var_value.Bind(AllocateHeapNumberWithValue(value));
      Goto(&allocate_entry_if_needed);
    }

    BIND(&if_holey_double);
    {
      Label if_hole(this, Label::kDeferred);
      Node* value = LoadFixedDoubleArrayElement(
          elements, index, MachineType::Float64(), 0, SMI_PARAMETERS, &if_hole);
      var_value.Bind(AllocateHeapNumberWithValue(value));
      Goto(&allocate_entry_if_needed);
      BIND(&if_hole);
      GotoIf(IsNoElementsProtectorCellInvalid(), &if_generic);
      var_value.Bind(UndefinedConstant());
      Goto(&allocate_entry_if_needed);
    }

    BIND(&if_unknown_kind);
    Unreachable();
  }

  BIND(&if_other);
  {
    // If a is undefined, return CreateIterResultObject(undefined, true)
    GotoIf(IsUndefined(array), &allocate_iterator_result);

    Node* length =
        CallBuiltin(Builtins::kToLength, context,
                    GetProperty(context, array, factory()->length_string()));

    GotoIfNumberGreaterThanOrEqual(index, length, &set_done);

    var_value.Bind(index);
    StoreObjectField(iterator, JSArrayIterator::kNextIndexOffset,
                     NumberInc(index));
    var_done.Bind(FalseConstant());

    GotoIf(Word32Equal(LoadAndUntagToWord32ObjectField(
                           iterator, JSArrayIterator::kKindOffset),
                       Int32Constant(static_cast<int>(IterationKind::kKeys))),
           &allocate_iterator_result);
    Goto(&if_generic);
  }

  BIND(&if_generic);
  {
    var_value.Bind(GetProperty(context, array, index));
    Goto(&allocate_entry_if_needed);
  }

  BIND(&if_typedarray);
  {
    Node* buffer = LoadObjectField(array, JSTypedArray::kBufferOffset);
    GotoIf(IsDetachedBuffer(buffer), &if_detached);

    TNode<Smi> length =
        CAST(LoadObjectField(array, JSTypedArray::kLengthOffset));

    GotoIfNot(SmiBelow(CAST(index), length), &set_done);

    var_value.Bind(index);
    TNode<Smi> one = SmiConstant(1);
    StoreObjectFieldNoWriteBarrier(iterator, JSArrayIterator::kNextIndexOffset,
                                   SmiAdd(CAST(index), one));
    var_done.Bind(FalseConstant());

    GotoIf(Word32Equal(LoadAndUntagToWord32ObjectField(
                           iterator, JSArrayIterator::kKindOffset),
                       Int32Constant(static_cast<int>(IterationKind::kKeys))),
           &allocate_iterator_result);

    Node* elements_kind = LoadMapElementsKind(array_map);
    Node* elements = LoadElements(array);
    Node* base_ptr =
        LoadObjectField(elements, FixedTypedArrayBase::kBasePointerOffset);
    Node* external_ptr =
        LoadObjectField(elements, FixedTypedArrayBase::kExternalPointerOffset,
                        MachineType::Pointer());
    Node* data_ptr = IntPtrAdd(BitcastTaggedToWord(base_ptr), external_ptr);

    Label if_unknown_type(this, Label::kDeferred);
    int32_t elements_kinds[] = {
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype, size) TYPE##_ELEMENTS,
        TYPED_ARRAYS(TYPED_ARRAY_CASE)
#undef TYPED_ARRAY_CASE
    };

#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype, size) \
  Label if_##type##array(this);
    TYPED_ARRAYS(TYPED_ARRAY_CASE)
#undef TYPED_ARRAY_CASE

    Label* elements_kind_labels[] = {
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype, size) &if_##type##array,
        TYPED_ARRAYS(TYPED_ARRAY_CASE)
#undef TYPED_ARRAY_CASE
    };
    STATIC_ASSERT(arraysize(elements_kinds) == arraysize(elements_kind_labels));

    Switch(elements_kind, &if_unknown_type, elements_kinds,
           elements_kind_labels, arraysize(elements_kinds));

    BIND(&if_unknown_type);
    Unreachable();

#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype, size)     \
  BIND(&if_##type##array);                                  \
  {                                                         \
    var_value.Bind(LoadFixedTypedArrayElementAsTagged(      \
        data_ptr, index, TYPE##_ELEMENTS, SMI_PARAMETERS)); \
    Goto(&allocate_entry_if_needed);                        \
  }
    TYPED_ARRAYS(TYPED_ARRAY_CASE)
#undef TYPED_ARRAY_CASE

    BIND(&if_detached);
    ThrowTypeError(context, MessageTemplate::kDetachedOperation, method_name);
  }

  BIND(&set_done);
  {
    StoreObjectFieldNoWriteBarrier(
        iterator, JSArrayIterator::kIteratedObjectOffset, UndefinedConstant());
    Goto(&allocate_iterator_result);
  }

  BIND(&allocate_entry_if_needed);
  {
    GotoIf(Word32Equal(LoadAndUntagToWord32ObjectField(
                           iterator, JSArrayIterator::kKindOffset),
                       Int32Constant(static_cast<int>(IterationKind::kValues))),
           &allocate_iterator_result);

    Node* elements = AllocateFixedArray(PACKED_ELEMENTS, IntPtrConstant(2));
    StoreFixedArrayElement(elements, 0, index, SKIP_WRITE_BARRIER);
    StoreFixedArrayElement(elements, 1, var_value.value(), SKIP_WRITE_BARRIER);

    Node* entry = Allocate(JSArray::kSize);
    Node* map = LoadContextElement(LoadNativeContext(context),
                                   Context::JS_ARRAY_PACKED_ELEMENTS_MAP_INDEX);

    StoreMapNoWriteBarrier(entry, map);
    StoreObjectFieldRoot(entry, JSArray::kPropertiesOrHashOffset,
                         Heap::kEmptyFixedArrayRootIndex);
    StoreObjectFieldNoWriteBarrier(entry, JSArray::kElementsOffset, elements);
    StoreObjectFieldNoWriteBarrier(entry, JSArray::kLengthOffset,
                                   SmiConstant(2));

    var_value.Bind(entry);
    Goto(&allocate_iterator_result);
  }

  BIND(&allocate_iterator_result);
  {
    Node* result = Allocate(JSIteratorResult::kSize);
    Node* map = LoadContextElement(LoadNativeContext(context),
                                   Context::ITERATOR_RESULT_MAP_INDEX);
    StoreMapNoWriteBarrier(result, map);
    StoreObjectFieldRoot(result, JSIteratorResult::kPropertiesOrHashOffset,
                         Heap::kEmptyFixedArrayRootIndex);
    StoreObjectFieldRoot(result, JSIteratorResult::kElementsOffset,
                         Heap::kEmptyFixedArrayRootIndex);
    StoreObjectFieldNoWriteBarrier(result, JSIteratorResult::kValueOffset,
                                   var_value.value());
    StoreObjectFieldNoWriteBarrier(result, JSIteratorResult::kDoneOffset,
                                   var_done.value());
    Return(result);
  }

  BIND(&throw_bad_receiver);
  {
    // The {receiver} is not a valid JSArrayIterator.
    ThrowTypeError(context, MessageTemplate::kIncompatibleMethodReceiver,
                   StringConstant(method_name), iterator);
  }
}

namespace {

class ArrayFlattenAssembler : public CodeStubAssembler {
 public:
  explicit ArrayFlattenAssembler(compiler::CodeAssemblerState* state)
      : CodeStubAssembler(state) {}

  // https://tc39.github.io/proposal-flatMap/#sec-FlattenIntoArray
  Node* FlattenIntoArray(Node* context, Node* target, Node* source,
                         Node* source_length, Node* start, Node* depth,
                         Node* mapper_function = nullptr,
                         Node* this_arg = nullptr) {
    CSA_ASSERT(this, IsJSReceiver(target));
    CSA_ASSERT(this, IsJSReceiver(source));
    CSA_ASSERT(this, IsNumberPositive(source_length));
    CSA_ASSERT(this, IsNumberPositive(start));
    CSA_ASSERT(this, IsNumber(depth));

    // 1. Let targetIndex be start.
    VARIABLE(var_target_index, MachineRepresentation::kTagged, start);

    // 2. Let sourceIndex be 0.
    VARIABLE(var_source_index, MachineRepresentation::kTagged, SmiConstant(0));

    // 3. Repeat...
    Label loop(this, {&var_target_index, &var_source_index}), done_loop(this);
    Goto(&loop);
    BIND(&loop);
    {
      Node* const source_index = var_source_index.value();
      Node* const target_index = var_target_index.value();

      // ...while sourceIndex < sourceLen
      GotoIfNumberGreaterThanOrEqual(source_index, source_length, &done_loop);

      // a. Let P be ! ToString(sourceIndex).
      // b. Let exists be ? HasProperty(source, P).
      CSA_ASSERT(this,
                 SmiGreaterThanOrEqual(CAST(source_index), SmiConstant(0)));
      Node* const exists =
          HasProperty(source, source_index, context, kHasProperty);

      // c. If exists is true, then
      Label next(this);
      GotoIfNot(IsTrue(exists), &next);
      {
        // i. Let element be ? Get(source, P).
        Node* element = GetProperty(context, source, source_index);

        // ii. If mapperFunction is present, then
        if (mapper_function != nullptr) {
          CSA_ASSERT(this, Word32Or(IsUndefined(mapper_function),
                                    IsCallable(mapper_function)));
          DCHECK_NOT_NULL(this_arg);

          // 1. Set element to ? Call(mapperFunction, thisArg , « element,
          //                          sourceIndex, source »).
          element =
              CallJS(CodeFactory::Call(isolate()), context, mapper_function,
                     this_arg, element, source_index, source);
        }

        // iii. Let shouldFlatten be false.
        Label if_flatten_array(this), if_flatten_proxy(this, Label::kDeferred),
            if_noflatten(this);
        // iv. If depth > 0, then
        GotoIfNumberGreaterThanOrEqual(SmiConstant(0), depth, &if_noflatten);
        // 1. Set shouldFlatten to ? IsArray(element).
        GotoIf(TaggedIsSmi(element), &if_noflatten);
        GotoIf(IsJSArray(element), &if_flatten_array);
        GotoIfNot(IsJSProxy(element), &if_noflatten);
        Branch(IsTrue(CallRuntime(Runtime::kArrayIsArray, context, element)),
               &if_flatten_proxy, &if_noflatten);

        BIND(&if_flatten_array);
        {
          CSA_ASSERT(this, IsJSArray(element));

          // 1. Let elementLen be ? ToLength(? Get(element, "length")).
          Node* const element_length =
              LoadObjectField(element, JSArray::kLengthOffset);

          // 2. Set targetIndex to ? FlattenIntoArray(target, element,
          //                                          elementLen, targetIndex,
          //                                          depth - 1).
          var_target_index.Bind(
              CallBuiltin(Builtins::kFlattenIntoArray, context, target, element,
                          element_length, target_index, NumberDec(depth)));
          Goto(&next);
        }

        BIND(&if_flatten_proxy);
        {
          CSA_ASSERT(this, IsJSProxy(element));

          // 1. Let elementLen be ? ToLength(? Get(element, "length")).
          Node* const element_length = ToLength_Inline(
              context, GetProperty(context, element, LengthStringConstant()));

          // 2. Set targetIndex to ? FlattenIntoArray(target, element,
          //                                          elementLen, targetIndex,
          //                                          depth - 1).
          var_target_index.Bind(
              CallBuiltin(Builtins::kFlattenIntoArray, context, target, element,
                          element_length, target_index, NumberDec(depth)));
          Goto(&next);
        }

        BIND(&if_noflatten);
        {
          // 1. If targetIndex >= 2^53-1, throw a TypeError exception.
          Label throw_error(this, Label::kDeferred);
          GotoIfNumberGreaterThanOrEqual(
              target_index, NumberConstant(kMaxSafeInteger), &throw_error);

          // 2. Perform ? CreateDataPropertyOrThrow(target,
          //                                        ! ToString(targetIndex),
          //                                        element).
          CallRuntime(Runtime::kCreateDataProperty, context, target,
                      target_index, element);

          // 3. Increase targetIndex by 1.
          var_target_index.Bind(NumberInc(target_index));
          Goto(&next);

          BIND(&throw_error);
          ThrowTypeError(context, MessageTemplate::kFlattenPastSafeLength,
                         source_length, target_index);
        }
      }
      BIND(&next);

      // d. Increase sourceIndex by 1.
      var_source_index.Bind(NumberInc(source_index));
      Goto(&loop);
    }

    BIND(&done_loop);
    return var_target_index.value();
  }
};

}  // namespace

// https://tc39.github.io/proposal-flatMap/#sec-FlattenIntoArray
TF_BUILTIN(FlattenIntoArray, ArrayFlattenAssembler) {
  Node* const context = Parameter(Descriptor::kContext);
  Node* const target = Parameter(Descriptor::kTarget);
  Node* const source = Parameter(Descriptor::kSource);
  Node* const source_length = Parameter(Descriptor::kSourceLength);
  Node* const start = Parameter(Descriptor::kStart);
  Node* const depth = Parameter(Descriptor::kDepth);

  Return(
      FlattenIntoArray(context, target, source, source_length, start, depth));
}

// https://tc39.github.io/proposal-flatMap/#sec-FlattenIntoArray
TF_BUILTIN(FlatMapIntoArray, ArrayFlattenAssembler) {
  Node* const context = Parameter(Descriptor::kContext);
  Node* const target = Parameter(Descriptor::kTarget);
  Node* const source = Parameter(Descriptor::kSource);
  Node* const source_length = Parameter(Descriptor::kSourceLength);
  Node* const start = Parameter(Descriptor::kStart);
  Node* const depth = Parameter(Descriptor::kDepth);
  Node* const mapper_function = Parameter(Descriptor::kMapperFunction);
  Node* const this_arg = Parameter(Descriptor::kThisArg);

  Return(FlattenIntoArray(context, target, source, source_length, start, depth,
                          mapper_function, this_arg));
}

// https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatten
TF_BUILTIN(ArrayPrototypeFlatten, CodeStubAssembler) {
  Node* const argc =
      ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
  CodeStubArguments args(this, argc);
  Node* const context = Parameter(BuiltinDescriptor::kContext);
  Node* const receiver = args.GetReceiver();
  Node* const depth = args.GetOptionalArgumentValue(0);

  // 1. Let O be ? ToObject(this value).
  Node* const o = ToObject(context, receiver);

  // 2. Let sourceLen be ? ToLength(? Get(O, "length")).
  Node* const source_length =
      ToLength_Inline(context, GetProperty(context, o, LengthStringConstant()));

  // 3. Let depthNum be 1.
  VARIABLE(var_depth_num, MachineRepresentation::kTagged, SmiConstant(1));

  // 4. If depth is not undefined, then
  Label done(this);
  GotoIf(IsUndefined(depth), &done);
  {
    // a. Set depthNum to ? ToInteger(depth).
    var_depth_num.Bind(ToInteger_Inline(context, depth));
    Goto(&done);
  }
  BIND(&done);

  // 5. Let A be ? ArraySpeciesCreate(O, 0).
  Node* const constructor =
      CallRuntime(Runtime::kArraySpeciesConstructor, context, o);
  Node* const a = ConstructJS(CodeFactory::Construct(isolate()), context,
                              constructor, SmiConstant(0));

  // 6. Perform ? FlattenIntoArray(A, O, sourceLen, 0, depthNum).
  CallBuiltin(Builtins::kFlattenIntoArray, context, a, o, source_length,
              SmiConstant(0), var_depth_num.value());

  // 7. Return A.
  args.PopAndReturn(a);
}

// https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap
TF_BUILTIN(ArrayPrototypeFlatMap, CodeStubAssembler) {
  Node* const argc =
      ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
  CodeStubArguments args(this, argc);
  Node* const context = Parameter(BuiltinDescriptor::kContext);
  Node* const receiver = args.GetReceiver();
  Node* const mapper_function = args.GetOptionalArgumentValue(0);

  // 1. Let O be ? ToObject(this value).
  Node* const o = ToObject(context, receiver);

  // 2. Let sourceLen be ? ToLength(? Get(O, "length")).
  Node* const source_length =
      ToLength_Inline(context, GetProperty(context, o, LengthStringConstant()));

  // 3. If IsCallable(mapperFunction) is false, throw a TypeError exception.
  Label if_not_callable(this, Label::kDeferred);
  GotoIf(TaggedIsSmi(mapper_function), &if_not_callable);
  GotoIfNot(IsCallable(mapper_function), &if_not_callable);

  // 4. If thisArg is present, let T be thisArg; else let T be undefined.
  Node* const t = args.GetOptionalArgumentValue(1);

  // 5. Let A be ? ArraySpeciesCreate(O, 0).
  Node* const constructor =
      CallRuntime(Runtime::kArraySpeciesConstructor, context, o);
  Node* const a = ConstructJS(CodeFactory::Construct(isolate()), context,
                              constructor, SmiConstant(0));

  // 6. Perform ? FlattenIntoArray(A, O, sourceLen, 0, 1, mapperFunction, T).
  CallBuiltin(Builtins::kFlatMapIntoArray, context, a, o, source_length,
              SmiConstant(0), SmiConstant(1), mapper_function, t);

  // 7. Return A.
  args.PopAndReturn(a);

  BIND(&if_not_callable);
  { ThrowTypeError(context, MessageTemplate::kMapperFunctionNonCallable); }
}

}  // namespace internal
}  // namespace v8
