xref: /openbsd-src/gnu/llvm/llvm/tools/llvm-exegesis/lib/BenchmarkRunner.cpp (revision d415bd752c734aee168c4ee86ff32e8cc249eb16)
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