// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/ic/binary-op-assembler.h"

#include "src/globals.h"

namespace v8 {
namespace internal {

using compiler::Node;

Node* BinaryOpAssembler::Generate_AddWithFeedback(Node* context, Node* lhs,
                                                  Node* rhs, Node* slot_id,
                                                  Node* feedback_vector,
                                                  Node* function,
                                                  bool rhs_is_smi) {
  // Shared entry for floating point addition.
  Label do_fadd(this), if_lhsisnotnumber(this, Label::kDeferred),
      check_rhsisoddball(this, Label::kDeferred),
      call_with_oddball_feedback(this), call_with_any_feedback(this),
      call_add_stub(this), end(this);
  VARIABLE(var_fadd_lhs, MachineRepresentation::kFloat64);
  VARIABLE(var_fadd_rhs, MachineRepresentation::kFloat64);
  VARIABLE(var_type_feedback, MachineRepresentation::kTaggedSigned);
  VARIABLE(var_result, MachineRepresentation::kTagged);

  // Check if the {lhs} is a Smi or a HeapObject.
  Label if_lhsissmi(this);
  // If rhs is known to be an Smi we want to fast path Smi operation. This is
  // for AddSmi operation. For the normal Add operation, we want to fast path
  // both Smi and Number operations, so this path should not be marked as
  // Deferred.
  Label if_lhsisnotsmi(this,
                       rhs_is_smi ? Label::kDeferred : Label::kNonDeferred);
  Branch(TaggedIsNotSmi(lhs), &if_lhsisnotsmi, &if_lhsissmi);

  BIND(&if_lhsissmi);
  {
    Comment("lhs is Smi");
    if (!rhs_is_smi) {
      // Check if the {rhs} is also a Smi.
      Label if_rhsissmi(this), if_rhsisnotsmi(this);
      Branch(TaggedIsSmi(rhs), &if_rhsissmi, &if_rhsisnotsmi);

      BIND(&if_rhsisnotsmi);
      {
        // Check if the {rhs} is a HeapNumber.
        GotoIfNot(IsHeapNumber(rhs), &check_rhsisoddball);

        var_fadd_lhs.Bind(SmiToFloat64(lhs));
        var_fadd_rhs.Bind(LoadHeapNumberValue(rhs));
        Goto(&do_fadd);
      }

      BIND(&if_rhsissmi);
    }

    {
      Comment("perform smi operation");
      // Try fast Smi addition first.
      Node* pair = IntPtrAddWithOverflow(BitcastTaggedToWord(lhs),
                                         BitcastTaggedToWord(rhs));
      Node* overflow = Projection(1, pair);

      // Check if the Smi additon overflowed.
      // If rhs is known to be an Smi we want to fast path Smi operation. This
      // is for AddSmi operation. For the normal Add operation, we want to fast
      // path both Smi and Number operations, so this path should not be marked
      // as Deferred.
      Label if_overflow(this,
                        rhs_is_smi ? Label::kDeferred : Label::kNonDeferred),
          if_notoverflow(this);
      Branch(overflow, &if_overflow, &if_notoverflow);

      BIND(&if_overflow);
      {
        var_fadd_lhs.Bind(SmiToFloat64(lhs));
        var_fadd_rhs.Bind(SmiToFloat64(rhs));
        Goto(&do_fadd);
      }

      BIND(&if_notoverflow);
      {
        var_type_feedback.Bind(
            SmiConstant(BinaryOperationFeedback::kSignedSmall));
        var_result.Bind(BitcastWordToTaggedSigned(Projection(0, pair)));
        Goto(&end);
      }
    }
  }

  BIND(&if_lhsisnotsmi);
  {
    // Check if {lhs} is a HeapNumber.
    GotoIfNot(IsHeapNumber(lhs), &if_lhsisnotnumber);

    if (!rhs_is_smi) {
      // Check if the {rhs} is Smi.
      Label if_rhsissmi(this), if_rhsisnotsmi(this);
      Branch(TaggedIsSmi(rhs), &if_rhsissmi, &if_rhsisnotsmi);

      BIND(&if_rhsisnotsmi);
      {
        // Check if the {rhs} is a HeapNumber.
        GotoIfNot(IsHeapNumber(rhs), &check_rhsisoddball);

        var_fadd_lhs.Bind(LoadHeapNumberValue(lhs));
        var_fadd_rhs.Bind(LoadHeapNumberValue(rhs));
        Goto(&do_fadd);
      }

      BIND(&if_rhsissmi);
    }
    {
      var_fadd_lhs.Bind(LoadHeapNumberValue(lhs));
      var_fadd_rhs.Bind(SmiToFloat64(rhs));
      Goto(&do_fadd);
    }
  }

  BIND(&do_fadd);
  {
    var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kNumber));
    Node* value = Float64Add(var_fadd_lhs.value(), var_fadd_rhs.value());
    Node* result = AllocateHeapNumberWithValue(value);
    var_result.Bind(result);
    Goto(&end);
  }

