Make core BOLT functionality more friendly to being used as a library instead of in our standalone driver llvm-bolt. To accomplish this, we augment BinaryContext with journaling streams that are to be used by most BOLT code whenever something needs to be logged to the screen. Users of the library can decide if logs should be printed to a file, no file or to the screen, as before. To illustrate this, this patch adds a new option `--log-file` that allows the user to redirect BOLT logging to a file on disk or completely hide it by using `--log-file=/dev/null`. Future BOLT code should now use `BinaryContext::outs()` for printing important messages instead of `llvm::outs()`. A new test log.test enforces this by verifying that no strings are print to screen once the `--log-file` option is used. In previous patches we also added a new BOLTError class to report common and fatal errors, so code shouldn't call exit(1) now. To easily handle problems as before (by quitting with exit(1)), callers can now use `BinaryContext::logBOLTErrorsAndQuitOnFatal(Error)` whenever code needs to deal with BOLT errors. To test this, we have fatal.s that checks we are correctly quitting and printing a fatal error to the screen. Because this is a significant change by itself, not all code was yet ported. Code from Profiler libs (DataAggregator and friends) still print errors directly to screen. Co-authored-by: Rafael Auler <rafaelauler@fb.com> Test Plan: NFC
248 lines
8.7 KiB
C++
248 lines
8.7 KiB
C++
//===- 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)
|
|
BF.getBinaryContext().errs()
|
|
<< "BOLT-WARNING: AsmDump: skipping unsupported CFI instruction in "
|
|
<< BF << ".\n";
|
|
|
|
return;
|
|
|
|
default:
|
|
// Emit regular CFI instructions.
|
|
MAP.emitCFIInstruction(*CFIInstr);
|
|
}
|
|
}
|
|
|
|
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)) {
|
|
BC.errs() << "BOLT-ERROR: could not create directory '" << opts::AsmDump
|
|
<< "': " << EC.message() << '\n';
|
|
return;
|
|
}
|
|
}
|
|
|
|
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();
|
|
BC.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) {
|
|
BC.errs() << "BOLT-ERROR: " << EC.message() << ", unable to open "
|
|
<< Filename << " for output.\n";
|
|
return;
|
|
}
|
|
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).
|
|
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);
|
|
}
|
|
|
|
Error AsmDumpPass::runOnFunctions(BinaryContext &BC) {
|
|
for (const auto &BFIt : BC.getBinaryFunctions())
|
|
dumpFunction(BFIt.second);
|
|
return Error::success();
|
|
}
|
|
|
|
} // namespace bolt
|
|
} // namespace llvm
|