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