// 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-factory.h"
#include "src/code-stub-assembler.h"

namespace v8 {
namespace internal {

// -----------------------------------------------------------------------------
// ES6 section 20.2.2 Function Properties of the Math Object

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

 protected:
  void MathRoundingOperation(
      Node* context, Node* x,
      TNode<Float64T> (CodeStubAssembler::*float64op)(SloppyTNode<Float64T>));
  void MathUnaryOperation(
      Node* context, Node* x,
      TNode<Float64T> (CodeStubAssembler::*float64op)(SloppyTNode<Float64T>));
  void MathMaxMin(Node* context, Node* argc,
                  TNode<Float64T> (CodeStubAssembler::*float64op)(
                      SloppyTNode<Float64T>, SloppyTNode<Float64T>),
                  double default_val);
};

// ES6 #sec-math.abs
TF_BUILTIN(MathAbs, CodeStubAssembler) {
  Node* context = Parameter(Descriptor::kContext);

  // We might need to loop once for ToNumber conversion.
  VARIABLE(var_x, MachineRepresentation::kTagged);
  Label loop(this, &var_x);
  var_x.Bind(Parameter(Descriptor::kX));
  Goto(&loop);
  BIND(&loop);
  {
    // Load the current {x} value.
    Node* x = var_x.value();

    // Check if {x} is a Smi or a HeapObject.
    Label if_xissmi(this), if_xisnotsmi(this);
    Branch(TaggedIsSmi(x), &if_xissmi, &if_xisnotsmi);

    BIND(&if_xissmi);
    {
      Label if_overflow(this, Label::kDeferred), if_notoverflow(this);
      Node* pair = NULL;

      // check if support abs function
      if (IsIntPtrAbsWithOverflowSupported()) {
        pair = IntPtrAbsWithOverflow(x);
        Node* overflow = Projection(1, pair);
        Branch(overflow, &if_overflow, &if_notoverflow);
      } else {
        // Check if {x} is already positive.
        Label if_xispositive(this), if_xisnotpositive(this);
        BranchIfSmiLessThanOrEqual(SmiConstant(0), x, &if_xispositive,
                                   &if_xisnotpositive);

        BIND(&if_xispositive);
        {
          // Just return the input {x}.
          Return(x);
        }

        BIND(&if_xisnotpositive);
        {
          // Try to negate the {x} value.
          pair =
              IntPtrSubWithOverflow(IntPtrConstant(0), BitcastTaggedToWord(x));
          Node* overflow = Projection(1, pair);
          Branch(overflow, &if_overflow, &if_notoverflow);
        }
      }

      BIND(&if_notoverflow);
      {
        // There is a Smi representation for negated {x}.
        Node* result = Projection(0, pair);
        Return(BitcastWordToTagged(result));
      }

      BIND(&if_overflow);
      { Return(NumberConstant(0.0 - Smi::kMinValue)); }
    }

    BIND(&if_xisnotsmi);
    {
      // Check if {x} is a HeapNumber.
      Label if_xisheapnumber(this), if_xisnotheapnumber(this, Label::kDeferred);
      Branch(IsHeapNumber(x), &if_xisheapnumber, &if_xisnotheapnumber);

      BIND(&if_xisheapnumber);
      {
        Node* x_value = LoadHeapNumberValue(x);
        Node* value = Float64Abs(x_value);
        Node* result = AllocateHeapNumberWithValue(value);
        Return(result);
      }

      BIND(&if_xisnotheapnumber);
      {
        // Need to convert {x} to a Number first.
        var_x.Bind(CallBuiltin(Builtins::kNonNumberToNumber, context, x));
        Goto(&loop);
      }
    }
  }
}

void MathBuiltinsAssembler::MathRoundingOperation(
    Node* context, Node* x,
    TNode<Float64T> (CodeStubAssembler::*float64op)(SloppyTNode<Float64T>)) {
  // We might need to loop once for ToNumber conversion.
  VARIABLE(var_x, MachineRepresentation::kTagged, x);
  Label loop(this, &var_x);
  Goto(&loop);
  BIND(&loop);
  {
    // Load the current {x} value.
    Node* x = var_x.value();

    // Check if {x} is a Smi or a HeapObject.
    Label if_xissmi(this), if_xisnotsmi(this);
    Branch(TaggedIsSmi(x), &if_xissmi, &if_xisnotsmi);

    BIND(&if_xissmi);
    {
      // Nothing to do when {x} is a Smi.
      Return(x);
    }

    BIND(&if_xisnotsmi);
    {
      // Check if {x} is a HeapNumber.
      Label if_xisheapnumber(this), if_xisnotheapnumber(this, Label::kDeferred);
      Branch(IsHeapNumber(x), &if_xisheapnumber, &if_xisnotheapnumber);

      BIND(&if_xisheapnumber);
      {
        Node* x_value = LoadHeapNumberValue(x);
        Node* value = (this->*float64op)(x_value);
        Node* result = ChangeFloat64ToTagged(value);
        Return(result);
      }

      BIND(&if_xisnotheapnumber);
      {
        // Need to convert {x} to a Number first.
        var_x.Bind(CallBuiltin(Builtins::kNonNumberToNumber, context, x));
        Goto(&loop);
      }
    }
  }
}

void MathBuiltinsAssembler::MathUnaryOperation(
    Node* context, Node* x,
    TNode<Float64T> (CodeStubAssembler::*float64op)(SloppyTNode<Float64T>)) {
  Node* x_value = TruncateTaggedToFloat64(context, x);
  Node* value = (this->*float64op)(x_value);
  Node* result = AllocateHeapNumberWithValue(value);
  Return(result);
}

void MathBuiltinsAssembler::MathMaxMin(
    Node* context, Node* argc,
    TNode<Float64T> (CodeStubAssembler::*float64op)(SloppyTNode<Float64T>,
                                                    SloppyTNode<Float64T>),
    double default_val) {
  CodeStubArguments arguments(this, ChangeInt32ToIntPtr(argc));
  argc = arguments.GetLength();

  VARIABLE(result, MachineRepresentation::kFloat64);
  result.Bind(Float64Constant(default_val));

  CodeStubAssembler::VariableList vars({&result}, zone());
  arguments.ForEach(vars, [=, &result](Node* arg) {
    Node* float_value = TruncateTaggedToFloat64(context, arg);
    result.Bind((this->*float64op)(result.value(), float_value));
  });

  arguments.PopAndReturn(ChangeFloat64ToTagged(result.value()));
}

// ES6 #sec-math.acos
TF_BUILTIN(MathAcos, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathUnaryOperation(context, x, &CodeStubAssembler::Float64Acos);
}

// ES6 #sec-math.acosh
TF_BUILTIN(MathAcosh, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathUnaryOperation(context, x, &CodeStubAssembler::Float64Acosh);
}

// ES6 #sec-math.asin
TF_BUILTIN(MathAsin, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathUnaryOperation(context, x, &CodeStubAssembler::Float64Asin);
}

// ES6 #sec-math.asinh
TF_BUILTIN(MathAsinh, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathUnaryOperation(context, x, &CodeStubAssembler::Float64Asinh);
}

// ES6 #sec-math.atan
TF_BUILTIN(MathAtan, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathUnaryOperation(context, x, &CodeStubAssembler::Float64Atan);
}

// ES6 #sec-math.atanh
TF_BUILTIN(MathAtanh, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathUnaryOperation(context, x, &CodeStubAssembler::Float64Atanh);
}

// ES6 #sec-math.atan2
TF_BUILTIN(MathAtan2, CodeStubAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* y = Parameter(Descriptor::kY);
  Node* x = Parameter(Descriptor::kX);

  Node* y_value = TruncateTaggedToFloat64(context, y);
  Node* x_value = TruncateTaggedToFloat64(context, x);
  Node* value = Float64Atan2(y_value, x_value);
  Node* result = AllocateHeapNumberWithValue(value);
  Return(result);
}

// ES6 #sec-math.ceil
TF_BUILTIN(MathCeil, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathRoundingOperation(context, x, &CodeStubAssembler::Float64Ceil);
}

// ES6 #sec-math.cbrt
TF_BUILTIN(MathCbrt, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathUnaryOperation(context, x, &CodeStubAssembler::Float64Cbrt);
}

// ES6 #sec-math.clz32
TF_BUILTIN(MathClz32, CodeStubAssembler) {
  Node* context = Parameter(Descriptor::kContext);

  // Shared entry point for the clz32 operation.
  VARIABLE(var_clz32_x, MachineRepresentation::kWord32);
  Label do_clz32(this);

  // We might need to loop once for ToNumber conversion.
  VARIABLE(var_x, MachineRepresentation::kTagged);
  Label loop(this, &var_x);
  var_x.Bind(Parameter(Descriptor::kX));
  Goto(&loop);
  BIND(&loop);
  {
    // Load the current {x} value.
    Node* x = var_x.value();

    // Check if {x} is a Smi or a HeapObject.
    Label if_xissmi(this), if_xisnotsmi(this);
    Branch(TaggedIsSmi(x), &if_xissmi, &if_xisnotsmi);

    BIND(&if_xissmi);
    {
      var_clz32_x.Bind(SmiToWord32(x));
      Goto(&do_clz32);
    }

    BIND(&if_xisnotsmi);
    {
      // Check if {x} is a HeapNumber.
      Label if_xisheapnumber(this), if_xisnotheapnumber(this, Label::kDeferred);
      Branch(IsHeapNumber(x), &if_xisheapnumber, &if_xisnotheapnumber);

      BIND(&if_xisheapnumber);
      {
        var_clz32_x.Bind(TruncateHeapNumberValueToWord32(x));
        Goto(&do_clz32);
      }

      BIND(&if_xisnotheapnumber);
      {
        // Need to convert {x} to a Number first.
        var_x.Bind(CallBuiltin(Builtins::kNonNumberToNumber, context, x));
        Goto(&loop);
      }
    }
  }

  BIND(&do_clz32);
  {
    Node* x_value = var_clz32_x.value();
    Node* value = Word32Clz(x_value);
    Node* result = ChangeInt32ToTagged(value);
    Return(result);
  }
}

// ES6 #sec-math.cos
TF_BUILTIN(MathCos, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathUnaryOperation(context, x, &CodeStubAssembler::Float64Cos);
}

// ES6 #sec-math.cosh
TF_BUILTIN(MathCosh, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathUnaryOperation(context, x, &CodeStubAssembler::Float64Cosh);
}

// ES6 #sec-math.exp
TF_BUILTIN(MathExp, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathUnaryOperation(context, x, &CodeStubAssembler::Float64Exp);
}

// ES6 #sec-math.expm1
TF_BUILTIN(MathExpm1, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathUnaryOperation(context, x, &CodeStubAssembler::Float64Expm1);
}

// ES6 #sec-math.floor
TF_BUILTIN(MathFloor, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathRoundingOperation(context, x, &CodeStubAssembler::Float64Floor);
}

// ES6 #sec-math.fround
TF_BUILTIN(MathFround, CodeStubAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  Node* x_value = TruncateTaggedToFloat64(context, x);
  Node* value32 = TruncateFloat64ToFloat32(x_value);
  Node* value = ChangeFloat32ToFloat64(value32);
  Node* result = AllocateHeapNumberWithValue(value);
  Return(result);
}

// ES6 #sec-math.imul
TF_BUILTIN(MathImul, CodeStubAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  Node* y = Parameter(Descriptor::kY);
  Node* x_value = TruncateTaggedToWord32(context, x);
  Node* y_value = TruncateTaggedToWord32(context, y);
  Node* value = Int32Mul(x_value, y_value);
  Node* result = ChangeInt32ToTagged(value);
  Return(result);
}

// ES6 #sec-math.log
TF_BUILTIN(MathLog, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathUnaryOperation(context, x, &CodeStubAssembler::Float64Log);
}

// ES6 #sec-math.log1p
TF_BUILTIN(MathLog1p, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathUnaryOperation(context, x, &CodeStubAssembler::Float64Log1p);
}

// ES6 #sec-math.log10
TF_BUILTIN(MathLog10, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathUnaryOperation(context, x, &CodeStubAssembler::Float64Log10);
}

// ES6 #sec-math.log2
TF_BUILTIN(MathLog2, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathUnaryOperation(context, x, &CodeStubAssembler::Float64Log2);
}

// ES6 #sec-math.pow
TF_BUILTIN(MathPow, CodeStubAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kBase);
  Node* y = Parameter(Descriptor::kExponent);
  Node* x_value = TruncateTaggedToFloat64(context, x);
  Node* y_value = TruncateTaggedToFloat64(context, y);
  Node* value = Float64Pow(x_value, y_value);
  Node* result = ChangeFloat64ToTagged(value);
  Return(result);
}

