// 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/bit-vector.h"
#include "src/compiler/control-equivalence.h"
#include "src/compiler/graph-visualizer.h"
#include "src/compiler/node-properties-inl.h"
#include "src/zone-containers.h"
#include "test/unittests/compiler/graph-unittest.h"

namespace v8 {
namespace internal {
namespace compiler {

#define ASSERT_EQUIVALENCE(...)                           \
  do {                                                    \
    Node* __n[] = {__VA_ARGS__};                          \
    ASSERT_TRUE(IsEquivalenceClass(arraysize(__n), __n)); \
  } while (false);

class ControlEquivalenceTest : public GraphTest {
 public:
  ControlEquivalenceTest() : all_nodes_(zone()), classes_(zone()) {
    Store(graph()->start());
  }

 protected:
  void ComputeEquivalence(Node* node) {
    graph()->SetEnd(graph()->NewNode(common()->End(), node));
    if (FLAG_trace_turbo) {
      OFStream os(stdout);
      os << AsDOT(*graph());
    }
    ControlEquivalence equivalence(zone(), graph());
    equivalence.Run(node);
    classes_.resize(graph()->NodeCount());
    for (Node* node : all_nodes_) {
      classes_[node->id()] = equivalence.ClassOf(node);
    }
  }

  bool IsEquivalenceClass(size_t length, Node** nodes) {
    BitVector in_class(graph()->NodeCount(), zone());
    size_t expected_class = classes_[nodes[0]->id()];
    for (size_t i = 0; i < length; ++i) {
      in_class.Add(nodes[i]->id());
    }
    for (Node* node : all_nodes_) {
      if (in_class.Contains(node->id())) {
        if (classes_[node->id()] != expected_class) return false;
      } else {
        if (classes_[node->id()] == expected_class) return false;
      }
    }
    return true;
  }

  Node* Value() { return NumberConstant(0.0); }

  Node* Branch(Node* control) {
    return Store(graph()->NewNode(common()->Branch(), Value(), control));
  }

  Node* IfTrue(Node* control) {
    return Store(graph()->NewNode(common()->IfTrue(), control));
  }

  Node* IfFalse(Node* control) {
    return Store(graph()->NewNode(common()->IfFalse(), control));
  }

  Node* Merge2(Node* control1, Node* control2) {
    return Store(graph()->NewNode(common()->Merge(2), control1, control2));
  }

  Node* Loop2(Node* control) {
    return Store(graph()->NewNode(common()->Loop(2), control, control));
  }

  Node* End(Node* control) {
    return Store(graph()->NewNode(common()->End(), control));
  }

 private:
  Node* Store(Node* node) {
    all_nodes_.push_back(node);
    return node;
  }

