1 //===-- BenchmarkResult.cpp -------------------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "BenchmarkResult.h" 10 #include "BenchmarkRunner.h" 11 #include "llvm/ADT/STLExtras.h" 12 #include "llvm/ADT/ScopeExit.h" 13 #include "llvm/ADT/StringMap.h" 14 #include "llvm/ADT/StringRef.h" 15 #include "llvm/ADT/bit.h" 16 #include "llvm/ObjectYAML/YAML.h" 17 #include "llvm/Support/FileOutputBuffer.h" 18 #include "llvm/Support/FileSystem.h" 19 #include "llvm/Support/Format.h" 20 #include "llvm/Support/raw_ostream.h" 21 22 static constexpr const char kIntegerPrefix[] = "i_0x"; 23 static constexpr const char kDoublePrefix[] = "f_"; 24 static constexpr const char kInvalidOperand[] = "INVALID"; 25 static constexpr llvm::StringLiteral kNoRegister("%noreg"); 26 27 namespace llvm { 28 29 namespace { 30 31 // A mutable struct holding an LLVMState that can be passed through the 32 // serialization process to encode/decode registers and instructions. 33 struct YamlContext { 34 YamlContext(const exegesis::LLVMState &State) 35 : State(&State), ErrorStream(LastError), 36 OpcodeNameToOpcodeIdx( 37 generateOpcodeNameToOpcodeIdxMapping(State.getInstrInfo())), 38 RegNameToRegNo(generateRegNameToRegNoMapping(State.getRegInfo())) {} 39 40 static llvm::StringMap<unsigned> 41 generateOpcodeNameToOpcodeIdxMapping(const llvm::MCInstrInfo &InstrInfo) { 42 llvm::StringMap<unsigned> Map(InstrInfo.getNumOpcodes()); 43 for (unsigned I = 0, E = InstrInfo.getNumOpcodes(); I < E; ++I) 44 Map[InstrInfo.getName(I)] = I; 45 assert(Map.size() == InstrInfo.getNumOpcodes() && "Size prediction failed"); 46 return Map; 47 }; 48 49 llvm::StringMap<unsigned> 50 generateRegNameToRegNoMapping(const llvm::MCRegisterInfo &RegInfo) { 51 llvm::StringMap<unsigned> Map(RegInfo.getNumRegs()); 52 // Special-case RegNo 0, which would otherwise be spelled as ''. 53 Map[kNoRegister] = 0; 54 for (unsigned I = 1, E = RegInfo.getNumRegs(); I < E; ++I) 55 Map[RegInfo.getName(I)] = I; 56 assert(Map.size() == RegInfo.getNumRegs() && "Size prediction failed"); 57 return Map; 58 }; 59 60 void serializeMCInst(const llvm::MCInst &MCInst, llvm::raw_ostream &OS) { 61 OS << getInstrName(MCInst.getOpcode()); 62 for (const auto &Op : MCInst) { 63 OS << ' '; 64 serializeMCOperand(Op, OS); 65 } 66 } 67 68 void deserializeMCInst(llvm::StringRef String, llvm::MCInst &Value) { 69 llvm::SmallVector<llvm::StringRef, 16> Pieces; 70 String.split(Pieces, " ", /* MaxSplit */ -1, /* KeepEmpty */ false); 71 if (Pieces.empty()) { 72 ErrorStream << "Unknown Instruction: '" << String << "'\n"; 73 return; 74 } 75 bool ProcessOpcode = true; 76 for (llvm::StringRef Piece : Pieces) { 77 if (ProcessOpcode) 78 Value.setOpcode(getInstrOpcode(Piece)); 79 else 80 Value.addOperand(deserializeMCOperand(Piece)); 81 ProcessOpcode = false; 82 } 83 } 84 85 std::string &getLastError() { return ErrorStream.str(); } 86 87 llvm::raw_string_ostream &getErrorStream() { return ErrorStream; } 88 89 llvm::StringRef getRegName(unsigned RegNo) { 90 // Special case: RegNo 0 is NoRegister. We have to deal with it explicitly. 91 if (RegNo == 0) 92 return kNoRegister; 93 const llvm::StringRef RegName = State->getRegInfo().getName(RegNo); 94 if (RegName.empty()) 95 ErrorStream << "No register with enum value '" << RegNo << "'\n"; 96 return RegName; 97 } 98 99 llvm::Optional<unsigned> getRegNo(llvm::StringRef RegName) { 100 auto Iter = RegNameToRegNo.find(RegName); 101 if (Iter != RegNameToRegNo.end()) 102 return Iter->second; 103 ErrorStream << "No register with name '" << RegName << "'\n"; 104 return llvm::None; 105 } 106 107 private: 108 void serializeIntegerOperand(llvm::raw_ostream &OS, int64_t Value) { 109 OS << kIntegerPrefix; 110 OS.write_hex(llvm::bit_cast<uint64_t>(Value)); 111 } 112 113 bool tryDeserializeIntegerOperand(llvm::StringRef String, int64_t &Value) { 114 if (!String.consume_front(kIntegerPrefix)) 115 return false; 116 return !String.consumeInteger(16, Value); 117 } 118 119 void serializeFPOperand(llvm::raw_ostream &OS, double Value) { 120 OS << kDoublePrefix << llvm::format("%la", Value); 121 } 122 123 bool tryDeserializeFPOperand(llvm::StringRef String, double &Value) { 124 if (!String.consume_front(kDoublePrefix)) 125 return false; 126 char *EndPointer = nullptr; 127 Value = strtod(String.begin(), &EndPointer); 128 return EndPointer == String.end(); 129 } 130 131 void serializeMCOperand(const llvm::MCOperand &MCOperand, 132 llvm::raw_ostream &OS) { 133 if (MCOperand.isReg()) { 134 OS << getRegName(MCOperand.getReg()); 135 } else if (MCOperand.isImm()) { 136 serializeIntegerOperand(OS, MCOperand.getImm()); 137 } else if (MCOperand.isFPImm()) { 138 serializeFPOperand(OS, MCOperand.getFPImm()); 139 } else { 140 OS << kInvalidOperand; 141 } 142 } 143 144 llvm::MCOperand deserializeMCOperand(llvm::StringRef String) { 145 assert(!String.empty()); 146 int64_t IntValue = 0; 147 double DoubleValue = 0; 148 if (tryDeserializeIntegerOperand(String, IntValue)) 149 return llvm::MCOperand::createImm(IntValue); 150 if (tryDeserializeFPOperand(String, DoubleValue)) 151 return llvm::MCOperand::createFPImm(DoubleValue); 152 if (auto RegNo = getRegNo(String)) 153 return llvm::MCOperand::createReg(*RegNo); 154 if (String != kInvalidOperand) 155 ErrorStream << "Unknown Operand: '" << String << "'\n"; 156 return {}; 157 } 158 159 llvm::StringRef getInstrName(unsigned InstrNo) { 160 const llvm::StringRef InstrName = State->getInstrInfo().getName(InstrNo); 161 if (InstrName.empty()) 162 ErrorStream << "No opcode with enum value '" << InstrNo << "'\n"; 163 return InstrName; 164 } 165 166 unsigned getInstrOpcode(llvm::StringRef InstrName) { 167 auto Iter = OpcodeNameToOpcodeIdx.find(InstrName); 168 if (Iter != OpcodeNameToOpcodeIdx.end()) 169 return Iter->second; 170 ErrorStream << "No opcode with name '" << InstrName << "'\n"; 171 return 0; 172 } 173 174 const llvm::exegesis::LLVMState *State; 175 std::string LastError; 176 llvm::raw_string_ostream ErrorStream; 177 const llvm::StringMap<unsigned> OpcodeNameToOpcodeIdx; 178 const llvm::StringMap<unsigned> RegNameToRegNo; 179 }; 180 } // namespace 181 182 // Defining YAML traits for IO. 183 namespace yaml { 184 185 static YamlContext &getTypedContext(void *Ctx) { 186 return *reinterpret_cast<YamlContext *>(Ctx); 187 } 188 189 // std::vector<llvm::MCInst> will be rendered as a list. 190 template <> struct SequenceElementTraits<llvm::MCInst> { 191 static const bool flow = false; 192 }; 193 194 template <> struct ScalarTraits<llvm::MCInst> { 195 196 static void output(const llvm::MCInst &Value, void *Ctx, 197 llvm::raw_ostream &Out) { 198 getTypedContext(Ctx).serializeMCInst(Value, Out); 199 } 200 201 static StringRef input(StringRef Scalar, void *Ctx, llvm::MCInst &Value) { 202 YamlContext &Context = getTypedContext(Ctx); 203 Context.deserializeMCInst(Scalar, Value); 204 return Context.getLastError(); 205 } 206 207 // By default strings are quoted only when necessary. 208 // We force the use of single quotes for uniformity. 209 static QuotingType mustQuote(StringRef) { return QuotingType::Single; } 210 211 static const bool flow = true; 212 }; 213 214 // std::vector<exegesis::Measure> will be rendered as a list. 215 template <> struct SequenceElementTraits<exegesis::BenchmarkMeasure> { 216 static const bool flow = false; 217 }; 218 219 // exegesis::Measure is rendererd as a flow instead of a list. 220 // e.g. { "key": "the key", "value": 0123 } 221 template <> struct MappingTraits<exegesis::BenchmarkMeasure> { 222 static void mapping(IO &Io, exegesis::BenchmarkMeasure &Obj) { 223 Io.mapRequired("key", Obj.Key); 224 if (!Io.outputting()) { 225 // For backward compatibility, interpret debug_string as a key. 226 Io.mapOptional("debug_string", Obj.Key); 227 } 228 Io.mapRequired("value", Obj.PerInstructionValue); 229 Io.mapOptional("per_snippet_value", Obj.PerSnippetValue); 230 } 231 static const bool flow = true; 232 }; 233 234 template <> 235 struct ScalarEnumerationTraits<exegesis::InstructionBenchmark::ModeE> { 236 static void enumeration(IO &Io, 237 exegesis::InstructionBenchmark::ModeE &Value) { 238 Io.enumCase(Value, "", exegesis::InstructionBenchmark::Unknown); 239 Io.enumCase(Value, "latency", exegesis::InstructionBenchmark::Latency); 240 Io.enumCase(Value, "uops", exegesis::InstructionBenchmark::Uops); 241 Io.enumCase(Value, "inverse_throughput", 242 exegesis::InstructionBenchmark::InverseThroughput); 243 } 244 }; 245 246 // std::vector<exegesis::RegisterValue> will be rendered as a list. 247 template <> struct SequenceElementTraits<exegesis::RegisterValue> { 248 static const bool flow = false; 249 }; 250 251 template <> struct ScalarTraits<exegesis::RegisterValue> { 252 static constexpr const unsigned kRadix = 16; 253 static constexpr const bool kSigned = false; 254 255 static void output(const exegesis::RegisterValue &RV, void *Ctx, 256 llvm::raw_ostream &Out) { 257 YamlContext &Context = getTypedContext(Ctx); 258 Out << Context.getRegName(RV.Register) << "=0x" 259 << RV.Value.toString(kRadix, kSigned); 260 } 261 262 static StringRef input(StringRef String, void *Ctx, 263 exegesis::RegisterValue &RV) { 264 llvm::SmallVector<llvm::StringRef, 2> Pieces; 265 String.split(Pieces, "=0x", /* MaxSplit */ -1, 266 /* KeepEmpty */ false); 267 YamlContext &Context = getTypedContext(Ctx); 268 llvm::Optional<unsigned> RegNo; 269 if (Pieces.size() == 2 && (RegNo = Context.getRegNo(Pieces[0]))) { 270 RV.Register = *RegNo; 271 const unsigned BitsNeeded = llvm::APInt::getBitsNeeded(Pieces[1], kRadix); 272 RV.Value = llvm::APInt(BitsNeeded, Pieces[1], kRadix); 273 } else { 274 Context.getErrorStream() 275 << "Unknown initial register value: '" << String << "'"; 276 } 277 return Context.getLastError(); 278 } 279 280 static QuotingType mustQuote(StringRef) { return QuotingType::Single; } 281 282 static const bool flow = true; 283 }; 284 285 template <> 286 struct MappingContextTraits<exegesis::InstructionBenchmarkKey, YamlContext> { 287 static void mapping(IO &Io, exegesis::InstructionBenchmarkKey &Obj, 288 YamlContext &Context) { 289 Io.setContext(&Context); 290 Io.mapRequired("instructions", Obj.Instructions); 291 Io.mapOptional("config", Obj.Config); 292 Io.mapRequired("register_initial_values", Obj.RegisterInitialValues); 293 } 294 }; 295 296 template <> 297 struct MappingContextTraits<exegesis::InstructionBenchmark, YamlContext> { 298 struct NormalizedBinary { 299 NormalizedBinary(IO &io) {} 300 NormalizedBinary(IO &, std::vector<uint8_t> &Data) : Binary(Data) {} 301 std::vector<uint8_t> denormalize(IO &) { 302 std::vector<uint8_t> Data; 303 std::string Str; 304 raw_string_ostream OSS(Str); 305 Binary.writeAsBinary(OSS); 306 OSS.flush(); 307 Data.assign(Str.begin(), Str.end()); 308 return Data; 309 } 310 311 BinaryRef Binary; 312 }; 313 314 static void mapping(IO &Io, exegesis::InstructionBenchmark &Obj, 315 YamlContext &Context) { 316 Io.mapRequired("mode", Obj.Mode); 317 Io.mapRequired("key", Obj.Key, Context); 318 Io.mapRequired("cpu_name", Obj.CpuName); 319 Io.mapRequired("llvm_triple", Obj.LLVMTriple); 320 Io.mapRequired("num_repetitions", Obj.NumRepetitions); 321 Io.mapRequired("measurements", Obj.Measurements); 322 Io.mapRequired("error", Obj.Error); 323 Io.mapOptional("info", Obj.Info); 324 // AssembledSnippet 325 MappingNormalization<NormalizedBinary, std::vector<uint8_t>> BinaryString( 326 Io, Obj.AssembledSnippet); 327 Io.mapOptional("assembled_snippet", BinaryString->Binary); 328 } 329 }; 330 331 } // namespace yaml 332 333 namespace exegesis { 334 335 llvm::Expected<InstructionBenchmark> 336 InstructionBenchmark::readYaml(const LLVMState &State, 337 llvm::StringRef Filename) { 338 if (auto ExpectedMemoryBuffer = 339 llvm::errorOrToExpected(llvm::MemoryBuffer::getFile(Filename))) { 340 llvm::yaml::Input Yin(*ExpectedMemoryBuffer.get()); 341 YamlContext Context(State); 342 InstructionBenchmark Benchmark; 343 if (Yin.setCurrentDocument()) 344 llvm::yaml::yamlize(Yin, Benchmark, /*unused*/ true, Context); 345 if (!Context.getLastError().empty()) 346 return llvm::make_error<BenchmarkFailure>(Context.getLastError()); 347 return Benchmark; 348 } else { 349 return ExpectedMemoryBuffer.takeError(); 350 } 351 } 352 353 llvm::Expected<std::vector<InstructionBenchmark>> 354 InstructionBenchmark::readYamls(const LLVMState &State, 355 llvm::StringRef Filename) { 356 if (auto ExpectedMemoryBuffer = 357 llvm::errorOrToExpected(llvm::MemoryBuffer::getFile(Filename))) { 358 llvm::yaml::Input Yin(*ExpectedMemoryBuffer.get()); 359 YamlContext Context(State); 360 std::vector<InstructionBenchmark> Benchmarks; 361 while (Yin.setCurrentDocument()) { 362 Benchmarks.emplace_back(); 363 yamlize(Yin, Benchmarks.back(), /*unused*/ true, Context); 364 if (Yin.error()) 365 return llvm::errorCodeToError(Yin.error()); 366 if (!Context.getLastError().empty()) 367 return llvm::make_error<BenchmarkFailure>(Context.getLastError()); 368 Yin.nextDocument(); 369 } 370 return Benchmarks; 371 } else { 372 return ExpectedMemoryBuffer.takeError(); 373 } 374 } 375 376 llvm::Error InstructionBenchmark::writeYamlTo(const LLVMState &State, 377 llvm::raw_ostream &OS) { 378 auto Cleanup = make_scope_exit([&] { OS.flush(); }); 379 llvm::yaml::Output Yout(OS, nullptr /*Ctx*/, 200 /*WrapColumn*/); 380 YamlContext Context(State); 381 Yout.beginDocuments(); 382 llvm::yaml::yamlize(Yout, *this, /*unused*/ true, Context); 383 if (!Context.getLastError().empty()) 384 return llvm::make_error<BenchmarkFailure>(Context.getLastError()); 385 Yout.endDocuments(); 386 return Error::success(); 387 } 388 389 llvm::Error InstructionBenchmark::readYamlFrom(const LLVMState &State, 390 llvm::StringRef InputContent) { 391 llvm::yaml::Input Yin(InputContent); 392 YamlContext Context(State); 393 if (Yin.setCurrentDocument()) 394 llvm::yaml::yamlize(Yin, *this, /*unused*/ true, Context); 395 if (!Context.getLastError().empty()) 396 return llvm::make_error<BenchmarkFailure>(Context.getLastError()); 397 return Error::success(); 398 } 399 400 llvm::Error InstructionBenchmark::writeYaml(const LLVMState &State, 401 const llvm::StringRef Filename) { 402 if (Filename == "-") { 403 if (auto Err = writeYamlTo(State, llvm::outs())) 404 return Err; 405 } else { 406 int ResultFD = 0; 407 if (auto E = llvm::errorCodeToError( 408 openFileForWrite(Filename, ResultFD, llvm::sys::fs::CD_CreateAlways, 409 llvm::sys::fs::OF_Text))) { 410 return E; 411 } 412 llvm::raw_fd_ostream Ostr(ResultFD, true /*shouldClose*/); 413 if (auto Err = writeYamlTo(State, Ostr)) 414 return Err; 415 } 416 return llvm::Error::success(); 417 } 418 419 void PerInstructionStats::push(const BenchmarkMeasure &BM) { 420 if (Key.empty()) 421 Key = BM.Key; 422 assert(Key == BM.Key); 423 ++NumValues; 424 SumValues += BM.PerInstructionValue; 425 MaxValue = std::max(MaxValue, BM.PerInstructionValue); 426 MinValue = std::min(MinValue, BM.PerInstructionValue); 427 } 428 429 } // namespace exegesis 430 } // namespace llvm 431