// 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-arguments.h"
#include "src/builtins/builtins-utils.h"
#include "src/builtins/builtins.h"
#include "src/code-factory.h"
#include "src/code-stub-assembler.h"
#include "src/interface-descriptors.h"
#include "src/objects-inl.h"

namespace v8 {
namespace internal {

typedef compiler::Node Node;

std::tuple<Node*, Node*, Node*>
ArgumentsBuiltinsAssembler::GetArgumentsFrameAndCount(Node* function,
                                                      ParameterMode mode) {
  CSA_ASSERT(this, HasInstanceType(function, JS_FUNCTION_TYPE));

  Variable frame_ptr(this, MachineType::PointerRepresentation());
  frame_ptr.Bind(LoadParentFramePointer());
  CSA_ASSERT(this,
             WordEqual(function,
                       LoadBufferObject(frame_ptr.value(),
                                        StandardFrameConstants::kFunctionOffset,
                                        MachineType::Pointer())));
  Variable argument_count(this, ParameterRepresentation(mode));
  VariableList list({&frame_ptr, &argument_count}, zone());
  Label done_argument_count(this, list);

  // Determine the number of passed parameters, which is either the count stored
  // in an arguments adapter frame or fetched from the shared function info.
  Node* frame_ptr_above = LoadBufferObject(
      frame_ptr.value(), StandardFrameConstants::kCallerFPOffset,
      MachineType::Pointer());
  Node* shared =
      LoadObjectField(function, JSFunction::kSharedFunctionInfoOffset);
  Node* formal_parameter_count = LoadSharedFunctionInfoSpecialField(
      shared, SharedFunctionInfo::kFormalParameterCountOffset, mode);
  argument_count.Bind(formal_parameter_count);
  Node* marker_or_function = LoadBufferObject(
      frame_ptr_above, CommonFrameConstants::kContextOrFrameTypeOffset);
  GotoIf(
      MarkerIsNotFrameType(marker_or_function, StackFrame::ARGUMENTS_ADAPTOR),
      &done_argument_count);
  Node* adapted_parameter_count = LoadBufferObject(
      frame_ptr_above, ArgumentsAdaptorFrameConstants::kLengthOffset);
  frame_ptr.Bind(frame_ptr_above);
  argument_count.Bind(TaggedToParameter(adapted_parameter_count, mode));
  Goto(&done_argument_count);

  Bind(&done_argument_count);
  return std::tuple<Node*, Node*, Node*>(
      frame_ptr.value(), argument_count.value(), formal_parameter_count);
}

std::tuple<Node*, Node*, Node*>
ArgumentsBuiltinsAssembler::AllocateArgumentsObject(Node* map,
                                                    Node* arguments_count,
                                                    Node* parameter_map_count,
                                                    ParameterMode mode,
                                                    int base_size) {
  // Allocate the parameter object (either a Rest parameter object, a strict
  // argument object or a sloppy arguments object) and the elements/mapped
  // arguments together.
  int elements_offset = base_size;
  Node* element_count = arguments_count;
  if (parameter_map_count != nullptr) {
    base_size += FixedArray::kHeaderSize;
    element_count = IntPtrOrSmiAdd(element_count, parameter_map_count, mode);
  }
  bool empty = IsIntPtrOrSmiConstantZero(arguments_count);
  DCHECK_IMPLIES(empty, parameter_map_count == nullptr);
  Node* size =
      empty ? IntPtrConstant(base_size)
            : ElementOffsetFromIndex(element_count, FAST_ELEMENTS, mode,
                                     base_size + FixedArray::kHeaderSize);
  Node* result = Allocate(size);
  Comment("Initialize arguments object");
  StoreMapNoWriteBarrier(result, map);
  Node* empty_fixed_array = LoadRoot(Heap::kEmptyFixedArrayRootIndex);
  StoreObjectField(result, JSArray::kPropertiesOffset, empty_fixed_array);
  Node* smi_arguments_count = ParameterToTagged(arguments_count, mode);
  StoreObjectFieldNoWriteBarrier(result, JSArray::kLengthOffset,
                                 smi_arguments_count);
  Node* arguments = nullptr;
  if (!empty) {
    arguments = InnerAllocate(result, elements_offset);
    StoreObjectFieldNoWriteBarrier(arguments, FixedArray::kLengthOffset,
                                   smi_arguments_count);
    Node* fixed_array_map = LoadRoot(Heap::kFixedArrayMapRootIndex);
    StoreMapNoWriteBarrier(arguments, fixed_array_map);
  }
  Node* parameter_map = nullptr;
  if (parameter_map_count != nullptr) {
    Node* parameter_map_offset = ElementOffsetFromIndex(
        arguments_count, FAST_ELEMENTS, mode, FixedArray::kHeaderSize);
    parameter_map = InnerAllocate(arguments, parameter_map_offset);
    StoreObjectFieldNoWriteBarrier(result, JSArray::kElementsOffset,
                                   parameter_map);
    Node* sloppy_elements_map =
        LoadRoot(Heap::kSloppyArgumentsElementsMapRootIndex);
    StoreMapNoWriteBarrier(parameter_map, sloppy_elements_map);
    parameter_map_count = ParameterToTagged(parameter_map_count, mode);
    StoreObjectFieldNoWriteBarrier(parameter_map, FixedArray::kLengthOffset,
                                   parameter_map_count);
  } else {
    if (empty) {
      StoreObjectFieldNoWriteBarrier(result, JSArray::kElementsOffset,
                                     empty_fixed_array);
    } else {
      StoreObjectFieldNoWriteBarrier(result, JSArray::kElementsOffset,
                                     arguments);
    }
  }
  return std::tuple<Node*, Node*, Node*>(result, arguments, parameter_map);
}

Node* ArgumentsBuiltinsAssembler::ConstructParametersObjectFromArgs(
    Node* map, Node* frame_ptr, Node* arg_count, Node* first_arg,
    Node* rest_count, ParameterMode param_mode, int base_size) {
  // Allocate the parameter object (either a Rest parameter object, a strict
  // argument object or a sloppy arguments object) and the elements together and
  // fill in the contents with the arguments above |formal_parameter_count|.
  Node* result;
  Node* elements;
  Node* unused;
  std::tie(result, elements, unused) =
      AllocateArgumentsObject(map, rest_count, nullptr, param_mode, base_size);
  DCHECK(unused == nullptr);
  CodeStubArguments arguments(this, arg_count, frame_ptr, param_mode);
  Variable offset(this, MachineType::PointerRepresentation());
  offset.Bind(IntPtrConstant(FixedArrayBase::kHeaderSize - kHeapObjectTag));
  VariableList list({&offset}, zone());
  arguments.ForEach(list,
                    [this, elements, &offset](Node* arg) {
                      StoreNoWriteBarrier(MachineRepresentation::kTagged,
                                          elements, offset.value(), arg);
                      Increment(offset, kPointerSize);
                    },
                    first_arg, nullptr, param_mode);
  return result;
}

Node* ArgumentsBuiltinsAssembler::EmitFastNewRestParameter(Node* context,
                                                           Node* function) {
  Node* frame_ptr;
  Node* argument_count;
  Node* formal_parameter_count;

  ParameterMode mode = OptimalParameterMode();
  Node* zero = IntPtrOrSmiConstant(0, mode);

  std::tie(frame_ptr, argument_count, formal_parameter_count) =
      GetArgumentsFrameAndCount(function, mode);

  Variable result(this, MachineRepresentation::kTagged);
  Label no_rest_parameters(this), runtime(this, Label::kDeferred),
      done(this, &result);

  Node* rest_count =
      IntPtrOrSmiSub(argument_count, formal_parameter_count, mode);
  Node* const native_context = LoadNativeContext(context);
  Node* const array_map = LoadJSArrayElementsMap(FAST_ELEMENTS, native_context);
  GotoIf(IntPtrOrSmiLessThanOrEqual(rest_count, zero, mode),
         &no_rest_parameters);

  GotoIfFixedArraySizeDoesntFitInNewSpace(
      rest_count, &runtime, JSArray::kSize + FixedArray::kHeaderSize, mode);

  // Allocate the Rest JSArray and the elements together and fill in the
  // contents with the arguments above |formal_parameter_count|.
  result.Bind(ConstructParametersObjectFromArgs(
      array_map, frame_ptr, argument_count, formal_parameter_count, rest_count,
      mode, JSArray::kSize));
  Goto(&done);

  Bind(&no_rest_parameters);
  {
    Node* arguments;
    Node* elements;
    Node* unused;
    std::tie(arguments, elements, unused) =
        AllocateArgumentsObject(array_map, zero, nullptr, mode, JSArray::kSize);
    result.Bind(arguments);
    Goto(&done);
  }

  Bind(&runtime);
  {
    result.Bind(CallRuntime(Runtime::kNewRestParameter, context, function));
    Goto(&done);
  }

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

TF_BUILTIN(FastNewRestParameter, ArgumentsBuiltinsAssembler) {
  Node* function = Parameter(FastNewArgumentsDescriptor::kFunction);
  Node* context = Parameter(FastNewArgumentsDescriptor::kContext);
  Return(EmitFastNewRestParameter(context, function));
}

Node* ArgumentsBuiltinsAssembler::EmitFastNewStrictArguments(Node* context,
                                                             Node* function) {
  Variable result(this, MachineRepresentation::kTagged);
  Label done(this, &result), empty(this), runtime(this, Label::kDeferred);

  Node* frame_ptr;
  Node* argument_count;
  Node* formal_parameter_count;

  ParameterMode mode = OptimalParameterMode();
  Node* zero = IntPtrOrSmiConstant(0, mode);

  std::tie(frame_ptr, argument_count, formal_parameter_count) =
      GetArgumentsFrameAndCount(function, mode);

  GotoIfFixedArraySizeDoesntFitInNewSpace(
      argument_count, &runtime,
      JSStrictArgumentsObject::kSize + FixedArray::kHeaderSize, mode);

  Node* const native_context = LoadNativeContext(context);
  Node* const map =
      LoadContextElement(native_context, Context::STRICT_ARGUMENTS_MAP_INDEX);
  GotoIf(WordEqual(argument_count, zero), &empty);

  result.Bind(ConstructParametersObjectFromArgs(
      map, frame_ptr, argument_count, zero, argument_count, mode,
      JSStrictArgumentsObject::kSize));
  Goto(&done);

  Bind(&empty);
  {
    Node* arguments;
    Node* elements;
    Node* unused;
    std::tie(arguments, elements, unused) = AllocateArgumentsObject(
        map, zero, nullptr, mode, JSStrictArgumentsObject::kSize);
    result.Bind(arguments);
    Goto(&done);
  }

  Bind(&runtime);
  {
    result.Bind(CallRuntime(Runtime::kNewStrictArguments, context, function));
    Goto(&done);
  }

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

TF_BUILTIN(FastNewStrictArguments, ArgumentsBuiltinsAssembler) {
  Node* function = Parameter(FastNewArgumentsDescriptor::kFunction);
  Node* context = Parameter(FastNewArgumentsDescriptor::kContext);
  Return(EmitFastNewStrictArguments(context, function));
}

Node* ArgumentsBuiltinsAssembler::EmitFastNewSloppyArguments(Node* context,
                                                             Node* function) {
  Node* frame_ptr;
  Node* argument_count;
  Node* formal_parameter_count;
  Variable result(this, MachineRepresentation::kTagged);

  ParameterMode mode = OptimalParameterMode();
  Node* zero = IntPtrOrSmiConstant(0, mode);

  Label done(this, &result), empty(this), no_parameters(this),
      runtime(this, Label::kDeferred);

  std::tie(frame_ptr, argument_count, formal_parameter_count) =
      GetArgumentsFrameAndCount(function, mode);

  GotoIf(WordEqual(argument_count, zero), &empty);

  GotoIf(WordEqual(formal_parameter_count, zero), &no_parameters);

  {
    Comment("Mapped parameter JSSloppyArgumentsObject");

    Node* mapped_count =
        IntPtrOrSmiMin(argument_count, formal_parameter_count, mode);

    Node* parameter_map_size =
        IntPtrOrSmiAdd(mapped_count, IntPtrOrSmiConstant(2, mode), mode);

    // Verify that the overall allocation will fit in new space.
    Node* elements_allocated =
        IntPtrOrSmiAdd(argument_count, parameter_map_size, mode);
    GotoIfFixedArraySizeDoesntFitInNewSpace(
        elements_allocated, &runtime,
        JSSloppyArgumentsObject::kSize + FixedArray::kHeaderSize * 2, mode);

    Node* const native_context = LoadNativeContext(context);
    Node* const map = LoadContextElement(
        native_context, Context::FAST_ALIASED_ARGUMENTS_MAP_INDEX);
    Node* argument_object;
    Node* elements;
    Node* map_array;
    std::tie(argument_object, elements, map_array) =
        AllocateArgumentsObject(map, argument_count, parameter_map_size, mode,
                                JSSloppyArgumentsObject::kSize);
    StoreObjectFieldNoWriteBarrier(
        argument_object, JSSloppyArgumentsObject::kCalleeOffset, function);
    StoreFixedArrayElement(map_array, 0, context, SKIP_WRITE_BARRIER);
    StoreFixedArrayElement(map_array, 1, elements, SKIP_WRITE_BARRIER);

    Comment("Fill in non-mapped parameters");
    Node* argument_offset =
        ElementOffsetFromIndex(argument_count, FAST_ELEMENTS, mode,
                               FixedArray::kHeaderSize - kHeapObjectTag);
    Node* mapped_offset =
        ElementOffsetFromIndex(mapped_count, FAST_ELEMENTS, mode,
                               FixedArray::kHeaderSize - kHeapObjectTag);
    CodeStubArguments arguments(this, argument_count, frame_ptr, mode);
    Variable current_argument(this, MachineType::PointerRepresentation());
    current_argument.Bind(arguments.AtIndexPtr(argument_count, mode));
    VariableList var_list1({&current_argument}, zone());
    mapped_offset = BuildFastLoop(
        var_list1, argument_offset, mapped_offset,
        [this, elements, &current_argument](Node* offset) {
          Increment(current_argument, kPointerSize);
          Node* arg = LoadBufferObject(current_argument.value(), 0);
          StoreNoWriteBarrier(MachineRepresentation::kTagged, elements, offset,
                              arg);
        },
        -kPointerSize, INTPTR_PARAMETERS);

    // Copy the parameter slots and the holes in the arguments.
    // We need to fill in mapped_count slots. They index the context,
    // where parameters are stored in reverse order, at
    //   MIN_CONTEXT_SLOTS .. MIN_CONTEXT_SLOTS+argument_count-1
    // The mapped parameter thus need to get indices
    //   MIN_CONTEXT_SLOTS+parameter_count-1 ..
    //       MIN_CONTEXT_SLOTS+argument_count-mapped_count
    // We loop from right to left.
    Comment("Fill in mapped parameters");
    Variable context_index(this, OptimalParameterRepresentation());
    context_index.Bind(IntPtrOrSmiSub(
        IntPtrOrSmiAdd(IntPtrOrSmiConstant(Context::MIN_CONTEXT_SLOTS, mode),
                       formal_parameter_count, mode),
        mapped_count, mode));
    Node* the_hole = TheHoleConstant();
    VariableList var_list2({&context_index}, zone());
    const int kParameterMapHeaderSize =
        FixedArray::kHeaderSize + 2 * kPointerSize;
    Node* adjusted_map_array = IntPtrAdd(
        BitcastTaggedToWord(map_array),
        IntPtrConstant(kParameterMapHeaderSize - FixedArray::kHeaderSize));
    Node* zero_offset = ElementOffsetFromIndex(
        zero, FAST_ELEMENTS, mode, FixedArray::kHeaderSize - kHeapObjectTag);
    BuildFastLoop(var_list2, mapped_offset, zero_offset,
                  [this, the_hole, elements, adjusted_map_array, &context_index,
                   mode](Node* offset) {
                    StoreNoWriteBarrier(MachineRepresentation::kTagged,
                                        elements, offset, the_hole);
                    StoreNoWriteBarrier(
                        MachineRepresentation::kTagged, adjusted_map_array,
                        offset, ParameterToTagged(context_index.value(), mode));
                    Increment(context_index, 1, mode);
                  },
                  -kPointerSize, INTPTR_PARAMETERS);

    result.Bind(argument_object);
    Goto(&done);
  }

  Bind(&no_parameters);
  {
    Comment("No parameters JSSloppyArgumentsObject");
    GotoIfFixedArraySizeDoesntFitInNewSpace(
        argument_count, &runtime,
        JSSloppyArgumentsObject::kSize + FixedArray::kHeaderSize, mode);
    Node* const native_context = LoadNativeContext(context);
    Node* const map =
        LoadContextElement(native_context, Context::SLOPPY_ARGUMENTS_MAP_INDEX);
    result.Bind(ConstructParametersObjectFromArgs(
        map, frame_ptr, argument_count, zero, argument_count, mode,
        JSSloppyArgumentsObject::kSize));
    StoreObjectFieldNoWriteBarrier(
        result.value(), JSSloppyArgumentsObject::kCalleeOffset, function);
    Goto(&done);
  }

  Bind(&empty);
  {
    Comment("Empty JSSloppyArgumentsObject");
    Node* const native_context = LoadNativeContext(context);
    Node* const map =
        LoadContextElement(native_context, Context::SLOPPY_ARGUMENTS_MAP_INDEX);
    Node* arguments;
    Node* elements;
    Node* unused;
    std::tie(arguments, elements, unused) = AllocateArgumentsObject(
        map, zero, nullptr, mode, JSSloppyArgumentsObject::kSize);
    result.Bind(arguments);
    StoreObjectFieldNoWriteBarrier(
        result.value(), JSSloppyArgumentsObject::kCalleeOffset, function);
    Goto(&done);
  }

  Bind(&runtime);
  {
    result.Bind(CallRuntime(Runtime::kNewSloppyArguments, context, function));
    Goto(&done);
  }

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

TF_BUILTIN(FastNewSloppyArguments, ArgumentsBuiltinsAssembler) {
  Node* function = Parameter(FastNewArgumentsDescriptor::kFunction);
  Node* context = Parameter(FastNewArgumentsDescriptor::kContext);
  Return(EmitFastNewSloppyArguments(context, function));
}

}  // namespace internal
}  // namespace v8