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