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 {
YamlContextllvm::__anon3e6093740111::YamlContext35   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>
generateOpcodeNameToOpcodeIdxMappingllvm::__anon3e6093740111::YamlContext42   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>
generateRegNameToRegNoMappingllvm::__anon3e6093740111::YamlContext51   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 
serializeMCInstllvm::__anon3e6093740111::YamlContext61   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 
deserializeMCInstllvm::__anon3e6093740111::YamlContext69   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 
getLastErrorllvm::__anon3e6093740111::YamlContext86   std::string &getLastError() { return ErrorStream.str(); }
87 
getErrorStreamllvm::__anon3e6093740111::YamlContext88   raw_string_ostream &getErrorStream() { return ErrorStream; }
89 
getRegNamellvm::__anon3e6093740111::YamlContext90   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 
getRegNollvm::__anon3e6093740111::YamlContext100   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:
serializeIntegerOperandllvm::__anon3e6093740111::YamlContext109   void serializeIntegerOperand(raw_ostream &OS, int64_t Value) {
110     OS << kIntegerPrefix;
111     OS.write_hex(bit_cast<uint64_t>(Value));
112   }
113 
tryDeserializeIntegerOperandllvm::__anon3e6093740111::YamlContext114   bool tryDeserializeIntegerOperand(StringRef String, int64_t &Value) {
115     if (!String.consume_front(kIntegerPrefix))
116       return false;
117     return !String.consumeInteger(16, Value);
118   }
119 
serializeFPOperandllvm::__anon3e6093740111::YamlContext120   void serializeFPOperand(raw_ostream &OS, double Value) {
121     OS << kDoublePrefix << format("%la", Value);
122   }
123 
tryDeserializeFPOperandllvm::__anon3e6093740111::YamlContext124   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 
serializeMCOperandllvm::__anon3e6093740111::YamlContext132   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 
deserializeMCOperandllvm::__anon3e6093740111::YamlContext144   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 
getInstrNamellvm::__anon3e6093740111::YamlContext159   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 
getInstrOpcodellvm::__anon3e6093740111::YamlContext166   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 
getTypedContext(void * Ctx)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 
outputllvm::yaml::ScalarTraits196   static void output(const MCInst &Value, void *Ctx, raw_ostream &Out) {
197     getTypedContext(Ctx).serializeMCInst(Value, Out);
198   }
199 
inputllvm::yaml::ScalarTraits200   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.
mustQuotellvm::yaml::ScalarTraits208   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> {
mappingllvm::yaml::MappingTraits221   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> {
enumerationllvm::yaml::ScalarEnumerationTraits235   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 
outputllvm::yaml::ScalarTraits254   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         << toString(RV.Value, kRadix, kSigned);
259   }
260 
inputllvm::yaml::ScalarTraits261   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 
mustQuotellvm::yaml::ScalarTraits279   static QuotingType mustQuote(StringRef) { return QuotingType::Single; }
280 
281   static const bool flow = true;
282 };
283 
284 template <>
285 struct MappingContextTraits<exegesis::InstructionBenchmarkKey, YamlContext> {
mappingllvm::yaml::MappingContextTraits286   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 {
NormalizedBinaryllvm::yaml::MappingContextTraits::NormalizedBinary298     NormalizedBinary(IO &io) {}
NormalizedBinaryllvm::yaml::MappingContextTraits::NormalizedBinary299     NormalizedBinary(IO &, std::vector<uint8_t> &Data) : Binary(Data) {}
denormalizellvm::yaml::MappingContextTraits::NormalizedBinary300     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 
mappingllvm::yaml::MappingContextTraits313   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>
readYaml(const LLVMState & State,StringRef Filename)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>>
readYamls(const LLVMState & State,StringRef Filename)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 
writeYamlTo(const LLVMState & State,raw_ostream & OS)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 
readYamlFrom(const LLVMState & State,StringRef InputContent)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 
writeYaml(const LLVMState & State,const StringRef Filename)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 
push(const BenchmarkMeasure & BM)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 
operator ==(const BenchmarkMeasure & A,const BenchmarkMeasure & B)426 bool operator==(const BenchmarkMeasure &A, const BenchmarkMeasure &B) {
427   return std::tie(A.Key, A.PerInstructionValue, A.PerSnippetValue) ==
428          std::tie(B.Key, B.PerInstructionValue, B.PerSnippetValue);
429 }
430 
431 
432 } // namespace exegesis
433 } // namespace llvm
434