xref: /llvm-project/llvm/tools/llvm-exegesis/lib/BenchmarkResult.cpp (revision ff1b01bb7897bf2401540096af775d35b12eb247)
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