// ES6 #sec-math.random
TF_BUILTIN(MathRandom, CodeStubAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* native_context = LoadNativeContext(context);

  // Load cache index.
  VARIABLE(smi_index, MachineRepresentation::kTagged);
  smi_index.Bind(
      LoadContextElement(native_context, Context::MATH_RANDOM_INDEX_INDEX));

  // Cached random numbers are exhausted if index is 0. Go to slow path.
  Label if_cached(this);
  GotoIf(SmiAbove(smi_index.value(), SmiConstant(0)), &if_cached);

  // Cache exhausted, populate the cache. Return value is the new index.
  smi_index.Bind(CallRuntime(Runtime::kGenerateRandomNumbers, context));
  Goto(&if_cached);

  // Compute next index by decrement.
  BIND(&if_cached);
  Node* new_smi_index = SmiSub(smi_index.value(), SmiConstant(1));
  StoreContextElement(native_context, Context::MATH_RANDOM_INDEX_INDEX,
                      new_smi_index);

  // Load and return next cached random number.
  Node* array =
      LoadContextElement(native_context, Context::MATH_RANDOM_CACHE_INDEX);
  Node* random = LoadFixedDoubleArrayElement(
      array, new_smi_index, MachineType::Float64(), 0, SMI_PARAMETERS);
  Return(AllocateHeapNumberWithValue(random));
}

