// 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/compiler/effect-control-linearizer.h"
#include "src/compiler/access-builder.h"
#include "src/compiler/compiler-source-position-table.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/linkage.h"
#include "src/compiler/node-origin-table.h"
#include "src/compiler/node-properties.h"
#include "src/compiler/schedule.h"
#include "src/compiler/simplified-operator.h"
#include "test/unittests/compiler/graph-unittest.h"
#include "test/unittests/compiler/node-test-utils.h"
#include "test/unittests/test-utils.h"
#include "testing/gmock-support.h"
#include "testing/gmock/include/gmock/gmock.h"

namespace v8 {
namespace internal {
namespace compiler {

using testing::Capture;

class EffectControlLinearizerTest : public GraphTest {
 public:
  EffectControlLinearizerTest()
      : GraphTest(3),
        machine_(zone()),
        javascript_(zone()),
        simplified_(zone()),
        jsgraph_(isolate(), graph(), common(), &javascript_, &simplified_,
                 &machine_) {
    source_positions_ = zone()->New<SourcePositionTable>(graph());
    node_origins_ = zone()->New<NodeOriginTable>(graph());
  }

  JSGraph* jsgraph() { return &jsgraph_; }
  SimplifiedOperatorBuilder* simplified() { return &simplified_; }
  SourcePositionTable* source_positions() { return source_positions_; }
  NodeOriginTable* node_origins() { return node_origins_; }

