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 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 135 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> 260 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 276 BenchmarkRunner::FunctionExecutor::~FunctionExecutor() {} 277 278 } // namespace exegesis 279 } // namespace llvm 280