// 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/backend/code-generator.h"
#include "src/compiler/backend/instruction.h"
#include "src/compiler/common-operator.h"
#include "src/compiler/graph.h"
#include "src/compiler/linkage.h"
#include "src/compiler/machine-operator.h"
#include "src/compiler/node.h"
#include "src/compiler/operator.h"
#include "src/compiler/schedule.h"
#include "src/compiler/scheduler.h"
#include "src/objects/objects-inl.h"
#include "test/cctest/cctest.h"

namespace v8 {
namespace internal {
namespace compiler {

using TestInstr = v8::internal::compiler::Instruction;
using TestInstrSeq = v8::internal::compiler::InstructionSequence;

// A testing helper for the register code abstraction.
class InstructionTester : public HandleAndZoneScope {
 public:  // We're all friends here.
  InstructionTester()
      : graph(zone()),
        schedule(zone()),
        common(zone()),
        machine(zone()),
        code(nullptr) {}

  Graph graph;
  Schedule schedule;
  CommonOperatorBuilder common;
  MachineOperatorBuilder machine;
  TestInstrSeq* code;

  Zone* zone() { return main_zone(); }

  void allocCode() {
    if (schedule.rpo_order()->size() == 0) {
      // Compute the RPO order.
      Scheduler::ComputeSpecialRPO(main_zone(), &schedule);
      CHECK_NE(0u, schedule.rpo_order()->size());
    }
    InstructionBlocks* instruction_blocks =
        TestInstrSeq::InstructionBlocksFor(main_zone(), &schedule);
    code = new (main_zone())
        TestInstrSeq(main_isolate(), main_zone(), instruction_blocks);
  }

  Node* Int32Constant(int32_t val) {
    Node* node = graph.NewNode(common.Int32Constant(val));
    schedule.AddNode(schedule.start(), node);
    return node;
  }

  Node* Float64Constant(double val) {
    Node* node = graph.NewNode(common.Float64Constant(val));
    schedule.AddNode(schedule.start(), node);
    return node;
  }

  Node* Parameter(int32_t which) {
    Node* node = graph.NewNode(common.Parameter(which));
    schedule.AddNode(schedule.start(), node);
    return node;
  }

  Node* NewNode(BasicBlock* block) {
    Node* node = graph.NewNode(common.Int32Constant(111));
    schedule.AddNode(block, node);
    return node;
  }

  int NewInstr() {
    InstructionCode opcode = static_cast<InstructionCode>(110);
    TestInstr* instr = TestInstr::New(zone(), opcode);
    return code->AddInstruction(instr);
  }

  int NewNop() {
    TestInstr* instr = TestInstr::New(zone(), kArchNop);
    return code->AddInstruction(instr);
  }

  UnallocatedOperand Unallocated(int vreg) {
    return UnallocatedOperand(UnallocatedOperand::REGISTER_OR_SLOT, vreg);
  }

  RpoNumber RpoFor(BasicBlock* block) {
    return RpoNumber::FromInt(block->rpo_number());
  }

