// 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.h"
#include "src/compiler/control-reducer.h"
#include "src/compiler/graph.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/node-properties-inl.h"
#include "src/zone-containers.h"

namespace v8 {
namespace internal {
namespace compiler {

enum VisitState { kUnvisited = 0, kOnStack = 1, kRevisit = 2, kVisited = 3 };
enum Reachability { kFromStart = 8 };

#define TRACE(x) \
  if (FLAG_trace_turbo_reduction) PrintF x

class ControlReducerImpl {
 public:
  ControlReducerImpl(Zone* zone, JSGraph* jsgraph,
                     CommonOperatorBuilder* common)
      : zone_(zone),
        jsgraph_(jsgraph),
        common_(common),
        state_(jsgraph->graph()->NodeCount(), kUnvisited, zone_),
        stack_(zone_),
        revisit_(zone_),
        dead_(NULL) {}

  Zone* zone_;
  JSGraph* jsgraph_;
  CommonOperatorBuilder* common_;
  ZoneVector<VisitState> state_;
  ZoneDeque<Node*> stack_;
  ZoneDeque<Node*> revisit_;
  Node* dead_;

  void Reduce() {
    Push(graph()->end());
    do {
      // Process the node on the top of the stack, potentially pushing more
      // or popping the node off the stack.
      ReduceTop();
      // If the stack becomes empty, revisit any nodes in the revisit queue.
      // If no nodes in the revisit queue, try removing dead loops.
      // If no dead loops, then finish.
    } while (!stack_.empty() || TryRevisit() || RepairAndRemoveLoops());
  }

  bool TryRevisit() {
    while (!revisit_.empty()) {
      Node* n = revisit_.back();
      revisit_.pop_back();
      if (state_[n->id()] == kRevisit) {  // state can change while in queue.
        Push(n);
        return true;
      }
    }
    return false;
  }

  // Repair the graph after the possible creation of non-terminating or dead
  // loops. Removing dead loops can produce more opportunities for reduction.
  bool RepairAndRemoveLoops() {
    // TODO(turbofan): we can skip this if the graph has no loops, but
    // we have to be careful about proper loop detection during reduction.

    // Gather all nodes backwards-reachable from end (through inputs).
    state_.assign(graph()->NodeCount(), kUnvisited);
    NodeVector nodes(zone_);
    AddNodesReachableFromEnd(nodes);

    // Walk forward through control nodes, looking for back edges to nodes
    // that are not connected to end. Those are non-terminating loops (NTLs).
    Node* start = graph()->start();
    ZoneVector<byte> fw_reachability(graph()->NodeCount(), 0, zone_);
    fw_reachability[start->id()] = kFromStart | kOnStack;
    stack_.push_back(start);

    while (!stack_.empty()) {
      Node* node = stack_.back();
      TRACE(("ControlFw: #%d:%s\n", node->id(), node->op()->mnemonic()));
      bool pop = true;
      for (Node* const succ : node->uses()) {
        byte reach = fw_reachability[succ->id()];
        if ((reach & kOnStack) != 0 && state_[succ->id()] != kVisited) {
          // {succ} is on stack and not reachable from end.
          ConnectNTL(nodes, succ);
          fw_reachability.resize(graph()->NodeCount(), 0);
          pop = false;  // continue traversing inputs to this node.
          break;
        }
        if ((reach & kFromStart) == 0 &&
            IrOpcode::IsControlOpcode(succ->opcode())) {
          // {succ} is a control node and not yet reached from start.
          fw_reachability[succ->id()] |= kFromStart | kOnStack;
          stack_.push_back(succ);
          pop = false;  // "recurse" into successor control node.
          break;
        }
      }
      if (pop) {
        fw_reachability[node->id()] &= ~kOnStack;
        stack_.pop_back();
      }
    }

    // Trim references from dead nodes to live nodes first.
    jsgraph_->GetCachedNodes(&nodes);
    TrimNodes(nodes);

    // Any control nodes not reachable from start are dead, even loops.
    for (size_t i = 0; i < nodes.size(); i++) {
      Node* node = nodes[i];
      byte reach = fw_reachability[node->id()];
      if ((reach & kFromStart) == 0 &&
          IrOpcode::IsControlOpcode(node->opcode())) {
        ReplaceNode(node, dead());  // uses will be added to revisit queue.
      }
    }
    return TryRevisit();  // try to push a node onto the stack.
  }

