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::__anona7e00c050111::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::__anona7e00c050111::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::__anona7e00c050111::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::__anona7e00c050111::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::__anona7e00c050111::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::__anona7e00c050111::YamlContext86 std::string &getLastError() { return ErrorStream.str(); }
87
getErrorStreamllvm::__anona7e00c050111::YamlContext88 raw_string_ostream &getErrorStream() { return ErrorStream; }
89
getRegNamellvm::__anona7e00c050111::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::__anona7e00c050111::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::__anona7e00c050111::YamlContext109 void serializeIntegerOperand(raw_ostream &OS, int64_t Value) {
110 OS << kIntegerPrefix;
111 OS.write_hex(bit_cast<uint64_t>(Value));
112 }
113
tryDeserializeIntegerOperandllvm::__anona7e00c050111::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::__anona7e00c050111::YamlContext120 void serializeFPOperand(raw_ostream &OS, double Value) {
121 OS << kDoublePrefix << format("%la", Value);
122 }
123
tryDeserializeFPOperandllvm::__anona7e00c050111::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::__anona7e00c050111::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.isFPImm()) {
138 serializeFPOperand(OS, MCOperand.getFPImm());
139 } else {
140 OS << kInvalidOperand;
141 }
142 }
143
deserializeMCOperandllvm::__anona7e00c050111::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::createFPImm(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::__anona7e00c050111::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::__anona7e00c050111::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 << RV.Value.toString(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))) {
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))) {
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(
405 Filename, ResultFD, sys::fs::CD_CreateAlways, sys::fs::OF_Text))) {
406 return E;
407 }
408 raw_fd_ostream Ostr(ResultFD, true /*shouldClose*/);
409 if (auto Err = writeYamlTo(State, Ostr))
410 return Err;
411 }
412 return Error::success();
413 }
414
push(const BenchmarkMeasure & BM)415 void PerInstructionStats::push(const BenchmarkMeasure &BM) {
416 if (Key.empty())
417 Key = BM.Key;
418 assert(Key == BM.Key);
419 ++NumValues;
420 SumValues += BM.PerInstructionValue;
421 MaxValue = std::max(MaxValue, BM.PerInstructionValue);
422 MinValue = std::min(MinValue, BM.PerInstructionValue);
423 }
424
425 } // namespace exegesis
426 } // namespace llvm
427