//===- InstrProfCorrelator.h ------------------------------------*- 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
//
//===----------------------------------------------------------------------===//
// This file defines InstrProfCorrelator used to generate PGO/coverage profiles
// from raw profile data and debug info/binary file.
//===----------------------------------------------------------------------===//

#ifndef LLVM_PROFILEDATA_INSTRPROFCORRELATOR_H
#define LLVM_PROFILEDATA_INSTRPROFCORRELATOR_H

#include "llvm/ADT/DenseSet.h"
#include "llvm/Debuginfod/BuildIDFetcher.h"
#include "llvm/Object/BuildID.h"
#include "llvm/ProfileData/InstrProf.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/YAMLTraits.h"
#include <optional>
#include <vector>

namespace llvm {
class DWARFContext;
class DWARFDie;
namespace object {
class ObjectFile;
}

/// InstrProfCorrelator - A base class used to create raw instrumentation data
/// to their functions.
class InstrProfCorrelator {
public:
  /// Indicate if we should use the debug info or profile metadata sections to
  /// correlate.
  enum ProfCorrelatorKind { NONE, DEBUG_INFO, BINARY };

  static llvm::Expected<std::unique_ptr<InstrProfCorrelator>>
  get(StringRef Filename, ProfCorrelatorKind FileKind,
      const object::BuildIDFetcher *BIDFetcher = nullptr,
      const ArrayRef<llvm::object::BuildID> BIs = {});

  /// Construct a ProfileData vector used to correlate raw instrumentation data
  /// to their functions.
  /// \param MaxWarnings the maximum number of warnings to emit (0 = no limit)
  virtual Error correlateProfileData(int MaxWarnings) = 0;

  /// Process debug info and dump the correlation data.
  /// \param MaxWarnings the maximum number of warnings to emit (0 = no limit)
  virtual Error dumpYaml(int MaxWarnings, raw_ostream &OS) = 0;

  /// Return the number of ProfileData elements.
  std::optional<size_t> getDataSize() const;

  /// Return a pointer to the names string that this class constructs.
  const char *getNamesPointer() const { return Names.c_str(); }

  /// Return the number of bytes in the names string.
  size_t getNamesSize() const { return Names.size(); }

  /// Return the size of the counters section in bytes.
  uint64_t getCountersSectionSize() const {
    return Ctx->CountersSectionEnd - Ctx->CountersSectionStart;
  }

  static const char *FunctionNameAttributeName;
  static const char *CFGHashAttributeName;
  static const char *NumCountersAttributeName;

  enum InstrProfCorrelatorKind { CK_32Bit, CK_64Bit };
  InstrProfCorrelatorKind getKind() const { return Kind; }
  virtual ~InstrProfCorrelator() = default;

protected:
  struct Context {
    static llvm::Expected<std::unique_ptr<Context>>
    get(std::unique_ptr<MemoryBuffer> Buffer, const object::ObjectFile &Obj,
        ProfCorrelatorKind FileKind);
    std::unique_ptr<MemoryBuffer> Buffer;
    /// The address range of the __llvm_prf_cnts section.
    uint64_t CountersSectionStart;
    uint64_t CountersSectionEnd;
    /// The pointer points to start/end of profile data/name sections if
    /// FileKind is Binary.
    const char *DataStart;
    const char *DataEnd;
    const char *NameStart;
    size_t NameSize;
    /// True if target and host have different endian orders.
    bool ShouldSwapBytes;
  };
  const std::unique_ptr<Context> Ctx;

  InstrProfCorrelator(InstrProfCorrelatorKind K, std::unique_ptr<Context> Ctx)
      : Ctx(std::move(Ctx)), Kind(K) {}

  std::string Names;
  std::vector<std::string> NamesVec;

  struct Probe {
    std::string FunctionName;
    std::optional<std::string> LinkageName;
    yaml::Hex64 CFGHash;
    yaml::Hex64 CounterOffset;
    uint32_t NumCounters;
    std::optional<std::string> FilePath;
    std::optional<int> LineNumber;
  };

  struct CorrelationData {
    std::vector<Probe> Probes;
  };

  friend struct yaml::MappingTraits<Probe>;
  friend struct yaml::SequenceElementTraits<Probe>;
  friend struct yaml::MappingTraits<CorrelationData>;

private:
  static llvm::Expected<std::unique_ptr<InstrProfCorrelator>>
  get(std::unique_ptr<MemoryBuffer> Buffer, ProfCorrelatorKind FileKind);

  const InstrProfCorrelatorKind Kind;
};

/// InstrProfCorrelatorImpl - A child of InstrProfCorrelator with a template
/// pointer type so that the ProfileData vector can be materialized.
template <class IntPtrT>
class InstrProfCorrelatorImpl : public InstrProfCorrelator {
public:
  InstrProfCorrelatorImpl(std::unique_ptr<InstrProfCorrelator::Context> Ctx);
  static bool classof(const InstrProfCorrelator *C);

