//===-- SnippetGeneratorTest.cpp --------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "../Common/AssemblerUtils.h"
#include "Latency.h"
#include "LlvmState.h"
#include "MCInstrDescView.h"
#include "RegisterAliasing.h"
#include "Uops.h"
#include "X86InstrInfo.h"

#include <unordered_set>

namespace exegesis {

void InitializeX86ExegesisTarget();

namespace {

using testing::AnyOf;
using testing::ElementsAre;
using testing::HasSubstr;
using testing::Not;
using testing::SizeIs;
using testing::UnorderedElementsAre;

MATCHER(IsInvalid, "") { return !arg.isValid(); }
MATCHER(IsReg, "") { return arg.isReg(); }

class X86SnippetGeneratorTest : public ::testing::Test {
protected:
  X86SnippetGeneratorTest()
      : State("x86_64-unknown-linux", "haswell"),
        MCInstrInfo(State.getInstrInfo()), MCRegisterInfo(State.getRegInfo()) {}

  static void SetUpTestCase() {
    LLVMInitializeX86TargetInfo();
    LLVMInitializeX86TargetMC();
    LLVMInitializeX86Target();
    LLVMInitializeX86AsmPrinter();
    InitializeX86ExegesisTarget();
  }

  const LLVMState State;
  const llvm::MCInstrInfo &MCInstrInfo;
  const llvm::MCRegisterInfo &MCRegisterInfo;
};

template <typename BenchmarkRunner>
class SnippetGeneratorTest : public X86SnippetGeneratorTest {
protected:
  SnippetGeneratorTest() : Runner(State) {}

  CodeTemplate checkAndGetCodeTemplate(unsigned Opcode) {
    randomGenerator().seed(0); // Initialize seed.
    auto CodeTemplateOrError = Runner.generateCodeTemplate(Opcode);
    EXPECT_FALSE(CodeTemplateOrError.takeError()); // Valid configuration.
    return std::move(CodeTemplateOrError.get());
  }

