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