Run DCE after a LoopFlatten test to reduce spurious output [nfc]
[llvm-project.git] / llvm / tools / llvm-exegesis / lib / BenchmarkResult.cpp
blob02c4da11e032d66c4a7cf2da68a1393c789587e7
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 //===----------------------------------------------------------------------===//
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/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"
22 static constexpr const char kIntegerPrefix[] = "i_0x";
23 static constexpr const char kDoublePrefix[] = "f_";
24 static constexpr const char kInvalidOperand[] = "INVALID";
26 namespace llvm {
28 namespace {
30 // A mutable struct holding an LLVMState that can be passed through the
31 // serialization process to encode/decode registers and instructions.
32 struct YamlContext {
33 YamlContext(const exegesis::LLVMState &State)
34 : State(&State), ErrorStream(LastError),
35 OpcodeNameToOpcodeIdx(State.getOpcodeNameToOpcodeIdxMapping()),
36 RegNameToRegNo(State.getRegNameToRegNoMapping()) {}
38 void serializeMCInst(const MCInst &MCInst, raw_ostream &OS) {
39 OS << getInstrName(MCInst.getOpcode());
40 for (const auto &Op : MCInst) {
41 OS << ' ';
42 serializeMCOperand(Op, OS);
46 void deserializeMCInst(StringRef String, MCInst &Value) {
47 SmallVector<StringRef, 16> Pieces;
48 String.split(Pieces, " ", /* MaxSplit */ -1, /* KeepEmpty */ false);
49 if (Pieces.empty()) {
50 ErrorStream << "Unknown Instruction: '" << String << "'\n";
51 return;
53 bool ProcessOpcode = true;
54 for (StringRef Piece : Pieces) {
55 if (ProcessOpcode)
56 Value.setOpcode(getInstrOpcode(Piece));
57 else
58 Value.addOperand(deserializeMCOperand(Piece));
59 ProcessOpcode = false;
63 std::string &getLastError() { return ErrorStream.str(); }
65 raw_string_ostream &getErrorStream() { return ErrorStream; }
67 StringRef getRegName(unsigned RegNo) {
68 // Special case: RegNo 0 is NoRegister. We have to deal with it explicitly.
69 if (RegNo == 0)
70 return kNoRegister;
71 const StringRef RegName = State->getRegInfo().getName(RegNo);
72 if (RegName.empty())
73 ErrorStream << "No register with enum value '" << RegNo << "'\n";
74 return RegName;
77 std::optional<unsigned> getRegNo(StringRef RegName) {
78 auto Iter = RegNameToRegNo.find(RegName);
79 if (Iter != RegNameToRegNo.end())
80 return Iter->second;
81 ErrorStream << "No register with name '" << RegName << "'\n";
82 return std::nullopt;
85 private:
86 void serializeIntegerOperand(raw_ostream &OS, int64_t Value) {
87 OS << kIntegerPrefix;
88 OS.write_hex(bit_cast<uint64_t>(Value));
91 bool tryDeserializeIntegerOperand(StringRef String, int64_t &Value) {
92 if (!String.consume_front(kIntegerPrefix))
93 return false;
94 return !String.consumeInteger(16, Value);
97 void serializeFPOperand(raw_ostream &OS, double Value) {
98 OS << kDoublePrefix << format("%la", Value);
101 bool tryDeserializeFPOperand(StringRef String, double &Value) {
102 if (!String.consume_front(kDoublePrefix))
103 return false;
104 char *EndPointer = nullptr;
105 Value = strtod(String.begin(), &EndPointer);
106 return EndPointer == String.end();
109 void serializeMCOperand(const MCOperand &MCOperand, raw_ostream &OS) {
110 if (MCOperand.isReg()) {
111 OS << getRegName(MCOperand.getReg());
112 } else if (MCOperand.isImm()) {
113 serializeIntegerOperand(OS, MCOperand.getImm());
114 } else if (MCOperand.isDFPImm()) {
115 serializeFPOperand(OS, bit_cast<double>(MCOperand.getDFPImm()));
116 } else {
117 OS << kInvalidOperand;
121 MCOperand deserializeMCOperand(StringRef String) {
122 assert(!String.empty());
123 int64_t IntValue = 0;
124 double DoubleValue = 0;
125 if (tryDeserializeIntegerOperand(String, IntValue))
126 return MCOperand::createImm(IntValue);
127 if (tryDeserializeFPOperand(String, DoubleValue))
128 return MCOperand::createDFPImm(bit_cast<uint64_t>(DoubleValue));
129 if (auto RegNo = getRegNo(String))
130 return MCOperand::createReg(*RegNo);
131 if (String != kInvalidOperand)
132 ErrorStream << "Unknown Operand: '" << String << "'\n";
133 return {};
136 StringRef getInstrName(unsigned InstrNo) {
137 const StringRef InstrName = State->getInstrInfo().getName(InstrNo);
138 if (InstrName.empty())
139 ErrorStream << "No opcode with enum value '" << InstrNo << "'\n";
140 return InstrName;
143 unsigned getInstrOpcode(StringRef InstrName) {
144 auto Iter = OpcodeNameToOpcodeIdx.find(InstrName);
145 if (Iter != OpcodeNameToOpcodeIdx.end())
146 return Iter->second;
147 ErrorStream << "No opcode with name '" << InstrName << "'\n";
148 return 0;
151 const exegesis::LLVMState *State;
152 std::string LastError;
153 raw_string_ostream ErrorStream;
154 const DenseMap<StringRef, unsigned> &OpcodeNameToOpcodeIdx;
155 const DenseMap<StringRef, unsigned> &RegNameToRegNo;
157 } // namespace
159 // Defining YAML traits for IO.
160 namespace yaml {
162 static YamlContext &getTypedContext(void *Ctx) {
163 return *reinterpret_cast<YamlContext *>(Ctx);
166 // std::vector<MCInst> will be rendered as a list.
167 template <> struct SequenceElementTraits<MCInst> {
168 static const bool flow = false;
171 template <> struct ScalarTraits<MCInst> {
173 static void output(const MCInst &Value, void *Ctx, raw_ostream &Out) {
174 getTypedContext(Ctx).serializeMCInst(Value, Out);
177 static StringRef input(StringRef Scalar, void *Ctx, MCInst &Value) {
178 YamlContext &Context = getTypedContext(Ctx);
179 Context.deserializeMCInst(Scalar, Value);
180 return Context.getLastError();
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; }
187 static const bool flow = true;
190 // std::vector<exegesis::Measure> will be rendered as a list.
191 template <> struct SequenceElementTraits<exegesis::BenchmarkMeasure> {
192 static const bool flow = false;
195 // exegesis::Measure is rendererd as a flow instead of a list.
196 // e.g. { "key": "the key", "value": 0123 }
197 template <> struct MappingTraits<exegesis::BenchmarkMeasure> {
198 static void mapping(IO &Io, exegesis::BenchmarkMeasure &Obj) {
199 Io.mapRequired("key", Obj.Key);
200 if (!Io.outputting()) {
201 // For backward compatibility, interpret debug_string as a key.
202 Io.mapOptional("debug_string", Obj.Key);
204 Io.mapRequired("value", Obj.PerInstructionValue);
205 Io.mapOptional("per_snippet_value", Obj.PerSnippetValue);
207 static const bool flow = true;
210 template <>
211 struct ScalarEnumerationTraits<exegesis::Benchmark::ModeE> {
212 static void enumeration(IO &Io,
213 exegesis::Benchmark::ModeE &Value) {
214 Io.enumCase(Value, "", exegesis::Benchmark::Unknown);
215 Io.enumCase(Value, "latency", exegesis::Benchmark::Latency);
216 Io.enumCase(Value, "uops", exegesis::Benchmark::Uops);
217 Io.enumCase(Value, "inverse_throughput",
218 exegesis::Benchmark::InverseThroughput);
222 // std::vector<exegesis::RegisterValue> will be rendered as a list.
223 template <> struct SequenceElementTraits<exegesis::RegisterValue> {
224 static const bool flow = false;
227 template <> struct ScalarTraits<exegesis::RegisterValue> {
228 static constexpr const unsigned kRadix = 16;
229 static constexpr const bool kSigned = false;
231 static void output(const exegesis::RegisterValue &RV, void *Ctx,
232 raw_ostream &Out) {
233 YamlContext &Context = getTypedContext(Ctx);
234 Out << Context.getRegName(RV.Register) << "=0x"
235 << toString(RV.Value, kRadix, kSigned);
238 static StringRef input(StringRef String, void *Ctx,
239 exegesis::RegisterValue &RV) {
240 SmallVector<StringRef, 2> Pieces;
241 String.split(Pieces, "=0x", /* MaxSplit */ -1,
242 /* KeepEmpty */ false);
243 YamlContext &Context = getTypedContext(Ctx);
244 std::optional<unsigned> RegNo;
245 if (Pieces.size() == 2 && (RegNo = Context.getRegNo(Pieces[0]))) {
246 RV.Register = *RegNo;
247 const unsigned BitsNeeded = APInt::getBitsNeeded(Pieces[1], kRadix);
248 RV.Value = APInt(BitsNeeded, Pieces[1], kRadix);
249 } else {
250 Context.getErrorStream()
251 << "Unknown initial register value: '" << String << "'";
253 return Context.getLastError();
256 static QuotingType mustQuote(StringRef) { return QuotingType::Single; }
258 static const bool flow = true;
261 template <>
262 struct MappingContextTraits<exegesis::BenchmarkKey, YamlContext> {
263 static void mapping(IO &Io, exegesis::BenchmarkKey &Obj,
264 YamlContext &Context) {
265 Io.setContext(&Context);
266 Io.mapRequired("instructions", Obj.Instructions);
267 Io.mapOptional("config", Obj.Config);
268 Io.mapRequired("register_initial_values", Obj.RegisterInitialValues);
272 template <>
273 struct MappingContextTraits<exegesis::Benchmark, YamlContext> {
274 struct NormalizedBinary {
275 NormalizedBinary(IO &io) {}
276 NormalizedBinary(IO &, std::vector<uint8_t> &Data) : Binary(Data) {}
277 std::vector<uint8_t> denormalize(IO &) {
278 std::vector<uint8_t> Data;
279 std::string Str;
280 raw_string_ostream OSS(Str);
281 Binary.writeAsBinary(OSS);
282 OSS.flush();
283 Data.assign(Str.begin(), Str.end());
284 return Data;
287 BinaryRef Binary;
290 static void mapping(IO &Io, exegesis::Benchmark &Obj,
291 YamlContext &Context) {
292 Io.mapRequired("mode", Obj.Mode);
293 Io.mapRequired("key", Obj.Key, Context);
294 Io.mapRequired("cpu_name", Obj.CpuName);
295 Io.mapRequired("llvm_triple", Obj.LLVMTriple);
296 Io.mapRequired("num_repetitions", Obj.NumRepetitions);
297 Io.mapRequired("measurements", Obj.Measurements);
298 Io.mapRequired("error", Obj.Error);
299 Io.mapOptional("info", Obj.Info);
300 // AssembledSnippet
301 MappingNormalization<NormalizedBinary, std::vector<uint8_t>> BinaryString(
302 Io, Obj.AssembledSnippet);
303 Io.mapOptional("assembled_snippet", BinaryString->Binary);
307 template <> struct MappingTraits<exegesis::Benchmark::TripleAndCpu> {
308 static void mapping(IO &Io,
309 exegesis::Benchmark::TripleAndCpu &Obj) {
310 assert(!Io.outputting() && "can only read TripleAndCpu");
311 // Read triple.
312 Io.mapRequired("llvm_triple", Obj.LLVMTriple);
313 Io.mapRequired("cpu_name", Obj.CpuName);
314 // Drop everything else.
318 } // namespace yaml
320 namespace exegesis {
322 Expected<std::set<Benchmark::TripleAndCpu>>
323 Benchmark::readTriplesAndCpusFromYamls(MemoryBufferRef Buffer) {
324 // We're only mapping a field, drop other fields and silence the corresponding
325 // warnings.
326 yaml::Input Yin(
327 Buffer, nullptr, +[](const SMDiagnostic &, void *Context) {});
328 Yin.setAllowUnknownKeys(true);
329 std::set<TripleAndCpu> Result;
330 yaml::EmptyContext Context;
331 while (Yin.setCurrentDocument()) {
332 TripleAndCpu TC;
333 yamlize(Yin, TC, /*unused*/ true, Context);
334 if (Yin.error())
335 return errorCodeToError(Yin.error());
336 Result.insert(TC);
337 Yin.nextDocument();
339 return Result;
342 Expected<Benchmark>
343 Benchmark::readYaml(const LLVMState &State, MemoryBufferRef Buffer) {
344 yaml::Input Yin(Buffer);
345 YamlContext Context(State);
346 Benchmark Benchmark;
347 if (Yin.setCurrentDocument())
348 yaml::yamlize(Yin, Benchmark, /*unused*/ true, Context);
349 if (!Context.getLastError().empty())
350 return make_error<Failure>(Context.getLastError());
351 return std::move(Benchmark);
354 Expected<std::vector<Benchmark>>
355 Benchmark::readYamls(const LLVMState &State,
356 MemoryBufferRef Buffer) {
357 yaml::Input Yin(Buffer);
358 YamlContext Context(State);
359 std::vector<Benchmark> Benchmarks;
360 while (Yin.setCurrentDocument()) {
361 Benchmarks.emplace_back();
362 yamlize(Yin, Benchmarks.back(), /*unused*/ true, Context);
363 if (Yin.error())
364 return errorCodeToError(Yin.error());
365 if (!Context.getLastError().empty())
366 return make_error<Failure>(Context.getLastError());
367 Yin.nextDocument();
369 return std::move(Benchmarks);
372 Error Benchmark::writeYamlTo(const LLVMState &State,
373 raw_ostream &OS) {
374 auto Cleanup = make_scope_exit([&] { OS.flush(); });
375 yaml::Output Yout(OS, nullptr /*Ctx*/, 200 /*WrapColumn*/);
376 YamlContext Context(State);
377 Yout.beginDocuments();
378 yaml::yamlize(Yout, *this, /*unused*/ true, Context);
379 if (!Context.getLastError().empty())
380 return make_error<Failure>(Context.getLastError());
381 Yout.endDocuments();
382 return Error::success();
385 Error Benchmark::readYamlFrom(const LLVMState &State,
386 StringRef InputContent) {
387 yaml::Input Yin(InputContent);
388 YamlContext Context(State);
389 if (Yin.setCurrentDocument())
390 yaml::yamlize(Yin, *this, /*unused*/ true, Context);
391 if (!Context.getLastError().empty())
392 return make_error<Failure>(Context.getLastError());
393 return Error::success();
396 void PerInstructionStats::push(const BenchmarkMeasure &BM) {
397 if (Key.empty())
398 Key = BM.Key;
399 assert(Key == BM.Key);
400 ++NumValues;
401 SumValues += BM.PerInstructionValue;
402 MaxValue = std::max(MaxValue, BM.PerInstructionValue);
403 MinValue = std::min(MinValue, BM.PerInstructionValue);
406 bool operator==(const BenchmarkMeasure &A, const BenchmarkMeasure &B) {
407 return std::tie(A.Key, A.PerInstructionValue, A.PerSnippetValue) ==
408 std::tie(B.Key, B.PerInstructionValue, B.PerSnippetValue);
412 } // namespace exegesis
413 } // namespace llvm