xref: /llvm-project/bolt/lib/Passes/ReorderData.cpp (revision d2c876993625ce9b36bdd7ccc5e0c4cb04f32fb9)
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 
102 bool isSupported(const BinarySection &BS) { return BS.isData() && !BS.isTLS(); }
103 
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 
139 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 
163 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 
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>
233 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>
305 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)?
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 
406 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 
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