  // Connect {loop}, the header of a non-terminating loop, to the end node.
  void ConnectNTL(NodeVector& nodes, Node* loop) {
    TRACE(("ConnectNTL: #%d:%s\n", loop->id(), loop->op()->mnemonic()));

    if (loop->opcode() != IrOpcode::kTerminate) {
      // Insert a {Terminate} node if the loop has effects.
      ZoneDeque<Node*> effects(zone_);
      for (Node* const use : loop->uses()) {
        if (use->opcode() == IrOpcode::kEffectPhi) effects.push_back(use);
      }
      int count = static_cast<int>(effects.size());
      if (count > 0) {
        Node** inputs = zone_->NewArray<Node*>(1 + count);
        for (int i = 0; i < count; i++) inputs[i] = effects[i];
        inputs[count] = loop;
        loop = graph()->NewNode(common_->Terminate(count), 1 + count, inputs);
        TRACE(("AddTerminate: #%d:%s[%d]\n", loop->id(), loop->op()->mnemonic(),
               count));
      }
    }

    Node* to_add = loop;
    Node* end = graph()->end();
    CHECK_EQ(IrOpcode::kEnd, end->opcode());
    Node* merge = end->InputAt(0);
    if (merge == NULL || merge->opcode() == IrOpcode::kDead) {
      // The end node died; just connect end to {loop}.
      end->ReplaceInput(0, loop);
    } else if (merge->opcode() != IrOpcode::kMerge) {
      // Introduce a final merge node for {end->InputAt(0)} and {loop}.
      merge = graph()->NewNode(common_->Merge(2), merge, loop);
      end->ReplaceInput(0, merge);
      to_add = merge;
    } else {
      // Append a new input to the final merge at the end.
      merge->AppendInput(graph()->zone(), loop);
      merge->set_op(common_->Merge(merge->InputCount()));
    }
    nodes.push_back(to_add);
    state_.resize(graph()->NodeCount(), kUnvisited);
    state_[to_add->id()] = kVisited;
    AddBackwardsReachableNodes(nodes, nodes.size() - 1);
  }

  void AddNodesReachableFromEnd(NodeVector& nodes) {
    Node* end = graph()->end();
    state_[end->id()] = kVisited;
    if (!end->IsDead()) {
      nodes.push_back(end);
      AddBackwardsReachableNodes(nodes, nodes.size() - 1);
    }
  }

  void AddBackwardsReachableNodes(NodeVector& nodes, size_t cursor) {
    while (cursor < nodes.size()) {
      Node* node = nodes[cursor++];
      for (Node* const input : node->inputs()) {
        if (state_[input->id()] != kVisited) {
          state_[input->id()] = kVisited;
          nodes.push_back(input);
        }
      }
    }
  }

  void Trim() {
    // Gather all nodes backwards-reachable from end through inputs.
    state_.assign(graph()->NodeCount(), kUnvisited);
    NodeVector nodes(zone_);
    AddNodesReachableFromEnd(nodes);

    // Process cached nodes in the JSGraph too.
    jsgraph_->GetCachedNodes(&nodes);
    TrimNodes(nodes);
  }

  void TrimNodes(NodeVector& nodes) {
    // Remove dead->live edges.
    for (size_t j = 0; j < nodes.size(); j++) {
      Node* node = nodes[j];
      for (UseIter i = node->uses().begin(); i != node->uses().end();) {
        size_t id = static_cast<size_t>((*i)->id());
        if (state_[id] != kVisited) {
          TRACE(("DeadLink: #%d:%s(%d) -> #%d:%s\n", (*i)->id(),
                 (*i)->op()->mnemonic(), i.index(), node->id(),
                 node->op()->mnemonic()));
          i.UpdateToAndIncrement(NULL);
        } else {
          ++i;
        }
      }
    }
#if DEBUG
    // Verify that no inputs to live nodes are NULL.
    for (size_t j = 0; j < nodes.size(); j++) {
      Node* node = nodes[j];
      for (Node* const input : node->inputs()) {
        CHECK_NE(NULL, input);
      }
      for (Node* const use : node->uses()) {
        size_t id = static_cast<size_t>(use->id());
        CHECK_EQ(kVisited, state_[id]);
      }
    }
#endif
  }

