// Copyright 2016 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/init/v8.h"

#include "src/interpreter/bytecode-label.h"
#include "src/interpreter/bytecode-register-optimizer.h"
#include "test/unittests/interpreter/bytecode-utils.h"
#include "test/unittests/test-utils.h"

namespace v8 {
namespace internal {
namespace interpreter {

class BytecodeRegisterOptimizerTest
    : public BytecodeRegisterOptimizer::BytecodeWriter,
      public TestWithIsolateAndZone {
 public:
  struct RegisterTransfer {
    Bytecode bytecode;
    Register input;
    Register output;
  };

  BytecodeRegisterOptimizerTest() = default;
  ~BytecodeRegisterOptimizerTest() override { delete register_allocator_; }

  void Initialize(int number_of_parameters, int number_of_locals) {
    register_allocator_ = new BytecodeRegisterAllocator(number_of_locals);
    register_optimizer_ = zone()->New<BytecodeRegisterOptimizer>(
        zone(), register_allocator_, number_of_locals, number_of_parameters,
        this);
  }

  void EmitLdar(Register input) override {
    output_.push_back({Bytecode::kLdar, input, Register()});
  }
  void EmitStar(Register output) override {
    output_.push_back({Bytecode::kStar, Register(), output});
  }
  void EmitMov(Register input, Register output) override {
    output_.push_back({Bytecode::kMov, input, output});
  }

  BytecodeRegisterAllocator* allocator() { return register_allocator_; }
  BytecodeRegisterOptimizer* optimizer() { return register_optimizer_; }

  Register NewTemporary() { return allocator()->NewRegister(); }

  void ReleaseTemporaries(Register reg) {
    allocator()->ReleaseRegisters(reg.index());
  }

  size_t write_count() const { return output_.size(); }
  const RegisterTransfer& last_written() const { return output_.back(); }
  const std::vector<RegisterTransfer>* output() { return &output_; }

 private:
  BytecodeRegisterAllocator* register_allocator_;
  BytecodeRegisterOptimizer* register_optimizer_;

  std::vector<RegisterTransfer> output_;
};

// Sanity tests.

TEST_F(BytecodeRegisterOptimizerTest, TemporaryMaterializedForFlush) {
  Initialize(1, 1);
  Register temp = NewTemporary();
  optimizer()->DoStar(temp);
  CHECK_EQ(write_count(), 0u);
  optimizer()->Flush();
  CHECK_EQ(write_count(), 1u);
  CHECK_EQ(output()->at(0).bytecode, Bytecode::kStar);
  CHECK_EQ(output()->at(0).output.index(), temp.index());
}

TEST_F(BytecodeRegisterOptimizerTest, TemporaryMaterializedForJump) {
  Initialize(1, 1);
  Register temp = NewTemporary();
  optimizer()->DoStar(temp);
  CHECK_EQ(write_count(), 0u);
  optimizer()
      ->PrepareForBytecode<Bytecode::kJump, ImplicitRegisterUse::kNone>();
  CHECK_EQ(write_count(), 1u);
  CHECK_EQ(output()->at(0).bytecode, Bytecode::kStar);
  CHECK_EQ(output()->at(0).output.index(), temp.index());
}

// Basic Register Optimizations

TEST_F(BytecodeRegisterOptimizerTest, TemporaryNotEmitted) {
  Initialize(3, 1);
  Register parameter = Register::FromParameterIndex(1);
  optimizer()->DoLdar(parameter);
  CHECK_EQ(write_count(), 0u);
  Register temp = NewTemporary();
  optimizer()->DoStar(temp);
  ReleaseTemporaries(temp);
  CHECK_EQ(write_count(), 0u);
  optimizer()
      ->PrepareForBytecode<Bytecode::kReturn,
                           ImplicitRegisterUse::kReadAccumulator>();
  CHECK_EQ(output()->at(0).bytecode, Bytecode::kLdar);
  CHECK_EQ(output()->at(0).input.index(), parameter.index());
}

TEST_F(BytecodeRegisterOptimizerTest, ReleasedRegisterUsed) {
  Initialize(3, 1);
  optimizer()
      ->PrepareForBytecode<Bytecode::kLdaSmi,
                           ImplicitRegisterUse::kWriteAccumulator>();
  Register temp0 = NewTemporary();
  Register temp1 = NewTemporary();
  optimizer()->DoStar(temp1);
  CHECK_EQ(write_count(), 0u);
  optimizer()
      ->PrepareForBytecode<Bytecode::kLdaSmi,
                           ImplicitRegisterUse::kWriteAccumulator>();
  CHECK_EQ(write_count(), 1u);
  CHECK_EQ(output()->at(0).bytecode, Bytecode::kStar);
  CHECK_EQ(output()->at(0).output.index(), temp1.index());
  optimizer()->DoMov(temp1, temp0);
  CHECK_EQ(write_count(), 1u);
  ReleaseTemporaries(temp1);
  CHECK_EQ(write_count(), 1u);
  optimizer()->DoLdar(temp0);
  CHECK_EQ(write_count(), 1u);
  optimizer()
      ->PrepareForBytecode<Bytecode::kReturn,
                           ImplicitRegisterUse::kReadAccumulator>();
  CHECK_EQ(write_count(), 2u);
  CHECK_EQ(output()->at(1).bytecode, Bytecode::kLdar);
  CHECK_EQ(output()->at(1).input.index(), temp1.index());
}

TEST_F(BytecodeRegisterOptimizerTest, ReleasedRegisterNotFlushed) {
  Initialize(3, 1);
  optimizer()
      ->PrepareForBytecode<Bytecode::kLdaSmi,
                           ImplicitRegisterUse::kWriteAccumulator>();
  Register temp0 = NewTemporary();
  Register temp1 = NewTemporary();
  optimizer()->DoStar(temp0);
  CHECK_EQ(write_count(), 0u);
  optimizer()->DoStar(temp1);
  CHECK_EQ(write_count(), 0u);
  ReleaseTemporaries(temp1);
  optimizer()->Flush();
  CHECK_EQ(write_count(), 1u);
  CHECK_EQ(output()->at(0).bytecode, Bytecode::kStar);
  CHECK_EQ(output()->at(0).output.index(), temp0.index());
}

TEST_F(BytecodeRegisterOptimizerTest, StoresToLocalsImmediate) {
  Initialize(3, 1);
  Register parameter = Register::FromParameterIndex(1);
  optimizer()->DoLdar(parameter);
  CHECK_EQ(write_count(), 0u);
  Register local = Register(0);
  optimizer()->DoStar(local);
  CHECK_EQ(write_count(), 1u);
  CHECK_EQ(output()->at(0).bytecode, Bytecode::kMov);
  CHECK_EQ(output()->at(0).input.index(), parameter.index());
  CHECK_EQ(output()->at(0).output.index(), local.index());

  optimizer()
      ->PrepareForBytecode<Bytecode::kReturn,
                           ImplicitRegisterUse::kReadAccumulator>();
  CHECK_EQ(write_count(), 2u);
  CHECK_EQ(output()->at(1).bytecode, Bytecode::kLdar);
  CHECK_EQ(output()->at(1).input.index(), local.index());
}

TEST_F(BytecodeRegisterOptimizerTest, SingleTemporaryNotMaterializedForInput) {
  Initialize(3, 1);
  Register parameter = Register::FromParameterIndex(1);
  Register temp0 = NewTemporary();
  Register temp1 = NewTemporary();
  optimizer()->DoMov(parameter, temp0);
  optimizer()->DoMov(parameter, temp1);
  CHECK_EQ(write_count(), 0u);

  Register reg = optimizer()->GetInputRegister(temp0);
  RegisterList reg_list = optimizer()->GetInputRegisterList(
      BytecodeUtils::NewRegisterList(temp0.index(), 1));
  CHECK_EQ(write_count(), 0u);
  CHECK_EQ(parameter.index(), reg.index());
  CHECK_EQ(parameter.index(), reg_list.first_register().index());
  CHECK_EQ(1, reg_list.register_count());
}

TEST_F(BytecodeRegisterOptimizerTest, RangeOfTemporariesMaterializedForInput) {
  Initialize(3, 1);
  Register parameter = Register::FromParameterIndex(1);
  Register temp0 = NewTemporary();
  Register temp1 = NewTemporary();
  optimizer()
      ->PrepareForBytecode<Bytecode::kLdaSmi,
                           ImplicitRegisterUse::kWriteAccumulator>();
  optimizer()->DoStar(temp0);
  optimizer()->DoMov(parameter, temp1);
  CHECK_EQ(write_count(), 0u);

  optimizer()
      ->PrepareForBytecode<Bytecode::kCallJSRuntime,
                           ImplicitRegisterUse::kWriteAccumulator>();
  RegisterList reg_list = optimizer()->GetInputRegisterList(
      BytecodeUtils::NewRegisterList(temp0.index(), 2));
  CHECK_EQ(temp0.index(), reg_list.first_register().index());
  CHECK_EQ(2, reg_list.register_count());
  CHECK_EQ(write_count(), 2u);
  CHECK_EQ(output()->at(0).bytecode, Bytecode::kStar);
  CHECK_EQ(output()->at(0).output.index(), temp0.index());
  CHECK_EQ(output()->at(1).bytecode, Bytecode::kMov);
  CHECK_EQ(output()->at(1).input.index(), parameter.index());
  CHECK_EQ(output()->at(1).output.index(), temp1.index());
}

}  // namespace interpreter
}  // namespace internal
}  // namespace v8