// ES6 #sec-math.round
TF_BUILTIN(MathRound, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathRoundingOperation(context, x, &CodeStubAssembler::Float64Round);
}

// ES6 #sec-math.sign
TF_BUILTIN(MathSign, CodeStubAssembler) {
  // Convert the {x} value to a Number.
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  Node* x_value = TruncateTaggedToFloat64(context, x);

  // Return -1 if {x} is negative, 1 if {x} is positive, or {x} itself.
  Label if_xisnegative(this), if_xispositive(this);
  GotoIf(Float64LessThan(x_value, Float64Constant(0.0)), &if_xisnegative);
  GotoIf(Float64LessThan(Float64Constant(0.0), x_value), &if_xispositive);
  Return(ChangeFloat64ToTagged(x_value));

  BIND(&if_xisnegative);
  Return(SmiConstant(-1));

  BIND(&if_xispositive);
  Return(SmiConstant(1));
}

// ES6 #sec-math.sin
TF_BUILTIN(MathSin, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathUnaryOperation(context, x, &CodeStubAssembler::Float64Sin);
}

// ES6 #sec-math.sinh
TF_BUILTIN(MathSinh, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathUnaryOperation(context, x, &CodeStubAssembler::Float64Sinh);
}

// ES6 #sec-math.sqrt
TF_BUILTIN(MathSqrt, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathUnaryOperation(context, x, &CodeStubAssembler::Float64Sqrt);
}

