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"
11 #include "llvm/ADT/STLExtras.h"
12 #include "llvm/ADT/ScopeExit.h"
13 #include "llvm/ADT/StringMap.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";
25 static constexpr llvm::StringLiteral
kNoRegister("%noreg");
31 // A mutable struct holding an LLVMState that can be passed through the
32 // serialization process to encode/decode registers and instructions.
34 YamlContext(const exegesis::LLVMState
&State
)
35 : State(&State
), ErrorStream(LastError
),
36 OpcodeNameToOpcodeIdx(
37 generateOpcodeNameToOpcodeIdxMapping(State
.getInstrInfo())),
38 RegNameToRegNo(generateRegNameToRegNoMapping(State
.getRegInfo())) {}
40 static llvm::StringMap
<unsigned>
41 generateOpcodeNameToOpcodeIdxMapping(const llvm::MCInstrInfo
&InstrInfo
) {
42 llvm::StringMap
<unsigned> Map(InstrInfo
.getNumOpcodes());
43 for (unsigned I
= 0, E
= InstrInfo
.getNumOpcodes(); I
< E
; ++I
)
44 Map
[InstrInfo
.getName(I
)] = I
;
45 assert(Map
.size() == InstrInfo
.getNumOpcodes() && "Size prediction failed");
49 llvm::StringMap
<unsigned>
50 generateRegNameToRegNoMapping(const llvm::MCRegisterInfo
&RegInfo
) {
51 llvm::StringMap
<unsigned> Map(RegInfo
.getNumRegs());
52 // Special-case RegNo 0, which would otherwise be spelled as ''.
54 for (unsigned I
= 1, E
= RegInfo
.getNumRegs(); I
< E
; ++I
)
55 Map
[RegInfo
.getName(I
)] = I
;
56 assert(Map
.size() == RegInfo
.getNumRegs() && "Size prediction failed");
60 void serializeMCInst(const llvm::MCInst
&MCInst
, llvm::raw_ostream
&OS
) {
61 OS
<< getInstrName(MCInst
.getOpcode());
62 for (const auto &Op
: MCInst
) {
64 serializeMCOperand(Op
, OS
);
68 void deserializeMCInst(llvm::StringRef String
, llvm::MCInst
&Value
) {
69 llvm::SmallVector
<llvm::StringRef
, 16> Pieces
;
70 String
.split(Pieces
, " ", /* MaxSplit */ -1, /* KeepEmpty */ false);
72 ErrorStream
<< "Unknown Instruction: '" << String
<< "'\n";
75 bool ProcessOpcode
= true;
76 for (llvm::StringRef Piece
: Pieces
) {
78 Value
.setOpcode(getInstrOpcode(Piece
));
80 Value
.addOperand(deserializeMCOperand(Piece
));
81 ProcessOpcode
= false;
85 std::string
&getLastError() { return ErrorStream
.str(); }
87 llvm::raw_string_ostream
&getErrorStream() { return ErrorStream
; }
89 llvm::StringRef
getRegName(unsigned RegNo
) {
90 // Special case: RegNo 0 is NoRegister. We have to deal with it explicitly.
93 const llvm::StringRef RegName
= State
->getRegInfo().getName(RegNo
);
95 ErrorStream
<< "No register with enum value '" << RegNo
<< "'\n";
99 llvm::Optional
<unsigned> getRegNo(llvm::StringRef RegName
) {
100 auto Iter
= RegNameToRegNo
.find(RegName
);
101 if (Iter
!= RegNameToRegNo
.end())
103 ErrorStream
<< "No register with name '" << RegName
<< "'\n";
108 void serializeIntegerOperand(llvm::raw_ostream
&OS
, int64_t Value
) {
109 OS
<< kIntegerPrefix
;
110 OS
.write_hex(llvm::bit_cast
<uint64_t>(Value
));
113 bool tryDeserializeIntegerOperand(llvm::StringRef String
, int64_t &Value
) {
114 if (!String
.consume_front(kIntegerPrefix
))
116 return !String
.consumeInteger(16, Value
);
119 void serializeFPOperand(llvm::raw_ostream
&OS
, double Value
) {
120 OS
<< kDoublePrefix
<< llvm::format("%la", Value
);
123 bool tryDeserializeFPOperand(llvm::StringRef String
, double &Value
) {
124 if (!String
.consume_front(kDoublePrefix
))
126 char *EndPointer
= nullptr;
127 Value
= strtod(String
.begin(), &EndPointer
);
128 return EndPointer
== String
.end();
131 void serializeMCOperand(const llvm::MCOperand
&MCOperand
,
132 llvm::raw_ostream
&OS
) {
133 if (MCOperand
.isReg()) {
134 OS
<< getRegName(MCOperand
.getReg());
135 } else if (MCOperand
.isImm()) {
136 serializeIntegerOperand(OS
, MCOperand
.getImm());
137 } else if (MCOperand
.isFPImm()) {
138 serializeFPOperand(OS
, MCOperand
.getFPImm());
140 OS
<< kInvalidOperand
;
144 llvm::MCOperand
deserializeMCOperand(llvm::StringRef String
) {
145 assert(!String
.empty());
146 int64_t IntValue
= 0;
147 double DoubleValue
= 0;
148 if (tryDeserializeIntegerOperand(String
, IntValue
))
149 return llvm::MCOperand::createImm(IntValue
);
150 if (tryDeserializeFPOperand(String
, DoubleValue
))
151 return llvm::MCOperand::createFPImm(DoubleValue
);
152 if (auto RegNo
= getRegNo(String
))
153 return llvm::MCOperand::createReg(*RegNo
);
154 if (String
!= kInvalidOperand
)
155 ErrorStream
<< "Unknown Operand: '" << String
<< "'\n";
159 llvm::StringRef
getInstrName(unsigned InstrNo
) {
160 const llvm::StringRef InstrName
= State
->getInstrInfo().getName(InstrNo
);
161 if (InstrName
.empty())
162 ErrorStream
<< "No opcode with enum value '" << InstrNo
<< "'\n";
166 unsigned getInstrOpcode(llvm::StringRef InstrName
) {
167 auto Iter
= OpcodeNameToOpcodeIdx
.find(InstrName
);
168 if (Iter
!= OpcodeNameToOpcodeIdx
.end())
170 ErrorStream
<< "No opcode with name '" << InstrName
<< "'\n";
174 const llvm::exegesis::LLVMState
*State
;
175 std::string LastError
;
176 llvm::raw_string_ostream ErrorStream
;
177 const llvm::StringMap
<unsigned> OpcodeNameToOpcodeIdx
;
178 const llvm::StringMap
<unsigned> RegNameToRegNo
;
182 // Defining YAML traits for IO.
185 static YamlContext
&getTypedContext(void *Ctx
) {
186 return *reinterpret_cast<YamlContext
*>(Ctx
);
189 // std::vector<llvm::MCInst> will be rendered as a list.
190 template <> struct SequenceElementTraits
<llvm::MCInst
> {
191 static const bool flow
= false;
194 template <> struct ScalarTraits
<llvm::MCInst
> {
196 static void output(const llvm::MCInst
&Value
, void *Ctx
,
197 llvm::raw_ostream
&Out
) {
198 getTypedContext(Ctx
).serializeMCInst(Value
, Out
);
201 static StringRef
input(StringRef Scalar
, void *Ctx
, llvm::MCInst
&Value
) {
202 YamlContext
&Context
= getTypedContext(Ctx
);
203 Context
.deserializeMCInst(Scalar
, Value
);
204 return Context
.getLastError();
207 // By default strings are quoted only when necessary.
208 // We force the use of single quotes for uniformity.
209 static QuotingType
mustQuote(StringRef
) { return QuotingType::Single
; }
211 static const bool flow
= true;
214 // std::vector<exegesis::Measure> will be rendered as a list.
215 template <> struct SequenceElementTraits
<exegesis::BenchmarkMeasure
> {
216 static const bool flow
= false;
219 // exegesis::Measure is rendererd as a flow instead of a list.
220 // e.g. { "key": "the key", "value": 0123 }
221 template <> struct MappingTraits
<exegesis::BenchmarkMeasure
> {
222 static void mapping(IO
&Io
, exegesis::BenchmarkMeasure
&Obj
) {
223 Io
.mapRequired("key", Obj
.Key
);
224 if (!Io
.outputting()) {
225 // For backward compatibility, interpret debug_string as a key.
226 Io
.mapOptional("debug_string", Obj
.Key
);
228 Io
.mapRequired("value", Obj
.PerInstructionValue
);
229 Io
.mapOptional("per_snippet_value", Obj
.PerSnippetValue
);
231 static const bool flow
= true;
235 struct ScalarEnumerationTraits
<exegesis::InstructionBenchmark::ModeE
> {
236 static void enumeration(IO
&Io
,
237 exegesis::InstructionBenchmark::ModeE
&Value
) {
238 Io
.enumCase(Value
, "", exegesis::InstructionBenchmark::Unknown
);
239 Io
.enumCase(Value
, "latency", exegesis::InstructionBenchmark::Latency
);
240 Io
.enumCase(Value
, "uops", exegesis::InstructionBenchmark::Uops
);
241 Io
.enumCase(Value
, "inverse_throughput",
242 exegesis::InstructionBenchmark::InverseThroughput
);
246 // std::vector<exegesis::RegisterValue> will be rendered as a list.
247 template <> struct SequenceElementTraits
<exegesis::RegisterValue
> {
248 static const bool flow
= false;
251 template <> struct ScalarTraits
<exegesis::RegisterValue
> {
252 static constexpr const unsigned kRadix
= 16;
253 static constexpr const bool kSigned
= false;
255 static void output(const exegesis::RegisterValue
&RV
, void *Ctx
,
256 llvm::raw_ostream
&Out
) {
257 YamlContext
&Context
= getTypedContext(Ctx
);
258 Out
<< Context
.getRegName(RV
.Register
) << "=0x"
259 << RV
.Value
.toString(kRadix
, kSigned
);
262 static StringRef
input(StringRef String
, void *Ctx
,
263 exegesis::RegisterValue
&RV
) {
264 llvm::SmallVector
<llvm::StringRef
, 2> Pieces
;
265 String
.split(Pieces
, "=0x", /* MaxSplit */ -1,
266 /* KeepEmpty */ false);
267 YamlContext
&Context
= getTypedContext(Ctx
);
268 llvm::Optional
<unsigned> RegNo
;
269 if (Pieces
.size() == 2 && (RegNo
= Context
.getRegNo(Pieces
[0]))) {
270 RV
.Register
= *RegNo
;
271 const unsigned BitsNeeded
= llvm::APInt::getBitsNeeded(Pieces
[1], kRadix
);
272 RV
.Value
= llvm::APInt(BitsNeeded
, Pieces
[1], kRadix
);
274 Context
.getErrorStream()
275 << "Unknown initial register value: '" << String
<< "'";
277 return Context
.getLastError();
280 static QuotingType
mustQuote(StringRef
) { return QuotingType::Single
; }
282 static const bool flow
= true;
286 struct MappingContextTraits
<exegesis::InstructionBenchmarkKey
, YamlContext
> {
287 static void mapping(IO
&Io
, exegesis::InstructionBenchmarkKey
&Obj
,
288 YamlContext
&Context
) {
289 Io
.setContext(&Context
);
290 Io
.mapRequired("instructions", Obj
.Instructions
);
291 Io
.mapOptional("config", Obj
.Config
);
292 Io
.mapRequired("register_initial_values", Obj
.RegisterInitialValues
);
297 struct MappingContextTraits
<exegesis::InstructionBenchmark
, 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
;
304 raw_string_ostream
OSS(Str
);
305 Binary
.writeAsBinary(OSS
);
307 Data
.assign(Str
.begin(), Str
.end());
314 static void mapping(IO
&Io
, exegesis::InstructionBenchmark
&Obj
,
315 YamlContext
&Context
) {
316 Io
.mapRequired("mode", Obj
.Mode
);
317 Io
.mapRequired("key", Obj
.Key
, Context
);
318 Io
.mapRequired("cpu_name", Obj
.CpuName
);
319 Io
.mapRequired("llvm_triple", Obj
.LLVMTriple
);
320 Io
.mapRequired("num_repetitions", Obj
.NumRepetitions
);
321 Io
.mapRequired("measurements", Obj
.Measurements
);
322 Io
.mapRequired("error", Obj
.Error
);
323 Io
.mapOptional("info", Obj
.Info
);
325 MappingNormalization
<NormalizedBinary
, std::vector
<uint8_t>> BinaryString(
326 Io
, Obj
.AssembledSnippet
);
327 Io
.mapOptional("assembled_snippet", BinaryString
->Binary
);
335 llvm::Expected
<InstructionBenchmark
>
336 InstructionBenchmark::readYaml(const LLVMState
&State
,
337 llvm::StringRef Filename
) {
338 if (auto ExpectedMemoryBuffer
=
339 llvm::errorOrToExpected(llvm::MemoryBuffer::getFile(Filename
))) {
340 llvm::yaml::Input
Yin(*ExpectedMemoryBuffer
.get());
341 YamlContext
Context(State
);
342 InstructionBenchmark Benchmark
;
343 if (Yin
.setCurrentDocument())
344 llvm::yaml::yamlize(Yin
, Benchmark
, /*unused*/ true, Context
);
345 if (!Context
.getLastError().empty())
346 return llvm::make_error
<BenchmarkFailure
>(Context
.getLastError());
349 return ExpectedMemoryBuffer
.takeError();
353 llvm::Expected
<std::vector
<InstructionBenchmark
>>
354 InstructionBenchmark::readYamls(const LLVMState
&State
,
355 llvm::StringRef Filename
) {
356 if (auto ExpectedMemoryBuffer
=
357 llvm::errorOrToExpected(llvm::MemoryBuffer::getFile(Filename
))) {
358 llvm::yaml::Input
Yin(*ExpectedMemoryBuffer
.get());
359 YamlContext
Context(State
);
360 std::vector
<InstructionBenchmark
> Benchmarks
;
361 while (Yin
.setCurrentDocument()) {
362 Benchmarks
.emplace_back();
363 yamlize(Yin
, Benchmarks
.back(), /*unused*/ true, Context
);
365 return llvm::errorCodeToError(Yin
.error());
366 if (!Context
.getLastError().empty())
367 return llvm::make_error
<BenchmarkFailure
>(Context
.getLastError());
372 return ExpectedMemoryBuffer
.takeError();
376 llvm::Error
InstructionBenchmark::writeYamlTo(const LLVMState
&State
,
377 llvm::raw_ostream
&OS
) {
378 auto Cleanup
= make_scope_exit([&] { OS
.flush(); });
379 llvm::yaml::Output
Yout(OS
, nullptr /*Ctx*/, 200 /*WrapColumn*/);
380 YamlContext
Context(State
);
381 Yout
.beginDocuments();
382 llvm::yaml::yamlize(Yout
, *this, /*unused*/ true, Context
);
383 if (!Context
.getLastError().empty())
384 return llvm::make_error
<BenchmarkFailure
>(Context
.getLastError());
386 return Error::success();
389 llvm::Error
InstructionBenchmark::readYamlFrom(const LLVMState
&State
,
390 llvm::StringRef InputContent
) {
391 llvm::yaml::Input
Yin(InputContent
);
392 YamlContext
Context(State
);
393 if (Yin
.setCurrentDocument())
394 llvm::yaml::yamlize(Yin
, *this, /*unused*/ true, Context
);
395 if (!Context
.getLastError().empty())
396 return llvm::make_error
<BenchmarkFailure
>(Context
.getLastError());
397 return Error::success();
400 llvm::Error
InstructionBenchmark::writeYaml(const LLVMState
&State
,
401 const llvm::StringRef Filename
) {
402 if (Filename
== "-") {
403 if (auto Err
= writeYamlTo(State
, llvm::outs()))
407 if (auto E
= llvm::errorCodeToError(
408 openFileForWrite(Filename
, ResultFD
, llvm::sys::fs::CD_CreateAlways
,
409 llvm::sys::fs::OF_Text
))) {
412 llvm::raw_fd_ostream
Ostr(ResultFD
, true /*shouldClose*/);
413 if (auto Err
= writeYamlTo(State
, Ostr
))
416 return llvm::Error::success();
419 void PerInstructionStats::push(const BenchmarkMeasure
&BM
) {
422 assert(Key
== BM
.Key
);
424 SumValues
+= BM
.PerInstructionValue
;
425 MaxValue
= std::max(MaxValue
, BM
.PerInstructionValue
);
426 MinValue
= std::min(MinValue
, BM
.PerInstructionValue
);
429 } // namespace exegesis