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
;
149 if (MergedHeader
.HashFunction
!= Header
.HashFunction
)
150 report_error("merge conflict",
151 "cannot merge profiles with different hash functions");
154 void mergeBasicBlockProfile(BinaryBasicBlockProfile
&MergedBB
,
155 BinaryBasicBlockProfile
&&BB
,
156 const BinaryFunctionProfile
&BF
) {
157 // Verify that the blocks match.
158 if (BB
.NumInstructions
!= MergedBB
.NumInstructions
)
159 report_error(BF
.Name
+ " : BB #" + Twine(BB
.Index
),
160 "number of instructions in block mismatch");
161 if (BB
.Hash
!= MergedBB
.Hash
)
162 report_error(BF
.Name
+ " : BB #" + Twine(BB
.Index
),
163 "basic block hash mismatch");
165 // Update the execution count.
166 MergedBB
.ExecCount
+= BB
.ExecCount
;
168 // Update the event count.
169 MergedBB
.EventCount
+= BB
.EventCount
;
171 // Merge calls sites.
172 std::unordered_map
<uint32_t, CallSiteInfo
*> CSByOffset
;
173 for (CallSiteInfo
&CS
: BB
.CallSites
)
174 CSByOffset
.emplace(std::make_pair(CS
.Offset
, &CS
));
176 for (CallSiteInfo
&MergedCS
: MergedBB
.CallSites
) {
177 auto CSI
= CSByOffset
.find(MergedCS
.Offset
);
178 if (CSI
== CSByOffset
.end())
180 yaml::bolt::CallSiteInfo
&CS
= *CSI
->second
;
184 MergedCS
.Count
+= CS
.Count
;
185 MergedCS
.Mispreds
+= CS
.Mispreds
;
187 CSByOffset
.erase(CSI
);
190 // Append the rest of call sites.
191 for (std::pair
<const uint32_t, CallSiteInfo
*> CSI
: CSByOffset
)
192 MergedBB
.CallSites
.emplace_back(std::move(*CSI
.second
));
194 // Merge successor info.
195 std::vector
<SuccessorInfo
*> SIByIndex(BF
.NumBasicBlocks
);
196 for (SuccessorInfo
&SI
: BB
.Successors
) {
197 if (SI
.Index
>= BF
.NumBasicBlocks
)
198 report_error(BF
.Name
, "bad successor index");
199 SIByIndex
[SI
.Index
] = &SI
;
201 for (SuccessorInfo
&MergedSI
: MergedBB
.Successors
) {
202 if (!SIByIndex
[MergedSI
.Index
])
204 SuccessorInfo
&SI
= *SIByIndex
[MergedSI
.Index
];
206 MergedSI
.Count
+= SI
.Count
;
207 MergedSI
.Mispreds
+= SI
.Mispreds
;
209 SIByIndex
[MergedSI
.Index
] = nullptr;
211 for (SuccessorInfo
*SI
: SIByIndex
)
213 MergedBB
.Successors
.emplace_back(std::move(*SI
));
216 void mergeFunctionProfile(BinaryFunctionProfile
&MergedBF
,
217 BinaryFunctionProfile
&&BF
) {
218 // Validate that we are merging the correct function.
219 if (BF
.NumBasicBlocks
!= MergedBF
.NumBasicBlocks
)
220 report_error(BF
.Name
, "number of basic blocks mismatch");
221 if (BF
.Id
!= MergedBF
.Id
)
222 report_error(BF
.Name
, "ID mismatch");
223 if (BF
.Hash
!= MergedBF
.Hash
)
224 report_error(BF
.Name
, "hash mismatch");
226 // Update the execution count.
227 MergedBF
.ExecCount
+= BF
.ExecCount
;
229 // Merge basic blocks profile.
230 std::vector
<BinaryBasicBlockProfile
*> BlockByIndex(BF
.NumBasicBlocks
);
231 for (BinaryBasicBlockProfile
&BB
: BF
.Blocks
) {
232 if (BB
.Index
>= BF
.NumBasicBlocks
)
233 report_error(BF
.Name
+ " : BB #" + Twine(BB
.Index
),
234 "bad basic block index");
235 BlockByIndex
[BB
.Index
] = &BB
;
237 for (BinaryBasicBlockProfile
&MergedBB
: MergedBF
.Blocks
) {
238 if (!BlockByIndex
[MergedBB
.Index
])
240 BinaryBasicBlockProfile
&BB
= *BlockByIndex
[MergedBB
.Index
];
242 mergeBasicBlockProfile(MergedBB
, std::move(BB
), MergedBF
);
244 // Ignore this block in the future.
245 BlockByIndex
[MergedBB
.Index
] = nullptr;
248 // Append blocks unique to BF (i.e. those that are not in MergedBF).
249 for (BinaryBasicBlockProfile
*BB
: BlockByIndex
)
251 MergedBF
.Blocks
.emplace_back(std::move(*BB
));
254 bool isYAML(const StringRef Filename
) {
255 ErrorOr
<std::unique_ptr
<MemoryBuffer
>> MB
=
256 MemoryBuffer::getFileOrSTDIN(Filename
);
257 if (std::error_code EC
= MB
.getError())
258 report_error(Filename
, EC
);
259 StringRef Buffer
= MB
.get()->getBuffer();
260 if (Buffer
.starts_with("---\n"))
265 void mergeLegacyProfiles(const SmallVectorImpl
<std::string
> &Filenames
) {
266 errs() << "Using legacy profile format.\n";
267 std::optional
<bool> BoltedCollection
;
268 std::mutex BoltedCollectionMutex
;
269 typedef StringMap
<uint64_t> ProfileTy
;
271 auto ParseProfile
= [&](const std::string
&Filename
, auto &Profiles
) {
272 const llvm::thread::id tid
= llvm::this_thread::get_id();
274 if (isYAML(Filename
))
275 report_error(Filename
, "cannot mix YAML and legacy formats");
276 ErrorOr
<std::unique_ptr
<MemoryBuffer
>> MB
=
277 MemoryBuffer::getFileOrSTDIN(Filename
);
278 if (std::error_code EC
= MB
.getError())
279 report_error(Filename
, EC
);
281 StringRef Buf
= MB
.get()->getBuffer();
284 std::lock_guard
<std::mutex
> Lock(BoltedCollectionMutex
);
285 // Check if the string "boltedcollection" is in the first line
286 if (Buf
.starts_with("boltedcollection\n")) {
287 if (!BoltedCollection
.value_or(true))
290 "cannot mix profile collected in BOLT and non-BOLT deployments");
291 BoltedCollection
= true;
292 Buf
= Buf
.drop_front(17);
294 if (BoltedCollection
.value_or(false))
297 "cannot mix profile collected in BOLT and non-BOLT deployments");
298 BoltedCollection
= false;
301 Profile
= &Profiles
[tid
];
304 SmallVector
<StringRef
> Lines
;
305 SplitString(Buf
, Lines
, "\n");
306 for (StringRef Line
: Lines
) {
307 size_t Pos
= Line
.rfind(" ");
308 if (Pos
== StringRef::npos
)
309 report_error(Filename
, "Malformed / corrupted profile");
310 StringRef Signature
= Line
.substr(0, Pos
);
312 if (Line
.substr(Pos
+ 1, Line
.size() - Pos
).getAsInteger(10, Count
))
313 report_error(Filename
, "Malformed / corrupted profile counter");
314 Count
+= Profile
->lookup(Signature
);
315 Profile
->insert_or_assign(Signature
, Count
);
319 // The final reduction has non-trivial cost, make sure each thread has at
321 ThreadPoolStrategy S
= optimal_concurrency(
322 std::max(Filenames
.size() / 4, static_cast<size_t>(1)));
323 DefaultThreadPool
Pool(S
);
324 DenseMap
<llvm::thread::id
, ProfileTy
> ParsedProfiles(
325 Pool
.getMaxConcurrency());
326 for (const auto &Filename
: Filenames
)
327 Pool
.async(ParseProfile
, std::cref(Filename
), std::ref(ParsedProfiles
));
330 ProfileTy MergedProfile
;
331 for (const auto &[Thread
, Profile
] : ParsedProfiles
)
332 for (const auto &[Key
, Value
] : Profile
) {
333 uint64_t Count
= MergedProfile
.lookup(Key
) + Value
;
334 MergedProfile
.insert_or_assign(Key
, Count
);
337 if (BoltedCollection
.value_or(false))
338 output() << "boltedcollection\n";
339 for (const auto &[Key
, Value
] : MergedProfile
)
340 output() << Key
<< " " << Value
<< "\n";
342 errs() << "Profile from " << Filenames
.size() << " files merged.\n";
345 } // anonymous namespace
347 int main(int argc
, char **argv
) {
348 // Print a stack trace if we signal out.
349 sys::PrintStackTraceOnErrorSignal(argv
[0]);
350 PrettyStackTraceProgram
X(argc
, argv
);
352 llvm_shutdown_obj Y
; // Call llvm_shutdown() on exit.
354 cl::HideUnrelatedOptions(opts::MergeFdataCategory
);
356 cl::ParseCommandLineOptions(argc
, argv
,
357 "merge multiple fdata into a single file");
361 // Recursively expand input directories into input file lists.
362 SmallVector
<std::string
> Inputs
;
363 for (std::string
&InputDataFilename
: opts::InputDataFilenames
) {
364 if (!llvm::sys::fs::exists(InputDataFilename
))
365 report_error(InputDataFilename
,
366 std::make_error_code(std::errc::no_such_file_or_directory
));
367 if (llvm::sys::fs::is_regular_file(InputDataFilename
))
368 Inputs
.emplace_back(InputDataFilename
);
369 else if (llvm::sys::fs::is_directory(InputDataFilename
)) {
371 for (llvm::sys::fs::recursive_directory_iterator
F(InputDataFilename
, EC
),
373 F
!= E
&& !EC
; F
.increment(EC
))
374 if (llvm::sys::fs::is_regular_file(F
->path()))
375 Inputs
.emplace_back(F
->path());
377 report_error(InputDataFilename
, EC
);
381 if (!isYAML(Inputs
.front())) {
382 mergeLegacyProfiles(Inputs
);
387 BinaryProfileHeader MergedHeader
;
388 MergedHeader
.Version
= 1;
390 // Merged information for all functions.
391 StringMap
<BinaryFunctionProfile
> MergedBFs
;
393 bool FirstHeader
= true;
394 for (std::string
&InputDataFilename
: Inputs
) {
395 ErrorOr
<std::unique_ptr
<MemoryBuffer
>> MB
=
396 MemoryBuffer::getFileOrSTDIN(InputDataFilename
);
397 if (std::error_code EC
= MB
.getError())
398 report_error(InputDataFilename
, EC
);
399 yaml::Input
YamlInput(MB
.get()->getBuffer());
400 YamlInput
.setAllowUnknownKeys(true);
402 errs() << "Merging data from " << InputDataFilename
<< "...\n";
406 if (YamlInput
.error())
407 report_error(InputDataFilename
, YamlInput
.error());
410 if (BP
.Header
.Version
!= 1) {
411 errs() << "Unable to merge data from profile using version "
412 << BP
.Header
.Version
<< '\n';
418 MergedHeader
= BP
.Header
;
421 mergeProfileHeaders(MergedHeader
, BP
.Header
);
424 // Do the function merge.
425 for (BinaryFunctionProfile
&BF
: BP
.Functions
) {
426 if (!MergedBFs
.count(BF
.Name
)) {
427 MergedBFs
.insert(std::make_pair(BF
.Name
, BF
));
431 BinaryFunctionProfile
&MergedBF
= MergedBFs
.find(BF
.Name
)->second
;
432 mergeFunctionProfile(MergedBF
, std::move(BF
));
436 if (!opts::SuppressMergedDataOutput
) {
437 yaml::Output
YamlOut(output());
439 BinaryProfile MergedProfile
;
440 MergedProfile
.Header
= MergedHeader
;
441 MergedProfile
.Functions
.resize(MergedBFs
.size());
442 llvm::copy(llvm::make_second_range(MergedBFs
),
443 MergedProfile
.Functions
.begin());
445 // For consistency, sort functions by their IDs.
446 llvm::sort(MergedProfile
.Functions
,
447 [](const BinaryFunctionProfile
&A
,
448 const BinaryFunctionProfile
&B
) { return A
.Id
< B
.Id
; });
450 YamlOut
<< MergedProfile
;
453 errs() << "Data for " << MergedBFs
.size()
454 << " unique objects successfully merged.\n";
456 if (opts::PrintFunctionList
!= opts::ST_NONE
) {
457 // List of function names with execution count.
458 std::vector
<std::pair
<uint64_t, StringRef
>> FunctionList(MergedBFs
.size());
459 using CountFuncType
= std::function
<std::pair
<uint64_t, StringRef
>(
460 const StringMapEntry
<BinaryFunctionProfile
> &)>;
461 CountFuncType ExecCountFunc
=
462 [](const StringMapEntry
<BinaryFunctionProfile
> &V
) {
463 return std::make_pair(V
.second
.ExecCount
, StringRef(V
.second
.Name
));
465 CountFuncType BranchCountFunc
=
466 [](const StringMapEntry
<BinaryFunctionProfile
> &V
) {
467 // Return total branch count.
468 uint64_t BranchCount
= 0;
469 for (const BinaryBasicBlockProfile
&BI
: V
.second
.Blocks
)
470 for (const SuccessorInfo
&SI
: BI
.Successors
)
471 BranchCount
+= SI
.Count
;
472 return std::make_pair(BranchCount
, StringRef(V
.second
.Name
));
475 CountFuncType CountFunc
= (opts::PrintFunctionList
== opts::ST_EXEC_COUNT
)
478 llvm::transform(MergedBFs
, FunctionList
.begin(), CountFunc
);
479 llvm::stable_sort(reverse(FunctionList
));
480 errs() << "Functions sorted by "
481 << (opts::PrintFunctionList
== opts::ST_EXEC_COUNT
? "execution"
484 for (std::pair
<uint64_t, StringRef
> &FI
: FunctionList
)
485 errs() << FI
.second
<< " : " << FI
.first
<< '\n';