// Copyright 2015 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/escape-analysis.h"
#include "src/compiler/escape-analysis-reducer.h"
#include "src/compiler/graph-visualizer.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/node-properties.h"
#include "src/compiler/simplified-operator.h"
#include "src/types-inl.h"
#include "src/zone-containers.h"
#include "test/unittests/compiler/graph-unittest.h"

namespace v8 {
namespace internal {
namespace compiler {

class EscapeAnalysisTest : public GraphTest {
 public:
  EscapeAnalysisTest()
      : simplified_(zone()),
        jsgraph_(isolate(), graph(), common(), nullptr, nullptr, nullptr),
        escape_analysis_(graph(), common(), zone()),
        effect_(graph()->start()),
        control_(graph()->start()) {}

  ~EscapeAnalysisTest() {}

  EscapeAnalysis* escape_analysis() { return &escape_analysis_; }

 protected:
  void Analysis() { escape_analysis_.Run(); }

  void Transformation() {
    GraphReducer graph_reducer(zone(), graph());
    EscapeAnalysisReducer escape_reducer(&graph_reducer, &jsgraph_,
                                         &escape_analysis_, zone());
    graph_reducer.AddReducer(&escape_reducer);
    graph_reducer.ReduceGraph();
  }

  // ---------------------------------Node Creation Helper----------------------

  Node* BeginRegion(Node* effect = nullptr) {
    if (!effect) {
      effect = effect_;
    }

    return effect_ = graph()->NewNode(common()->BeginRegion(), effect);
  }

  Node* FinishRegion(Node* value, Node* effect = nullptr) {
    if (!effect) {
      effect = effect_;
    }
    return effect_ = graph()->NewNode(common()->FinishRegion(), value, effect);
  }

  Node* Allocate(Node* size, Node* effect = nullptr, Node* control = nullptr) {
    if (!effect) {
      effect = effect_;
    }
    if (!control) {
      control = control_;
    }
    return effect_ = graph()->NewNode(simplified()->Allocate(), size, effect,
                                      control);
  }

  Node* Constant(int num) {
    return graph()->NewNode(common()->NumberConstant(num));
  }

  Node* Store(const FieldAccess& access, Node* allocation, Node* value,
              Node* effect = nullptr, Node* control = nullptr) {
    if (!effect) {
      effect = effect_;
    }
    if (!control) {
      control = control_;
    }
    return effect_ = graph()->NewNode(simplified()->StoreField(access),
                                      allocation, value, effect, control);
  }

  Node* Load(const FieldAccess& access, Node* from, Node* effect = nullptr,
             Node* control = nullptr) {
    if (!effect) {
      effect = effect_;
    }
    if (!control) {
      control = control_;
    }
    return graph()->NewNode(simplified()->LoadField(access), from, effect,
                            control);
  }

  Node* Return(Node* value, Node* effect = nullptr, Node* control = nullptr) {
    if (!effect) {
      effect = effect_;
    }
    if (!control) {
      control = control_;
    }
    return control_ =
               graph()->NewNode(common()->Return(), value, effect, control);
  }

  void EndGraph() {
    for (Edge edge : graph()->end()->input_edges()) {
      if (NodeProperties::IsControlEdge(edge)) {
        edge.UpdateTo(control_);
      }
    }
  }

  Node* Branch() {
    return control_ =
               graph()->NewNode(common()->Branch(), Constant(0), control_);
  }

  Node* IfTrue() {
    return control_ = graph()->NewNode(common()->IfTrue(), control_);
  }

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

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

  FieldAccess AccessAtIndex(int offset) {
    FieldAccess access = {kTaggedBase, offset, MaybeHandle<Name>(), Type::Any(),
                          MachineType::AnyTagged()};
    return access;
  }

  // ---------------------------------Assertion Helper--------------------------

  void ExpectReplacement(Node* node, Node* rep) {
    EXPECT_EQ(rep, escape_analysis()->GetReplacement(node));
  }

  void ExpectReplacementPhi(Node* node, Node* left, Node* right) {
    Node* rep = escape_analysis()->GetReplacement(node);
    ASSERT_NE(nullptr, rep);
    ASSERT_EQ(IrOpcode::kPhi, rep->opcode());
    EXPECT_EQ(left, NodeProperties::GetValueInput(rep, 0));
    EXPECT_EQ(right, NodeProperties::GetValueInput(rep, 1));
  }

  void ExpectVirtual(Node* node) {
    EXPECT_TRUE(node->opcode() == IrOpcode::kAllocate ||
                node->opcode() == IrOpcode::kFinishRegion);
    EXPECT_TRUE(escape_analysis()->IsVirtual(node));
  }

