//===- bolt/Rewrite/BoltDiff.cpp ------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// RewriteInstance methods related to comparing one instance to another, used
// by the boltdiff tool to print a report.
//
//===----------------------------------------------------------------------===//

#include "bolt/Passes/IdenticalCodeFolding.h"
#include "bolt/Profile/ProfileReaderBase.h"
#include "bolt/Rewrite/RewriteInstance.h"
#include "bolt/Utils/Utils.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/CommandLine.h"

#undef  DEBUG_TYPE
#define DEBUG_TYPE "boltdiff"

using namespace llvm;
using namespace object;
using namespace bolt;

namespace opts {
extern cl::OptionCategory BoltDiffCategory;
extern cl::opt<bool> NeverPrint;
extern cl::opt<bolt::IdenticalCodeFolding::ICFLevel, false,
               llvm::bolt::DeprecatedICFNumericOptionParser>
    ICF;

static cl::opt<bool> IgnoreLTOSuffix(
    "ignore-lto-suffix",
    cl::desc("ignore lto_priv or const suffixes when matching functions"),
    cl::init(true), cl::cat(BoltDiffCategory));

static cl::opt<bool> PrintUnmapped(
    "print-unmapped",
    cl::desc("print functions of binary 2 that were not matched to any "
             "function in binary 1"),
    cl::cat(BoltDiffCategory));

static cl::opt<bool> PrintProfiledUnmapped(
    "print-profiled-unmapped",
    cl::desc("print functions that have profile in binary 1 but do not "
             "in binary 2"),
    cl::cat(BoltDiffCategory));

static cl::opt<bool> PrintDiffCFG(
    "print-diff-cfg",
    cl::desc("print the CFG of important functions that changed in "
             "binary 2"),
    cl::cat(BoltDiffCategory));

static cl::opt<bool>
    PrintDiffBBs("print-diff-bbs",
                 cl::desc("print the basic blocks showed in top differences"),
                 cl::cat(BoltDiffCategory));

static cl::opt<bool> MatchByHash(
    "match-by-hash",
    cl::desc("match functions in binary 2 to binary 1 if they have the same "
             "hash of a function in binary 1"),
    cl::cat(BoltDiffCategory));

static cl::opt<bool> IgnoreUnchanged(
    "ignore-unchanged",
    cl::desc("do not diff functions whose contents have not been changed from "
             "one binary to another"),
    cl::cat(BoltDiffCategory));

static cl::opt<unsigned> DisplayCount(
    "display-count",
    cl::desc("number of functions to display when printing the top largest "
             "differences in function activity"),
    cl::init(10), cl::cat(BoltDiffCategory));

static cl::opt<bool> NormalizeByBin1(
    "normalize-by-bin1",
    cl::desc("show execution count of functions in binary 2 as a ratio of the "
             "total samples in binary 1 - make sure both profiles have equal "
             "collection time and sampling rate for this to make sense"),
    cl::cat(BoltDiffCategory));

static cl::opt<bool>
    SkipNonSimple("skip-non-simple",
                  cl::desc("skip non-simple functions in reporting"),
                  cl::ReallyHidden, cl::cat(BoltDiffCategory));

} // end namespace opts

namespace llvm {
namespace bolt {

namespace {

/// Helper used to print colored numbers
void printColoredPercentage(double Perc) {
  if (outs().has_colors() && Perc > 0.0)
    outs().changeColor(raw_ostream::RED);
  else if (outs().has_colors() && Perc < 0.0)
    outs().changeColor(raw_ostream::GREEN);
  else if (outs().has_colors())
    outs().changeColor(raw_ostream::YELLOW);
  outs() << format("%.2f", Perc) << "%";
  if (outs().has_colors())
    outs().resetColor();
}

void setLightColor() {
  if (opts::PrintDiffBBs && outs().has_colors())
    outs().changeColor(raw_ostream::CYAN);
}

void setTitleColor() {
  if (outs().has_colors())
    outs().changeColor(raw_ostream::WHITE, /*Bold=*/true);
}

void setRegularColor() {
  if (outs().has_colors())
    outs().resetColor();
}

} // end anonymous namespace

/// Perform the comparison between two binaries with profiling information
class RewriteInstanceDiff {
  typedef std::tuple<const BinaryBasicBlock *, const BinaryBasicBlock *, double>
      EdgeTy;

