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 "llvm/ADT/STLExtras.h"
12 #include "llvm/ADT/ScopeExit.h"
13 #include "llvm/ADT/StringMap.h"
14 #include "llvm/ADT/StringRef.h"
15 #include "llvm/ADT/bit.h"
16 #include "llvm/ObjectYAML/YAML.h"
17 #include "llvm/Support/FileOutputBuffer.h"
18 #include "llvm/Support/FileSystem.h"
19 #include "llvm/Support/Format.h"
20 #include "llvm/Support/raw_ostream.h"
21 
22 static constexpr const char kIntegerPrefix[] = "i_0x";
23 static constexpr const char kDoublePrefix[] = "f_";
24 static constexpr const char kInvalidOperand[] = "INVALID";
25 static constexpr llvm::StringLiteral kNoRegister("%noreg");
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::__anonb721a3540111::YamlContext34   YamlContext(const exegesis::LLVMState &State)
35       : State(&State), ErrorStream(LastError),
36         OpcodeNameToOpcodeIdx(
37             generateOpcodeNameToOpcodeIdxMapping(State.getInstrInfo())),
38         RegNameToRegNo(generateRegNameToRegNoMapping(State.getRegInfo())) {}
39 
40   static llvm::StringMap<unsigned>
generateOpcodeNameToOpcodeIdxMappingllvm::__anonb721a3540111::YamlContext41   generateOpcodeNameToOpcodeIdxMapping(const llvm::MCInstrInfo &InstrInfo) {
42     llvm::StringMap<unsigned> Map(InstrInfo.getNumOpcodes());
43     for (unsigned I = 0, E = InstrInfo.getNumOpcodes(); I < E; ++I)
44       Map[InstrInfo.getName(I)] = I;
45     assert(Map.size() == InstrInfo.getNumOpcodes() && "Size prediction failed");
46     return Map;
47   };
48 
49   llvm::StringMap<unsigned>
generateRegNameToRegNoMappingllvm::__anonb721a3540111::YamlContext50   generateRegNameToRegNoMapping(const llvm::MCRegisterInfo &RegInfo) {
51     llvm::StringMap<unsigned> Map(RegInfo.getNumRegs());
52     // Special-case RegNo 0, which would otherwise be spelled as ''.
53     Map[kNoRegister] = 0;
54     for (unsigned I = 1, E = RegInfo.getNumRegs(); I < E; ++I)
55       Map[RegInfo.getName(I)] = I;
56     assert(Map.size() == RegInfo.getNumRegs() && "Size prediction failed");
57     return Map;
58   };
59 
serializeMCInstllvm::__anonb721a3540111::YamlContext60   void serializeMCInst(const llvm::MCInst &MCInst, llvm::raw_ostream &OS) {
61     OS << getInstrName(MCInst.getOpcode());
62     for (const auto &Op : MCInst) {
63       OS << ' ';
64       serializeMCOperand(Op, OS);
65     }
66   }
67 
deserializeMCInstllvm::__anonb721a3540111::YamlContext68   void deserializeMCInst(llvm::StringRef String, llvm::MCInst &Value) {
69     llvm::SmallVector<llvm::StringRef, 16> Pieces;
70     String.split(Pieces, " ", /* MaxSplit */ -1, /* KeepEmpty */ false);
71     if (Pieces.empty()) {
72       ErrorStream << "Unknown Instruction: '" << String << "'\n";
73       return;
74     }
75     bool ProcessOpcode = true;
76     for (llvm::StringRef Piece : Pieces) {
77       if (ProcessOpcode)
78         Value.setOpcode(getInstrOpcode(Piece));
79       else
80         Value.addOperand(deserializeMCOperand(Piece));
81       ProcessOpcode = false;
82     }
83   }
84 
getLastErrorllvm::__anonb721a3540111::YamlContext85   std::string &getLastError() { return ErrorStream.str(); }
86 
getErrorStreamllvm::__anonb721a3540111::YamlContext87   llvm::raw_string_ostream &getErrorStream() { return ErrorStream; }
88 
getRegNamellvm::__anonb721a3540111::YamlContext89   llvm::StringRef getRegName(unsigned RegNo) {
90     // Special case: RegNo 0 is NoRegister. We have to deal with it explicitly.
91     if (RegNo == 0)
92       return kNoRegister;
93     const llvm::StringRef RegName = State->getRegInfo().getName(RegNo);
94     if (RegName.empty())
95       ErrorStream << "No register with enum value '" << RegNo << "'\n";
96     return RegName;
97   }
98 
getRegNollvm::__anonb721a3540111::YamlContext99   llvm::Optional<unsigned> getRegNo(llvm::StringRef RegName) {
100     auto Iter = RegNameToRegNo.find(RegName);
101     if (Iter != RegNameToRegNo.end())
102       return Iter->second;
103     ErrorStream << "No register with name '" << RegName << "'\n";
104     return llvm::None;
105   }
106 
107 private:
serializeIntegerOperandllvm::__anonb721a3540111::YamlContext108   void serializeIntegerOperand(llvm::raw_ostream &OS, int64_t Value) {
109     OS << kIntegerPrefix;
110     OS.write_hex(llvm::bit_cast<uint64_t>(Value));
111   }
112 
tryDeserializeIntegerOperandllvm::__anonb721a3540111::YamlContext113   bool tryDeserializeIntegerOperand(llvm::StringRef String, int64_t &Value) {
114     if (!String.consume_front(kIntegerPrefix))
115       return false;
116     return !String.consumeInteger(16, Value);
117   }
118 
serializeFPOperandllvm::__anonb721a3540111::YamlContext119   void serializeFPOperand(llvm::raw_ostream &OS, double Value) {
120     OS << kDoublePrefix << llvm::format("%la", Value);
121   }
122 
tryDeserializeFPOperandllvm::__anonb721a3540111::YamlContext123   bool tryDeserializeFPOperand(llvm::StringRef String, double &Value) {
124     if (!String.consume_front(kDoublePrefix))
125       return false;
126     char *EndPointer = nullptr;
127     Value = strtod(String.begin(), &EndPointer);
128     return EndPointer == String.end();
129   }
130 
serializeMCOperandllvm::__anonb721a3540111::YamlContext131   void serializeMCOperand(const llvm::MCOperand &MCOperand,
132                           llvm::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.isFPImm()) {
138       serializeFPOperand(OS, MCOperand.getFPImm());
139     } else {
140       OS << kInvalidOperand;
141     }
142   }
143 
deserializeMCOperandllvm::__anonb721a3540111::YamlContext144   llvm::MCOperand deserializeMCOperand(llvm::StringRef String) {
145     assert(!String.empty());
146     int64_t IntValue = 0;
147     double DoubleValue = 0;
148     if (tryDeserializeIntegerOperand(String, IntValue))
149       return llvm::MCOperand::createImm(IntValue);
150     if (tryDeserializeFPOperand(String, DoubleValue))
151       return llvm::MCOperand::createFPImm(DoubleValue);
152     if (auto RegNo = getRegNo(String))
153       return llvm::MCOperand::createReg(*RegNo);
154     if (String != kInvalidOperand)
155       ErrorStream << "Unknown Operand: '" << String << "'\n";
156     return {};
157   }
158 
getInstrNamellvm::__anonb721a3540111::YamlContext159   llvm::StringRef getInstrName(unsigned InstrNo) {
160     const llvm::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::__anonb721a3540111::YamlContext166   unsigned getInstrOpcode(llvm::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 llvm::exegesis::LLVMState *State;
175   std::string LastError;
176   llvm::raw_string_ostream ErrorStream;
177   const llvm::StringMap<unsigned> OpcodeNameToOpcodeIdx;
178   const llvm::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<llvm::MCInst> will be rendered as a list.
190 template <> struct SequenceElementTraits<llvm::MCInst> {
191   static const bool flow = false;
192 };
193 
194 template <> struct ScalarTraits<llvm::MCInst> {
195 
outputllvm::yaml::ScalarTraits196   static void output(const llvm::MCInst &Value, void *Ctx,
197                      llvm::raw_ostream &Out) {
198     getTypedContext(Ctx).serializeMCInst(Value, Out);
199   }
200 
inputllvm::yaml::ScalarTraits201   static StringRef input(StringRef Scalar, void *Ctx, llvm::MCInst &Value) {
202     YamlContext &Context = getTypedContext(Ctx);
203     Context.deserializeMCInst(Scalar, Value);
204     return Context.getLastError();
205   }
206 
207   // By default strings are quoted only when necessary.
208   // We force the use of single quotes for uniformity.
mustQuotellvm::yaml::ScalarTraits209   static QuotingType mustQuote(StringRef) { return QuotingType::Single; }
210 
211   static const bool flow = true;
212 };
213 
214 // std::vector<exegesis::Measure> will be rendered as a list.
215 template <> struct SequenceElementTraits<exegesis::BenchmarkMeasure> {
216   static const bool flow = false;
217 };
218 
219 // exegesis::Measure is rendererd as a flow instead of a list.
220 // e.g. { "key": "the key", "value": 0123 }
221 template <> struct MappingTraits<exegesis::BenchmarkMeasure> {
mappingllvm::yaml::MappingTraits222   static void mapping(IO &Io, exegesis::BenchmarkMeasure &Obj) {
223     Io.mapRequired("key", Obj.Key);
224     if (!Io.outputting()) {
225       // For backward compatibility, interpret debug_string as a key.
226       Io.mapOptional("debug_string", Obj.Key);
227     }
228     Io.mapRequired("value", Obj.PerInstructionValue);
229     Io.mapOptional("per_snippet_value", Obj.PerSnippetValue);
230   }
231   static const bool flow = true;
232 };
233 
234 template <>
235 struct ScalarEnumerationTraits<exegesis::InstructionBenchmark::ModeE> {
enumerationllvm::yaml::ScalarEnumerationTraits236   static void enumeration(IO &Io,
237                           exegesis::InstructionBenchmark::ModeE &Value) {
238     Io.enumCase(Value, "", exegesis::InstructionBenchmark::Unknown);
239     Io.enumCase(Value, "latency", exegesis::InstructionBenchmark::Latency);
240     Io.enumCase(Value, "uops", exegesis::InstructionBenchmark::Uops);
241     Io.enumCase(Value, "inverse_throughput",
242                 exegesis::InstructionBenchmark::InverseThroughput);
243   }
244 };
245 
246 // std::vector<exegesis::RegisterValue> will be rendered as a list.
247 template <> struct SequenceElementTraits<exegesis::RegisterValue> {
248   static const bool flow = false;
249 };
250 
251 template <> struct ScalarTraits<exegesis::RegisterValue> {
252   static constexpr const unsigned kRadix = 16;
253   static constexpr const bool kSigned = false;
254 
outputllvm::yaml::ScalarTraits255   static void output(const exegesis::RegisterValue &RV, void *Ctx,
256                      llvm::raw_ostream &Out) {
257     YamlContext &Context = getTypedContext(Ctx);
258     Out << Context.getRegName(RV.Register) << "=0x"
259         << RV.Value.toString(kRadix, kSigned);
260   }
261 
inputllvm::yaml::ScalarTraits262   static StringRef input(StringRef String, void *Ctx,
263                          exegesis::RegisterValue &RV) {
264     llvm::SmallVector<llvm::StringRef, 2> Pieces;
265     String.split(Pieces, "=0x", /* MaxSplit */ -1,
266                  /* KeepEmpty */ false);
267     YamlContext &Context = getTypedContext(Ctx);
268     llvm::Optional<unsigned> RegNo;
269     if (Pieces.size() == 2 && (RegNo = Context.getRegNo(Pieces[0]))) {
270       RV.Register = *RegNo;
271       const unsigned BitsNeeded = llvm::APInt::getBitsNeeded(Pieces[1], kRadix);
272       RV.Value = llvm::APInt(BitsNeeded, Pieces[1], kRadix);
273     } else {
274       Context.getErrorStream()
275           << "Unknown initial register value: '" << String << "'";
276     }
277     return Context.getLastError();
278   }
279 
mustQuotellvm::yaml::ScalarTraits280   static QuotingType mustQuote(StringRef) { return QuotingType::Single; }
281 
282   static const bool flow = true;
283 };
284 
285 template <>
286 struct MappingContextTraits<exegesis::InstructionBenchmarkKey, YamlContext> {
mappingllvm::yaml::MappingContextTraits287   static void mapping(IO &Io, exegesis::InstructionBenchmarkKey &Obj,
288                       YamlContext &Context) {
289     Io.setContext(&Context);
290     Io.mapRequired("instructions", Obj.Instructions);
291     Io.mapOptional("config", Obj.Config);
292     Io.mapRequired("register_initial_values", Obj.RegisterInitialValues);
293   }
294 };
295 
296 template <>
297 struct MappingContextTraits<exegesis::InstructionBenchmark, YamlContext> {
298   struct NormalizedBinary {
NormalizedBinaryllvm::yaml::MappingContextTraits::NormalizedBinary299     NormalizedBinary(IO &io) {}
NormalizedBinaryllvm::yaml::MappingContextTraits::NormalizedBinary300     NormalizedBinary(IO &, std::vector<uint8_t> &Data) : Binary(Data) {}
denormalizellvm::yaml::MappingContextTraits::NormalizedBinary301     std::vector<uint8_t> denormalize(IO &) {
302       std::vector<uint8_t> Data;
303       std::string Str;
304       raw_string_ostream OSS(Str);
305       Binary.writeAsBinary(OSS);
306       OSS.flush();
307       Data.assign(Str.begin(), Str.end());
308       return Data;
309     }
310 
311     BinaryRef Binary;
312   };
313 
mappingllvm::yaml::MappingContextTraits314   static void mapping(IO &Io, exegesis::InstructionBenchmark &Obj,
315                       YamlContext &Context) {
316     Io.mapRequired("mode", Obj.Mode);
317     Io.mapRequired("key", Obj.Key, Context);
318     Io.mapRequired("cpu_name", Obj.CpuName);
319     Io.mapRequired("llvm_triple", Obj.LLVMTriple);
320     Io.mapRequired("num_repetitions", Obj.NumRepetitions);
321     Io.mapRequired("measurements", Obj.Measurements);
322     Io.mapRequired("error", Obj.Error);
323     Io.mapOptional("info", Obj.Info);
324     // AssembledSnippet
325     MappingNormalization<NormalizedBinary, std::vector<uint8_t>> BinaryString(
326         Io, Obj.AssembledSnippet);
327     Io.mapOptional("assembled_snippet", BinaryString->Binary);
328   }
329 };
330 
331 } // namespace yaml
332 
333 namespace exegesis {
334 
335 llvm::Expected<InstructionBenchmark>
readYaml(const LLVMState & State,llvm::StringRef Filename)336 InstructionBenchmark::readYaml(const LLVMState &State,
337                                llvm::StringRef Filename) {
338   if (auto ExpectedMemoryBuffer =
339           llvm::errorOrToExpected(llvm::MemoryBuffer::getFile(Filename))) {
340     llvm::yaml::Input Yin(*ExpectedMemoryBuffer.get());
341     YamlContext Context(State);
342     InstructionBenchmark Benchmark;
343     if (Yin.setCurrentDocument())
344       llvm::yaml::yamlize(Yin, Benchmark, /*unused*/ true, Context);
345     if (!Context.getLastError().empty())
346       return llvm::make_error<BenchmarkFailure>(Context.getLastError());
347     return Benchmark;
348   } else {
349     return ExpectedMemoryBuffer.takeError();
350   }
351 }
352 
353 llvm::Expected<std::vector<InstructionBenchmark>>
readYamls(const LLVMState & State,llvm::StringRef Filename)354 InstructionBenchmark::readYamls(const LLVMState &State,
355                                 llvm::StringRef Filename) {
356   if (auto ExpectedMemoryBuffer =
357           llvm::errorOrToExpected(llvm::MemoryBuffer::getFile(Filename))) {
358     llvm::yaml::Input Yin(*ExpectedMemoryBuffer.get());
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 llvm::errorCodeToError(Yin.error());
366       if (!Context.getLastError().empty())
367         return llvm::make_error<BenchmarkFailure>(Context.getLastError());
368       Yin.nextDocument();
369     }
370     return Benchmarks;
371   } else {
372     return ExpectedMemoryBuffer.takeError();
373   }
374 }
375 
writeYamlTo(const LLVMState & State,llvm::raw_ostream & OS)376 llvm::Error InstructionBenchmark::writeYamlTo(const LLVMState &State,
377                                               llvm::raw_ostream &OS) {
378   auto Cleanup = make_scope_exit([&] { OS.flush(); });
379   llvm::yaml::Output Yout(OS, nullptr /*Ctx*/, 200 /*WrapColumn*/);
380   YamlContext Context(State);
381   Yout.beginDocuments();
382   llvm::yaml::yamlize(Yout, *this, /*unused*/ true, Context);
383   if (!Context.getLastError().empty())
384     return llvm::make_error<BenchmarkFailure>(Context.getLastError());
385   Yout.endDocuments();
386   return Error::success();
387 }
388 
readYamlFrom(const LLVMState & State,llvm::StringRef InputContent)389 llvm::Error InstructionBenchmark::readYamlFrom(const LLVMState &State,
390                                                llvm::StringRef InputContent) {
391   llvm::yaml::Input Yin(InputContent);
392   YamlContext Context(State);
393   if (Yin.setCurrentDocument())
394     llvm::yaml::yamlize(Yin, *this, /*unused*/ true, Context);
395   if (!Context.getLastError().empty())
396     return llvm::make_error<BenchmarkFailure>(Context.getLastError());
397   return Error::success();
398 }
399 
writeYaml(const LLVMState & State,const llvm::StringRef Filename)400 llvm::Error InstructionBenchmark::writeYaml(const LLVMState &State,
401                                             const llvm::StringRef Filename) {
402   if (Filename == "-") {
403     if (auto Err = writeYamlTo(State, llvm::outs()))
404       return Err;
405   } else {
406     int ResultFD = 0;
407     if (auto E = llvm::errorCodeToError(
408             openFileForWrite(Filename, ResultFD, llvm::sys::fs::CD_CreateAlways,
409                              llvm::sys::fs::F_Text))) {
410       return E;
411     }
412     llvm::raw_fd_ostream Ostr(ResultFD, true /*shouldClose*/);
413     if (auto Err = writeYamlTo(State, Ostr))
414       return Err;
415   }
416   return llvm::Error::success();
417 }
418 
push(const BenchmarkMeasure & BM)419 void PerInstructionStats::push(const BenchmarkMeasure &BM) {
420   if (Key.empty())
421     Key = BM.Key;
422   assert(Key == BM.Key);
423   ++NumValues;
424   SumValues += BM.PerInstructionValue;
425   MaxValue = std::max(MaxValue, BM.PerInstructionValue);
426   MinValue = std::min(MinValue, BM.PerInstructionValue);
427 }
428 
429 } // namespace exegesis
430 } // namespace llvm
431