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