  RewriteInstance &RI1;
  RewriteInstance &RI2;

  // The map of functions keyed by functions in binary 2, providing its
  // corresponding function in binary 1
  std::map<const BinaryFunction *, const BinaryFunction *> FuncMap;

  // The map of basic blocks correspondence, analogue to FuncMap for BBs,
  // sorted by score difference
  std::map<const BinaryBasicBlock *, const BinaryBasicBlock *> BBMap;

  // The map of edge correspondence
  std::map<double, std::pair<EdgeTy, EdgeTy>> EdgeMap;

  // Maps all known basic blocks back to their parent function
  std::map<const BinaryBasicBlock *, const BinaryFunction *> BBToFuncMap;

  // Accounting which functions were matched
  std::set<const BinaryFunction *> Bin1MappedFuncs;
  std::set<const BinaryFunction *> Bin2MappedFuncs;

  // Structures for our 3 matching strategies: by name, by hash and by lto name,
  // from the strongest to the weakest bind between two functions
  StringMap<const BinaryFunction *> NameLookup;
  DenseMap<size_t, const BinaryFunction *> HashLookup;
  StringMap<const BinaryFunction *> LTONameLookup1;
  StringMap<const BinaryFunction *> LTONameLookup2;

  // Score maps used to order and find hottest functions
  std::multimap<double, const BinaryFunction *> LargestBin1;
  std::multimap<double, const BinaryFunction *> LargestBin2;

  // Map multiple functions in the same LTO bucket to a single parent function
  // representing all functions sharing the same prefix
  std::map<const BinaryFunction *, const BinaryFunction *> LTOMap1;
  std::map<const BinaryFunction *, const BinaryFunction *> LTOMap2;
  std::map<const BinaryFunction *, double> LTOAggregatedScore1;
  std::map<const BinaryFunction *, double> LTOAggregatedScore2;

  // Map scores in bin2 and 1 keyed by a binary 2 function - post-matching
  DenseMap<const BinaryFunction *, std::pair<double, double>> ScoreMap;

  double getNormalizedScore(const BinaryFunction &Function,
                            const RewriteInstance &Ctx) {
    if (!opts::NormalizeByBin1)
      return static_cast<double>(Function.getFunctionScore()) /
             Ctx.getTotalScore();
    return static_cast<double>(Function.getFunctionScore()) /
           RI1.getTotalScore();
  }

  double getNormalizedScore(const BinaryBasicBlock &BB,
                            const RewriteInstance &Ctx) {
    if (!opts::NormalizeByBin1)
      return static_cast<double>(BB.getKnownExecutionCount()) /
             Ctx.getTotalScore();
    return static_cast<double>(BB.getKnownExecutionCount()) /
           RI1.getTotalScore();
  }

  double getNormalizedScore(BinaryBasicBlock::const_branch_info_iterator BIIter,
                            const RewriteInstance &Ctx) {
    double Score =
        BIIter->Count == BinaryBasicBlock::COUNT_NO_PROFILE ? 0 : BIIter->Count;
    if (!opts::NormalizeByBin1)
      return Score / Ctx.getTotalScore();
    return Score / RI1.getTotalScore();
  }

