1 //===- unittest/ProfileData/SampleProfTest.cpp ------------------*- C++ -*-===// 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 #include "llvm/ProfileData/SampleProf.h" 10 #include "llvm/ADT/StringMap.h" 11 #include "llvm/ADT/StringRef.h" 12 #include "llvm/IR/DebugInfoMetadata.h" 13 #include "llvm/IR/LLVMContext.h" 14 #include "llvm/IR/Metadata.h" 15 #include "llvm/IR/Module.h" 16 #include "llvm/ProfileData/SampleProfReader.h" 17 #include "llvm/ProfileData/SampleProfWriter.h" 18 #include "llvm/Support/Casting.h" 19 #include "llvm/Support/ErrorOr.h" 20 #include "llvm/Support/FileSystem.h" 21 #include "llvm/Support/MemoryBuffer.h" 22 #include "llvm/Support/VirtualFileSystem.h" 23 #include "llvm/Support/raw_ostream.h" 24 #include "llvm/Testing/Support/SupportHelpers.h" 25 #include "gtest/gtest.h" 26 #include <string> 27 #include <vector> 28 29 using namespace llvm; 30 using namespace sampleprof; 31 32 using llvm::unittest::TempFile; 33 34 static ::testing::AssertionResult NoError(std::error_code EC) { 35 if (!EC) 36 return ::testing::AssertionSuccess(); 37 return ::testing::AssertionFailure() << "error " << EC.value() << ": " 38 << EC.message(); 39 } 40 41 namespace { 42 43 struct SampleProfTest : ::testing::Test { 44 LLVMContext Context; 45 std::unique_ptr<SampleProfileWriter> Writer; 46 std::unique_ptr<SampleProfileReader> Reader; 47 48 SampleProfTest() : Writer(), Reader() {} 49 50 void createWriter(SampleProfileFormat Format, StringRef Profile) { 51 std::error_code EC; 52 std::unique_ptr<raw_ostream> OS( 53 new raw_fd_ostream(Profile, EC, sys::fs::OF_None)); 54 auto WriterOrErr = SampleProfileWriter::create(OS, Format); 55 ASSERT_TRUE(NoError(WriterOrErr.getError())); 56 Writer = std::move(WriterOrErr.get()); 57 } 58 59 void readProfile(const Module &M, StringRef Profile, 60 StringRef RemapFile = "") { 61 auto FS = vfs::getRealFileSystem(); 62 auto ReaderOrErr = SampleProfileReader::create( 63 std::string(Profile), Context, *FS, FSDiscriminatorPass::Base, 64 std::string(RemapFile)); 65 ASSERT_TRUE(NoError(ReaderOrErr.getError())); 66 Reader = std::move(ReaderOrErr.get()); 67 Reader->setModule(&M); 68 } 69 70 TempFile createRemapFile() { 71 return TempFile("remapfile", "", R"( 72 # Types 'int' and 'long' are equivalent 73 type i l 74 # Function names 'foo' and 'faux' are equivalent 75 name 3foo 4faux 76 )", 77 /*Unique*/ true); 78 } 79 80 // Verify profile summary is consistent in the roundtrip to and from 81 // Metadata. \p AddPartialField is to choose whether the Metadata 82 // contains the IsPartialProfile field which is optional. 83 void verifyProfileSummary(ProfileSummary &Summary, Module &M, 84 const bool AddPartialField, 85 const bool AddPartialProfileRatioField) { 86 LLVMContext &Context = M.getContext(); 87 const bool IsPartialProfile = Summary.isPartialProfile(); 88 const double PartialProfileRatio = Summary.getPartialProfileRatio(); 89 auto VerifySummary = [IsPartialProfile, PartialProfileRatio]( 90 ProfileSummary &Summary) mutable { 91 ASSERT_EQ(ProfileSummary::PSK_Sample, Summary.getKind()); 92 ASSERT_EQ(138211u, Summary.getTotalCount()); 93 ASSERT_EQ(10u, Summary.getNumCounts()); 94 ASSERT_EQ(4u, Summary.getNumFunctions()); 95 ASSERT_EQ(1437u, Summary.getMaxFunctionCount()); 96 ASSERT_EQ(60351u, Summary.getMaxCount()); 97 ASSERT_EQ(IsPartialProfile, Summary.isPartialProfile()); 98 ASSERT_EQ(PartialProfileRatio, Summary.getPartialProfileRatio()); 99 100 uint32_t Cutoff = 800000; 101 auto Predicate = [&Cutoff](const ProfileSummaryEntry &PE) { 102 return PE.Cutoff == Cutoff; 103 }; 104 const std::vector<ProfileSummaryEntry> &Details = 105 Summary.getDetailedSummary(); 106 auto EightyPerc = find_if(Details, Predicate); 107 Cutoff = 900000; 108 auto NinetyPerc = find_if(Details, Predicate); 109 Cutoff = 950000; 110 auto NinetyFivePerc = find_if(Details, Predicate); 111 Cutoff = 990000; 112 auto NinetyNinePerc = find_if(Details, Predicate); 113 ASSERT_EQ(60000u, EightyPerc->MinCount); 114 ASSERT_EQ(12557u, NinetyPerc->MinCount); 115 ASSERT_EQ(12557u, NinetyFivePerc->MinCount); 116 ASSERT_EQ(600u, NinetyNinePerc->MinCount); 117 }; 118 VerifySummary(Summary); 119 120 // Test that conversion of summary to and from Metadata works. 121 Metadata *MD = 122 Summary.getMD(Context, AddPartialField, AddPartialProfileRatioField); 123 ASSERT_TRUE(MD); 124 ProfileSummary *PS = ProfileSummary::getFromMD(MD); 125 ASSERT_TRUE(PS); 126 VerifySummary(*PS); 127 delete PS; 128 129 // Test that summary can be attached to and read back from module. 130 M.eraseNamedMetadata(M.getOrInsertModuleFlagsMetadata()); 131 M.setProfileSummary(MD, ProfileSummary::PSK_Sample); 132 MD = M.getProfileSummary(/* IsCS */ false); 133 ASSERT_TRUE(MD); 134 PS = ProfileSummary::getFromMD(MD); 135 ASSERT_TRUE(PS); 136 VerifySummary(*PS); 137 delete PS; 138 } 139 140 void testRoundTrip(SampleProfileFormat Format, bool Remap, bool UseMD5) { 141 TempFile ProfileFile("profile", "", "", /*Unique*/ true); 142 createWriter(Format, ProfileFile.path()); 143 if (Format == SampleProfileFormat::SPF_Ext_Binary && UseMD5) 144 static_cast<SampleProfileWriterExtBinary *>(Writer.get())->setUseMD5(); 145 146 StringRef FooName("_Z3fooi"); 147 FunctionSamples FooSamples; 148 FooSamples.setName(FooName); 149 FooSamples.addTotalSamples(7711); 150 FooSamples.addHeadSamples(610); 151 FooSamples.addBodySamples(1, 0, 610); 152 FooSamples.addBodySamples(2, 0, 600); 153 FooSamples.addBodySamples(4, 0, 60000); 154 FooSamples.addBodySamples(8, 0, 60351); 155 FooSamples.addBodySamples(10, 0, 605); 156 157 // Add inline instance with name "_Z3gooi". 158 StringRef GooName("_Z3gooi"); 159 auto &GooSamples = 160 FooSamples.functionSamplesAt(LineLocation(7, 0))[GooName.str()]; 161 GooSamples.setName(GooName); 162 GooSamples.addTotalSamples(502); 163 GooSamples.addBodySamples(3, 0, 502); 164 165 // Add inline instance with name "_Z3hooi". 166 StringRef HooName("_Z3hooi"); 167 auto &HooSamples = 168 GooSamples.functionSamplesAt(LineLocation(9, 0))[HooName.str()]; 169 HooSamples.setName(HooName); 170 HooSamples.addTotalSamples(317); 171 HooSamples.addBodySamples(4, 0, 317); 172 173 StringRef BarName("_Z3bari"); 174 FunctionSamples BarSamples; 175 BarSamples.setName(BarName); 176 BarSamples.addTotalSamples(20301); 177 BarSamples.addHeadSamples(1437); 178 BarSamples.addBodySamples(1, 0, 1437); 179 // Test how reader/writer handles unmangled names. 180 StringRef MconstructName("_M_construct<char *>"); 181 StringRef StringviewName("string_view<std::allocator<char> >"); 182 BarSamples.addCalledTargetSamples(1, 0, MconstructName, 1000); 183 BarSamples.addCalledTargetSamples(1, 0, StringviewName, 437); 184 185 StringRef BazName("_Z3bazi"); 186 FunctionSamples BazSamples; 187 BazSamples.setName(BazName); 188 BazSamples.addTotalSamples(12557); 189 BazSamples.addHeadSamples(1257); 190 BazSamples.addBodySamples(1, 0, 12557); 191 192 StringRef BooName("_Z3booi"); 193 FunctionSamples BooSamples; 194 BooSamples.setName(BooName); 195 BooSamples.addTotalSamples(1232); 196 BooSamples.addHeadSamples(1); 197 BooSamples.addBodySamples(1, 0, 1232); 198 199 SampleProfileMap Profiles; 200 Profiles[FooName] = std::move(FooSamples); 201 Profiles[BarName] = std::move(BarSamples); 202 Profiles[BazName] = std::move(BazSamples); 203 Profiles[BooName] = std::move(BooSamples); 204 205 Module M("my_module", Context); 206 FunctionType *fn_type = 207 FunctionType::get(Type::getVoidTy(Context), {}, false); 208 209 TempFile RemapFile(createRemapFile()); 210 if (Remap) { 211 FooName = "_Z4fauxi"; 212 BarName = "_Z3barl"; 213 GooName = "_Z3gool"; 214 HooName = "_Z3hool"; 215 } 216 217 M.getOrInsertFunction(FooName, fn_type); 218 M.getOrInsertFunction(BarName, fn_type); 219 M.getOrInsertFunction(BooName, fn_type); 220 221 ProfileSymbolList List; 222 if (Format == SampleProfileFormat::SPF_Ext_Binary) { 223 List.add("zoo", true); 224 List.add("moo", true); 225 } 226 Writer->setProfileSymbolList(&List); 227 228 std::error_code EC; 229 EC = Writer->write(Profiles); 230 ASSERT_TRUE(NoError(EC)); 231 232 Writer->getOutputStream().flush(); 233 234 readProfile(M, ProfileFile.path(), RemapFile.path()); 235 EC = Reader->read(); 236 ASSERT_TRUE(NoError(EC)); 237 238 if (Format == SampleProfileFormat::SPF_Ext_Binary) { 239 std::unique_ptr<ProfileSymbolList> ReaderList = 240 Reader->getProfileSymbolList(); 241 ReaderList->contains("zoo"); 242 ReaderList->contains("moo"); 243 } 244 245 FunctionSamples *ReadFooSamples = Reader->getSamplesFor(FooName); 246 ASSERT_TRUE(ReadFooSamples != nullptr); 247 if (!UseMD5) { 248 ASSERT_EQ("_Z3fooi", ReadFooSamples->getName()); 249 } 250 ASSERT_EQ(7711u, ReadFooSamples->getTotalSamples()); 251 ASSERT_EQ(610u, ReadFooSamples->getHeadSamples()); 252 253 // Try to find a FunctionSamples with GooName at given callsites containing 254 // inline instance for GooName. Test the correct FunctionSamples can be 255 // found with Remapper support. 256 const FunctionSamples *ReadGooSamples = 257 ReadFooSamples->findFunctionSamplesAt(LineLocation(7, 0), GooName, 258 Reader->getRemapper()); 259 ASSERT_TRUE(ReadGooSamples != nullptr); 260 ASSERT_EQ(502u, ReadGooSamples->getTotalSamples()); 261 262 // Try to find a FunctionSamples with GooName at given callsites containing 263 // no inline instance for GooName. Test no FunctionSamples will be 264 // found with Remapper support. 265 const FunctionSamples *ReadGooSamplesAgain = 266 ReadFooSamples->findFunctionSamplesAt(LineLocation(9, 0), GooName, 267 Reader->getRemapper()); 268 ASSERT_TRUE(ReadGooSamplesAgain == nullptr); 269 270 // The inline instance of Hoo is inside of the inline instance of Goo. 271 // Try to find a FunctionSamples with HooName at given callsites containing 272 // inline instance for HooName. Test the correct FunctionSamples can be 273 // found with Remapper support. 274 const FunctionSamples *ReadHooSamples = 275 ReadGooSamples->findFunctionSamplesAt(LineLocation(9, 0), HooName, 276 Reader->getRemapper()); 277 ASSERT_TRUE(ReadHooSamples != nullptr); 278 ASSERT_EQ(317u, ReadHooSamples->getTotalSamples()); 279 280 FunctionSamples *ReadBarSamples = Reader->getSamplesFor(BarName); 281 ASSERT_TRUE(ReadBarSamples != nullptr); 282 if (!UseMD5) { 283 ASSERT_EQ("_Z3bari", ReadBarSamples->getName()); 284 } 285 ASSERT_EQ(20301u, ReadBarSamples->getTotalSamples()); 286 ASSERT_EQ(1437u, ReadBarSamples->getHeadSamples()); 287 ErrorOr<SampleRecord::CallTargetMap> CTMap = 288 ReadBarSamples->findCallTargetMapAt(1, 0); 289 ASSERT_FALSE(CTMap.getError()); 290 291 // Because _Z3bazi is not defined in module M, expect _Z3bazi's profile 292 // is not loaded when the profile is ExtBinary format because this format 293 // supports loading function profiles on demand. 294 FunctionSamples *ReadBazSamples = Reader->getSamplesFor(BazName); 295 if (Format == SampleProfileFormat::SPF_Ext_Binary) { 296 ASSERT_TRUE(ReadBazSamples == nullptr); 297 ASSERT_EQ(3u, Reader->getProfiles().size()); 298 } else { 299 ASSERT_TRUE(ReadBazSamples != nullptr); 300 ASSERT_EQ(12557u, ReadBazSamples->getTotalSamples()); 301 ASSERT_EQ(4u, Reader->getProfiles().size()); 302 } 303 304 FunctionSamples *ReadBooSamples = Reader->getSamplesFor(BooName); 305 ASSERT_TRUE(ReadBooSamples != nullptr); 306 ASSERT_EQ(1232u, ReadBooSamples->getTotalSamples()); 307 308 std::string MconstructGUID; 309 StringRef MconstructRep = 310 getRepInFormat(MconstructName, UseMD5, MconstructGUID); 311 std::string StringviewGUID; 312 StringRef StringviewRep = 313 getRepInFormat(StringviewName, UseMD5, StringviewGUID); 314 ASSERT_EQ(1000u, CTMap.get()[MconstructRep]); 315 ASSERT_EQ(437u, CTMap.get()[StringviewRep]); 316 317 318 ProfileSummary &Summary = Reader->getSummary(); 319 Summary.setPartialProfile(true); 320 verifyProfileSummary(Summary, M, true, false); 321 322 Summary.setPartialProfile(false); 323 verifyProfileSummary(Summary, M, true, false); 324 325 verifyProfileSummary(Summary, M, false, false); 326 327 Summary.setPartialProfile(true); 328 Summary.setPartialProfileRatio(0.5); 329 verifyProfileSummary(Summary, M, true, true); 330 } 331 332 void addFunctionSamples(SampleProfileMap *Smap, const char *Fname, 333 uint64_t TotalSamples, uint64_t HeadSamples) { 334 StringRef Name(Fname); 335 FunctionSamples FcnSamples; 336 FcnSamples.setName(Name); 337 FcnSamples.addTotalSamples(TotalSamples); 338 FcnSamples.addHeadSamples(HeadSamples); 339 FcnSamples.addBodySamples(1, 0, HeadSamples); 340 (*Smap)[Name] = FcnSamples; 341 } 342 343 SampleProfileMap setupFcnSamplesForElisionTest(StringRef Policy) { 344 SampleProfileMap Smap; 345 addFunctionSamples(&Smap, "foo", uint64_t(20301), uint64_t(1437)); 346 if (Policy == "" || Policy == "all") 347 return Smap; 348 addFunctionSamples(&Smap, "foo.bar", uint64_t(20303), uint64_t(1439)); 349 if (Policy == "selected") 350 return Smap; 351 addFunctionSamples(&Smap, "foo.llvm.2465", uint64_t(20305), uint64_t(1441)); 352 return Smap; 353 } 354 355 void createFunctionWithSampleProfileElisionPolicy(Module *M, 356 const char *Fname, 357 StringRef Policy) { 358 FunctionType *FnType = 359 FunctionType::get(Type::getVoidTy(Context), {}, false); 360 auto Inserted = M->getOrInsertFunction(Fname, FnType); 361 auto Fcn = cast<Function>(Inserted.getCallee()); 362 if (Policy != "") 363 Fcn->addFnAttr("sample-profile-suffix-elision-policy", Policy); 364 } 365 366 void setupModuleForElisionTest(Module *M, StringRef Policy) { 367 createFunctionWithSampleProfileElisionPolicy(M, "foo", Policy); 368 createFunctionWithSampleProfileElisionPolicy(M, "foo.bar", Policy); 369 createFunctionWithSampleProfileElisionPolicy(M, "foo.llvm.2465", Policy); 370 } 371 372 void testSuffixElisionPolicy(SampleProfileFormat Format, StringRef Policy, 373 const StringMap<uint64_t> &Expected) { 374 TempFile ProfileFile("profile", "", "", /*Unique*/ true); 375 376 Module M("my_module", Context); 377 setupModuleForElisionTest(&M, Policy); 378 SampleProfileMap ProfMap = setupFcnSamplesForElisionTest(Policy); 379 380 // write profile 381 createWriter(Format, ProfileFile.path()); 382 std::error_code EC; 383 EC = Writer->write(ProfMap); 384 ASSERT_TRUE(NoError(EC)); 385 Writer->getOutputStream().flush(); 386 387 // read profile 388 readProfile(M, ProfileFile.path()); 389 EC = Reader->read(); 390 ASSERT_TRUE(NoError(EC)); 391 392 for (auto I = Expected.begin(); I != Expected.end(); ++I) { 393 uint64_t Esamples = uint64_t(-1); 394 FunctionSamples *Samples = Reader->getSamplesFor(I->getKey()); 395 if (Samples != nullptr) 396 Esamples = Samples->getTotalSamples(); 397 ASSERT_EQ(I->getValue(), Esamples); 398 } 399 } 400 }; 401 402 TEST_F(SampleProfTest, roundtrip_text_profile) { 403 testRoundTrip(SampleProfileFormat::SPF_Text, false, false); 404 } 405 406 TEST_F(SampleProfTest, roundtrip_raw_binary_profile) { 407 testRoundTrip(SampleProfileFormat::SPF_Binary, false, false); 408 } 409 410 TEST_F(SampleProfTest, roundtrip_ext_binary_profile) { 411 testRoundTrip(SampleProfileFormat::SPF_Ext_Binary, false, false); 412 } 413 414 TEST_F(SampleProfTest, roundtrip_md5_ext_binary_profile) { 415 testRoundTrip(SampleProfileFormat::SPF_Ext_Binary, false, true); 416 } 417 418 TEST_F(SampleProfTest, remap_text_profile) { 419 testRoundTrip(SampleProfileFormat::SPF_Text, true, false); 420 } 421 422 TEST_F(SampleProfTest, remap_raw_binary_profile) { 423 testRoundTrip(SampleProfileFormat::SPF_Binary, true, false); 424 } 425 426 TEST_F(SampleProfTest, remap_ext_binary_profile) { 427 testRoundTrip(SampleProfileFormat::SPF_Ext_Binary, true, false); 428 } 429 430 TEST_F(SampleProfTest, sample_overflow_saturation) { 431 const uint64_t Max = std::numeric_limits<uint64_t>::max(); 432 sampleprof_error Result; 433 434 FunctionSamples FooSamples; 435 Result = FooSamples.addTotalSamples(1); 436 ASSERT_EQ(Result, sampleprof_error::success); 437 438 Result = FooSamples.addHeadSamples(1); 439 ASSERT_EQ(Result, sampleprof_error::success); 440 441 Result = FooSamples.addBodySamples(10, 0, 1); 442 ASSERT_EQ(Result, sampleprof_error::success); 443 444 Result = FooSamples.addTotalSamples(Max); 445 ASSERT_EQ(Result, sampleprof_error::counter_overflow); 446 ASSERT_EQ(FooSamples.getTotalSamples(), Max); 447 448 Result = FooSamples.addHeadSamples(Max); 449 ASSERT_EQ(Result, sampleprof_error::counter_overflow); 450 ASSERT_EQ(FooSamples.getHeadSamples(), Max); 451 452 Result = FooSamples.addBodySamples(10, 0, Max); 453 ASSERT_EQ(Result, sampleprof_error::counter_overflow); 454 ErrorOr<uint64_t> BodySamples = FooSamples.findSamplesAt(10, 0); 455 ASSERT_FALSE(BodySamples.getError()); 456 ASSERT_EQ(BodySamples.get(), Max); 457 } 458 459 TEST_F(SampleProfTest, default_suffix_elision_text) { 460 // Default suffix elision policy: strip everything after first dot. 461 // This implies that all suffix variants will map to "foo", so 462 // we don't expect to see any entries for them in the sample 463 // profile. 464 StringMap<uint64_t> Expected; 465 Expected["foo"] = uint64_t(20301); 466 Expected["foo.bar"] = uint64_t(-1); 467 Expected["foo.llvm.2465"] = uint64_t(-1); 468 testSuffixElisionPolicy(SampleProfileFormat::SPF_Text, "", Expected); 469 } 470 471 TEST_F(SampleProfTest, selected_suffix_elision_text) { 472 // Profile is created and searched using the "selected" 473 // suffix elision policy: we only strip a .XXX suffix if 474 // it matches a pattern known to be generated by the compiler 475 // (e.g. ".llvm.<digits>"). 476 StringMap<uint64_t> Expected; 477 Expected["foo"] = uint64_t(20301); 478 Expected["foo.bar"] = uint64_t(20303); 479 Expected["foo.llvm.2465"] = uint64_t(-1); 480 testSuffixElisionPolicy(SampleProfileFormat::SPF_Text, "selected", Expected); 481 } 482 483 TEST_F(SampleProfTest, none_suffix_elision_text) { 484 // Profile is created and searched using the "none" 485 // suffix elision policy: no stripping of suffixes at all. 486 // Here we expect to see all variants in the profile. 487 StringMap<uint64_t> Expected; 488 Expected["foo"] = uint64_t(20301); 489 Expected["foo.bar"] = uint64_t(20303); 490 Expected["foo.llvm.2465"] = uint64_t(20305); 491 testSuffixElisionPolicy(SampleProfileFormat::SPF_Text, "none", Expected); 492 } 493 494 } // end anonymous namespace 495