  void ExpectEscaped(Node* node) {
    EXPECT_TRUE(node->opcode() == IrOpcode::kAllocate ||
                node->opcode() == IrOpcode::kFinishRegion);
    EXPECT_TRUE(escape_analysis()->IsEscaped(node));
  }

  SimplifiedOperatorBuilder* simplified() { return &simplified_; }

  Node* effect() { return effect_; }

 private:
  SimplifiedOperatorBuilder simplified_;
  JSGraph jsgraph_;
  EscapeAnalysis escape_analysis_;

  Node* effect_;
  Node* control_;
};


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


TEST_F(EscapeAnalysisTest, StraightNonEscape) {
  Node* object1 = Constant(1);
  BeginRegion();
  Node* allocation = Allocate(Constant(kPointerSize));
  Store(AccessAtIndex(0), allocation, object1);
  Node* finish = FinishRegion(allocation);
  Node* load = Load(AccessAtIndex(0), finish);
  Node* result = Return(load);
  EndGraph();

  Analysis();

  ExpectVirtual(allocation);
  ExpectReplacement(load, object1);

  Transformation();

  ASSERT_EQ(object1, NodeProperties::GetValueInput(result, 0));
}


TEST_F(EscapeAnalysisTest, StraightEscape) {
  Node* object1 = Constant(1);
  BeginRegion();
  Node* allocation = Allocate(Constant(kPointerSize));
  Store(AccessAtIndex(0), allocation, object1);
  Node* finish = FinishRegion(allocation);
  Node* load = Load(AccessAtIndex(0), finish);
  Node* result = Return(allocation);
  EndGraph();
  graph()->end()->AppendInput(zone(), load);

  Analysis();

  ExpectEscaped(allocation);
  ExpectReplacement(load, object1);

  Transformation();

  ASSERT_EQ(allocation, NodeProperties::GetValueInput(result, 0));
}


TEST_F(EscapeAnalysisTest, StoreLoadEscape) {
  Node* object1 = Constant(1);

  BeginRegion();
  Node* allocation1 = Allocate(Constant(kPointerSize));
  Store(AccessAtIndex(0), allocation1, object1);
  Node* finish1 = FinishRegion(allocation1);

  BeginRegion();
  Node* allocation2 = Allocate(Constant(kPointerSize));
  Store(AccessAtIndex(0), allocation2, finish1);
  Node* finish2 = FinishRegion(allocation2);

  Node* load = Load(AccessAtIndex(0), finish2);
  Node* result = Return(load);
  EndGraph();
  Analysis();

  ExpectEscaped(allocation1);
  ExpectVirtual(allocation2);
  ExpectReplacement(load, finish1);

  Transformation();

  ASSERT_EQ(finish1, NodeProperties::GetValueInput(result, 0));
}


TEST_F(EscapeAnalysisTest, BranchNonEscape) {
  Node* object1 = Constant(1);
  Node* object2 = Constant(2);
  BeginRegion();
  Node* allocation = Allocate(Constant(kPointerSize));
  Store(AccessAtIndex(0), allocation, object1);
  Node* finish = FinishRegion(allocation);
  Branch();
  Node* ifFalse = IfFalse();
  Node* ifTrue = IfTrue();
  Node* effect1 = Store(AccessAtIndex(0), allocation, object1, finish, ifFalse);
  Node* effect2 = Store(AccessAtIndex(0), allocation, object2, finish, ifTrue);
  Node* merge = Merge2(ifFalse, ifTrue);
  Node* phi = graph()->NewNode(common()->EffectPhi(2), effect1, effect2, merge);
  Node* load = Load(AccessAtIndex(0), finish, phi, merge);
  Node* result = Return(load, phi);
  EndGraph();
  graph()->end()->AppendInput(zone(), result);

  Analysis();

  ExpectVirtual(allocation);
  ExpectReplacementPhi(load, object1, object2);
  Node* replacement_phi = escape_analysis()->GetReplacement(load);

  Transformation();

  ASSERT_EQ(replacement_phi, NodeProperties::GetValueInput(result, 0));
}


TEST_F(EscapeAnalysisTest, DanglingLoadOrder) {
  Node* object1 = Constant(1);
  Node* object2 = Constant(2);
  Node* allocation = Allocate(Constant(kPointerSize));
  Node* store1 = Store(AccessAtIndex(0), allocation, object1);
  Node* load1 = Load(AccessAtIndex(0), allocation);
  Node* store2 = Store(AccessAtIndex(0), allocation, object2);
  Node* load2 = Load(AccessAtIndex(0), allocation, store1);
  Node* result = Return(load2);
  EndGraph();
  graph()->end()->AppendInput(zone(), store2);
  graph()->end()->AppendInput(zone(), load1);

  Analysis();

  ExpectVirtual(allocation);
  ExpectReplacement(load1, object1);
  ExpectReplacement(load2, object1);

  Transformation();

  ASSERT_EQ(object1, NodeProperties::GetValueInput(result, 0));
}


TEST_F(EscapeAnalysisTest, DeoptReplacement) {
  Node* object1 = Constant(1);
  BeginRegion();
  Node* allocation = Allocate(Constant(kPointerSize));
  Store(AccessAtIndex(0), allocation, object1);
  Node* finish = FinishRegion(allocation);
  Node* effect1 = Store(AccessAtIndex(0), allocation, object1, finish);
  Branch();
  Node* ifFalse = IfFalse();
  Node* state_values1 = graph()->NewNode(common()->StateValues(1), finish);
  Node* state_values2 = graph()->NewNode(common()->StateValues(0));
  Node* state_values3 = graph()->NewNode(common()->StateValues(0));
  Node* frame_state = graph()->NewNode(
      common()->FrameState(BailoutId::None(), OutputFrameStateCombine::Ignore(),
                           nullptr),
      state_values1, state_values2, state_values3, UndefinedConstant(),
      graph()->start(), graph()->start());
  Node* deopt = graph()->NewNode(common()->Deoptimize(DeoptimizeKind::kEager),
                                 frame_state, effect1, ifFalse);
  Node* ifTrue = IfTrue();
  Node* load = Load(AccessAtIndex(0), finish, effect1, ifTrue);
  Node* result = Return(load, effect1, ifTrue);
  EndGraph();
  graph()->end()->AppendInput(zone(), deopt);
  Analysis();

  ExpectVirtual(allocation);
  ExpectReplacement(load, object1);

  Transformation();

  ASSERT_EQ(object1, NodeProperties::GetValueInput(result, 0));
  Node* object_state = NodeProperties::GetValueInput(state_values1, 0);
  ASSERT_EQ(object_state->opcode(), IrOpcode::kObjectState);
  ASSERT_EQ(1, object_state->op()->ValueInputCount());
  ASSERT_EQ(object1, NodeProperties::GetValueInput(object_state, 0));
}


TEST_F(EscapeAnalysisTest, DeoptReplacementIdentity) {
  Node* object1 = Constant(1);
  BeginRegion();
  Node* allocation = Allocate(Constant(kPointerSize * 2));
  Store(AccessAtIndex(0), allocation, object1);
  Store(AccessAtIndex(kPointerSize), allocation, allocation);
  Node* finish = FinishRegion(allocation);
  Node* effect1 = Store(AccessAtIndex(0), allocation, object1, finish);
  Branch();
  Node* ifFalse = IfFalse();
  Node* state_values1 = graph()->NewNode(common()->StateValues(1), finish);
  Node* state_values2 = graph()->NewNode(common()->StateValues(1), finish);
  Node* state_values3 = graph()->NewNode(common()->StateValues(0));
  Node* frame_state = graph()->NewNode(
      common()->FrameState(BailoutId::None(), OutputFrameStateCombine::Ignore(),
                           nullptr),
      state_values1, state_values2, state_values3, UndefinedConstant(),
      graph()->start(), graph()->start());
  Node* deopt = graph()->NewNode(common()->Deoptimize(DeoptimizeKind::kEager),
                                 frame_state, effect1, ifFalse);
  Node* ifTrue = IfTrue();
  Node* load = Load(AccessAtIndex(0), finish, effect1, ifTrue);
  Node* result = Return(load, effect1, ifTrue);
  EndGraph();
  graph()->end()->AppendInput(zone(), deopt);
  Analysis();

  ExpectVirtual(allocation);
  ExpectReplacement(load, object1);

  Transformation();

  ASSERT_EQ(object1, NodeProperties::GetValueInput(result, 0));

  Node* object_state = NodeProperties::GetValueInput(state_values1, 0);
  ASSERT_EQ(object_state->opcode(), IrOpcode::kObjectState);
  ASSERT_EQ(2, object_state->op()->ValueInputCount());
  ASSERT_EQ(object1, NodeProperties::GetValueInput(object_state, 0));
  ASSERT_EQ(object_state, NodeProperties::GetValueInput(object_state, 1));

  Node* object_state2 = NodeProperties::GetValueInput(state_values1, 0);
  ASSERT_EQ(object_state, object_state2);
}

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