  /// Initialize data structures used for function lookup in binary 1, used
  /// later when matching functions in binary 2 to corresponding functions
  /// in binary 1
  void buildLookupMaps() {
    for (const auto &BFI : RI1.BC->getBinaryFunctions()) {
      StringRef LTOName;
      const BinaryFunction &Function = BFI.second;
      const double Score = getNormalizedScore(Function, RI1);
      LargestBin1.insert(std::make_pair<>(Score, &Function));
      for (const StringRef &Name : Function.getNames()) {
        if (std::optional<StringRef> OptionalLTOName = getLTOCommonName(Name))
          LTOName = *OptionalLTOName;
        NameLookup[Name] = &Function;
      }
      if (opts::MatchByHash && Function.hasCFG())
        HashLookup[Function.computeHash(/*UseDFS=*/true)] = &Function;
      if (opts::IgnoreLTOSuffix && !LTOName.empty()) {
        if (!LTONameLookup1.count(LTOName))
          LTONameLookup1[LTOName] = &Function;
        LTOMap1[&Function] = LTONameLookup1[LTOName];
      }
    }

    // Compute LTONameLookup2 and LargestBin2
    for (const auto &BFI : RI2.BC->getBinaryFunctions()) {
      StringRef LTOName;
      const BinaryFunction &Function = BFI.second;
      const double Score = getNormalizedScore(Function, RI2);
      LargestBin2.insert(std::make_pair<>(Score, &Function));
      for (const StringRef &Name : Function.getNames()) {
        if (std::optional<StringRef> OptionalLTOName = getLTOCommonName(Name))
          LTOName = *OptionalLTOName;
      }
      if (opts::IgnoreLTOSuffix && !LTOName.empty()) {
        if (!LTONameLookup2.count(LTOName))
          LTONameLookup2[LTOName] = &Function;
        LTOMap2[&Function] = LTONameLookup2[LTOName];
      }
    }
  }

  /// Match functions in binary 2 with functions in binary 1
  void matchFunctions() {
    outs() << "BOLT-DIFF: Mapping functions in Binary2 to Binary1\n";
    uint64_t BothHaveProfile = 0ull;
    std::set<const BinaryFunction *> Bin1ProfiledMapped;

    for (const auto &BFI2 : RI2.BC->getBinaryFunctions()) {
      const BinaryFunction &Function2 = BFI2.second;
      StringRef LTOName;
      bool Match = false;
      for (const StringRef &Name : Function2.getNames()) {
        auto Iter = NameLookup.find(Name);
        if (std::optional<StringRef> OptionalLTOName = getLTOCommonName(Name))
          LTOName = *OptionalLTOName;
        if (Iter == NameLookup.end())
          continue;
        FuncMap.insert(std::make_pair<>(&Function2, Iter->second));
        Bin1MappedFuncs.insert(Iter->second);
        Bin2MappedFuncs.insert(&Function2);
        if (Function2.hasValidProfile() && Iter->second->hasValidProfile()) {
          ++BothHaveProfile;
          Bin1ProfiledMapped.insert(Iter->second);
        }
        Match = true;
        break;
      }
      if (Match || !Function2.hasCFG())
        continue;
      auto Iter = HashLookup.find(Function2.computeHash(/*UseDFS*/ true));
      if (Iter != HashLookup.end()) {
        FuncMap.insert(std::make_pair<>(&Function2, Iter->second));
        Bin1MappedFuncs.insert(Iter->second);
        Bin2MappedFuncs.insert(&Function2);
        if (Function2.hasValidProfile() && Iter->second->hasValidProfile()) {
          ++BothHaveProfile;
          Bin1ProfiledMapped.insert(Iter->second);
        }
        continue;
      }
      if (LTOName.empty())
        continue;
      auto LTOIter = LTONameLookup1.find(LTOName);
      if (LTOIter != LTONameLookup1.end()) {
        FuncMap.insert(std::make_pair<>(&Function2, LTOIter->second));
        Bin1MappedFuncs.insert(LTOIter->second);
        Bin2MappedFuncs.insert(&Function2);
        if (Function2.hasValidProfile() && LTOIter->second->hasValidProfile()) {
          ++BothHaveProfile;
          Bin1ProfiledMapped.insert(LTOIter->second);
        }
      }
    }
    PrintProgramStats PPS;
    outs() << "* BOLT-DIFF: Starting print program stats pass for binary 1\n";
    RI1.BC->logBOLTErrorsAndQuitOnFatal(PPS.runOnFunctions(*RI1.BC));
    outs() << "* BOLT-DIFF: Starting print program stats pass for binary 2\n";
    RI1.BC->logBOLTErrorsAndQuitOnFatal(PPS.runOnFunctions(*RI2.BC));
    outs() << "=====\n";
    outs() << "Inputs share " << BothHaveProfile
           << " functions with valid profile.\n";
    if (opts::PrintProfiledUnmapped) {
      outs() << "\nFunctions in profile 1 that are missing in the profile 2:\n";
      std::vector<const BinaryFunction *> Unmapped;
      for (const auto &BFI : RI1.BC->getBinaryFunctions()) {
        const BinaryFunction &Function = BFI.second;
        if (!Function.hasValidProfile() || Bin1ProfiledMapped.count(&Function))
          continue;
        Unmapped.emplace_back(&Function);
      }
      llvm::sort(Unmapped,
                 [&](const BinaryFunction *A, const BinaryFunction *B) {
                   return A->getFunctionScore() > B->getFunctionScore();
                 });
      for (const BinaryFunction *Function : Unmapped) {
        outs() << Function->getPrintName() << " : ";
        outs() << Function->getFunctionScore() << "\n";
      }
      outs() << "=====\n";
    }
  }

