1 //===- bolt/Passes/ReorderSection.cpp - Reordering of section data --------===//
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 // This file implements ReorderData class.
11 //===----------------------------------------------------------------------===//
14 // - make sure writable data isn't put on same cache line unless temporally
16 // - estimate temporal locality by looking at CFG?
18 #include "bolt/Passes/ReorderData.h"
19 #include "llvm/ADT/MapVector.h"
23 #define DEBUG_TYPE "reorder-data"
29 extern cl::OptionCategory BoltCategory
;
30 extern cl::OptionCategory BoltOptCategory
;
31 extern cl::opt
<JumpTableSupportLevel
> JumpTables
;
34 PrintReorderedData("print-reordered-data",
35 cl::desc("print section contents after reordering"),
36 cl::Hidden
, cl::cat(BoltCategory
));
39 ReorderData("reorder-data",
41 cl::desc("list of sections to reorder"),
42 cl::value_desc("section1,section2,section3,..."),
43 cl::cat(BoltOptCategory
));
45 enum ReorderAlgo
: char {
50 static cl::opt
<ReorderAlgo
>
51 ReorderAlgorithm("reorder-data-algo",
52 cl::desc("algorithm used to reorder data sections"),
53 cl::init(REORDER_COUNT
),
55 clEnumValN(REORDER_COUNT
,
57 "sort hot data by read counts"),
58 clEnumValN(REORDER_FUNCS
,
60 "sort hot data by hot function usage and count")),
62 cl::cat(BoltOptCategory
));
64 static cl::opt
<unsigned>
65 ReorderDataMaxSymbols("reorder-data-max-symbols",
66 cl::desc("maximum number of symbols to reorder"),
67 cl::init(std::numeric_limits
<unsigned>::max()),
68 cl::cat(BoltOptCategory
));
70 static cl::opt
<unsigned> ReorderDataMaxBytes(
71 "reorder-data-max-bytes", cl::desc("maximum number of bytes to reorder"),
72 cl::init(std::numeric_limits
<unsigned>::max()), cl::cat(BoltOptCategory
));
74 static cl::list
<std::string
>
75 ReorderSymbols("reorder-symbols",
77 cl::desc("list of symbol names that can be reordered"),
78 cl::value_desc("symbol1,symbol2,symbol3,..."),
80 cl::cat(BoltCategory
));
82 static cl::list
<std::string
>
83 SkipSymbols("reorder-skip-symbols",
85 cl::desc("list of symbol names that cannot be reordered"),
86 cl::value_desc("symbol1,symbol2,symbol3,..."),
88 cl::cat(BoltCategory
));
90 static cl::opt
<bool> ReorderInplace("reorder-data-inplace",
91 cl::desc("reorder data sections in place"),
93 cl::cat(BoltOptCategory
));
101 static constexpr uint16_t MinAlignment
= 16;
103 bool isSupported(const BinarySection
&BS
) { return BS
.isData() && !BS
.isTLS(); }
105 bool filterSymbol(const BinaryData
*BD
) {
106 if (!BD
->isAtomic() || BD
->isJumpTable() || !BD
->isMoveable())
111 if (!opts::ReorderSymbols
.empty()) {
112 IsValid
= llvm::any_of(opts::ReorderSymbols
, [&](const std::string
&Name
) {
113 return BD
->hasName(Name
);
120 if (!opts::SkipSymbols
.empty()) {
121 for (const std::string
&Name
: opts::SkipSymbols
) {
122 if (BD
->hasName(Name
)) {
134 using DataOrder
= ReorderData::DataOrder
;
136 void ReorderData::printOrder(BinaryContext
&BC
, const BinarySection
&Section
,
137 DataOrder::const_iterator Begin
,
138 DataOrder::const_iterator End
) const {
139 uint64_t TotalSize
= 0;
140 bool PrintHeader
= false;
141 while (Begin
!= End
) {
142 const BinaryData
*BD
= Begin
->first
;
145 BC
.outs() << "BOLT-INFO: Hot global symbols for " << Section
.getName()
150 BC
.outs() << "BOLT-INFO: " << *BD
<< ", moveable=" << BD
->isMoveable()
151 << format(", weight=%.5f\n",
152 double(Begin
->second
) / BD
->getSize());
154 TotalSize
+= BD
->getSize();
158 BC
.outs() << "BOLT-INFO: Total hot symbol size = " << TotalSize
<< "\n";
161 DataOrder
ReorderData::baseOrder(BinaryContext
&BC
,
162 const BinarySection
&Section
) const {
164 for (auto &Entry
: BC
.getBinaryDataForSection(Section
)) {
165 BinaryData
*BD
= Entry
.second
;
166 if (!BD
->isAtomic()) // skip sub-symbols
168 auto BDCI
= BinaryDataCounts
.find(BD
);
169 uint64_t BDCount
= BDCI
== BinaryDataCounts
.end() ? 0 : BDCI
->second
;
170 Order
.emplace_back(BD
, BDCount
);
175 void ReorderData::assignMemData(BinaryContext
&BC
) {
176 // Map of sections (or heap/stack) to count/size.
177 MapVector
<StringRef
, uint64_t> Counts
;
178 MapVector
<StringRef
, uint64_t> JumpTableCounts
;
179 uint64_t TotalCount
= 0;
180 for (auto &BFI
: BC
.getBinaryFunctions()) {
181 const BinaryFunction
&BF
= BFI
.second
;
182 if (!BF
.hasMemoryProfile())
185 for (const BinaryBasicBlock
&BB
: BF
) {
186 for (const MCInst
&Inst
: BB
) {
187 auto ErrorOrMemAccessProfile
=
188 BC
.MIB
->tryGetAnnotationAs
<MemoryAccessProfile
>(
189 Inst
, "MemoryAccessProfile");
190 if (!ErrorOrMemAccessProfile
)
193 const MemoryAccessProfile
&MemAccessProfile
=
194 ErrorOrMemAccessProfile
.get();
195 for (const AddressAccess
&AccessInfo
:
196 MemAccessProfile
.AddressAccessInfo
) {
197 if (BinaryData
*BD
= AccessInfo
.MemoryObject
) {
198 BinaryDataCounts
[BD
->getAtomicRoot()] += AccessInfo
.Count
;
199 Counts
[BD
->getSectionName()] += AccessInfo
.Count
;
200 if (BD
->getAtomicRoot()->isJumpTable())
201 JumpTableCounts
[BD
->getSectionName()] += AccessInfo
.Count
;
203 Counts
["Heap/stack"] += AccessInfo
.Count
;
205 TotalCount
+= AccessInfo
.Count
;
211 if (!Counts
.empty()) {
212 BC
.outs() << "BOLT-INFO: Memory stats breakdown:\n";
213 for (const auto &KV
: Counts
) {
214 StringRef Section
= KV
.first
;
215 const uint64_t Count
= KV
.second
;
216 BC
.outs() << "BOLT-INFO: " << Section
<< " = " << Count
217 << format(" (%.1f%%)\n", 100.0 * Count
/ TotalCount
);
218 if (JumpTableCounts
.count(Section
) != 0) {
219 const uint64_t JTCount
= JumpTableCounts
[Section
];
220 BC
.outs() << "BOLT-INFO: jump tables = " << JTCount
221 << format(" (%.1f%%)\n", 100.0 * JTCount
/ Count
);
224 BC
.outs() << "BOLT-INFO: Total memory events: " << TotalCount
<< "\n";
228 /// Only consider moving data that is used by the hottest functions with
230 std::pair
<DataOrder
, unsigned>
231 ReorderData::sortedByFunc(BinaryContext
&BC
, const BinarySection
&Section
,
232 std::map
<uint64_t, BinaryFunction
> &BFs
) const {
233 std::map
<BinaryData
*, std::set
<BinaryFunction
*>> BDtoFunc
;
234 std::map
<BinaryData
*, uint64_t> BDtoFuncCount
;
236 auto dataUses
= [&BC
](const BinaryFunction
&BF
, bool OnlyHot
) {
237 std::set
<BinaryData
*> Uses
;
238 for (const BinaryBasicBlock
&BB
: BF
) {
239 if (OnlyHot
&& BB
.isCold())
242 for (const MCInst
&Inst
: BB
) {
243 auto ErrorOrMemAccessProfile
=
244 BC
.MIB
->tryGetAnnotationAs
<MemoryAccessProfile
>(
245 Inst
, "MemoryAccessProfile");
246 if (!ErrorOrMemAccessProfile
)
249 const MemoryAccessProfile
&MemAccessProfile
=
250 ErrorOrMemAccessProfile
.get();
251 for (const AddressAccess
&AccessInfo
:
252 MemAccessProfile
.AddressAccessInfo
) {
253 if (AccessInfo
.MemoryObject
)
254 Uses
.insert(AccessInfo
.MemoryObject
);
261 for (auto &Entry
: BFs
) {
262 BinaryFunction
&BF
= Entry
.second
;
263 if (BF
.hasValidProfile()) {
264 for (BinaryData
*BD
: dataUses(BF
, true)) {
265 if (!BC
.getFunctionForSymbol(BD
->getSymbol())) {
266 BDtoFunc
[BD
->getAtomicRoot()].insert(&BF
);
267 BDtoFuncCount
[BD
->getAtomicRoot()] += BF
.getKnownExecutionCount();
273 DataOrder Order
= baseOrder(BC
, Section
);
274 unsigned SplitPoint
= Order
.size();
278 [&](const DataOrder::value_type
&A
, const DataOrder::value_type
&B
) {
279 // Total execution counts of functions referencing BD.
280 const uint64_t ACount
= BDtoFuncCount
[A
.first
];
281 const uint64_t BCount
= BDtoFuncCount
[B
.first
];
282 // Weight by number of loads/data size.
283 const double AWeight
= double(A
.second
) / A
.first
->getSize();
284 const double BWeight
= double(B
.second
) / B
.first
->getSize();
285 return (ACount
> BCount
||
287 (AWeight
> BWeight
||
288 (AWeight
== BWeight
&&
289 A
.first
->getAddress() < B
.first
->getAddress()))));
292 for (unsigned Idx
= 0; Idx
< Order
.size(); ++Idx
) {
293 if (!BDtoFuncCount
[Order
[Idx
].first
]) {
299 return std::make_pair(Order
, SplitPoint
);
302 std::pair
<DataOrder
, unsigned>
303 ReorderData::sortedByCount(BinaryContext
&BC
,
304 const BinarySection
&Section
) const {
305 DataOrder Order
= baseOrder(BC
, Section
);
306 unsigned SplitPoint
= Order
.size();
308 llvm::sort(Order
, [](const DataOrder::value_type
&A
,
309 const DataOrder::value_type
&B
) {
310 // Weight by number of loads/data size.
311 const double AWeight
= double(A
.second
) / A
.first
->getSize();
312 const double BWeight
= double(B
.second
) / B
.first
->getSize();
313 return (AWeight
> BWeight
||
314 (AWeight
== BWeight
&&
315 (A
.first
->getSize() < B
.first
->getSize() ||
316 (A
.first
->getSize() == B
.first
->getSize() &&
317 A
.first
->getAddress() < B
.first
->getAddress()))));
320 for (unsigned Idx
= 0; Idx
< Order
.size(); ++Idx
) {
321 if (!Order
[Idx
].second
) {
327 return std::make_pair(Order
, SplitPoint
);
331 // add option for cache-line alignment (or just use cache-line when section
333 void ReorderData::setSectionOrder(BinaryContext
&BC
,
334 BinarySection
&OutputSection
,
335 DataOrder::iterator Begin
,
336 DataOrder::iterator End
) {
337 std::vector
<BinaryData
*> NewOrder
;
338 unsigned NumReordered
= 0;
342 // Get the total count just for stats
343 uint64_t TotalCount
= 0;
344 for (auto Itr
= Begin
; Itr
!= End
; ++Itr
)
345 TotalCount
+= Itr
->second
;
347 LLVM_DEBUG(dbgs() << "BOLT-DEBUG: setSectionOrder for "
348 << OutputSection
.getName() << "\n");
350 for (; Begin
!= End
; ++Begin
) {
351 BinaryData
*BD
= Begin
->first
;
353 // We can't move certain symbols.
354 if (!filterSymbol(BD
))
358 if (NumReordered
> opts::ReorderDataMaxSymbols
) {
359 if (!NewOrder
.empty())
360 LLVM_DEBUG(dbgs() << "BOLT-DEBUG: processing ending on symbol "
361 << *NewOrder
.back() << "\n");
365 uint16_t Alignment
= std::max(BD
->getAlignment(), MinAlignment
);
366 Offset
= alignTo(Offset
, Alignment
);
368 if ((Offset
+ BD
->getSize()) > opts::ReorderDataMaxBytes
) {
369 if (!NewOrder
.empty())
370 LLVM_DEBUG(dbgs() << "BOLT-DEBUG: processing ending on symbol "
371 << *NewOrder
.back() << "\n");
375 LLVM_DEBUG(dbgs() << "BOLT-DEBUG: " << BD
->getName() << " @ 0x"
376 << Twine::utohexstr(Offset
) << "\n");
378 BD
->setOutputLocation(OutputSection
, Offset
);
380 // reorder sub-symbols
381 for (std::pair
<const uint64_t, BinaryData
*> &SubBD
:
382 BC
.getSubBinaryData(BD
)) {
383 if (!SubBD
.second
->isJumpTable()) {
385 Offset
+ SubBD
.second
->getAddress() - BD
->getAddress();
386 LLVM_DEBUG(dbgs() << "BOLT-DEBUG: SubBD " << SubBD
.second
->getName()
387 << " @ " << SubOffset
<< "\n");
388 SubBD
.second
->setOutputLocation(OutputSection
, SubOffset
);
392 Offset
+= BD
->getSize();
393 Count
+= Begin
->second
;
394 NewOrder
.push_back(BD
);
397 OutputSection
.reorderContents(NewOrder
, opts::ReorderInplace
);
399 BC
.outs() << "BOLT-INFO: reorder-data: " << Count
<< "/" << TotalCount
400 << format(" (%.1f%%)", 100.0 * Count
/ TotalCount
) << " events, "
401 << Offset
<< " hot bytes\n";
404 bool ReorderData::markUnmoveableSymbols(BinaryContext
&BC
,
405 BinarySection
&Section
) const {
406 // Private symbols currently can't be moved because data can "leak" across
407 // the boundary of one symbol to the next, e.g. a string that has a common
408 // suffix might start in one private symbol and end with the common
409 // suffix in another.
410 auto isPrivate
= [&](const BinaryData
*BD
) {
411 auto Prefix
= std::string("PG") + BC
.AsmInfo
->getPrivateGlobalPrefix();
412 return BD
->getName().starts_with(Prefix
.str());
414 auto Range
= BC
.getBinaryDataForSection(Section
);
415 bool FoundUnmoveable
= false;
416 for (auto Itr
= Range
.begin(); Itr
!= Range
.end(); ++Itr
) {
418 std::next(Itr
) != Range
.end() ? std::next(Itr
)->second
: nullptr;
419 if (Itr
->second
->getName().starts_with("PG.")) {
421 Itr
!= Range
.begin() ? std::prev(Itr
)->second
: nullptr;
422 bool PrevIsPrivate
= Prev
&& isPrivate(Prev
);
423 bool NextIsPrivate
= Next
&& isPrivate(Next
);
424 if (isPrivate(Itr
->second
) && (PrevIsPrivate
|| NextIsPrivate
))
425 Itr
->second
->setIsMoveable(false);
427 // check for overlapping symbols.
428 if (Next
&& Itr
->second
->getEndAddress() != Next
->getAddress() &&
429 Next
->containsAddress(Itr
->second
->getEndAddress())) {
430 Itr
->second
->setIsMoveable(false);
431 Next
->setIsMoveable(false);
434 FoundUnmoveable
|= !Itr
->second
->isMoveable();
436 return FoundUnmoveable
;
439 Error
ReorderData::runOnFunctions(BinaryContext
&BC
) {
440 static const char *DefaultSections
[] = {".rodata", ".data", ".bss", nullptr};
442 if (!BC
.HasRelocations
|| opts::ReorderData
.empty())
443 return Error::success();
446 if (opts::JumpTables
> JTS_BASIC
) {
447 BC
.outs() << "BOLT-WARNING: jump table support must be basic for "
448 << "data reordering to work.\n";
449 return Error::success();
454 std::vector
<BinarySection
*> Sections
;
456 for (const std::string
&SectionName
: opts::ReorderData
) {
457 if (SectionName
== "default") {
458 for (unsigned I
= 0; DefaultSections
[I
]; ++I
)
459 if (ErrorOr
<BinarySection
&> Section
=
460 BC
.getUniqueSectionByName(DefaultSections
[I
]))
461 Sections
.push_back(&*Section
);
465 ErrorOr
<BinarySection
&> Section
= BC
.getUniqueSectionByName(SectionName
);
467 BC
.outs() << "BOLT-WARNING: Section " << SectionName
468 << " not found, skipping.\n";
472 if (!isSupported(*Section
)) {
473 BC
.errs() << "BOLT-ERROR: Section " << SectionName
<< " not supported.\n";
474 return createFatalBOLTError("");
477 Sections
.push_back(&*Section
);
480 for (BinarySection
*Section
: Sections
) {
481 const bool FoundUnmoveable
= markUnmoveableSymbols(BC
, *Section
);
484 unsigned SplitPointIdx
;
486 if (opts::ReorderAlgorithm
== opts::ReorderAlgo::REORDER_COUNT
) {
487 BC
.outs() << "BOLT-INFO: reorder-sections: ordering data by count\n";
488 std::tie(Order
, SplitPointIdx
) = sortedByCount(BC
, *Section
);
490 BC
.outs() << "BOLT-INFO: reorder-sections: ordering data by funcs\n";
491 std::tie(Order
, SplitPointIdx
) =
492 sortedByFunc(BC
, *Section
, BC
.getBinaryFunctions());
494 auto SplitPoint
= Order
.begin() + SplitPointIdx
;
496 if (opts::PrintReorderedData
)
497 printOrder(BC
, *Section
, Order
.begin(), SplitPoint
);
499 if (!opts::ReorderInplace
|| FoundUnmoveable
) {
500 if (opts::ReorderInplace
&& FoundUnmoveable
)
501 BC
.outs() << "BOLT-INFO: Found unmoveable symbols in "
502 << Section
->getName() << " falling back to splitting "
503 << "instead of in-place reordering.\n";
507 BC
.registerSection(Section
->getName() + ".hot", *Section
);
508 Hot
.setOutputName(Section
->getName());
509 Section
->setOutputName(".bolt.org" + Section
->getName());
511 // Reorder contents of original section.
512 setSectionOrder(BC
, Hot
, Order
.begin(), SplitPoint
);
514 // This keeps the original data from thinking it has been moved.
515 for (std::pair
<const uint64_t, BinaryData
*> &Entry
:
516 BC
.getBinaryDataForSection(*Section
)) {
517 if (!Entry
.second
->isMoved()) {
518 Entry
.second
->setSection(*Section
);
519 Entry
.second
->setOutputSection(*Section
);
524 << "BOLT-WARNING: Inplace section reordering not supported yet.\n";
525 setSectionOrder(BC
, *Section
, Order
.begin(), Order
.end());
528 return Error::success();