  // Reduce the node on the top of the stack.
  // If an input {i} is not yet visited or needs to be revisited, push {i} onto
  // the stack and return. Otherwise, all inputs are visited, so apply
  // reductions for {node} and pop it off the stack.
  void ReduceTop() {
    size_t height = stack_.size();
    Node* node = stack_.back();

    if (node->IsDead()) return Pop();  // Node was killed while on stack.

    TRACE(("ControlReduce: #%d:%s\n", node->id(), node->op()->mnemonic()));

    // Recurse on an input if necessary.
    for (Node* const input : node->inputs()) {
      if (Recurse(input)) return;
    }

    // All inputs should be visited or on stack. Apply reductions to node.
    Node* replacement = ReduceNode(node);
    if (replacement != node) ReplaceNode(node, replacement);

    // After reducing the node, pop it off the stack.
    CHECK_EQ(static_cast<int>(height), static_cast<int>(stack_.size()));
    Pop();

    // If there was a replacement, reduce it after popping {node}.
    if (replacement != node) Recurse(replacement);
  }

  // Push a node onto the stack if its state is {kUnvisited} or {kRevisit}.
  bool Recurse(Node* node) {
    size_t id = static_cast<size_t>(node->id());
    if (id < state_.size()) {
      if (state_[id] != kRevisit && state_[id] != kUnvisited) return false;
    } else {
      state_.resize((3 * id) / 2, kUnvisited);
    }
    Push(node);
    return true;
  }

  void Push(Node* node) {
    state_[node->id()] = kOnStack;
    stack_.push_back(node);
  }

  void Pop() {
    int pos = static_cast<int>(stack_.size()) - 1;
    DCHECK_GE(pos, 0);
    DCHECK_EQ(kOnStack, state_[stack_[pos]->id()]);
    state_[stack_[pos]->id()] = kVisited;
    stack_.pop_back();
  }

  // Queue a node to be revisited if it has been visited once already.
  void Revisit(Node* node) {
    size_t id = static_cast<size_t>(node->id());
    if (id < state_.size() && state_[id] == kVisited) {
      TRACE(("  Revisit #%d:%s\n", node->id(), node->op()->mnemonic()));
      state_[id] = kRevisit;
      revisit_.push_back(node);
    }
  }

  Node* dead() {
    if (dead_ == NULL) dead_ = graph()->NewNode(common_->Dead());
    return dead_;
  }

  //===========================================================================
  // Reducer implementation: perform reductions on a node.
  //===========================================================================
  Node* ReduceNode(Node* node) {
    if (OperatorProperties::GetControlInputCount(node->op()) == 1) {
      // If a node has only one control input and it is dead, replace with dead.
      Node* control = NodeProperties::GetControlInput(node);
      if (control->opcode() == IrOpcode::kDead) {
        TRACE(("ControlDead: #%d:%s\n", node->id(), node->op()->mnemonic()));
        return control;
      }
    }

    // Reduce branches, phis, and merges.
    switch (node->opcode()) {
      case IrOpcode::kBranch:
        return ReduceBranch(node);
      case IrOpcode::kLoop:
      case IrOpcode::kMerge:
        return ReduceMerge(node);
      case IrOpcode::kSelect:
        return ReduceSelect(node);
      case IrOpcode::kPhi:
      case IrOpcode::kEffectPhi:
        return ReducePhi(node);
      default:
        return node;
    }
  }