 private:
  MachineOperatorBuilder machine_;
  JSOperatorBuilder javascript_;
  SimplifiedOperatorBuilder simplified_;
  JSGraph jsgraph_;
  SourcePositionTable* source_positions_;
  NodeOriginTable* node_origins_;
};

namespace {

BasicBlock* AddBlockToSchedule(Schedule* schedule) {
  BasicBlock* block = schedule->NewBasicBlock();
  block->set_rpo_number(static_cast<int32_t>(schedule->rpo_order()->size()));
  schedule->rpo_order()->push_back(block);
  return block;
}

}  // namespace

TEST_F(EffectControlLinearizerTest, SimpleLoad) {
  Schedule schedule(zone());

  // Create the graph.
  Node* heap_number = NumberConstant(0.5);
  Node* load = graph()->NewNode(
      simplified()->LoadField(AccessBuilder::ForHeapNumberValue()), heap_number,
      graph()->start(), graph()->start());
  Node* zero = graph()->NewNode(common()->Int32Constant(0));
  Node* ret = graph()->NewNode(common()->Return(), zero, load, graph()->start(),
                               graph()->start());

  // Build the basic block structure.
  BasicBlock* start = schedule.start();
  schedule.rpo_order()->push_back(start);
  start->set_rpo_number(0);

  // Populate the basic blocks with nodes.
  schedule.AddNode(start, graph()->start());
  schedule.AddNode(start, heap_number);
  schedule.AddNode(start, load);
  schedule.AddReturn(start, ret);

  // Run the state effect introducer.
  LinearizeEffectControl(jsgraph(), &schedule, zone(), source_positions(),
                         node_origins(), PoisoningMitigationLevel::kDontPoison,
                         broker());

  EXPECT_THAT(load,
              IsLoadField(AccessBuilder::ForHeapNumberValue(), heap_number,
                          graph()->start(), graph()->start()));
  // The return should have reconnected effect edge to the load.
  EXPECT_THAT(ret, IsReturn(load, load, graph()->start()));
}

TEST_F(EffectControlLinearizerTest, DiamondLoad) {
  Schedule schedule(zone());

  // Create the graph.
  Node* branch =
      graph()->NewNode(common()->Branch(), Int32Constant(0), graph()->start());

  Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
  Node* heap_number = NumberConstant(0.5);
  Node* vtrue = graph()->NewNode(
      simplified()->LoadField(AccessBuilder::ForHeapNumberValue()), heap_number,
      graph()->start(), if_true);

  Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
  Node* vfalse = Float64Constant(2);

  Node* merge = graph()->NewNode(common()->Merge(2), if_true, if_false);
  Node* phi = graph()->NewNode(
      common()->Phi(MachineRepresentation::kFloat64, 2), vtrue, vfalse, merge);

  Node* zero = graph()->NewNode(common()->Int32Constant(0));
  Node* ret =
      graph()->NewNode(common()->Return(), zero, phi, graph()->start(), merge);

  // Build the basic block structure.
  BasicBlock* start = schedule.start();
  schedule.rpo_order()->push_back(start);
  start->set_rpo_number(0);

  BasicBlock* tblock = AddBlockToSchedule(&schedule);
  BasicBlock* fblock = AddBlockToSchedule(&schedule);
  BasicBlock* mblock = AddBlockToSchedule(&schedule);

  // Populate the basic blocks with nodes.
  schedule.AddNode(start, graph()->start());
  schedule.AddBranch(start, branch, tblock, fblock);

  schedule.AddNode(tblock, if_true);
  schedule.AddNode(tblock, heap_number);
  schedule.AddNode(tblock, vtrue);
  schedule.AddGoto(tblock, mblock);

  schedule.AddNode(fblock, if_false);
  schedule.AddNode(fblock, vfalse);
  schedule.AddGoto(fblock, mblock);

  schedule.AddNode(mblock, merge);
  schedule.AddNode(mblock, phi);
  schedule.AddReturn(mblock, ret);

  // Run the state effect introducer.
  LinearizeEffectControl(jsgraph(), &schedule, zone(), source_positions(),
                         node_origins(), PoisoningMitigationLevel::kDontPoison,
                         broker());

  // The effect input to the return should be an effect phi with the
  // newly introduced effectful change operators.
  ASSERT_THAT(
      ret, IsReturn(phi, IsEffectPhi(vtrue, graph()->start(), merge), merge));
}

TEST_F(EffectControlLinearizerTest, LoopLoad) {
  Schedule schedule(zone());

  // Create the graph.
  Node* loop = graph()->NewNode(common()->Loop(1), graph()->start());
  Node* effect_phi =
      graph()->NewNode(common()->EffectPhi(1), graph()->start(), loop);

  Node* cond = Int32Constant(0);
  Node* branch = graph()->NewNode(common()->Branch(), cond, loop);

  Node* if_true = graph()->NewNode(common()->IfTrue(), branch);

  Node* if_false = graph()->NewNode(common()->IfFalse(), branch);

  loop->AppendInput(zone(), if_false);
  NodeProperties::ChangeOp(loop, common()->Loop(2));

  effect_phi->InsertInput(zone(), 1, effect_phi);
  NodeProperties::ChangeOp(effect_phi, common()->EffectPhi(2));

  Node* heap_number = NumberConstant(0.5);
  Node* load = graph()->NewNode(
      simplified()->LoadField(AccessBuilder::ForHeapNumberValue()), heap_number,
      graph()->start(), loop);

  Node* zero = graph()->NewNode(common()->Int32Constant(0));
  Node* ret =
      graph()->NewNode(common()->Return(), zero, load, effect_phi, if_true);

  // Build the basic block structure.
  BasicBlock* start = schedule.start();
  schedule.rpo_order()->push_back(start);
  start->set_rpo_number(0);

  BasicBlock* lblock = AddBlockToSchedule(&schedule);
  BasicBlock* fblock = AddBlockToSchedule(&schedule);
  BasicBlock* rblock = AddBlockToSchedule(&schedule);

  // Populate the basic blocks with nodes.
  schedule.AddNode(start, graph()->start());
  schedule.AddGoto(start, lblock);

  schedule.AddNode(lblock, loop);
  schedule.AddNode(lblock, effect_phi);
  schedule.AddNode(lblock, heap_number);
  schedule.AddNode(lblock, load);
  schedule.AddNode(lblock, cond);
  schedule.AddBranch(lblock, branch, rblock, fblock);

  schedule.AddNode(fblock, if_false);
  schedule.AddGoto(fblock, lblock);

  schedule.AddNode(rblock, if_true);
  schedule.AddReturn(rblock, ret);

  // Run the state effect introducer.
  LinearizeEffectControl(jsgraph(), &schedule, zone(), source_positions(),
                         node_origins(), PoisoningMitigationLevel::kDontPoison,
                         broker());

  ASSERT_THAT(ret, IsReturn(load, load, if_true));
  EXPECT_THAT(load, IsLoadField(AccessBuilder::ForHeapNumberValue(),
                                heap_number, effect_phi, loop));
}

TEST_F(EffectControlLinearizerTest, CloneBranch) {
  Schedule schedule(zone());

  Node* cond0 = Parameter(0);
  Node* cond1 = Parameter(1);
  Node* cond2 = Parameter(2);
  Node* branch0 = graph()->NewNode(common()->Branch(), cond0, start());
  Node* control1 = graph()->NewNode(common()->IfTrue(), branch0);
  Node* control2 = graph()->NewNode(common()->IfFalse(), branch0);
  Node* merge0 = graph()->NewNode(common()->Merge(2), control1, control2);
  Node* phi0 = graph()->NewNode(common()->Phi(MachineRepresentation::kBit, 2),
                                cond1, cond2, merge0);
  Node* branch = graph()->NewNode(common()->Branch(), phi0, merge0);
  Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
  Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
  Node* merge = graph()->NewNode(common()->Merge(2), if_true, if_false);
  graph()->SetEnd(graph()->NewNode(common()->End(1), merge));

  BasicBlock* start = schedule.start();
  schedule.rpo_order()->push_back(start);
  start->set_rpo_number(0);

  BasicBlock* f1block = AddBlockToSchedule(&schedule);
  BasicBlock* t1block = AddBlockToSchedule(&schedule);
  BasicBlock* bblock = AddBlockToSchedule(&schedule);

  BasicBlock* f2block = AddBlockToSchedule(&schedule);
  BasicBlock* t2block = AddBlockToSchedule(&schedule);
  BasicBlock* mblock = AddBlockToSchedule(&schedule);

  // Populate the basic blocks with nodes.
  schedule.AddNode(start, graph()->start());

  schedule.AddBranch(start, branch0, t1block, f1block);

  schedule.AddNode(t1block, control1);
  schedule.AddGoto(t1block, bblock);

  schedule.AddNode(f1block, control2);
  schedule.AddGoto(f1block, bblock);

  schedule.AddNode(bblock, merge0);
  schedule.AddNode(bblock, phi0);
  schedule.AddBranch(bblock, branch, t2block, f2block);

  schedule.AddNode(t2block, if_true);
  schedule.AddGoto(t2block, mblock);

  schedule.AddNode(f2block, if_false);
  schedule.AddGoto(f2block, mblock);

  schedule.AddNode(mblock, merge);
  schedule.AddNode(mblock, graph()->end());

  LinearizeEffectControl(jsgraph(), &schedule, zone(), source_positions(),
                         node_origins(), PoisoningMitigationLevel::kDontPoison,
                         broker());

  Capture<Node *> branch1_capture, branch2_capture;
  EXPECT_THAT(
      end(),
      IsEnd(IsMerge(IsMerge(IsIfTrue(CaptureEq(&branch1_capture)),
                            IsIfTrue(CaptureEq(&branch2_capture))),
                    IsMerge(IsIfFalse(AllOf(CaptureEq(&branch1_capture),
                                            IsBranch(cond1, control1))),
                            IsIfFalse(AllOf(CaptureEq(&branch2_capture),
                                            IsBranch(cond2, control2)))))));
}

TEST_F(EffectControlLinearizerTest, UnreachableThenBranch) {
  Schedule schedule(zone());

  // Create the graph.
  Node* unreachable = graph()->NewNode(common()->Unreachable(),
                                       graph()->start(), graph()->start());
  Node* branch =
      graph()->NewNode(common()->Branch(), Int32Constant(0), graph()->start());

  Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
  Node* true_throw = graph()->NewNode(common()->Throw(), unreachable, if_true);

  Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
  Node* false_throw =
      graph()->NewNode(common()->Throw(), unreachable, if_false);

  graph()->SetEnd(graph()->NewNode(common()->End(0)));

  // Build the basic block structure.
  BasicBlock* start = schedule.start();
  schedule.rpo_order()->push_back(start);
  start->set_rpo_number(0);

  BasicBlock* tblock = AddBlockToSchedule(&schedule);
  BasicBlock* fblock = AddBlockToSchedule(&schedule);

  // Populate the basic blocks with nodes.
  schedule.AddNode(start, graph()->start());
  schedule.AddNode(start, unreachable);
  schedule.AddBranch(start, branch, tblock, fblock);

  schedule.AddNode(tblock, if_true);
  schedule.AddThrow(tblock, true_throw);
  NodeProperties::MergeControlToEnd(graph(), common(), true_throw);

  schedule.AddNode(fblock, if_false);
  schedule.AddThrow(fblock, false_throw);
  NodeProperties::MergeControlToEnd(graph(), common(), false_throw);

  ASSERT_THAT(end(), IsEnd(IsThrow(), IsThrow()));
  ASSERT_THAT(end()->op()->ControlInputCount(), 2);

  // Run the state effect linearizer and machine lowering, maintaining the
  // schedule.
  LowerToMachineSchedule(jsgraph(), &schedule, zone(), source_positions(),
                         node_origins(), PoisoningMitigationLevel::kDontPoison,
                         broker());

  ASSERT_THAT(end(), IsEnd(IsThrow()));
}

TEST_F(EffectControlLinearizerTest, UnreachableThenDiamond) {
  Schedule schedule(zone());

  // Create the graph.
  Node* unreachable = graph()->NewNode(common()->Unreachable(),
                                       graph()->start(), graph()->start());
  Node* branch =
      graph()->NewNode(common()->Branch(), Int32Constant(0), graph()->start());

  Node* if_true = graph()->NewNode(common()->IfTrue(), branch);

  Node* if_false = graph()->NewNode(common()->IfFalse(), branch);

  Node* merge = graph()->NewNode(common()->Merge(2), if_true, if_false);
  Node* throw_node = graph()->NewNode(common()->Throw(), unreachable, if_false);
  graph()->SetEnd(graph()->NewNode(common()->End(0)));

  // Build the basic block structure.
  BasicBlock* start = schedule.start();
  schedule.rpo_order()->push_back(start);
  start->set_rpo_number(0);

  BasicBlock* tblock = AddBlockToSchedule(&schedule);
  BasicBlock* fblock = AddBlockToSchedule(&schedule);
  BasicBlock* mblock = AddBlockToSchedule(&schedule);

  // Populate the basic blocks with nodes.
  schedule.AddNode(start, graph()->start());
  schedule.AddNode(start, unreachable);
  schedule.AddBranch(start, branch, tblock, fblock);

  schedule.AddNode(tblock, if_true);
  schedule.AddGoto(tblock, mblock);

  schedule.AddNode(fblock, if_false);
  schedule.AddGoto(fblock, mblock);

  schedule.AddNode(mblock, merge);
  schedule.AddThrow(mblock, throw_node);
  NodeProperties::MergeControlToEnd(graph(), common(), throw_node);

  ASSERT_THAT(end(), IsEnd(IsThrow()));
  ASSERT_THAT(end()->op()->ControlInputCount(), 1);

  // Run the state effect linearizer and machine lowering, maintaining the
  // schedule.
  LowerToMachineSchedule(jsgraph(), &schedule, zone(), source_positions(),
                         node_origins(), PoisoningMitigationLevel::kDontPoison,
                         broker());

  ASSERT_THAT(end(), IsEnd(IsThrow()));
}

TEST_F(EffectControlLinearizerTest, UnreachableThenLoop) {
  Schedule schedule(zone());

  // Create the graph.
  Node* unreachable = graph()->NewNode(common()->Unreachable(),
                                       graph()->start(), graph()->start());
  Node* loop = graph()->NewNode(common()->Loop(1), graph()->start());

  Node* cond = Int32Constant(0);
  Node* branch = graph()->NewNode(common()->Branch(), cond, loop);

  Node* if_true = graph()->NewNode(common()->IfTrue(), branch);

  Node* if_false = graph()->NewNode(common()->IfFalse(), branch);

  loop->AppendInput(zone(), if_false);
  NodeProperties::ChangeOp(loop, common()->Loop(2));

  Node* throw_node = graph()->NewNode(common()->Throw(), unreachable, if_false);
  graph()->SetEnd(graph()->NewNode(common()->End(0)));

  // Build the basic block structure.
  BasicBlock* start = schedule.start();
  schedule.rpo_order()->push_back(start);
  start->set_rpo_number(0);

  BasicBlock* lblock = AddBlockToSchedule(&schedule);
  BasicBlock* fblock = AddBlockToSchedule(&schedule);
  BasicBlock* tblock = AddBlockToSchedule(&schedule);

  // Populate the basic blocks with nodes.
  schedule.AddNode(start, graph()->start());
  schedule.AddNode(start, unreachable);
  schedule.AddGoto(start, lblock);

  schedule.AddNode(lblock, loop);
  schedule.AddNode(lblock, cond);
  schedule.AddBranch(lblock, branch, tblock, fblock);

  schedule.AddNode(fblock, if_false);
  schedule.AddGoto(fblock, lblock);

  schedule.AddNode(tblock, if_true);
  schedule.AddThrow(tblock, throw_node);
  NodeProperties::MergeControlToEnd(graph(), common(), throw_node);

  ASSERT_THAT(end(), IsEnd(IsThrow()));
  ASSERT_THAT(end()->op()->ControlInputCount(), 1);

  // Run the state effect linearizer and machine lowering, maintaining the
  // schedule.
  LowerToMachineSchedule(jsgraph(), &schedule, zone(), source_positions(),
                         node_origins(), PoisoningMitigationLevel::kDontPoison,
                         broker());

  ASSERT_THAT(end(), IsEnd(IsThrow()));
}

TEST_F(EffectControlLinearizerTest, UnreachableInChangedBlockThenBranch) {
  Schedule schedule(zone());

  // Create the graph.
  Node* truncate = graph()->NewNode(simplified()->TruncateTaggedToWord32(),
                                    NumberConstant(1.1));
  Node* unreachable = graph()->NewNode(common()->Unreachable(),
                                       graph()->start(), graph()->start());
  Node* branch =
      graph()->NewNode(common()->Branch(), Int32Constant(0), graph()->start());

  Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
  Node* true_throw = graph()->NewNode(common()->Throw(), unreachable, if_true);

  Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
  Node* false_throw =
      graph()->NewNode(common()->Throw(), unreachable, if_false);

  graph()->SetEnd(graph()->NewNode(common()->End(0)));

  // Build the basic block structure.
  BasicBlock* start = schedule.start();
  schedule.rpo_order()->push_back(start);
  start->set_rpo_number(0);

  BasicBlock* tblock = AddBlockToSchedule(&schedule);
  BasicBlock* fblock = AddBlockToSchedule(&schedule);

  // Populate the basic blocks with nodes.
  schedule.AddNode(start, graph()->start());
  schedule.AddNode(start, truncate);
  schedule.AddNode(start, unreachable);
  schedule.AddBranch(start, branch, tblock, fblock);

  schedule.AddNode(tblock, if_true);
  schedule.AddThrow(tblock, true_throw);
  NodeProperties::MergeControlToEnd(graph(), common(), true_throw);

  schedule.AddNode(fblock, if_false);
  schedule.AddThrow(fblock, false_throw);
  NodeProperties::MergeControlToEnd(graph(), common(), false_throw);

  ASSERT_THAT(end(), IsEnd(IsThrow(), IsThrow()));
  ASSERT_THAT(end()->op()->ControlInputCount(), 2);

  // Run the state effect linearizer and machine lowering, maintaining the
  // schedule.
  LowerToMachineSchedule(jsgraph(), &schedule, zone(), source_positions(),
                         node_origins(), PoisoningMitigationLevel::kDontPoison,
                         broker());

  ASSERT_THAT(end(), IsEnd(IsThrow()));
}

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