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 "MCInstrDescView.h"
15 #include "PerfHelper.h"
16 #include "llvm/ADT/StringExtras.h"
17 #include "llvm/ADT/StringRef.h"
18 #include "llvm/ADT/Twine.h"
19 #include "llvm/Support/CrashRecoveryContext.h"
20 #include "llvm/Support/FileSystem.h"
21 #include "llvm/Support/MemoryBuffer.h"
22 #include "llvm/Support/Program.h"
23 
24 namespace llvm {
25 namespace exegesis {
26 
27 BenchmarkFailure::BenchmarkFailure(const llvm::Twine &S)
28     : llvm::StringError(S, llvm::inconvertibleErrorCode()) {}
29 
30 BenchmarkRunner::BenchmarkRunner(const LLVMState &State,
31                                  InstructionBenchmark::ModeE Mode)
32     : State(State), Mode(Mode), Scratch(std::make_unique<ScratchSpace>()) {}
33 
34 BenchmarkRunner::~BenchmarkRunner() = default;
35 
36 // Repeat the snippet until there are at least MinInstructions in the resulting
37 // code.
38 static std::vector<llvm::MCInst>
39 GenerateInstructions(const BenchmarkCode &BC, const size_t MinInstructions) {
40   if (BC.Instructions.empty())
41     return {};
42   std::vector<llvm::MCInst> Code = BC.Instructions;
43   for (int I = 0; Code.size() < MinInstructions; ++I)
44     Code.push_back(BC.Instructions[I % BC.Instructions.size()]);
45   return Code;
46 }
47 
48 namespace {
49 class FunctionExecutorImpl : public BenchmarkRunner::FunctionExecutor {
50 public:
51   FunctionExecutorImpl(const LLVMState &State,
52                        llvm::object::OwningBinary<llvm::object::ObjectFile> Obj,
53                        BenchmarkRunner::ScratchSpace *Scratch)
54       : Function(State.createTargetMachine(), std::move(Obj)),
55         Scratch(Scratch) {}
56 
57 private:
58   llvm::Expected<int64_t> runAndMeasure(const char *Counters) const override {
59     // We sum counts when there are several counters for a single ProcRes
60     // (e.g. P23 on SandyBridge).
61     int64_t CounterValue = 0;
62     llvm::SmallVector<llvm::StringRef, 2> CounterNames;
63     llvm::StringRef(Counters).split(CounterNames, '+');
64     char *const ScratchPtr = Scratch->ptr();
65     for (auto &CounterName : CounterNames) {
66       CounterName = CounterName.trim();
67       pfm::PerfEvent PerfEvent(CounterName);
68       if (!PerfEvent.valid())
69         llvm::report_fatal_error(llvm::Twine("invalid perf event '")
70                                      .concat(CounterName)
71                                      .concat("'"));
72       pfm::Counter Counter(PerfEvent);
73       Scratch->clear();
74       {
75         llvm::CrashRecoveryContext CRC;
76         llvm::CrashRecoveryContext::Enable();
77         const bool Crashed = !CRC.RunSafely([this, &Counter, ScratchPtr]() {
78           Counter.start();
79           this->Function(ScratchPtr);
80           Counter.stop();
81         });
82         llvm::CrashRecoveryContext::Disable();
83         // FIXME: Better diagnosis.
84         if (Crashed)
85           return llvm::make_error<BenchmarkFailure>(
86               "snippet crashed while running");
87       }
88       CounterValue += Counter.read();
89     }
90     return CounterValue;
91   }
92 
93   const ExecutableFunction Function;
94   BenchmarkRunner::ScratchSpace *const Scratch;
95 };
96 } // namespace
97 
98 InstructionBenchmark
99 BenchmarkRunner::runConfiguration(const BenchmarkCode &BC,
100                                   unsigned NumRepetitions,
101                                   bool DumpObjectToDisk) const {
102   InstructionBenchmark InstrBenchmark;
103   InstrBenchmark.Mode = Mode;
104   InstrBenchmark.CpuName = State.getTargetMachine().getTargetCPU();
105   InstrBenchmark.LLVMTriple =
106       State.getTargetMachine().getTargetTriple().normalize();
107   InstrBenchmark.NumRepetitions = NumRepetitions;
108   InstrBenchmark.Info = BC.Info;
109 
110   const std::vector<llvm::MCInst> &Instructions = BC.Instructions;
111 
112   InstrBenchmark.Key.Instructions = Instructions;
113   InstrBenchmark.Key.RegisterInitialValues = BC.RegisterInitialValues;
114 
115   // Assemble at least kMinInstructionsForSnippet instructions by repeating the
116   // snippet for debug/analysis. This is so that the user clearly understands
117   // that the inside instructions are repeated.
118   constexpr const int kMinInstructionsForSnippet = 16;
119   {
120     llvm::SmallString<0> Buffer;
121     llvm::raw_svector_ostream OS(Buffer);
122     assembleToStream(State.getExegesisTarget(), State.createTargetMachine(),
123                      BC.LiveIns, BC.RegisterInitialValues,
124                      GenerateInstructions(BC, kMinInstructionsForSnippet), OS);
125     const ExecutableFunction EF(State.createTargetMachine(),
126                                 getObjectFromBuffer(OS.str()));
127     const auto FnBytes = EF.getFunctionBytes();
128     InstrBenchmark.AssembledSnippet.assign(FnBytes.begin(), FnBytes.end());
129   }
130 
131   // Assemble NumRepetitions instructions repetitions of the snippet for
132   // measurements.
133   const auto Code = GenerateInstructions(BC, InstrBenchmark.NumRepetitions);
134 
135   llvm::object::OwningBinary<llvm::object::ObjectFile> ObjectFile;
136   if (DumpObjectToDisk) {
137     auto ObjectFilePath = writeObjectFile(BC, Code);
138     if (llvm::Error E = ObjectFilePath.takeError()) {
139       InstrBenchmark.Error = llvm::toString(std::move(E));
140       return InstrBenchmark;
141     }
142     llvm::outs() << "Check generated assembly with: /usr/bin/objdump -d "
143                  << *ObjectFilePath << "\n";
144     ObjectFile = getObjectFromFile(*ObjectFilePath);
145   } else {
146     llvm::SmallString<0> Buffer;
147     llvm::raw_svector_ostream OS(Buffer);
148     assembleToStream(State.getExegesisTarget(), State.createTargetMachine(),
149                      BC.LiveIns, BC.RegisterInitialValues, Code, OS);
150     ObjectFile = getObjectFromBuffer(OS.str());
151   }
152 
153   const FunctionExecutorImpl Executor(State, std::move(ObjectFile),
154                                       Scratch.get());
155   auto Measurements = runMeasurements(Executor);
156   if (llvm::Error E = Measurements.takeError()) {
157     InstrBenchmark.Error = llvm::toString(std::move(E));
158     return InstrBenchmark;
159   }
160   InstrBenchmark.Measurements = std::move(*Measurements);
161   assert(InstrBenchmark.NumRepetitions > 0 && "invalid NumRepetitions");
162   for (BenchmarkMeasure &BM : InstrBenchmark.Measurements) {
163     // Scale the measurements by instruction.
164     BM.PerInstructionValue /= InstrBenchmark.NumRepetitions;
165     // Scale the measurements by snippet.
166     BM.PerSnippetValue *= static_cast<double>(BC.Instructions.size()) /
167                           InstrBenchmark.NumRepetitions;
168   }
169 
170   return InstrBenchmark;
171 }
172 
173 llvm::Expected<std::string>
174 BenchmarkRunner::writeObjectFile(const BenchmarkCode &BC,
175                                  llvm::ArrayRef<llvm::MCInst> Code) const {
176   int ResultFD = 0;
177   llvm::SmallString<256> ResultPath;
178   if (llvm::Error E = llvm::errorCodeToError(llvm::sys::fs::createTemporaryFile(
179           "snippet", "o", ResultFD, ResultPath)))
180     return std::move(E);
181   llvm::raw_fd_ostream OFS(ResultFD, true /*ShouldClose*/);
182   assembleToStream(State.getExegesisTarget(), State.createTargetMachine(),
183                    BC.LiveIns, BC.RegisterInitialValues, Code, OFS);
184   return ResultPath.str();
185 }
186 
187 BenchmarkRunner::FunctionExecutor::~FunctionExecutor() {}
188 
189 } // namespace exegesis
190 } // namespace llvm
191