1 //===- bolt/tools/merge-fdata/merge-fdata.cpp -----------------------------===//
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 // Tool for merging profile in fdata format:
10 //
11 //   $ merge-fdata 1.fdata 2.fdata 3.fdata > merged.fdata
12 //
13 //===----------------------------------------------------------------------===//
14 
15 #include "bolt/Profile/ProfileYAMLMapping.h"
16 #include "llvm/ADT/StringMap.h"
17 #include "llvm/Support/CommandLine.h"
18 #include "llvm/Support/FileSystem.h"
19 #include "llvm/Support/ManagedStatic.h"
20 #include "llvm/Support/PrettyStackTrace.h"
21 #include "llvm/Support/Signals.h"
22 #include <unordered_map>
23 
24 using namespace llvm;
25 using namespace llvm::yaml::bolt;
26 
27 namespace opts {
28 
29 cl::OptionCategory MergeFdataCategory("merge-fdata options");
30 
31 enum SortType : char {
32   ST_NONE,
33   ST_EXEC_COUNT,      /// Sort based on function execution count.
34   ST_TOTAL_BRANCHES,  /// Sort based on all branches in the function.
35 };
36 
37 static cl::list<std::string>
38 InputDataFilenames(
39   cl::Positional,
40   cl::CommaSeparated,
41   cl::desc("<fdata1> [<fdata2>]..."),
42   cl::OneOrMore,
43   cl::cat(MergeFdataCategory));
44 
45 static cl::opt<SortType>
46 PrintFunctionList("print",
47   cl::desc("print the list of objects with count to stderr"),
48   cl::init(ST_NONE),
49   cl::values(clEnumValN(ST_NONE,
50       "none",
51       "do not print objects/functions"),
52     clEnumValN(ST_EXEC_COUNT,
53       "exec",
54       "print functions sorted by execution count"),
55     clEnumValN(ST_TOTAL_BRANCHES,
56       "branches",
57       "print functions sorted by total branch count")),
58   cl::cat(MergeFdataCategory));
59 
60 static cl::opt<bool>
61 SuppressMergedDataOutput("q",
62   cl::desc("do not print merged data to stdout"),
63   cl::init(false),
64   cl::Optional,
65   cl::cat(MergeFdataCategory));
66 
67 static cl::opt<std::string>
68 OutputFilePath("o",
69   cl::value_desc("file"),
70   cl::desc("Write output to <file>"),
71   cl::cat(MergeFdataCategory));
72 
73 } // namespace opts
74 
75 namespace {
76 
77 static StringRef ToolName;
78 
report_error(StringRef Message,std::error_code EC)79 static void report_error(StringRef Message, std::error_code EC) {
80   assert(EC);
81   errs() << ToolName << ": '" << Message << "': " << EC.message() << ".\n";
82   exit(1);
83 }
84 
report_error(Twine Message,StringRef CustomError)85 static void report_error(Twine Message, StringRef CustomError) {
86   errs() << ToolName << ": '" << Message << "': " << CustomError << ".\n";
87   exit(1);
88 }
89 
output()90 static raw_fd_ostream &output() {
91   if (opts::OutputFilePath.empty() || opts::OutputFilePath == "-")
92     return outs();
93   else {
94     std::error_code EC;
95     static raw_fd_ostream Output(opts::OutputFilePath, EC);
96     if (EC)
97       report_error(opts::OutputFilePath, EC);
98     return Output;
99   }
100 }
101 
mergeProfileHeaders(BinaryProfileHeader & MergedHeader,const BinaryProfileHeader & Header)102 void mergeProfileHeaders(BinaryProfileHeader &MergedHeader,
103                          const BinaryProfileHeader &Header) {
104   if (MergedHeader.FileName.empty())
105     MergedHeader.FileName = Header.FileName;
106 
107   if (!MergedHeader.FileName.empty() &&
108       MergedHeader.FileName != Header.FileName)
109     errs() << "WARNING: merging profile from a binary for " << Header.FileName
110            << " into a profile for binary " << MergedHeader.FileName << '\n';
111 
112   if (MergedHeader.Id.empty())
113     MergedHeader.Id = Header.Id;
114 
115   if (!MergedHeader.Id.empty() && (MergedHeader.Id != Header.Id))
116     errs() << "WARNING: build-ids in merged profiles do not match\n";
117 
118   // Cannot merge samples profile with LBR profile.
119   if (!MergedHeader.Flags)
120     MergedHeader.Flags = Header.Flags;
121 
122   constexpr auto Mask = llvm::bolt::BinaryFunction::PF_LBR |
123                         llvm::bolt::BinaryFunction::PF_SAMPLE;
124   if ((MergedHeader.Flags & Mask) != (Header.Flags & Mask)) {
125     errs() << "ERROR: cannot merge LBR profile with non-LBR profile\n";
126     exit(1);
127   }
128   MergedHeader.Flags = MergedHeader.Flags | Header.Flags;
129 
130   if (!Header.Origin.empty()) {
131     if (MergedHeader.Origin.empty())
132       MergedHeader.Origin = Header.Origin;
133     else if (MergedHeader.Origin != Header.Origin)
134       MergedHeader.Origin += "; " + Header.Origin;
135   }
136 
137   if (MergedHeader.EventNames.empty())
138     MergedHeader.EventNames = Header.EventNames;
139 
140   if (MergedHeader.EventNames != Header.EventNames) {
141     errs() << "WARNING: merging profiles with different sampling events\n";
142     MergedHeader.EventNames += "," + Header.EventNames;
143   }
144 }
145 
mergeBasicBlockProfile(BinaryBasicBlockProfile & MergedBB,BinaryBasicBlockProfile && BB,const BinaryFunctionProfile & BF)146 void mergeBasicBlockProfile(BinaryBasicBlockProfile &MergedBB,
147                             BinaryBasicBlockProfile &&BB,
148                             const BinaryFunctionProfile &BF) {
149   // Verify that the blocks match.
150   if (BB.NumInstructions != MergedBB.NumInstructions)
151     report_error(BF.Name + " : BB #" + Twine(BB.Index),
152                  "number of instructions in block mismatch");
153   if (BB.Hash != MergedBB.Hash)
154     report_error(BF.Name + " : BB #" + Twine(BB.Index),
155                  "basic block hash mismatch");
156 
157   // Update the execution count.
158   MergedBB.ExecCount += BB.ExecCount;
159 
160   // Update the event count.
161   MergedBB.EventCount += BB.EventCount;
162 
163   // Merge calls sites.
164   std::unordered_map<uint32_t, CallSiteInfo *> CSByOffset;
165   for (CallSiteInfo &CS : BB.CallSites)
166     CSByOffset.emplace(std::make_pair(CS.Offset, &CS));
167 
168   for (CallSiteInfo &MergedCS : MergedBB.CallSites) {
169     auto CSI = CSByOffset.find(MergedCS.Offset);
170     if (CSI == CSByOffset.end())
171       continue;
172     yaml::bolt::CallSiteInfo &CS = *CSI->second;
173     if (CS != MergedCS)
174       continue;
175 
176     MergedCS.Count += CS.Count;
177     MergedCS.Mispreds += CS.Mispreds;
178 
179     CSByOffset.erase(CSI);
180   }
181 
182   // Append the rest of call sites.
183   for (std::pair<const uint32_t, CallSiteInfo *> CSI : CSByOffset)
184     MergedBB.CallSites.emplace_back(std::move(*CSI.second));
185 
186   // Merge successor info.
187   std::vector<SuccessorInfo *> SIByIndex(BF.NumBasicBlocks);
188   for (SuccessorInfo &SI : BB.Successors) {
189     if (SI.Index >= BF.NumBasicBlocks)
190       report_error(BF.Name, "bad successor index");
191     SIByIndex[SI.Index] = &SI;
192   }
193   for (SuccessorInfo &MergedSI : MergedBB.Successors) {
194     if (!SIByIndex[MergedSI.Index])
195       continue;
196     SuccessorInfo &SI = *SIByIndex[MergedSI.Index];
197 
198     MergedSI.Count += SI.Count;
199     MergedSI.Mispreds += SI.Mispreds;
200 
201     SIByIndex[MergedSI.Index] = nullptr;
202   }
203   for (SuccessorInfo *SI : SIByIndex)
204     if (SI)
205       MergedBB.Successors.emplace_back(std::move(*SI));
206 }
207 
mergeFunctionProfile(BinaryFunctionProfile & MergedBF,BinaryFunctionProfile && BF)208 void mergeFunctionProfile(BinaryFunctionProfile &MergedBF,
209                           BinaryFunctionProfile &&BF) {
210   // Validate that we are merging the correct function.
211   if (BF.NumBasicBlocks != MergedBF.NumBasicBlocks)
212     report_error(BF.Name, "number of basic blocks mismatch");
213   if (BF.Id != MergedBF.Id)
214     report_error(BF.Name, "ID mismatch");
215   if (BF.Hash != MergedBF.Hash)
216     report_error(BF.Name, "hash mismatch");
217 
218   // Update the execution count.
219   MergedBF.ExecCount += BF.ExecCount;
220 
221   // Merge basic blocks profile.
222   std::vector<BinaryBasicBlockProfile *> BlockByIndex(BF.NumBasicBlocks);
223   for (BinaryBasicBlockProfile &BB : BF.Blocks) {
224     if (BB.Index >= BF.NumBasicBlocks)
225       report_error(BF.Name + " : BB #" + Twine(BB.Index),
226                    "bad basic block index");
227     BlockByIndex[BB.Index] = &BB;
228   }
229   for (BinaryBasicBlockProfile &MergedBB : MergedBF.Blocks) {
230     if (!BlockByIndex[MergedBB.Index])
231       continue;
232     BinaryBasicBlockProfile &BB = *BlockByIndex[MergedBB.Index];
233 
234     mergeBasicBlockProfile(MergedBB, std::move(BB), MergedBF);
235 
236     // Ignore this block in the future.
237     BlockByIndex[MergedBB.Index] = nullptr;
238   }
239 
240   // Append blocks unique to BF (i.e. those that are not in MergedBF).
241   for (BinaryBasicBlockProfile *BB : BlockByIndex)
242     if (BB)
243       MergedBF.Blocks.emplace_back(std::move(*BB));
244 }
245 
isYAML(const StringRef Filename)246 bool isYAML(const StringRef Filename) {
247   ErrorOr<std::unique_ptr<MemoryBuffer>> MB =
248       MemoryBuffer::getFileOrSTDIN(Filename);
249   if (std::error_code EC = MB.getError())
250     report_error(Filename, EC);
251   StringRef Buffer = MB.get()->getBuffer();
252   if (Buffer.startswith("---\n"))
253     return true;
254   return false;
255 }
256 
mergeLegacyProfiles(const SmallVectorImpl<std::string> & Filenames)257 void mergeLegacyProfiles(const SmallVectorImpl<std::string> &Filenames) {
258   errs() << "Using legacy profile format.\n";
259   bool BoltedCollection = false;
260   bool First = true;
261   StringMap<uint64_t> Entries;
262   for (const std::string &Filename : Filenames) {
263     if (isYAML(Filename))
264       report_error(Filename, "cannot mix YAML and legacy formats");
265     ErrorOr<std::unique_ptr<MemoryBuffer>> MB =
266         MemoryBuffer::getFileOrSTDIN(Filename);
267     if (std::error_code EC = MB.getError())
268       report_error(Filename, EC);
269     errs() << "Merging data from " << Filename << "...\n";
270 
271     StringRef Buf = MB.get()->getBuffer();
272     // Check if the string "boltedcollection" is in the first line
273     if (Buf.startswith("boltedcollection\n")) {
274       if (!First && !BoltedCollection)
275         report_error(
276             Filename,
277             "cannot mix profile collected in BOLT and non-BOLT deployments");
278       BoltedCollection = true;
279       Buf = Buf.drop_front(17);
280     } else {
281       if (BoltedCollection)
282         report_error(
283             Filename,
284             "cannot mix profile collected in BOLT and non-BOLT deployments");
285     }
286 
287     SmallVector<StringRef> Lines;
288     SplitString(Buf, Lines, "\n");
289     for (StringRef Line : Lines) {
290       size_t Pos = Line.rfind(" ");
291       if (Pos == StringRef::npos)
292         report_error(Filename, "Malformed / corrupted profile");
293       StringRef Signature = Line.substr(0, Pos);
294       uint64_t Count;
295       if (Line.substr(Pos + 1, Line.size() - Pos).getAsInteger(10, Count))
296         report_error(Filename, "Malformed / corrupted profile counter");
297       Count += Entries.lookup(Signature);
298       Entries.insert_or_assign(Signature, Count);
299     }
300     First = false;
301   }
302 
303   if (BoltedCollection)
304     output() << "boltedcollection\n";
305   for (const auto &Entry : Entries)
306     output() << Entry.getKey() << " " << Entry.getValue() << "\n";
307 
308   errs() << "Profile from " << Filenames.size() << " files merged.\n";
309 }
310 
311 } // anonymous namespace
312 
main(int argc,char ** argv)313 int main(int argc, char **argv) {
314   // Print a stack trace if we signal out.
315   sys::PrintStackTraceOnErrorSignal(argv[0]);
316   PrettyStackTraceProgram X(argc, argv);
317 
318   llvm_shutdown_obj Y; // Call llvm_shutdown() on exit.
319 
320   cl::HideUnrelatedOptions(opts::MergeFdataCategory);
321 
322   cl::ParseCommandLineOptions(argc, argv,
323                               "merge multiple fdata into a single file");
324 
325   ToolName = argv[0];
326 
327   // Recursively expand input directories into input file lists.
328   SmallVector<std::string> Inputs;
329   for (std::string &InputDataFilename : opts::InputDataFilenames) {
330     if (!llvm::sys::fs::exists(InputDataFilename))
331       report_error(InputDataFilename,
332                    std::make_error_code(std::errc::no_such_file_or_directory));
333     if (llvm::sys::fs::is_regular_file(InputDataFilename))
334       Inputs.emplace_back(InputDataFilename);
335     else if (llvm::sys::fs::is_directory(InputDataFilename)) {
336       std::error_code EC;
337       for (llvm::sys::fs::recursive_directory_iterator F(InputDataFilename, EC),
338            E;
339            F != E && !EC; F.increment(EC))
340         if (llvm::sys::fs::is_regular_file(F->path()))
341           Inputs.emplace_back(F->path());
342       if (EC)
343         report_error(InputDataFilename, EC);
344     }
345   }
346 
347   if (!isYAML(Inputs.front())) {
348     mergeLegacyProfiles(Inputs);
349     return 0;
350   }
351 
352   // Merged header.
353   BinaryProfileHeader MergedHeader;
354   MergedHeader.Version = 1;
355 
356   // Merged information for all functions.
357   StringMap<BinaryFunctionProfile> MergedBFs;
358 
359   for (std::string &InputDataFilename : Inputs) {
360     ErrorOr<std::unique_ptr<MemoryBuffer>> MB =
361         MemoryBuffer::getFileOrSTDIN(InputDataFilename);
362     if (std::error_code EC = MB.getError())
363       report_error(InputDataFilename, EC);
364     yaml::Input YamlInput(MB.get()->getBuffer());
365 
366     errs() << "Merging data from " << InputDataFilename << "...\n";
367 
368     BinaryProfile BP;
369     YamlInput >> BP;
370     if (YamlInput.error())
371       report_error(InputDataFilename, YamlInput.error());
372 
373     // Sanity check.
374     if (BP.Header.Version != 1) {
375       errs() << "Unable to merge data from profile using version "
376              << BP.Header.Version << '\n';
377       exit(1);
378     }
379 
380     // Merge the header.
381     mergeProfileHeaders(MergedHeader, BP.Header);
382 
383     // Do the function merge.
384     for (BinaryFunctionProfile &BF : BP.Functions) {
385       if (!MergedBFs.count(BF.Name)) {
386         MergedBFs.insert(std::make_pair(BF.Name, BF));
387         continue;
388       }
389 
390       BinaryFunctionProfile &MergedBF = MergedBFs.find(BF.Name)->second;
391       mergeFunctionProfile(MergedBF, std::move(BF));
392     }
393   }
394 
395   if (!opts::SuppressMergedDataOutput) {
396     yaml::Output YamlOut(output());
397 
398     BinaryProfile MergedProfile;
399     MergedProfile.Header = MergedHeader;
400     MergedProfile.Functions.resize(MergedBFs.size());
401     llvm::transform(
402         MergedBFs, MergedProfile.Functions.begin(),
403         [](StringMapEntry<BinaryFunctionProfile> &V) { return V.second; });
404 
405     // For consistency, sort functions by their IDs.
406     llvm::sort(MergedProfile.Functions,
407                [](const BinaryFunctionProfile &A,
408                   const BinaryFunctionProfile &B) { return A.Id < B.Id; });
409 
410     YamlOut << MergedProfile;
411   }
412 
413   errs() << "Data for " << MergedBFs.size()
414          << " unique objects successfully merged.\n";
415 
416   if (opts::PrintFunctionList != opts::ST_NONE) {
417     // List of function names with execution count.
418     std::vector<std::pair<uint64_t, StringRef>> FunctionList(MergedBFs.size());
419     using CountFuncType = std::function<std::pair<uint64_t, StringRef>(
420         const StringMapEntry<BinaryFunctionProfile> &)>;
421     CountFuncType ExecCountFunc =
422         [](const StringMapEntry<BinaryFunctionProfile> &V) {
423           return std::make_pair(V.second.ExecCount, StringRef(V.second.Name));
424         };
425     CountFuncType BranchCountFunc =
426         [](const StringMapEntry<BinaryFunctionProfile> &V) {
427           // Return total branch count.
428           uint64_t BranchCount = 0;
429           for (const BinaryBasicBlockProfile &BI : V.second.Blocks)
430             for (const SuccessorInfo &SI : BI.Successors)
431               BranchCount += SI.Count;
432           return std::make_pair(BranchCount, StringRef(V.second.Name));
433         };
434 
435     CountFuncType CountFunc = (opts::PrintFunctionList == opts::ST_EXEC_COUNT)
436                                   ? ExecCountFunc
437                                   : BranchCountFunc;
438     llvm::transform(MergedBFs, FunctionList.begin(), CountFunc);
439     llvm::stable_sort(reverse(FunctionList));
440     errs() << "Functions sorted by "
441            << (opts::PrintFunctionList == opts::ST_EXEC_COUNT ? "execution"
442                                                               : "total branch")
443            << " count:\n";
444     for (std::pair<uint64_t, StringRef> &FI : FunctionList)
445       errs() << FI.second << " : " << FI.first << '\n';
446   }
447 
448   return 0;
449 }
450