1 //===-- BenchmarkRunner.cpp -------------------------------------*- C++ -*-===//
2 //
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
6 //
7 //===----------------------------------------------------------------------===//
8
9 #include <array>
10 #include <memory>
11 #include <string>
12
13 #include "Assembler.h"
14 #include "BenchmarkRunner.h"
15 #include "Error.h"
16 #include "MCInstrDescView.h"
17 #include "PerfHelper.h"
18 #include "Target.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"
28
29 namespace llvm {
30 namespace exegesis {
31
BenchmarkRunner(const LLVMState & State,InstructionBenchmark::ModeE Mode,BenchmarkPhaseSelectorE BenchmarkPhaseSelector)32 BenchmarkRunner::BenchmarkRunner(const LLVMState &State,
33 InstructionBenchmark::ModeE Mode,
34 BenchmarkPhaseSelectorE BenchmarkPhaseSelector)
35 : State(State), Mode(Mode), BenchmarkPhaseSelector(BenchmarkPhaseSelector),
36 Scratch(std::make_unique<ScratchSpace>()) {}
37
38 BenchmarkRunner::~BenchmarkRunner() = default;
39
40 namespace {
41 class FunctionExecutorImpl : public BenchmarkRunner::FunctionExecutor {
42 public:
FunctionExecutorImpl(const LLVMState & State,object::OwningBinary<object::ObjectFile> Obj,BenchmarkRunner::ScratchSpace * Scratch)43 FunctionExecutorImpl(const LLVMState &State,
44 object::OwningBinary<object::ObjectFile> Obj,
45 BenchmarkRunner::ScratchSpace *Scratch)
46 : State(State), Function(State.createTargetMachine(), std::move(Obj)),
47 Scratch(Scratch) {}
48
49 private:
runAndMeasure(const char * Counters) const50 Expected<int64_t> runAndMeasure(const char *Counters) const override {
51 auto ResultOrError = runAndSample(Counters);
52 if (ResultOrError)
53 return ResultOrError.get()[0];
54 return ResultOrError.takeError();
55 }
56
57 static void
accumulateCounterValues(const llvm::SmallVector<int64_t,4> & NewValues,llvm::SmallVector<int64_t,4> * Result)58 accumulateCounterValues(const llvm::SmallVector<int64_t, 4> &NewValues,
59 llvm::SmallVector<int64_t, 4> *Result) {
60 const size_t NumValues = std::max(NewValues.size(), Result->size());
61 if (NumValues > Result->size())
62 Result->resize(NumValues, 0);
63 for (size_t I = 0, End = NewValues.size(); I < End; ++I)
64 (*Result)[I] += NewValues[I];
65 }
66
67 Expected<llvm::SmallVector<int64_t, 4>>
runAndSample(const char * Counters) const68 runAndSample(const char *Counters) const override {
69 // We sum counts when there are several counters for a single ProcRes
70 // (e.g. P23 on SandyBridge).
71 llvm::SmallVector<int64_t, 4> CounterValues;
72 int Reserved = 0;
73 SmallVector<StringRef, 2> CounterNames;
74 StringRef(Counters).split(CounterNames, '+');
75 char *const ScratchPtr = Scratch->ptr();
76 const ExegesisTarget &ET = State.getExegesisTarget();
77 for (auto &CounterName : CounterNames) {
78 CounterName = CounterName.trim();
79 auto CounterOrError = ET.createCounter(CounterName, State);
80
81 if (!CounterOrError)
82 return CounterOrError.takeError();
83
84 pfm::Counter *Counter = CounterOrError.get().get();
85 if (Reserved == 0) {
86 Reserved = Counter->numValues();
87 CounterValues.reserve(Reserved);
88 } else if (Reserved != Counter->numValues())
89 // It'd be wrong to accumulate vectors of different sizes.
90 return make_error<Failure>(
91 llvm::Twine("Inconsistent number of values for counter ")
92 .concat(CounterName)
93 .concat(std::to_string(Counter->numValues()))
94 .concat(" vs expected of ")
95 .concat(std::to_string(Reserved)));
96 Scratch->clear();
97 {
98 auto PS = ET.withSavedState();
99 CrashRecoveryContext CRC;
100 CrashRecoveryContext::Enable();
101 const bool Crashed = !CRC.RunSafely([this, Counter, ScratchPtr]() {
102 Counter->start();
103 this->Function(ScratchPtr);
104 Counter->stop();
105 });
106 CrashRecoveryContext::Disable();
107 PS.reset();
108 if (Crashed) {
109 std::string Msg = "snippet crashed while running";
110 #ifdef LLVM_ON_UNIX
111 // See "Exit Status for Commands":
112 // https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xcu_chap02.html
113 constexpr const int kSigOffset = 128;
114 if (const char *const SigName = strsignal(CRC.RetCode - kSigOffset)) {
115 Msg += ": ";
116 Msg += SigName;
117 }
118 #endif
119 return make_error<SnippetCrash>(std::move(Msg));
120 }
121 }
122
123 auto ValueOrError = Counter->readOrError(Function.getFunctionBytes());
124 if (!ValueOrError)
125 return ValueOrError.takeError();
126 accumulateCounterValues(ValueOrError.get(), &CounterValues);
127 }
128 return CounterValues;
129 }
130
131 const LLVMState &State;
132 const ExecutableFunction Function;
133 BenchmarkRunner::ScratchSpace *const Scratch;
134 };
135 } // namespace
136
assembleSnippet(const BenchmarkCode & BC,const SnippetRepetitor & Repetitor,unsigned MinInstructions,unsigned LoopBodySize) const137 Expected<SmallString<0>> BenchmarkRunner::assembleSnippet(
138 const BenchmarkCode &BC, const SnippetRepetitor &Repetitor,
139 unsigned MinInstructions, unsigned LoopBodySize) const {
140 const std::vector<MCInst> &Instructions = BC.Key.Instructions;
141 SmallString<0> Buffer;
142 raw_svector_ostream OS(Buffer);
143 if (Error E = assembleToStream(
144 State.getExegesisTarget(), State.createTargetMachine(), BC.LiveIns,
145 BC.Key.RegisterInitialValues,
146 Repetitor.Repeat(Instructions, MinInstructions, LoopBodySize), OS)) {
147 return std::move(E);
148 }
149 return Buffer;
150 }
151
152 Expected<BenchmarkRunner::RunnableConfiguration>
getRunnableConfiguration(const BenchmarkCode & BC,unsigned NumRepetitions,unsigned LoopBodySize,const SnippetRepetitor & Repetitor) const153 BenchmarkRunner::getRunnableConfiguration(
154 const BenchmarkCode &BC, unsigned NumRepetitions, unsigned LoopBodySize,
155 const SnippetRepetitor &Repetitor) const {
156 RunnableConfiguration RC;
157
158 InstructionBenchmark &InstrBenchmark = RC.InstrBenchmark;
159 InstrBenchmark.Mode = Mode;
160 InstrBenchmark.CpuName = std::string(State.getTargetMachine().getTargetCPU());
161 InstrBenchmark.LLVMTriple =
162 State.getTargetMachine().getTargetTriple().normalize();
163 InstrBenchmark.NumRepetitions = NumRepetitions;
164 InstrBenchmark.Info = BC.Info;
165
166 const std::vector<MCInst> &Instructions = BC.Key.Instructions;
167
168 InstrBenchmark.Key = BC.Key;
169
170 // Assemble at least kMinInstructionsForSnippet instructions by repeating
171 // the snippet for debug/analysis. This is so that the user clearly
172 // understands that the inside instructions are repeated.
173 if (BenchmarkPhaseSelector > BenchmarkPhaseSelectorE::PrepareSnippet) {
174 const int MinInstructionsForSnippet = 4 * Instructions.size();
175 const int LoopBodySizeForSnippet = 2 * Instructions.size();
176 auto Snippet = assembleSnippet(BC, Repetitor, MinInstructionsForSnippet,
177 LoopBodySizeForSnippet);
178 if (Error E = Snippet.takeError())
179 return std::move(E);
180 const ExecutableFunction EF(State.createTargetMachine(),
181 getObjectFromBuffer(*Snippet));
182 const auto FnBytes = EF.getFunctionBytes();
183 llvm::append_range(InstrBenchmark.AssembledSnippet, FnBytes);
184 }
185
186 // Assemble NumRepetitions instructions repetitions of the snippet for
187 // measurements.
188 if (BenchmarkPhaseSelector > BenchmarkPhaseSelectorE::PrepareAndAssembleSnippet) {
189 auto Snippet = assembleSnippet(BC, Repetitor, InstrBenchmark.NumRepetitions,
190 LoopBodySize);
191 if (Error E = Snippet.takeError())
192 return std::move(E);
193 RC.ObjectFile = getObjectFromBuffer(*Snippet);
194 }
195
196 return std::move(RC);
197 }
198
199 Expected<InstructionBenchmark>
runConfiguration(RunnableConfiguration && RC,bool DumpObjectToDisk) const200 BenchmarkRunner::runConfiguration(RunnableConfiguration &&RC,
201 bool DumpObjectToDisk) const {
202 InstructionBenchmark &InstrBenchmark = RC.InstrBenchmark;
203 object::OwningBinary<object::ObjectFile> &ObjectFile = RC.ObjectFile;
204
205 if (DumpObjectToDisk &&
206 BenchmarkPhaseSelector > BenchmarkPhaseSelectorE::PrepareAndAssembleSnippet) {
207 auto ObjectFilePath = writeObjectFile(ObjectFile.getBinary()->getData());
208 if (Error E = ObjectFilePath.takeError()) {
209 InstrBenchmark.Error = toString(std::move(E));
210 return std::move(InstrBenchmark);
211 }
212 outs() << "Check generated assembly with: /usr/bin/objdump -d "
213 << *ObjectFilePath << "\n";
214 }
215
216 if (BenchmarkPhaseSelector < BenchmarkPhaseSelectorE::Measure) {
217 InstrBenchmark.Error = "actual measurements skipped.";
218 return std::move(InstrBenchmark);
219 }
220
221 const FunctionExecutorImpl Executor(State, std::move(ObjectFile),
222 Scratch.get());
223 auto NewMeasurements = runMeasurements(Executor);
224 if (Error E = NewMeasurements.takeError()) {
225 if (!E.isA<SnippetCrash>())
226 return std::move(E);
227 InstrBenchmark.Error = toString(std::move(E));
228 return std::move(InstrBenchmark);
229 }
230 assert(InstrBenchmark.NumRepetitions > 0 && "invalid NumRepetitions");
231 for (BenchmarkMeasure &BM : *NewMeasurements) {
232 // Scale the measurements by instruction.
233 BM.PerInstructionValue /= InstrBenchmark.NumRepetitions;
234 // Scale the measurements by snippet.
235 BM.PerSnippetValue *=
236 static_cast<double>(InstrBenchmark.Key.Instructions.size()) /
237 InstrBenchmark.NumRepetitions;
238 }
239 InstrBenchmark.Measurements = std::move(*NewMeasurements);
240
241 return std::move(InstrBenchmark);
242 }
243
writeObjectFile(StringRef Buffer) const244 Expected<std::string> BenchmarkRunner::writeObjectFile(StringRef Buffer) const {
245 int ResultFD = 0;
246 SmallString<256> ResultPath;
247 if (Error E = errorCodeToError(
248 sys::fs::createTemporaryFile("snippet", "o", ResultFD, ResultPath)))
249 return std::move(E);
250 raw_fd_ostream OFS(ResultFD, true /*ShouldClose*/);
251 OFS.write(Buffer.data(), Buffer.size());
252 OFS.flush();
253 return std::string(ResultPath.str());
254 }
255
~FunctionExecutor()256 BenchmarkRunner::FunctionExecutor::~FunctionExecutor() {}
257
258 } // namespace exegesis
259 } // namespace llvm
260