1 //=-- SampleProf.cpp - Sample profiling format support --------------------===// 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 contains common definitions used in the reading and writing of 10 // sample profile data. 11 // 12 //===----------------------------------------------------------------------===// 13 14 #include "llvm/ProfileData/SampleProf.h" 15 #include "llvm/Config/llvm-config.h" 16 #include "llvm/IR/DebugInfoMetadata.h" 17 #include "llvm/IR/PseudoProbe.h" 18 #include "llvm/ProfileData/SampleProfReader.h" 19 #include "llvm/Support/CommandLine.h" 20 #include "llvm/Support/Compiler.h" 21 #include "llvm/Support/Debug.h" 22 #include "llvm/Support/Error.h" 23 #include "llvm/Support/ErrorHandling.h" 24 #include "llvm/Support/LEB128.h" 25 #include "llvm/Support/ManagedStatic.h" 26 #include "llvm/Support/raw_ostream.h" 27 #include <string> 28 #include <system_error> 29 30 using namespace llvm; 31 using namespace sampleprof; 32 33 static cl::opt<uint64_t> ProfileSymbolListCutOff( 34 "profile-symbol-list-cutoff", cl::Hidden, cl::init(-1), cl::ZeroOrMore, 35 cl::desc("Cutoff value about how many symbols in profile symbol list " 36 "will be used. This is very useful for performance debugging")); 37 38 namespace llvm { 39 namespace sampleprof { 40 SampleProfileFormat FunctionSamples::Format; 41 bool FunctionSamples::ProfileIsProbeBased = false; 42 bool FunctionSamples::ProfileIsCS = false; 43 bool FunctionSamples::UseMD5 = false; 44 bool FunctionSamples::HasUniqSuffix = true; 45 bool FunctionSamples::ProfileIsFS = false; 46 } // namespace sampleprof 47 } // namespace llvm 48 49 namespace { 50 51 // FIXME: This class is only here to support the transition to llvm::Error. It 52 // will be removed once this transition is complete. Clients should prefer to 53 // deal with the Error value directly, rather than converting to error_code. 54 class SampleProfErrorCategoryType : public std::error_category { 55 const char *name() const noexcept override { return "llvm.sampleprof"; } 56 57 std::string message(int IE) const override { 58 sampleprof_error E = static_cast<sampleprof_error>(IE); 59 switch (E) { 60 case sampleprof_error::success: 61 return "Success"; 62 case sampleprof_error::bad_magic: 63 return "Invalid sample profile data (bad magic)"; 64 case sampleprof_error::unsupported_version: 65 return "Unsupported sample profile format version"; 66 case sampleprof_error::too_large: 67 return "Too much profile data"; 68 case sampleprof_error::truncated: 69 return "Truncated profile data"; 70 case sampleprof_error::malformed: 71 return "Malformed sample profile data"; 72 case sampleprof_error::unrecognized_format: 73 return "Unrecognized sample profile encoding format"; 74 case sampleprof_error::unsupported_writing_format: 75 return "Profile encoding format unsupported for writing operations"; 76 case sampleprof_error::truncated_name_table: 77 return "Truncated function name table"; 78 case sampleprof_error::not_implemented: 79 return "Unimplemented feature"; 80 case sampleprof_error::counter_overflow: 81 return "Counter overflow"; 82 case sampleprof_error::ostream_seek_unsupported: 83 return "Ostream does not support seek"; 84 case sampleprof_error::compress_failed: 85 return "Compress failure"; 86 case sampleprof_error::uncompress_failed: 87 return "Uncompress failure"; 88 case sampleprof_error::zlib_unavailable: 89 return "Zlib is unavailable"; 90 case sampleprof_error::hash_mismatch: 91 return "Function hash mismatch"; 92 } 93 llvm_unreachable("A value of sampleprof_error has no message."); 94 } 95 }; 96 97 } // end anonymous namespace 98 99 static ManagedStatic<SampleProfErrorCategoryType> ErrorCategory; 100 101 const std::error_category &llvm::sampleprof_category() { 102 return *ErrorCategory; 103 } 104 105 void LineLocation::print(raw_ostream &OS) const { 106 OS << LineOffset; 107 if (Discriminator > 0) 108 OS << "." << Discriminator; 109 } 110 111 raw_ostream &llvm::sampleprof::operator<<(raw_ostream &OS, 112 const LineLocation &Loc) { 113 Loc.print(OS); 114 return OS; 115 } 116 117 /// Merge the samples in \p Other into this record. 118 /// Optionally scale sample counts by \p Weight. 119 sampleprof_error SampleRecord::merge(const SampleRecord &Other, 120 uint64_t Weight) { 121 sampleprof_error Result; 122 // With pseudo probes, merge a dangling sample with a non-dangling sample 123 // should result in a dangling sample. 124 if (FunctionSamples::ProfileIsProbeBased && 125 (getSamples() == FunctionSamples::InvalidProbeCount || 126 Other.getSamples() == FunctionSamples::InvalidProbeCount)) { 127 NumSamples = FunctionSamples::InvalidProbeCount; 128 Result = sampleprof_error::success; 129 } else { 130 Result = addSamples(Other.getSamples(), Weight); 131 } 132 for (const auto &I : Other.getCallTargets()) { 133 MergeResult(Result, addCalledTarget(I.first(), I.second, Weight)); 134 } 135 return Result; 136 } 137 138 #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) 139 LLVM_DUMP_METHOD void LineLocation::dump() const { print(dbgs()); } 140 #endif 141 142 /// Print the sample record to the stream \p OS indented by \p Indent. 143 void SampleRecord::print(raw_ostream &OS, unsigned Indent) const { 144 OS << NumSamples; 145 if (hasCalls()) { 146 OS << ", calls:"; 147 for (const auto &I : getSortedCallTargets()) 148 OS << " " << I.first << ":" << I.second; 149 } 150 OS << "\n"; 151 } 152 153 #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) 154 LLVM_DUMP_METHOD void SampleRecord::dump() const { print(dbgs(), 0); } 155 #endif 156 157 raw_ostream &llvm::sampleprof::operator<<(raw_ostream &OS, 158 const SampleRecord &Sample) { 159 Sample.print(OS, 0); 160 return OS; 161 } 162 163 /// Print the samples collected for a function on stream \p OS. 164 void FunctionSamples::print(raw_ostream &OS, unsigned Indent) const { 165 if (getFunctionHash()) 166 OS << "CFG checksum " << getFunctionHash() << "\n"; 167 168 OS << TotalSamples << ", " << TotalHeadSamples << ", " << BodySamples.size() 169 << " sampled lines\n"; 170 171 OS.indent(Indent); 172 if (!BodySamples.empty()) { 173 OS << "Samples collected in the function's body {\n"; 174 SampleSorter<LineLocation, SampleRecord> SortedBodySamples(BodySamples); 175 for (const auto &SI : SortedBodySamples.get()) { 176 OS.indent(Indent + 2); 177 OS << SI->first << ": " << SI->second; 178 } 179 OS.indent(Indent); 180 OS << "}\n"; 181 } else { 182 OS << "No samples collected in the function's body\n"; 183 } 184 185 OS.indent(Indent); 186 if (!CallsiteSamples.empty()) { 187 OS << "Samples collected in inlined callsites {\n"; 188 SampleSorter<LineLocation, FunctionSamplesMap> SortedCallsiteSamples( 189 CallsiteSamples); 190 for (const auto &CS : SortedCallsiteSamples.get()) { 191 for (const auto &FS : CS->second) { 192 OS.indent(Indent + 2); 193 OS << CS->first << ": inlined callee: " << FS.second.getName() << ": "; 194 FS.second.print(OS, Indent + 4); 195 } 196 } 197 OS.indent(Indent); 198 OS << "}\n"; 199 } else { 200 OS << "No inlined callsites in this function\n"; 201 } 202 } 203 204 raw_ostream &llvm::sampleprof::operator<<(raw_ostream &OS, 205 const FunctionSamples &FS) { 206 FS.print(OS); 207 return OS; 208 } 209 210 unsigned FunctionSamples::getOffset(const DILocation *DIL) { 211 return (DIL->getLine() - DIL->getScope()->getSubprogram()->getLine()) & 212 0xffff; 213 } 214 215 LineLocation FunctionSamples::getCallSiteIdentifier(const DILocation *DIL) { 216 if (FunctionSamples::ProfileIsProbeBased) 217 // In a pseudo-probe based profile, a callsite is simply represented by the 218 // ID of the probe associated with the call instruction. The probe ID is 219 // encoded in the Discriminator field of the call instruction's debug 220 // metadata. 221 return LineLocation(PseudoProbeDwarfDiscriminator::extractProbeIndex( 222 DIL->getDiscriminator()), 223 0); 224 else 225 return LineLocation(FunctionSamples::getOffset(DIL), 226 DIL->getBaseDiscriminator()); 227 } 228 229 const FunctionSamples *FunctionSamples::findFunctionSamples( 230 const DILocation *DIL, SampleProfileReaderItaniumRemapper *Remapper) const { 231 assert(DIL); 232 SmallVector<std::pair<LineLocation, StringRef>, 10> S; 233 234 const DILocation *PrevDIL = DIL; 235 for (DIL = DIL->getInlinedAt(); DIL; DIL = DIL->getInlinedAt()) { 236 unsigned Discriminator; 237 if (ProfileIsFS) 238 Discriminator = DIL->getDiscriminator(); 239 else 240 Discriminator = DIL->getBaseDiscriminator(); 241 242 S.push_back( 243 std::make_pair(LineLocation(getOffset(DIL), Discriminator), 244 PrevDIL->getScope()->getSubprogram()->getLinkageName())); 245 PrevDIL = DIL; 246 } 247 if (S.size() == 0) 248 return this; 249 const FunctionSamples *FS = this; 250 for (int i = S.size() - 1; i >= 0 && FS != nullptr; i--) { 251 FS = FS->findFunctionSamplesAt(S[i].first, S[i].second, Remapper); 252 } 253 return FS; 254 } 255 256 void FunctionSamples::findAllNames(DenseSet<StringRef> &NameSet) const { 257 NameSet.insert(Name); 258 for (const auto &BS : BodySamples) 259 for (const auto &TS : BS.second.getCallTargets()) 260 NameSet.insert(TS.getKey()); 261 262 for (const auto &CS : CallsiteSamples) { 263 for (const auto &NameFS : CS.second) { 264 NameSet.insert(NameFS.first); 265 NameFS.second.findAllNames(NameSet); 266 } 267 } 268 } 269 270 const FunctionSamples *FunctionSamples::findFunctionSamplesAt( 271 const LineLocation &Loc, StringRef CalleeName, 272 SampleProfileReaderItaniumRemapper *Remapper) const { 273 CalleeName = getCanonicalFnName(CalleeName); 274 275 std::string CalleeGUID; 276 CalleeName = getRepInFormat(CalleeName, UseMD5, CalleeGUID); 277 278 auto iter = CallsiteSamples.find(Loc); 279 if (iter == CallsiteSamples.end()) 280 return nullptr; 281 auto FS = iter->second.find(CalleeName); 282 if (FS != iter->second.end()) 283 return &FS->second; 284 if (Remapper) { 285 if (auto NameInProfile = Remapper->lookUpNameInProfile(CalleeName)) { 286 auto FS = iter->second.find(*NameInProfile); 287 if (FS != iter->second.end()) 288 return &FS->second; 289 } 290 } 291 // If we cannot find exact match of the callee name, return the FS with 292 // the max total count. Only do this when CalleeName is not provided, 293 // i.e., only for indirect calls. 294 if (!CalleeName.empty()) 295 return nullptr; 296 uint64_t MaxTotalSamples = 0; 297 const FunctionSamples *R = nullptr; 298 for (const auto &NameFS : iter->second) 299 if (NameFS.second.getTotalSamples() >= MaxTotalSamples) { 300 MaxTotalSamples = NameFS.second.getTotalSamples(); 301 R = &NameFS.second; 302 } 303 return R; 304 } 305 306 #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) 307 LLVM_DUMP_METHOD void FunctionSamples::dump() const { print(dbgs(), 0); } 308 #endif 309 310 std::error_code ProfileSymbolList::read(const uint8_t *Data, 311 uint64_t ListSize) { 312 const char *ListStart = reinterpret_cast<const char *>(Data); 313 uint64_t Size = 0; 314 uint64_t StrNum = 0; 315 while (Size < ListSize && StrNum < ProfileSymbolListCutOff) { 316 StringRef Str(ListStart + Size); 317 add(Str); 318 Size += Str.size() + 1; 319 StrNum++; 320 } 321 if (Size != ListSize && StrNum != ProfileSymbolListCutOff) 322 return sampleprof_error::malformed; 323 return sampleprof_error::success; 324 } 325 326 void SampleContextTrimmer::trimAndMergeColdContextProfiles( 327 uint64_t ColdCountThreshold, bool TrimColdContext, bool MergeColdContext) { 328 if (!TrimColdContext && !MergeColdContext) 329 return; 330 331 // Nothing to merge if sample threshold is zero 332 if (ColdCountThreshold == 0) 333 return; 334 335 // Filter the cold profiles from ProfileMap and move them into a tmp 336 // container 337 std::vector<std::pair<StringRef, const FunctionSamples *>> ColdProfiles; 338 for (const auto &I : ProfileMap) { 339 const FunctionSamples &FunctionProfile = I.second; 340 if (FunctionProfile.getTotalSamples() >= ColdCountThreshold) 341 continue; 342 ColdProfiles.emplace_back(I.getKey(), &I.second); 343 } 344 345 // Remove the cold profile from ProfileMap and merge them into BaseProileMap 346 StringMap<FunctionSamples> BaseProfileMap; 347 for (const auto &I : ColdProfiles) { 348 if (MergeColdContext) { 349 auto Ret = BaseProfileMap.try_emplace( 350 I.second->getContext().getNameWithoutContext(), FunctionSamples()); 351 FunctionSamples &BaseProfile = Ret.first->second; 352 BaseProfile.merge(*I.second); 353 } 354 ProfileMap.erase(I.first); 355 } 356 357 // Merge the base profiles into ProfileMap; 358 for (const auto &I : BaseProfileMap) { 359 // Filter the cold base profile 360 if (TrimColdContext && I.second.getTotalSamples() < ColdCountThreshold && 361 ProfileMap.find(I.getKey()) == ProfileMap.end()) 362 continue; 363 // Merge the profile if the original profile exists, otherwise just insert 364 // as a new profile 365 auto Ret = ProfileMap.try_emplace(I.getKey(), FunctionSamples()); 366 if (Ret.second) { 367 SampleContext FContext(Ret.first->first(), RawContext); 368 FunctionSamples &FProfile = Ret.first->second; 369 FProfile.setContext(FContext); 370 FProfile.setName(FContext.getNameWithoutContext()); 371 } 372 FunctionSamples &OrigProfile = Ret.first->second; 373 OrigProfile.merge(I.second); 374 } 375 } 376 377 void SampleContextTrimmer::canonicalizeContextProfiles() { 378 StringSet<> ProfilesToBeRemoved; 379 // Note that StringMap order is guaranteed to be top-down order, 380 // this makes sure we make room for promoted/merged context in the 381 // map, before we move profiles in the map. 382 for (auto &I : ProfileMap) { 383 FunctionSamples &FProfile = I.second; 384 StringRef ContextStr = FProfile.getNameWithContext(); 385 if (I.first() == ContextStr) 386 continue; 387 388 // Use the context string from FunctionSamples to update the keys of 389 // ProfileMap. They can get out of sync after context profile promotion 390 // through pre-inliner. 391 auto Ret = ProfileMap.try_emplace(ContextStr, FProfile); 392 assert(Ret.second && "Conext conflict during canonicalization"); 393 FProfile = Ret.first->second; 394 395 // Track the context profile to remove 396 ProfilesToBeRemoved.erase(ContextStr); 397 ProfilesToBeRemoved.insert(I.first()); 398 } 399 400 for (auto &I : ProfilesToBeRemoved) { 401 ProfileMap.erase(I.first()); 402 } 403 } 404 405 std::error_code ProfileSymbolList::write(raw_ostream &OS) { 406 // Sort the symbols before output. If doing compression. 407 // It will make the compression much more effective. 408 std::vector<StringRef> SortedList(Syms.begin(), Syms.end()); 409 llvm::sort(SortedList); 410 411 std::string OutputString; 412 for (auto &Sym : SortedList) { 413 OutputString.append(Sym.str()); 414 OutputString.append(1, '\0'); 415 } 416 417 OS << OutputString; 418 return sampleprof_error::success; 419 } 420 421 void ProfileSymbolList::dump(raw_ostream &OS) const { 422 OS << "======== Dump profile symbol list ========\n"; 423 std::vector<StringRef> SortedList(Syms.begin(), Syms.end()); 424 llvm::sort(SortedList); 425 426 for (auto &Sym : SortedList) 427 OS << Sym << "\n"; 428 } 429