  ZoneVector<Node*> all_nodes_;
  ZoneVector<size_t> classes_;
};


// -----------------------------------------------------------------------------
// Test cases.


TEST_F(ControlEquivalenceTest, Empty1) {
  Node* start = graph()->start();
  ComputeEquivalence(start);

  ASSERT_EQUIVALENCE(start);
}


TEST_F(ControlEquivalenceTest, Empty2) {
  Node* start = graph()->start();
  Node* end = End(start);
  ComputeEquivalence(end);

  ASSERT_EQUIVALENCE(start, end);
}


TEST_F(ControlEquivalenceTest, Diamond1) {
  Node* start = graph()->start();
  Node* b = Branch(start);
  Node* t = IfTrue(b);
  Node* f = IfFalse(b);
  Node* m = Merge2(t, f);
  ComputeEquivalence(m);

  ASSERT_EQUIVALENCE(b, m, start);
  ASSERT_EQUIVALENCE(f);
  ASSERT_EQUIVALENCE(t);
}


TEST_F(ControlEquivalenceTest, Diamond2) {
  Node* start = graph()->start();
  Node* b1 = Branch(start);
  Node* t1 = IfTrue(b1);
  Node* f1 = IfFalse(b1);
  Node* b2 = Branch(f1);
  Node* t2 = IfTrue(b2);
  Node* f2 = IfFalse(b2);
  Node* m2 = Merge2(t2, f2);
  Node* m1 = Merge2(t1, m2);
  ComputeEquivalence(m1);

  ASSERT_EQUIVALENCE(b1, m1, start);
  ASSERT_EQUIVALENCE(t1);
  ASSERT_EQUIVALENCE(f1, b2, m2);
  ASSERT_EQUIVALENCE(t2);
  ASSERT_EQUIVALENCE(f2);
}


TEST_F(ControlEquivalenceTest, Diamond3) {
  Node* start = graph()->start();
  Node* b1 = Branch(start);
  Node* t1 = IfTrue(b1);
  Node* f1 = IfFalse(b1);
  Node* m1 = Merge2(t1, f1);
  Node* b2 = Branch(m1);
  Node* t2 = IfTrue(b2);
  Node* f2 = IfFalse(b2);
  Node* m2 = Merge2(t2, f2);
  ComputeEquivalence(m2);

  ASSERT_EQUIVALENCE(b1, m1, b2, m2, start);
  ASSERT_EQUIVALENCE(t1);
  ASSERT_EQUIVALENCE(f1);
  ASSERT_EQUIVALENCE(t2);
  ASSERT_EQUIVALENCE(f2);
}


TEST_F(ControlEquivalenceTest, Switch1) {
  Node* start = graph()->start();
  Node* b1 = Branch(start);
  Node* t1 = IfTrue(b1);
  Node* f1 = IfFalse(b1);
  Node* b2 = Branch(f1);
  Node* t2 = IfTrue(b2);
  Node* f2 = IfFalse(b2);
  Node* b3 = Branch(f2);
  Node* t3 = IfTrue(b3);
  Node* f3 = IfFalse(b3);
  Node* m1 = Merge2(t1, t2);
  Node* m2 = Merge2(m1, t3);
  Node* m3 = Merge2(m2, f3);
  ComputeEquivalence(m3);

  ASSERT_EQUIVALENCE(b1, m3, start);
  ASSERT_EQUIVALENCE(t1);
  ASSERT_EQUIVALENCE(f1, b2);
  ASSERT_EQUIVALENCE(t2);
  ASSERT_EQUIVALENCE(f2, b3);
  ASSERT_EQUIVALENCE(t3);
  ASSERT_EQUIVALENCE(f3);
  ASSERT_EQUIVALENCE(m1);
  ASSERT_EQUIVALENCE(m2);
}


TEST_F(ControlEquivalenceTest, Loop1) {
  Node* start = graph()->start();
  Node* l = Loop2(start);
  l->ReplaceInput(1, l);
  ComputeEquivalence(l);

  ASSERT_EQUIVALENCE(start);
  ASSERT_EQUIVALENCE(l);
}


TEST_F(ControlEquivalenceTest, Loop2) {
  Node* start = graph()->start();
  Node* l = Loop2(start);
  Node* b = Branch(l);
  Node* t = IfTrue(b);
  Node* f = IfFalse(b);
  l->ReplaceInput(1, t);
  ComputeEquivalence(f);

  ASSERT_EQUIVALENCE(f, start);
  ASSERT_EQUIVALENCE(t);
  ASSERT_EQUIVALENCE(l, b);
}


TEST_F(ControlEquivalenceTest, Irreducible) {
  Node* start = graph()->start();
  Node* b1 = Branch(start);
  Node* t1 = IfTrue(b1);
  Node* f1 = IfFalse(b1);
  Node* lp = Loop2(f1);
  Node* m1 = Merge2(t1, lp);
  Node* b2 = Branch(m1);
  Node* t2 = IfTrue(b2);
  Node* f2 = IfFalse(b2);
  Node* m2 = Merge2(t2, f2);
  Node* b3 = Branch(m2);
  Node* t3 = IfTrue(b3);
  Node* f3 = IfFalse(b3);
  lp->ReplaceInput(1, f3);
  ComputeEquivalence(t3);

  ASSERT_EQUIVALENCE(b1, t3, start);
  ASSERT_EQUIVALENCE(t1);
  ASSERT_EQUIVALENCE(f1);
  ASSERT_EQUIVALENCE(m1, b2, m2, b3);
  ASSERT_EQUIVALENCE(t2);
  ASSERT_EQUIVALENCE(f2);
  ASSERT_EQUIVALENCE(f3);
  ASSERT_EQUIVALENCE(lp);
}


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