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 "ValidationEvent.h" 13 #include "llvm/ADT/STLExtras.h" 14 #include "llvm/ADT/ScopeExit.h" 15 #include "llvm/ADT/StringRef.h" 16 #include "llvm/ADT/bit.h" 17 #include "llvm/ObjectYAML/YAML.h" 18 #include "llvm/Support/Errc.h" 19 #include "llvm/Support/FileOutputBuffer.h" 20 #include "llvm/Support/FileSystem.h" 21 #include "llvm/Support/Format.h" 22 #include "llvm/Support/raw_ostream.h" 23 24 static constexpr const char kIntegerPrefix[] = "i_0x"; 25 static constexpr const char kDoublePrefix[] = "f_"; 26 static constexpr const char kInvalidOperand[] = "INVALID"; 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(State.getOpcodeNameToOpcodeIdxMapping()) {} 38 39 void serializeMCInst(const MCInst &MCInst, raw_ostream &OS) { 40 OS << getInstrName(MCInst.getOpcode()); 41 for (const auto &Op : MCInst) { 42 OS << ' '; 43 serializeMCOperand(Op, OS); 44 } 45 } 46 47 void deserializeMCInst(StringRef String, MCInst &Value) { 48 SmallVector<StringRef, 16> Pieces; 49 String.split(Pieces, " ", /* MaxSplit */ -1, /* KeepEmpty */ false); 50 if (Pieces.empty()) { 51 ErrorStream << "Unknown Instruction: '" << String << "'\n"; 52 return; 53 } 54 bool ProcessOpcode = true; 55 for (StringRef Piece : Pieces) { 56 if (ProcessOpcode) 57 Value.setOpcode(getInstrOpcode(Piece)); 58 else 59 Value.addOperand(deserializeMCOperand(Piece)); 60 ProcessOpcode = false; 61 } 62 } 63 64 std::string &getLastError() { return ErrorStream.str(); } 65 66 raw_string_ostream &getErrorStream() { return ErrorStream; } 67 68 StringRef getRegName(MCRegister Reg) { 69 // Special case: Reg may be invalid. We have to deal with it explicitly. 70 if (!Reg.isValid()) 71 return kNoRegister; 72 const StringRef RegName = State->getRegInfo().getName(Reg); 73 if (RegName.empty()) 74 ErrorStream << "No register with enum value '" << Reg.id() << "'\n"; 75 return RegName; 76 } 77 78 std::optional<MCRegister> getRegNo(StringRef RegName) { 79 std::optional<MCRegister> RegisterNumber = 80 State->getRegisterNumberFromName(RegName); 81 if (!RegisterNumber.has_value()) 82 ErrorStream << "No register with name '" << RegName << "'\n"; 83 return RegisterNumber; 84 } 85 86 private: 87 void serializeIntegerOperand(raw_ostream &OS, int64_t Value) { 88 OS << kIntegerPrefix; 89 OS.write_hex(bit_cast<uint64_t>(Value)); 90 } 91 92 bool tryDeserializeIntegerOperand(StringRef String, int64_t &Value) { 93 if (!String.consume_front(kIntegerPrefix)) 94 return false; 95 return !String.consumeInteger(16, Value); 96 } 97 98 void serializeFPOperand(raw_ostream &OS, double Value) { 99 OS << kDoublePrefix << format("%la", Value); 100 } 101 102 bool tryDeserializeFPOperand(StringRef String, double &Value) { 103 if (!String.consume_front(kDoublePrefix)) 104 return false; 105 char *EndPointer = nullptr; 106 Value = strtod(String.begin(), &EndPointer); 107 return EndPointer == String.end(); 108 } 109 110 void serializeMCOperand(const MCOperand &MCOperand, raw_ostream &OS) { 111 if (MCOperand.isReg()) { 112 OS << getRegName(MCOperand.getReg()); 113 } else if (MCOperand.isImm()) { 114 serializeIntegerOperand(OS, MCOperand.getImm()); 115 } else if (MCOperand.isDFPImm()) { 116 serializeFPOperand(OS, bit_cast<double>(MCOperand.getDFPImm())); 117 } else { 118 OS << kInvalidOperand; 119 } 120 } 121 122 MCOperand deserializeMCOperand(StringRef String) { 123 assert(!String.empty()); 124 int64_t IntValue = 0; 125 double DoubleValue = 0; 126 if (tryDeserializeIntegerOperand(String, IntValue)) 127 return MCOperand::createImm(IntValue); 128 if (tryDeserializeFPOperand(String, DoubleValue)) 129 return MCOperand::createDFPImm(bit_cast<uint64_t>(DoubleValue)); 130 if (auto RegNo = getRegNo(String)) 131 return MCOperand::createReg(*RegNo); 132 if (String != kInvalidOperand) 133 ErrorStream << "Unknown Operand: '" << String << "'\n"; 134 return {}; 135 } 136 137 StringRef getInstrName(unsigned InstrNo) { 138 const StringRef InstrName = State->getInstrInfo().getName(InstrNo); 139 if (InstrName.empty()) 140 ErrorStream << "No opcode with enum value '" << InstrNo << "'\n"; 141 return InstrName; 142 } 143 144 unsigned getInstrOpcode(StringRef InstrName) { 145 auto Iter = OpcodeNameToOpcodeIdx.find(InstrName); 146 if (Iter != OpcodeNameToOpcodeIdx.end()) 147 return Iter->second; 148 ErrorStream << "No opcode with name '" << InstrName << "'\n"; 149 return 0; 150 } 151 152 const exegesis::LLVMState *State; 153 std::string LastError; 154 raw_string_ostream ErrorStream; 155 const DenseMap<StringRef, unsigned> &OpcodeNameToOpcodeIdx; 156 }; 157 } // namespace 158 159 // Defining YAML traits for IO. 160 namespace yaml { 161 162 static YamlContext &getTypedContext(void *Ctx) { 163 return *reinterpret_cast<YamlContext *>(Ctx); 164 } 165 166 // std::vector<MCInst> will be rendered as a list. 167 template <> struct SequenceElementTraits<MCInst> { 168 static const bool flow = false; 169 }; 170 171 template <> struct ScalarTraits<MCInst> { 172 173 static void output(const MCInst &Value, void *Ctx, raw_ostream &Out) { 174 getTypedContext(Ctx).serializeMCInst(Value, Out); 175 } 176 177 static StringRef input(StringRef Scalar, void *Ctx, MCInst &Value) { 178 YamlContext &Context = getTypedContext(Ctx); 179 Context.deserializeMCInst(Scalar, Value); 180 return Context.getLastError(); 181 } 182 183 // By default strings are quoted only when necessary. 184 // We force the use of single quotes for uniformity. 185 static QuotingType mustQuote(StringRef) { return QuotingType::Single; } 186 187 static const bool flow = true; 188 }; 189 190 // std::vector<exegesis::Measure> will be rendered as a list. 191 template <> struct SequenceElementTraits<exegesis::BenchmarkMeasure> { 192 static const bool flow = false; 193 }; 194 195 template <> 196 struct CustomMappingTraits<std::map<exegesis::ValidationEvent, int64_t>> { 197 static void inputOne(IO &Io, StringRef KeyStr, 198 std::map<exegesis::ValidationEvent, int64_t> &VI) { 199 Expected<exegesis::ValidationEvent> Key = 200 exegesis::getValidationEventByName(KeyStr); 201 if (!Key) { 202 Io.setError("Key is not a valid validation event"); 203 return; 204 } 205 Io.mapRequired(KeyStr.str().c_str(), VI[*Key]); 206 } 207 208 static void output(IO &Io, std::map<exegesis::ValidationEvent, int64_t> &VI) { 209 for (auto &IndividualVI : VI) { 210 Io.mapRequired(exegesis::getValidationEventName(IndividualVI.first), 211 IndividualVI.second); 212 } 213 } 214 }; 215 216 // exegesis::Measure is rendererd as a flow instead of a list. 217 // e.g. { "key": "the key", "value": 0123 } 218 template <> struct MappingTraits<exegesis::BenchmarkMeasure> { 219 static void mapping(IO &Io, exegesis::BenchmarkMeasure &Obj) { 220 Io.mapRequired("key", Obj.Key); 221 if (!Io.outputting()) { 222 // For backward compatibility, interpret debug_string as a key. 223 Io.mapOptional("debug_string", Obj.Key); 224 } 225 Io.mapRequired("value", Obj.PerInstructionValue); 226 Io.mapOptional("per_snippet_value", Obj.PerSnippetValue); 227 Io.mapOptional("validation_counters", Obj.ValidationCounters); 228 } 229 static const bool flow = true; 230 }; 231 232 template <> struct ScalarEnumerationTraits<exegesis::Benchmark::ModeE> { 233 static void enumeration(IO &Io, exegesis::Benchmark::ModeE &Value) { 234 Io.enumCase(Value, "", exegesis::Benchmark::Unknown); 235 Io.enumCase(Value, "latency", exegesis::Benchmark::Latency); 236 Io.enumCase(Value, "uops", exegesis::Benchmark::Uops); 237 Io.enumCase(Value, "inverse_throughput", 238 exegesis::Benchmark::InverseThroughput); 239 } 240 }; 241 242 // std::vector<exegesis::RegisterValue> will be rendered as a list. 243 template <> struct SequenceElementTraits<exegesis::RegisterValue> { 244 static const bool flow = false; 245 }; 246 247 template <> struct ScalarTraits<exegesis::RegisterValue> { 248 static constexpr const unsigned kRadix = 16; 249 static constexpr const bool kSigned = false; 250 251 static void output(const exegesis::RegisterValue &RV, void *Ctx, 252 raw_ostream &Out) { 253 YamlContext &Context = getTypedContext(Ctx); 254 Out << Context.getRegName(RV.Register) << "=0x" 255 << toString(RV.Value, kRadix, kSigned); 256 } 257 258 static StringRef input(StringRef String, void *Ctx, 259 exegesis::RegisterValue &RV) { 260 SmallVector<StringRef, 2> Pieces; 261 String.split(Pieces, "=0x", /* MaxSplit */ -1, 262 /* KeepEmpty */ false); 263 YamlContext &Context = getTypedContext(Ctx); 264 std::optional<MCRegister> RegNo; 265 if (Pieces.size() == 2 && (RegNo = Context.getRegNo(Pieces[0]))) { 266 RV.Register = *RegNo; 267 const unsigned BitsNeeded = APInt::getBitsNeeded(Pieces[1], kRadix); 268 RV.Value = APInt(BitsNeeded, Pieces[1], kRadix); 269 } else { 270 Context.getErrorStream() 271 << "Unknown initial register value: '" << String << "'"; 272 } 273 return Context.getLastError(); 274 } 275 276 static QuotingType mustQuote(StringRef) { return QuotingType::Single; } 277 278 static const bool flow = true; 279 }; 280 281 template <> struct MappingContextTraits<exegesis::BenchmarkKey, YamlContext> { 282 static void mapping(IO &Io, exegesis::BenchmarkKey &Obj, 283 YamlContext &Context) { 284 Io.setContext(&Context); 285 Io.mapRequired("instructions", Obj.Instructions); 286 Io.mapOptional("config", Obj.Config); 287 Io.mapRequired("register_initial_values", Obj.RegisterInitialValues); 288 } 289 }; 290 291 template <> struct MappingContextTraits<exegesis::Benchmark, YamlContext> { 292 struct NormalizedBinary { 293 NormalizedBinary(IO &io) {} 294 NormalizedBinary(IO &, std::vector<uint8_t> &Data) : Binary(Data) {} 295 std::vector<uint8_t> denormalize(IO &) { 296 std::vector<uint8_t> Data; 297 std::string Str; 298 raw_string_ostream OSS(Str); 299 Binary.writeAsBinary(OSS); 300 OSS.flush(); 301 Data.assign(Str.begin(), Str.end()); 302 return Data; 303 } 304 305 BinaryRef Binary; 306 }; 307 308 static void mapping(IO &Io, exegesis::Benchmark &Obj, YamlContext &Context) { 309 Io.mapRequired("mode", Obj.Mode); 310 Io.mapRequired("key", Obj.Key, Context); 311 Io.mapRequired("cpu_name", Obj.CpuName); 312 Io.mapRequired("llvm_triple", Obj.LLVMTriple); 313 // Optionally map num_repetitions and min_instructions to the same 314 // value to preserve backwards compatibility. 315 // TODO(boomanaiden154): Move min_instructions to mapRequired and 316 // remove num_repetitions once num_repetitions is ready to be removed 317 // completely. 318 if (Io.outputting()) 319 Io.mapRequired("min_instructions", Obj.MinInstructions); 320 else { 321 Io.mapOptional("num_repetitions", Obj.MinInstructions); 322 Io.mapOptional("min_instructions", Obj.MinInstructions); 323 } 324 Io.mapRequired("measurements", Obj.Measurements); 325 Io.mapRequired("error", Obj.Error); 326 Io.mapOptional("info", Obj.Info); 327 // AssembledSnippet 328 MappingNormalization<NormalizedBinary, std::vector<uint8_t>> BinaryString( 329 Io, Obj.AssembledSnippet); 330 Io.mapOptional("assembled_snippet", BinaryString->Binary); 331 } 332 }; 333 334 template <> struct MappingTraits<exegesis::Benchmark::TripleAndCpu> { 335 static void mapping(IO &Io, exegesis::Benchmark::TripleAndCpu &Obj) { 336 assert(!Io.outputting() && "can only read TripleAndCpu"); 337 // Read triple. 338 Io.mapRequired("llvm_triple", Obj.LLVMTriple); 339 Io.mapRequired("cpu_name", Obj.CpuName); 340 // Drop everything else. 341 } 342 }; 343 344 } // namespace yaml 345 346 namespace exegesis { 347 348 Expected<std::set<Benchmark::TripleAndCpu>> 349 Benchmark::readTriplesAndCpusFromYamls(MemoryBufferRef Buffer) { 350 // We're only mapping a field, drop other fields and silence the corresponding 351 // warnings. 352 yaml::Input Yin(Buffer, nullptr, +[](const SMDiagnostic &, void *Context) {}); 353 Yin.setAllowUnknownKeys(true); 354 std::set<TripleAndCpu> Result; 355 yaml::EmptyContext Context; 356 while (Yin.setCurrentDocument()) { 357 TripleAndCpu TC; 358 yamlize(Yin, TC, /*unused*/ true, Context); 359 if (Yin.error()) 360 return errorCodeToError(Yin.error()); 361 Result.insert(TC); 362 Yin.nextDocument(); 363 } 364 return Result; 365 } 366 367 Expected<Benchmark> Benchmark::readYaml(const LLVMState &State, 368 MemoryBufferRef Buffer) { 369 yaml::Input Yin(Buffer); 370 YamlContext Context(State); 371 Benchmark Benchmark; 372 if (Yin.setCurrentDocument()) 373 yaml::yamlize(Yin, Benchmark, /*unused*/ true, Context); 374 if (!Context.getLastError().empty()) 375 return make_error<Failure>(Context.getLastError()); 376 return std::move(Benchmark); 377 } 378 379 Expected<std::vector<Benchmark>> Benchmark::readYamls(const LLVMState &State, 380 MemoryBufferRef Buffer) { 381 yaml::Input Yin(Buffer); 382 YamlContext Context(State); 383 std::vector<Benchmark> Benchmarks; 384 while (Yin.setCurrentDocument()) { 385 Benchmarks.emplace_back(); 386 yamlize(Yin, Benchmarks.back(), /*unused*/ true, Context); 387 if (Yin.error()) 388 return errorCodeToError(Yin.error()); 389 if (!Context.getLastError().empty()) 390 return make_error<Failure>(Context.getLastError()); 391 Yin.nextDocument(); 392 } 393 return std::move(Benchmarks); 394 } 395 396 Error Benchmark::writeYamlTo(const LLVMState &State, raw_ostream &OS) { 397 auto Cleanup = make_scope_exit([&] { OS.flush(); }); 398 yaml::Output Yout(OS, nullptr /*Ctx*/, 200 /*WrapColumn*/); 399 YamlContext Context(State); 400 Yout.beginDocuments(); 401 yaml::yamlize(Yout, *this, /*unused*/ true, Context); 402 if (!Context.getLastError().empty()) 403 return make_error<Failure>(Context.getLastError()); 404 Yout.endDocuments(); 405 return Error::success(); 406 } 407 408 Error Benchmark::readYamlFrom(const LLVMState &State, StringRef InputContent) { 409 yaml::Input Yin(InputContent); 410 YamlContext Context(State); 411 if (Yin.setCurrentDocument()) 412 yaml::yamlize(Yin, *this, /*unused*/ true, Context); 413 if (!Context.getLastError().empty()) 414 return make_error<Failure>(Context.getLastError()); 415 return Error::success(); 416 } 417 418 void PerInstructionStats::push(const BenchmarkMeasure &BM) { 419 if (Key.empty()) 420 Key = BM.Key; 421 assert(Key == BM.Key); 422 ++NumValues; 423 SumValues += BM.PerInstructionValue; 424 MaxValue = std::max(MaxValue, BM.PerInstructionValue); 425 MinValue = std::min(MinValue, BM.PerInstructionValue); 426 } 427 428 bool operator==(const BenchmarkMeasure &A, const BenchmarkMeasure &B) { 429 return std::tie(A.Key, A.PerInstructionValue, A.PerSnippetValue) == 430 std::tie(B.Key, B.PerInstructionValue, B.PerSnippetValue); 431 } 432 433 } // namespace exegesis 434 } // namespace llvm 435