  BIND(&if_lhsisnotnumber);
  {
    // No checks on rhs are done yet. We just know lhs is not a number or Smi.
    Label if_lhsisoddball(this), if_lhsisnotoddball(this);
    Node* lhs_instance_type = LoadInstanceType(lhs);
    Node* lhs_is_oddball =
        Word32Equal(lhs_instance_type, Int32Constant(ODDBALL_TYPE));
    Branch(lhs_is_oddball, &if_lhsisoddball, &if_lhsisnotoddball);

    BIND(&if_lhsisoddball);
    {
      GotoIf(TaggedIsSmi(rhs), &call_with_oddball_feedback);

      // Check if {rhs} is a HeapNumber.
      Branch(IsHeapNumber(rhs), &call_with_oddball_feedback,
             &check_rhsisoddball);
    }

    BIND(&if_lhsisnotoddball);
    {
      // Exit unless {lhs} is a string
      GotoIfNot(IsStringInstanceType(lhs_instance_type),
                &call_with_any_feedback);

      // Check if the {rhs} is a smi, and exit the string check early if it is.
      GotoIf(TaggedIsSmi(rhs), &call_with_any_feedback);

      Node* rhs_instance_type = LoadInstanceType(rhs);

      // Exit unless {rhs} is a string. Since {lhs} is a string we no longer
      // need an Oddball check.
      GotoIfNot(IsStringInstanceType(rhs_instance_type),
                &call_with_any_feedback);

      var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kString));
      Callable callable =
          CodeFactory::StringAdd(isolate(), STRING_ADD_CHECK_NONE, NOT_TENURED);
      var_result.Bind(CallStub(callable, context, lhs, rhs));

      Goto(&end);
    }
  }

  BIND(&check_rhsisoddball);
  {
    // Check if rhs is an oddball. At this point we know lhs is either a
    // Smi or number or oddball and rhs is not a number or Smi.
    Node* rhs_instance_type = LoadInstanceType(rhs);
    Node* rhs_is_oddball =
        Word32Equal(rhs_instance_type, Int32Constant(ODDBALL_TYPE));
    Branch(rhs_is_oddball, &call_with_oddball_feedback,
           &call_with_any_feedback);
  }

  BIND(&call_with_oddball_feedback);
  {
    var_type_feedback.Bind(
        SmiConstant(BinaryOperationFeedback::kNumberOrOddball));
    Goto(&call_add_stub);
  }

  BIND(&call_with_any_feedback);
  {
    var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kAny));
    Goto(&call_add_stub);
  }

  BIND(&call_add_stub);
  {
    var_result.Bind(CallBuiltin(Builtins::kAdd, context, lhs, rhs));
    Goto(&end);
  }

  BIND(&end);
  UpdateFeedback(var_type_feedback.value(), feedback_vector, slot_id, function);
  return var_result.value();
}