  InstructionBlock* BlockAt(BasicBlock* block) {
    return code->InstructionBlockAt(RpoFor(block));
  }
  BasicBlock* GetBasicBlock(int instruction_index) {
    const InstructionBlock* block =
        code->GetInstructionBlock(instruction_index);
    return schedule.rpo_order()->at(block->rpo_number().ToSize());
  }
  int first_instruction_index(BasicBlock* block) {
    return BlockAt(block)->first_instruction_index();
  }
  int last_instruction_index(BasicBlock* block) {
    return BlockAt(block)->last_instruction_index();
  }
};


TEST(InstructionBasic) {
  InstructionTester R;

  for (int i = 0; i < 10; i++) {
    R.Int32Constant(i);  // Add some nodes to the graph.
  }

  BasicBlock* last = R.schedule.start();
  for (int i = 0; i < 5; i++) {
    BasicBlock* block = R.schedule.NewBasicBlock();
    R.schedule.AddGoto(last, block);
    last = block;
  }

  R.allocCode();

  BasicBlockVector* blocks = R.schedule.rpo_order();
  CHECK_EQ(static_cast<int>(blocks->size()), R.code->InstructionBlockCount());

  for (auto block : *blocks) {
    CHECK_EQ(block->rpo_number(), R.BlockAt(block)->rpo_number().ToInt());
    CHECK(!block->loop_end());
  }
}


TEST(InstructionGetBasicBlock) {
  InstructionTester R;

  BasicBlock* b0 = R.schedule.start();
  BasicBlock* b1 = R.schedule.NewBasicBlock();
  BasicBlock* b2 = R.schedule.NewBasicBlock();
  BasicBlock* b3 = R.schedule.end();

  R.schedule.AddGoto(b0, b1);
  R.schedule.AddGoto(b1, b2);
  R.schedule.AddGoto(b2, b3);

  R.allocCode();

  R.code->StartBlock(R.RpoFor(b0));
  int i0 = R.NewInstr();
  int i1 = R.NewInstr();
  R.code->EndBlock(R.RpoFor(b0));
  R.code->StartBlock(R.RpoFor(b1));
  int i2 = R.NewInstr();
  int i3 = R.NewInstr();
  int i4 = R.NewInstr();
  int i5 = R.NewInstr();
  R.code->EndBlock(R.RpoFor(b1));
  R.code->StartBlock(R.RpoFor(b2));
  int i6 = R.NewInstr();
  int i7 = R.NewInstr();
  int i8 = R.NewInstr();
  R.code->EndBlock(R.RpoFor(b2));
  R.code->StartBlock(R.RpoFor(b3));
  R.NewNop();
  R.code->EndBlock(R.RpoFor(b3));

  CHECK_EQ(b0, R.GetBasicBlock(i0));
  CHECK_EQ(b0, R.GetBasicBlock(i1));

  CHECK_EQ(b1, R.GetBasicBlock(i2));
  CHECK_EQ(b1, R.GetBasicBlock(i3));
  CHECK_EQ(b1, R.GetBasicBlock(i4));
  CHECK_EQ(b1, R.GetBasicBlock(i5));

  CHECK_EQ(b2, R.GetBasicBlock(i6));
  CHECK_EQ(b2, R.GetBasicBlock(i7));
  CHECK_EQ(b2, R.GetBasicBlock(i8));

  CHECK_EQ(b0, R.GetBasicBlock(R.first_instruction_index(b0)));
  CHECK_EQ(b0, R.GetBasicBlock(R.last_instruction_index(b0)));

  CHECK_EQ(b1, R.GetBasicBlock(R.first_instruction_index(b1)));
  CHECK_EQ(b1, R.GetBasicBlock(R.last_instruction_index(b1)));

  CHECK_EQ(b2, R.GetBasicBlock(R.first_instruction_index(b2)));
  CHECK_EQ(b2, R.GetBasicBlock(R.last_instruction_index(b2)));

  CHECK_EQ(b3, R.GetBasicBlock(R.first_instruction_index(b3)));
  CHECK_EQ(b3, R.GetBasicBlock(R.last_instruction_index(b3)));
}


TEST(InstructionIsGapAt) {
  InstructionTester R;

  BasicBlock* b0 = R.schedule.start();
  R.schedule.AddReturn(b0, R.Int32Constant(1));

  R.allocCode();
  TestInstr* i0 = TestInstr::New(R.zone(), 100);
  TestInstr* g = TestInstr::New(R.zone(), 103);
  R.code->StartBlock(R.RpoFor(b0));
  R.code->AddInstruction(i0);
  R.code->AddInstruction(g);
  R.code->EndBlock(R.RpoFor(b0));

  CHECK_EQ(2, R.code->instructions().size());
}


TEST(InstructionIsGapAt2) {
  InstructionTester R;

  BasicBlock* b0 = R.schedule.start();
  BasicBlock* b1 = R.schedule.end();
  R.schedule.AddGoto(b0, b1);
  R.schedule.AddReturn(b1, R.Int32Constant(1));

  R.allocCode();
  TestInstr* i0 = TestInstr::New(R.zone(), 100);
  TestInstr* g = TestInstr::New(R.zone(), 103);
  R.code->StartBlock(R.RpoFor(b0));
  R.code->AddInstruction(i0);
  R.code->AddInstruction(g);
  R.code->EndBlock(R.RpoFor(b0));

  TestInstr* i1 = TestInstr::New(R.zone(), 102);
  TestInstr* g1 = TestInstr::New(R.zone(), 104);
  R.code->StartBlock(R.RpoFor(b1));
  R.code->AddInstruction(i1);
  R.code->AddInstruction(g1);
  R.code->EndBlock(R.RpoFor(b1));

  CHECK_EQ(4, R.code->instructions().size());
}


TEST(InstructionAddGapMove) {
  InstructionTester R;

  BasicBlock* b0 = R.schedule.start();
  R.schedule.AddReturn(b0, R.Int32Constant(1));

  R.allocCode();
  TestInstr* i0 = TestInstr::New(R.zone(), 100);
  TestInstr* g = TestInstr::New(R.zone(), 103);
  R.code->StartBlock(R.RpoFor(b0));
  R.code->AddInstruction(i0);
  R.code->AddInstruction(g);
  R.code->EndBlock(R.RpoFor(b0));

  CHECK_EQ(2, R.code->instructions().size());

  int index = 0;
  for (auto instr : R.code->instructions()) {
    UnallocatedOperand op1 = R.Unallocated(index++);
    UnallocatedOperand op2 = R.Unallocated(index++);
    instr->GetOrCreateParallelMove(TestInstr::START, R.zone())
        ->AddMove(op1, op2);
    ParallelMove* move = instr->GetParallelMove(TestInstr::START);
    CHECK(move);
    CHECK_EQ(1u, move->size());
    MoveOperands* cur = move->at(0);
    CHECK(op1.Equals(cur->source()));
    CHECK(op2.Equals(cur->destination()));
  }
}


TEST(InstructionOperands) {
  v8::internal::AccountingAllocator allocator;
  Zone zone(&allocator, ZONE_NAME);

  {
    TestInstr* i = TestInstr::New(&zone, 101);
    CHECK_EQ(0, static_cast<int>(i->OutputCount()));
    CHECK_EQ(0, static_cast<int>(i->InputCount()));
    CHECK_EQ(0, static_cast<int>(i->TempCount()));
  }

  int vreg = 15;
  InstructionOperand outputs[] = {
      UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg),
      UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg),
      UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg),
      UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg)};

  InstructionOperand inputs[] = {
      UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg),
      UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg),
      UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg),
      UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg)};

  InstructionOperand temps[] = {
      UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg),
      UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg),
      UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg),
      UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg)};

  for (size_t i = 0; i < arraysize(outputs); i++) {
    for (size_t j = 0; j < arraysize(inputs); j++) {
      for (size_t k = 0; k < arraysize(temps); k++) {
        TestInstr* m =
            TestInstr::New(&zone, 101, i, outputs, j, inputs, k, temps);
        CHECK(i == m->OutputCount());
        CHECK(j == m->InputCount());
        CHECK(k == m->TempCount());

        for (size_t z = 0; z < i; z++) {
          CHECK(outputs[z].Equals(*m->OutputAt(z)));
        }

        for (size_t z = 0; z < j; z++) {
          CHECK(inputs[z].Equals(*m->InputAt(z)));
        }

        for (size_t z = 0; z < k; z++) {
          CHECK(temps[z].Equals(*m->TempAt(z)));
        }
      }
    }
  }
}

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