  BenchmarkRunner Runner;
};

using LatencyBenchmarkRunnerTest = SnippetGeneratorTest<LatencyBenchmarkRunner>;

using UopsBenchmarkRunnerTest = SnippetGeneratorTest<UopsBenchmarkRunner>;

TEST_F(LatencyBenchmarkRunnerTest, ImplicitSelfDependency) {
  // ADC16i16 self alias because of implicit use and def.

  // explicit use 0       : imm
  // implicit def         : AX
  // implicit def         : EFLAGS
  // implicit use         : AX
  // implicit use         : EFLAGS
  const unsigned Opcode = llvm::X86::ADC16i16;
  EXPECT_THAT(MCInstrInfo.get(Opcode).getImplicitDefs()[0], llvm::X86::AX);
  EXPECT_THAT(MCInstrInfo.get(Opcode).getImplicitDefs()[1], llvm::X86::EFLAGS);
  EXPECT_THAT(MCInstrInfo.get(Opcode).getImplicitUses()[0], llvm::X86::AX);
  EXPECT_THAT(MCInstrInfo.get(Opcode).getImplicitUses()[1], llvm::X86::EFLAGS);
  const CodeTemplate CT = checkAndGetCodeTemplate(Opcode);
  EXPECT_THAT(CT.Info, HasSubstr("implicit"));
  ASSERT_THAT(CT.Instructions, SizeIs(1));
  const InstructionBuilder &IB = CT.Instructions[0];
  EXPECT_THAT(IB.getOpcode(), Opcode);
  ASSERT_THAT(IB.VariableValues, SizeIs(1)); // Imm.
  EXPECT_THAT(IB.VariableValues[0], IsInvalid()) << "Immediate is not set";
}

TEST_F(LatencyBenchmarkRunnerTest, ExplicitSelfDependency) {
  // ADD16ri self alias because Op0 and Op1 are tied together.

  // explicit def 0       : reg RegClass=GR16
  // explicit use 1       : reg RegClass=GR16 | TIED_TO:0
  // explicit use 2       : imm
  // implicit def         : EFLAGS
  const unsigned Opcode = llvm::X86::ADD16ri;
  EXPECT_THAT(MCInstrInfo.get(Opcode).getImplicitDefs()[0], llvm::X86::EFLAGS);
  const CodeTemplate CT = checkAndGetCodeTemplate(Opcode);
  EXPECT_THAT(CT.Info, HasSubstr("explicit"));
  ASSERT_THAT(CT.Instructions, SizeIs(1));
  const InstructionBuilder &IB = CT.Instructions[0];
  EXPECT_THAT(IB.getOpcode(), Opcode);
  ASSERT_THAT(IB.VariableValues, SizeIs(2));
  EXPECT_THAT(IB.VariableValues[0], IsReg()) << "Operand 0 and 1";
  EXPECT_THAT(IB.VariableValues[1], IsInvalid()) << "Operand 2 is not set";
}

TEST_F(LatencyBenchmarkRunnerTest, DependencyThroughOtherOpcode) {
  // CMP64rr
  // explicit use 0       : reg RegClass=GR64
  // explicit use 1       : reg RegClass=GR64
  // implicit def         : EFLAGS

  const unsigned Opcode = llvm::X86::CMP64rr;
  const CodeTemplate CT = checkAndGetCodeTemplate(Opcode);
  EXPECT_THAT(CT.Info, HasSubstr("cycle through"));
  ASSERT_THAT(CT.Instructions, SizeIs(2));
  const InstructionBuilder &IB = CT.Instructions[0];
  EXPECT_THAT(IB.getOpcode(), Opcode);
  ASSERT_THAT(IB.VariableValues, SizeIs(2));
  EXPECT_THAT(IB.VariableValues, AnyOf(ElementsAre(IsReg(), IsInvalid()),
                                       ElementsAre(IsInvalid(), IsReg())));
  EXPECT_THAT(CT.Instructions[1].getOpcode(), Not(Opcode));
  // TODO: check that the two instructions alias each other.
}

TEST_F(LatencyBenchmarkRunnerTest, LAHF) {
  const unsigned Opcode = llvm::X86::LAHF;
  const CodeTemplate CT = checkAndGetCodeTemplate(Opcode);
  EXPECT_THAT(CT.Info, HasSubstr("cycle through"));
  ASSERT_THAT(CT.Instructions, SizeIs(2));
  const InstructionBuilder &IB = CT.Instructions[0];
  EXPECT_THAT(IB.getOpcode(), Opcode);
  ASSERT_THAT(IB.VariableValues, SizeIs(0));
}

TEST_F(UopsBenchmarkRunnerTest, ParallelInstruction) {
  // BNDCL32rr is parallel no matter what.

  // explicit use 0       : reg RegClass=BNDR
  // explicit use 1       : reg RegClass=GR32

  const unsigned Opcode = llvm::X86::BNDCL32rr;
  const CodeTemplate CT = checkAndGetCodeTemplate(Opcode);
  EXPECT_THAT(CT.Info, HasSubstr("parallel"));
  ASSERT_THAT(CT.Instructions, SizeIs(1));
  const InstructionBuilder &IB = CT.Instructions[0];
  EXPECT_THAT(IB.getOpcode(), Opcode);
  ASSERT_THAT(IB.VariableValues, SizeIs(2));
  EXPECT_THAT(IB.VariableValues[0], IsInvalid());
  EXPECT_THAT(IB.VariableValues[1], IsInvalid());
}

TEST_F(UopsBenchmarkRunnerTest, SerialInstruction) {
  // CDQ is serial no matter what.

  // implicit def         : EAX
  // implicit def         : EDX
  // implicit use         : EAX
  const unsigned Opcode = llvm::X86::CDQ;
  const CodeTemplate CT = checkAndGetCodeTemplate(Opcode);
  EXPECT_THAT(CT.Info, HasSubstr("serial"));
  ASSERT_THAT(CT.Instructions, SizeIs(1));
  const InstructionBuilder &IB = CT.Instructions[0];
  EXPECT_THAT(IB.getOpcode(), Opcode);
  ASSERT_THAT(IB.VariableValues, SizeIs(0));
}

TEST_F(UopsBenchmarkRunnerTest, StaticRenaming) {
  // CMOVA32rr has tied variables, we enumarate the possible values to execute
  // as many in parallel as possible.

  // explicit def 0       : reg RegClass=GR32
  // explicit use 1       : reg RegClass=GR32 | TIED_TO:0
  // explicit use 2       : reg RegClass=GR32
  // implicit use         : EFLAGS
  const unsigned Opcode = llvm::X86::CMOVA32rr;
  const CodeTemplate CT = checkAndGetCodeTemplate(Opcode);
  EXPECT_THAT(CT.Info, HasSubstr("static renaming"));
  constexpr const unsigned kInstructionCount = 15;
  ASSERT_THAT(CT.Instructions, SizeIs(kInstructionCount));
  std::unordered_set<unsigned> AllDefRegisters;
  for (const auto &IB : CT.Instructions) {
    ASSERT_THAT(IB.VariableValues, SizeIs(2));
    AllDefRegisters.insert(IB.VariableValues[0].getReg());
  }
  EXPECT_THAT(AllDefRegisters, SizeIs(kInstructionCount))
      << "Each instruction writes to a different register";
}

TEST_F(UopsBenchmarkRunnerTest, NoTiedVariables) {
  // CMOV_GR32 has no tied variables, we make sure def and use are different
  // from each other.

  // explicit def 0       : reg RegClass=GR32
  // explicit use 1       : reg RegClass=GR32
  // explicit use 2       : reg RegClass=GR32
  // explicit use 3       : imm
  // implicit use         : EFLAGS
  const unsigned Opcode = llvm::X86::CMOV_GR32;
  const CodeTemplate CT = checkAndGetCodeTemplate(Opcode);
  EXPECT_THAT(CT.Info, HasSubstr("no tied variables"));
  ASSERT_THAT(CT.Instructions, SizeIs(1));
  const InstructionBuilder &IB = CT.Instructions[0];
  EXPECT_THAT(IB.getOpcode(), Opcode);
  ASSERT_THAT(IB.VariableValues, SizeIs(4));
  EXPECT_THAT(IB.VariableValues[0].getReg(), Not(IB.VariableValues[1].getReg()))
      << "Def is different from first Use";
  EXPECT_THAT(IB.VariableValues[0].getReg(), Not(IB.VariableValues[2].getReg()))
      << "Def is different from second Use";
  EXPECT_THAT(IB.VariableValues[3], IsInvalid());
}

TEST_F(UopsBenchmarkRunnerTest, MemoryUse) {
  // Mov32rm reads from memory.
  const unsigned Opcode = llvm::X86::MOV32rm;
  const CodeTemplate CT = checkAndGetCodeTemplate(Opcode);
  EXPECT_THAT(CT.Info, HasSubstr("no tied variables"));
  ASSERT_THAT(CT.Instructions,
              SizeIs(UopsBenchmarkRunner::kMinNumDifferentAddresses));
  const InstructionBuilder &IB = CT.Instructions[0];
  EXPECT_THAT(IB.getOpcode(), Opcode);
  ASSERT_THAT(IB.VariableValues, SizeIs(6));
  EXPECT_EQ(IB.VariableValues[2].getImm(), 1);
  EXPECT_EQ(IB.VariableValues[3].getReg(), 0u);
  EXPECT_EQ(IB.VariableValues[4].getImm(), 0);
  EXPECT_EQ(IB.VariableValues[5].getReg(), 0u);
}

TEST_F(UopsBenchmarkRunnerTest, MemoryUse_Movsb) {
  // MOVSB writes to scratch memory register.
  const unsigned Opcode = llvm::X86::MOVSB;
  auto Error = Runner.generateCodeTemplate(Opcode).takeError();
  EXPECT_TRUE((bool)Error);
  llvm::consumeError(std::move(Error));
}

class FakeBenchmarkRunner : public BenchmarkRunner {
public:
  FakeBenchmarkRunner(const LLVMState &State)
      : BenchmarkRunner(State, InstructionBenchmark::Unknown) {}