Node* BinaryOpAssembler::Generate_BinaryOperationWithFeedback(
    Node* context, Node* lhs, Node* rhs, Node* slot_id, Node* feedback_vector,
    Node* function, const SmiOperation& smiOperation,
    const FloatOperation& floatOperation, Token::Value opcode,
    bool rhs_is_smi) {
  Label do_float_operation(this), end(this), call_stub(this),
      check_rhsisoddball(this, Label::kDeferred), call_with_any_feedback(this),
      if_lhsisnotnumber(this, Label::kDeferred);
  VARIABLE(var_float_lhs, MachineRepresentation::kFloat64);
  VARIABLE(var_float_rhs, MachineRepresentation::kFloat64);
  VARIABLE(var_type_feedback, MachineRepresentation::kTaggedSigned);
  VARIABLE(var_result, MachineRepresentation::kTagged);

  Label if_lhsissmi(this);
  // If rhs is known to be an Smi (in the SubSmi, MulSmi, DivSmi, ModSmi
  // bytecode handlers) we want to fast path Smi operation. For the normal
  // operation, we want to fast path both Smi and Number operations, so this
  // path should not be marked as Deferred.
  Label if_lhsisnotsmi(this,
                       rhs_is_smi ? Label::kDeferred : Label::kNonDeferred);
  Branch(TaggedIsNotSmi(lhs), &if_lhsisnotsmi, &if_lhsissmi);

  // Check if the {lhs} is a Smi or a HeapObject.
  BIND(&if_lhsissmi);
  {
    Comment("lhs is Smi");
    if (!rhs_is_smi) {
      // Check if the {rhs} is also a Smi.
      Label if_rhsissmi(this), if_rhsisnotsmi(this);
      Branch(TaggedIsSmi(rhs), &if_rhsissmi, &if_rhsisnotsmi);
      BIND(&if_rhsisnotsmi);
      {
        // Check if {rhs} is a HeapNumber.
        GotoIfNot(IsHeapNumber(rhs), &check_rhsisoddball);

        // Perform a floating point operation.
        var_float_lhs.Bind(SmiToFloat64(lhs));
        var_float_rhs.Bind(LoadHeapNumberValue(rhs));
        Goto(&do_float_operation);
      }

      BIND(&if_rhsissmi);
    }

    {
      Comment("perform smi operation");
      var_result.Bind(smiOperation(lhs, rhs, &var_type_feedback));
      Goto(&end);
    }
  }

  BIND(&if_lhsisnotsmi);
  {
    Comment("lhs is not Smi");
    // Check if the {lhs} is a HeapNumber.
    GotoIfNot(IsHeapNumber(lhs), &if_lhsisnotnumber);

    if (!rhs_is_smi) {
      // Check if the {rhs} is a Smi.
      Label if_rhsissmi(this), if_rhsisnotsmi(this);
      Branch(TaggedIsSmi(rhs), &if_rhsissmi, &if_rhsisnotsmi);

      BIND(&if_rhsisnotsmi);
      {
        // Check if the {rhs} is a HeapNumber.
        GotoIfNot(IsHeapNumber(rhs), &check_rhsisoddball);

        // Perform a floating point operation.
        var_float_lhs.Bind(LoadHeapNumberValue(lhs));
        var_float_rhs.Bind(LoadHeapNumberValue(rhs));
        Goto(&do_float_operation);
      }

      BIND(&if_rhsissmi);
    }

    {
      // Perform a floating point subtraction.
      var_float_lhs.Bind(LoadHeapNumberValue(lhs));
      var_float_rhs.Bind(SmiToFloat64(rhs));
      Goto(&do_float_operation);
    }
  }

  BIND(&do_float_operation);
  {
    var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kNumber));
    Node* lhs_value = var_float_lhs.value();
    Node* rhs_value = var_float_rhs.value();
    Node* value = floatOperation(lhs_value, rhs_value);
    var_result.Bind(AllocateHeapNumberWithValue(value));
    Goto(&end);
  }

  BIND(&if_lhsisnotnumber);
  {
    // No checks on rhs are done yet. We just know lhs is not a number or Smi.
    // Check if lhs is an oddball.
    Node* lhs_instance_type = LoadInstanceType(lhs);
    Node* lhs_is_oddball =
        Word32Equal(lhs_instance_type, Int32Constant(ODDBALL_TYPE));
    GotoIfNot(lhs_is_oddball, &call_with_any_feedback);

    Label if_rhsissmi(this), if_rhsisnotsmi(this);
    Branch(TaggedIsSmi(rhs), &if_rhsissmi, &if_rhsisnotsmi);

    BIND(&if_rhsissmi);
    {
      var_type_feedback.Bind(
          SmiConstant(BinaryOperationFeedback::kNumberOrOddball));
      Goto(&call_stub);
    }

    BIND(&if_rhsisnotsmi);
    {
      // Check if {rhs} is a HeapNumber.
      GotoIfNot(IsHeapNumber(rhs), &check_rhsisoddball);

      var_type_feedback.Bind(
          SmiConstant(BinaryOperationFeedback::kNumberOrOddball));
      Goto(&call_stub);
    }
  }

  BIND(&check_rhsisoddball);
  {
    // Check if rhs is an oddball. At this point we know lhs is either a
    // Smi or number or oddball and rhs is not a number or Smi.
    Node* rhs_instance_type = LoadInstanceType(rhs);
    Node* rhs_is_oddball =
        Word32Equal(rhs_instance_type, Int32Constant(ODDBALL_TYPE));
    GotoIfNot(rhs_is_oddball, &call_with_any_feedback);

    var_type_feedback.Bind(
        SmiConstant(BinaryOperationFeedback::kNumberOrOddball));
    Goto(&call_stub);
  }

  BIND(&call_with_any_feedback);
  {
    var_type_feedback.Bind(SmiConstant(BinaryOperationFeedback::kAny));
    Goto(&call_stub);
  }

  BIND(&call_stub);
  {
    Node* result;
    switch (opcode) {
      case Token::SUB:
        result = CallBuiltin(Builtins::kSubtract, context, lhs, rhs);
        break;
      case Token::MUL:
        result = CallBuiltin(Builtins::kMultiply, context, lhs, rhs);
        break;
      case Token::DIV:
        result = CallBuiltin(Builtins::kDivide, context, lhs, rhs);
        break;
      case Token::MOD:
        result = CallBuiltin(Builtins::kModulus, context, lhs, rhs);
        break;
      default:
        UNREACHABLE();
    }
    var_result.Bind(result);
    Goto(&end);
  }

  BIND(&end);
  UpdateFeedback(var_type_feedback.value(), feedback_vector, slot_id, function);
  return var_result.value();
}

