// 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-utils-gen.h"
#include "src/builtins/builtins.h"
#include "src/code-stub-assembler.h"
#include "src/frame-constants.h"
#include "src/objects/api-callbacks.h"
#include "src/objects/descriptor-array.h"

namespace v8 {
namespace internal {

TF_BUILTIN(FastFunctionPrototypeBind, CodeStubAssembler) {
  Label slow(this);

  // TODO(ishell): use constants from Descriptor once the JSFunction linkage
  // arguments are reordered.
  Node* argc = Parameter(BuiltinDescriptor::kArgumentsCount);
  Node* context = Parameter(BuiltinDescriptor::kContext);
  Node* new_target = Parameter(BuiltinDescriptor::kNewTarget);

  CodeStubArguments args(this, ChangeInt32ToIntPtr(argc));

  // Check that receiver has instance type of JS_FUNCTION_TYPE
  Node* receiver = args.GetReceiver();
  GotoIf(TaggedIsSmi(receiver), &slow);

  Node* receiver_map = LoadMap(receiver);
  {
    Node* instance_type = LoadMapInstanceType(receiver_map);
    GotoIfNot(
        Word32Or(InstanceTypeEqual(instance_type, JS_FUNCTION_TYPE),
                 InstanceTypeEqual(instance_type, JS_BOUND_FUNCTION_TYPE)),
        &slow);
  }

  // Disallow binding of slow-mode functions. We need to figure out whether the
  // length and name property are in the original state.
  Comment("Disallow binding of slow-mode functions");
  GotoIf(IsDictionaryMap(receiver_map), &slow);

  // Check whether the length and name properties are still present as
  // AccessorInfo objects. In that case, their value can be recomputed even if
  // the actual value on the object changes.
  Comment("Check descriptor array length");
  TNode<DescriptorArray> descriptors = LoadMapDescriptors(receiver_map);
  // Minimum descriptor array length required for fast path.
  const int min_descriptors_length = DescriptorArray::LengthFor(Max(
      JSFunction::kLengthDescriptorIndex, JSFunction::kNameDescriptorIndex));
  TNode<Smi> descriptors_length = LoadFixedArrayBaseLength(descriptors);
  GotoIf(SmiLessThanOrEqual(descriptors_length,
                            SmiConstant(min_descriptors_length)),
         &slow);

  // Check whether the length and name properties are still present as
  // AccessorInfo objects. In that case, their value can be recomputed even if
  // the actual value on the object changes.
  Comment("Check name and length properties");
  {
    const int length_index = JSFunction::kLengthDescriptorIndex;
    TNode<Name> maybe_length = CAST(LoadFixedArrayElement(
        descriptors, DescriptorArray::ToKeyIndex(length_index)));
    GotoIf(WordNotEqual(maybe_length, LoadRoot(Heap::klength_stringRootIndex)),
           &slow);

    TNode<Object> maybe_length_accessor = LoadFixedArrayElement(
        descriptors, DescriptorArray::ToValueIndex(length_index));
    GotoIf(TaggedIsSmi(maybe_length_accessor), &slow);
    Node* length_value_map = LoadMap(CAST(maybe_length_accessor));
    GotoIfNot(IsAccessorInfoMap(length_value_map), &slow);

    const int name_index = JSFunction::kNameDescriptorIndex;
    TNode<Name> maybe_name = CAST(LoadFixedArrayElement(
        descriptors, DescriptorArray::ToKeyIndex(name_index)));
    GotoIf(WordNotEqual(maybe_name, LoadRoot(Heap::kname_stringRootIndex)),
           &slow);

    TNode<Object> maybe_name_accessor = LoadFixedArrayElement(
        descriptors, DescriptorArray::ToValueIndex(name_index));
    GotoIf(TaggedIsSmi(maybe_name_accessor), &slow);
    TNode<Map> name_value_map = LoadMap(CAST(maybe_name_accessor));
    GotoIfNot(IsAccessorInfoMap(name_value_map), &slow);
  }

  // Choose the right bound function map based on whether the target is
  // constructable.
  Comment("Choose the right bound function map");
  VARIABLE(bound_function_map, MachineRepresentation::kTagged);
  {
    Label with_constructor(this);
    VariableList vars({&bound_function_map}, zone());
    Node* native_context = LoadNativeContext(context);

    Label map_done(this, vars);
    GotoIf(IsConstructorMap(receiver_map), &with_constructor);

    bound_function_map.Bind(LoadContextElement(
        native_context, Context::BOUND_FUNCTION_WITHOUT_CONSTRUCTOR_MAP_INDEX));
    Goto(&map_done);

    BIND(&with_constructor);
    bound_function_map.Bind(LoadContextElement(
        native_context, Context::BOUND_FUNCTION_WITH_CONSTRUCTOR_MAP_INDEX));
    Goto(&map_done);

    BIND(&map_done);
  }

  // Verify that __proto__ matches that of a the target bound function.
  Comment("Verify that __proto__ matches target bound function");
  Node* prototype = LoadMapPrototype(receiver_map);
  Node* expected_prototype = LoadMapPrototype(bound_function_map.value());
  GotoIf(WordNotEqual(prototype, expected_prototype), &slow);

  // Allocate the arguments array.
  Comment("Allocate the arguments array");
  VARIABLE(argument_array, MachineRepresentation::kTagged);
  {
    Label empty_arguments(this);
    Label arguments_done(this, &argument_array);
    GotoIf(Uint32LessThanOrEqual(argc, Int32Constant(1)), &empty_arguments);
    Node* elements_length =
        ChangeUint32ToWord(Unsigned(Int32Sub(argc, Int32Constant(1))));
    Node* elements =
        AllocateFixedArray(PACKED_ELEMENTS, elements_length, INTPTR_PARAMETERS,
                           kAllowLargeObjectAllocation);
    VARIABLE(index, MachineType::PointerRepresentation());
    index.Bind(IntPtrConstant(0));
    VariableList foreach_vars({&index}, zone());
    args.ForEach(foreach_vars,
                 [this, elements, &index](Node* arg) {
                   StoreFixedArrayElement(elements, index.value(), arg);
                   Increment(&index);
                 },
                 IntPtrConstant(1));
    argument_array.Bind(elements);
    Goto(&arguments_done);

    BIND(&empty_arguments);
    argument_array.Bind(EmptyFixedArrayConstant());
    Goto(&arguments_done);

    BIND(&arguments_done);
  }

  // Determine bound receiver.
  Comment("Determine bound receiver");
  VARIABLE(bound_receiver, MachineRepresentation::kTagged);
  {
    Label has_receiver(this);
    Label receiver_done(this, &bound_receiver);
    GotoIf(Word32NotEqual(argc, Int32Constant(0)), &has_receiver);
    bound_receiver.Bind(UndefinedConstant());
    Goto(&receiver_done);

    BIND(&has_receiver);
    bound_receiver.Bind(args.AtIndex(0));
    Goto(&receiver_done);

    BIND(&receiver_done);
  }

  // Allocate the resulting bound function.
  Comment("Allocate the resulting bound function");
  {
    Node* bound_function = Allocate(JSBoundFunction::kSize);
    StoreMapNoWriteBarrier(bound_function, bound_function_map.value());
    StoreObjectFieldNoWriteBarrier(
        bound_function, JSBoundFunction::kBoundTargetFunctionOffset, receiver);
    StoreObjectFieldNoWriteBarrier(bound_function,
                                   JSBoundFunction::kBoundThisOffset,
                                   bound_receiver.value());
    StoreObjectFieldNoWriteBarrier(bound_function,
                                   JSBoundFunction::kBoundArgumentsOffset,
                                   argument_array.value());
    Node* empty_fixed_array = EmptyFixedArrayConstant();
    StoreObjectFieldNoWriteBarrier(
        bound_function, JSObject::kPropertiesOrHashOffset, empty_fixed_array);
    StoreObjectFieldNoWriteBarrier(bound_function, JSObject::kElementsOffset,
                                   empty_fixed_array);

    args.PopAndReturn(bound_function);
  }

  BIND(&slow);
  Node* target = LoadFromFrame(StandardFrameConstants::kFunctionOffset,
                               MachineType::TaggedPointer());
  TailCallStub(CodeFactory::FunctionPrototypeBind(isolate()), context, target,
               new_target, argc);
}

// ES6 #sec-function.prototype-@@hasinstance
TF_BUILTIN(FunctionPrototypeHasInstance, CodeStubAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* f = Parameter(Descriptor::kReceiver);
  Node* v = Parameter(Descriptor::kV);
  Node* result = OrdinaryHasInstance(context, f, v);
  Return(result);
}

}  // namespace internal
}  // namespace v8