  /// Check if opcodes in BB1 match those in BB2
  bool compareBBs(const BinaryBasicBlock &BB1,
                  const BinaryBasicBlock &BB2) const {
    auto Iter1 = BB1.begin();
    auto Iter2 = BB2.begin();
    if ((Iter1 == BB1.end() && Iter2 != BB2.end()) ||
        (Iter1 != BB1.end() && Iter2 == BB2.end()))
      return false;

    while (Iter1 != BB1.end()) {
      if (Iter2 == BB2.end() || Iter1->getOpcode() != Iter2->getOpcode())
        return false;

      ++Iter1;
      ++Iter2;
    }

    if (Iter2 != BB2.end())
      return false;
    return true;
  }

  /// For a function in binary 2 that matched one in binary 1, now match each
  /// individual basic block in it to its corresponding blocks in binary 1.
  /// Also match each edge in binary 2 to the corresponding ones in binary 1.
  void matchBasicBlocks() {
    for (const auto &MapEntry : FuncMap) {
      const BinaryFunction *const &Func1 = MapEntry.second;
      const BinaryFunction *const &Func2 = MapEntry.first;

      auto Iter1 = Func1->getLayout().block_begin();
      auto Iter2 = Func2->getLayout().block_begin();

      bool Match = true;
      std::map<const BinaryBasicBlock *, const BinaryBasicBlock *> Map;
      std::map<double, std::pair<EdgeTy, EdgeTy>> EMap;
      while (Iter1 != Func1->getLayout().block_end()) {
        if (Iter2 == Func2->getLayout().block_end()) {
          Match = false;
          break;
        }
        if (!compareBBs(**Iter1, **Iter2)) {
          Match = false;
          break;
        }
        Map.insert(std::make_pair<>(*Iter2, *Iter1));

        auto SuccIter1 = (*Iter1)->succ_begin();
        auto SuccIter2 = (*Iter2)->succ_begin();
        auto BIIter1 = (*Iter1)->branch_info_begin();
        auto BIIter2 = (*Iter2)->branch_info_begin();
        while (SuccIter1 != (*Iter1)->succ_end()) {
          if (SuccIter2 == (*Iter2)->succ_end()) {
            Match = false;
            break;
          }
          const double ScoreEdge1 = getNormalizedScore(BIIter1, RI1);
          const double ScoreEdge2 = getNormalizedScore(BIIter2, RI2);
          EMap.insert(std::make_pair<>(
              std::abs(ScoreEdge2 - ScoreEdge1),
              std::make_pair<>(
                  std::make_tuple<>(*Iter2, *SuccIter2, ScoreEdge2),
                  std::make_tuple<>(*Iter1, *SuccIter1, ScoreEdge1))));

          ++SuccIter1;
          ++SuccIter2;
          ++BIIter1;
          ++BIIter2;
        }
        if (SuccIter2 != (*Iter2)->succ_end())
          Match = false;
        if (!Match)
          break;

        BBToFuncMap[*Iter1] = Func1;
        BBToFuncMap[*Iter2] = Func2;
        ++Iter1;
        ++Iter2;
      }
      if (!Match || Iter2 != Func2->getLayout().block_end())
        continue;

      BBMap.insert(Map.begin(), Map.end());
      EdgeMap.insert(EMap.begin(), EMap.end());
    }
  }

