summaryrefslogtreecommitdiff
path: root/unittests/tools/llvm-exegesis/X86/SnippetGeneratorTest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'unittests/tools/llvm-exegesis/X86/SnippetGeneratorTest.cpp')
-rw-r--r--unittests/tools/llvm-exegesis/X86/SnippetGeneratorTest.cpp285
1 files changed, 285 insertions, 0 deletions
diff --git a/unittests/tools/llvm-exegesis/X86/SnippetGeneratorTest.cpp b/unittests/tools/llvm-exegesis/X86/SnippetGeneratorTest.cpp
new file mode 100644
index 000000000000..edacd7fe6291
--- /dev/null
+++ b/unittests/tools/llvm-exegesis/X86/SnippetGeneratorTest.cpp
@@ -0,0 +1,285 @@
+//===-- 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 {
+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();
+ }
+
+ const LLVMState State;
+ const llvm::MCInstrInfo &MCInstrInfo;
+ const llvm::MCRegisterInfo &MCRegisterInfo;
+};
+
+template <typename BenchmarkRunner>
+class SnippetGeneratorTest : public X86SnippetGeneratorTest {
+protected:
+ SnippetGeneratorTest() : Runner(State) {}
+
+ SnippetPrototype checkAndGetConfigurations(unsigned Opcode) {
+ randomGenerator().seed(0); // Initialize seed.
+ auto ProtoOrError = Runner.generatePrototype(Opcode);
+ EXPECT_FALSE(ProtoOrError.takeError()); // Valid configuration.
+ return std::move(ProtoOrError.get());
+ }
+
+ BenchmarkRunner Runner;
+};
+
+using LatencySnippetGeneratorTest =
+ SnippetGeneratorTest<LatencyBenchmarkRunner>;
+
+using UopsSnippetGeneratorTest = SnippetGeneratorTest<UopsBenchmarkRunner>;
+
+TEST_F(LatencySnippetGeneratorTest, 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 SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
+ EXPECT_THAT(Proto.Explanation, HasSubstr("implicit"));
+ ASSERT_THAT(Proto.Snippet, SizeIs(1));
+ const InstructionInstance &II = Proto.Snippet[0];
+ EXPECT_THAT(II.getOpcode(), Opcode);
+ ASSERT_THAT(II.VariableValues, SizeIs(1)); // Imm.
+ EXPECT_THAT(II.VariableValues[0], IsInvalid()) << "Immediate is not set";
+}
+
+TEST_F(LatencySnippetGeneratorTest, 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 SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
+ EXPECT_THAT(Proto.Explanation, HasSubstr("explicit"));
+ ASSERT_THAT(Proto.Snippet, SizeIs(1));
+ const InstructionInstance &II = Proto.Snippet[0];
+ EXPECT_THAT(II.getOpcode(), Opcode);
+ ASSERT_THAT(II.VariableValues, SizeIs(2));
+ EXPECT_THAT(II.VariableValues[0], IsReg()) << "Operand 0 and 1";
+ EXPECT_THAT(II.VariableValues[1], IsInvalid()) << "Operand 2 is not set";
+}
+
+TEST_F(LatencySnippetGeneratorTest, DependencyThroughOtherOpcode) {
+ // CMP64rr
+ // explicit use 0 : reg RegClass=GR64
+ // explicit use 1 : reg RegClass=GR64
+ // implicit def : EFLAGS
+
+ const unsigned Opcode = llvm::X86::CMP64rr;
+ const SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
+ EXPECT_THAT(Proto.Explanation, HasSubstr("cycle through"));
+ ASSERT_THAT(Proto.Snippet, SizeIs(2));
+ const InstructionInstance &II = Proto.Snippet[0];
+ EXPECT_THAT(II.getOpcode(), Opcode);
+ ASSERT_THAT(II.VariableValues, SizeIs(2));
+ EXPECT_THAT(II.VariableValues, AnyOf(ElementsAre(IsReg(), IsInvalid()),
+ ElementsAre(IsInvalid(), IsReg())));
+ EXPECT_THAT(Proto.Snippet[1].getOpcode(), Not(Opcode));
+ // TODO: check that the two instructions alias each other.
+}
+
+TEST_F(LatencySnippetGeneratorTest, LAHF) {
+ const unsigned Opcode = llvm::X86::LAHF;
+ const SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
+ EXPECT_THAT(Proto.Explanation, HasSubstr("cycle through"));
+ ASSERT_THAT(Proto.Snippet, SizeIs(2));
+ const InstructionInstance &II = Proto.Snippet[0];
+ EXPECT_THAT(II.getOpcode(), Opcode);
+ ASSERT_THAT(II.VariableValues, SizeIs(0));
+}
+
+TEST_F(UopsSnippetGeneratorTest, 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 SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
+ EXPECT_THAT(Proto.Explanation, HasSubstr("parallel"));
+ ASSERT_THAT(Proto.Snippet, SizeIs(1));
+ const InstructionInstance &II = Proto.Snippet[0];
+ EXPECT_THAT(II.getOpcode(), Opcode);
+ ASSERT_THAT(II.VariableValues, SizeIs(2));
+ EXPECT_THAT(II.VariableValues[0], IsInvalid());
+ EXPECT_THAT(II.VariableValues[1], IsInvalid());
+}
+
+TEST_F(UopsSnippetGeneratorTest, SerialInstruction) {
+ // CDQ is serial no matter what.
+
+ // implicit def : EAX
+ // implicit def : EDX
+ // implicit use : EAX
+ const unsigned Opcode = llvm::X86::CDQ;
+ const SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
+ EXPECT_THAT(Proto.Explanation, HasSubstr("serial"));
+ ASSERT_THAT(Proto.Snippet, SizeIs(1));
+ const InstructionInstance &II = Proto.Snippet[0];
+ EXPECT_THAT(II.getOpcode(), Opcode);
+ ASSERT_THAT(II.VariableValues, SizeIs(0));
+}
+
+TEST_F(UopsSnippetGeneratorTest, 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 SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
+ EXPECT_THAT(Proto.Explanation, HasSubstr("static renaming"));
+ constexpr const unsigned kInstructionCount = 15;
+ ASSERT_THAT(Proto.Snippet, SizeIs(kInstructionCount));
+ std::unordered_set<unsigned> AllDefRegisters;
+ for (const auto &II : Proto.Snippet) {
+ ASSERT_THAT(II.VariableValues, SizeIs(2));
+ AllDefRegisters.insert(II.VariableValues[0].getReg());
+ }
+ EXPECT_THAT(AllDefRegisters, SizeIs(kInstructionCount))
+ << "Each instruction writes to a different register";
+}
+
+TEST_F(UopsSnippetGeneratorTest, 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 SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
+ EXPECT_THAT(Proto.Explanation, HasSubstr("no tied variables"));
+ ASSERT_THAT(Proto.Snippet, SizeIs(1));
+ const InstructionInstance &II = Proto.Snippet[0];
+ EXPECT_THAT(II.getOpcode(), Opcode);
+ ASSERT_THAT(II.VariableValues, SizeIs(4));
+ EXPECT_THAT(II.VariableValues[0].getReg(), Not(II.VariableValues[1].getReg()))
+ << "Def is different from first Use";
+ EXPECT_THAT(II.VariableValues[0].getReg(), Not(II.VariableValues[2].getReg()))
+ << "Def is different from second Use";
+ EXPECT_THAT(II.VariableValues[3], IsInvalid());
+}
+
+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<SnippetPrototype>
+ generatePrototype(unsigned Opcode) const override {
+ return llvm::make_error<llvm::StringError>("not implemented",
+ llvm::inconvertibleErrorCode());
+ }
+
+ std::vector<BenchmarkMeasure>
+ runMeasurements(const ExecutableFunction &EF,
+ 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
+ InstructionInstance II(Runner.createInstruction(llvm::X86::ADD16ri));
+ II.getValueFor(II.Instr.Variables[0]) =
+ llvm::MCOperand::createReg(llvm::X86::AX);
+ std::vector<InstructionInstance> Snippet;
+ Snippet.push_back(std::move(II));
+ 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<InstructionInstance> Snippet;
+ {
+ InstructionInstance 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));
+ }
+ {
+ InstructionInstance 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