Home | History | Annotate | Download | only in X86
      1 //===-- SnippetGeneratorTest.cpp --------------------------------*- C++ -*-===//
      2 //
      3 //                     The LLVM Compiler Infrastructure
      4 //
      5 // This file is distributed under the University of Illinois Open Source
      6 // License. See LICENSE.TXT for details.
      7 //
      8 //===----------------------------------------------------------------------===//
      9 
     10 #include "../Common/AssemblerUtils.h"
     11 #include "Latency.h"
     12 #include "LlvmState.h"
     13 #include "MCInstrDescView.h"
     14 #include "RegisterAliasing.h"
     15 #include "Uops.h"
     16 #include "X86InstrInfo.h"
     17 
     18 #include <unordered_set>
     19 
     20 namespace exegesis {
     21 namespace {
     22 
     23 using testing::AnyOf;
     24 using testing::ElementsAre;
     25 using testing::HasSubstr;
     26 using testing::Not;
     27 using testing::SizeIs;
     28 using testing::UnorderedElementsAre;
     29 
     30 MATCHER(IsInvalid, "") { return !arg.isValid(); }
     31 MATCHER(IsReg, "") { return arg.isReg(); }
     32 
     33 class X86SnippetGeneratorTest : public ::testing::Test {
     34 protected:
     35   X86SnippetGeneratorTest()
     36       : State("x86_64-unknown-linux", "haswell"),
     37         MCInstrInfo(State.getInstrInfo()), MCRegisterInfo(State.getRegInfo()) {}
     38 
     39   static void SetUpTestCase() {
     40     LLVMInitializeX86TargetInfo();
     41     LLVMInitializeX86TargetMC();
     42     LLVMInitializeX86Target();
     43     LLVMInitializeX86AsmPrinter();
     44   }
     45 
     46   const LLVMState State;
     47   const llvm::MCInstrInfo &MCInstrInfo;
     48   const llvm::MCRegisterInfo &MCRegisterInfo;
     49 };
     50 
     51 template <typename BenchmarkRunner>
     52 class SnippetGeneratorTest : public X86SnippetGeneratorTest {
     53 protected:
     54   SnippetGeneratorTest() : Runner(State) {}
     55 
     56   SnippetPrototype checkAndGetConfigurations(unsigned Opcode) {
     57     randomGenerator().seed(0); // Initialize seed.
     58     auto ProtoOrError = Runner.generatePrototype(Opcode);
     59     EXPECT_FALSE(ProtoOrError.takeError()); // Valid configuration.
     60     return std::move(ProtoOrError.get());
     61   }
     62 
     63   BenchmarkRunner Runner;
     64 };
     65 
     66 using LatencySnippetGeneratorTest =
     67     SnippetGeneratorTest<LatencyBenchmarkRunner>;
     68 
     69 using UopsSnippetGeneratorTest = SnippetGeneratorTest<UopsBenchmarkRunner>;
     70 
     71 TEST_F(LatencySnippetGeneratorTest, ImplicitSelfDependency) {
     72   // ADC16i16 self alias because of implicit use and def.
     73 
     74   // explicit use 0       : imm
     75   // implicit def         : AX
     76   // implicit def         : EFLAGS
     77   // implicit use         : AX
     78   // implicit use         : EFLAGS
     79   const unsigned Opcode = llvm::X86::ADC16i16;
     80   EXPECT_THAT(MCInstrInfo.get(Opcode).getImplicitDefs()[0], llvm::X86::AX);
     81   EXPECT_THAT(MCInstrInfo.get(Opcode).getImplicitDefs()[1], llvm::X86::EFLAGS);
     82   EXPECT_THAT(MCInstrInfo.get(Opcode).getImplicitUses()[0], llvm::X86::AX);
     83   EXPECT_THAT(MCInstrInfo.get(Opcode).getImplicitUses()[1], llvm::X86::EFLAGS);
     84   const SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
     85   EXPECT_THAT(Proto.Explanation, HasSubstr("implicit"));
     86   ASSERT_THAT(Proto.Snippet, SizeIs(1));
     87   const InstructionInstance &II = Proto.Snippet[0];
     88   EXPECT_THAT(II.getOpcode(), Opcode);
     89   ASSERT_THAT(II.VariableValues, SizeIs(1)); // Imm.
     90   EXPECT_THAT(II.VariableValues[0], IsInvalid()) << "Immediate is not set";
     91 }
     92 
     93 TEST_F(LatencySnippetGeneratorTest, ExplicitSelfDependency) {
     94   // ADD16ri self alias because Op0 and Op1 are tied together.
     95 
     96   // explicit def 0       : reg RegClass=GR16
     97   // explicit use 1       : reg RegClass=GR16 | TIED_TO:0
     98   // explicit use 2       : imm
     99   // implicit def         : EFLAGS
    100   const unsigned Opcode = llvm::X86::ADD16ri;
    101   EXPECT_THAT(MCInstrInfo.get(Opcode).getImplicitDefs()[0], llvm::X86::EFLAGS);
    102   const SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
    103   EXPECT_THAT(Proto.Explanation, HasSubstr("explicit"));
    104   ASSERT_THAT(Proto.Snippet, SizeIs(1));
    105   const InstructionInstance &II = Proto.Snippet[0];
    106   EXPECT_THAT(II.getOpcode(), Opcode);
    107   ASSERT_THAT(II.VariableValues, SizeIs(2));
    108   EXPECT_THAT(II.VariableValues[0], IsReg()) << "Operand 0 and 1";
    109   EXPECT_THAT(II.VariableValues[1], IsInvalid()) << "Operand 2 is not set";
    110 }
    111 
    112 TEST_F(LatencySnippetGeneratorTest, DependencyThroughOtherOpcode) {
    113   // CMP64rr
    114   // explicit use 0       : reg RegClass=GR64
    115   // explicit use 1       : reg RegClass=GR64
    116   // implicit def         : EFLAGS
    117 
    118   const unsigned Opcode = llvm::X86::CMP64rr;
    119   const SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
    120   EXPECT_THAT(Proto.Explanation, HasSubstr("cycle through"));
    121   ASSERT_THAT(Proto.Snippet, SizeIs(2));
    122   const InstructionInstance &II = Proto.Snippet[0];
    123   EXPECT_THAT(II.getOpcode(), Opcode);
    124   ASSERT_THAT(II.VariableValues, SizeIs(2));
    125   EXPECT_THAT(II.VariableValues, AnyOf(ElementsAre(IsReg(), IsInvalid()),
    126                                        ElementsAre(IsInvalid(), IsReg())));
    127   EXPECT_THAT(Proto.Snippet[1].getOpcode(), Not(Opcode));
    128   // TODO: check that the two instructions alias each other.
    129 }
    130 
    131 TEST_F(LatencySnippetGeneratorTest, LAHF) {
    132   const unsigned Opcode = llvm::X86::LAHF;
    133   const SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
    134   EXPECT_THAT(Proto.Explanation, HasSubstr("cycle through"));
    135   ASSERT_THAT(Proto.Snippet, SizeIs(2));
    136   const InstructionInstance &II = Proto.Snippet[0];
    137   EXPECT_THAT(II.getOpcode(), Opcode);
    138   ASSERT_THAT(II.VariableValues, SizeIs(0));
    139 }
    140 
    141 TEST_F(UopsSnippetGeneratorTest, ParallelInstruction) {
    142   // BNDCL32rr is parallel no matter what.
    143 
    144   // explicit use 0       : reg RegClass=BNDR
    145   // explicit use 1       : reg RegClass=GR32
    146 
    147   const unsigned Opcode = llvm::X86::BNDCL32rr;
    148   const SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
    149   EXPECT_THAT(Proto.Explanation, HasSubstr("parallel"));
    150   ASSERT_THAT(Proto.Snippet, SizeIs(1));
    151   const InstructionInstance &II = Proto.Snippet[0];
    152   EXPECT_THAT(II.getOpcode(), Opcode);
    153   ASSERT_THAT(II.VariableValues, SizeIs(2));
    154   EXPECT_THAT(II.VariableValues[0], IsInvalid());
    155   EXPECT_THAT(II.VariableValues[1], IsInvalid());
    156 }
    157 
    158 TEST_F(UopsSnippetGeneratorTest, SerialInstruction) {
    159   // CDQ is serial no matter what.
    160 
    161   // implicit def         : EAX
    162   // implicit def         : EDX
    163   // implicit use         : EAX
    164   const unsigned Opcode = llvm::X86::CDQ;
    165   const SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
    166   EXPECT_THAT(Proto.Explanation, HasSubstr("serial"));
    167   ASSERT_THAT(Proto.Snippet, SizeIs(1));
    168   const InstructionInstance &II = Proto.Snippet[0];
    169   EXPECT_THAT(II.getOpcode(), Opcode);
    170   ASSERT_THAT(II.VariableValues, SizeIs(0));
    171 }
    172 
    173 TEST_F(UopsSnippetGeneratorTest, StaticRenaming) {
    174   // CMOVA32rr has tied variables, we enumarate the possible values to execute
    175   // as many in parallel as possible.
    176 
    177   // explicit def 0       : reg RegClass=GR32
    178   // explicit use 1       : reg RegClass=GR32 | TIED_TO:0
    179   // explicit use 2       : reg RegClass=GR32
    180   // implicit use         : EFLAGS
    181   const unsigned Opcode = llvm::X86::CMOVA32rr;
    182   const SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
    183   EXPECT_THAT(Proto.Explanation, HasSubstr("static renaming"));
    184   constexpr const unsigned kInstructionCount = 15;
    185   ASSERT_THAT(Proto.Snippet, SizeIs(kInstructionCount));
    186   std::unordered_set<unsigned> AllDefRegisters;
    187   for (const auto &II : Proto.Snippet) {
    188     ASSERT_THAT(II.VariableValues, SizeIs(2));
    189     AllDefRegisters.insert(II.VariableValues[0].getReg());
    190   }
    191   EXPECT_THAT(AllDefRegisters, SizeIs(kInstructionCount))
    192       << "Each instruction writes to a different register";
    193 }
    194 
    195 TEST_F(UopsSnippetGeneratorTest, NoTiedVariables) {
    196   // CMOV_GR32 has no tied variables, we make sure def and use are different
    197   // from each other.
    198 
    199   // explicit def 0       : reg RegClass=GR32
    200   // explicit use 1       : reg RegClass=GR32
    201   // explicit use 2       : reg RegClass=GR32
    202   // explicit use 3       : imm
    203   // implicit use         : EFLAGS
    204   const unsigned Opcode = llvm::X86::CMOV_GR32;
    205   const SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
    206   EXPECT_THAT(Proto.Explanation, HasSubstr("no tied variables"));
    207   ASSERT_THAT(Proto.Snippet, SizeIs(1));
    208   const InstructionInstance &II = Proto.Snippet[0];
    209   EXPECT_THAT(II.getOpcode(), Opcode);
    210   ASSERT_THAT(II.VariableValues, SizeIs(4));
    211   EXPECT_THAT(II.VariableValues[0].getReg(), Not(II.VariableValues[1].getReg()))
    212       << "Def is different from first Use";
    213   EXPECT_THAT(II.VariableValues[0].getReg(), Not(II.VariableValues[2].getReg()))
    214       << "Def is different from second Use";
    215   EXPECT_THAT(II.VariableValues[3], IsInvalid());
    216 }
    217 
    218 class FakeBenchmarkRunner : public BenchmarkRunner {
    219 public:
    220   FakeBenchmarkRunner(const LLVMState &State)
    221       : BenchmarkRunner(State, InstructionBenchmark::Unknown) {}
    222 
    223   Instruction createInstruction(unsigned Opcode) {
    224     return Instruction(State.getInstrInfo().get(Opcode), RATC);
    225   }
    226 
    227 private:
    228   llvm::Expected<SnippetPrototype>
    229   generatePrototype(unsigned Opcode) const override {
    230     return llvm::make_error<llvm::StringError>("not implemented",
    231                                                llvm::inconvertibleErrorCode());
    232   }
    233 
    234   std::vector<BenchmarkMeasure>
    235   runMeasurements(const ExecutableFunction &EF,
    236                   const unsigned NumRepetitions) const override {
    237     return {};
    238   }
    239 };
    240 
    241 using FakeSnippetGeneratorTest = SnippetGeneratorTest<FakeBenchmarkRunner>;
    242 
    243 TEST_F(FakeSnippetGeneratorTest, ComputeRegsToDefAdd16ri) {
    244   // ADD16ri:
    245   // explicit def 0       : reg RegClass=GR16
    246   // explicit use 1       : reg RegClass=GR16 | TIED_TO:0
    247   // explicit use 2       : imm
    248   // implicit def         : EFLAGS
    249   InstructionInstance II(Runner.createInstruction(llvm::X86::ADD16ri));
    250   II.getValueFor(II.Instr.Variables[0]) =
    251       llvm::MCOperand::createReg(llvm::X86::AX);
    252   std::vector<InstructionInstance> Snippet;
    253   Snippet.push_back(std::move(II));
    254   const auto RegsToDef = Runner.computeRegsToDef(Snippet);
    255   EXPECT_THAT(RegsToDef, UnorderedElementsAre(llvm::X86::AX));
    256 }
    257 
    258 TEST_F(FakeSnippetGeneratorTest, ComputeRegsToDefAdd64rr) {
    259   // ADD64rr:
    260   //  mov64ri rax, 42
    261   //  add64rr rax, rax, rbx
    262   // -> only rbx needs defining.
    263   std::vector<InstructionInstance> Snippet;
    264   {
    265     InstructionInstance Mov(Runner.createInstruction(llvm::X86::MOV64ri));
    266     Mov.getValueFor(Mov.Instr.Variables[0]) =
    267         llvm::MCOperand::createReg(llvm::X86::RAX);
    268     Mov.getValueFor(Mov.Instr.Variables[1]) = llvm::MCOperand::createImm(42);
    269     Snippet.push_back(std::move(Mov));
    270   }
    271   {
    272     InstructionInstance Add(Runner.createInstruction(llvm::X86::ADD64rr));
    273     Add.getValueFor(Add.Instr.Variables[0]) =
    274         llvm::MCOperand::createReg(llvm::X86::RAX);
    275     Add.getValueFor(Add.Instr.Variables[1]) =
    276         llvm::MCOperand::createReg(llvm::X86::RBX);
    277     Snippet.push_back(std::move(Add));
    278   }
    279 
    280   const auto RegsToDef = Runner.computeRegsToDef(Snippet);
    281   EXPECT_THAT(RegsToDef, UnorderedElementsAre(llvm::X86::RBX));
    282 }
    283 
    284 } // namespace
    285 } // namespace exegesis
    286