//===-- BenchmarkRunner.cpp -------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include <array>
#include <memory>
#include <string>

#include "Assembler.h"
#include "BenchmarkRunner.h"
#include "Error.h"
#include "MCInstrDescView.h"
#include "PerfHelper.h"
#include "Target.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/CrashRecoveryContext.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Program.h"

namespace llvm {
namespace exegesis {

BenchmarkRunner::BenchmarkRunner(const LLVMState &State,
                                 InstructionBenchmark::ModeE Mode)
    : State(State), Mode(Mode), Scratch(std::make_unique<ScratchSpace>()) {}

BenchmarkRunner::~BenchmarkRunner() = default;

namespace {
class FunctionExecutorImpl : public BenchmarkRunner::FunctionExecutor {
public:
  FunctionExecutorImpl(const LLVMState &State,
                       object::OwningBinary<object::ObjectFile> Obj,
                       BenchmarkRunner::ScratchSpace *Scratch)
      : State(State), Function(State.createTargetMachine(), std::move(Obj)),
        Scratch(Scratch) {}

private:
  Expected<int64_t> runAndMeasure(const char *Counters) const override {
    auto ResultOrError = runAndSample(Counters);
    if (ResultOrError)
      return ResultOrError.get()[0];
    return ResultOrError.takeError();
  }

  static void
  accumulateCounterValues(const llvm::SmallVector<int64_t, 4> &NewValues,
                          llvm::SmallVector<int64_t, 4> *Result) {
    const size_t NumValues = std::max(NewValues.size(), Result->size());
    if (NumValues > Result->size())
      Result->resize(NumValues, 0);
    for (size_t I = 0, End = NewValues.size(); I < End; ++I)
      (*Result)[I] += NewValues[I];
  }

  Expected<llvm::SmallVector<int64_t, 4>>
  runAndSample(const char *Counters) const override {
    // We sum counts when there are several counters for a single ProcRes
    // (e.g. P23 on SandyBridge).
    llvm::SmallVector<int64_t, 4> CounterValues;
    int Reserved = 0;
    SmallVector<StringRef, 2> CounterNames;
    StringRef(Counters).split(CounterNames, '+');
    char *const ScratchPtr = Scratch->ptr();
    const ExegesisTarget &ET = State.getExegesisTarget();
    for (auto &CounterName : CounterNames) {
      CounterName = CounterName.trim();
      auto CounterOrError = ET.createCounter(CounterName, State);

      if (!CounterOrError)
        return CounterOrError.takeError();

      pfm::Counter *Counter = CounterOrError.get().get();
      if (Reserved == 0) {
        Reserved = Counter->numValues();
        CounterValues.reserve(Reserved);
      } else if (Reserved != Counter->numValues())
        // It'd be wrong to accumulate vectors of different sizes.
        return make_error<Failure>(
            llvm::Twine("Inconsistent number of values for counter ")
                .concat(CounterName)
                .concat(std::to_string(Counter->numValues()))
                .concat(" vs expected of ")
                .concat(std::to_string(Reserved)));
      Scratch->clear();
      {
        auto PS = ET.withSavedState();
        CrashRecoveryContext CRC;
        CrashRecoveryContext::Enable();
        const bool Crashed = !CRC.RunSafely([this, Counter, ScratchPtr]() {
          Counter->start();
          this->Function(ScratchPtr);
          Counter->stop();
        });
        CrashRecoveryContext::Disable();
        PS.reset();
        if (Crashed) {
          std::string Msg = "snippet crashed while running";
#ifdef LLVM_ON_UNIX
          // See "Exit Status for Commands":
          // https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xcu_chap02.html
          constexpr const int kSigOffset = 128;
          if (const char *const SigName = strsignal(CRC.RetCode - kSigOffset)) {
            Msg += ": ";
            Msg += SigName;
          }
#endif
          return make_error<SnippetCrash>(std::move(Msg));
        }
      }

      auto ValueOrError = Counter->readOrError(Function.getFunctionBytes());
      if (!ValueOrError)
        return ValueOrError.takeError();
      accumulateCounterValues(ValueOrError.get(), &CounterValues);
    }
    return CounterValues;
  }

