1 //===- bolt/Passes/AsmDump.cpp - Dump BinaryFunction into assembly --------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This file implements the AsmDumpPass class.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "bolt/Passes/AsmDump.h"
14 #include "llvm/CodeGen/AsmPrinter.h"
15 #include "llvm/MC/TargetRegistry.h"
16 #include "llvm/Support/FileSystem.h"
17 #include "llvm/Support/Path.h"
18 #include "llvm/Target/TargetMachine.h"
19 #include <unordered_set>
20 
21 #define DEBUG_TYPE "asm-dump"
22 
23 using namespace llvm;
24 
25 namespace opts {
26 extern bool shouldPrint(const bolt::BinaryFunction &Function);
27 extern cl::OptionCategory BoltCategory;
28 extern cl::opt<unsigned> Verbosity;
29 
30 cl::opt<std::string> AsmDump("asm-dump",
31                              cl::desc("dump function into assembly"),
32                              cl::value_desc("dump folder"), cl::ValueOptional,
33                              cl::ZeroOrMore, cl::Hidden, cl::cat(BoltCategory));
34 } // end namespace opts
35 
36 namespace llvm {
37 namespace bolt {
38 
39 void dumpCFI(const BinaryFunction &BF, const MCInst &Instr, AsmPrinter &MAP) {
40   const MCCFIInstruction *CFIInstr = BF.getCFIFor(Instr);
41   switch (CFIInstr->getOperation()) {
42   // Skip unsupported CFI instructions.
43   case MCCFIInstruction::OpRememberState:
44   case MCCFIInstruction::OpRestoreState:
45     if (opts::Verbosity >= 2)
46       errs()
47           << "BOLT-WARNING: AsmDump: skipping unsupported CFI instruction in "
48           << BF << ".\n";
49 
50     return;
51 
52   default:
53     // Emit regular CFI instructions.
54     MAP.emitCFIInstruction(*CFIInstr);
55   }
56 }
57 
58 void dumpJumpTableFdata(raw_ostream &OS, const BinaryFunction &BF,
59                         const MCInst &Instr, const std::string &BranchLabel) {
60   StringRef FunctionName = BF.getOneName();
61   const JumpTable *JT = BF.getJumpTable(Instr);
62   for (const uint64_t EntryOffset : JT->OffsetEntries) {
63     auto LI = JT->Labels.find(EntryOffset);
64     StringRef TargetName = LI->second->getName();
65     const uint64_t Mispreds = JT->Counts[EntryOffset].Mispreds;
66     const uint64_t Count = JT->Counts[EntryOffset].Count;
67     OS << "# FDATA: 1 " << FunctionName << " #" << BranchLabel << "# "
68        << "1 " << FunctionName << " #" << TargetName << "# " << Mispreds << " "
69        << Count << '\n';
70   }
71 }
72 
73 void dumpTailCallFdata(raw_ostream &OS, const BinaryFunction &BF,
74                        const MCInst &Instr, const std::string &BranchLabel) {
75   const BinaryContext &BC = BF.getBinaryContext();
76   StringRef FunctionName = BF.getOneName();
77   auto CallFreq = BC.MIB->getAnnotationWithDefault<uint64_t>(Instr, "Count");
78   const MCSymbol *Target = BC.MIB->getTargetSymbol(Instr);
79   const BinaryFunction *TargetBF = BC.getFunctionForSymbol(Target);
80   if (!TargetBF)
81     return;
82   OS << "# FDATA: 1 " << FunctionName << " #" << BranchLabel << "# "
83      << "1 " << TargetBF->getPrintName() << " 0 "
84      << "0 " << CallFreq << '\n';
85 }
86 
87 void dumpTargetFunctionStub(raw_ostream &OS, const BinaryContext &BC,
88                             const MCSymbol *CalleeSymb,
89                             const BinarySection *&LastCS) {
90   const BinaryFunction *CalleeFunc = BC.getFunctionForSymbol(CalleeSymb);
91   if (!CalleeFunc || CalleeFunc->isPLTFunction())
92     return;
93 
94   if (CalleeFunc->getOriginSection() != LastCS) {
95     OS << ".section " << CalleeFunc->getOriginSectionName() << '\n';
96     LastCS = CalleeFunc->getOriginSection();
97   }
98   StringRef CalleeName = CalleeFunc->getOneName();
99   OS << ".set \"" << CalleeName << "\", 0\n";
100 }
101 
102 void dumpJumpTableSymbols(raw_ostream &OS, const JumpTable *JT, AsmPrinter &MAP,
103                           const BinarySection *&LastBS) {
104   if (&JT->getSection() != LastBS) {
105     OS << ".section " << JT->getSectionName() << '\n';
106     LastBS = &JT->getSection();
107   }
108   OS << "\"" << JT->getName() << "\":\n";
109   for (MCSymbol *JTEntry : JT->Entries)
110     MAP.OutStreamer->emitSymbolValue(JTEntry, JT->OutputEntrySize);
111   OS << '\n';
112 }
113 
114 void dumpBinaryDataSymbols(raw_ostream &OS, const BinaryData *BD,
115                            const BinarySection *&LastBS) {
116   if (BD->isJumpTable())
117     return;
118   if (&BD->getSection() != LastBS) {
119     OS << ".section " << BD->getSectionName() << '\n';
120     LastBS = &BD->getSection();
121   }
122   OS << "\"" << BD->getName() << "\": ";
123   OS << '\n';
124 }
125 
126 void dumpFunction(const BinaryFunction &BF) {
127   const BinaryContext &BC = BF.getBinaryContext();
128   if (!opts::shouldPrint(BF))
129     return;
130 
131   // Make sure the new directory exists, creating it if necessary.
132   if (!opts::AsmDump.empty()) {
133     if (std::error_code EC = sys::fs::create_directories(opts::AsmDump)) {
134       errs() << "BOLT-ERROR: could not create directory '" << opts::AsmDump
135              << "': " << EC.message() << '\n';
136       exit(1);
137     }
138   }
139 
140   std::string PrintName = BF.getPrintName();
141   std::replace(PrintName.begin(), PrintName.end(), '/', '-');
142   std::string Filename =
143       opts::AsmDump.empty()
144           ? (PrintName + ".s")
145           : (opts::AsmDump + sys::path::get_separator() + PrintName + ".s")
146                 .str();
147   outs() << "BOLT-INFO: Dumping function assembly to " << Filename << "\n";
148 
149   std::error_code EC;
150   raw_fd_ostream OS(Filename, EC, sys::fs::OF_None);
151   if (EC) {
152     errs() << "BOLT-ERROR: " << EC.message() << ", unable to open " << Filename
153            << " for output.\n";
154     exit(1);
155   }
156   OS.SetUnbuffered();
157 
158   // Create local MC context to isolate the effect of ephemeral assembly
159   // emission.
160   BinaryContext::IndependentCodeEmitter MCEInstance =
161       BC.createIndependentMCCodeEmitter();
162   MCContext *LocalCtx = MCEInstance.LocalCtx.get();
163   std::unique_ptr<MCAsmBackend> MAB(
164       BC.TheTarget->createMCAsmBackend(*BC.STI, *BC.MRI, MCTargetOptions()));
165   int AsmPrinterVariant = BC.AsmInfo->getAssemblerDialect();
166   MCInstPrinter *InstructionPrinter(BC.TheTarget->createMCInstPrinter(
167       *BC.TheTriple, AsmPrinterVariant, *BC.AsmInfo, *BC.MII, *BC.MRI));
168   auto FOut = std::make_unique<formatted_raw_ostream>(OS);
169   FOut->SetUnbuffered();
170   std::unique_ptr<MCStreamer> AsmStreamer(
171       createAsmStreamer(*LocalCtx, std::move(FOut),
172                         /*isVerboseAsm=*/true,
173                         /*useDwarfDirectory=*/false, InstructionPrinter,
174                         std::move(MCEInstance.MCE), std::move(MAB),
175                         /*ShowInst=*/false));
176   AsmStreamer->initSections(true, *BC.STI);
177   std::unique_ptr<TargetMachine> TM(BC.TheTarget->createTargetMachine(
178       BC.TripleName, "", "", TargetOptions(), None));
179   std::unique_ptr<AsmPrinter> MAP(
180       BC.TheTarget->createAsmPrinter(*TM, std::move(AsmStreamer)));
181 
182   StringRef FunctionName = BF.getOneName();
183   OS << "  .globl " << FunctionName << '\n';
184   OS << "  .type " << FunctionName << ", %function\n";
185   OS << FunctionName << ":\n";
186 
187   // FDATA for the entry point
188   if (uint64_t EntryExecCount = BF.getKnownExecutionCount())
189     OS << "# FDATA: 0 [unknown] 0 "
190        << "1 " << FunctionName << " 0 "
191        << "0 " << EntryExecCount << '\n';
192 
193   // Binary data references from the function.
194   std::unordered_set<const BinaryData *> BDReferences;
195   // Function references from the function (to avoid constructing call graph).
196   std::unordered_set<const MCSymbol *> CallReferences;
197 
198   MAP->OutStreamer->emitCFIStartProc(/*IsSimple=*/false);
199   for (BinaryBasicBlock *BB : BF.layout()) {
200     OS << BB->getName() << ": \n";
201 
202     const std::string BranchLabel = Twine(BB->getName(), "_br").str();
203     const MCInst *LastInst = BB->getLastNonPseudoInstr();
204 
205     for (const MCInst &Instr : *BB) {
206       // Dump pseudo instructions (CFI)
207       if (BC.MIB->isPseudo(Instr)) {
208         if (BC.MIB->isCFI(Instr))
209           dumpCFI(BF, Instr, *MAP.get());
210         continue;
211       }
212 
213       // Analyze symbol references (data, functions) from the instruction.
214       bool IsCall = BC.MIB->isCall(Instr);
215       for (unsigned I = 0, E = MCPlus::getNumPrimeOperands(Instr); I != E;
216            ++I) {
217         MCOperand Operand = Instr.getOperand(I);
218         if (Operand.isExpr() &&
219             Operand.getExpr()->getKind() == MCExpr::SymbolRef) {
220           std::pair<const MCSymbol *, uint64_t> TSI =
221               BC.MIB->getTargetSymbolInfo(Operand.getExpr());
222           const MCSymbol *Symbol = TSI.first;
223           if (IsCall)
224             CallReferences.insert(Symbol);
225           else if (const BinaryData *BD =
226                        BC.getBinaryDataByName(Symbol->getName()))
227             BDReferences.insert(BD);
228         }
229       }
230 
231       if (&Instr == LastInst && (BB->succ_size() || IsCall))
232         OS << BranchLabel << ":\n";
233 
234       BC.InstPrinter->printInst(&Instr, 0, "", *BC.STI, OS);
235       OS << '\n';
236 
237       // Dump profile data in FDATA format (as parsed by link_fdata).
238       if (BC.MIB->getJumpTable(Instr))
239         dumpJumpTableFdata(OS, BF, Instr, BranchLabel);
240       else if (BC.MIB->isTailCall(Instr))
241         dumpTailCallFdata(OS, BF, Instr, BranchLabel);
242     }
243 
244     // Dump profile data in FDATA format (as parsed by link_fdata).
245     for (const BinaryBasicBlock *Succ : BB->successors()) {
246       const BinaryBasicBlock::BinaryBranchInfo BI = BB->getBranchInfo(*Succ);
247       if (!BI.MispredictedCount && !BI.Count)
248         continue;
249 
250       OS << "# FDATA: 1 " << FunctionName << " #" << BranchLabel << "# "
251          << "1 " << FunctionName << " #" << Succ->getName() << "# "
252          << BI.MispredictedCount << " " << BI.Count << '\n';
253     }
254 
255     OS << '\n';
256   }
257   MAP->OutStreamer->emitCFIEndProc();
258 
259   OS << ".size " << FunctionName << ", .-" << FunctionName << '\n';
260 
261   const BinarySection *LastSection = BF.getOriginSection();
262   // Print stubs for all target functions.
263   for (const MCSymbol *CalleeSymb : CallReferences)
264     dumpTargetFunctionStub(OS, BC, CalleeSymb, LastSection);
265 
266   OS << "# Jump tables\n";
267   // Print all jump tables.
268   for (auto &JTI : BF.jumpTables())
269     dumpJumpTableSymbols(OS, JTI.second, *MAP.get(), LastSection);
270 
271   OS << "# BinaryData\n";
272   // Print data references.
273   for (const BinaryData *BD : BDReferences)
274     dumpBinaryDataSymbols(OS, BD, LastSection);
275 }
276 
277 void AsmDumpPass::runOnFunctions(BinaryContext &BC) {
278   for (const auto &BFIt : BC.getBinaryFunctions())
279     dumpFunction(BFIt.second);
280 }
281 
282 } // namespace bolt
283 } // namespace llvm
284