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