//===- bolt/Passes/AsmDump.cpp - Dump BinaryFunction into assembly --------===//
//
// 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 implements the AsmDumpPass class.
//
//===----------------------------------------------------------------------===//

#include "bolt/Passes/AsmDump.h"
#include "llvm/CodeGen/AsmPrinter.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Target/TargetMachine.h"
#include <unordered_set>

#define DEBUG_TYPE "asm-dump"

using namespace llvm;

namespace opts {
extern bool shouldPrint(const bolt::BinaryFunction &Function);
extern cl::OptionCategory BoltCategory;
extern cl::opt<unsigned> Verbosity;

cl::opt<std::string> AsmDump("asm-dump",
                             cl::desc("dump function into assembly"),
                             cl::value_desc("dump folder"), cl::ValueOptional,
                             cl::Hidden, cl::cat(BoltCategory));
} // end namespace opts

namespace llvm {
namespace bolt {

void dumpCFI(const BinaryFunction &BF, const MCInst &Instr, AsmPrinter &MAP) {
  const MCCFIInstruction *CFIInstr = BF.getCFIFor(Instr);
  switch (CFIInstr->getOperation()) {
  // Skip unsupported CFI instructions.
  case MCCFIInstruction::OpRememberState:
  case MCCFIInstruction::OpRestoreState:
    if (opts::Verbosity >= 2)
      errs()
          << "BOLT-WARNING: AsmDump: skipping unsupported CFI instruction in "
          << BF << ".\n";

    return;

  default:
    // Emit regular CFI instructions.
    MAP.emitCFIInstruction(*CFIInstr);
  }
}

void dumpJumpTableFdata(raw_ostream &OS, const BinaryFunction &BF,
                        const MCInst &Instr, const std::string &BranchLabel) {
  StringRef FunctionName = BF.getOneName();
  const JumpTable *JT = BF.getJumpTable(Instr);
  for (uint32_t i = 0; i < JT->Entries.size(); ++i) {
    StringRef TargetName = JT->Entries[i]->getName();
    const uint64_t Mispreds = JT->Counts[i].Mispreds;
    const uint64_t Count = JT->Counts[i].Count;
    OS << "# FDATA: 1 " << FunctionName << " #" << BranchLabel << "# "
       << "1 " << FunctionName << " #" << TargetName << "# " << Mispreds << " "
       << Count << '\n';
  }
}

void dumpTailCallFdata(raw_ostream &OS, const BinaryFunction &BF,
                       const MCInst &Instr, const std::string &BranchLabel) {
  const BinaryContext &BC = BF.getBinaryContext();
  StringRef FunctionName = BF.getOneName();
  auto CallFreq = BC.MIB->getAnnotationWithDefault<uint64_t>(Instr, "Count");
  const MCSymbol *Target = BC.MIB->getTargetSymbol(Instr);
  const BinaryFunction *TargetBF = BC.getFunctionForSymbol(Target);
  if (!TargetBF)
    return;
  OS << "# FDATA: 1 " << FunctionName << " #" << BranchLabel << "# "
     << "1 " << TargetBF->getPrintName() << " 0 "
     << "0 " << CallFreq << '\n';
}

void dumpTargetFunctionStub(raw_ostream &OS, const BinaryContext &BC,
                            const MCSymbol *CalleeSymb,
                            const BinarySection *&LastCS) {
  const BinaryFunction *CalleeFunc = BC.getFunctionForSymbol(CalleeSymb);
  if (!CalleeFunc || CalleeFunc->isPLTFunction())
    return;

  if (CalleeFunc->getOriginSection() != LastCS) {
    OS << ".section " << CalleeFunc->getOriginSectionName() << '\n';
    LastCS = CalleeFunc->getOriginSection();
  }
  StringRef CalleeName = CalleeFunc->getOneName();
  OS << ".set \"" << CalleeName << "\", 0\n";
}

void dumpJumpTableSymbols(raw_ostream &OS, const JumpTable *JT, AsmPrinter &MAP,
                          const BinarySection *&LastBS) {
  if (&JT->getSection() != LastBS) {
    OS << ".section " << JT->getSectionName() << '\n';
    LastBS = &JT->getSection();
  }
  OS << "\"" << JT->getName() << "\":\n";
  for (MCSymbol *JTEntry : JT->Entries)
    MAP.OutStreamer->emitSymbolValue(JTEntry, JT->OutputEntrySize);
  OS << '\n';
}

void dumpBinaryDataSymbols(raw_ostream &OS, const BinaryData *BD,
                           const BinarySection *&LastBS) {
  if (BD->isJumpTable())
    return;
  if (&BD->getSection() != LastBS) {
    OS << ".section " << BD->getSectionName() << '\n';
    LastBS = &BD->getSection();
  }
  OS << "\"" << BD->getName() << "\": ";
  OS << '\n';
}

void dumpFunction(const BinaryFunction &BF) {
  const BinaryContext &BC = BF.getBinaryContext();
  if (!opts::shouldPrint(BF))
    return;

  // Make sure the new directory exists, creating it if necessary.
  if (!opts::AsmDump.empty()) {
    if (std::error_code EC = sys::fs::create_directories(opts::AsmDump)) {
      errs() << "BOLT-ERROR: could not create directory '" << opts::AsmDump
             << "': " << EC.message() << '\n';
      exit(1);
    }
  }

  std::string PrintName = BF.getPrintName();
  std::replace(PrintName.begin(), PrintName.end(), '/', '-');
  std::string Filename =
      opts::AsmDump.empty()
          ? (PrintName + ".s")
          : (opts::AsmDump + sys::path::get_separator() + PrintName + ".s")
                .str();
  outs() << "BOLT-INFO: Dumping function assembly to " << Filename << "\n";

  std::error_code EC;
  raw_fd_ostream OS(Filename, EC, sys::fs::OF_None);
  if (EC) {
    errs() << "BOLT-ERROR: " << EC.message() << ", unable to open " << Filename
           << " for output.\n";
    exit(1);
  }
  OS.SetUnbuffered();

  // Create local MC context to isolate the effect of ephemeral assembly
  // emission.
  BinaryContext::IndependentCodeEmitter MCEInstance =
      BC.createIndependentMCCodeEmitter();
  MCContext *LocalCtx = MCEInstance.LocalCtx.get();
  std::unique_ptr<MCAsmBackend> MAB(
      BC.TheTarget->createMCAsmBackend(*BC.STI, *BC.MRI, MCTargetOptions()));
  int AsmPrinterVariant = BC.AsmInfo->getAssemblerDialect();
  MCInstPrinter *InstructionPrinter(BC.TheTarget->createMCInstPrinter(
      *BC.TheTriple, AsmPrinterVariant, *BC.AsmInfo, *BC.MII, *BC.MRI));
  auto FOut = std::make_unique<formatted_raw_ostream>(OS);
  FOut->SetUnbuffered();
  std::unique_ptr<MCStreamer> AsmStreamer(
      createAsmStreamer(*LocalCtx, std::move(FOut),
                        /*isVerboseAsm=*/true,
                        /*useDwarfDirectory=*/false, InstructionPrinter,
                        std::move(MCEInstance.MCE), std::move(MAB),
                        /*ShowInst=*/false));
  AsmStreamer->initSections(true, *BC.STI);
  std::unique_ptr<TargetMachine> TM(BC.TheTarget->createTargetMachine(
      BC.TripleName, "", "", TargetOptions(), std::nullopt));
  std::unique_ptr<AsmPrinter> MAP(
      BC.TheTarget->createAsmPrinter(*TM, std::move(AsmStreamer)));

  StringRef FunctionName = BF.getOneName();
  OS << "  .globl " << FunctionName << '\n';
  OS << "  .type " << FunctionName << ", %function\n";
  OS << FunctionName << ":\n";

  // FDATA for the entry point
  if (uint64_t EntryExecCount = BF.getKnownExecutionCount())
    OS << "# FDATA: 0 [unknown] 0 "
       << "1 " << FunctionName << " 0 "
       << "0 " << EntryExecCount << '\n';

  // Binary data references from the function.
  std::unordered_set<const BinaryData *> BDReferences;
  // Function references from the function (to avoid constructing call graph).
  std::unordered_set<const MCSymbol *> CallReferences;

  MAP->OutStreamer->emitCFIStartProc(/*IsSimple=*/false);
  for (const BinaryBasicBlock *BB : BF.getLayout().blocks()) {
    OS << BB->getName() << ": \n";

    const std::string BranchLabel = Twine(BB->getName(), "_br").str();
    const MCInst *LastInst = BB->getLastNonPseudoInstr();

    for (const MCInst &Instr : *BB) {
      // Dump pseudo instructions (CFI)
      if (BC.MIB->isPseudo(Instr)) {
        if (BC.MIB->isCFI(Instr))
          dumpCFI(BF, Instr, *MAP.get());
        continue;
      }

      // Analyze symbol references (data, functions) from the instruction.
      bool IsCall = BC.MIB->isCall(Instr);
      for (const MCOperand &Operand : MCPlus::primeOperands(Instr)) {
        if (Operand.isExpr() &&
            Operand.getExpr()->getKind() == MCExpr::SymbolRef) {
          std::pair<const MCSymbol *, uint64_t> TSI =
              BC.MIB->getTargetSymbolInfo(Operand.getExpr());
          const MCSymbol *Symbol = TSI.first;
          if (IsCall)
            CallReferences.insert(Symbol);
          else if (const BinaryData *BD =
                       BC.getBinaryDataByName(Symbol->getName()))
            BDReferences.insert(BD);
        }
      }

      if (&Instr == LastInst && (BB->succ_size() || IsCall))
        OS << BranchLabel << ":\n";

      BC.InstPrinter->printInst(&Instr, 0, "", *BC.STI, OS);
      OS << '\n';

      // Dump profile data in FDATA format (as parsed by link_fdata).
      if (BC.MIB->getJumpTable(Instr))
        dumpJumpTableFdata(OS, BF, Instr, BranchLabel);
      else if (BC.MIB->isTailCall(Instr))
        dumpTailCallFdata(OS, BF, Instr, BranchLabel);
    }

    // Dump profile data in FDATA format (as parsed by link_fdata).
    for (const BinaryBasicBlock *Succ : BB->successors()) {
      const BinaryBasicBlock::BinaryBranchInfo BI = BB->getBranchInfo(*Succ);
      if (!BI.MispredictedCount && !BI.Count)
        continue;

      OS << "# FDATA: 1 " << FunctionName << " #" << BranchLabel << "# "
         << "1 " << FunctionName << " #" << Succ->getName() << "# "
         << BI.MispredictedCount << " " << BI.Count << '\n';
    }

    OS << '\n';
  }
  MAP->OutStreamer->emitCFIEndProc();

  OS << ".size " << FunctionName << ", .-" << FunctionName << '\n';

  const BinarySection *LastSection = BF.getOriginSection();
  // Print stubs for all target functions.
  for (const MCSymbol *CalleeSymb : CallReferences)
    dumpTargetFunctionStub(OS, BC, CalleeSymb, LastSection);

  OS << "# Jump tables\n";
  // Print all jump tables.
  for (auto &JTI : BF.jumpTables())
    dumpJumpTableSymbols(OS, JTI.second, *MAP.get(), LastSection);

  OS << "# BinaryData\n";
  // Print data references.
  for (const BinaryData *BD : BDReferences)
    dumpBinaryDataSymbols(OS, BD, LastSection);
}

void AsmDumpPass::runOnFunctions(BinaryContext &BC) {
  for (const auto &BFIt : BC.getBinaryFunctions())
    dumpFunction(BFIt.second);
}

} // namespace bolt
} // namespace llvm
