1 //===- bolt/tools/merge-fdata/merge-fdata.cpp -----------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 // Tool for merging profile in fdata format:
11 // $ merge-fdata 1.fdata 2.fdata 3.fdata > merged.fdata
13 //===----------------------------------------------------------------------===//
15 #include "bolt/Profile/ProfileYAMLMapping.h"
16 #include "llvm/ADT/STLExtras.h"
17 #include "llvm/ADT/StringMap.h"
18 #include "llvm/Support/CommandLine.h"
19 #include "llvm/Support/FileSystem.h"
20 #include "llvm/Support/ManagedStatic.h"
21 #include "llvm/Support/PrettyStackTrace.h"
22 #include "llvm/Support/Signals.h"
23 #include "llvm/Support/ThreadPool.h"
26 #include <unordered_map>
29 using namespace llvm::yaml::bolt
;
33 cl::OptionCategory
MergeFdataCategory("merge-fdata options");
35 enum SortType
: char {
37 ST_EXEC_COUNT
, /// Sort based on function execution count.
38 ST_TOTAL_BRANCHES
, /// Sort based on all branches in the function.
41 static cl::list
<std::string
>
45 cl::desc("<fdata1> [<fdata2>]..."),
47 cl::cat(MergeFdataCategory
));
49 static cl::opt
<SortType
>
50 PrintFunctionList("print",
51 cl::desc("print the list of objects with count to stderr"),
53 cl::values(clEnumValN(ST_NONE
,
55 "do not print objects/functions"),
56 clEnumValN(ST_EXEC_COUNT
,
58 "print functions sorted by execution count"),
59 clEnumValN(ST_TOTAL_BRANCHES
,
61 "print functions sorted by total branch count")),
62 cl::cat(MergeFdataCategory
));
65 SuppressMergedDataOutput("q",
66 cl::desc("do not print merged data to stdout"),
69 cl::cat(MergeFdataCategory
));
71 static cl::opt
<std::string
>
73 cl::value_desc("file"),
74 cl::desc("Write output to <file>"),
75 cl::cat(MergeFdataCategory
));
81 static StringRef ToolName
;
83 static void report_error(StringRef Message
, std::error_code EC
) {
85 errs() << ToolName
<< ": '" << Message
<< "': " << EC
.message() << ".\n";
89 static void report_error(Twine Message
, StringRef CustomError
) {
90 errs() << ToolName
<< ": '" << Message
<< "': " << CustomError
<< ".\n";
94 static raw_fd_ostream
&output() {
95 if (opts::OutputFilePath
.empty() || opts::OutputFilePath
== "-")
99 static raw_fd_ostream
Output(opts::OutputFilePath
, EC
);
101 report_error(opts::OutputFilePath
, EC
);
106 void mergeProfileHeaders(BinaryProfileHeader
&MergedHeader
,
107 const BinaryProfileHeader
&Header
) {
108 if (MergedHeader
.FileName
.empty())
109 MergedHeader
.FileName
= Header
.FileName
;
111 if (!MergedHeader
.FileName
.empty() &&
112 MergedHeader
.FileName
!= Header
.FileName
)
113 errs() << "WARNING: merging profile from a binary for " << Header
.FileName
114 << " into a profile for binary " << MergedHeader
.FileName
<< '\n';
116 if (MergedHeader
.Id
.empty())
117 MergedHeader
.Id
= Header
.Id
;
119 if (!MergedHeader
.Id
.empty() && (MergedHeader
.Id
!= Header
.Id
))
120 errs() << "WARNING: build-ids in merged profiles do not match\n";
122 // Cannot merge samples profile with LBR profile.
123 if (!MergedHeader
.Flags
)
124 MergedHeader
.Flags
= Header
.Flags
;
126 constexpr auto Mask
= llvm::bolt::BinaryFunction::PF_LBR
|
127 llvm::bolt::BinaryFunction::PF_SAMPLE
;
128 if ((MergedHeader
.Flags
& Mask
) != (Header
.Flags
& Mask
)) {
129 errs() << "ERROR: cannot merge LBR profile with non-LBR profile\n";
132 MergedHeader
.Flags
= MergedHeader
.Flags
| Header
.Flags
;
134 if (!Header
.Origin
.empty()) {
135 if (MergedHeader
.Origin
.empty())
136 MergedHeader
.Origin
= Header
.Origin
;
137 else if (MergedHeader
.Origin
!= Header
.Origin
)
138 MergedHeader
.Origin
+= "; " + Header
.Origin
;
141 if (MergedHeader
.EventNames
.empty())
142 MergedHeader
.EventNames
= Header
.EventNames
;
144 if (MergedHeader
.EventNames
!= Header
.EventNames
) {
145 errs() << "WARNING: merging profiles with different sampling events\n";
146 MergedHeader
.EventNames
+= "," + Header
.EventNames
;
150 void mergeBasicBlockProfile(BinaryBasicBlockProfile
&MergedBB
,
151 BinaryBasicBlockProfile
&&BB
,
152 const BinaryFunctionProfile
&BF
) {
153 // Verify that the blocks match.
154 if (BB
.NumInstructions
!= MergedBB
.NumInstructions
)
155 report_error(BF
.Name
+ " : BB #" + Twine(BB
.Index
),
156 "number of instructions in block mismatch");
157 if (BB
.Hash
!= MergedBB
.Hash
)
158 report_error(BF
.Name
+ " : BB #" + Twine(BB
.Index
),
159 "basic block hash mismatch");
161 // Update the execution count.
162 MergedBB
.ExecCount
+= BB
.ExecCount
;
164 // Update the event count.
165 MergedBB
.EventCount
+= BB
.EventCount
;
167 // Merge calls sites.
168 std::unordered_map
<uint32_t, CallSiteInfo
*> CSByOffset
;
169 for (CallSiteInfo
&CS
: BB
.CallSites
)
170 CSByOffset
.emplace(std::make_pair(CS
.Offset
, &CS
));
172 for (CallSiteInfo
&MergedCS
: MergedBB
.CallSites
) {
173 auto CSI
= CSByOffset
.find(MergedCS
.Offset
);
174 if (CSI
== CSByOffset
.end())
176 yaml::bolt::CallSiteInfo
&CS
= *CSI
->second
;
180 MergedCS
.Count
+= CS
.Count
;
181 MergedCS
.Mispreds
+= CS
.Mispreds
;
183 CSByOffset
.erase(CSI
);
186 // Append the rest of call sites.
187 for (std::pair
<const uint32_t, CallSiteInfo
*> CSI
: CSByOffset
)
188 MergedBB
.CallSites
.emplace_back(std::move(*CSI
.second
));
190 // Merge successor info.
191 std::vector
<SuccessorInfo
*> SIByIndex(BF
.NumBasicBlocks
);
192 for (SuccessorInfo
&SI
: BB
.Successors
) {
193 if (SI
.Index
>= BF
.NumBasicBlocks
)
194 report_error(BF
.Name
, "bad successor index");
195 SIByIndex
[SI
.Index
] = &SI
;
197 for (SuccessorInfo
&MergedSI
: MergedBB
.Successors
) {
198 if (!SIByIndex
[MergedSI
.Index
])
200 SuccessorInfo
&SI
= *SIByIndex
[MergedSI
.Index
];
202 MergedSI
.Count
+= SI
.Count
;
203 MergedSI
.Mispreds
+= SI
.Mispreds
;
205 SIByIndex
[MergedSI
.Index
] = nullptr;
207 for (SuccessorInfo
*SI
: SIByIndex
)
209 MergedBB
.Successors
.emplace_back(std::move(*SI
));
212 void mergeFunctionProfile(BinaryFunctionProfile
&MergedBF
,
213 BinaryFunctionProfile
&&BF
) {
214 // Validate that we are merging the correct function.
215 if (BF
.NumBasicBlocks
!= MergedBF
.NumBasicBlocks
)
216 report_error(BF
.Name
, "number of basic blocks mismatch");
217 if (BF
.Id
!= MergedBF
.Id
)
218 report_error(BF
.Name
, "ID mismatch");
219 if (BF
.Hash
!= MergedBF
.Hash
)
220 report_error(BF
.Name
, "hash mismatch");
222 // Update the execution count.
223 MergedBF
.ExecCount
+= BF
.ExecCount
;
225 // Merge basic blocks profile.
226 std::vector
<BinaryBasicBlockProfile
*> BlockByIndex(BF
.NumBasicBlocks
);
227 for (BinaryBasicBlockProfile
&BB
: BF
.Blocks
) {
228 if (BB
.Index
>= BF
.NumBasicBlocks
)
229 report_error(BF
.Name
+ " : BB #" + Twine(BB
.Index
),
230 "bad basic block index");
231 BlockByIndex
[BB
.Index
] = &BB
;
233 for (BinaryBasicBlockProfile
&MergedBB
: MergedBF
.Blocks
) {
234 if (!BlockByIndex
[MergedBB
.Index
])
236 BinaryBasicBlockProfile
&BB
= *BlockByIndex
[MergedBB
.Index
];
238 mergeBasicBlockProfile(MergedBB
, std::move(BB
), MergedBF
);
240 // Ignore this block in the future.
241 BlockByIndex
[MergedBB
.Index
] = nullptr;
244 // Append blocks unique to BF (i.e. those that are not in MergedBF).
245 for (BinaryBasicBlockProfile
*BB
: BlockByIndex
)
247 MergedBF
.Blocks
.emplace_back(std::move(*BB
));
250 bool isYAML(const StringRef Filename
) {
251 ErrorOr
<std::unique_ptr
<MemoryBuffer
>> MB
=
252 MemoryBuffer::getFileOrSTDIN(Filename
);
253 if (std::error_code EC
= MB
.getError())
254 report_error(Filename
, EC
);
255 StringRef Buffer
= MB
.get()->getBuffer();
256 if (Buffer
.startswith("---\n"))
261 void mergeLegacyProfiles(const SmallVectorImpl
<std::string
> &Filenames
) {
262 errs() << "Using legacy profile format.\n";
263 std::optional
<bool> BoltedCollection
;
264 std::mutex BoltedCollectionMutex
;
265 typedef StringMap
<uint64_t> ProfileTy
;
267 auto ParseProfile
= [&](const std::string
&Filename
, auto &Profiles
) {
268 const llvm::thread::id tid
= llvm::this_thread::get_id();
270 if (isYAML(Filename
))
271 report_error(Filename
, "cannot mix YAML and legacy formats");
272 ErrorOr
<std::unique_ptr
<MemoryBuffer
>> MB
=
273 MemoryBuffer::getFileOrSTDIN(Filename
);
274 if (std::error_code EC
= MB
.getError())
275 report_error(Filename
, EC
);
277 StringRef Buf
= MB
.get()->getBuffer();
280 std::lock_guard
<std::mutex
> Lock(BoltedCollectionMutex
);
281 // Check if the string "boltedcollection" is in the first line
282 if (Buf
.startswith("boltedcollection\n")) {
283 if (!BoltedCollection
.value_or(true))
286 "cannot mix profile collected in BOLT and non-BOLT deployments");
287 BoltedCollection
= true;
288 Buf
= Buf
.drop_front(17);
290 if (BoltedCollection
.value_or(false))
293 "cannot mix profile collected in BOLT and non-BOLT deployments");
294 BoltedCollection
= false;
297 Profile
= &Profiles
[tid
];
300 SmallVector
<StringRef
> Lines
;
301 SplitString(Buf
, Lines
, "\n");
302 for (StringRef Line
: Lines
) {
303 size_t Pos
= Line
.rfind(" ");
304 if (Pos
== StringRef::npos
)
305 report_error(Filename
, "Malformed / corrupted profile");
306 StringRef Signature
= Line
.substr(0, Pos
);
308 if (Line
.substr(Pos
+ 1, Line
.size() - Pos
).getAsInteger(10, Count
))
309 report_error(Filename
, "Malformed / corrupted profile counter");
310 Count
+= Profile
->lookup(Signature
);
311 Profile
->insert_or_assign(Signature
, Count
);
315 // The final reduction has non-trivial cost, make sure each thread has at
317 ThreadPoolStrategy S
= optimal_concurrency(
318 std::max(Filenames
.size() / 4, static_cast<size_t>(1)));
320 DenseMap
<llvm::thread::id
, ProfileTy
> ParsedProfiles(Pool
.getThreadCount());
321 for (const auto &Filename
: Filenames
)
322 Pool
.async(ParseProfile
, std::cref(Filename
), std::ref(ParsedProfiles
));
325 ProfileTy MergedProfile
;
326 for (const auto &[Thread
, Profile
] : ParsedProfiles
)
327 for (const auto &[Key
, Value
] : Profile
) {
328 uint64_t Count
= MergedProfile
.lookup(Key
) + Value
;
329 MergedProfile
.insert_or_assign(Key
, Count
);
332 if (BoltedCollection
)
333 output() << "boltedcollection\n";
334 for (const auto &[Key
, Value
] : MergedProfile
)
335 output() << Key
<< " " << Value
<< "\n";
337 errs() << "Profile from " << Filenames
.size() << " files merged.\n";
340 } // anonymous namespace
342 int main(int argc
, char **argv
) {
343 // Print a stack trace if we signal out.
344 sys::PrintStackTraceOnErrorSignal(argv
[0]);
345 PrettyStackTraceProgram
X(argc
, argv
);
347 llvm_shutdown_obj Y
; // Call llvm_shutdown() on exit.
349 cl::HideUnrelatedOptions(opts::MergeFdataCategory
);
351 cl::ParseCommandLineOptions(argc
, argv
,
352 "merge multiple fdata into a single file");
356 // Recursively expand input directories into input file lists.
357 SmallVector
<std::string
> Inputs
;
358 for (std::string
&InputDataFilename
: opts::InputDataFilenames
) {
359 if (!llvm::sys::fs::exists(InputDataFilename
))
360 report_error(InputDataFilename
,
361 std::make_error_code(std::errc::no_such_file_or_directory
));
362 if (llvm::sys::fs::is_regular_file(InputDataFilename
))
363 Inputs
.emplace_back(InputDataFilename
);
364 else if (llvm::sys::fs::is_directory(InputDataFilename
)) {
366 for (llvm::sys::fs::recursive_directory_iterator
F(InputDataFilename
, EC
),
368 F
!= E
&& !EC
; F
.increment(EC
))
369 if (llvm::sys::fs::is_regular_file(F
->path()))
370 Inputs
.emplace_back(F
->path());
372 report_error(InputDataFilename
, EC
);
376 if (!isYAML(Inputs
.front())) {
377 mergeLegacyProfiles(Inputs
);
382 BinaryProfileHeader MergedHeader
;
383 MergedHeader
.Version
= 1;
385 // Merged information for all functions.
386 StringMap
<BinaryFunctionProfile
> MergedBFs
;
388 for (std::string
&InputDataFilename
: Inputs
) {
389 ErrorOr
<std::unique_ptr
<MemoryBuffer
>> MB
=
390 MemoryBuffer::getFileOrSTDIN(InputDataFilename
);
391 if (std::error_code EC
= MB
.getError())
392 report_error(InputDataFilename
, EC
);
393 yaml::Input
YamlInput(MB
.get()->getBuffer());
395 errs() << "Merging data from " << InputDataFilename
<< "...\n";
399 if (YamlInput
.error())
400 report_error(InputDataFilename
, YamlInput
.error());
403 if (BP
.Header
.Version
!= 1) {
404 errs() << "Unable to merge data from profile using version "
405 << BP
.Header
.Version
<< '\n';
410 mergeProfileHeaders(MergedHeader
, BP
.Header
);
412 // Do the function merge.
413 for (BinaryFunctionProfile
&BF
: BP
.Functions
) {
414 if (!MergedBFs
.count(BF
.Name
)) {
415 MergedBFs
.insert(std::make_pair(BF
.Name
, BF
));
419 BinaryFunctionProfile
&MergedBF
= MergedBFs
.find(BF
.Name
)->second
;
420 mergeFunctionProfile(MergedBF
, std::move(BF
));
424 if (!opts::SuppressMergedDataOutput
) {
425 yaml::Output
YamlOut(output());
427 BinaryProfile MergedProfile
;
428 MergedProfile
.Header
= MergedHeader
;
429 MergedProfile
.Functions
.resize(MergedBFs
.size());
430 llvm::copy(llvm::make_second_range(MergedBFs
),
431 MergedProfile
.Functions
.begin());
433 // For consistency, sort functions by their IDs.
434 llvm::sort(MergedProfile
.Functions
,
435 [](const BinaryFunctionProfile
&A
,
436 const BinaryFunctionProfile
&B
) { return A
.Id
< B
.Id
; });
438 YamlOut
<< MergedProfile
;
441 errs() << "Data for " << MergedBFs
.size()
442 << " unique objects successfully merged.\n";
444 if (opts::PrintFunctionList
!= opts::ST_NONE
) {
445 // List of function names with execution count.
446 std::vector
<std::pair
<uint64_t, StringRef
>> FunctionList(MergedBFs
.size());
447 using CountFuncType
= std::function
<std::pair
<uint64_t, StringRef
>(
448 const StringMapEntry
<BinaryFunctionProfile
> &)>;
449 CountFuncType ExecCountFunc
=
450 [](const StringMapEntry
<BinaryFunctionProfile
> &V
) {
451 return std::make_pair(V
.second
.ExecCount
, StringRef(V
.second
.Name
));
453 CountFuncType BranchCountFunc
=
454 [](const StringMapEntry
<BinaryFunctionProfile
> &V
) {
455 // Return total branch count.
456 uint64_t BranchCount
= 0;
457 for (const BinaryBasicBlockProfile
&BI
: V
.second
.Blocks
)
458 for (const SuccessorInfo
&SI
: BI
.Successors
)
459 BranchCount
+= SI
.Count
;
460 return std::make_pair(BranchCount
, StringRef(V
.second
.Name
));
463 CountFuncType CountFunc
= (opts::PrintFunctionList
== opts::ST_EXEC_COUNT
)
466 llvm::transform(MergedBFs
, FunctionList
.begin(), CountFunc
);
467 llvm::stable_sort(reverse(FunctionList
));
468 errs() << "Functions sorted by "
469 << (opts::PrintFunctionList
== opts::ST_EXEC_COUNT
? "execution"
472 for (std::pair
<uint64_t, StringRef
> &FI
: FunctionList
)
473 errs() << FI
.second
<< " : " << FI
.first
<< '\n';