Node* BinaryOpAssembler::Generate_SubtractWithFeedback(Node* context, Node* lhs,
                                                       Node* rhs, Node* slot_id,
                                                       Node* feedback_vector,
                                                       Node* function,
                                                       bool rhs_is_smi) {
  auto smiFunction = [=](Node* lhs, Node* rhs, Variable* var_type_feedback) {
    VARIABLE(var_result, MachineRepresentation::kTagged);
    // Try a fast Smi subtraction first.
    Node* pair = IntPtrSubWithOverflow(BitcastTaggedToWord(lhs),
                                       BitcastTaggedToWord(rhs));
    Node* overflow = Projection(1, pair);

    // Check if the Smi subtraction overflowed.
    Label if_notoverflow(this), end(this);
    // If rhs is known to be an Smi (for SubSmi) we want to fast path Smi
    // operation. For the normal Sub operation, we want to fast path both
    // Smi and Number operations, so this path should not be marked as Deferred.
    Label if_overflow(this,
                      rhs_is_smi ? Label::kDeferred : Label::kNonDeferred);
    Branch(overflow, &if_overflow, &if_notoverflow);

    BIND(&if_notoverflow);
    {
      var_type_feedback->Bind(
          SmiConstant(BinaryOperationFeedback::kSignedSmall));
      var_result.Bind(BitcastWordToTaggedSigned(Projection(0, pair)));
      Goto(&end);
    }

    BIND(&if_overflow);
    {
      var_type_feedback->Bind(SmiConstant(BinaryOperationFeedback::kNumber));
      Node* value = Float64Sub(SmiToFloat64(lhs), SmiToFloat64(rhs));
      var_result.Bind(AllocateHeapNumberWithValue(value));
      Goto(&end);
    }

    BIND(&end);
    return var_result.value();
  };
  auto floatFunction = [=](Node* lhs, Node* rhs) {
    return Float64Sub(lhs, rhs);
  };
  return Generate_BinaryOperationWithFeedback(
      context, lhs, rhs, slot_id, feedback_vector, function, smiFunction,
      floatFunction, Token::SUB, rhs_is_smi);
}