  // Reduce redundant selects.
  Node* ReduceSelect(Node* const node) {
    Node* const cond = node->InputAt(0);
    Node* const tvalue = node->InputAt(1);
    Node* const fvalue = node->InputAt(2);
    if (tvalue == fvalue) return tvalue;
    switch (cond->opcode()) {
      case IrOpcode::kInt32Constant:
        return Int32Matcher(cond).Is(0) ? fvalue : tvalue;
      case IrOpcode::kInt64Constant:
        return Int64Matcher(cond).Is(0) ? fvalue : tvalue;
      case IrOpcode::kNumberConstant:
        return NumberMatcher(cond).Is(0) ? fvalue : tvalue;
      case IrOpcode::kHeapConstant: {
        Handle<Object> object =
            HeapObjectMatcher<Object>(cond).Value().handle();
        if (object->IsTrue()) return tvalue;
        if (object->IsFalse()) return fvalue;
        break;
      }
      default:
        break;
    }
    return node;
  }

  // Reduce redundant phis.
  Node* ReducePhi(Node* node) {
    int n = node->InputCount();
    if (n <= 1) return dead();            // No non-control inputs.
    if (n == 2) return node->InputAt(0);  // Only one non-control input.

    Node* replacement = NULL;
    Node::Inputs inputs = node->inputs();
    for (InputIter it = inputs.begin(); n > 1; --n, ++it) {
      Node* input = *it;
      if (input->opcode() == IrOpcode::kDead) continue;  // ignore dead inputs.
      if (input != node && input != replacement) {       // non-redundant input.
        if (replacement != NULL) return node;
        replacement = input;
      }
    }
    return replacement == NULL ? dead() : replacement;
  }

  // Reduce merges by trimming away dead inputs from the merge and phis.
  Node* ReduceMerge(Node* node) {
    // Count the number of live inputs.
    int live = 0;
    int index = 0;
    int live_index = 0;
    for (Node* const input : node->inputs()) {
      if (input->opcode() != IrOpcode::kDead) {
        live++;
        live_index = index;
      }
      index++;
    }

    if (live > 1 && live == node->InputCount()) return node;  // nothing to do.

    TRACE(("ReduceMerge: #%d:%s (%d live)\n", node->id(),
           node->op()->mnemonic(), live));

    if (live == 0) return dead();  // no remaining inputs.

    // Gather phis and effect phis to be edited.
    ZoneVector<Node*> phis(zone_);
    for (Node* const use : node->uses()) {
      if (use->opcode() == IrOpcode::kPhi ||
          use->opcode() == IrOpcode::kEffectPhi) {
        phis.push_back(use);
      }
    }

    if (live == 1) {
      // All phis are redundant. Replace them with their live input.
      for (Node* const phi : phis) ReplaceNode(phi, phi->InputAt(live_index));
      // The merge itself is redundant.
      return node->InputAt(live_index);
    }

    // Edit phis in place, removing dead inputs and revisiting them.
    for (Node* const phi : phis) {
      TRACE(("  PhiInMerge: #%d:%s (%d live)\n", phi->id(),
             phi->op()->mnemonic(), live));
      RemoveDeadInputs(node, phi);
      Revisit(phi);
    }
    // Edit the merge in place, removing dead inputs.
    RemoveDeadInputs(node, node);
    return node;
  }

  // Reduce branches if they have constant inputs.
  Node* ReduceBranch(Node* node) {
    Node* cond = node->InputAt(0);
    bool is_true;
    switch (cond->opcode()) {
      case IrOpcode::kInt32Constant:
        is_true = !Int32Matcher(cond).Is(0);
        break;
      case IrOpcode::kInt64Constant:
        is_true = !Int64Matcher(cond).Is(0);
        break;
      case IrOpcode::kNumberConstant:
        is_true = !NumberMatcher(cond).Is(0);
        break;
      case IrOpcode::kHeapConstant: {
        Handle<Object> object =
            HeapObjectMatcher<Object>(cond).Value().handle();
        if (object->IsTrue())
          is_true = true;
        else if (object->IsFalse())
          is_true = false;
        else
          return node;  // TODO(turbofan): fold branches on strings, objects.
        break;
      }
      default:
        return node;
    }

    TRACE(("BranchReduce: #%d:%s = %s\n", node->id(), node->op()->mnemonic(),
           is_true ? "true" : "false"));

    // Replace IfTrue and IfFalse projections from this branch.
    Node* control = NodeProperties::GetControlInput(node);
    for (UseIter i = node->uses().begin(); i != node->uses().end();) {
      Node* to = *i;
      if (to->opcode() == IrOpcode::kIfTrue) {
        TRACE(("  IfTrue: #%d:%s\n", to->id(), to->op()->mnemonic()));
        i.UpdateToAndIncrement(NULL);
        ReplaceNode(to, is_true ? control : dead());
      } else if (to->opcode() == IrOpcode::kIfFalse) {
        TRACE(("  IfFalse: #%d:%s\n", to->id(), to->op()->mnemonic()));
        i.UpdateToAndIncrement(NULL);
        ReplaceNode(to, is_true ? dead() : control);
      } else {
        ++i;
      }
    }
    return control;
  }

