1 //===-- BenchmarkResult.cpp -------------------------------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
9 #include "BenchmarkResult.h"
10 #include "BenchmarkRunner.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"
24 static constexpr const char kIntegerPrefix
[] = "i_0x";
25 static constexpr const char kDoublePrefix
[] = "f_";
26 static constexpr const char kInvalidOperand
[] = "INVALID";
32 // A mutable struct holding an LLVMState that can be passed through the
33 // serialization process to encode/decode registers and instructions.
35 YamlContext(const exegesis::LLVMState
&State
)
36 : State(&State
), ErrorStream(LastError
),
37 OpcodeNameToOpcodeIdx(State
.getOpcodeNameToOpcodeIdxMapping()) {}
39 void serializeMCInst(const MCInst
&MCInst
, raw_ostream
&OS
) {
40 OS
<< getInstrName(MCInst
.getOpcode());
41 for (const auto &Op
: MCInst
) {
43 serializeMCOperand(Op
, OS
);
47 void deserializeMCInst(StringRef String
, MCInst
&Value
) {
48 SmallVector
<StringRef
, 16> Pieces
;
49 String
.split(Pieces
, " ", /* MaxSplit */ -1, /* KeepEmpty */ false);
51 ErrorStream
<< "Unknown Instruction: '" << String
<< "'\n";
54 bool ProcessOpcode
= true;
55 for (StringRef Piece
: Pieces
) {
57 Value
.setOpcode(getInstrOpcode(Piece
));
59 Value
.addOperand(deserializeMCOperand(Piece
));
60 ProcessOpcode
= false;
64 std::string
&getLastError() { return ErrorStream
.str(); }
66 raw_string_ostream
&getErrorStream() { return ErrorStream
; }
68 StringRef
getRegName(unsigned RegNo
) {
69 // Special case: RegNo 0 is NoRegister. We have to deal with it explicitly.
72 const StringRef RegName
= State
->getRegInfo().getName(RegNo
);
74 ErrorStream
<< "No register with enum value '" << RegNo
<< "'\n";
78 std::optional
<unsigned> getRegNo(StringRef RegName
) {
79 std::optional
<MCRegister
> RegisterNumber
=
80 State
->getRegisterNumberFromName(RegName
);
81 if (!RegisterNumber
.has_value())
82 ErrorStream
<< "No register with name '" << RegName
<< "'\n";
83 return RegisterNumber
;
87 void serializeIntegerOperand(raw_ostream
&OS
, int64_t Value
) {
89 OS
.write_hex(bit_cast
<uint64_t>(Value
));
92 bool tryDeserializeIntegerOperand(StringRef String
, int64_t &Value
) {
93 if (!String
.consume_front(kIntegerPrefix
))
95 return !String
.consumeInteger(16, Value
);
98 void serializeFPOperand(raw_ostream
&OS
, double Value
) {
99 OS
<< kDoublePrefix
<< format("%la", Value
);
102 bool tryDeserializeFPOperand(StringRef String
, double &Value
) {
103 if (!String
.consume_front(kDoublePrefix
))
105 char *EndPointer
= nullptr;
106 Value
= strtod(String
.begin(), &EndPointer
);
107 return EndPointer
== String
.end();
110 void serializeMCOperand(const MCOperand
&MCOperand
, raw_ostream
&OS
) {
111 if (MCOperand
.isReg()) {
112 OS
<< getRegName(MCOperand
.getReg());
113 } else if (MCOperand
.isImm()) {
114 serializeIntegerOperand(OS
, MCOperand
.getImm());
115 } else if (MCOperand
.isDFPImm()) {
116 serializeFPOperand(OS
, bit_cast
<double>(MCOperand
.getDFPImm()));
118 OS
<< kInvalidOperand
;
122 MCOperand
deserializeMCOperand(StringRef String
) {
123 assert(!String
.empty());
124 int64_t IntValue
= 0;
125 double DoubleValue
= 0;
126 if (tryDeserializeIntegerOperand(String
, IntValue
))
127 return MCOperand::createImm(IntValue
);
128 if (tryDeserializeFPOperand(String
, DoubleValue
))
129 return MCOperand::createDFPImm(bit_cast
<uint64_t>(DoubleValue
));
130 if (auto RegNo
= getRegNo(String
))
131 return MCOperand::createReg(*RegNo
);
132 if (String
!= kInvalidOperand
)
133 ErrorStream
<< "Unknown Operand: '" << String
<< "'\n";
137 StringRef
getInstrName(unsigned InstrNo
) {
138 const StringRef InstrName
= State
->getInstrInfo().getName(InstrNo
);
139 if (InstrName
.empty())
140 ErrorStream
<< "No opcode with enum value '" << InstrNo
<< "'\n";
144 unsigned getInstrOpcode(StringRef InstrName
) {
145 auto Iter
= OpcodeNameToOpcodeIdx
.find(InstrName
);
146 if (Iter
!= OpcodeNameToOpcodeIdx
.end())
148 ErrorStream
<< "No opcode with name '" << InstrName
<< "'\n";
152 const exegesis::LLVMState
*State
;
153 std::string LastError
;
154 raw_string_ostream ErrorStream
;
155 const DenseMap
<StringRef
, unsigned> &OpcodeNameToOpcodeIdx
;
159 // Defining YAML traits for IO.
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;
196 struct 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(KeyStr
);
202 Io
.setError("Key is not a valid validation event");
205 Io
.mapRequired(KeyStr
.str().c_str(), VI
[*Key
]);
208 static void output(IO
&Io
, std::map
<exegesis::ValidationEvent
, int64_t> &VI
) {
209 for (auto &IndividualVI
: VI
) {
210 Io
.mapRequired(exegesis::getValidationEventName(IndividualVI
.first
),
211 IndividualVI
.second
);
216 // exegesis::Measure is rendererd as a flow instead of a list.
217 // e.g. { "key": "the key", "value": 0123 }
218 template <> struct MappingTraits
<exegesis::BenchmarkMeasure
> {
219 static void mapping(IO
&Io
, exegesis::BenchmarkMeasure
&Obj
) {
220 Io
.mapRequired("key", Obj
.Key
);
221 if (!Io
.outputting()) {
222 // For backward compatibility, interpret debug_string as a key.
223 Io
.mapOptional("debug_string", Obj
.Key
);
225 Io
.mapRequired("value", Obj
.PerInstructionValue
);
226 Io
.mapOptional("per_snippet_value", Obj
.PerSnippetValue
);
227 Io
.mapOptional("validation_counters", Obj
.ValidationCounters
);
229 static const bool flow
= true;
232 template <> struct ScalarEnumerationTraits
<exegesis::Benchmark::ModeE
> {
233 static void enumeration(IO
&Io
, exegesis::Benchmark::ModeE
&Value
) {
234 Io
.enumCase(Value
, "", exegesis::Benchmark::Unknown
);
235 Io
.enumCase(Value
, "latency", exegesis::Benchmark::Latency
);
236 Io
.enumCase(Value
, "uops", exegesis::Benchmark::Uops
);
237 Io
.enumCase(Value
, "inverse_throughput",
238 exegesis::Benchmark::InverseThroughput
);
242 // std::vector<exegesis::RegisterValue> will be rendered as a list.
243 template <> struct SequenceElementTraits
<exegesis::RegisterValue
> {
244 static const bool flow
= false;
247 template <> struct ScalarTraits
<exegesis::RegisterValue
> {
248 static constexpr const unsigned kRadix
= 16;
249 static constexpr const bool kSigned
= false;
251 static void output(const exegesis::RegisterValue
&RV
, void *Ctx
,
253 YamlContext
&Context
= getTypedContext(Ctx
);
254 Out
<< Context
.getRegName(RV
.Register
) << "=0x"
255 << toString(RV
.Value
, kRadix
, kSigned
);
258 static StringRef
input(StringRef String
, void *Ctx
,
259 exegesis::RegisterValue
&RV
) {
260 SmallVector
<StringRef
, 2> Pieces
;
261 String
.split(Pieces
, "=0x", /* MaxSplit */ -1,
262 /* KeepEmpty */ false);
263 YamlContext
&Context
= getTypedContext(Ctx
);
264 std::optional
<unsigned> RegNo
;
265 if (Pieces
.size() == 2 && (RegNo
= Context
.getRegNo(Pieces
[0]))) {
266 RV
.Register
= *RegNo
;
267 const unsigned BitsNeeded
= APInt::getBitsNeeded(Pieces
[1], kRadix
);
268 RV
.Value
= APInt(BitsNeeded
, Pieces
[1], kRadix
);
270 Context
.getErrorStream()
271 << "Unknown initial register value: '" << String
<< "'";
273 return Context
.getLastError();
276 static QuotingType
mustQuote(StringRef
) { return QuotingType::Single
; }
278 static const bool flow
= true;
281 template <> struct MappingContextTraits
<exegesis::BenchmarkKey
, YamlContext
> {
282 static void mapping(IO
&Io
, exegesis::BenchmarkKey
&Obj
,
283 YamlContext
&Context
) {
284 Io
.setContext(&Context
);
285 Io
.mapRequired("instructions", Obj
.Instructions
);
286 Io
.mapOptional("config", Obj
.Config
);
287 Io
.mapRequired("register_initial_values", Obj
.RegisterInitialValues
);
291 template <> 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
;
298 raw_string_ostream
OSS(Str
);
299 Binary
.writeAsBinary(OSS
);
301 Data
.assign(Str
.begin(), Str
.end());
308 static void mapping(IO
&Io
, exegesis::Benchmark
&Obj
, YamlContext
&Context
) {
309 Io
.mapRequired("mode", Obj
.Mode
);
310 Io
.mapRequired("key", Obj
.Key
, Context
);
311 Io
.mapRequired("cpu_name", Obj
.CpuName
);
312 Io
.mapRequired("llvm_triple", 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
319 Io
.mapRequired("min_instructions", Obj
.MinInstructions
);
321 Io
.mapOptional("num_repetitions", Obj
.MinInstructions
);
322 Io
.mapOptional("min_instructions", Obj
.MinInstructions
);
324 Io
.mapRequired("measurements", Obj
.Measurements
);
325 Io
.mapRequired("error", Obj
.Error
);
326 Io
.mapOptional("info", Obj
.Info
);
328 MappingNormalization
<NormalizedBinary
, std::vector
<uint8_t>> BinaryString(
329 Io
, Obj
.AssembledSnippet
);
330 Io
.mapOptional("assembled_snippet", BinaryString
->Binary
);
334 template <> struct MappingTraits
<exegesis::Benchmark::TripleAndCpu
> {
335 static void mapping(IO
&Io
, exegesis::Benchmark::TripleAndCpu
&Obj
) {
336 assert(!Io
.outputting() && "can only read TripleAndCpu");
338 Io
.mapRequired("llvm_triple", Obj
.LLVMTriple
);
339 Io
.mapRequired("cpu_name", Obj
.CpuName
);
340 // Drop everything else.
348 Expected
<std::set
<Benchmark::TripleAndCpu
>>
349 Benchmark::readTriplesAndCpusFromYamls(MemoryBufferRef Buffer
) {
350 // We're only mapping a field, drop other fields and silence the corresponding
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()) {
358 yamlize(Yin
, TC
, /*unused*/ true, Context
);
360 return errorCodeToError(Yin
.error());
367 Expected
<Benchmark
> Benchmark::readYaml(const LLVMState
&State
,
368 MemoryBufferRef Buffer
) {
369 yaml::Input
Yin(Buffer
);
370 YamlContext
Context(State
);
372 if (Yin
.setCurrentDocument())
373 yaml::yamlize(Yin
, Benchmark
, /*unused*/ true, Context
);
374 if (!Context
.getLastError().empty())
375 return make_error
<Failure
>(Context
.getLastError());
376 return std::move(Benchmark
);
379 Expected
<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(Yin
, Benchmarks
.back(), /*unused*/ true, Context
);
388 return errorCodeToError(Yin
.error());
389 if (!Context
.getLastError().empty())
390 return make_error
<Failure
>(Context
.getLastError());
393 return std::move(Benchmarks
);
396 Error
Benchmark::writeYamlTo(const LLVMState
&State
, raw_ostream
&OS
) {
397 auto Cleanup
= make_scope_exit([&] { OS
.flush(); });
398 yaml::Output
Yout(OS
, nullptr /*Ctx*/, 200 /*WrapColumn*/);
399 YamlContext
Context(State
);
400 Yout
.beginDocuments();
401 yaml::yamlize(Yout
, *this, /*unused*/ true, Context
);
402 if (!Context
.getLastError().empty())
403 return make_error
<Failure
>(Context
.getLastError());
405 return Error::success();
408 Error
Benchmark::readYamlFrom(const LLVMState
&State
, StringRef InputContent
) {
409 yaml::Input
Yin(InputContent
);
410 YamlContext
Context(State
);
411 if (Yin
.setCurrentDocument())
412 yaml::yamlize(Yin
, *this, /*unused*/ true, Context
);
413 if (!Context
.getLastError().empty())
414 return make_error
<Failure
>(Context
.getLastError());
415 return Error::success();
418 void PerInstructionStats::push(const BenchmarkMeasure
&BM
) {
421 assert(Key
== BM
.Key
);
423 SumValues
+= BM
.PerInstructionValue
;
424 MaxValue
= std::max(MaxValue
, BM
.PerInstructionValue
);
425 MinValue
= std::min(MinValue
, BM
.PerInstructionValue
);
428 bool operator==(const BenchmarkMeasure
&A
, const BenchmarkMeasure
&B
) {
429 return std::tie(A
.Key
, A
.PerInstructionValue
, A
.PerSnippetValue
) ==
430 std::tie(B
.Key
, B
.PerInstructionValue
, B
.PerSnippetValue
);
433 } // namespace exegesis