Node* BinaryOpAssembler::Generate_MultiplyWithFeedback(Node* context, Node* lhs,
                                                       Node* rhs, Node* slot_id,
                                                       Node* feedback_vector,
                                                       Node* function,
                                                       bool rhs_is_smi) {
  auto smiFunction = [=](Node* lhs, Node* rhs, Variable* var_type_feedback) {
    Node* result = SmiMul(lhs, rhs);
    var_type_feedback->Bind(SelectSmiConstant(
        TaggedIsSmi(result), BinaryOperationFeedback::kSignedSmall,
        BinaryOperationFeedback::kNumber));
    return result;
  };
  auto floatFunction = [=](Node* lhs, Node* rhs) {
    return Float64Mul(lhs, rhs);
  };
  return Generate_BinaryOperationWithFeedback(
      context, lhs, rhs, slot_id, feedback_vector, function, smiFunction,
      floatFunction, Token::MUL, rhs_is_smi);
}

Node* BinaryOpAssembler::Generate_DivideWithFeedback(
    Node* context, Node* dividend, Node* divisor, Node* slot_id,
    Node* feedback_vector, Node* function, bool rhs_is_smi) {
  auto smiFunction = [=](Node* lhs, Node* rhs, Variable* var_type_feedback) {
    VARIABLE(var_result, MachineRepresentation::kTagged);
    // If rhs is known to be an Smi (for DivSmi) we want to fast path Smi
    // operation. For the normal Div operation, we want to fast path both
    // Smi and Number operations, so this path should not be marked as Deferred.
    Label bailout(this, rhs_is_smi ? Label::kDeferred : Label::kNonDeferred),
        end(this);
    var_result.Bind(TrySmiDiv(lhs, rhs, &bailout));
    var_type_feedback->Bind(SmiConstant(BinaryOperationFeedback::kSignedSmall));
    Goto(&end);

    BIND(&bailout);
    {
      var_type_feedback->Bind(SmiConstant(BinaryOperationFeedback::kNumber));
      Node* value = Float64Div(SmiToFloat64(lhs), SmiToFloat64(rhs));
      var_result.Bind(AllocateHeapNumberWithValue(value));
      Goto(&end);
    }

    BIND(&end);
    return var_result.value();
  };
  auto floatFunction = [=](Node* lhs, Node* rhs) {
    return Float64Div(lhs, rhs);
  };
  return Generate_BinaryOperationWithFeedback(
      context, dividend, divisor, slot_id, feedback_vector, function,
      smiFunction, floatFunction, Token::DIV, rhs_is_smi);
}

Node* BinaryOpAssembler::Generate_ModulusWithFeedback(
    Node* context, Node* dividend, Node* divisor, Node* slot_id,
    Node* feedback_vector, Node* function, bool rhs_is_smi) {
  auto smiFunction = [=](Node* lhs, Node* rhs, Variable* var_type_feedback) {
    Node* result = SmiMod(lhs, rhs);
    var_type_feedback->Bind(SelectSmiConstant(
        TaggedIsSmi(result), BinaryOperationFeedback::kSignedSmall,
        BinaryOperationFeedback::kNumber));
    return result;
  };
  auto floatFunction = [=](Node* lhs, Node* rhs) {
    return Float64Mod(lhs, rhs);
  };
  return Generate_BinaryOperationWithFeedback(
      context, dividend, divisor, slot_id, feedback_vector, function,
      smiFunction, floatFunction, Token::MOD, rhs_is_smi);
}

}  // namespace internal
}  // namespace v8
