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/StringMap.h"
15 #include "llvm/ADT/StringRef.h"
16 #include "llvm/ADT/bit.h"
17 #include "llvm/ObjectYAML/YAML.h"
18 #include "llvm/Support/FileOutputBuffer.h"
19 #include "llvm/Support/FileSystem.h"
20 #include "llvm/Support/Format.h"
21 #include "llvm/Support/raw_ostream.h"
23 static constexpr const char kIntegerPrefix
[] = "i_0x";
24 static constexpr const char kDoublePrefix
[] = "f_";
25 static constexpr const char kInvalidOperand
[] = "INVALID";
26 static constexpr llvm::StringLiteral
kNoRegister("%noreg");
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(
38 generateOpcodeNameToOpcodeIdxMapping(State
.getInstrInfo())),
39 RegNameToRegNo(generateRegNameToRegNoMapping(State
.getRegInfo())) {}
41 static StringMap
<unsigned>
42 generateOpcodeNameToOpcodeIdxMapping(const MCInstrInfo
&InstrInfo
) {
43 StringMap
<unsigned> Map(InstrInfo
.getNumOpcodes());
44 for (unsigned I
= 0, E
= InstrInfo
.getNumOpcodes(); I
< E
; ++I
)
45 Map
[InstrInfo
.getName(I
)] = I
;
46 assert(Map
.size() == InstrInfo
.getNumOpcodes() && "Size prediction failed");
51 generateRegNameToRegNoMapping(const MCRegisterInfo
&RegInfo
) {
52 StringMap
<unsigned> Map(RegInfo
.getNumRegs());
53 // Special-case RegNo 0, which would otherwise be spelled as ''.
55 for (unsigned I
= 1, E
= RegInfo
.getNumRegs(); I
< E
; ++I
)
56 Map
[RegInfo
.getName(I
)] = I
;
57 assert(Map
.size() == RegInfo
.getNumRegs() && "Size prediction failed");
61 void serializeMCInst(const MCInst
&MCInst
, raw_ostream
&OS
) {
62 OS
<< getInstrName(MCInst
.getOpcode());
63 for (const auto &Op
: MCInst
) {
65 serializeMCOperand(Op
, OS
);
69 void deserializeMCInst(StringRef String
, MCInst
&Value
) {
70 SmallVector
<StringRef
, 16> Pieces
;
71 String
.split(Pieces
, " ", /* MaxSplit */ -1, /* KeepEmpty */ false);
73 ErrorStream
<< "Unknown Instruction: '" << String
<< "'\n";
76 bool ProcessOpcode
= true;
77 for (StringRef Piece
: Pieces
) {
79 Value
.setOpcode(getInstrOpcode(Piece
));
81 Value
.addOperand(deserializeMCOperand(Piece
));
82 ProcessOpcode
= false;
86 std::string
&getLastError() { return ErrorStream
.str(); }
88 raw_string_ostream
&getErrorStream() { return ErrorStream
; }
90 StringRef
getRegName(unsigned RegNo
) {
91 // Special case: RegNo 0 is NoRegister. We have to deal with it explicitly.
94 const StringRef RegName
= State
->getRegInfo().getName(RegNo
);
96 ErrorStream
<< "No register with enum value '" << RegNo
<< "'\n";
100 Optional
<unsigned> getRegNo(StringRef RegName
) {
101 auto Iter
= RegNameToRegNo
.find(RegName
);
102 if (Iter
!= RegNameToRegNo
.end())
104 ErrorStream
<< "No register with name '" << RegName
<< "'\n";
109 void serializeIntegerOperand(raw_ostream
&OS
, int64_t Value
) {
110 OS
<< kIntegerPrefix
;
111 OS
.write_hex(bit_cast
<uint64_t>(Value
));
114 bool tryDeserializeIntegerOperand(StringRef String
, int64_t &Value
) {
115 if (!String
.consume_front(kIntegerPrefix
))
117 return !String
.consumeInteger(16, Value
);
120 void serializeFPOperand(raw_ostream
&OS
, double Value
) {
121 OS
<< kDoublePrefix
<< format("%la", Value
);
124 bool tryDeserializeFPOperand(StringRef String
, double &Value
) {
125 if (!String
.consume_front(kDoublePrefix
))
127 char *EndPointer
= nullptr;
128 Value
= strtod(String
.begin(), &EndPointer
);
129 return EndPointer
== String
.end();
132 void serializeMCOperand(const MCOperand
&MCOperand
, 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 MCOperand
deserializeMCOperand(StringRef String
) {
145 assert(!String
.empty());
146 int64_t IntValue
= 0;
147 double DoubleValue
= 0;
148 if (tryDeserializeIntegerOperand(String
, IntValue
))
149 return MCOperand::createImm(IntValue
);
150 if (tryDeserializeFPOperand(String
, DoubleValue
))
151 return MCOperand::createFPImm(DoubleValue
);
152 if (auto RegNo
= getRegNo(String
))
153 return MCOperand::createReg(*RegNo
);
154 if (String
!= kInvalidOperand
)
155 ErrorStream
<< "Unknown Operand: '" << String
<< "'\n";
159 StringRef
getInstrName(unsigned InstrNo
) {
160 const StringRef InstrName
= State
->getInstrInfo().getName(InstrNo
);
161 if (InstrName
.empty())
162 ErrorStream
<< "No opcode with enum value '" << InstrNo
<< "'\n";
166 unsigned getInstrOpcode(StringRef InstrName
) {
167 auto Iter
= OpcodeNameToOpcodeIdx
.find(InstrName
);
168 if (Iter
!= OpcodeNameToOpcodeIdx
.end())
170 ErrorStream
<< "No opcode with name '" << InstrName
<< "'\n";
174 const exegesis::LLVMState
*State
;
175 std::string LastError
;
176 raw_string_ostream ErrorStream
;
177 const StringMap
<unsigned> OpcodeNameToOpcodeIdx
;
178 const StringMap
<unsigned> RegNameToRegNo
;
182 // Defining YAML traits for IO.
185 static YamlContext
&getTypedContext(void *Ctx
) {
186 return *reinterpret_cast<YamlContext
*>(Ctx
);
189 // std::vector<MCInst> will be rendered as a list.
190 template <> struct SequenceElementTraits
<MCInst
> {
191 static const bool flow
= false;
194 template <> struct ScalarTraits
<MCInst
> {
196 static void output(const MCInst
&Value
, void *Ctx
, raw_ostream
&Out
) {
197 getTypedContext(Ctx
).serializeMCInst(Value
, Out
);
200 static StringRef
input(StringRef Scalar
, void *Ctx
, MCInst
&Value
) {
201 YamlContext
&Context
= getTypedContext(Ctx
);
202 Context
.deserializeMCInst(Scalar
, Value
);
203 return Context
.getLastError();
206 // By default strings are quoted only when necessary.
207 // We force the use of single quotes for uniformity.
208 static QuotingType
mustQuote(StringRef
) { return QuotingType::Single
; }
210 static const bool flow
= true;
213 // std::vector<exegesis::Measure> will be rendered as a list.
214 template <> struct SequenceElementTraits
<exegesis::BenchmarkMeasure
> {
215 static const bool flow
= false;
218 // exegesis::Measure is rendererd as a flow instead of a list.
219 // e.g. { "key": "the key", "value": 0123 }
220 template <> struct MappingTraits
<exegesis::BenchmarkMeasure
> {
221 static void mapping(IO
&Io
, exegesis::BenchmarkMeasure
&Obj
) {
222 Io
.mapRequired("key", Obj
.Key
);
223 if (!Io
.outputting()) {
224 // For backward compatibility, interpret debug_string as a key.
225 Io
.mapOptional("debug_string", Obj
.Key
);
227 Io
.mapRequired("value", Obj
.PerInstructionValue
);
228 Io
.mapOptional("per_snippet_value", Obj
.PerSnippetValue
);
230 static const bool flow
= true;
234 struct ScalarEnumerationTraits
<exegesis::InstructionBenchmark::ModeE
> {
235 static void enumeration(IO
&Io
,
236 exegesis::InstructionBenchmark::ModeE
&Value
) {
237 Io
.enumCase(Value
, "", exegesis::InstructionBenchmark::Unknown
);
238 Io
.enumCase(Value
, "latency", exegesis::InstructionBenchmark::Latency
);
239 Io
.enumCase(Value
, "uops", exegesis::InstructionBenchmark::Uops
);
240 Io
.enumCase(Value
, "inverse_throughput",
241 exegesis::InstructionBenchmark::InverseThroughput
);
245 // std::vector<exegesis::RegisterValue> will be rendered as a list.
246 template <> struct SequenceElementTraits
<exegesis::RegisterValue
> {
247 static const bool flow
= false;
250 template <> struct ScalarTraits
<exegesis::RegisterValue
> {
251 static constexpr const unsigned kRadix
= 16;
252 static constexpr const bool kSigned
= false;
254 static void output(const exegesis::RegisterValue
&RV
, void *Ctx
,
256 YamlContext
&Context
= getTypedContext(Ctx
);
257 Out
<< Context
.getRegName(RV
.Register
) << "=0x"
258 << RV
.Value
.toString(kRadix
, kSigned
);
261 static StringRef
input(StringRef String
, void *Ctx
,
262 exegesis::RegisterValue
&RV
) {
263 SmallVector
<StringRef
, 2> Pieces
;
264 String
.split(Pieces
, "=0x", /* MaxSplit */ -1,
265 /* KeepEmpty */ false);
266 YamlContext
&Context
= getTypedContext(Ctx
);
267 Optional
<unsigned> RegNo
;
268 if (Pieces
.size() == 2 && (RegNo
= Context
.getRegNo(Pieces
[0]))) {
269 RV
.Register
= *RegNo
;
270 const unsigned BitsNeeded
= APInt::getBitsNeeded(Pieces
[1], kRadix
);
271 RV
.Value
= APInt(BitsNeeded
, Pieces
[1], kRadix
);
273 Context
.getErrorStream()
274 << "Unknown initial register value: '" << String
<< "'";
276 return Context
.getLastError();
279 static QuotingType
mustQuote(StringRef
) { return QuotingType::Single
; }
281 static const bool flow
= true;
285 struct MappingContextTraits
<exegesis::InstructionBenchmarkKey
, YamlContext
> {
286 static void mapping(IO
&Io
, exegesis::InstructionBenchmarkKey
&Obj
,
287 YamlContext
&Context
) {
288 Io
.setContext(&Context
);
289 Io
.mapRequired("instructions", Obj
.Instructions
);
290 Io
.mapOptional("config", Obj
.Config
);
291 Io
.mapRequired("register_initial_values", Obj
.RegisterInitialValues
);
296 struct MappingContextTraits
<exegesis::InstructionBenchmark
, YamlContext
> {
297 struct NormalizedBinary
{
298 NormalizedBinary(IO
&io
) {}
299 NormalizedBinary(IO
&, std::vector
<uint8_t> &Data
) : Binary(Data
) {}
300 std::vector
<uint8_t> denormalize(IO
&) {
301 std::vector
<uint8_t> Data
;
303 raw_string_ostream
OSS(Str
);
304 Binary
.writeAsBinary(OSS
);
306 Data
.assign(Str
.begin(), Str
.end());
313 static void mapping(IO
&Io
, exegesis::InstructionBenchmark
&Obj
,
314 YamlContext
&Context
) {
315 Io
.mapRequired("mode", Obj
.Mode
);
316 Io
.mapRequired("key", Obj
.Key
, Context
);
317 Io
.mapRequired("cpu_name", Obj
.CpuName
);
318 Io
.mapRequired("llvm_triple", Obj
.LLVMTriple
);
319 Io
.mapRequired("num_repetitions", Obj
.NumRepetitions
);
320 Io
.mapRequired("measurements", Obj
.Measurements
);
321 Io
.mapRequired("error", Obj
.Error
);
322 Io
.mapOptional("info", Obj
.Info
);
324 MappingNormalization
<NormalizedBinary
, std::vector
<uint8_t>> BinaryString(
325 Io
, Obj
.AssembledSnippet
);
326 Io
.mapOptional("assembled_snippet", BinaryString
->Binary
);
334 Expected
<InstructionBenchmark
>
335 InstructionBenchmark::readYaml(const LLVMState
&State
, StringRef Filename
) {
336 if (auto ExpectedMemoryBuffer
=
337 errorOrToExpected(MemoryBuffer::getFile(Filename
))) {
338 yaml::Input
Yin(*ExpectedMemoryBuffer
.get());
339 YamlContext
Context(State
);
340 InstructionBenchmark Benchmark
;
341 if (Yin
.setCurrentDocument())
342 yaml::yamlize(Yin
, Benchmark
, /*unused*/ true, Context
);
343 if (!Context
.getLastError().empty())
344 return make_error
<Failure
>(Context
.getLastError());
347 return ExpectedMemoryBuffer
.takeError();
351 Expected
<std::vector
<InstructionBenchmark
>>
352 InstructionBenchmark::readYamls(const LLVMState
&State
, StringRef Filename
) {
353 if (auto ExpectedMemoryBuffer
=
354 errorOrToExpected(MemoryBuffer::getFile(Filename
))) {
355 yaml::Input
Yin(*ExpectedMemoryBuffer
.get());
356 YamlContext
Context(State
);
357 std::vector
<InstructionBenchmark
> Benchmarks
;
358 while (Yin
.setCurrentDocument()) {
359 Benchmarks
.emplace_back();
360 yamlize(Yin
, Benchmarks
.back(), /*unused*/ true, Context
);
362 return errorCodeToError(Yin
.error());
363 if (!Context
.getLastError().empty())
364 return make_error
<Failure
>(Context
.getLastError());
369 return ExpectedMemoryBuffer
.takeError();
373 Error
InstructionBenchmark::writeYamlTo(const LLVMState
&State
,
375 auto Cleanup
= make_scope_exit([&] { OS
.flush(); });
376 yaml::Output
Yout(OS
, nullptr /*Ctx*/, 200 /*WrapColumn*/);
377 YamlContext
Context(State
);
378 Yout
.beginDocuments();
379 yaml::yamlize(Yout
, *this, /*unused*/ true, Context
);
380 if (!Context
.getLastError().empty())
381 return make_error
<Failure
>(Context
.getLastError());
383 return Error::success();
386 Error
InstructionBenchmark::readYamlFrom(const LLVMState
&State
,
387 StringRef InputContent
) {
388 yaml::Input
Yin(InputContent
);
389 YamlContext
Context(State
);
390 if (Yin
.setCurrentDocument())
391 yaml::yamlize(Yin
, *this, /*unused*/ true, Context
);
392 if (!Context
.getLastError().empty())
393 return make_error
<Failure
>(Context
.getLastError());
394 return Error::success();
397 Error
InstructionBenchmark::writeYaml(const LLVMState
&State
,
398 const StringRef Filename
) {
399 if (Filename
== "-") {
400 if (auto Err
= writeYamlTo(State
, outs()))
404 if (auto E
= errorCodeToError(openFileForWrite(
405 Filename
, ResultFD
, sys::fs::CD_CreateAlways
, sys::fs::OF_Text
))) {
408 raw_fd_ostream
Ostr(ResultFD
, true /*shouldClose*/);
409 if (auto Err
= writeYamlTo(State
, Ostr
))
412 return Error::success();
415 void PerInstructionStats::push(const BenchmarkMeasure
&BM
) {
418 assert(Key
== BM
.Key
);
420 SumValues
+= BM
.PerInstructionValue
;
421 MaxValue
= std::max(MaxValue
, BM
.PerInstructionValue
);
422 MinValue
= std::min(MinValue
, BM
.PerInstructionValue
);
425 } // namespace exegesis