xref: /openbsd-src/gnu/llvm/llvm/tools/llvm-exegesis/lib/BenchmarkResult.cpp (revision d415bd752c734aee168c4ee86ff32e8cc249eb16)
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 
27 namespace llvm {
28 
29 namespace {
30 
31 // A mutable struct holding an LLVMState that can be passed through the
32 // serialization process to encode/decode registers and instructions.
33 struct YamlContext {
YamlContextllvm::__anonca40d2a50111::YamlContext34   YamlContext(const exegesis::LLVMState &State)
35       : State(&State), ErrorStream(LastError),
36         OpcodeNameToOpcodeIdx(State.getOpcodeNameToOpcodeIdxMapping()),
37         RegNameToRegNo(State.getRegNameToRegNoMapping()) {}
38 
serializeMCInstllvm::__anonca40d2a50111::YamlContext39   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 
deserializeMCInstllvm::__anonca40d2a50111::YamlContext47   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 
getLastErrorllvm::__anonca40d2a50111::YamlContext64   std::string &getLastError() { return ErrorStream.str(); }
65 
getErrorStreamllvm::__anonca40d2a50111::YamlContext66   raw_string_ostream &getErrorStream() { return ErrorStream; }
67 
getRegNamellvm::__anonca40d2a50111::YamlContext68   StringRef getRegName(unsigned RegNo) {
69     // Special case: RegNo 0 is NoRegister. We have to deal with it explicitly.
70     if (RegNo == 0)
71       return kNoRegister;
72     const StringRef RegName = State->getRegInfo().getName(RegNo);
73     if (RegName.empty())
74       ErrorStream << "No register with enum value '" << RegNo << "'\n";
75     return RegName;
76   }
77 
getRegNollvm::__anonca40d2a50111::YamlContext78   std::optional<unsigned> getRegNo(StringRef RegName) {
79     auto Iter = RegNameToRegNo.find(RegName);
80     if (Iter != RegNameToRegNo.end())
81       return Iter->second;
82     ErrorStream << "No register with name '" << RegName << "'\n";
83     return std::nullopt;
84   }
85 
86 private:
serializeIntegerOperandllvm::__anonca40d2a50111::YamlContext87   void serializeIntegerOperand(raw_ostream &OS, int64_t Value) {
88     OS << kIntegerPrefix;
89     OS.write_hex(bit_cast<uint64_t>(Value));
90   }
91 
tryDeserializeIntegerOperandllvm::__anonca40d2a50111::YamlContext92   bool tryDeserializeIntegerOperand(StringRef String, int64_t &Value) {
93     if (!String.consume_front(kIntegerPrefix))
94       return false;
95     return !String.consumeInteger(16, Value);
96   }
97 
serializeFPOperandllvm::__anonca40d2a50111::YamlContext98   void serializeFPOperand(raw_ostream &OS, double Value) {
99     OS << kDoublePrefix << format("%la", Value);
100   }
101 
tryDeserializeFPOperandllvm::__anonca40d2a50111::YamlContext102   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 
serializeMCOperandllvm::__anonca40d2a50111::YamlContext110   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 
deserializeMCOperandllvm::__anonca40d2a50111::YamlContext122   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 
getInstrNamellvm::__anonca40d2a50111::YamlContext137   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 
getInstrOpcodellvm::__anonca40d2a50111::YamlContext144   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   const DenseMap<StringRef, unsigned> &RegNameToRegNo;
157 };
158 } // namespace
159 
160 // Defining YAML traits for IO.
161 namespace yaml {
162 
getTypedContext(void * Ctx)163 static YamlContext &getTypedContext(void *Ctx) {
164   return *reinterpret_cast<YamlContext *>(Ctx);
165 }
166 
167 // std::vector<MCInst> will be rendered as a list.
168 template <> struct SequenceElementTraits<MCInst> {
169   static const bool flow = false;
170 };
171 
172 template <> struct ScalarTraits<MCInst> {
173 
outputllvm::yaml::ScalarTraits174   static void output(const MCInst &Value, void *Ctx, raw_ostream &Out) {
175     getTypedContext(Ctx).serializeMCInst(Value, Out);
176   }
177 
inputllvm::yaml::ScalarTraits178   static StringRef input(StringRef Scalar, void *Ctx, MCInst &Value) {
179     YamlContext &Context = getTypedContext(Ctx);
180     Context.deserializeMCInst(Scalar, Value);
181     return Context.getLastError();
182   }
183 
184   // By default strings are quoted only when necessary.
185   // We force the use of single quotes for uniformity.
mustQuotellvm::yaml::ScalarTraits186   static QuotingType mustQuote(StringRef) { return QuotingType::Single; }
187 
188   static const bool flow = true;
189 };
190 
191 // std::vector<exegesis::Measure> will be rendered as a list.
192 template <> struct SequenceElementTraits<exegesis::BenchmarkMeasure> {
193   static const bool flow = false;
194 };
195 
196 // exegesis::Measure is rendererd as a flow instead of a list.
197 // e.g. { "key": "the key", "value": 0123 }
198 template <> struct MappingTraits<exegesis::BenchmarkMeasure> {
mappingllvm::yaml::MappingTraits199   static void mapping(IO &Io, exegesis::BenchmarkMeasure &Obj) {
200     Io.mapRequired("key", Obj.Key);
201     if (!Io.outputting()) {
202       // For backward compatibility, interpret debug_string as a key.
203       Io.mapOptional("debug_string", Obj.Key);
204     }
205     Io.mapRequired("value", Obj.PerInstructionValue);
206     Io.mapOptional("per_snippet_value", Obj.PerSnippetValue);
207   }
208   static const bool flow = true;
209 };
210 
211 template <>
212 struct ScalarEnumerationTraits<exegesis::InstructionBenchmark::ModeE> {
enumerationllvm::yaml::ScalarEnumerationTraits213   static void enumeration(IO &Io,
214                           exegesis::InstructionBenchmark::ModeE &Value) {
215     Io.enumCase(Value, "", exegesis::InstructionBenchmark::Unknown);
216     Io.enumCase(Value, "latency", exegesis::InstructionBenchmark::Latency);
217     Io.enumCase(Value, "uops", exegesis::InstructionBenchmark::Uops);
218     Io.enumCase(Value, "inverse_throughput",
219                 exegesis::InstructionBenchmark::InverseThroughput);
220   }
221 };
222 
223 // std::vector<exegesis::RegisterValue> will be rendered as a list.
224 template <> struct SequenceElementTraits<exegesis::RegisterValue> {
225   static const bool flow = false;
226 };
227 
228 template <> struct ScalarTraits<exegesis::RegisterValue> {
229   static constexpr const unsigned kRadix = 16;
230   static constexpr const bool kSigned = false;
231 
outputllvm::yaml::ScalarTraits232   static void output(const exegesis::RegisterValue &RV, void *Ctx,
233                      raw_ostream &Out) {
234     YamlContext &Context = getTypedContext(Ctx);
235     Out << Context.getRegName(RV.Register) << "=0x"
236         << toString(RV.Value, kRadix, kSigned);
237   }
238 
inputllvm::yaml::ScalarTraits239   static StringRef input(StringRef String, void *Ctx,
240                          exegesis::RegisterValue &RV) {
241     SmallVector<StringRef, 2> Pieces;
242     String.split(Pieces, "=0x", /* MaxSplit */ -1,
243                  /* KeepEmpty */ false);
244     YamlContext &Context = getTypedContext(Ctx);
245     std::optional<unsigned> RegNo;
246     if (Pieces.size() == 2 && (RegNo = Context.getRegNo(Pieces[0]))) {
247       RV.Register = *RegNo;
248       const unsigned BitsNeeded = APInt::getBitsNeeded(Pieces[1], kRadix);
249       RV.Value = APInt(BitsNeeded, Pieces[1], kRadix);
250     } else {
251       Context.getErrorStream()
252           << "Unknown initial register value: '" << String << "'";
253     }
254     return Context.getLastError();
255   }
256 
mustQuotellvm::yaml::ScalarTraits257   static QuotingType mustQuote(StringRef) { return QuotingType::Single; }
258 
259   static const bool flow = true;
260 };
261 
262 template <>
263 struct MappingContextTraits<exegesis::InstructionBenchmarkKey, YamlContext> {
mappingllvm::yaml::MappingContextTraits264   static void mapping(IO &Io, exegesis::InstructionBenchmarkKey &Obj,
265                       YamlContext &Context) {
266     Io.setContext(&Context);
267     Io.mapRequired("instructions", Obj.Instructions);
268     Io.mapOptional("config", Obj.Config);
269     Io.mapRequired("register_initial_values", Obj.RegisterInitialValues);
270   }
271 };
272 
273 template <>
274 struct MappingContextTraits<exegesis::InstructionBenchmark, YamlContext> {
275   struct NormalizedBinary {
NormalizedBinaryllvm::yaml::MappingContextTraits::NormalizedBinary276     NormalizedBinary(IO &io) {}
NormalizedBinaryllvm::yaml::MappingContextTraits::NormalizedBinary277     NormalizedBinary(IO &, std::vector<uint8_t> &Data) : Binary(Data) {}
denormalizellvm::yaml::MappingContextTraits::NormalizedBinary278     std::vector<uint8_t> denormalize(IO &) {
279       std::vector<uint8_t> Data;
280       std::string Str;
281       raw_string_ostream OSS(Str);
282       Binary.writeAsBinary(OSS);
283       OSS.flush();
284       Data.assign(Str.begin(), Str.end());
285       return Data;
286     }
287 
288     BinaryRef Binary;
289   };
290 
mappingllvm::yaml::MappingContextTraits291   static void mapping(IO &Io, exegesis::InstructionBenchmark &Obj,
292                       YamlContext &Context) {
293     Io.mapRequired("mode", Obj.Mode);
294     Io.mapRequired("key", Obj.Key, Context);
295     Io.mapRequired("cpu_name", Obj.CpuName);
296     Io.mapRequired("llvm_triple", Obj.LLVMTriple);
297     Io.mapRequired("num_repetitions", Obj.NumRepetitions);
298     Io.mapRequired("measurements", Obj.Measurements);
299     Io.mapRequired("error", Obj.Error);
300     Io.mapOptional("info", Obj.Info);
301     // AssembledSnippet
302     MappingNormalization<NormalizedBinary, std::vector<uint8_t>> BinaryString(
303         Io, Obj.AssembledSnippet);
304     Io.mapOptional("assembled_snippet", BinaryString->Binary);
305   }
306 };
307 
308 template <> struct MappingTraits<exegesis::InstructionBenchmark::TripleAndCpu> {
mappingllvm::yaml::MappingTraits309   static void mapping(IO &Io,
310                       exegesis::InstructionBenchmark::TripleAndCpu &Obj) {
311     assert(!Io.outputting() && "can only read TripleAndCpu");
312     // Read triple.
313     Io.mapRequired("llvm_triple", Obj.LLVMTriple);
314     Io.mapRequired("cpu_name", Obj.CpuName);
315     // Drop everything else.
316   }
317 };
318 
319 } // namespace yaml
320 
321 namespace exegesis {
322 
323 Expected<std::set<InstructionBenchmark::TripleAndCpu>>
readTriplesAndCpusFromYamls(MemoryBufferRef Buffer)324 InstructionBenchmark::readTriplesAndCpusFromYamls(MemoryBufferRef Buffer) {
325   // We're only mapping a field, drop other fields and silence the corresponding
326   // warnings.
327   yaml::Input Yin(
328       Buffer, nullptr, +[](const SMDiagnostic &, void *Context) {});
329   Yin.setAllowUnknownKeys(true);
330   std::set<TripleAndCpu> Result;
331   yaml::EmptyContext Context;
332   while (Yin.setCurrentDocument()) {
333     TripleAndCpu TC;
334     yamlize(Yin, TC, /*unused*/ true, Context);
335     if (Yin.error())
336       return errorCodeToError(Yin.error());
337     Result.insert(TC);
338     Yin.nextDocument();
339   }
340   return Result;
341 }
342 
343 Expected<InstructionBenchmark>
readYaml(const LLVMState & State,MemoryBufferRef Buffer)344 InstructionBenchmark::readYaml(const LLVMState &State, MemoryBufferRef Buffer) {
345   yaml::Input Yin(Buffer);
346   YamlContext Context(State);
347   InstructionBenchmark Benchmark;
348   if (Yin.setCurrentDocument())
349     yaml::yamlize(Yin, Benchmark, /*unused*/ true, Context);
350   if (!Context.getLastError().empty())
351     return make_error<Failure>(Context.getLastError());
352   return std::move(Benchmark);
353 }
354 
355 Expected<std::vector<InstructionBenchmark>>
readYamls(const LLVMState & State,MemoryBufferRef Buffer)356 InstructionBenchmark::readYamls(const LLVMState &State,
357                                 MemoryBufferRef Buffer) {
358   yaml::Input Yin(Buffer);
359   YamlContext Context(State);
360   std::vector<InstructionBenchmark> Benchmarks;
361   while (Yin.setCurrentDocument()) {
362     Benchmarks.emplace_back();
363     yamlize(Yin, Benchmarks.back(), /*unused*/ true, Context);
364     if (Yin.error())
365       return errorCodeToError(Yin.error());
366     if (!Context.getLastError().empty())
367       return make_error<Failure>(Context.getLastError());
368     Yin.nextDocument();
369   }
370   return std::move(Benchmarks);
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 
push(const BenchmarkMeasure & BM)397 void PerInstructionStats::push(const BenchmarkMeasure &BM) {
398   if (Key.empty())
399     Key = BM.Key;
400   assert(Key == BM.Key);
401   ++NumValues;
402   SumValues += BM.PerInstructionValue;
403   MaxValue = std::max(MaxValue, BM.PerInstructionValue);
404   MinValue = std::min(MinValue, BM.PerInstructionValue);
405 }
406 
operator ==(const BenchmarkMeasure & A,const BenchmarkMeasure & B)407 bool operator==(const BenchmarkMeasure &A, const BenchmarkMeasure &B) {
408   return std::tie(A.Key, A.PerInstructionValue, A.PerSnippetValue) ==
409          std::tie(B.Key, B.PerInstructionValue, B.PerSnippetValue);
410 }
411 
412 
413 } // namespace exegesis
414 } // namespace llvm
415