  /// Return a pointer to the underlying ProfileData vector that this class
  /// constructs.
  const RawInstrProf::ProfileData<IntPtrT> *getDataPointer() const {
    return Data.empty() ? nullptr : Data.data();
  }

  /// Return the number of ProfileData elements.
  size_t getDataSize() const { return Data.size(); }

  static llvm::Expected<std::unique_ptr<InstrProfCorrelatorImpl<IntPtrT>>>
  get(std::unique_ptr<InstrProfCorrelator::Context> Ctx,
      const object::ObjectFile &Obj, ProfCorrelatorKind FileKind);

protected:
  std::vector<RawInstrProf::ProfileData<IntPtrT>> Data;

  Error correlateProfileData(int MaxWarnings) override;
  virtual void correlateProfileDataImpl(
      int MaxWarnings,
      InstrProfCorrelator::CorrelationData *Data = nullptr) = 0;

  virtual Error correlateProfileNameImpl() = 0;

  Error dumpYaml(int MaxWarnings, raw_ostream &OS) override;

  void addDataProbe(uint64_t FunctionName, uint64_t CFGHash,
                    IntPtrT CounterOffset, IntPtrT FunctionPtr,
                    uint32_t NumCounters);

  // Byte-swap the value if necessary.
  template <class T> T maybeSwap(T Value) const {
    return Ctx->ShouldSwapBytes ? llvm::byteswap(Value) : Value;
  }

private:
  InstrProfCorrelatorImpl(InstrProfCorrelatorKind Kind,
                          std::unique_ptr<InstrProfCorrelator::Context> Ctx)
      : InstrProfCorrelator(Kind, std::move(Ctx)){};
  llvm::DenseSet<IntPtrT> CounterOffsets;
};

/// DwarfInstrProfCorrelator - A child of InstrProfCorrelatorImpl that takes
/// DWARF debug info as input to correlate profiles.
template <class IntPtrT>
class DwarfInstrProfCorrelator : public InstrProfCorrelatorImpl<IntPtrT> {
public:
  DwarfInstrProfCorrelator(std::unique_ptr<DWARFContext> DICtx,
                           std::unique_ptr<InstrProfCorrelator::Context> Ctx)
      : InstrProfCorrelatorImpl<IntPtrT>(std::move(Ctx)),
        DICtx(std::move(DICtx)) {}

private:
  std::unique_ptr<DWARFContext> DICtx;

  /// Return the address of the object that the provided DIE symbolizes.
  std::optional<uint64_t> getLocation(const DWARFDie &Die) const;

  /// Returns true if the provided DIE symbolizes an instrumentation probe
  /// symbol.
  static bool isDIEOfProbe(const DWARFDie &Die);

  /// Iterate over DWARF DIEs to find those that symbolize instrumentation
  /// probes and construct the ProfileData vector and Names string.
  ///
  /// Here is some example DWARF for an instrumentation probe we are looking
  /// for:
  /// \code
  ///   DW_TAG_subprogram
  ///   DW_AT_low_pc	(0x0000000000000000)
  ///   DW_AT_high_pc	(0x0000000000000014)
  ///   DW_AT_name	("foo")
  ///     DW_TAG_variable
  ///       DW_AT_name	("__profc_foo")
  ///       DW_AT_location	(DW_OP_addr 0x0)
  ///       DW_TAG_LLVM_annotation
  ///         DW_AT_name	("Function Name")
  ///         DW_AT_const_value	("foo")
  ///       DW_TAG_LLVM_annotation
  ///         DW_AT_name	("CFG Hash")
  ///         DW_AT_const_value	(12345678)
  ///       DW_TAG_LLVM_annotation
  ///         DW_AT_name	("Num Counters")
  ///         DW_AT_const_value	(2)
  ///       NULL
  ///     NULL
  /// \endcode
  /// \param MaxWarnings the maximum number of warnings to emit (0 = no limit)
  /// \param Data if provided, populate with the correlation data found
  void correlateProfileDataImpl(
      int MaxWarnings,
      InstrProfCorrelator::CorrelationData *Data = nullptr) override;

  Error correlateProfileNameImpl() override;
};

/// BinaryInstrProfCorrelator - A child of InstrProfCorrelatorImpl that
/// takes an object file as input to correlate profiles.
template <class IntPtrT>
class BinaryInstrProfCorrelator : public InstrProfCorrelatorImpl<IntPtrT> {
public:
  BinaryInstrProfCorrelator(std::unique_ptr<InstrProfCorrelator::Context> Ctx)
      : InstrProfCorrelatorImpl<IntPtrT>(std::move(Ctx)) {}

  /// Return a pointer to the names string that this class constructs.
  const char *getNamesPointer() const { return this->Ctx.NameStart; }

  /// Return the number of bytes in the names string.
  size_t getNamesSize() const { return this->Ctx.NameSize; }

private:
  void correlateProfileDataImpl(
      int MaxWarnings,
      InstrProfCorrelator::CorrelationData *Data = nullptr) override;

  Error correlateProfileNameImpl() override;
};

} // end namespace llvm

#endif // LLVM_PROFILEDATA_INSTRPROFCORRELATOR_H