  /// Print the largest differences in basic block performance from binary 1
  /// to binary 2
  void reportHottestBBDiffs() {
    std::map<double, const BinaryBasicBlock *> LargestDiffs;
    for (const auto &MapEntry : BBMap) {
      const BinaryBasicBlock *BB2 = MapEntry.first;
      const BinaryBasicBlock *BB1 = MapEntry.second;
      LargestDiffs.insert(
          std::make_pair<>(std::abs(getNormalizedScore(*BB2, RI2) -
                                    getNormalizedScore(*BB1, RI1)),
                           BB2));
    }

    unsigned Printed = 0;
    setTitleColor();
    outs()
        << "\nTop " << opts::DisplayCount
        << " largest differences in basic block performance bin 2 -> bin 1:\n";
    outs() << "=========================================================\n";
    setRegularColor();
    outs() << " * Functions with different contents do not appear here\n\n";
    for (const BinaryBasicBlock *BB2 :
         llvm::make_second_range(llvm::reverse(LargestDiffs))) {
      const double Score2 = getNormalizedScore(*BB2, RI2);
      const double Score1 = getNormalizedScore(*BBMap[BB2], RI1);
      const BinaryFunction *Func = BBToFuncMap[BB2];
      if (opts::SkipNonSimple && !Func->isSimple())
        continue;
      outs() << "BB " << BB2->getName() << " from " << Func->getDemangledName()
             << "\n\tScore bin1 = " << format("%.4f", Score1 * 100.0)
             << "%\n\tScore bin2 = " << format("%.4f", Score2 * 100.0);
      outs() << "%\t(Difference: ";
      printColoredPercentage((Score2 - Score1) * 100.0);
      outs() << ")\n";
      if (opts::PrintDiffBBs) {
        setLightColor();
        BB2->dump();
        setRegularColor();
      }
      if (Printed++ == opts::DisplayCount)
        break;
    }
  }

  /// Print the largest differences in edge counts from one binary to another
  void reportHottestEdgeDiffs() {
    unsigned Printed = 0;
    setTitleColor();
    outs() << "\nTop " << opts::DisplayCount
           << " largest differences in edge hotness bin 2 -> bin 1:\n";
    outs() << "=========================================================\n";
    setRegularColor();
    outs() << " * Functions with different contents do not appear here\n";
    for (std::pair<EdgeTy, EdgeTy> &EI :
         llvm::make_second_range(llvm::reverse(EdgeMap))) {
      EdgeTy &Edge2 = EI.first;
      EdgeTy &Edge1 = EI.second;
      const double Score2 = std::get<2>(Edge2);
      const double Score1 = std::get<2>(Edge1);
      const BinaryFunction *Func = BBToFuncMap[std::get<0>(Edge2)];
      if (opts::SkipNonSimple && !Func->isSimple())
        continue;
      outs() << "Edge (" << std::get<0>(Edge2)->getName() << " -> "
             << std::get<1>(Edge2)->getName() << ") in "
             << Func->getDemangledName()
             << "\n\tScore bin1 = " << format("%.4f", Score1 * 100.0)
             << "%\n\tScore bin2 = " << format("%.4f", Score2 * 100.0);
      outs() << "%\t(Difference: ";
      printColoredPercentage((Score2 - Score1) * 100.0);
      outs() << ")\n";
      if (opts::PrintDiffBBs) {
        setLightColor();
        std::get<0>(Edge2)->dump();
        std::get<1>(Edge2)->dump();
        setRegularColor();
      }
      if (Printed++ == opts::DisplayCount)
        break;
    }
  }

  /// For LTO functions sharing the same prefix (for example, func1.lto_priv.1
  /// and func1.lto_priv.2 share the func1.lto_priv prefix), compute aggregated
  /// scores for them. This is used to avoid reporting all LTO functions as
  /// having a large difference in performance because hotness shifted from
  /// LTO variant 1 to variant 2, even though they represent the same function.
  void computeAggregatedLTOScore() {
    for (const auto &BFI : RI1.BC->getBinaryFunctions()) {
      const BinaryFunction &Function = BFI.second;
      double Score = getNormalizedScore(Function, RI1);
      auto Iter = LTOMap1.find(&Function);
      if (Iter == LTOMap1.end())
        continue;
      LTOAggregatedScore1[Iter->second] += Score;
    }

    double UnmappedScore = 0;
    for (const auto &BFI : RI2.BC->getBinaryFunctions()) {
      const BinaryFunction &Function = BFI.second;
      bool Matched = FuncMap.find(&Function) != FuncMap.end();
      double Score = getNormalizedScore(Function, RI2);
      auto Iter = LTOMap2.find(&Function);
      if (Iter == LTOMap2.end()) {
        if (!Matched)
          UnmappedScore += Score;
        continue;
      }
      LTOAggregatedScore2[Iter->second] += Score;
      if (FuncMap.find(Iter->second) == FuncMap.end())
        UnmappedScore += Score;
    }
    int64_t Unmapped =
        RI2.BC->getBinaryFunctions().size() - Bin2MappedFuncs.size();
    outs() << "BOLT-DIFF: " << Unmapped
           << " functions in Binary2 have no correspondence to any other "
              "function in Binary1.\n";

    // Print the hotness score of functions in binary 2 that were not matched
    // to any function in binary 1
    outs() << "BOLT-DIFF: These unmapped functions in Binary2 represent "
           << format("%.2f", UnmappedScore * 100.0) << "% of execution.\n";
  }

