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 
32 BenchmarkRunner::BenchmarkRunner(const LLVMState &State,
33                                  InstructionBenchmark::ModeE Mode)
34     : State(State), Mode(Mode), Scratch(std::make_unique<ScratchSpace>()) {}
35 
36 BenchmarkRunner::~BenchmarkRunner() = default;
37 
38 namespace {
39 class FunctionExecutorImpl : public BenchmarkRunner::FunctionExecutor {
40 public:
41   FunctionExecutorImpl(const LLVMState &State,
42                        object::OwningBinary<object::ObjectFile> Obj,
43                        BenchmarkRunner::ScratchSpace *Scratch)
44       : State(State), Function(State.createTargetMachine(), std::move(Obj)),
45         Scratch(Scratch) {}
46 
47 private:
48   Expected<int64_t> runAndMeasure(const char *Counters) const override {
49     auto ResultOrError = runAndSample(Counters);
50     if (ResultOrError)
51       return ResultOrError.get()[0];
52     return ResultOrError.takeError();
53   }
54 
55   static void
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];
63   }
64 
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;
70     int Reserved = 0;
71     SmallVector<StringRef, 2> CounterNames;
72     StringRef(Counters).split(CounterNames, '+');
73     char *const ScratchPtr = Scratch->ptr();
74     for (auto &CounterName : CounterNames) {
75       CounterName = CounterName.trim();
76       auto CounterOrError =
77           State.getExegesisTarget().createCounter(CounterName, State);
78 
79       if (!CounterOrError)
80         return CounterOrError.takeError();
81 
82       pfm::Counter *Counter = CounterOrError.get().get();
83       if (Reserved == 0) {
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 ")
90                 .concat(CounterName)
91                 .concat(std::to_string(Counter->numValues()))
92                 .concat(" vs expected of ")
93                 .concat(std::to_string(Reserved)));
94       Scratch->clear();
95       {
96         CrashRecoveryContext CRC;
97         CrashRecoveryContext::Enable();
98         const bool Crashed = !CRC.RunSafely([this, Counter, ScratchPtr]() {
99           Counter->start();
100           this->Function(ScratchPtr);
101           Counter->stop();
102         });
103         CrashRecoveryContext::Disable();
104         // FIXME: Better diagnosis.
105         if (Crashed)
106           return make_error<SnippetCrash>("snippet crashed while running");
107       }
108 
109       auto ValueOrError = Counter->readOrError(Function.getFunctionBytes());
110       if (!ValueOrError)
111         return ValueOrError.takeError();
112       accumulateCounterValues(ValueOrError.get(), &CounterValues);
113     }
114     return CounterValues;
115   }
116 
117   const LLVMState &State;
118   const ExecutableFunction Function;
119   BenchmarkRunner::ScratchSpace *const Scratch;
120 };
121 } // namespace
122 
123 Expected<InstructionBenchmark> BenchmarkRunner::runConfiguration(
124     const BenchmarkCode &BC, unsigned NumRepetitions,
125     ArrayRef<std::unique_ptr<const SnippetRepetitor>> Repetitors,
126     bool DumpObjectToDisk) const {
127   InstructionBenchmark InstrBenchmark;
128   InstrBenchmark.Mode = Mode;
129   InstrBenchmark.CpuName = std::string(State.getTargetMachine().getTargetCPU());
130   InstrBenchmark.LLVMTriple =
131       State.getTargetMachine().getTargetTriple().normalize();
132   InstrBenchmark.NumRepetitions = NumRepetitions;
133   InstrBenchmark.Info = BC.Info;
134 
135   const std::vector<MCInst> &Instructions = BC.Key.Instructions;
136 
137   InstrBenchmark.Key = BC.Key;
138 
139   // If we end up having an error, and we've previously succeeded with
140   // some other Repetitor, we want to discard the previous measurements.
141   struct ClearBenchmarkOnReturn {
142     ClearBenchmarkOnReturn(InstructionBenchmark *IB) : IB(IB) {}
143     ~ClearBenchmarkOnReturn() {
144       if (Clear)
145         IB->Measurements.clear();
146     }
147     void disarm() { Clear = false; }
148 
149   private:
150     InstructionBenchmark *const IB;
151     bool Clear = true;
152   };
153   ClearBenchmarkOnReturn CBOR(&InstrBenchmark);
154 
155   for (const std::unique_ptr<const SnippetRepetitor> &Repetitor : Repetitors) {
156     // Assemble at least kMinInstructionsForSnippet instructions by repeating
157     // the snippet for debug/analysis. This is so that the user clearly
158     // understands that the inside instructions are repeated.
159     constexpr const int kMinInstructionsForSnippet = 16;
160     {
161       SmallString<0> Buffer;
162       raw_svector_ostream OS(Buffer);
163       if (Error E = assembleToStream(
164               State.getExegesisTarget(), State.createTargetMachine(),
165               BC.LiveIns, BC.Key.RegisterInitialValues,
166               Repetitor->Repeat(Instructions, kMinInstructionsForSnippet),
167               OS)) {
168         return std::move(E);
169       }
170       const ExecutableFunction EF(State.createTargetMachine(),
171                                   getObjectFromBuffer(OS.str()));
172       const auto FnBytes = EF.getFunctionBytes();
173       InstrBenchmark.AssembledSnippet.insert(
174           InstrBenchmark.AssembledSnippet.end(), FnBytes.begin(),
175           FnBytes.end());
176     }
177 
178     // Assemble NumRepetitions instructions repetitions of the snippet for
179     // measurements.
180     const auto Filler =
181         Repetitor->Repeat(Instructions, InstrBenchmark.NumRepetitions);
182 
183     object::OwningBinary<object::ObjectFile> ObjectFile;
184     if (DumpObjectToDisk) {
185       auto ObjectFilePath = writeObjectFile(BC, Filler);
186       if (Error E = ObjectFilePath.takeError()) {
187         InstrBenchmark.Error = toString(std::move(E));
188         return InstrBenchmark;
189       }
190       outs() << "Check generated assembly with: /usr/bin/objdump -d "
191              << *ObjectFilePath << "\n";
192       ObjectFile = getObjectFromFile(*ObjectFilePath);
193     } else {
194       SmallString<0> Buffer;
195       raw_svector_ostream OS(Buffer);
196       if (Error E = assembleToStream(
197               State.getExegesisTarget(), State.createTargetMachine(),
198               BC.LiveIns, BC.Key.RegisterInitialValues, Filler, OS)) {
199         return std::move(E);
200       }
201       ObjectFile = getObjectFromBuffer(OS.str());
202     }
203 
204     const FunctionExecutorImpl Executor(State, std::move(ObjectFile),
205                                         Scratch.get());
206     auto NewMeasurements = runMeasurements(Executor);
207     if (Error E = NewMeasurements.takeError()) {
208       if (!E.isA<SnippetCrash>())
209         return std::move(E);
210       InstrBenchmark.Error = toString(std::move(E));
211       return InstrBenchmark;
212     }
213     assert(InstrBenchmark.NumRepetitions > 0 && "invalid NumRepetitions");
214     for (BenchmarkMeasure &BM : *NewMeasurements) {
215       // Scale the measurements by instruction.
216       BM.PerInstructionValue /= InstrBenchmark.NumRepetitions;
217       // Scale the measurements by snippet.
218       BM.PerSnippetValue *= static_cast<double>(Instructions.size()) /
219                             InstrBenchmark.NumRepetitions;
220     }
221     if (InstrBenchmark.Measurements.empty()) {
222       InstrBenchmark.Measurements = std::move(*NewMeasurements);
223       continue;
224     }
225 
226     assert(Repetitors.size() > 1 && !InstrBenchmark.Measurements.empty() &&
227            "We're in an 'min' repetition mode, and need to aggregate new "
228            "result to the existing result.");
229     assert(InstrBenchmark.Measurements.size() == NewMeasurements->size() &&
230            "Expected to have identical number of measurements.");
231     for (auto I : zip(InstrBenchmark.Measurements, *NewMeasurements)) {
232       BenchmarkMeasure &Measurement = std::get<0>(I);
233       BenchmarkMeasure &NewMeasurement = std::get<1>(I);
234       assert(Measurement.Key == NewMeasurement.Key &&
235              "Expected measurements to be symmetric");
236 
237       Measurement.PerInstructionValue = std::min(
238           Measurement.PerInstructionValue, NewMeasurement.PerInstructionValue);
239       Measurement.PerSnippetValue =
240           std::min(Measurement.PerSnippetValue, NewMeasurement.PerSnippetValue);
241     }
242   }
243 
244   // We successfully measured everything, so don't discard the results.
245   CBOR.disarm();
246   return InstrBenchmark;
247 }
248 
249 Expected<std::string>
250 BenchmarkRunner::writeObjectFile(const BenchmarkCode &BC,
251                                  const FillFunction &FillFunction) const {
252   int ResultFD = 0;
253   SmallString<256> ResultPath;
254   if (Error E = errorCodeToError(
255           sys::fs::createTemporaryFile("snippet", "o", ResultFD, ResultPath)))
256     return std::move(E);
257   raw_fd_ostream OFS(ResultFD, true /*ShouldClose*/);
258   if (Error E = assembleToStream(
259           State.getExegesisTarget(), State.createTargetMachine(), BC.LiveIns,
260           BC.Key.RegisterInitialValues, FillFunction, OFS)) {
261     return std::move(E);
262   }
263   return std::string(ResultPath.str());
264 }
265 
266 BenchmarkRunner::FunctionExecutor::~FunctionExecutor() {}
267 
268 } // namespace exegesis
269 } // namespace llvm
270