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