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
24static constexpr const char kIntegerPrefix[] = "i_0x";
25static constexpr const char kDoublePrefix[] = "f_";
26static constexpr const char kInvalidOperand[] = "INVALID";
27
28namespace llvm {
29
30namespace {
31
32// A mutable struct holding an LLVMState that can be passed through the
33// serialization process to encode/decode registers and instructions.
34struct YamlContext {
35 YamlContext(const exegesis::LLVMState &State)
36 : State(&State), ErrorStream(LastError),
37 OpcodeNameToOpcodeIdx(State.getOpcodeNameToOpcodeIdxMapping()),
38 RegNameToRegNo(State.getRegNameToRegNoMapping()) {}
39
40 void serializeMCInst(const MCInst &MCInst, raw_ostream &OS) {
41 OS << getInstrName(InstrNo: MCInst.getOpcode());
42 for (const auto &Op : MCInst) {
43 OS << ' ';
44 serializeMCOperand(MCOperand: Op, OS);
45 }
46 }
47
48 void deserializeMCInst(StringRef String, MCInst &Value) {
49 SmallVector<StringRef, 16> Pieces;
50 String.split(A&: Pieces, Separator: " ", /* MaxSplit */ -1, /* KeepEmpty */ false);
51 if (Pieces.empty()) {
52 ErrorStream << "Unknown Instruction: '" << String << "'\n";
53 return;
54 }
55 bool ProcessOpcode = true;
56 for (StringRef Piece : Pieces) {
57 if (ProcessOpcode)
58 Value.setOpcode(getInstrOpcode(InstrName: Piece));
59 else
60 Value.addOperand(Op: deserializeMCOperand(String: Piece));
61 ProcessOpcode = false;
62 }
63 }
64
65 std::string &getLastError() { return ErrorStream.str(); }
66
67 raw_string_ostream &getErrorStream() { return ErrorStream; }
68
69 StringRef getRegName(unsigned RegNo) {
70 // Special case: RegNo 0 is NoRegister. We have to deal with it explicitly.
71 if (RegNo == 0)
72 return kNoRegister;
73 const StringRef RegName = State->getRegInfo().getName(RegNo);
74 if (RegName.empty())
75 ErrorStream << "No register with enum value '" << RegNo << "'\n";
76 return RegName;
77 }
78
79 std::optional<unsigned> getRegNo(StringRef RegName) {
80 auto Iter = RegNameToRegNo.find(Val: RegName);
81 if (Iter != RegNameToRegNo.end())
82 return Iter->second;
83 ErrorStream << "No register with name '" << RegName << "'\n";
84 return std::nullopt;
85 }
86
87private:
88 void serializeIntegerOperand(raw_ostream &OS, int64_t Value) {
89 OS << kIntegerPrefix;
90 OS.write_hex(N: bit_cast<uint64_t>(from: Value));
91 }
92
93 bool tryDeserializeIntegerOperand(StringRef String, int64_t &Value) {
94 if (!String.consume_front(Prefix: kIntegerPrefix))
95 return false;
96 return !String.consumeInteger(Radix: 16, Result&: Value);
97 }
98
99 void serializeFPOperand(raw_ostream &OS, double Value) {
100 OS << kDoublePrefix << format(Fmt: "%la", Vals: Value);
101 }
102
103 bool tryDeserializeFPOperand(StringRef String, double &Value) {
104 if (!String.consume_front(Prefix: kDoublePrefix))
105 return false;
106 char *EndPointer = nullptr;
107 Value = strtod(nptr: String.begin(), endptr: &EndPointer);
108 return EndPointer == String.end();
109 }
110
111 void serializeMCOperand(const MCOperand &MCOperand, raw_ostream &OS) {
112 if (MCOperand.isReg()) {
113 OS << getRegName(RegNo: MCOperand.getReg());
114 } else if (MCOperand.isImm()) {
115 serializeIntegerOperand(OS, Value: MCOperand.getImm());
116 } else if (MCOperand.isDFPImm()) {
117 serializeFPOperand(OS, Value: bit_cast<double>(from: MCOperand.getDFPImm()));
118 } else {
119 OS << kInvalidOperand;
120 }
121 }
122
123 MCOperand deserializeMCOperand(StringRef String) {
124 assert(!String.empty());
125 int64_t IntValue = 0;
126 double DoubleValue = 0;
127 if (tryDeserializeIntegerOperand(String, Value&: IntValue))
128 return MCOperand::createImm(Val: IntValue);
129 if (tryDeserializeFPOperand(String, Value&: DoubleValue))
130 return MCOperand::createDFPImm(Val: bit_cast<uint64_t>(from: DoubleValue));
131 if (auto RegNo = getRegNo(RegName: String))
132 return MCOperand::createReg(Reg: *RegNo);
133 if (String != kInvalidOperand)
134 ErrorStream << "Unknown Operand: '" << String << "'\n";
135 return {};
136 }
137
138 StringRef getInstrName(unsigned InstrNo) {
139 const StringRef InstrName = State->getInstrInfo().getName(Opcode: InstrNo);
140 if (InstrName.empty())
141 ErrorStream << "No opcode with enum value '" << InstrNo << "'\n";
142 return InstrName;
143 }
144
145 unsigned getInstrOpcode(StringRef InstrName) {
146 auto Iter = OpcodeNameToOpcodeIdx.find(Val: InstrName);
147 if (Iter != OpcodeNameToOpcodeIdx.end())
148 return Iter->second;
149 ErrorStream << "No opcode with name '" << InstrName << "'\n";
150 return 0;
151 }
152
153 const exegesis::LLVMState *State;
154 std::string LastError;
155 raw_string_ostream ErrorStream;
156 const DenseMap<StringRef, unsigned> &OpcodeNameToOpcodeIdx;
157 const DenseMap<StringRef, unsigned> &RegNameToRegNo;
158};
159} // namespace
160
161// Defining YAML traits for IO.
162namespace yaml {
163
164static YamlContext &getTypedContext(void *Ctx) {
165 return *reinterpret_cast<YamlContext *>(Ctx);
166}
167
168// std::vector<MCInst> will be rendered as a list.
169template <> struct SequenceElementTraits<MCInst> {
170 static const bool flow = false;
171};
172
173template <> struct ScalarTraits<MCInst> {
174
175 static void output(const MCInst &Value, void *Ctx, raw_ostream &Out) {
176 getTypedContext(Ctx).serializeMCInst(MCInst: Value, OS&: Out);
177 }
178
179 static StringRef input(StringRef Scalar, void *Ctx, MCInst &Value) {
180 YamlContext &Context = getTypedContext(Ctx);
181 Context.deserializeMCInst(String: Scalar, Value);
182 return Context.getLastError();
183 }
184
185 // By default strings are quoted only when necessary.
186 // We force the use of single quotes for uniformity.
187 static QuotingType mustQuote(StringRef) { return QuotingType::Single; }
188
189 static const bool flow = true;
190};
191
192// std::vector<exegesis::Measure> will be rendered as a list.
193template <> struct SequenceElementTraits<exegesis::BenchmarkMeasure> {
194 static const bool flow = false;
195};
196
197template <>
198struct CustomMappingTraits<std::map<exegesis::ValidationEvent, int64_t>> {
199 static void inputOne(IO &Io, StringRef KeyStr,
200 std::map<exegesis::ValidationEvent, int64_t> &VI) {
201 Expected<exegesis::ValidationEvent> Key =
202 exegesis::getValidationEventByName(Name: KeyStr);
203 if (!Key) {
204 Io.setError("Key is not a valid validation event");
205 return;
206 }
207 Io.mapRequired(Key: KeyStr.str().c_str(), Val&: VI[*Key]);
208 }
209
210 static void output(IO &Io, std::map<exegesis::ValidationEvent, int64_t> &VI) {
211 for (auto &IndividualVI : VI) {
212 Io.mapRequired(Key: exegesis::getValidationEventName(VE: IndividualVI.first),
213 Val&: IndividualVI.second);
214 }
215 }
216};
217
218// exegesis::Measure is rendererd as a flow instead of a list.
219// e.g. { "key": "the key", "value": 0123 }
220template <> struct MappingTraits<exegesis::BenchmarkMeasure> {
221 static void mapping(IO &Io, exegesis::BenchmarkMeasure &Obj) {
222 Io.mapRequired(Key: "key", Val&: Obj.Key);
223 if (!Io.outputting()) {
224 // For backward compatibility, interpret debug_string as a key.
225 Io.mapOptional(Key: "debug_string", Val&: Obj.Key);
226 }
227 Io.mapRequired(Key: "value", Val&: Obj.PerInstructionValue);
228 Io.mapOptional(Key: "per_snippet_value", Val&: Obj.PerSnippetValue);
229 Io.mapOptional(Key: "validation_counters", Val&: Obj.ValidationCounters);
230 }
231 static const bool flow = true;
232};
233
234template <>
235struct ScalarEnumerationTraits<exegesis::Benchmark::ModeE> {
236 static void enumeration(IO &Io,
237 exegesis::Benchmark::ModeE &Value) {
238 Io.enumCase(Val&: Value, Str: "", ConstVal: exegesis::Benchmark::Unknown);
239 Io.enumCase(Val&: Value, Str: "latency", ConstVal: exegesis::Benchmark::Latency);
240 Io.enumCase(Val&: Value, Str: "uops", ConstVal: exegesis::Benchmark::Uops);
241 Io.enumCase(Val&: Value, Str: "inverse_throughput",
242 ConstVal: exegesis::Benchmark::InverseThroughput);
243 }
244};
245
246// std::vector<exegesis::RegisterValue> will be rendered as a list.
247template <> struct SequenceElementTraits<exegesis::RegisterValue> {
248 static const bool flow = false;
249};
250
251template <> struct ScalarTraits<exegesis::RegisterValue> {
252 static constexpr const unsigned kRadix = 16;
253 static constexpr const bool kSigned = false;
254
255 static void output(const exegesis::RegisterValue &RV, void *Ctx,
256 raw_ostream &Out) {
257 YamlContext &Context = getTypedContext(Ctx);
258 Out << Context.getRegName(RegNo: RV.Register) << "=0x"
259 << toString(I: RV.Value, Radix: kRadix, Signed: kSigned);
260 }
261
262 static StringRef input(StringRef String, void *Ctx,
263 exegesis::RegisterValue &RV) {
264 SmallVector<StringRef, 2> Pieces;
265 String.split(A&: Pieces, Separator: "=0x", /* MaxSplit */ -1,
266 /* KeepEmpty */ false);
267 YamlContext &Context = getTypedContext(Ctx);
268 std::optional<unsigned> RegNo;
269 if (Pieces.size() == 2 && (RegNo = Context.getRegNo(RegName: Pieces[0]))) {
270 RV.Register = *RegNo;
271 const unsigned BitsNeeded = APInt::getBitsNeeded(str: Pieces[1], radix: kRadix);
272 RV.Value = APInt(BitsNeeded, Pieces[1], kRadix);
273 } else {
274 Context.getErrorStream()
275 << "Unknown initial register value: '" << String << "'";
276 }
277 return Context.getLastError();
278 }
279
280 static QuotingType mustQuote(StringRef) { return QuotingType::Single; }
281
282 static const bool flow = true;
283};
284
285template <>
286struct MappingContextTraits<exegesis::BenchmarkKey, YamlContext> {
287 static void mapping(IO &Io, exegesis::BenchmarkKey &Obj,
288 YamlContext &Context) {
289 Io.setContext(&Context);
290 Io.mapRequired(Key: "instructions", Val&: Obj.Instructions);
291 Io.mapOptional(Key: "config", Val&: Obj.Config);
292 Io.mapRequired(Key: "register_initial_values", Val&: Obj.RegisterInitialValues);
293 }
294};
295
296template <>
297struct MappingContextTraits<exegesis::Benchmark, YamlContext> {
298 struct NormalizedBinary {
299 NormalizedBinary(IO &io) {}
300 NormalizedBinary(IO &, std::vector<uint8_t> &Data) : Binary(Data) {}
301 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(OS&: OSS);
306 OSS.flush();
307 Data.assign(first: Str.begin(), last: Str.end());
308 return Data;
309 }
310
311 BinaryRef Binary;
312 };
313
314 static void mapping(IO &Io, exegesis::Benchmark &Obj,
315 YamlContext &Context) {
316 Io.mapRequired(Key: "mode", Val&: Obj.Mode);
317 Io.mapRequired(Key: "key", Val&: Obj.Key, Ctx&: Context);
318 Io.mapRequired(Key: "cpu_name", Val&: Obj.CpuName);
319 Io.mapRequired(Key: "llvm_triple", Val&: Obj.LLVMTriple);
320 // Optionally map num_repetitions and min_instructions to the same
321 // value to preserve backwards compatibility.
322 // TODO(boomanaiden154): Move min_instructions to mapRequired and
323 // remove num_repetitions once num_repetitions is ready to be removed
324 // completely.
325 if (Io.outputting())
326 Io.mapRequired(Key: "min_instructions", Val&: Obj.MinInstructions);
327 else {
328 Io.mapOptional(Key: "num_repetitions", Val&: Obj.MinInstructions);
329 Io.mapOptional(Key: "min_instructions", Val&: Obj.MinInstructions);
330 }
331 Io.mapRequired(Key: "measurements", Val&: Obj.Measurements);
332 Io.mapRequired(Key: "error", Val&: Obj.Error);
333 Io.mapOptional(Key: "info", Val&: Obj.Info);
334 // AssembledSnippet
335 MappingNormalization<NormalizedBinary, std::vector<uint8_t>> BinaryString(
336 Io, Obj.AssembledSnippet);
337 Io.mapOptional(Key: "assembled_snippet", Val&: BinaryString->Binary);
338 }
339};
340
341template <> struct MappingTraits<exegesis::Benchmark::TripleAndCpu> {
342 static void mapping(IO &Io,
343 exegesis::Benchmark::TripleAndCpu &Obj) {
344 assert(!Io.outputting() && "can only read TripleAndCpu");
345 // Read triple.
346 Io.mapRequired(Key: "llvm_triple", Val&: Obj.LLVMTriple);
347 Io.mapRequired(Key: "cpu_name", Val&: Obj.CpuName);
348 // Drop everything else.
349 }
350};
351
352} // namespace yaml
353
354namespace exegesis {
355
356Expected<std::set<Benchmark::TripleAndCpu>>
357Benchmark::readTriplesAndCpusFromYamls(MemoryBufferRef Buffer) {
358 // We're only mapping a field, drop other fields and silence the corresponding
359 // warnings.
360 yaml::Input Yin(
361 Buffer, nullptr, +[](const SMDiagnostic &, void *Context) {});
362 Yin.setAllowUnknownKeys(true);
363 std::set<TripleAndCpu> Result;
364 yaml::EmptyContext Context;
365 while (Yin.setCurrentDocument()) {
366 TripleAndCpu TC;
367 yamlize(io&: Yin, Val&: TC, /*unused*/ true, Ctx&: Context);
368 if (Yin.error())
369 return errorCodeToError(EC: Yin.error());
370 Result.insert(x: TC);
371 Yin.nextDocument();
372 }
373 return Result;
374}
375
376Expected<Benchmark>
377Benchmark::readYaml(const LLVMState &State, MemoryBufferRef Buffer) {
378 yaml::Input Yin(Buffer);
379 YamlContext Context(State);
380 Benchmark Benchmark;
381 if (Yin.setCurrentDocument())
382 yaml::yamlize(io&: Yin, Val&: Benchmark, /*unused*/ true, Ctx&: Context);
383 if (!Context.getLastError().empty())
384 return make_error<Failure>(Args&: Context.getLastError());
385 return std::move(Benchmark);
386}
387
388Expected<std::vector<Benchmark>>
389Benchmark::readYamls(const LLVMState &State,
390 MemoryBufferRef Buffer) {
391 yaml::Input Yin(Buffer);
392 YamlContext Context(State);
393 std::vector<Benchmark> Benchmarks;
394 while (Yin.setCurrentDocument()) {
395 Benchmarks.emplace_back();
396 yamlize(io&: Yin, Val&: Benchmarks.back(), /*unused*/ true, Ctx&: Context);
397 if (Yin.error())
398 return errorCodeToError(EC: Yin.error());
399 if (!Context.getLastError().empty())
400 return make_error<Failure>(Args&: Context.getLastError());
401 Yin.nextDocument();
402 }
403 return std::move(Benchmarks);
404}
405
406Error Benchmark::writeYamlTo(const LLVMState &State,
407 raw_ostream &OS) {
408 auto Cleanup = make_scope_exit(F: [&] { OS.flush(); });
409 yaml::Output Yout(OS, nullptr /*Ctx*/, 200 /*WrapColumn*/);
410 YamlContext Context(State);
411 Yout.beginDocuments();
412 yaml::yamlize(io&: Yout, Val&: *this, /*unused*/ true, Ctx&: Context);
413 if (!Context.getLastError().empty())
414 return make_error<Failure>(Args&: Context.getLastError());
415 Yout.endDocuments();
416 return Error::success();
417}
418
419Error Benchmark::readYamlFrom(const LLVMState &State,
420 StringRef InputContent) {
421 yaml::Input Yin(InputContent);
422 YamlContext Context(State);
423 if (Yin.setCurrentDocument())
424 yaml::yamlize(io&: Yin, Val&: *this, /*unused*/ true, Ctx&: Context);
425 if (!Context.getLastError().empty())
426 return make_error<Failure>(Args&: Context.getLastError());
427 return Error::success();
428}
429
430void PerInstructionStats::push(const BenchmarkMeasure &BM) {
431 if (Key.empty())
432 Key = BM.Key;
433 assert(Key == BM.Key);
434 ++NumValues;
435 SumValues += BM.PerInstructionValue;
436 MaxValue = std::max(a: MaxValue, b: BM.PerInstructionValue);
437 MinValue = std::min(a: MinValue, b: BM.PerInstructionValue);
438}
439
440bool operator==(const BenchmarkMeasure &A, const BenchmarkMeasure &B) {
441 return std::tie(args: A.Key, args: A.PerInstructionValue, args: A.PerSnippetValue) ==
442 std::tie(args: B.Key, args: B.PerInstructionValue, args: B.PerSnippetValue);
443}
444
445} // namespace exegesis
446} // namespace llvm
447