1 //===- bolt/Passes/ReorderSection.cpp - Reordering of section data --------===//
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 ReorderData class.
10 //
11 //===----------------------------------------------------------------------===//
12
13 // TODO:
14 // - make sure writeable data isn't put on same cache line unless temporally
15 // local
16 // - estimate temporal locality by looking at CFG?
17
18 #include "bolt/Passes/ReorderData.h"
19 #include <algorithm>
20
21 #undef DEBUG_TYPE
22 #define DEBUG_TYPE "reorder-data"
23
24 using namespace llvm;
25 using namespace bolt;
26
27 namespace opts {
28 extern cl::OptionCategory BoltCategory;
29 extern cl::OptionCategory BoltOptCategory;
30 extern cl::opt<JumpTableSupportLevel> JumpTables;
31
32 static cl::opt<bool>
33 PrintReorderedData("print-reordered-data",
34 cl::desc("print section contents after reordering"),
35 cl::Hidden, cl::cat(BoltCategory));
36
37 cl::list<std::string>
38 ReorderData("reorder-data",
39 cl::CommaSeparated,
40 cl::desc("list of sections to reorder"),
41 cl::value_desc("section1,section2,section3,..."),
42 cl::cat(BoltOptCategory));
43
44 enum ReorderAlgo : char {
45 REORDER_COUNT = 0,
46 REORDER_FUNCS = 1
47 };
48
49 static cl::opt<ReorderAlgo>
50 ReorderAlgorithm("reorder-data-algo",
51 cl::desc("algorithm used to reorder data sections"),
52 cl::init(REORDER_COUNT),
53 cl::values(
54 clEnumValN(REORDER_COUNT,
55 "count",
56 "sort hot data by read counts"),
57 clEnumValN(REORDER_FUNCS,
58 "funcs",
59 "sort hot data by hot function usage and count")),
60 cl::ZeroOrMore,
61 cl::cat(BoltOptCategory));
62
63 static cl::opt<unsigned>
64 ReorderDataMaxSymbols("reorder-data-max-symbols",
65 cl::desc("maximum number of symbols to reorder"),
66 cl::init(std::numeric_limits<unsigned>::max()),
67 cl::cat(BoltOptCategory));
68
69 static cl::opt<unsigned> ReorderDataMaxBytes(
70 "reorder-data-max-bytes", cl::desc("maximum number of bytes to reorder"),
71 cl::init(std::numeric_limits<unsigned>::max()), cl::cat(BoltOptCategory));
72
73 static cl::list<std::string>
74 ReorderSymbols("reorder-symbols",
75 cl::CommaSeparated,
76 cl::desc("list of symbol names that can be reordered"),
77 cl::value_desc("symbol1,symbol2,symbol3,..."),
78 cl::Hidden,
79 cl::cat(BoltCategory));
80
81 static cl::list<std::string>
82 SkipSymbols("reorder-skip-symbols",
83 cl::CommaSeparated,
84 cl::desc("list of symbol names that cannot be reordered"),
85 cl::value_desc("symbol1,symbol2,symbol3,..."),
86 cl::Hidden,
87 cl::cat(BoltCategory));
88
89 static cl::opt<bool> ReorderInplace("reorder-data-inplace",
90 cl::desc("reorder data sections in place"),
91
92 cl::cat(BoltOptCategory));
93 }
94
95 namespace llvm {
96 namespace bolt {
97
98 namespace {
99
100 static constexpr uint16_t MinAlignment = 16;
101
isSupported(const BinarySection & BS)102 bool isSupported(const BinarySection &BS) { return BS.isData() && !BS.isTLS(); }
103
filterSymbol(const BinaryData * BD)104 bool filterSymbol(const BinaryData *BD) {
105 if (!BD->isAtomic() || BD->isJumpTable() || !BD->isMoveable())
106 return false;
107
108 bool IsValid = true;
109
110 if (!opts::ReorderSymbols.empty()) {
111 IsValid = false;
112 for (const std::string &Name : opts::ReorderSymbols) {
113 if (BD->hasName(Name)) {
114 IsValid = true;
115 break;
116 }
117 }
118 }
119
120 if (!IsValid)
121 return false;
122
123 if (!opts::SkipSymbols.empty()) {
124 for (const std::string &Name : opts::SkipSymbols) {
125 if (BD->hasName(Name)) {
126 IsValid = false;
127 break;
128 }
129 }
130 }
131
132 return IsValid;
133 }
134
135 } // namespace
136
137 using DataOrder = ReorderData::DataOrder;
138
printOrder(const BinarySection & Section,DataOrder::const_iterator Begin,DataOrder::const_iterator End) const139 void ReorderData::printOrder(const BinarySection &Section,
140 DataOrder::const_iterator Begin,
141 DataOrder::const_iterator End) const {
142 uint64_t TotalSize = 0;
143 bool PrintHeader = false;
144 while (Begin != End) {
145 const BinaryData *BD = Begin->first;
146
147 if (!PrintHeader) {
148 outs() << "BOLT-INFO: Hot global symbols for " << Section.getName()
149 << ":\n";
150 PrintHeader = true;
151 }
152
153 outs() << "BOLT-INFO: " << *BD << ", moveable=" << BD->isMoveable()
154 << format(", weight=%.5f\n", double(Begin->second) / BD->getSize());
155
156 TotalSize += BD->getSize();
157 ++Begin;
158 }
159 if (TotalSize)
160 outs() << "BOLT-INFO: Total hot symbol size = " << TotalSize << "\n";
161 }
162
baseOrder(BinaryContext & BC,const BinarySection & Section) const163 DataOrder ReorderData::baseOrder(BinaryContext &BC,
164 const BinarySection &Section) const {
165 DataOrder Order;
166 for (auto &Entry : BC.getBinaryDataForSection(Section)) {
167 BinaryData *BD = Entry.second;
168 if (!BD->isAtomic()) // skip sub-symbols
169 continue;
170 auto BDCI = BinaryDataCounts.find(BD);
171 uint64_t BDCount = BDCI == BinaryDataCounts.end() ? 0 : BDCI->second;
172 Order.emplace_back(BD, BDCount);
173 }
174 return Order;
175 }
176
assignMemData(BinaryContext & BC)177 void ReorderData::assignMemData(BinaryContext &BC) {
178 // Map of sections (or heap/stack) to count/size.
179 StringMap<uint64_t> Counts;
180 StringMap<uint64_t> JumpTableCounts;
181 uint64_t TotalCount = 0;
182 for (auto &BFI : BC.getBinaryFunctions()) {
183 const BinaryFunction &BF = BFI.second;
184 if (!BF.hasMemoryProfile())
185 continue;
186
187 for (const BinaryBasicBlock &BB : BF) {
188 for (const MCInst &Inst : BB) {
189 auto ErrorOrMemAccesssProfile =
190 BC.MIB->tryGetAnnotationAs<MemoryAccessProfile>(
191 Inst, "MemoryAccessProfile");
192 if (!ErrorOrMemAccesssProfile)
193 continue;
194
195 const MemoryAccessProfile &MemAccessProfile =
196 ErrorOrMemAccesssProfile.get();
197 for (const AddressAccess &AccessInfo :
198 MemAccessProfile.AddressAccessInfo) {
199 if (BinaryData *BD = AccessInfo.MemoryObject) {
200 BinaryDataCounts[BD->getAtomicRoot()] += AccessInfo.Count;
201 Counts[BD->getSectionName()] += AccessInfo.Count;
202 if (BD->getAtomicRoot()->isJumpTable())
203 JumpTableCounts[BD->getSectionName()] += AccessInfo.Count;
204 } else {
205 Counts["Heap/stack"] += AccessInfo.Count;
206 }
207 TotalCount += AccessInfo.Count;
208 }
209 }
210 }
211 }
212
213 if (!Counts.empty()) {
214 outs() << "BOLT-INFO: Memory stats breakdown:\n";
215 for (StringMapEntry<uint64_t> &Entry : Counts) {
216 StringRef Section = Entry.first();
217 const uint64_t Count = Entry.second;
218 outs() << "BOLT-INFO: " << Section << " = " << Count
219 << format(" (%.1f%%)\n", 100.0 * Count / TotalCount);
220 if (JumpTableCounts.count(Section) != 0) {
221 const uint64_t JTCount = JumpTableCounts[Section];
222 outs() << "BOLT-INFO: jump tables = " << JTCount
223 << format(" (%.1f%%)\n", 100.0 * JTCount / Count);
224 }
225 }
226 outs() << "BOLT-INFO: Total memory events: " << TotalCount << "\n";
227 }
228 }
229
230 /// Only consider moving data that is used by the hottest functions with
231 /// valid profiles.
232 std::pair<DataOrder, unsigned>
sortedByFunc(BinaryContext & BC,const BinarySection & Section,std::map<uint64_t,BinaryFunction> & BFs) const233 ReorderData::sortedByFunc(BinaryContext &BC, const BinarySection &Section,
234 std::map<uint64_t, BinaryFunction> &BFs) const {
235 std::map<BinaryData *, std::set<BinaryFunction *>> BDtoFunc;
236 std::map<BinaryData *, uint64_t> BDtoFuncCount;
237
238 auto dataUses = [&BC](const BinaryFunction &BF, bool OnlyHot) {
239 std::set<BinaryData *> Uses;
240 for (const BinaryBasicBlock &BB : BF) {
241 if (OnlyHot && BB.isCold())
242 continue;
243
244 for (const MCInst &Inst : BB) {
245 auto ErrorOrMemAccesssProfile =
246 BC.MIB->tryGetAnnotationAs<MemoryAccessProfile>(
247 Inst, "MemoryAccessProfile");
248 if (!ErrorOrMemAccesssProfile)
249 continue;
250
251 const MemoryAccessProfile &MemAccessProfile =
252 ErrorOrMemAccesssProfile.get();
253 for (const AddressAccess &AccessInfo :
254 MemAccessProfile.AddressAccessInfo) {
255 if (AccessInfo.MemoryObject)
256 Uses.insert(AccessInfo.MemoryObject);
257 }
258 }
259 }
260 return Uses;
261 };
262
263 for (auto &Entry : BFs) {
264 BinaryFunction &BF = Entry.second;
265 if (BF.hasValidProfile()) {
266 for (BinaryData *BD : dataUses(BF, true)) {
267 if (!BC.getFunctionForSymbol(BD->getSymbol())) {
268 BDtoFunc[BD->getAtomicRoot()].insert(&BF);
269 BDtoFuncCount[BD->getAtomicRoot()] += BF.getKnownExecutionCount();
270 }
271 }
272 }
273 }
274
275 DataOrder Order = baseOrder(BC, Section);
276 unsigned SplitPoint = Order.size();
277
278 llvm::sort(
279 Order,
280 [&](const DataOrder::value_type &A, const DataOrder::value_type &B) {
281 // Total execution counts of functions referencing BD.
282 const uint64_t ACount = BDtoFuncCount[A.first];
283 const uint64_t BCount = BDtoFuncCount[B.first];
284 // Weight by number of loads/data size.
285 const double AWeight = double(A.second) / A.first->getSize();
286 const double BWeight = double(B.second) / B.first->getSize();
287 return (ACount > BCount ||
288 (ACount == BCount &&
289 (AWeight > BWeight ||
290 (AWeight == BWeight &&
291 A.first->getAddress() < B.first->getAddress()))));
292 });
293
294 for (unsigned Idx = 0; Idx < Order.size(); ++Idx) {
295 if (!BDtoFuncCount[Order[Idx].first]) {
296 SplitPoint = Idx;
297 break;
298 }
299 }
300
301 return std::make_pair(Order, SplitPoint);
302 }
303
304 std::pair<DataOrder, unsigned>
sortedByCount(BinaryContext & BC,const BinarySection & Section) const305 ReorderData::sortedByCount(BinaryContext &BC,
306 const BinarySection &Section) const {
307 DataOrder Order = baseOrder(BC, Section);
308 unsigned SplitPoint = Order.size();
309
310 llvm::sort(Order, [](const DataOrder::value_type &A,
311 const DataOrder::value_type &B) {
312 // Weight by number of loads/data size.
313 const double AWeight = double(A.second) / A.first->getSize();
314 const double BWeight = double(B.second) / B.first->getSize();
315 return (AWeight > BWeight ||
316 (AWeight == BWeight &&
317 (A.first->getSize() < B.first->getSize() ||
318 (A.first->getSize() == B.first->getSize() &&
319 A.first->getAddress() < B.first->getAddress()))));
320 });
321
322 for (unsigned Idx = 0; Idx < Order.size(); ++Idx) {
323 if (!Order[Idx].second) {
324 SplitPoint = Idx;
325 break;
326 }
327 }
328
329 return std::make_pair(Order, SplitPoint);
330 }
331
332 // TODO
333 // add option for cache-line alignment (or just use cache-line when section
334 // is writeable)?
setSectionOrder(BinaryContext & BC,BinarySection & OutputSection,DataOrder::iterator Begin,DataOrder::iterator End)335 void ReorderData::setSectionOrder(BinaryContext &BC,
336 BinarySection &OutputSection,
337 DataOrder::iterator Begin,
338 DataOrder::iterator End) {
339 std::vector<BinaryData *> NewOrder;
340 unsigned NumReordered = 0;
341 uint64_t Offset = 0;
342 uint64_t Count = 0;
343
344 // Get the total count just for stats
345 uint64_t TotalCount = 0;
346 for (auto Itr = Begin; Itr != End; ++Itr)
347 TotalCount += Itr->second;
348
349 LLVM_DEBUG(dbgs() << "BOLT-DEBUG: setSectionOrder for "
350 << OutputSection.getName() << "\n");
351
352 for (; Begin != End; ++Begin) {
353 BinaryData *BD = Begin->first;
354
355 // We can't move certain symbols.
356 if (!filterSymbol(BD))
357 continue;
358
359 ++NumReordered;
360 if (NumReordered > opts::ReorderDataMaxSymbols) {
361 if (!NewOrder.empty())
362 LLVM_DEBUG(dbgs() << "BOLT-DEBUG: processing ending on symbol "
363 << *NewOrder.back() << "\n");
364 break;
365 }
366
367 uint16_t Alignment = std::max(BD->getAlignment(), MinAlignment);
368 Offset = alignTo(Offset, Alignment);
369
370 if ((Offset + BD->getSize()) > opts::ReorderDataMaxBytes) {
371 if (!NewOrder.empty())
372 LLVM_DEBUG(dbgs() << "BOLT-DEBUG: processing ending on symbol "
373 << *NewOrder.back() << "\n");
374 break;
375 }
376
377 LLVM_DEBUG(dbgs() << "BOLT-DEBUG: " << BD->getName() << " @ 0x"
378 << Twine::utohexstr(Offset) << "\n");
379
380 BD->setOutputLocation(OutputSection, Offset);
381
382 // reorder sub-symbols
383 for (std::pair<const uint64_t, BinaryData *> &SubBD :
384 BC.getSubBinaryData(BD)) {
385 if (!SubBD.second->isJumpTable()) {
386 uint64_t SubOffset =
387 Offset + SubBD.second->getAddress() - BD->getAddress();
388 LLVM_DEBUG(dbgs() << "BOLT-DEBUG: SubBD " << SubBD.second->getName()
389 << " @ " << SubOffset << "\n");
390 SubBD.second->setOutputLocation(OutputSection, SubOffset);
391 }
392 }
393
394 Offset += BD->getSize();
395 Count += Begin->second;
396 NewOrder.push_back(BD);
397 }
398
399 OutputSection.reorderContents(NewOrder, opts::ReorderInplace);
400
401 outs() << "BOLT-INFO: reorder-data: " << Count << "/" << TotalCount
402 << format(" (%.1f%%)", 100.0 * Count / TotalCount) << " events, "
403 << Offset << " hot bytes\n";
404 }
405
markUnmoveableSymbols(BinaryContext & BC,BinarySection & Section) const406 bool ReorderData::markUnmoveableSymbols(BinaryContext &BC,
407 BinarySection &Section) const {
408 // Private symbols currently can't be moved because data can "leak" across
409 // the boundary of one symbol to the next, e.g. a string that has a common
410 // suffix might start in one private symbol and end with the common
411 // suffix in another.
412 auto isPrivate = [&](const BinaryData *BD) {
413 auto Prefix = std::string("PG") + BC.AsmInfo->getPrivateGlobalPrefix();
414 return BD->getName().startswith(Prefix.str());
415 };
416 auto Range = BC.getBinaryDataForSection(Section);
417 bool FoundUnmoveable = false;
418 for (auto Itr = Range.begin(); Itr != Range.end(); ++Itr) {
419 if (Itr->second->getName().startswith("PG.")) {
420 BinaryData *Prev =
421 Itr != Range.begin() ? std::prev(Itr)->second : nullptr;
422 BinaryData *Next = Itr != Range.end() ? std::next(Itr)->second : nullptr;
423 bool PrevIsPrivate = Prev && isPrivate(Prev);
424 bool NextIsPrivate = Next && isPrivate(Next);
425 if (isPrivate(Itr->second) && (PrevIsPrivate || NextIsPrivate))
426 Itr->second->setIsMoveable(false);
427 } else {
428 // check for overlapping symbols.
429 BinaryData *Next = Itr != Range.end() ? std::next(Itr)->second : nullptr;
430 if (Next && Itr->second->getEndAddress() != Next->getAddress() &&
431 Next->containsAddress(Itr->second->getEndAddress())) {
432 Itr->second->setIsMoveable(false);
433 Next->setIsMoveable(false);
434 }
435 }
436 FoundUnmoveable |= !Itr->second->isMoveable();
437 }
438 return FoundUnmoveable;
439 }
440
runOnFunctions(BinaryContext & BC)441 void ReorderData::runOnFunctions(BinaryContext &BC) {
442 static const char *DefaultSections[] = {".rodata", ".data", ".bss", nullptr};
443
444 if (!BC.HasRelocations || opts::ReorderData.empty())
445 return;
446
447 // For now
448 if (opts::JumpTables > JTS_BASIC) {
449 outs() << "BOLT-WARNING: jump table support must be basic for "
450 << "data reordering to work.\n";
451 return;
452 }
453
454 assignMemData(BC);
455
456 std::vector<BinarySection *> Sections;
457
458 for (const std::string &SectionName : opts::ReorderData) {
459 if (SectionName == "default") {
460 for (unsigned I = 0; DefaultSections[I]; ++I)
461 if (ErrorOr<BinarySection &> Section =
462 BC.getUniqueSectionByName(DefaultSections[I]))
463 Sections.push_back(&*Section);
464 continue;
465 }
466
467 ErrorOr<BinarySection &> Section = BC.getUniqueSectionByName(SectionName);
468 if (!Section) {
469 outs() << "BOLT-WARNING: Section " << SectionName
470 << " not found, skipping.\n";
471 continue;
472 }
473
474 if (!isSupported(*Section)) {
475 outs() << "BOLT-ERROR: Section " << SectionName << " not supported.\n";
476 exit(1);
477 }
478
479 Sections.push_back(&*Section);
480 }
481
482 for (BinarySection *Section : Sections) {
483 const bool FoundUnmoveable = markUnmoveableSymbols(BC, *Section);
484
485 DataOrder Order;
486 unsigned SplitPointIdx;
487
488 if (opts::ReorderAlgorithm == opts::ReorderAlgo::REORDER_COUNT) {
489 outs() << "BOLT-INFO: reorder-sections: ordering data by count\n";
490 std::tie(Order, SplitPointIdx) = sortedByCount(BC, *Section);
491 } else {
492 outs() << "BOLT-INFO: reorder-sections: ordering data by funcs\n";
493 std::tie(Order, SplitPointIdx) =
494 sortedByFunc(BC, *Section, BC.getBinaryFunctions());
495 }
496 auto SplitPoint = Order.begin() + SplitPointIdx;
497
498 if (opts::PrintReorderedData)
499 printOrder(*Section, Order.begin(), SplitPoint);
500
501 if (!opts::ReorderInplace || FoundUnmoveable) {
502 if (opts::ReorderInplace && FoundUnmoveable)
503 outs() << "BOLT-INFO: Found unmoveable symbols in "
504 << Section->getName() << " falling back to splitting "
505 << "instead of in-place reordering.\n";
506
507 // Copy original section to <section name>.cold.
508 BinarySection &Cold = BC.registerSection(
509 std::string(Section->getName()) + ".cold", *Section);
510
511 // Reorder contents of original section.
512 setSectionOrder(BC, *Section, Order.begin(), SplitPoint);
513
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(Cold);
519 Entry.second->setOutputSection(Cold);
520 }
521 }
522 } else {
523 outs() << "BOLT-WARNING: Inplace section reordering not supported yet.\n";
524 setSectionOrder(BC, *Section, Order.begin(), Order.end());
525 }
526 }
527 }
528
529 } // namespace bolt
530 } // namespace llvm
531