1 //===- unittest/ProfileData/SampleProfTest.cpp ------------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
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/raw_ostream.h"
23 #include "llvm/Testing/Support/SupportHelpers.h"
24 #include "gtest/gtest.h"
29 using namespace sampleprof
;
31 using llvm::unittest::TempFile
;
33 static ::testing::AssertionResult
NoError(std::error_code EC
) {
35 return ::testing::AssertionSuccess();
36 return ::testing::AssertionFailure() << "error " << EC
.value() << ": "
42 struct SampleProfTest
: ::testing::Test
{
44 std::unique_ptr
<SampleProfileWriter
> Writer
;
45 std::unique_ptr
<SampleProfileReader
> Reader
;
47 SampleProfTest() : Writer(), Reader() {}
49 void createWriter(SampleProfileFormat Format
, StringRef Profile
) {
51 std::unique_ptr
<raw_ostream
> OS(
52 new raw_fd_ostream(Profile
, EC
, sys::fs::OF_None
));
53 auto WriterOrErr
= SampleProfileWriter::create(OS
, Format
);
54 ASSERT_TRUE(NoError(WriterOrErr
.getError()));
55 Writer
= std::move(WriterOrErr
.get());
58 void readProfile(const Module
&M
, StringRef Profile
,
59 StringRef RemapFile
= "") {
60 auto ReaderOrErr
= SampleProfileReader::create(
61 std::string(Profile
), Context
, FSDiscriminatorPass::Base
,
62 std::string(RemapFile
));
63 ASSERT_TRUE(NoError(ReaderOrErr
.getError()));
64 Reader
= std::move(ReaderOrErr
.get());
65 Reader
->setModule(&M
);
68 TempFile
createRemapFile() {
69 return TempFile("remapfile", "", R
"(
70 # Types 'int' and 'long' are equivalent
72 # Function names 'foo' and 'faux' are equivalent
78 // Verify profile summary is consistent in the roundtrip to and from
79 // Metadata. \p AddPartialField is to choose whether the Metadata
80 // contains the IsPartialProfile field which is optional.
81 void verifyProfileSummary(ProfileSummary
&Summary
, Module
&M
,
82 const bool AddPartialField
,
83 const bool AddPartialProfileRatioField
) {
84 LLVMContext
&Context
= M
.getContext();
85 const bool IsPartialProfile
= Summary
.isPartialProfile();
86 const double PartialProfileRatio
= Summary
.getPartialProfileRatio();
87 auto VerifySummary
= [IsPartialProfile
, PartialProfileRatio
](
88 ProfileSummary
&Summary
) mutable {
89 ASSERT_EQ(ProfileSummary::PSK_Sample
, Summary
.getKind());
90 ASSERT_EQ(138211u, Summary
.getTotalCount());
91 ASSERT_EQ(10u, Summary
.getNumCounts());
92 ASSERT_EQ(4u, Summary
.getNumFunctions());
93 ASSERT_EQ(1437u, Summary
.getMaxFunctionCount());
94 ASSERT_EQ(60351u, Summary
.getMaxCount());
95 ASSERT_EQ(IsPartialProfile
, Summary
.isPartialProfile());
96 ASSERT_EQ(PartialProfileRatio
, Summary
.getPartialProfileRatio());
98 uint32_t Cutoff
= 800000;
99 auto Predicate
= [&Cutoff
](const ProfileSummaryEntry
&PE
) {
100 return PE
.Cutoff
== Cutoff
;
102 const std::vector
<ProfileSummaryEntry
> &Details
=
103 Summary
.getDetailedSummary();
104 auto EightyPerc
= find_if(Details
, Predicate
);
106 auto NinetyPerc
= find_if(Details
, Predicate
);
108 auto NinetyFivePerc
= find_if(Details
, Predicate
);
110 auto NinetyNinePerc
= find_if(Details
, Predicate
);
111 ASSERT_EQ(60000u, EightyPerc
->MinCount
);
112 ASSERT_EQ(12557u, NinetyPerc
->MinCount
);
113 ASSERT_EQ(12557u, NinetyFivePerc
->MinCount
);
114 ASSERT_EQ(600u, NinetyNinePerc
->MinCount
);
116 VerifySummary(Summary
);
118 // Test that conversion of summary to and from Metadata works.
120 Summary
.getMD(Context
, AddPartialField
, AddPartialProfileRatioField
);
122 ProfileSummary
*PS
= ProfileSummary::getFromMD(MD
);
127 // Test that summary can be attached to and read back from module.
128 M
.eraseNamedMetadata(M
.getOrInsertModuleFlagsMetadata());
129 M
.setProfileSummary(MD
, ProfileSummary::PSK_Sample
);
130 MD
= M
.getProfileSummary(/* IsCS */ false);
132 PS
= ProfileSummary::getFromMD(MD
);
138 void testRoundTrip(SampleProfileFormat Format
, bool Remap
, bool UseMD5
) {
139 TempFile
ProfileFile("profile", "", "", /*Unique*/ true);
140 createWriter(Format
, ProfileFile
.path());
141 if (Format
== SampleProfileFormat::SPF_Ext_Binary
&& UseMD5
)
142 static_cast<SampleProfileWriterExtBinary
*>(Writer
.get())->setUseMD5();
144 StringRef
FooName("_Z3fooi");
145 FunctionSamples FooSamples
;
146 FooSamples
.setName(FooName
);
147 FooSamples
.addTotalSamples(7711);
148 FooSamples
.addHeadSamples(610);
149 FooSamples
.addBodySamples(1, 0, 610);
150 FooSamples
.addBodySamples(2, 0, 600);
151 FooSamples
.addBodySamples(4, 0, 60000);
152 FooSamples
.addBodySamples(8, 0, 60351);
153 FooSamples
.addBodySamples(10, 0, 605);
155 // Add inline instance with name "_Z3gooi".
156 StringRef
GooName("_Z3gooi");
158 FooSamples
.functionSamplesAt(LineLocation(7, 0))[GooName
.str()];
159 GooSamples
.setName(GooName
);
160 GooSamples
.addTotalSamples(502);
161 GooSamples
.addBodySamples(3, 0, 502);
163 // Add inline instance with name "_Z3hooi".
164 StringRef
HooName("_Z3hooi");
166 GooSamples
.functionSamplesAt(LineLocation(9, 0))[HooName
.str()];
167 HooSamples
.setName(HooName
);
168 HooSamples
.addTotalSamples(317);
169 HooSamples
.addBodySamples(4, 0, 317);
171 StringRef
BarName("_Z3bari");
172 FunctionSamples BarSamples
;
173 BarSamples
.setName(BarName
);
174 BarSamples
.addTotalSamples(20301);
175 BarSamples
.addHeadSamples(1437);
176 BarSamples
.addBodySamples(1, 0, 1437);
177 // Test how reader/writer handles unmangled names.
178 StringRef
MconstructName("_M_construct<char *>");
179 StringRef
StringviewName("string_view<std::allocator<char> >");
180 BarSamples
.addCalledTargetSamples(1, 0, MconstructName
, 1000);
181 BarSamples
.addCalledTargetSamples(1, 0, StringviewName
, 437);
183 StringRef
BazName("_Z3bazi");
184 FunctionSamples BazSamples
;
185 BazSamples
.setName(BazName
);
186 BazSamples
.addTotalSamples(12557);
187 BazSamples
.addHeadSamples(1257);
188 BazSamples
.addBodySamples(1, 0, 12557);
190 StringRef
BooName("_Z3booi");
191 FunctionSamples BooSamples
;
192 BooSamples
.setName(BooName
);
193 BooSamples
.addTotalSamples(1232);
194 BooSamples
.addHeadSamples(1);
195 BooSamples
.addBodySamples(1, 0, 1232);
197 SampleProfileMap Profiles
;
198 Profiles
[FooName
] = std::move(FooSamples
);
199 Profiles
[BarName
] = std::move(BarSamples
);
200 Profiles
[BazName
] = std::move(BazSamples
);
201 Profiles
[BooName
] = std::move(BooSamples
);
203 Module
M("my_module", Context
);
204 FunctionType
*fn_type
=
205 FunctionType::get(Type::getVoidTy(Context
), {}, false);
207 TempFile
RemapFile(createRemapFile());
209 FooName
= "_Z4fauxi";
215 M
.getOrInsertFunction(FooName
, fn_type
);
216 M
.getOrInsertFunction(BarName
, fn_type
);
217 M
.getOrInsertFunction(BooName
, fn_type
);
219 ProfileSymbolList List
;
220 if (Format
== SampleProfileFormat::SPF_Ext_Binary
) {
221 List
.add("zoo", true);
222 List
.add("moo", true);
224 Writer
->setProfileSymbolList(&List
);
227 EC
= Writer
->write(Profiles
);
228 ASSERT_TRUE(NoError(EC
));
230 Writer
->getOutputStream().flush();
232 readProfile(M
, ProfileFile
.path(), RemapFile
.path());
234 ASSERT_TRUE(NoError(EC
));
236 if (Format
== SampleProfileFormat::SPF_Ext_Binary
) {
237 std::unique_ptr
<ProfileSymbolList
> ReaderList
=
238 Reader
->getProfileSymbolList();
239 ReaderList
->contains("zoo");
240 ReaderList
->contains("moo");
243 FunctionSamples
*ReadFooSamples
= Reader
->getSamplesFor(FooName
);
244 ASSERT_TRUE(ReadFooSamples
!= nullptr);
246 ASSERT_EQ("_Z3fooi", ReadFooSamples
->getName());
248 ASSERT_EQ(7711u, ReadFooSamples
->getTotalSamples());
249 ASSERT_EQ(610u, ReadFooSamples
->getHeadSamples());
251 // Try to find a FunctionSamples with GooName at given callsites containing
252 // inline instance for GooName. Test the correct FunctionSamples can be
253 // found with Remapper support.
254 const FunctionSamples
*ReadGooSamples
=
255 ReadFooSamples
->findFunctionSamplesAt(LineLocation(7, 0), GooName
,
256 Reader
->getRemapper());
257 ASSERT_TRUE(ReadGooSamples
!= nullptr);
258 ASSERT_EQ(502u, ReadGooSamples
->getTotalSamples());
260 // Try to find a FunctionSamples with GooName at given callsites containing
261 // no inline instance for GooName. Test no FunctionSamples will be
262 // found with Remapper support.
263 const FunctionSamples
*ReadGooSamplesAgain
=
264 ReadFooSamples
->findFunctionSamplesAt(LineLocation(9, 0), GooName
,
265 Reader
->getRemapper());
266 ASSERT_TRUE(ReadGooSamplesAgain
== nullptr);
268 // The inline instance of Hoo is inside of the inline instance of Goo.
269 // Try to find a FunctionSamples with HooName at given callsites containing
270 // inline instance for HooName. Test the correct FunctionSamples can be
271 // found with Remapper support.
272 const FunctionSamples
*ReadHooSamples
=
273 ReadGooSamples
->findFunctionSamplesAt(LineLocation(9, 0), HooName
,
274 Reader
->getRemapper());
275 ASSERT_TRUE(ReadHooSamples
!= nullptr);
276 ASSERT_EQ(317u, ReadHooSamples
->getTotalSamples());
278 FunctionSamples
*ReadBarSamples
= Reader
->getSamplesFor(BarName
);
279 ASSERT_TRUE(ReadBarSamples
!= nullptr);
281 ASSERT_EQ("_Z3bari", ReadBarSamples
->getName());
283 ASSERT_EQ(20301u, ReadBarSamples
->getTotalSamples());
284 ASSERT_EQ(1437u, ReadBarSamples
->getHeadSamples());
285 ErrorOr
<SampleRecord::CallTargetMap
> CTMap
=
286 ReadBarSamples
->findCallTargetMapAt(1, 0);
287 ASSERT_FALSE(CTMap
.getError());
289 // Because _Z3bazi is not defined in module M, expect _Z3bazi's profile
290 // is not loaded when the profile is ExtBinary or Compact format because
291 // these formats support loading function profiles on demand.
292 FunctionSamples
*ReadBazSamples
= Reader
->getSamplesFor(BazName
);
293 if (Format
== SampleProfileFormat::SPF_Ext_Binary
||
294 Format
== SampleProfileFormat::SPF_Compact_Binary
) {
295 ASSERT_TRUE(ReadBazSamples
== nullptr);
296 ASSERT_EQ(3u, Reader
->getProfiles().size());
298 ASSERT_TRUE(ReadBazSamples
!= nullptr);
299 ASSERT_EQ(12557u, ReadBazSamples
->getTotalSamples());
300 ASSERT_EQ(4u, Reader
->getProfiles().size());
303 FunctionSamples
*ReadBooSamples
= Reader
->getSamplesFor(BooName
);
304 ASSERT_TRUE(ReadBooSamples
!= nullptr);
305 ASSERT_EQ(1232u, ReadBooSamples
->getTotalSamples());
307 std::string MconstructGUID
;
308 StringRef MconstructRep
=
309 getRepInFormat(MconstructName
, UseMD5
, MconstructGUID
);
310 std::string StringviewGUID
;
311 StringRef StringviewRep
=
312 getRepInFormat(StringviewName
, UseMD5
, StringviewGUID
);
313 ASSERT_EQ(1000u, CTMap
.get()[MconstructRep
]);
314 ASSERT_EQ(437u, CTMap
.get()[StringviewRep
]);
317 ProfileSummary
&Summary
= Reader
->getSummary();
318 Summary
.setPartialProfile(true);
319 verifyProfileSummary(Summary
, M
, true, false);
321 Summary
.setPartialProfile(false);
322 verifyProfileSummary(Summary
, M
, true, false);
324 verifyProfileSummary(Summary
, M
, false, false);
326 Summary
.setPartialProfile(true);
327 Summary
.setPartialProfileRatio(0.5);
328 verifyProfileSummary(Summary
, M
, true, true);
331 void addFunctionSamples(SampleProfileMap
*Smap
, const char *Fname
,
332 uint64_t TotalSamples
, uint64_t HeadSamples
) {
333 StringRef
Name(Fname
);
334 FunctionSamples FcnSamples
;
335 FcnSamples
.setName(Name
);
336 FcnSamples
.addTotalSamples(TotalSamples
);
337 FcnSamples
.addHeadSamples(HeadSamples
);
338 FcnSamples
.addBodySamples(1, 0, HeadSamples
);
339 (*Smap
)[Name
] = FcnSamples
;
342 SampleProfileMap
setupFcnSamplesForElisionTest(StringRef Policy
) {
343 SampleProfileMap Smap
;
344 addFunctionSamples(&Smap
, "foo", uint64_t(20301), uint64_t(1437));
345 if (Policy
== "" || Policy
== "all")
347 addFunctionSamples(&Smap
, "foo.bar", uint64_t(20303), uint64_t(1439));
348 if (Policy
== "selected")
350 addFunctionSamples(&Smap
, "foo.llvm.2465", uint64_t(20305), uint64_t(1441));
354 void createFunctionWithSampleProfileElisionPolicy(Module
*M
,
357 FunctionType
*FnType
=
358 FunctionType::get(Type::getVoidTy(Context
), {}, false);
359 auto Inserted
= M
->getOrInsertFunction(Fname
, FnType
);
360 auto Fcn
= cast
<Function
>(Inserted
.getCallee());
362 Fcn
->addFnAttr("sample-profile-suffix-elision-policy", Policy
);
365 void setupModuleForElisionTest(Module
*M
, StringRef Policy
) {
366 createFunctionWithSampleProfileElisionPolicy(M
, "foo", Policy
);
367 createFunctionWithSampleProfileElisionPolicy(M
, "foo.bar", Policy
);
368 createFunctionWithSampleProfileElisionPolicy(M
, "foo.llvm.2465", Policy
);
371 void testSuffixElisionPolicy(SampleProfileFormat Format
, StringRef Policy
,
372 const StringMap
<uint64_t> &Expected
) {
373 TempFile
ProfileFile("profile", "", "", /*Unique*/ true);
375 Module
M("my_module", Context
);
376 setupModuleForElisionTest(&M
, Policy
);
377 SampleProfileMap ProfMap
= setupFcnSamplesForElisionTest(Policy
);
380 createWriter(Format
, ProfileFile
.path());
382 EC
= Writer
->write(ProfMap
);
383 ASSERT_TRUE(NoError(EC
));
384 Writer
->getOutputStream().flush();
387 readProfile(M
, ProfileFile
.path());
389 ASSERT_TRUE(NoError(EC
));
391 for (auto I
= Expected
.begin(); I
!= Expected
.end(); ++I
) {
392 uint64_t Esamples
= uint64_t(-1);
393 FunctionSamples
*Samples
= Reader
->getSamplesFor(I
->getKey());
394 if (Samples
!= nullptr)
395 Esamples
= Samples
->getTotalSamples();
396 ASSERT_EQ(I
->getValue(), Esamples
);
401 TEST_F(SampleProfTest
, roundtrip_text_profile
) {
402 testRoundTrip(SampleProfileFormat::SPF_Text
, false, false);
405 TEST_F(SampleProfTest
, roundtrip_raw_binary_profile
) {
406 testRoundTrip(SampleProfileFormat::SPF_Binary
, false, false);
409 TEST_F(SampleProfTest
, roundtrip_compact_binary_profile
) {
410 testRoundTrip(SampleProfileFormat::SPF_Compact_Binary
, false, true);
413 TEST_F(SampleProfTest
, roundtrip_ext_binary_profile
) {
414 testRoundTrip(SampleProfileFormat::SPF_Ext_Binary
, false, false);
417 TEST_F(SampleProfTest
, roundtrip_md5_ext_binary_profile
) {
418 testRoundTrip(SampleProfileFormat::SPF_Ext_Binary
, false, true);
421 TEST_F(SampleProfTest
, remap_text_profile
) {
422 testRoundTrip(SampleProfileFormat::SPF_Text
, true, false);
425 TEST_F(SampleProfTest
, remap_raw_binary_profile
) {
426 testRoundTrip(SampleProfileFormat::SPF_Binary
, true, false);
429 TEST_F(SampleProfTest
, remap_ext_binary_profile
) {
430 testRoundTrip(SampleProfileFormat::SPF_Ext_Binary
, true, false);
433 TEST_F(SampleProfTest
, sample_overflow_saturation
) {
434 const uint64_t Max
= std::numeric_limits
<uint64_t>::max();
435 sampleprof_error Result
;
437 FunctionSamples FooSamples
;
438 Result
= FooSamples
.addTotalSamples(1);
439 ASSERT_EQ(Result
, sampleprof_error::success
);
441 Result
= FooSamples
.addHeadSamples(1);
442 ASSERT_EQ(Result
, sampleprof_error::success
);
444 Result
= FooSamples
.addBodySamples(10, 0, 1);
445 ASSERT_EQ(Result
, sampleprof_error::success
);
447 Result
= FooSamples
.addTotalSamples(Max
);
448 ASSERT_EQ(Result
, sampleprof_error::counter_overflow
);
449 ASSERT_EQ(FooSamples
.getTotalSamples(), Max
);
451 Result
= FooSamples
.addHeadSamples(Max
);
452 ASSERT_EQ(Result
, sampleprof_error::counter_overflow
);
453 ASSERT_EQ(FooSamples
.getHeadSamples(), Max
);
455 Result
= FooSamples
.addBodySamples(10, 0, Max
);
456 ASSERT_EQ(Result
, sampleprof_error::counter_overflow
);
457 ErrorOr
<uint64_t> BodySamples
= FooSamples
.findSamplesAt(10, 0);
458 ASSERT_FALSE(BodySamples
.getError());
459 ASSERT_EQ(BodySamples
.get(), Max
);
462 TEST_F(SampleProfTest
, default_suffix_elision_text
) {
463 // Default suffix elision policy: strip everything after first dot.
464 // This implies that all suffix variants will map to "foo", so
465 // we don't expect to see any entries for them in the sample
467 StringMap
<uint64_t> Expected
;
468 Expected
["foo"] = uint64_t(20301);
469 Expected
["foo.bar"] = uint64_t(-1);
470 Expected
["foo.llvm.2465"] = uint64_t(-1);
471 testSuffixElisionPolicy(SampleProfileFormat::SPF_Text
, "", Expected
);
474 TEST_F(SampleProfTest
, default_suffix_elision_compact_binary
) {
475 // Default suffix elision policy: strip everything after first dot.
476 // This implies that all suffix variants will map to "foo", so
477 // we don't expect to see any entries for them in the sample
479 StringMap
<uint64_t> Expected
;
480 Expected
["foo"] = uint64_t(20301);
481 Expected
["foo.bar"] = uint64_t(-1);
482 Expected
["foo.llvm.2465"] = uint64_t(-1);
483 testSuffixElisionPolicy(SampleProfileFormat::SPF_Compact_Binary
, "",
487 TEST_F(SampleProfTest
, selected_suffix_elision_text
) {
488 // Profile is created and searched using the "selected"
489 // suffix elision policy: we only strip a .XXX suffix if
490 // it matches a pattern known to be generated by the compiler
491 // (e.g. ".llvm.<digits>").
492 StringMap
<uint64_t> Expected
;
493 Expected
["foo"] = uint64_t(20301);
494 Expected
["foo.bar"] = uint64_t(20303);
495 Expected
["foo.llvm.2465"] = uint64_t(-1);
496 testSuffixElisionPolicy(SampleProfileFormat::SPF_Text
, "selected", Expected
);
499 TEST_F(SampleProfTest
, selected_suffix_elision_compact_binary
) {
500 // Profile is created and searched using the "selected"
501 // suffix elision policy: we only strip a .XXX suffix if
502 // it matches a pattern known to be generated by the compiler
503 // (e.g. ".llvm.<digits>").
504 StringMap
<uint64_t> Expected
;
505 Expected
["foo"] = uint64_t(20301);
506 Expected
["foo.bar"] = uint64_t(20303);
507 Expected
["foo.llvm.2465"] = uint64_t(-1);
508 testSuffixElisionPolicy(SampleProfileFormat::SPF_Compact_Binary
, "selected",
512 TEST_F(SampleProfTest
, none_suffix_elision_text
) {
513 // Profile is created and searched using the "none"
514 // suffix elision policy: no stripping of suffixes at all.
515 // Here we expect to see all variants in the profile.
516 StringMap
<uint64_t> Expected
;
517 Expected
["foo"] = uint64_t(20301);
518 Expected
["foo.bar"] = uint64_t(20303);
519 Expected
["foo.llvm.2465"] = uint64_t(20305);
520 testSuffixElisionPolicy(SampleProfileFormat::SPF_Text
, "none", Expected
);
523 TEST_F(SampleProfTest
, none_suffix_elision_compact_binary
) {
524 // Profile is created and searched using the "none"
525 // suffix elision policy: no stripping of suffixes at all.
526 // Here we expect to see all variants in the profile.
527 StringMap
<uint64_t> Expected
;
528 Expected
["foo"] = uint64_t(20301);
529 Expected
["foo.bar"] = uint64_t(20303);
530 Expected
["foo.llvm.2465"] = uint64_t(20305);
531 testSuffixElisionPolicy(SampleProfileFormat::SPF_Compact_Binary
, "none",
535 } // end anonymous namespace