Home | History | Annotate | Line # | Download | only in lib
      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