1 //===-- BenchmarkRunner.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 //===----------------------------------------------------------------------===//
13 #include "Assembler.h"
14 #include "BenchmarkRunner.h"
16 #include "MCInstrDescView.h"
17 #include "PerfHelper.h"
19 #include "llvm/ADT/ScopeExit.h"
20 #include "llvm/ADT/StringExtras.h"
21 #include "llvm/ADT/StringRef.h"
22 #include "llvm/ADT/Twine.h"
23 #include "llvm/Support/CrashRecoveryContext.h"
24 #include "llvm/Support/Error.h"
25 #include "llvm/Support/FileSystem.h"
26 #include "llvm/Support/MemoryBuffer.h"
27 #include "llvm/Support/Program.h"
32 BenchmarkRunner::BenchmarkRunner(const LLVMState
&State
,
33 InstructionBenchmark::ModeE Mode
)
34 : State(State
), Mode(Mode
), Scratch(std::make_unique
<ScratchSpace
>()) {}
36 BenchmarkRunner::~BenchmarkRunner() = default;
39 class FunctionExecutorImpl
: public BenchmarkRunner::FunctionExecutor
{
41 FunctionExecutorImpl(const LLVMState
&State
,
42 object::OwningBinary
<object::ObjectFile
> Obj
,
43 BenchmarkRunner::ScratchSpace
*Scratch
)
44 : State(State
), Function(State
.createTargetMachine(), std::move(Obj
)),
48 Expected
<int64_t> runAndMeasure(const char *Counters
) const override
{
49 auto ResultOrError
= runAndSample(Counters
);
51 return ResultOrError
.get()[0];
52 return ResultOrError
.takeError();
56 accumulateCounterValues(const llvm::SmallVector
<int64_t, 4> &NewValues
,
57 llvm::SmallVector
<int64_t, 4> *Result
) {
58 const size_t NumValues
= std::max(NewValues
.size(), Result
->size());
59 if (NumValues
> Result
->size())
60 Result
->resize(NumValues
, 0);
61 for (size_t I
= 0, End
= NewValues
.size(); I
< End
; ++I
)
62 (*Result
)[I
] += NewValues
[I
];
65 Expected
<llvm::SmallVector
<int64_t, 4>>
66 runAndSample(const char *Counters
) const override
{
67 // We sum counts when there are several counters for a single ProcRes
68 // (e.g. P23 on SandyBridge).
69 llvm::SmallVector
<int64_t, 4> CounterValues
;
71 SmallVector
<StringRef
, 2> CounterNames
;
72 StringRef(Counters
).split(CounterNames
, '+');
73 char *const ScratchPtr
= Scratch
->ptr();
74 const ExegesisTarget
&ET
= State
.getExegesisTarget();
75 for (auto &CounterName
: CounterNames
) {
76 CounterName
= CounterName
.trim();
77 auto CounterOrError
= ET
.createCounter(CounterName
, State
);
80 return CounterOrError
.takeError();
82 pfm::Counter
*Counter
= CounterOrError
.get().get();
84 Reserved
= Counter
->numValues();
85 CounterValues
.reserve(Reserved
);
86 } else if (Reserved
!= Counter
->numValues())
87 // It'd be wrong to accumulate vectors of different sizes.
88 return make_error
<Failure
>(
89 llvm::Twine("Inconsistent number of values for counter ")
91 .concat(std::to_string(Counter
->numValues()))
92 .concat(" vs expected of ")
93 .concat(std::to_string(Reserved
)));
96 auto PS
= ET
.withSavedState();
97 CrashRecoveryContext CRC
;
98 CrashRecoveryContext::Enable();
99 const bool Crashed
= !CRC
.RunSafely([this, Counter
, ScratchPtr
]() {
101 this->Function(ScratchPtr
);
104 CrashRecoveryContext::Disable();
107 std::string Msg
= "snippet crashed while running";
109 // See "Exit Status for Commands":
110 // https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xcu_chap02.html
111 constexpr const int kSigOffset
= 128;
112 if (const char *const SigName
= strsignal(CRC
.RetCode
- kSigOffset
)) {
117 return make_error
<SnippetCrash
>(std::move(Msg
));
121 auto ValueOrError
= Counter
->readOrError(Function
.getFunctionBytes());
123 return ValueOrError
.takeError();
124 accumulateCounterValues(ValueOrError
.get(), &CounterValues
);
126 return CounterValues
;
129 const LLVMState
&State
;
130 const ExecutableFunction Function
;
131 BenchmarkRunner::ScratchSpace
*const Scratch
;
135 Expected
<InstructionBenchmark
> BenchmarkRunner::runConfiguration(
136 const BenchmarkCode
&BC
, unsigned NumRepetitions
, unsigned LoopBodySize
,
137 ArrayRef
<std::unique_ptr
<const SnippetRepetitor
>> Repetitors
,
138 bool DumpObjectToDisk
) const {
139 InstructionBenchmark InstrBenchmark
;
140 InstrBenchmark
.Mode
= Mode
;
141 InstrBenchmark
.CpuName
= std::string(State
.getTargetMachine().getTargetCPU());
142 InstrBenchmark
.LLVMTriple
=
143 State
.getTargetMachine().getTargetTriple().normalize();
144 InstrBenchmark
.NumRepetitions
= NumRepetitions
;
145 InstrBenchmark
.Info
= BC
.Info
;
147 const std::vector
<MCInst
> &Instructions
= BC
.Key
.Instructions
;
149 InstrBenchmark
.Key
= BC
.Key
;
151 // If we end up having an error, and we've previously succeeded with
152 // some other Repetitor, we want to discard the previous measurements.
153 struct ClearBenchmarkOnReturn
{
154 ClearBenchmarkOnReturn(InstructionBenchmark
*IB
) : IB(IB
) {}
155 ~ClearBenchmarkOnReturn() {
157 IB
->Measurements
.clear();
159 void disarm() { Clear
= false; }
162 InstructionBenchmark
*const IB
;
165 ClearBenchmarkOnReturn
CBOR(&InstrBenchmark
);
167 for (const std::unique_ptr
<const SnippetRepetitor
> &Repetitor
: Repetitors
) {
168 // Assemble at least kMinInstructionsForSnippet instructions by repeating
169 // the snippet for debug/analysis. This is so that the user clearly
170 // understands that the inside instructions are repeated.
171 const int MinInstructionsForSnippet
= 4 * Instructions
.size();
172 const int LoopBodySizeForSnippet
= 2 * Instructions
.size();
174 SmallString
<0> Buffer
;
175 raw_svector_ostream
OS(Buffer
);
176 if (Error E
= assembleToStream(
177 State
.getExegesisTarget(), State
.createTargetMachine(),
178 BC
.LiveIns
, BC
.Key
.RegisterInitialValues
,
179 Repetitor
->Repeat(Instructions
, MinInstructionsForSnippet
,
180 LoopBodySizeForSnippet
),
184 const ExecutableFunction
EF(State
.createTargetMachine(),
185 getObjectFromBuffer(OS
.str()));
186 const auto FnBytes
= EF
.getFunctionBytes();
187 llvm::append_range(InstrBenchmark
.AssembledSnippet
, FnBytes
);
190 // Assemble NumRepetitions instructions repetitions of the snippet for
192 const auto Filler
= Repetitor
->Repeat(
193 Instructions
, InstrBenchmark
.NumRepetitions
, LoopBodySize
);
195 object::OwningBinary
<object::ObjectFile
> ObjectFile
;
196 if (DumpObjectToDisk
) {
197 auto ObjectFilePath
= writeObjectFile(BC
, Filler
);
198 if (Error E
= ObjectFilePath
.takeError()) {
199 InstrBenchmark
.Error
= toString(std::move(E
));
200 return InstrBenchmark
;
202 outs() << "Check generated assembly with: /usr/bin/objdump -d "
203 << *ObjectFilePath
<< "\n";
204 ObjectFile
= getObjectFromFile(*ObjectFilePath
);
206 SmallString
<0> Buffer
;
207 raw_svector_ostream
OS(Buffer
);
208 if (Error E
= assembleToStream(
209 State
.getExegesisTarget(), State
.createTargetMachine(),
210 BC
.LiveIns
, BC
.Key
.RegisterInitialValues
, Filler
, OS
)) {
213 ObjectFile
= getObjectFromBuffer(OS
.str());
216 const FunctionExecutorImpl
Executor(State
, std::move(ObjectFile
),
218 auto NewMeasurements
= runMeasurements(Executor
);
219 if (Error E
= NewMeasurements
.takeError()) {
220 if (!E
.isA
<SnippetCrash
>())
222 InstrBenchmark
.Error
= toString(std::move(E
));
223 return InstrBenchmark
;
225 assert(InstrBenchmark
.NumRepetitions
> 0 && "invalid NumRepetitions");
226 for (BenchmarkMeasure
&BM
: *NewMeasurements
) {
227 // Scale the measurements by instruction.
228 BM
.PerInstructionValue
/= InstrBenchmark
.NumRepetitions
;
229 // Scale the measurements by snippet.
230 BM
.PerSnippetValue
*= static_cast<double>(Instructions
.size()) /
231 InstrBenchmark
.NumRepetitions
;
233 if (InstrBenchmark
.Measurements
.empty()) {
234 InstrBenchmark
.Measurements
= std::move(*NewMeasurements
);
238 assert(Repetitors
.size() > 1 && !InstrBenchmark
.Measurements
.empty() &&
239 "We're in an 'min' repetition mode, and need to aggregate new "
240 "result to the existing result.");
241 assert(InstrBenchmark
.Measurements
.size() == NewMeasurements
->size() &&
242 "Expected to have identical number of measurements.");
243 for (auto I
: zip(InstrBenchmark
.Measurements
, *NewMeasurements
)) {
244 BenchmarkMeasure
&Measurement
= std::get
<0>(I
);
245 BenchmarkMeasure
&NewMeasurement
= std::get
<1>(I
);
246 assert(Measurement
.Key
== NewMeasurement
.Key
&&
247 "Expected measurements to be symmetric");
249 Measurement
.PerInstructionValue
= std::min(
250 Measurement
.PerInstructionValue
, NewMeasurement
.PerInstructionValue
);
251 Measurement
.PerSnippetValue
=
252 std::min(Measurement
.PerSnippetValue
, NewMeasurement
.PerSnippetValue
);
256 // We successfully measured everything, so don't discard the results.
258 return InstrBenchmark
;
261 Expected
<std::string
>
262 BenchmarkRunner::writeObjectFile(const BenchmarkCode
&BC
,
263 const FillFunction
&FillFunction
) const {
265 SmallString
<256> ResultPath
;
266 if (Error E
= errorCodeToError(
267 sys::fs::createTemporaryFile("snippet", "o", ResultFD
, ResultPath
)))
269 raw_fd_ostream
OFS(ResultFD
, true /*ShouldClose*/);
270 if (Error E
= assembleToStream(
271 State
.getExegesisTarget(), State
.createTargetMachine(), BC
.LiveIns
,
272 BC
.Key
.RegisterInitialValues
, FillFunction
, OFS
)) {
275 return std::string(ResultPath
.str());
278 BenchmarkRunner::FunctionExecutor::~FunctionExecutor() {}
280 } // namespace exegesis