//===- bolt/Core/DynoStats.h - Dynamic execution stats ----------*- 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
//
//===----------------------------------------------------------------------===//
//
// Keep track of statistics about the trace of execution captured in BOLT
// profile.
//
//===----------------------------------------------------------------------===//

#ifndef BOLT_CORE_DYNO_STATS_H
#define BOLT_CORE_DYNO_STATS_H

#include "llvm/ADT/ArrayRef.h"
#include "llvm/MC/MCInstPrinter.h"
#include "llvm/Support/raw_ostream.h"
#include <map>
#include <unordered_map>

namespace llvm {

namespace bolt {
class BinaryFunction;

/// Class encapsulating runtime statistics about an execution unit.
class DynoStats {

#define REAL_DYNO_STATS                                                        \
  D(FORWARD_COND_BRANCHES, "executed forward branches", Fn)                    \
  D(FORWARD_COND_BRANCHES_TAKEN, "taken forward branches", Fn)                 \
  D(BACKWARD_COND_BRANCHES, "executed backward branches", Fn)                  \
  D(BACKWARD_COND_BRANCHES_TAKEN, "taken backward branches", Fn)               \
  D(UNCOND_BRANCHES, "executed unconditional branches", Fn)                    \
  D(FUNCTION_CALLS, "all function calls", Fn)                                  \
  D(INDIRECT_CALLS, "indirect calls", Fn)                                      \
  D(PLT_CALLS, "PLT calls", Fn)                                                \
  D(INSTRUCTIONS, "executed instructions", Fn)                                 \
  D(LOADS, "executed load instructions", Fn)                                   \
  D(STORES, "executed store instructions", Fn)                                 \
  D(JUMP_TABLE_BRANCHES, "taken jump table branches", Fn)                      \
  D(UNKNOWN_INDIRECT_BRANCHES, "taken unknown indirect branches", Fn)          \
  D(ALL_BRANCHES, "total branches", Fadd(ALL_CONDITIONAL, UNCOND_BRANCHES))    \
  D(ALL_TAKEN, "taken branches", Fadd(TAKEN_CONDITIONAL, UNCOND_BRANCHES))     \
  D(NONTAKEN_CONDITIONAL, "non-taken conditional branches",                    \
    Fsub(ALL_CONDITIONAL, TAKEN_CONDITIONAL))                                  \
  D(TAKEN_CONDITIONAL, "taken conditional branches",                           \
    Fadd(FORWARD_COND_BRANCHES_TAKEN, BACKWARD_COND_BRANCHES_TAKEN))           \
  D(ALL_CONDITIONAL, "all conditional branches",                               \
    Fadd(FORWARD_COND_BRANCHES, BACKWARD_COND_BRANCHES))                       \
  D(VENEER_CALLS_AARCH64, "linker-inserted veneer calls", Fn)

#define DYNO_STATS                                                             \
  D(FIRST_DYNO_STAT, "<reserved>", 0)                                          \
  REAL_DYNO_STATS                                                              \
  D(LAST_DYNO_STAT, "<reserved>", 0)

public:
#define D(name, ...) name,
  enum Category : uint8_t { DYNO_STATS };
#undef D

private:
  uint64_t Stats[LAST_DYNO_STAT + 1];
  bool PrintAArch64Stats;

#define D(name, desc, ...) desc,
  static constexpr const char *Desc[] = { DYNO_STATS };
#undef D

public:
  DynoStats(bool PrintAArch64Stats) {
    this->PrintAArch64Stats = PrintAArch64Stats;
    for (auto Stat = FIRST_DYNO_STAT + 0; Stat < LAST_DYNO_STAT; ++Stat)
      Stats[Stat] = 0;
  }

  uint64_t &operator[](size_t I) {
    assert(I > FIRST_DYNO_STAT && I < LAST_DYNO_STAT && "index out of bounds");
    return Stats[I];
  }

  uint64_t operator[](size_t I) const {
    switch (I) {
#define D(name, desc, func) \
    case name: \
      return func;
#define Fn Stats[I]
#define Fadd(a, b) operator[](a) + operator[](b)
#define Fsub(a, b) operator[](a) - operator[](b)
#define F(a) operator[](a)
#define Radd(a, b) (a + b)
#define Rsub(a, b) (a - b)
    DYNO_STATS
#undef Rsub
#undef Radd
#undef F
#undef Fsub
#undef Fadd
#undef Fn
#undef D
    default:
      llvm_unreachable("index out of bounds");
    }
    return 0;
  }

  void print(raw_ostream &OS, const DynoStats *Other = nullptr,
             MCInstPrinter *Printer = nullptr) const;

  void operator+=(const DynoStats &Other);
  bool operator<(const DynoStats &Other) const;
  bool operator==(const DynoStats &Other) const;
  bool operator!=(const DynoStats &Other) const { return !operator==(Other); }
  bool lessThan(const DynoStats &Other, ArrayRef<Category> Keys) const;

  static const char *Description(const Category C) { return Desc[C]; }

  /// Maps instruction opcodes to:
  /// 1. Accumulated executed instruction counts.
  /// 2. a multimap that records highest execution counts, function names,
  /// and BB offsets where intructions of these opcodes occur.
  using MaxOpcodeHistogramTy =
      std::multimap<uint64_t, std::pair<StringRef, uint32_t>>;
  using OpcodeHistogramTy =
      std::unordered_map<unsigned, std::pair<uint64_t, MaxOpcodeHistogramTy>>;
  using OpcodeStatTy = OpcodeHistogramTy::value_type;

  OpcodeHistogramTy OpcodeHistogram;
};

inline raw_ostream &operator<<(raw_ostream &OS, const DynoStats &Stats) {
  Stats.print(OS, nullptr);
  return OS;
}

DynoStats operator+(const DynoStats &A, const DynoStats &B);

/// Return dynostats for the function.
///
/// The function relies on branch instructions being in-sync with CFG for
/// branch instructions stats. Thus it is better to call it after
/// fixBranches().
DynoStats getDynoStats(BinaryFunction &BF);

/// Return program-wide dynostats.
template <typename FuncsType>
inline DynoStats getDynoStats(FuncsType &Funcs, bool IsAArch64) {
  DynoStats dynoStats(IsAArch64);
  for (auto &BFI : Funcs) {
    auto &BF = BFI.second;
    if (BF.isSimple())
      dynoStats += getDynoStats(BF);
  }
  return dynoStats;
}

/// Call a function with optional before and after dynostats printing.
template <typename FnType, typename FuncsType>
inline void callWithDynoStats(FnType &&Func, FuncsType &Funcs, StringRef Phase,
                              const bool Flag, bool IsAArch64) {
  DynoStats DynoStatsBefore(IsAArch64);
  if (Flag)
    DynoStatsBefore = getDynoStats(Funcs, IsAArch64);

  Func();

  if (Flag) {
    const DynoStats DynoStatsAfter = getDynoStats(Funcs, IsAArch64);
    const bool Changed = (DynoStatsAfter != DynoStatsBefore);
    outs() << "BOLT-INFO: program-wide dynostats after running " << Phase
           << (Changed ? "" : " (no change)") << ":\n\n"
           << DynoStatsBefore << '\n';
    if (Changed)
      DynoStatsAfter.print(outs(), &DynoStatsBefore);
    outs() << '\n';
  }
}

} // namespace bolt
} // namespace llvm

#endif