  /// Print the largest hotness differences from binary 2 to binary 1
  void reportHottestFuncDiffs() {
    std::multimap<double, decltype(FuncMap)::value_type> LargestDiffs;
    for (const auto &MapEntry : FuncMap) {
      const BinaryFunction *const &Func1 = MapEntry.second;
      const BinaryFunction *const &Func2 = MapEntry.first;
      double Score1 = getNormalizedScore(*Func1, RI1);
      auto Iter1 = LTOMap1.find(Func1);
      if (Iter1 != LTOMap1.end())
        Score1 = LTOAggregatedScore1[Iter1->second];
      double Score2 = getNormalizedScore(*Func2, RI2);
      auto Iter2 = LTOMap2.find(Func2);
      if (Iter2 != LTOMap2.end())
        Score2 = LTOAggregatedScore2[Iter2->second];
      if (Score1 == 0.0 || Score2 == 0.0)
        continue;
      if (opts::SkipNonSimple && !Func1->isSimple() && !Func2->isSimple())
        continue;
      LargestDiffs.insert(
          std::make_pair<>(std::abs(Score1 - Score2), MapEntry));
      ScoreMap[Func2] = std::make_pair<>(Score1, Score2);
    }

    unsigned Printed = 0;
    setTitleColor();
    outs() << "\nTop " << opts::DisplayCount
           << " largest differences in performance bin 2 -> bin 1:\n";
    outs() << "=========================================================\n";
    setRegularColor();
    for (decltype(this->FuncMap)::value_type &MapEntry :
         llvm::make_second_range(llvm::reverse(LargestDiffs))) {
      if (opts::IgnoreUnchanged &&
          MapEntry.second->computeHash(/*UseDFS=*/true) ==
              MapEntry.first->computeHash(/*UseDFS=*/true))
        continue;
      const std::pair<double, double> &Scores = ScoreMap[MapEntry.first];
      outs() << "Function " << MapEntry.first->getDemangledName();
      if (MapEntry.first->getDemangledName() !=
          MapEntry.second->getDemangledName())
        outs() << "\nmatched  " << MapEntry.second->getDemangledName();
      outs() << "\n\tScore bin1 = " << format("%.2f", Scores.first * 100.0)
             << "%\n\tScore bin2 = " << format("%.2f", Scores.second * 100.0)
             << "%\t(Difference: ";
      printColoredPercentage((Scores.second - Scores.first) * 100.0);
      outs() << ")";
      if (MapEntry.second->computeHash(/*UseDFS=*/true) !=
          MapEntry.first->computeHash(/*UseDFS=*/true)) {
        outs() << "\t[Functions have different contents]";
        if (opts::PrintDiffCFG) {
          outs() << "\n *** CFG for function in binary 1:\n";
          setLightColor();
          MapEntry.second->dump();
          setRegularColor();
          outs() << "\n *** CFG for function in binary 2:\n";
          setLightColor();
          MapEntry.first->dump();
          setRegularColor();
        }
      }
      outs() << "\n";
      if (Printed++ == opts::DisplayCount)
        break;
    }
  }

