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