// ES6 #sec-math.tan
TF_BUILTIN(MathTan, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathUnaryOperation(context, x, &CodeStubAssembler::Float64Tan);
}

// ES6 #sec-math.tanh
TF_BUILTIN(MathTanh, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathUnaryOperation(context, x, &CodeStubAssembler::Float64Tanh);
}

// ES6 #sec-math.trunc
TF_BUILTIN(MathTrunc, MathBuiltinsAssembler) {
  Node* context = Parameter(Descriptor::kContext);
  Node* x = Parameter(Descriptor::kX);
  MathRoundingOperation(context, x, &CodeStubAssembler::Float64Trunc);
}

// ES6 #sec-math.max
TF_BUILTIN(MathMax, MathBuiltinsAssembler) {
  // TODO(ishell): use constants from Descriptor once the JSFunction linkage
  // arguments are reordered.
  Node* context = Parameter(BuiltinDescriptor::kContext);
  Node* argc = Parameter(BuiltinDescriptor::kArgumentsCount);
  MathMaxMin(context, argc, &CodeStubAssembler::Float64Max, -1.0 * V8_INFINITY);
}

// ES6 #sec-math.min
TF_BUILTIN(MathMin, MathBuiltinsAssembler) {
  // TODO(ishell): use constants from Descriptor once the JSFunction linkage
  // arguments are reordered.
  Node* context = Parameter(BuiltinDescriptor::kContext);
  Node* argc = Parameter(BuiltinDescriptor::kArgumentsCount);
  MathMaxMin(context, argc, &CodeStubAssembler::Float64Min, V8_INFINITY);
}

}  // namespace internal
}  // namespace v8
