BenchmarkResult.cpp 15 KB

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