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 "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";
30 // A mutable struct holding an LLVMState that can be passed through the
31 // serialization process to encode/decode registers and instructions.
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
) {
42 serializeMCOperand(Op
, OS
);
46 void deserializeMCInst(StringRef String
, MCInst
&Value
) {
47 SmallVector
<StringRef
, 16> Pieces
;
48 String
.split(Pieces
, " ", /* MaxSplit */ -1, /* KeepEmpty */ false);
50 ErrorStream
<< "Unknown Instruction: '" << String
<< "'\n";
53 bool ProcessOpcode
= true;
54 for (StringRef Piece
: Pieces
) {
56 Value
.setOpcode(getInstrOpcode(Piece
));
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.
71 const StringRef RegName
= State
->getRegInfo().getName(RegNo
);
73 ErrorStream
<< "No register with enum value '" << RegNo
<< "'\n";
77 std::optional
<unsigned> getRegNo(StringRef RegName
) {
78 auto Iter
= RegNameToRegNo
.find(RegName
);
79 if (Iter
!= RegNameToRegNo
.end())
81 ErrorStream
<< "No register with name '" << RegName
<< "'\n";
86 void serializeIntegerOperand(raw_ostream
&OS
, int64_t Value
) {
88 OS
.write_hex(bit_cast
<uint64_t>(Value
));
91 bool tryDeserializeIntegerOperand(StringRef String
, int64_t &Value
) {
92 if (!String
.consume_front(kIntegerPrefix
))
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
))
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()));
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";
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";
143 unsigned getInstrOpcode(StringRef InstrName
) {
144 auto Iter
= OpcodeNameToOpcodeIdx
.find(InstrName
);
145 if (Iter
!= OpcodeNameToOpcodeIdx
.end())
147 ErrorStream
<< "No opcode with name '" << InstrName
<< "'\n";
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
;
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;
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;
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
,
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
);
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;
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
);
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
;
280 raw_string_ostream
OSS(Str
);
281 Binary
.writeAsBinary(OSS
);
283 Data
.assign(Str
.begin(), Str
.end());
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
);
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");
312 Io
.mapRequired("llvm_triple", Obj
.LLVMTriple
);
313 Io
.mapRequired("cpu_name", Obj
.CpuName
);
314 // Drop everything else.
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
327 Buffer
, nullptr, +[](const SMDiagnostic
&, void *Context
) {});
328 Yin
.setAllowUnknownKeys(true);
329 std::set
<TripleAndCpu
> Result
;
330 yaml::EmptyContext Context
;
331 while (Yin
.setCurrentDocument()) {
333 yamlize(Yin
, TC
, /*unused*/ true, Context
);
335 return errorCodeToError(Yin
.error());
343 Benchmark::readYaml(const LLVMState
&State
, MemoryBufferRef Buffer
) {
344 yaml::Input
Yin(Buffer
);
345 YamlContext
Context(State
);
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
);
364 return errorCodeToError(Yin
.error());
365 if (!Context
.getLastError().empty())
366 return make_error
<Failure
>(Context
.getLastError());
369 return std::move(Benchmarks
);
372 Error
Benchmark::writeYamlTo(const LLVMState
&State
,
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());
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
) {
399 assert(Key
== BM
.Key
);
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