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