  /// Print hottest functions from each binary
  void reportHottestFuncs() {
    unsigned Printed = 0;
    setTitleColor();
    outs() << "\nTop " << opts::DisplayCount
           << " hottest functions in binary 2:\n";
    outs() << "=====================================\n";
    setRegularColor();
    for (std::pair<const double, const BinaryFunction *> &MapEntry :
         llvm::reverse(LargestBin2)) {
      outs() << "Function " << MapEntry.second->getDemangledName() << "\n";
      auto Iter = ScoreMap.find(MapEntry.second);
      if (Iter != ScoreMap.end())
        outs() << "\tScore bin1 = "
               << format("%.2f", Iter->second.first * 100.0) << "%\n";
      outs() << "\tScore bin2 = " << format("%.2f", MapEntry.first * 100.0)
             << "%\n";
      if (Printed++ == opts::DisplayCount)
        break;
    }

    Printed = 0;
    setTitleColor();
    outs() << "\nTop " << opts::DisplayCount
           << " hottest functions in binary 1:\n";
    outs() << "=====================================\n";
    setRegularColor();
    for (const std::pair<const double, const BinaryFunction *> &MapEntry :
         llvm::reverse(LargestBin1)) {
      outs() << "Function " << MapEntry.second->getDemangledName()
             << "\n\tScore bin1 = " << format("%.2f", MapEntry.first * 100.0)
             << "%\n";
      if (Printed++ == opts::DisplayCount)
        break;
    }
  }

  /// Print functions in binary 2 that did not match anything in binary 1.
  /// Unfortunately, in an LTO build, even a small change can lead to several
  /// LTO variants being unmapped, corresponding to local functions that never
  /// appear in one of the binaries because they were previously inlined.
  void reportUnmapped() {
    outs() << "List of functions from binary 2 that were not matched with any "
           << "function in binary 1:\n";
    for (const auto &BFI2 : RI2.BC->getBinaryFunctions()) {
      const BinaryFunction &Function2 = BFI2.second;
      if (Bin2MappedFuncs.count(&Function2))
        continue;
      outs() << Function2.getPrintName() << "\n";
    }
  }

public:
  /// Main entry point: coordinate all tasks necessary to compare two binaries
  void compareAndReport() {
    buildLookupMaps();
    matchFunctions();
    if (opts::IgnoreLTOSuffix)
      computeAggregatedLTOScore();
    matchBasicBlocks();
    reportHottestFuncDiffs();
    reportHottestBBDiffs();
    reportHottestEdgeDiffs();
    reportHottestFuncs();
    if (!opts::PrintUnmapped)
      return;
    reportUnmapped();
  }

  RewriteInstanceDiff(RewriteInstance &RI1, RewriteInstance &RI2)
      : RI1(RI1), RI2(RI2) {
    compareAndReport();
  }

};

} // end namespace bolt
} // end namespace llvm

void RewriteInstance::compare(RewriteInstance &RI2) {
  outs() << "BOLT-DIFF: ======== Binary1 vs. Binary2 ========\n";
  outs() << "Trace for binary 1 has " << this->getTotalScore()
         << " instructions executed.\n";
  outs() << "Trace for binary 2 has " << RI2.getTotalScore()
         << " instructions executed.\n";
  if (opts::NormalizeByBin1) {
    double Diff2to1 =
        static_cast<double>(RI2.getTotalScore() - this->getTotalScore()) /
        this->getTotalScore();
    outs() << "Binary2 change in score with respect to Binary1: ";
    printColoredPercentage(Diff2to1 * 100.0);
    outs() << "\n";
  }

  if (!this->getTotalScore() || !RI2.getTotalScore()) {
    outs() << "BOLT-DIFF: Both binaries must have recorded activity in known "
              "functions.\n";
    return;
  }

  // Pre-pass ICF
  if (opts::ICF != IdenticalCodeFolding::ICFLevel::None) {
    IdenticalCodeFolding ICF(opts::NeverPrint);
    outs() << "BOLT-DIFF: Starting ICF pass for binary 1";
    BC->logBOLTErrorsAndQuitOnFatal(ICF.runOnFunctions(*BC));
    outs() << "BOLT-DIFF: Starting ICF pass for binary 2";
    BC->logBOLTErrorsAndQuitOnFatal(ICF.runOnFunctions(*RI2.BC));
  }

  RewriteInstanceDiff RID(*this, RI2);
}