  // Remove inputs to {node} corresponding to the dead inputs to {merge}
  // and compact the remaining inputs, updating the operator.
  void RemoveDeadInputs(Node* merge, Node* node) {
    int pos = 0;
    for (int i = 0; i < node->InputCount(); i++) {
      // skip dead inputs.
      if (i < merge->InputCount() &&
          merge->InputAt(i)->opcode() == IrOpcode::kDead)
        continue;
      // compact live inputs.
      if (pos != i) node->ReplaceInput(pos, node->InputAt(i));
      pos++;
    }
    node->TrimInputCount(pos);
    if (node->opcode() == IrOpcode::kPhi) {
      node->set_op(common_->Phi(OpParameter<MachineType>(node->op()), pos - 1));
    } else if (node->opcode() == IrOpcode::kEffectPhi) {
      node->set_op(common_->EffectPhi(pos - 1));
    } else if (node->opcode() == IrOpcode::kMerge) {
      node->set_op(common_->Merge(pos));
    } else if (node->opcode() == IrOpcode::kLoop) {
      node->set_op(common_->Loop(pos));
    } else {
      UNREACHABLE();
    }
  }

  // Replace uses of {node} with {replacement} and revisit the uses.
  void ReplaceNode(Node* node, Node* replacement) {
    if (node == replacement) return;
    TRACE(("  Replace: #%d:%s with #%d:%s\n", node->id(),
           node->op()->mnemonic(), replacement->id(),
           replacement->op()->mnemonic()));
    for (Node* const use : node->uses()) {
      // Don't revisit this node if it refers to itself.
      if (use != node) Revisit(use);
    }
    node->ReplaceUses(replacement);
    node->Kill();
  }

  Graph* graph() { return jsgraph_->graph(); }
};


void ControlReducer::ReduceGraph(Zone* zone, JSGraph* jsgraph,
                                 CommonOperatorBuilder* common) {
  ControlReducerImpl impl(zone, jsgraph, common);
  impl.Reduce();
  impl.Trim();
}


void ControlReducer::TrimGraph(Zone* zone, JSGraph* jsgraph) {
  ControlReducerImpl impl(zone, jsgraph, NULL);
  impl.Trim();
}


Node* ControlReducer::ReducePhiForTesting(JSGraph* jsgraph,
                                          CommonOperatorBuilder* common,
                                          Node* node) {
  Zone zone(jsgraph->graph()->zone()->isolate());
  ControlReducerImpl impl(&zone, jsgraph, common);
  return impl.ReducePhi(node);
}


Node* ControlReducer::ReduceMergeForTesting(JSGraph* jsgraph,
                                            CommonOperatorBuilder* common,
                                            Node* node) {
  Zone zone(jsgraph->graph()->zone()->isolate());
  ControlReducerImpl impl(&zone, jsgraph, common);
  return impl.ReduceMerge(node);
}


Node* ControlReducer::ReduceBranchForTesting(JSGraph* jsgraph,
                                             CommonOperatorBuilder* common,
                                             Node* node) {
  Zone zone(jsgraph->graph()->zone()->isolate());
  ControlReducerImpl impl(&zone, jsgraph, common);
  return impl.ReduceBranch(node);
}
}
}
}  // namespace v8::internal::compiler