  const LLVMState &State;
  const ExecutableFunction Function;
  BenchmarkRunner::ScratchSpace *const Scratch;
};
} // namespace

Expected<InstructionBenchmark> BenchmarkRunner::runConfiguration(
    const BenchmarkCode &BC, unsigned NumRepetitions, unsigned LoopBodySize,
    ArrayRef<std::unique_ptr<const SnippetRepetitor>> Repetitors,
    bool DumpObjectToDisk) const {
  InstructionBenchmark InstrBenchmark;
  InstrBenchmark.Mode = Mode;
  InstrBenchmark.CpuName = std::string(State.getTargetMachine().getTargetCPU());
  InstrBenchmark.LLVMTriple =
      State.getTargetMachine().getTargetTriple().normalize();
  InstrBenchmark.NumRepetitions = NumRepetitions;
  InstrBenchmark.Info = BC.Info;

  const std::vector<MCInst> &Instructions = BC.Key.Instructions;

  InstrBenchmark.Key = BC.Key;

  // If we end up having an error, and we've previously succeeded with
  // some other Repetitor, we want to discard the previous measurements.
  struct ClearBenchmarkOnReturn {
    ClearBenchmarkOnReturn(InstructionBenchmark *IB) : IB(IB) {}
    ~ClearBenchmarkOnReturn() {
      if (Clear)
        IB->Measurements.clear();
    }
    void disarm() { Clear = false; }

  private:
    InstructionBenchmark *const IB;
    bool Clear = true;
  };
  ClearBenchmarkOnReturn CBOR(&InstrBenchmark);

  for (const std::unique_ptr<const SnippetRepetitor> &Repetitor : Repetitors) {
    // Assemble at least kMinInstructionsForSnippet instructions by repeating
    // the snippet for debug/analysis. This is so that the user clearly
    // understands that the inside instructions are repeated.
    const int MinInstructionsForSnippet = 4 * Instructions.size();
    const int LoopBodySizeForSnippet = 2 * Instructions.size();
    {
      SmallString<0> Buffer;
      raw_svector_ostream OS(Buffer);
      if (Error E = assembleToStream(
              State.getExegesisTarget(), State.createTargetMachine(),
              BC.LiveIns, BC.Key.RegisterInitialValues,
              Repetitor->Repeat(Instructions, MinInstructionsForSnippet,
                                LoopBodySizeForSnippet),
              OS)) {
        return std::move(E);
      }
      const ExecutableFunction EF(State.createTargetMachine(),
                                  getObjectFromBuffer(OS.str()));
      const auto FnBytes = EF.getFunctionBytes();
      llvm::append_range(InstrBenchmark.AssembledSnippet, FnBytes);
    }

    // Assemble NumRepetitions instructions repetitions of the snippet for
    // measurements.
    const auto Filler = Repetitor->Repeat(
        Instructions, InstrBenchmark.NumRepetitions, LoopBodySize);

    object::OwningBinary<object::ObjectFile> ObjectFile;
    if (DumpObjectToDisk) {
      auto ObjectFilePath = writeObjectFile(BC, Filler);
      if (Error E = ObjectFilePath.takeError()) {
        InstrBenchmark.Error = toString(std::move(E));
        return InstrBenchmark;
      }
      outs() << "Check generated assembly with: /usr/bin/objdump -d "
             << *ObjectFilePath << "\n";
      ObjectFile = getObjectFromFile(*ObjectFilePath);
    } else {
      SmallString<0> Buffer;
      raw_svector_ostream OS(Buffer);
      if (Error E = assembleToStream(
              State.getExegesisTarget(), State.createTargetMachine(),
              BC.LiveIns, BC.Key.RegisterInitialValues, Filler, OS)) {
        return std::move(E);
      }
      ObjectFile = getObjectFromBuffer(OS.str());
    }

    const FunctionExecutorImpl Executor(State, std::move(ObjectFile),
                                        Scratch.get());
    auto NewMeasurements = runMeasurements(Executor);
    if (Error E = NewMeasurements.takeError()) {
      if (!E.isA<SnippetCrash>())
        return std::move(E);
      InstrBenchmark.Error = toString(std::move(E));
      return InstrBenchmark;
    }
    assert(InstrBenchmark.NumRepetitions > 0 && "invalid NumRepetitions");
    for (BenchmarkMeasure &BM : *NewMeasurements) {
      // Scale the measurements by instruction.
      BM.PerInstructionValue /= InstrBenchmark.NumRepetitions;
      // Scale the measurements by snippet.
      BM.PerSnippetValue *= static_cast<double>(Instructions.size()) /
                            InstrBenchmark.NumRepetitions;
    }
    if (InstrBenchmark.Measurements.empty()) {
      InstrBenchmark.Measurements = std::move(*NewMeasurements);
      continue;
    }

    assert(Repetitors.size() > 1 && !InstrBenchmark.Measurements.empty() &&
           "We're in an 'min' repetition mode, and need to aggregate new "
           "result to the existing result.");
    assert(InstrBenchmark.Measurements.size() == NewMeasurements->size() &&
           "Expected to have identical number of measurements.");
    for (auto I : zip(InstrBenchmark.Measurements, *NewMeasurements)) {
      BenchmarkMeasure &Measurement = std::get<0>(I);
      BenchmarkMeasure &NewMeasurement = std::get<1>(I);
      assert(Measurement.Key == NewMeasurement.Key &&
             "Expected measurements to be symmetric");

      Measurement.PerInstructionValue = std::min(
          Measurement.PerInstructionValue, NewMeasurement.PerInstructionValue);
      Measurement.PerSnippetValue =
          std::min(Measurement.PerSnippetValue, NewMeasurement.PerSnippetValue);
    }
  }

  // We successfully measured everything, so don't discard the results.
  CBOR.disarm();
  return InstrBenchmark;
}

Expected<std::string>
BenchmarkRunner::writeObjectFile(const BenchmarkCode &BC,
                                 const FillFunction &FillFunction) const {
  int ResultFD = 0;
  SmallString<256> ResultPath;
  if (Error E = errorCodeToError(
          sys::fs::createTemporaryFile("snippet", "o", ResultFD, ResultPath)))
    return std::move(E);
  raw_fd_ostream OFS(ResultFD, true /*ShouldClose*/);
  if (Error E = assembleToStream(
          State.getExegesisTarget(), State.createTargetMachine(), BC.LiveIns,
          BC.Key.RegisterInitialValues, FillFunction, OFS)) {
    return std::move(E);
  }
  return std::string(ResultPath.str());
}

BenchmarkRunner::FunctionExecutor::~FunctionExecutor() {}

} // namespace exegesis
} // namespace llvm
