1 //===- bolt/Rewrite/BoltDiff.cpp ------------------------------------------===// 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 // RewriteInstance methods related to comparing one instance to another, used 10 // by the boltdiff tool to print a report. 11 // 12 //===----------------------------------------------------------------------===// 13 14 #include "bolt/Passes/IdenticalCodeFolding.h" 15 #include "bolt/Profile/ProfileReaderBase.h" 16 #include "bolt/Rewrite/RewriteInstance.h" 17 #include "llvm/ADT/STLExtras.h" 18 #include "llvm/Support/CommandLine.h" 19 20 #undef DEBUG_TYPE 21 #define DEBUG_TYPE "boltdiff" 22 23 using namespace llvm; 24 using namespace object; 25 using namespace bolt; 26 27 namespace opts { 28 extern cl::OptionCategory BoltDiffCategory; 29 extern cl::opt<bool> NeverPrint; 30 extern cl::opt<bool> ICF; 31 32 static cl::opt<bool> IgnoreLTOSuffix( 33 "ignore-lto-suffix", 34 cl::desc("ignore lto_priv or const suffixes when matching functions"), 35 cl::init(true), cl::cat(BoltDiffCategory)); 36 37 static cl::opt<bool> PrintUnmapped( 38 "print-unmapped", 39 cl::desc("print functions of binary 2 that were not matched to any " 40 "function in binary 1"), 41 cl::cat(BoltDiffCategory)); 42 43 static cl::opt<bool> PrintProfiledUnmapped( 44 "print-profiled-unmapped", 45 cl::desc("print functions that have profile in binary 1 but do not " 46 "in binary 2"), 47 cl::cat(BoltDiffCategory)); 48 49 static cl::opt<bool> PrintDiffCFG( 50 "print-diff-cfg", 51 cl::desc("print the CFG of important functions that changed in " 52 "binary 2"), 53 cl::cat(BoltDiffCategory)); 54 55 static cl::opt<bool> 56 PrintDiffBBs("print-diff-bbs", 57 cl::desc("print the basic blocks showed in top differences"), 58 cl::cat(BoltDiffCategory)); 59 60 static cl::opt<bool> MatchByHash( 61 "match-by-hash", 62 cl::desc("match functions in binary 2 to binary 1 if they have the same " 63 "hash of a function in binary 1"), 64 cl::cat(BoltDiffCategory)); 65 66 static cl::opt<bool> IgnoreUnchanged( 67 "ignore-unchanged", 68 cl::desc("do not diff functions whose contents have not been changed from " 69 "one binary to another"), 70 cl::cat(BoltDiffCategory)); 71 72 static cl::opt<unsigned> DisplayCount( 73 "display-count", 74 cl::desc("number of functions to display when printing the top largest " 75 "differences in function activity"), 76 cl::init(10), cl::cat(BoltDiffCategory)); 77 78 static cl::opt<bool> NormalizeByBin1( 79 "normalize-by-bin1", 80 cl::desc("show execution count of functions in binary 2 as a ratio of the " 81 "total samples in binary 1 - make sure both profiles have equal " 82 "collection time and sampling rate for this to make sense"), 83 cl::cat(BoltDiffCategory)); 84 85 } // end namespace opts 86 87 namespace llvm { 88 namespace bolt { 89 90 namespace { 91 92 /// Helper used to print colored numbers 93 void printColoredPercentage(double Perc) { 94 if (outs().has_colors() && Perc > 0.0) 95 outs().changeColor(raw_ostream::RED); 96 else if (outs().has_colors() && Perc < 0.0) 97 outs().changeColor(raw_ostream::GREEN); 98 else if (outs().has_colors()) 99 outs().changeColor(raw_ostream::YELLOW); 100 outs() << format("%.2f", Perc) << "%"; 101 if (outs().has_colors()) 102 outs().resetColor(); 103 } 104 105 void setLightColor() { 106 if (opts::PrintDiffBBs && outs().has_colors()) 107 outs().changeColor(raw_ostream::CYAN); 108 } 109 110 void setTitleColor() { 111 if (outs().has_colors()) 112 outs().changeColor(raw_ostream::WHITE, /*Bold=*/true); 113 } 114 115 void setRegularColor() { 116 if (outs().has_colors()) 117 outs().resetColor(); 118 } 119 120 } // end anonymous namespace 121 122 /// Perform the comparison between two binaries with profiling information 123 class RewriteInstanceDiff { 124 typedef std::tuple<const BinaryBasicBlock *, const BinaryBasicBlock *, double> 125 EdgeTy; 126 127 RewriteInstance &RI1; 128 RewriteInstance &RI2; 129 130 // The map of functions keyed by functions in binary 2, providing its 131 // corresponding function in binary 1 132 std::map<const BinaryFunction *, const BinaryFunction *> FuncMap; 133 134 // The map of basic blocks correspondence, analogue to FuncMap for BBs, 135 // sorted by score difference 136 std::map<const BinaryBasicBlock *, const BinaryBasicBlock *> BBMap; 137 138 // The map of edge correspondence 139 std::map<double, std::pair<EdgeTy, EdgeTy>> EdgeMap; 140 141 // Maps all known basic blocks back to their parent function 142 std::map<const BinaryBasicBlock *, const BinaryFunction *> BBToFuncMap; 143 144 // Accounting which functions were matched 145 std::set<const BinaryFunction *> Bin1MappedFuncs; 146 std::set<const BinaryFunction *> Bin2MappedFuncs; 147 148 // Structures for our 3 matching strategies: by name, by hash and by lto name, 149 // from the strongest to the weakest bind between two functions 150 StringMap<const BinaryFunction *> NameLookup; 151 DenseMap<size_t, const BinaryFunction *> HashLookup; 152 StringMap<const BinaryFunction *> LTONameLookup1; 153 StringMap<const BinaryFunction *> LTONameLookup2; 154 155 // Score maps used to order and find hottest functions 156 std::multimap<double, const BinaryFunction *> LargestBin1; 157 std::multimap<double, const BinaryFunction *> LargestBin2; 158 159 // Map multiple functions in the same LTO bucket to a single parent function 160 // representing all functions sharing the same prefix 161 std::map<const BinaryFunction *, const BinaryFunction *> LTOMap1; 162 std::map<const BinaryFunction *, const BinaryFunction *> LTOMap2; 163 std::map<const BinaryFunction *, double> LTOAggregatedScore1; 164 std::map<const BinaryFunction *, double> LTOAggregatedScore2; 165 166 // Map scores in bin2 and 1 keyed by a binary 2 function - post-matching 167 DenseMap<const BinaryFunction *, std::pair<double, double>> ScoreMap; 168 169 double getNormalizedScore(const BinaryFunction &Function, 170 const RewriteInstance &Ctx) { 171 if (!opts::NormalizeByBin1) 172 return static_cast<double>(Function.getFunctionScore()) / 173 Ctx.getTotalScore(); 174 return static_cast<double>(Function.getFunctionScore()) / 175 RI1.getTotalScore(); 176 } 177 178 double getNormalizedScore(const BinaryBasicBlock &BB, 179 const RewriteInstance &Ctx) { 180 if (!opts::NormalizeByBin1) 181 return static_cast<double>(BB.getKnownExecutionCount()) / 182 Ctx.getTotalScore(); 183 return static_cast<double>(BB.getKnownExecutionCount()) / 184 RI1.getTotalScore(); 185 } 186 187 double getNormalizedScore(BinaryBasicBlock::const_branch_info_iterator BIIter, 188 const RewriteInstance &Ctx) { 189 double Score = 190 BIIter->Count == BinaryBasicBlock::COUNT_NO_PROFILE ? 0 : BIIter->Count; 191 if (!opts::NormalizeByBin1) 192 return Score / Ctx.getTotalScore(); 193 return Score / RI1.getTotalScore(); 194 } 195 196 /// Initialize data structures used for function lookup in binary 1, used 197 /// later when matching functions in binary 2 to corresponding functions 198 /// in binary 1 199 void buildLookupMaps() { 200 for (const auto &BFI : RI1.BC->getBinaryFunctions()) { 201 StringRef LTOName; 202 const BinaryFunction &Function = BFI.second; 203 const double Score = getNormalizedScore(Function, RI1); 204 LargestBin1.insert(std::make_pair<>(Score, &Function)); 205 for (const StringRef &Name : Function.getNames()) { 206 if (std::optional<StringRef> OptionalLTOName = getLTOCommonName(Name)) 207 LTOName = *OptionalLTOName; 208 NameLookup[Name] = &Function; 209 } 210 if (opts::MatchByHash && Function.hasCFG()) 211 HashLookup[Function.computeHash(/*UseDFS=*/true)] = &Function; 212 if (opts::IgnoreLTOSuffix && !LTOName.empty()) { 213 if (!LTONameLookup1.count(LTOName)) 214 LTONameLookup1[LTOName] = &Function; 215 LTOMap1[&Function] = LTONameLookup1[LTOName]; 216 } 217 } 218 219 // Compute LTONameLookup2 and LargestBin2 220 for (const auto &BFI : RI2.BC->getBinaryFunctions()) { 221 StringRef LTOName; 222 const BinaryFunction &Function = BFI.second; 223 const double Score = getNormalizedScore(Function, RI2); 224 LargestBin2.insert(std::make_pair<>(Score, &Function)); 225 for (const StringRef &Name : Function.getNames()) { 226 if (std::optional<StringRef> OptionalLTOName = getLTOCommonName(Name)) 227 LTOName = *OptionalLTOName; 228 } 229 if (opts::IgnoreLTOSuffix && !LTOName.empty()) { 230 if (!LTONameLookup2.count(LTOName)) 231 LTONameLookup2[LTOName] = &Function; 232 LTOMap2[&Function] = LTONameLookup2[LTOName]; 233 } 234 } 235 } 236 237 /// Match functions in binary 2 with functions in binary 1 238 void matchFunctions() { 239 outs() << "BOLT-DIFF: Mapping functions in Binary2 to Binary1\n"; 240 uint64_t BothHaveProfile = 0ull; 241 std::set<const BinaryFunction *> Bin1ProfiledMapped; 242 243 for (const auto &BFI2 : RI2.BC->getBinaryFunctions()) { 244 const BinaryFunction &Function2 = BFI2.second; 245 StringRef LTOName; 246 bool Match = false; 247 for (const StringRef &Name : Function2.getNames()) { 248 auto Iter = NameLookup.find(Name); 249 if (std::optional<StringRef> OptionalLTOName = getLTOCommonName(Name)) 250 LTOName = *OptionalLTOName; 251 if (Iter == NameLookup.end()) 252 continue; 253 FuncMap.insert(std::make_pair<>(&Function2, Iter->second)); 254 Bin1MappedFuncs.insert(Iter->second); 255 Bin2MappedFuncs.insert(&Function2); 256 if (Function2.hasValidProfile() && Iter->second->hasValidProfile()) { 257 ++BothHaveProfile; 258 Bin1ProfiledMapped.insert(Iter->second); 259 } 260 Match = true; 261 break; 262 } 263 if (Match || !Function2.hasCFG()) 264 continue; 265 auto Iter = HashLookup.find(Function2.computeHash(/*UseDFS*/ true)); 266 if (Iter != HashLookup.end()) { 267 FuncMap.insert(std::make_pair<>(&Function2, Iter->second)); 268 Bin1MappedFuncs.insert(Iter->second); 269 Bin2MappedFuncs.insert(&Function2); 270 if (Function2.hasValidProfile() && Iter->second->hasValidProfile()) { 271 ++BothHaveProfile; 272 Bin1ProfiledMapped.insert(Iter->second); 273 } 274 continue; 275 } 276 if (LTOName.empty()) 277 continue; 278 auto LTOIter = LTONameLookup1.find(LTOName); 279 if (LTOIter != LTONameLookup1.end()) { 280 FuncMap.insert(std::make_pair<>(&Function2, LTOIter->second)); 281 Bin1MappedFuncs.insert(LTOIter->second); 282 Bin2MappedFuncs.insert(&Function2); 283 if (Function2.hasValidProfile() && LTOIter->second->hasValidProfile()) { 284 ++BothHaveProfile; 285 Bin1ProfiledMapped.insert(LTOIter->second); 286 } 287 } 288 } 289 PrintProgramStats PPS(opts::NeverPrint); 290 outs() << "* BOLT-DIFF: Starting print program stats pass for binary 1\n"; 291 PPS.runOnFunctions(*RI1.BC); 292 outs() << "* BOLT-DIFF: Starting print program stats pass for binary 2\n"; 293 PPS.runOnFunctions(*RI2.BC); 294 outs() << "=====\n"; 295 outs() << "Inputs share " << BothHaveProfile 296 << " functions with valid profile.\n"; 297 if (opts::PrintProfiledUnmapped) { 298 outs() << "\nFunctions in profile 1 that are missing in the profile 2:\n"; 299 std::vector<const BinaryFunction *> Unmapped; 300 for (const auto &BFI : RI1.BC->getBinaryFunctions()) { 301 const BinaryFunction &Function = BFI.second; 302 if (!Function.hasValidProfile() || Bin1ProfiledMapped.count(&Function)) 303 continue; 304 Unmapped.emplace_back(&Function); 305 } 306 llvm::sort(Unmapped, 307 [&](const BinaryFunction *A, const BinaryFunction *B) { 308 return A->getFunctionScore() > B->getFunctionScore(); 309 }); 310 for (const BinaryFunction *Function : Unmapped) { 311 outs() << Function->getPrintName() << " : "; 312 outs() << Function->getFunctionScore() << "\n"; 313 } 314 outs() << "=====\n"; 315 } 316 } 317 318 /// Check if opcodes in BB1 match those in BB2 319 bool compareBBs(const BinaryBasicBlock &BB1, 320 const BinaryBasicBlock &BB2) const { 321 auto Iter1 = BB1.begin(); 322 auto Iter2 = BB2.begin(); 323 if ((Iter1 == BB1.end() && Iter2 != BB2.end()) || 324 (Iter1 != BB1.end() && Iter2 == BB2.end())) 325 return false; 326 327 while (Iter1 != BB1.end()) { 328 if (Iter2 == BB2.end() || Iter1->getOpcode() != Iter2->getOpcode()) 329 return false; 330 331 ++Iter1; 332 ++Iter2; 333 } 334 335 if (Iter2 != BB2.end()) 336 return false; 337 return true; 338 } 339 340 /// For a function in binary 2 that matched one in binary 1, now match each 341 /// individual basic block in it to its corresponding blocks in binary 1. 342 /// Also match each edge in binary 2 to the corresponding ones in binary 1. 343 void matchBasicBlocks() { 344 for (const auto &MapEntry : FuncMap) { 345 const BinaryFunction *const &Func1 = MapEntry.second; 346 const BinaryFunction *const &Func2 = MapEntry.first; 347 348 auto Iter1 = Func1->getLayout().block_begin(); 349 auto Iter2 = Func2->getLayout().block_begin(); 350 351 bool Match = true; 352 std::map<const BinaryBasicBlock *, const BinaryBasicBlock *> Map; 353 std::map<double, std::pair<EdgeTy, EdgeTy>> EMap; 354 while (Iter1 != Func1->getLayout().block_end()) { 355 if (Iter2 == Func2->getLayout().block_end()) { 356 Match = false; 357 break; 358 } 359 if (!compareBBs(**Iter1, **Iter2)) { 360 Match = false; 361 break; 362 } 363 Map.insert(std::make_pair<>(*Iter2, *Iter1)); 364 365 auto SuccIter1 = (*Iter1)->succ_begin(); 366 auto SuccIter2 = (*Iter2)->succ_begin(); 367 auto BIIter1 = (*Iter1)->branch_info_begin(); 368 auto BIIter2 = (*Iter2)->branch_info_begin(); 369 while (SuccIter1 != (*Iter1)->succ_end()) { 370 if (SuccIter2 == (*Iter2)->succ_end()) { 371 Match = false; 372 break; 373 } 374 const double ScoreEdge1 = getNormalizedScore(BIIter1, RI1); 375 const double ScoreEdge2 = getNormalizedScore(BIIter2, RI2); 376 EMap.insert(std::make_pair<>( 377 std::abs(ScoreEdge2 - ScoreEdge1), 378 std::make_pair<>( 379 std::make_tuple<>(*Iter2, *SuccIter2, ScoreEdge2), 380 std::make_tuple<>(*Iter1, *SuccIter1, ScoreEdge1)))); 381 382 ++SuccIter1; 383 ++SuccIter2; 384 ++BIIter1; 385 ++BIIter2; 386 } 387 if (SuccIter2 != (*Iter2)->succ_end()) 388 Match = false; 389 if (!Match) 390 break; 391 392 BBToFuncMap[*Iter1] = Func1; 393 BBToFuncMap[*Iter2] = Func2; 394 ++Iter1; 395 ++Iter2; 396 } 397 if (!Match || Iter2 != Func2->getLayout().block_end()) 398 continue; 399 400 BBMap.insert(Map.begin(), Map.end()); 401 EdgeMap.insert(EMap.begin(), EMap.end()); 402 } 403 } 404 405 /// Print the largest differences in basic block performance from binary 1 406 /// to binary 2 407 void reportHottestBBDiffs() { 408 std::map<double, const BinaryBasicBlock *> LargestDiffs; 409 for (const auto &MapEntry : BBMap) { 410 const BinaryBasicBlock *BB2 = MapEntry.first; 411 const BinaryBasicBlock *BB1 = MapEntry.second; 412 LargestDiffs.insert( 413 std::make_pair<>(std::abs(getNormalizedScore(*BB2, RI2) - 414 getNormalizedScore(*BB1, RI1)), 415 BB2)); 416 } 417 418 unsigned Printed = 0; 419 setTitleColor(); 420 outs() 421 << "\nTop " << opts::DisplayCount 422 << " largest differences in basic block performance bin 2 -> bin 1:\n"; 423 outs() << "=========================================================\n"; 424 setRegularColor(); 425 outs() << " * Functions with different contents do not appear here\n\n"; 426 for (const BinaryBasicBlock *BB2 : 427 llvm::make_second_range(llvm::reverse(LargestDiffs))) { 428 const double Score2 = getNormalizedScore(*BB2, RI2); 429 const double Score1 = getNormalizedScore(*BBMap[BB2], RI1); 430 outs() << "BB " << BB2->getName() << " from " 431 << BBToFuncMap[BB2]->getDemangledName() 432 << "\n\tScore bin1 = " << format("%.4f", Score1 * 100.0) 433 << "%\n\tScore bin2 = " << format("%.4f", Score2 * 100.0); 434 outs() << "%\t(Difference: "; 435 printColoredPercentage((Score2 - Score1) * 100.0); 436 outs() << ")\n"; 437 if (opts::PrintDiffBBs) { 438 setLightColor(); 439 BB2->dump(); 440 setRegularColor(); 441 } 442 if (Printed++ == opts::DisplayCount) 443 break; 444 } 445 } 446 447 /// Print the largest differences in edge counts from one binary to another 448 void reportHottestEdgeDiffs() { 449 unsigned Printed = 0; 450 setTitleColor(); 451 outs() << "\nTop " << opts::DisplayCount 452 << " largest differences in edge hotness bin 2 -> bin 1:\n"; 453 outs() << "=========================================================\n"; 454 setRegularColor(); 455 outs() << " * Functions with different contents do not appear here\n"; 456 for (std::pair<EdgeTy, EdgeTy> &EI : 457 llvm::make_second_range(llvm::reverse(EdgeMap))) { 458 EdgeTy &Edge2 = EI.first; 459 EdgeTy &Edge1 = EI.second; 460 const double Score2 = std::get<2>(Edge2); 461 const double Score1 = std::get<2>(Edge1); 462 outs() << "Edge (" << std::get<0>(Edge2)->getName() << " -> " 463 << std::get<1>(Edge2)->getName() << ") in " 464 << BBToFuncMap[std::get<0>(Edge2)]->getDemangledName() 465 << "\n\tScore bin1 = " << format("%.4f", Score1 * 100.0) 466 << "%\n\tScore bin2 = " << format("%.4f", Score2 * 100.0); 467 outs() << "%\t(Difference: "; 468 printColoredPercentage((Score2 - Score1) * 100.0); 469 outs() << ")\n"; 470 if (opts::PrintDiffBBs) { 471 setLightColor(); 472 std::get<0>(Edge2)->dump(); 473 std::get<1>(Edge2)->dump(); 474 setRegularColor(); 475 } 476 if (Printed++ == opts::DisplayCount) 477 break; 478 } 479 } 480 481 /// For LTO functions sharing the same prefix (for example, func1.lto_priv.1 482 /// and func1.lto_priv.2 share the func1.lto_priv prefix), compute aggregated 483 /// scores for them. This is used to avoid reporting all LTO functions as 484 /// having a large difference in performance because hotness shifted from 485 /// LTO variant 1 to variant 2, even though they represent the same function. 486 void computeAggregatedLTOScore() { 487 for (const auto &BFI : RI1.BC->getBinaryFunctions()) { 488 const BinaryFunction &Function = BFI.second; 489 double Score = getNormalizedScore(Function, RI1); 490 auto Iter = LTOMap1.find(&Function); 491 if (Iter == LTOMap1.end()) 492 continue; 493 LTOAggregatedScore1[Iter->second] += Score; 494 } 495 496 double UnmappedScore = 0; 497 for (const auto &BFI : RI2.BC->getBinaryFunctions()) { 498 const BinaryFunction &Function = BFI.second; 499 bool Matched = FuncMap.find(&Function) != FuncMap.end(); 500 double Score = getNormalizedScore(Function, RI2); 501 auto Iter = LTOMap2.find(&Function); 502 if (Iter == LTOMap2.end()) { 503 if (!Matched) 504 UnmappedScore += Score; 505 continue; 506 } 507 LTOAggregatedScore2[Iter->second] += Score; 508 if (FuncMap.find(Iter->second) == FuncMap.end()) 509 UnmappedScore += Score; 510 } 511 int64_t Unmapped = 512 RI2.BC->getBinaryFunctions().size() - Bin2MappedFuncs.size(); 513 outs() << "BOLT-DIFF: " << Unmapped 514 << " functions in Binary2 have no correspondence to any other " 515 "function in Binary1.\n"; 516 517 // Print the hotness score of functions in binary 2 that were not matched 518 // to any function in binary 1 519 outs() << "BOLT-DIFF: These unmapped functions in Binary2 represent " 520 << format("%.2f", UnmappedScore * 100.0) << "% of execution.\n"; 521 } 522 523 /// Print the largest hotness differences from binary 2 to binary 1 524 void reportHottestFuncDiffs() { 525 std::multimap<double, decltype(FuncMap)::value_type> LargestDiffs; 526 for (const auto &MapEntry : FuncMap) { 527 const BinaryFunction *const &Func1 = MapEntry.second; 528 const BinaryFunction *const &Func2 = MapEntry.first; 529 double Score1 = getNormalizedScore(*Func1, RI1); 530 auto Iter1 = LTOMap1.find(Func1); 531 if (Iter1 != LTOMap1.end()) 532 Score1 = LTOAggregatedScore1[Iter1->second]; 533 double Score2 = getNormalizedScore(*Func2, RI2); 534 auto Iter2 = LTOMap2.find(Func2); 535 if (Iter2 != LTOMap2.end()) 536 Score2 = LTOAggregatedScore2[Iter2->second]; 537 if (Score1 == 0.0 || Score2 == 0.0) 538 continue; 539 LargestDiffs.insert( 540 std::make_pair<>(std::abs(Score1 - Score2), MapEntry)); 541 ScoreMap[Func2] = std::make_pair<>(Score1, Score2); 542 } 543 544 unsigned Printed = 0; 545 setTitleColor(); 546 outs() << "\nTop " << opts::DisplayCount 547 << " largest differences in performance bin 2 -> bin 1:\n"; 548 outs() << "=========================================================\n"; 549 setRegularColor(); 550 for (decltype(this->FuncMap)::value_type &MapEntry : 551 llvm::make_second_range(llvm::reverse(LargestDiffs))) { 552 if (opts::IgnoreUnchanged && 553 MapEntry.second->computeHash(/*UseDFS=*/true) == 554 MapEntry.first->computeHash(/*UseDFS=*/true)) 555 continue; 556 const std::pair<double, double> &Scores = ScoreMap[MapEntry.first]; 557 outs() << "Function " << MapEntry.first->getDemangledName(); 558 if (MapEntry.first->getDemangledName() != 559 MapEntry.second->getDemangledName()) 560 outs() << "\nmatched " << MapEntry.second->getDemangledName(); 561 outs() << "\n\tScore bin1 = " << format("%.2f", Scores.first * 100.0) 562 << "%\n\tScore bin2 = " << format("%.2f", Scores.second * 100.0) 563 << "%\t(Difference: "; 564 printColoredPercentage((Scores.second - Scores.first) * 100.0); 565 outs() << ")"; 566 if (MapEntry.second->computeHash(/*UseDFS=*/true) != 567 MapEntry.first->computeHash(/*UseDFS=*/true)) { 568 outs() << "\t[Functions have different contents]"; 569 if (opts::PrintDiffCFG) { 570 outs() << "\n *** CFG for function in binary 1:\n"; 571 setLightColor(); 572 MapEntry.second->dump(); 573 setRegularColor(); 574 outs() << "\n *** CFG for function in binary 2:\n"; 575 setLightColor(); 576 MapEntry.first->dump(); 577 setRegularColor(); 578 } 579 } 580 outs() << "\n"; 581 if (Printed++ == opts::DisplayCount) 582 break; 583 } 584 } 585 586 /// Print hottest functions from each binary 587 void reportHottestFuncs() { 588 unsigned Printed = 0; 589 setTitleColor(); 590 outs() << "\nTop " << opts::DisplayCount 591 << " hottest functions in binary 2:\n"; 592 outs() << "=====================================\n"; 593 setRegularColor(); 594 for (std::pair<const double, const BinaryFunction *> &MapEntry : 595 llvm::reverse(LargestBin2)) { 596 outs() << "Function " << MapEntry.second->getDemangledName() << "\n"; 597 auto Iter = ScoreMap.find(MapEntry.second); 598 if (Iter != ScoreMap.end()) 599 outs() << "\tScore bin1 = " 600 << format("%.2f", Iter->second.first * 100.0) << "%\n"; 601 outs() << "\tScore bin2 = " << format("%.2f", MapEntry.first * 100.0) 602 << "%\n"; 603 if (Printed++ == opts::DisplayCount) 604 break; 605 } 606 607 Printed = 0; 608 setTitleColor(); 609 outs() << "\nTop " << opts::DisplayCount 610 << " hottest functions in binary 1:\n"; 611 outs() << "=====================================\n"; 612 setRegularColor(); 613 for (const std::pair<const double, const BinaryFunction *> &MapEntry : 614 llvm::reverse(LargestBin1)) { 615 outs() << "Function " << MapEntry.second->getDemangledName() 616 << "\n\tScore bin1 = " << format("%.2f", MapEntry.first * 100.0) 617 << "%\n"; 618 if (Printed++ == opts::DisplayCount) 619 break; 620 } 621 } 622 623 /// Print functions in binary 2 that did not match anything in binary 1. 624 /// Unfortunately, in an LTO build, even a small change can lead to several 625 /// LTO variants being unmapped, corresponding to local functions that never 626 /// appear in one of the binaries because they were previously inlined. 627 void reportUnmapped() { 628 outs() << "List of functions from binary 2 that were not matched with any " 629 << "function in binary 1:\n"; 630 for (const auto &BFI2 : RI2.BC->getBinaryFunctions()) { 631 const BinaryFunction &Function2 = BFI2.second; 632 if (Bin2MappedFuncs.count(&Function2)) 633 continue; 634 outs() << Function2.getPrintName() << "\n"; 635 } 636 } 637 638 public: 639 /// Main entry point: coordinate all tasks necessary to compare two binaries 640 void compareAndReport() { 641 buildLookupMaps(); 642 matchFunctions(); 643 if (opts::IgnoreLTOSuffix) 644 computeAggregatedLTOScore(); 645 matchBasicBlocks(); 646 reportHottestFuncDiffs(); 647 reportHottestBBDiffs(); 648 reportHottestEdgeDiffs(); 649 reportHottestFuncs(); 650 if (!opts::PrintUnmapped) 651 return; 652 reportUnmapped(); 653 } 654 655 RewriteInstanceDiff(RewriteInstance &RI1, RewriteInstance &RI2) 656 : RI1(RI1), RI2(RI2) { 657 compareAndReport(); 658 } 659 660 }; 661 662 } // end nampespace bolt 663 } // end namespace llvm 664 665 void RewriteInstance::compare(RewriteInstance &RI2) { 666 outs() << "BOLT-DIFF: ======== Binary1 vs. Binary2 ========\n"; 667 outs() << "Trace for binary 1 has " << this->getTotalScore() 668 << " instructions executed.\n"; 669 outs() << "Trace for binary 2 has " << RI2.getTotalScore() 670 << " instructions executed.\n"; 671 if (opts::NormalizeByBin1) { 672 double Diff2to1 = 673 static_cast<double>(RI2.getTotalScore() - this->getTotalScore()) / 674 this->getTotalScore(); 675 outs() << "Binary2 change in score with respect to Binary1: "; 676 printColoredPercentage(Diff2to1 * 100.0); 677 outs() << "\n"; 678 } 679 680 if (!this->getTotalScore() || !RI2.getTotalScore()) { 681 outs() << "BOLT-DIFF: Both binaries must have recorded activity in known " 682 "functions.\n"; 683 return; 684 } 685 686 // Pre-pass ICF 687 if (opts::ICF) { 688 IdenticalCodeFolding ICF(opts::NeverPrint); 689 outs() << "BOLT-DIFF: Starting ICF pass for binary 1"; 690 ICF.runOnFunctions(*BC); 691 outs() << "BOLT-DIFF: Starting ICF pass for binary 2"; 692 ICF.runOnFunctions(*RI2.BC); 693 } 694 695 RewriteInstanceDiff RID(*this, RI2); 696 } 697