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, unsigned LoopBodySize, 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 const int MinInstructionsForSnippet = 4 * Instructions.size(); 172 const int LoopBodySizeForSnippet = 2 * Instructions.size(); 173 { 174 SmallString<0> Buffer; 175 raw_svector_ostream OS(Buffer); 176 if (Error E = assembleToStream( 177 State.getExegesisTarget(), State.createTargetMachine(), 178 BC.LiveIns, BC.Key.RegisterInitialValues, 179 Repetitor->Repeat(Instructions, MinInstructionsForSnippet, 180 LoopBodySizeForSnippet), 181 OS)) { 182 return std::move(E); 183 } 184 const ExecutableFunction EF(State.createTargetMachine(), 185 getObjectFromBuffer(OS.str())); 186 const auto FnBytes = EF.getFunctionBytes(); 187 llvm::append_range(InstrBenchmark.AssembledSnippet, FnBytes); 188 } 189 190 // Assemble NumRepetitions instructions repetitions of the snippet for 191 // measurements. 192 const auto Filler = Repetitor->Repeat( 193 Instructions, InstrBenchmark.NumRepetitions, LoopBodySize); 194 195 object::OwningBinary<object::ObjectFile> ObjectFile; 196 if (DumpObjectToDisk) { 197 auto ObjectFilePath = writeObjectFile(BC, Filler); 198 if (Error E = ObjectFilePath.takeError()) { 199 InstrBenchmark.Error = toString(std::move(E)); 200 return InstrBenchmark; 201 } 202 outs() << "Check generated assembly with: /usr/bin/objdump -d " 203 << *ObjectFilePath << "\n"; 204 ObjectFile = getObjectFromFile(*ObjectFilePath); 205 } else { 206 SmallString<0> Buffer; 207 raw_svector_ostream OS(Buffer); 208 if (Error E = assembleToStream( 209 State.getExegesisTarget(), State.createTargetMachine(), 210 BC.LiveIns, BC.Key.RegisterInitialValues, Filler, OS)) { 211 return std::move(E); 212 } 213 ObjectFile = getObjectFromBuffer(OS.str()); 214 } 215 216 const FunctionExecutorImpl Executor(State, std::move(ObjectFile), 217 Scratch.get()); 218 auto NewMeasurements = runMeasurements(Executor); 219 if (Error E = NewMeasurements.takeError()) { 220 if (!E.isA<SnippetCrash>()) 221 return std::move(E); 222 InstrBenchmark.Error = toString(std::move(E)); 223 return InstrBenchmark; 224 } 225 assert(InstrBenchmark.NumRepetitions > 0 && "invalid NumRepetitions"); 226 for (BenchmarkMeasure &BM : *NewMeasurements) { 227 // Scale the measurements by instruction. 228 BM.PerInstructionValue /= InstrBenchmark.NumRepetitions; 229 // Scale the measurements by snippet. 230 BM.PerSnippetValue *= static_cast<double>(Instructions.size()) / 231 InstrBenchmark.NumRepetitions; 232 } 233 if (InstrBenchmark.Measurements.empty()) { 234 InstrBenchmark.Measurements = std::move(*NewMeasurements); 235 continue; 236 } 237 238 assert(Repetitors.size() > 1 && !InstrBenchmark.Measurements.empty() && 239 "We're in an 'min' repetition mode, and need to aggregate new " 240 "result to the existing result."); 241 assert(InstrBenchmark.Measurements.size() == NewMeasurements->size() && 242 "Expected to have identical number of measurements."); 243 for (auto I : zip(InstrBenchmark.Measurements, *NewMeasurements)) { 244 BenchmarkMeasure &Measurement = std::get<0>(I); 245 BenchmarkMeasure &NewMeasurement = std::get<1>(I); 246 assert(Measurement.Key == NewMeasurement.Key && 247 "Expected measurements to be symmetric"); 248 249 Measurement.PerInstructionValue = std::min( 250 Measurement.PerInstructionValue, NewMeasurement.PerInstructionValue); 251 Measurement.PerSnippetValue = 252 std::min(Measurement.PerSnippetValue, NewMeasurement.PerSnippetValue); 253 } 254 } 255 256 // We successfully measured everything, so don't discard the results. 257 CBOR.disarm(); 258 return InstrBenchmark; 259 } 260 261 Expected<std::string> 262 BenchmarkRunner::writeObjectFile(const BenchmarkCode &BC, 263 const FillFunction &FillFunction) const { 264 int ResultFD = 0; 265 SmallString<256> ResultPath; 266 if (Error E = errorCodeToError( 267 sys::fs::createTemporaryFile("snippet", "o", ResultFD, ResultPath))) 268 return std::move(E); 269 raw_fd_ostream OFS(ResultFD, true /*ShouldClose*/); 270 if (Error E = assembleToStream( 271 State.getExegesisTarget(), State.createTargetMachine(), BC.LiveIns, 272 BC.Key.RegisterInitialValues, FillFunction, OFS)) { 273 return std::move(E); 274 } 275 return std::string(ResultPath.str()); 276 } 277 278 BenchmarkRunner::FunctionExecutor::~FunctionExecutor() {} 279 280 } // namespace exegesis 281 } // namespace llvm 282