// Copyright 2014 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/compiler/common-operator-reducer.h"

#include <algorithm>
#include <optional>

#include "src/compiler/common-operator.h"
#include "src/compiler/js-heap-broker.h"
#include "src/compiler/machine-operator.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/node-properties.h"
#include "src/compiler/node.h"
#include "src/compiler/opcodes.h"
#include "src/compiler/turbofan-graph.h"

namespace v8 {
namespace internal {
namespace compiler {

CommonOperatorReducer::CommonOperatorReducer(
    Editor* editor, TFGraph* graph, JSHeapBroker* broker,
    CommonOperatorBuilder* common, MachineOperatorBuilder* machine,
    Zone* temp_zone, BranchSemantics default_branch_semantics)
    : AdvancedReducer(editor),
      graph_(graph),
      broker_(broker),
      common_(common),
      machine_(machine),
      dead_(graph->NewNode(common->Dead())),
      zone_(temp_zone),
      default_branch_semantics_(default_branch_semantics) {
  NodeProperties::SetType(dead_, Type::None());
}

Reduction CommonOperatorReducer::Reduce(Node* node) {
  DisallowHeapAccessIf no_heap_access(broker() == nullptr);
  switch (node->opcode()) {
    case IrOpcode::kBranch:
      return ReduceBranch(node);
    case IrOpcode::kDeoptimizeIf:
    case IrOpcode::kDeoptimizeUnless:
      return ReduceDeoptimizeConditional(node);
    case IrOpcode::kMerge:
      return ReduceMerge(node);
    case IrOpcode::kEffectPhi:
      return ReduceEffectPhi(node);
    case IrOpcode::kPhi:
      return ReducePhi(node);
    case IrOpcode::kReturn:
      return ReduceReturn(node);
    case IrOpcode::kSelect:
      return ReduceSelect(node);
    case IrOpcode::kSwitch:
      return ReduceSwitch(node);
    case IrOpcode::kStaticAssert:
      return ReduceStaticAssert(node);
    case IrOpcode::kTrapIf:
    case IrOpcode::kTrapUnless:
      return ReduceTrapConditional(node);
    default:
      break;
  }
  return NoChange();
}

Decision CommonOperatorReducer::DecideCondition(
    Node* const cond, BranchSemantics branch_semantics) {
  Node* unwrapped = SkipValueIdentities(cond);
  switch (unwrapped->opcode()) {
    case IrOpcode::kInt32Constant: {
      DCHECK_EQ(branch_semantics, BranchSemantics::kMachine);
      Int32Matcher m(unwrapped);
      return m.ResolvedValue() ? Decision::kTrue : Decision::kFalse;
    }
    case IrOpcode::kHeapConstant: {
      if (branch_semantics == BranchSemantics::kMachine) {
        return Decision::kTrue;
      }
      HeapObjectMatcher m(unwrapped);
      std::optional<bool> maybe_result =
          m.Ref(broker_).TryGetBooleanValue(broker());
      if (!maybe_result.has_value()) return Decision::kUnknown;
      return *maybe_result ? Decision::kTrue : Decision::kFalse;
    }
    default:
      return Decision::kUnknown;
  }
}

Reduction CommonOperatorReducer::ReduceBranch(Node* node) {
  DCHECK_EQ(IrOpcode::kBranch, node->opcode());
  BranchSemantics branch_semantics = BranchSemanticsOf(node);
  Node* const cond = node->InputAt(0);
  // Swap IfTrue/IfFalse on {branch} if {cond} is a BooleanNot and use the input
  // to BooleanNot as new condition for {branch}. Note we assume that {cond} was
  // already properly optimized before we get here (as guaranteed by the graph
  // reduction logic). The same applies if {cond} is a Select acting as boolean
  // not (i.e. true being returned in the false case and vice versa).
  if (cond->opcode() == IrOpcode::kBooleanNot ||
      (cond->opcode() == IrOpcode::kSelect &&
       DecideCondition(cond->InputAt(1), branch_semantics) ==
           Decision::kFalse &&
       DecideCondition(cond->InputAt(2), branch_semantics) ==
           Decision::kTrue)) {
    for (Node* const use : node->uses()) {
      switch (use->opcode()) {
        case IrOpcode::kIfTrue:
          NodeProperties::ChangeOp(use, common()->IfFalse());
          break;
        case IrOpcode::kIfFalse:
          NodeProperties::ChangeOp(use, common()->IfTrue());
          break;
        default:
          UNREACHABLE();
      }
    }
    // Update the condition of {branch}. No need to mark the uses for revisit,
    // since we tell the graph reducer that the {branch} was changed and the
    // graph reduction logic will ensure that the uses are revisited properly.
    node->ReplaceInput(0, cond->InputAt(0));
    // Negate the hint for {branch}.
    NodeProperties::ChangeOp(
        node, common()->Branch(NegateBranchHint(BranchHintOf(node->op()))));
    return Changed(node);
  }
  Decision const decision = DecideCondition(cond, branch_semantics);
  if (decision == Decision::kUnknown) return NoChange();
  Node* const control = node->InputAt(1);
  for (Node* const use : node->uses()) {
    switch (use->opcode()) {
      case IrOpcode::kIfTrue:
        Replace(use, (decision == Decision::kTrue) ? control : dead());
        break;
      case IrOpcode::kIfFalse:
        Replace(use, (decision == Decision::kFalse) ? control : dead());
        break;
      default:
        UNREACHABLE();
    }
  }
  return Replace(dead());
}

Reduction CommonOperatorReducer::ReduceDeoptimizeConditional(Node* node) {
  DCHECK(node->opcode() == IrOpcode::kDeoptimizeIf ||
         node->opcode() == IrOpcode::kDeoptimizeUnless);
  bool condition_is_true = node->opcode() == IrOpcode::kDeoptimizeUnless;
  DeoptimizeParameters p = DeoptimizeParametersOf(node->op());
  Node* condition = NodeProperties::GetValueInput(node, 0);
  Node* frame_state = NodeProperties::GetValueInput(node, 1);
  Node* effect = NodeProperties::GetEffectInput(node);
  Node* control = NodeProperties::GetControlInput(node);
  // Swap DeoptimizeIf/DeoptimizeUnless on {node} if {cond} is a BooleaNot
  // and use the input to BooleanNot as new condition for {node}.  Note we
  // assume that {cond} was already properly optimized before we get here
  // (as guaranteed by the graph reduction logic).
  if (condition->opcode() == IrOpcode::kBooleanNot) {
    NodeProperties::ReplaceValueInput(node, condition->InputAt(0), 0);
    NodeProperties::ChangeOp(
        node, condition_is_true
                  ? common()->DeoptimizeIf(p.reason(), p.feedback())
                  : common()->DeoptimizeUnless(p.reason(), p.feedback()));
    return Changed(node);
  }
  Decision const decision =
      DecideCondition(condition, default_branch_semantics_);
  if (decision == Decision::kUnknown) return NoChange();
  if (condition_is_true == (decision == Decision::kTrue)) {
    ReplaceWithValue(node, dead(), effect, control);
  } else {
    control = graph()->NewNode(common()->Deoptimize(p.reason(), p.feedback()),
                               frame_state, effect, control);
    MergeControlToEnd(graph(), common(), control);
  }
  return Replace(dead());
}

Reduction CommonOperatorReducer::ReduceMerge(Node* node) {
  DCHECK_EQ(IrOpcode::kMerge, node->opcode());
  //
  // Check if this is a merge that belongs to an unused diamond, which means
  // that:
  //
  //  a) the {Merge} has no {Phi} or {EffectPhi} uses, and
  //  b) the {Merge} has two inputs, one {IfTrue} and one {IfFalse}, which are
  //     both owned by the Merge, and
  //  c) and the {IfTrue} and {IfFalse} nodes point to the same {Branch}.
  //
  if (node->InputCount() == 2) {
    for (Node* const use : node->uses()) {
      if (IrOpcode::IsPhiOpcode(use->opcode())) return NoChange();
    }
    Node* if_true = node->InputAt(0);
    Node* if_false = node->InputAt(1);
    if (if_true->opcode() != IrOpcode::kIfTrue) std::swap(if_true, if_false);
    if (if_true->opcode() == IrOpcode::kIfTrue &&
        if_false->opcode() == IrOpcode::kIfFalse &&
        if_true->InputAt(0) == if_false->InputAt(0) && if_true->OwnedBy(node) &&
        if_false->OwnedBy(node)) {
      Node* const branch = if_true->InputAt(0);
      DCHECK_EQ(IrOpcode::kBranch, branch->opcode());
      DCHECK(branch->OwnedBy(if_true, if_false));
      Node* const control = branch->InputAt(1);
      // Mark the {branch} as {Dead}.
      branch->TrimInputCount(0);
      NodeProperties::ChangeOp(branch, common()->Dead());
      return Replace(control);
    }
  }
  return NoChange();
}


Reduction CommonOperatorReducer::ReduceEffectPhi(Node* node) {
  DCHECK_EQ(IrOpcode::kEffectPhi, node->opcode());
  Node::Inputs inputs = node->inputs();
  int const effect_input_count = inputs.count() - 1;
  DCHECK_LE(1, effect_input_count);
  Node* const merge = inputs[effect_input_count];
  DCHECK(IrOpcode::IsMergeOpcode(merge->opcode()));
  DCHECK_EQ(effect_input_count, merge->InputCount());
  Node* const effect = inputs[0];
  DCHECK_NE(node, effect);
  for (int i = 1; i < effect_input_count; ++i) {
    Node* const input = inputs[i];
    if (input == node) {
      // Ignore redundant inputs.
      DCHECK_EQ(IrOpcode::kLoop, merge->opcode());
      continue;
    }
    if (input != effect) return NoChange();
  }
  // We might now be able to further reduce the {merge} node.
  Revisit(merge);
  return Replace(effect);
}


Reduction CommonOperatorReducer::ReducePhi(Node* node) {
  DCHECK_EQ(IrOpcode::kPhi, node->opcode());
  Node::Inputs inputs = node->inputs();
  int const value_input_count = inputs.count() - 1;
  DCHECK_LE(1, value_input_count);
  Node* const merge = inputs[value_input_count];
  DCHECK(IrOpcode::IsMergeOpcode(merge->opcode()));
  DCHECK_EQ(value_input_count, merge->InputCount());
  if (value_input_count == 2) {
    // The following optimization tries to match `0 < v ? v : 0 - v`, which
    // corresponds in Turbofan to something like:
    //
    //       Branch(0 < v)
    //         /      \
    //        /        \
    //       v        0 - v
    //        \        /
    //         \      /
    //        phi(v, 0-v)
    //
    // And replace it by `fabs(v)`.
    // TODO(dmercadier): it seems that these optimizations never kick in. While
    // keeping them doesn't cost too much, we could consider removing them to
    // simplify the code and not maintain unused pieces of code.
    Node* vtrue = inputs[0];
    Node* vfalse = inputs[1];
    Node::Inputs merge_inputs = merge->inputs();
    Node* if_true = merge_inputs[0];
    Node* if_false = merge_inputs[1];
    if (if_true->opcode() != IrOpcode::kIfTrue) {
      std::swap(if_true, if_false);
      std::swap(vtrue, vfalse);
    }
    if (if_true->opcode() == IrOpcode::kIfTrue &&
        if_false->opcode() == IrOpcode::kIfFalse &&
        if_true->InputAt(0) == if_false->InputAt(0)) {
      Node* const branch = if_true->InputAt(0);
      // Check that the branch is not dead already.
      if (branch->opcode() != IrOpcode::kBranch) return NoChange();
      Node* const cond = branch->InputAt(0);
      if (cond->opcode() == IrOpcode::kFloat32LessThan) {
        Float32BinopMatcher mcond(cond);
        if (mcond.left().Is(0.0) && mcond.right().Equals(vtrue) &&
            vfalse->opcode() == IrOpcode::kFloat32Sub) {
          Float32BinopMatcher mvfalse(vfalse);
          if (mvfalse.left().IsZero() && mvfalse.right().Equals(vtrue)) {
            // We might now be able to further reduce the {merge} node.
            Revisit(merge);
            return Change(node, machine()->Float32Abs(), vtrue);
          }
        }
      } else if (cond->opcode() == IrOpcode::kFloat64LessThan) {
        Float64BinopMatcher mcond(cond);
        if (mcond.left().Is(0.0) && mcond.right().Equals(vtrue) &&
            vfalse->opcode() == IrOpcode::kFloat64Sub) {
          Float64BinopMatcher mvfalse(vfalse);
          if (mvfalse.left().IsZero() && mvfalse.right().Equals(vtrue)) {
            // We might now be able to further reduce the {merge} node.
            Revisit(merge);
            return Change(node, machine()->Float64Abs(), vtrue);
          }
        }
      } else if (cond->opcode() == IrOpcode::kInt32LessThan) {
        Int32BinopMatcher mcond(cond);
        if (mcond.left().Is(0) && mcond.right().Equals(vtrue) &&
            (vfalse->opcode() == IrOpcode::kInt32Sub)) {
          Int32BinopMatcher mvfalse(vfalse);
          if (mvfalse.left().Is(0) && mvfalse.right().Equals(vtrue)) {
            // We might now be able to further reduce the {merge} node.
            Revisit(merge);

            if (machine()->Word32Select().IsSupported()) {
              // Select positive value with conditional move if is supported.
              Node* abs = graph()->NewNode(machine()->Word32Select().op(), cond,
                                           vtrue, vfalse);
              return Replace(abs);
            } else {
              // Generate absolute integer value.
              //
              //    let sign = input >> 31 in
              //    (input ^ sign) - sign
              Node* sign = graph()->NewNode(
                  machine()->Word32Sar(), vtrue,
                  graph()->NewNode(common()->Int32Constant(31)));
              Node* abs = graph()->NewNode(
                  machine()->Int32Sub(),
                  graph()->NewNode(machine()->Word32Xor(), vtrue, sign), sign);
              return Replace(abs);
            }
          }
        }
      }
    }
  }
  Node* const value = inputs[0];
  DCHECK_NE(node, value);
  for (int i = 1; i < value_input_count; ++i) {
    Node* const input = inputs[i];
    if (input == node) {
      // Ignore redundant inputs.
      DCHECK_EQ(IrOpcode::kLoop, merge->opcode());
      continue;
    }
    if (input != value) return NoChange();
  }
  // We might now be able to further reduce the {merge} node.
  Revisit(merge);
  return Replace(value);
}

Reduction CommonOperatorReducer::ReduceReturn(Node* node) {
  DCHECK_EQ(IrOpcode::kReturn, node->opcode());
  Node* effect = NodeProperties::GetEffectInput(node);
  // TODO(mslekova): Port this to Turboshaft.
  if (effect->opcode() == IrOpcode::kCheckpoint) {
    // Any {Return} node can never be used to insert a deoptimization point,
    // hence checkpoints can be cut out of the effect chain flowing into it.
    effect = NodeProperties::GetEffectInput(effect);
    NodeProperties::ReplaceEffectInput(node, effect);
    return Changed(node).FollowedBy(ReduceReturn(node));
  }
  // TODO(ahaas): Extend the reduction below to multiple return values.
  if (ValueInputCountOfReturn(node->op()) != 1) {
    return NoChange();
  }
  Node* pop_count = NodeProperties::GetValueInput(node, 0);
  Node* value = NodeProperties::GetValueInput(node, 1);
  Node* control = NodeProperties::GetControlInput(node);
  if (value->opcode() == IrOpcode::kPhi &&
      NodeProperties::GetControlInput(value) == control &&
      control->opcode() == IrOpcode::kMerge) {
    // This optimization pushes {Return} nodes through merges. It checks that
    // the return value is actually a {Phi} and the return control dependency
    // is the {Merge} to which the {Phi} belongs.

    // Value1 ... ValueN Control1 ... ControlN
    //   ^          ^       ^            ^
    //   |          |       |            |
    //   +----+-----+       +------+-----+
    //        |                    |
    //       Phi --------------> Merge
    //        ^                    ^
    //        |                    |
    //        |  +-----------------+
    //        |  |
    //       Return -----> Effect
    //         ^
    //         |
    //        End

    // Now the effect input to the {Return} node can be either an {EffectPhi}
    // hanging off the same {Merge}, or the effect chain doesn't depend on the
    // {Phi} or the {Merge}, in which case we know that the effect input must
    // somehow dominate all merged branches.

    Node::Inputs control_inputs = control->inputs();
    Node::Inputs value_inputs = value->inputs();
    DCHECK_NE(0, control_inputs.count());
    DCHECK_EQ(control_inputs.count(), value_inputs.count() - 1);
    DCHECK_EQ(IrOpcode::kEnd, graph()->end()->opcode());
    DCHECK_NE(0, graph()->end()->InputCount());
    if (control->OwnedBy(node, value) && value->OwnedBy(node)) {
      for (int i = 0; i < control_inputs.count(); ++i) {
        // Create a new {Return} and connect it to {end}. We don't need to mark
        // {end} as revisit, because we mark {node} as {Dead} below, which was
        // previously connected to {end}, so we know for sure that at some point
        // the reducer logic will visit {end} again.
        Node* ret = graph()->NewNode(node->op(), pop_count, value_inputs[i],
                                     effect, control_inputs[i]);
        MergeControlToEnd(graph(), common(), ret);
      }
      // Mark the Merge {control} and Return {node} as {dead}.
      Replace(control, dead());
      return Replace(dead());
    } else if (effect->opcode() == IrOpcode::kEffectPhi &&
               NodeProperties::GetControlInput(effect) == control) {
      Node::Inputs effect_inputs = effect->inputs();
      DCHECK_EQ(control_inputs.count(), effect_inputs.count() - 1);
      for (int i = 0; i < control_inputs.count(); ++i) {
        // Create a new {Return} and connect it to {end}. We don't need to mark
        // {end} as revisit, because we mark {node} as {Dead} below, which was
        // previously connected to {end}, so we know for sure that at some point
        // the reducer logic will visit {end} again.
        Node* ret = graph()->NewNode(node->op(), pop_count, value_inputs[i],
                                     effect_inputs[i], control_inputs[i]);
        MergeControlToEnd(graph(), common(), ret);
      }
      // Mark the Merge {control} and Return {node} as {dead}.
      Replace(control, dead());
      return Replace(dead());
    }
  }
  return NoChange();
}

Reduction CommonOperatorReducer::ReduceSelect(Node* node) {
  DCHECK_EQ(IrOpcode::kSelect, node->opcode());
  Node* const cond = node->InputAt(0);
  Node* const vtrue = node->InputAt(1);
  Node* const vfalse = node->InputAt(2);
  if (vtrue == vfalse) return Replace(vtrue);
  switch (DecideCondition(cond, default_branch_semantics_)) {
    case Decision::kTrue:
      return Replace(vtrue);
    case Decision::kFalse:
      return Replace(vfalse);
    case Decision::kUnknown:
      break;
  }
  // The following optimization tries to replace `select(0 < v ? v : 0 - v)` by
  // `fabs(v)`.
  // TODO(dmercadier): it seems that these optimizations never kick in. While
  // keeping them doesn't cost too much, we could consider removing them to
  // simplify the code and not maintain unused pieces of code.
  switch (cond->opcode()) {
    case IrOpcode::kFloat32LessThan: {
      Float32BinopMatcher mcond(cond);
      if (mcond.left().Is(0.0) && mcond.right().Equals(vtrue) &&
          vfalse->opcode() == IrOpcode::kFloat32Sub) {
        Float32BinopMatcher mvfalse(vfalse);
        if (mvfalse.left().IsZero() && mvfalse.right().Equals(vtrue)) {
          return Change(node, machine()->Float32Abs(), vtrue);
        }
      }
      break;
    }
    case IrOpcode::kFloat64LessThan: {
      Float64BinopMatcher mcond(cond);
      if (mcond.left().Is(0.0) && mcond.right().Equals(vtrue) &&
          vfalse->opcode() == IrOpcode::kFloat64Sub) {
        Float64BinopMatcher mvfalse(vfalse);
        if (mvfalse.left().IsZero() && mvfalse.right().Equals(vtrue)) {
          return Change(node, machine()->Float64Abs(), vtrue);
        }
      }
      break;
    }
    default:
      break;
  }
  return NoChange();
}

Reduction CommonOperatorReducer::ReduceSwitch(Node* node) {
  DCHECK_EQ(IrOpcode::kSwitch, node->opcode());
  Node* const switched_value = node->InputAt(0);
  Node* const control = node->InputAt(1);

  // Attempt to constant match the switched value against the IfValue cases. If
  // no case matches, then use the IfDefault. We don't bother marking
  // non-matching cases as dead code (same for an unused IfDefault), because the
  // Switch itself will be marked as dead code.
  Int32Matcher mswitched(switched_value);
  if (mswitched.HasResolvedValue()) {
    bool matched = false;

    size_t const projection_count = node->op()->ControlOutputCount();
    Node** projections = zone_->AllocateArray<Node*>(projection_count);
    NodeProperties::CollectControlProjections(node, projections,
                                              projection_count);
    for (size_t i = 0; i < projection_count - 1; i++) {
      Node* if_value = projections[i];
      DCHECK_EQ(IrOpcode::kIfValue, if_value->opcode());
      const IfValueParameters& p = IfValueParametersOf(if_value->op());
      if (p.value() == mswitched.ResolvedValue()) {
        matched = true;
        Replace(if_value, control);
        break;
      }
    }
    if (!matched) {
      Node* if_default = projections[projection_count - 1];
      DCHECK_EQ(IrOpcode::kIfDefault, if_default->opcode());
      Replace(if_default, control);
    }
    return Replace(dead());
  }
  return NoChange();
}

Reduction CommonOperatorReducer::ReduceStaticAssert(Node* node) {
  DCHECK_EQ(IrOpcode::kStaticAssert, node->opcode());
  Node* const cond = node->InputAt(0);
  Decision decision = DecideCondition(cond, default_branch_semantics_);
  if (decision == Decision::kTrue) {
    RelaxEffectsAndControls(node);
    return Changed(node);
  } else {
    return NoChange();
  }
}

Reduction CommonOperatorReducer::ReduceTrapConditional(Node* trap) {
  DCHECK(trap->opcode() == IrOpcode::kTrapIf ||
         trap->opcode() == IrOpcode::kTrapUnless);
  bool trapping_condition = trap->opcode() == IrOpcode::kTrapIf;
  Node* const cond = trap->InputAt(0);
  Decision decision = DecideCondition(cond, default_branch_semantics_);

  if (decision == Decision::kUnknown) {
    return NoChange();
  } else if ((decision == Decision::kTrue) == trapping_condition) {
    // This will always trap. Mark its outputs as dead and connect it to
    // graph()->end().
    ReplaceWithValue(trap, dead(), dead(), dead());
    Node* control = graph()->NewNode(common()->Throw(), trap, trap);
    MergeControlToEnd(graph(), common(), control);
    return Changed(trap);
  } else {
    // This will not trap, remove it by relaxing effect/control.
    Node* control = NodeProperties::GetControlInput(trap);
    ReplaceWithValue(trap, dead());
    trap->Kill();
    // The argument below is irrelevant, picked {control} for debugging.
    return Replace(control);
  }
}

Reduction CommonOperatorReducer::Change(Node* node, Operator const* op,
                                        Node* a) {
  node->ReplaceInput(0, a);
  node->TrimInputCount(1);
  NodeProperties::ChangeOp(node, op);
  return Changed(node);
}


Reduction CommonOperatorReducer::Change(Node* node, Operator const* op, Node* a,
                                        Node* b) {
  node->ReplaceInput(0, a);
  node->ReplaceInput(1, b);
  node->TrimInputCount(2);
  NodeProperties::ChangeOp(node, op);
  return Changed(node);
}

}  // namespace compiler
}  // namespace internal
}  // namespace v8