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
39 void serializeMCInst(const MCInst &MCInst, raw_ostream &OS) {
40 OS << getInstrName(InstrNo: MCInst.getOpcode());
41 for (const auto &Op : MCInst) {
42 OS << ' ';
43 serializeMCOperand(MCOperand: Op, OS);
44 }
45 }
46
47 void deserializeMCInst(StringRef String, MCInst &Value) {
48 SmallVector<StringRef, 16> Pieces;
49 String.split(A&: Pieces, Separator: " ", /* 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(InstrName: Piece));
58 else
59 Value.addOperand(Op: deserializeMCOperand(String: 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(RegNo: 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(RegisterName: RegName);
81 if (!RegisterNumber.has_value())
82 ErrorStream << "No register with name '" << RegName << "'\n";
83 return RegisterNumber;
84 }
85
86private:
87 void serializeIntegerOperand(raw_ostream &OS, int64_t Value) {
88 OS << kIntegerPrefix;
89 OS.write_hex(N: bit_cast<uint64_t>(from: Value));
90 }
91
92 bool tryDeserializeIntegerOperand(StringRef String, int64_t &Value) {
93 if (!String.consume_front(Prefix: kIntegerPrefix))
94 return false;
95 return !String.consumeInteger(Radix: 16, Result&: Value);
96 }
97
98 void serializeFPOperand(raw_ostream &OS, double Value) {
99 OS << kDoublePrefix << format(Fmt: "%la", Vals: Value);
100 }
101
102 bool tryDeserializeFPOperand(StringRef String, double &Value) {
103 if (!String.consume_front(Prefix: kDoublePrefix))
104 return false;
105 char *EndPointer = nullptr;
106 Value = strtod(nptr: String.begin(), endptr: &EndPointer);
107 return EndPointer == String.end();
108 }
109
110 void serializeMCOperand(const MCOperand &MCOperand, raw_ostream &OS) {
111 if (MCOperand.isReg()) {
112 OS << getRegName(Reg: MCOperand.getReg());
113 } else if (MCOperand.isImm()) {
114 serializeIntegerOperand(OS, Value: MCOperand.getImm());
115 } else if (MCOperand.isDFPImm()) {
116 serializeFPOperand(OS, Value: bit_cast<double>(from: 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, Value&: IntValue))
127 return MCOperand::createImm(Val: IntValue);
128 if (tryDeserializeFPOperand(String, Value&: DoubleValue))
129 return MCOperand::createDFPImm(Val: bit_cast<uint64_t>(from: DoubleValue));
130 if (auto RegNo = getRegNo(RegName: String))
131 return MCOperand::createReg(Reg: *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(Opcode: 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(Val: 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.
160namespace yaml {
161
162static YamlContext &getTypedContext(void *Ctx) {
163 return *reinterpret_cast<YamlContext *>(Ctx);
164}
165
166// std::vector<MCInst> will be rendered as a list.
167template <> struct SequenceElementTraits<MCInst> {
168 static const bool flow = false;
169};
170
171template <> struct ScalarTraits<MCInst> {
172
173 static void output(const MCInst &Value, void *Ctx, raw_ostream &Out) {
174 getTypedContext(Ctx).serializeMCInst(MCInst: Value, OS&: Out);
175 }
176
177 static StringRef input(StringRef Scalar, void *Ctx, MCInst &Value) {
178 YamlContext &Context = getTypedContext(Ctx);
179 Context.deserializeMCInst(String: 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.
191template <> struct SequenceElementTraits<exegesis::BenchmarkMeasure> {
192 static const bool flow = false;
193};
194
195template <>
196struct 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(Name: KeyStr);
201 if (!Key) {
202 Io.setError("Key is not a valid validation event");
203 return;
204 }
205 Io.mapRequired(Key: KeyStr.str().c_str(), Val&: 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(Key: exegesis::getValidationEventName(VE: IndividualVI.first),
211 Val&: 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 }
218template <> struct MappingTraits<exegesis::BenchmarkMeasure> {
219 static void mapping(IO &Io, exegesis::BenchmarkMeasure &Obj) {
220 Io.mapRequired(Key: "key", Val&: Obj.Key);
221 if (!Io.outputting()) {
222 // For backward compatibility, interpret debug_string as a key.
223 Io.mapOptional(Key: "debug_string", Val&: Obj.Key);
224 }
225 Io.mapRequired(Key: "value", Val&: Obj.PerInstructionValue);
226 Io.mapOptional(Key: "per_snippet_value", Val&: Obj.PerSnippetValue);
227 Io.mapOptional(Key: "validation_counters", Val&: Obj.ValidationCounters);
228 }
229 static const bool flow = true;
230};
231
232template <> struct ScalarEnumerationTraits<exegesis::Benchmark::ModeE> {
233 static void enumeration(IO &Io, exegesis::Benchmark::ModeE &Value) {
234 Io.enumCase(Val&: Value, Str: "", ConstVal: exegesis::Benchmark::Unknown);
235 Io.enumCase(Val&: Value, Str: "latency", ConstVal: exegesis::Benchmark::Latency);
236 Io.enumCase(Val&: Value, Str: "uops", ConstVal: exegesis::Benchmark::Uops);
237 Io.enumCase(Val&: Value, Str: "inverse_throughput",
238 ConstVal: exegesis::Benchmark::InverseThroughput);
239 }
240};
241
242// std::vector<exegesis::RegisterValue> will be rendered as a list.
243template <> struct SequenceElementTraits<exegesis::RegisterValue> {
244 static const bool flow = false;
245};
246
247template <> 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(Reg: RV.Register) << "=0x"
255 << toString(I: RV.Value, Radix: kRadix, Signed: kSigned);
256 }
257
258 static StringRef input(StringRef String, void *Ctx,
259 exegesis::RegisterValue &RV) {
260 SmallVector<StringRef, 2> Pieces;
261 String.split(A&: Pieces, Separator: "=0x", /* MaxSplit */ -1,
262 /* KeepEmpty */ false);
263 YamlContext &Context = getTypedContext(Ctx);
264 std::optional<MCRegister> RegNo;
265 if (Pieces.size() == 2 && (RegNo = Context.getRegNo(RegName: Pieces[0]))) {
266 RV.Register = *RegNo;
267 const unsigned BitsNeeded = APInt::getBitsNeeded(str: Pieces[1], radix: 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
281template <> struct MappingContextTraits<exegesis::BenchmarkKey, YamlContext> {
282 static void mapping(IO &Io, exegesis::BenchmarkKey &Obj,
283 YamlContext &Context) {
284 Io.setContext(&Context);
285 Io.mapRequired(Key: "instructions", Val&: Obj.Instructions);
286 Io.mapOptional(Key: "config", Val&: Obj.Config);
287 Io.mapRequired(Key: "register_initial_values", Val&: Obj.RegisterInitialValues);
288 }
289};
290
291template <> 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(OS&: OSS);
300 OSS.flush();
301 Data.assign(first: Str.begin(), last: 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(Key: "mode", Val&: Obj.Mode);
310 Io.mapRequired(Key: "key", Val&: Obj.Key, Ctx&: Context);
311 Io.mapRequired(Key: "cpu_name", Val&: Obj.CpuName);
312 Io.mapRequired(Key: "llvm_triple", Val&: 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(Key: "min_instructions", Val&: Obj.MinInstructions);
320 else {
321 Io.mapOptional(Key: "num_repetitions", Val&: Obj.MinInstructions);
322 Io.mapOptional(Key: "min_instructions", Val&: Obj.MinInstructions);
323 }
324 Io.mapRequired(Key: "measurements", Val&: Obj.Measurements);
325 Io.mapRequired(Key: "error", Val&: Obj.Error);
326 Io.mapOptional(Key: "info", Val&: Obj.Info);
327 // AssembledSnippet
328 MappingNormalization<NormalizedBinary, std::vector<uint8_t>> BinaryString(
329 Io, Obj.AssembledSnippet);
330 Io.mapOptional(Key: "assembled_snippet", Val&: BinaryString->Binary);
331 }
332};
333
334template <> 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(Key: "llvm_triple", Val&: Obj.LLVMTriple);
339 Io.mapRequired(Key: "cpu_name", Val&: Obj.CpuName);
340 // Drop everything else.
341 }
342};
343
344} // namespace yaml
345
346namespace exegesis {
347
348Expected<std::set<Benchmark::TripleAndCpu>>
349Benchmark::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(io&: Yin, Val&: TC, /*unused*/ true, Ctx&: Context);
359 if (Yin.error())
360 return errorCodeToError(EC: Yin.error());
361 Result.insert(x: TC);
362 Yin.nextDocument();
363 }
364 return Result;
365}
366
367Expected<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(io&: Yin, Val&: Benchmark, /*unused*/ true, Ctx&: Context);
374 if (!Context.getLastError().empty())
375 return make_error<Failure>(Args&: Context.getLastError());
376 return std::move(Benchmark);
377}
378
379Expected<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(io&: Yin, Val&: Benchmarks.back(), /*unused*/ true, Ctx&: Context);
387 if (Yin.error())
388 return errorCodeToError(EC: Yin.error());
389 if (!Context.getLastError().empty())
390 return make_error<Failure>(Args&: Context.getLastError());
391 Yin.nextDocument();
392 }
393 return std::move(Benchmarks);
394}
395
396Error Benchmark::writeYamlTo(const LLVMState &State, raw_ostream &OS) {
397 auto Cleanup = make_scope_exit(F: [&] { OS.flush(); });
398 yaml::Output Yout(OS, nullptr /*Ctx*/, 200 /*WrapColumn*/);
399 YamlContext Context(State);
400 Yout.beginDocuments();
401 yaml::yamlize(io&: Yout, Val&: *this, /*unused*/ true, Ctx&: Context);
402 if (!Context.getLastError().empty())
403 return make_error<Failure>(Args&: Context.getLastError());
404 Yout.endDocuments();
405 return Error::success();
406}
407
408Error Benchmark::readYamlFrom(const LLVMState &State, StringRef InputContent) {
409 yaml::Input Yin(InputContent);
410 YamlContext Context(State);
411 if (Yin.setCurrentDocument())
412 yaml::yamlize(io&: Yin, Val&: *this, /*unused*/ true, Ctx&: Context);
413 if (!Context.getLastError().empty())
414 return make_error<Failure>(Args&: Context.getLastError());
415 return Error::success();
416}
417
418void 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(a: MaxValue, b: BM.PerInstructionValue);
425 MinValue = std::min(a: MinValue, b: BM.PerInstructionValue);
426}
427
428bool operator==(const BenchmarkMeasure &A, const BenchmarkMeasure &B) {
429 return std::tie(args: A.Key, args: A.PerInstructionValue, args: A.PerSnippetValue) ==
430 std::tie(args: B.Key, args: B.PerInstructionValue, args: B.PerSnippetValue);
431}
432
433} // namespace exegesis
434} // namespace llvm
435