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)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:
FunctionExecutorImpl(const LLVMState & State,object::OwningBinary<object::ObjectFile> Obj,BenchmarkRunner::ScratchSpace * Scratch)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:
runAndMeasure(const char * Counters) const48   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
accumulateCounterValues(const llvm::SmallVector<int64_t,4> & NewValues,llvm::SmallVector<int64_t,4> * Result)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>>
runAndSample(const char * Counters) const66   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     const ExegesisTarget &ET = State.getExegesisTarget();
75     for (auto &CounterName : CounterNames) {
76       CounterName = CounterName.trim();
77       auto CounterOrError = ET.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         auto PS = ET.withSavedState();
97         CrashRecoveryContext CRC;
98         CrashRecoveryContext::Enable();
99         const bool Crashed = !CRC.RunSafely([this, Counter, ScratchPtr]() {
100           Counter->start();
101           this->Function(ScratchPtr);
102           Counter->stop();
103         });
104         CrashRecoveryContext::Disable();
105         PS.reset();
106         if (Crashed) {
107           std::string Msg = "snippet crashed while running";
108 #ifdef LLVM_ON_UNIX
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)) {
113             Msg += ": ";
114             Msg += SigName;
115           }
116 #endif
117           return make_error<SnippetCrash>(std::move(Msg));
118         }
119       }
120 
121       auto ValueOrError = Counter->readOrError(Function.getFunctionBytes());
122       if (!ValueOrError)
123         return ValueOrError.takeError();
124       accumulateCounterValues(ValueOrError.get(), &CounterValues);
125     }
126     return CounterValues;
127   }
128 
129   const LLVMState &State;
130   const ExecutableFunction Function;
131   BenchmarkRunner::ScratchSpace *const Scratch;
132 };
133 } // namespace
134 
runConfiguration(const BenchmarkCode & BC,unsigned NumRepetitions,ArrayRef<std::unique_ptr<const SnippetRepetitor>> Repetitors,bool DumpObjectToDisk) const135 Expected<InstructionBenchmark> BenchmarkRunner::runConfiguration(
136     const BenchmarkCode &BC, unsigned NumRepetitions,
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;
146 
147   const std::vector<MCInst> &Instructions = BC.Key.Instructions;
148 
149   InstrBenchmark.Key = BC.Key;
150 
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() {
156       if (Clear)
157         IB->Measurements.clear();
158     }
159     void disarm() { Clear = false; }
160 
161   private:
162     InstructionBenchmark *const IB;
163     bool Clear = true;
164   };
165   ClearBenchmarkOnReturn CBOR(&InstrBenchmark);
166 
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     constexpr const int kMinInstructionsForSnippet = 16;
172     {
173       SmallString<0> Buffer;
174       raw_svector_ostream OS(Buffer);
175       if (Error E = assembleToStream(
176               State.getExegesisTarget(), State.createTargetMachine(),
177               BC.LiveIns, BC.Key.RegisterInitialValues,
178               Repetitor->Repeat(Instructions, kMinInstructionsForSnippet),
179               OS)) {
180         return std::move(E);
181       }
182       const ExecutableFunction EF(State.createTargetMachine(),
183                                   getObjectFromBuffer(OS.str()));
184       const auto FnBytes = EF.getFunctionBytes();
185       llvm::append_range(InstrBenchmark.AssembledSnippet, FnBytes);
186     }
187 
188     // Assemble NumRepetitions instructions repetitions of the snippet for
189     // measurements.
190     const auto Filler =
191         Repetitor->Repeat(Instructions, InstrBenchmark.NumRepetitions);
192 
193     object::OwningBinary<object::ObjectFile> ObjectFile;
194     if (DumpObjectToDisk) {
195       auto ObjectFilePath = writeObjectFile(BC, Filler);
196       if (Error E = ObjectFilePath.takeError()) {
197         InstrBenchmark.Error = toString(std::move(E));
198         return InstrBenchmark;
199       }
200       outs() << "Check generated assembly with: /usr/bin/objdump -d "
201              << *ObjectFilePath << "\n";
202       ObjectFile = getObjectFromFile(*ObjectFilePath);
203     } else {
204       SmallString<0> Buffer;
205       raw_svector_ostream OS(Buffer);
206       if (Error E = assembleToStream(
207               State.getExegesisTarget(), State.createTargetMachine(),
208               BC.LiveIns, BC.Key.RegisterInitialValues, Filler, OS)) {
209         return std::move(E);
210       }
211       ObjectFile = getObjectFromBuffer(OS.str());
212     }
213 
214     const FunctionExecutorImpl Executor(State, std::move(ObjectFile),
215                                         Scratch.get());
216     auto NewMeasurements = runMeasurements(Executor);
217     if (Error E = NewMeasurements.takeError()) {
218       if (!E.isA<SnippetCrash>())
219         return std::move(E);
220       InstrBenchmark.Error = toString(std::move(E));
221       return InstrBenchmark;
222     }
223     assert(InstrBenchmark.NumRepetitions > 0 && "invalid NumRepetitions");
224     for (BenchmarkMeasure &BM : *NewMeasurements) {
225       // Scale the measurements by instruction.
226       BM.PerInstructionValue /= InstrBenchmark.NumRepetitions;
227       // Scale the measurements by snippet.
228       BM.PerSnippetValue *= static_cast<double>(Instructions.size()) /
229                             InstrBenchmark.NumRepetitions;
230     }
231     if (InstrBenchmark.Measurements.empty()) {
232       InstrBenchmark.Measurements = std::move(*NewMeasurements);
233       continue;
234     }
235 
236     assert(Repetitors.size() > 1 && !InstrBenchmark.Measurements.empty() &&
237            "We're in an 'min' repetition mode, and need to aggregate new "
238            "result to the existing result.");
239     assert(InstrBenchmark.Measurements.size() == NewMeasurements->size() &&
240            "Expected to have identical number of measurements.");
241     for (auto I : zip(InstrBenchmark.Measurements, *NewMeasurements)) {
242       BenchmarkMeasure &Measurement = std::get<0>(I);
243       BenchmarkMeasure &NewMeasurement = std::get<1>(I);
244       assert(Measurement.Key == NewMeasurement.Key &&
245              "Expected measurements to be symmetric");
246 
247       Measurement.PerInstructionValue = std::min(
248           Measurement.PerInstructionValue, NewMeasurement.PerInstructionValue);
249       Measurement.PerSnippetValue =
250           std::min(Measurement.PerSnippetValue, NewMeasurement.PerSnippetValue);
251     }
252   }
253 
254   // We successfully measured everything, so don't discard the results.
255   CBOR.disarm();
256   return InstrBenchmark;
257 }
258 
259 Expected<std::string>
writeObjectFile(const BenchmarkCode & BC,const FillFunction & FillFunction) const260 BenchmarkRunner::writeObjectFile(const BenchmarkCode &BC,
261                                  const FillFunction &FillFunction) const {
262   int ResultFD = 0;
263   SmallString<256> ResultPath;
264   if (Error E = errorCodeToError(
265           sys::fs::createTemporaryFile("snippet", "o", ResultFD, ResultPath)))
266     return std::move(E);
267   raw_fd_ostream OFS(ResultFD, true /*ShouldClose*/);
268   if (Error E = assembleToStream(
269           State.getExegesisTarget(), State.createTargetMachine(), BC.LiveIns,
270           BC.Key.RegisterInitialValues, FillFunction, OFS)) {
271     return std::move(E);
272   }
273   return std::string(ResultPath.str());
274 }
275 
~FunctionExecutor()276 BenchmarkRunner::FunctionExecutor::~FunctionExecutor() {}
277 
278 } // namespace exegesis
279 } // namespace llvm
280