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