  Instruction createInstruction(unsigned Opcode) {
    return Instruction(State.getInstrInfo().get(Opcode), RATC);
  }

private:
  llvm::Expected<CodeTemplate>
  generateCodeTemplate(unsigned Opcode) const override {
    return llvm::make_error<llvm::StringError>("not implemented",
                                               llvm::inconvertibleErrorCode());
  }

  std::vector<BenchmarkMeasure>
  runMeasurements(const ExecutableFunction &EF, ScratchSpace &Scratch,
                  const unsigned NumRepetitions) const override {
    return {};
  }
};

using FakeSnippetGeneratorTest = SnippetGeneratorTest<FakeBenchmarkRunner>;

TEST_F(FakeSnippetGeneratorTest, ComputeRegsToDefAdd16ri) {
  // ADD16ri:
  // explicit def 0       : reg RegClass=GR16
  // explicit use 1       : reg RegClass=GR16 | TIED_TO:0
  // explicit use 2       : imm
  // implicit def         : EFLAGS
  InstructionBuilder IB(Runner.createInstruction(llvm::X86::ADD16ri));
  IB.getValueFor(IB.Instr.Variables[0]) =
      llvm::MCOperand::createReg(llvm::X86::AX);
  std::vector<InstructionBuilder> Snippet;
  Snippet.push_back(std::move(IB));
  const auto RegsToDef = Runner.computeRegsToDef(Snippet);
  EXPECT_THAT(RegsToDef, UnorderedElementsAre(llvm::X86::AX));
}

TEST_F(FakeSnippetGeneratorTest, ComputeRegsToDefAdd64rr) {
  // ADD64rr:
  //  mov64ri rax, 42
  //  add64rr rax, rax, rbx
  // -> only rbx needs defining.
  std::vector<InstructionBuilder> Snippet;
  {
    InstructionBuilder Mov(Runner.createInstruction(llvm::X86::MOV64ri));
    Mov.getValueFor(Mov.Instr.Variables[0]) =
        llvm::MCOperand::createReg(llvm::X86::RAX);
    Mov.getValueFor(Mov.Instr.Variables[1]) = llvm::MCOperand::createImm(42);
    Snippet.push_back(std::move(Mov));
  }
  {
    InstructionBuilder Add(Runner.createInstruction(llvm::X86::ADD64rr));
    Add.getValueFor(Add.Instr.Variables[0]) =
        llvm::MCOperand::createReg(llvm::X86::RAX);
    Add.getValueFor(Add.Instr.Variables[1]) =
        llvm::MCOperand::createReg(llvm::X86::RBX);
    Snippet.push_back(std::move(Add));
  }

  const auto RegsToDef = Runner.computeRegsToDef(Snippet);
  EXPECT_THAT(RegsToDef, UnorderedElementsAre(llvm::X86::RBX));
}

} // namespace
} // namespace exegesis
