Index: include/llvm/ProfileData/SampleProf.h =================================================================== --- include/llvm/ProfileData/SampleProf.h +++ include/llvm/ProfileData/SampleProf.h @@ -410,6 +410,31 @@ return getNameInModule(Name, M); } + /// Return the canonical name for a function, taking into account + /// suffix elision policy attributes. + static StringRef CanonicalFnName(const Function &F) { + auto attrName = "sample-profile-suffix-elision-policy"; + auto Attr = F.getFnAttribute(attrName).getValueAsString(); + if (Attr == "" || Attr == "all") { + return F.getName().split('.').first; + } else if (Attr == "selected") { + StringRef suffix(".llvm."); + StringRef cand(F.getName()); + auto it = cand.rfind(suffix); + if (it == StringRef::npos) + return cand; + auto dit = cand.rfind('.'); + if (dit == it + suffix.size()-1) + return cand.substr(0, it); + return cand; + } else if (Attr == "none") { + return F.getName(); + } else { + assert(false); + } + return F.getName(); + } + /// Translate \p Name into its original name in Module. /// When the Format is not SPF_Compact_Binary, \p Name needs no translation. /// When the Format is SPF_Compact_Binary, \p Name in current FunctionSamples @@ -465,11 +490,9 @@ /// built in post-thin-link phase and var promotion has been done, /// we need to add the substring of function name without the suffix /// into the GUIDToFuncNameMap. - auto pos = OrigName.find('.'); - if (pos != StringRef::npos) { - StringRef NewName = OrigName.substr(0, pos); - GUIDToFuncNameMap.insert({Function::getGUID(NewName), NewName}); - } + StringRef CanonName = CanonicalFnName(F); + if (CanonName != OrigName) + GUIDToFuncNameMap.insert({Function::getGUID(CanonName), CanonName}); } CurrentModule = &M; } Index: include/llvm/ProfileData/SampleProfReader.h =================================================================== --- include/llvm/ProfileData/SampleProfReader.h +++ include/llvm/ProfileData/SampleProfReader.h @@ -289,7 +289,8 @@ // The function name may have been updated by adding suffix. In sample // profile, the function names are all stripped, so we need to strip // the function name suffix before matching with profile. - return getSamplesFor(F.getName().split('.').first); + StringRef CanonName = FunctionSamples::CanonicalFnName(F); + return getSamplesFor(CanonName); } /// Return the samples collected for function \p F. Index: lib/ProfileData/SampleProfReader.cpp =================================================================== --- lib/ProfileData/SampleProfReader.cpp +++ lib/ProfileData/SampleProfReader.cpp @@ -593,8 +593,8 @@ void SampleProfileReaderCompactBinary::collectFuncsToUse(const Module &M) { FuncsToUse.clear(); for (auto &F : M) { - StringRef Fname = F.getName().split('.').first; - FuncsToUse.insert(Fname); + StringRef CanonName = FunctionSamples::CanonicalFnName(F); + FuncsToUse.insert(CanonName); } } Index: unittests/ProfileData/SampleProfTest.cpp =================================================================== --- unittests/ProfileData/SampleProfTest.cpp +++ unittests/ProfileData/SampleProfTest.cpp @@ -199,6 +199,79 @@ VerifySummary(*PS); delete PS; } + + void addFcnSamples(StringMap *smap, + const char *fname, + uint64_t totalSamples, + uint64_t headSamples) { + StringRef Name(fname); + FunctionSamples FcnSamples; + FcnSamples.setName(Name); + FcnSamples.addTotalSamples(totalSamples); + FcnSamples.addHeadSamples(headSamples); + FcnSamples.addBodySamples(1, 0, headSamples); + (*smap)[Name] = FcnSamples; + } + + StringMap setupFcnSamplesForElisionTest(StringRef policy) { + StringMap smap; + addFcnSamples(&smap, "foo", uint64_t(20301), uint64_t(1437)); + if (policy == "" || policy == "all") + return smap; + addFcnSamples(&smap, "foo.bar", uint64_t(20303), uint64_t(1439)); + if (policy == "selected") + return smap; + addFcnSamples(&smap, "foo.llvm.2465", uint64_t(20305), uint64_t(1441)); + return smap; + } + + void mkModFcn(Module *M, const char *fname, StringRef policy) { + FunctionType *fn_type = + FunctionType::get(Type::getVoidTy(Context), {}, false); + auto inserted = M->getOrInsertFunction(fname, fn_type); + auto fcn = cast(inserted.getCallee()); + if (policy != "") + fcn->addFnAttr("sample-profile-suffix-elision-policy", policy); + } + + void setupModuleForElisionTest(Module *M, StringRef policy) { + mkModFcn(M, "foo", policy); + mkModFcn(M, "foo.bar", policy); + mkModFcn(M, "foo.llvm.2465", policy); + } + + void testSuffixElisionPolicy(SampleProfileFormat Format, + StringRef policy, + const StringMap &expected) { + SmallVector ProfilePath; + std::error_code EC; + EC = llvm::sys::fs::createTemporaryFile("profile", "", ProfilePath); + ASSERT_TRUE(NoError(EC)); + StringRef ProfileFile(ProfilePath.data(), ProfilePath.size()); + + Module M("my_module", Context); + setupModuleForElisionTest(&M, policy); + StringMap ProfMap = setupFcnSamplesForElisionTest(policy); + + // write profile + createWriter(Format, ProfileFile); + EC = Writer->write(ProfMap); + ASSERT_TRUE(NoError(EC)); + Writer->getOutputStream().flush(); + + // read profile + readProfile(M, ProfileFile); + EC = Reader->read(); + ASSERT_TRUE(NoError(EC)); + + for (auto I = expected.begin(); I != expected.end(); ++I) { + uint64_t esamples = uint64_t(-1); + FunctionSamples *Samples = Reader->getSamplesFor(I->getKey()); + if (Samples != nullptr) + esamples = Samples->getTotalSamples(); + ASSERT_EQ(I->getValue(), esamples); + } + } }; TEST_F(SampleProfTest, roundtrip_text_profile) { @@ -251,4 +324,80 @@ ASSERT_EQ(BodySamples.get(), Max); } +TEST_F(SampleProfTest, default_suffix_elision_text) { + // Default suffix elision policy: strip everything after first dot. + // This implies that all suffix variants will map to "foo", so + // we don't expect to see any entries for them in the sample + // profile. + StringMap expected; + expected["foo"] = uint64_t(20301); + expected["foo.bar"] = uint64_t(-1); + expected["foo.llvm.2465"] = uint64_t(-1); + testSuffixElisionPolicy(SampleProfileFormat::SPF_Text, + "", expected); +} + +TEST_F(SampleProfTest, default_suffix_elision_compact_binary) { + // Default suffix elision policy: strip everything after first dot. + // This implies that all suffix variants will map to "foo", so + // we don't expect to see any entries for them in the sample + // profile. + StringMap expected; + expected["foo"] = uint64_t(20301); + expected["foo.bar"] = uint64_t(-1); + expected["foo.llvm.2465"] = uint64_t(-1); + testSuffixElisionPolicy(SampleProfileFormat::SPF_Compact_Binary, + "", expected); +} + +TEST_F(SampleProfTest, selected_suffix_elision_text) { + // Profile is created and searched using the "selected" + // suffix elision policy: we only strip a .XXX suffix if + // it matches a pattern known to be generated by the compiler + // (e.g. ".llvm."). + StringMap expected; + expected["foo"] = uint64_t(20301); + expected["foo.bar"] = uint64_t(20303); + expected["foo.llvm.2465"] = uint64_t(-1); + testSuffixElisionPolicy(SampleProfileFormat::SPF_Text, + "selected", expected); +} + +TEST_F(SampleProfTest, selected_suffix_elision_compact_binary) { + // Profile is created and searched using the "selected" + // suffix elision policy: we only strip a .XXX suffix if + // it matches a pattern known to be generated by the compiler + // (e.g. ".llvm."). + StringMap expected; + expected["foo"] = uint64_t(20301); + expected["foo.bar"] = uint64_t(20303); + expected["foo.llvm.2465"] = uint64_t(-1); + testSuffixElisionPolicy(SampleProfileFormat::SPF_Compact_Binary, + "selected", expected); +} + +TEST_F(SampleProfTest, none_suffix_elision_text) { + // Profile is created and searched using the "none" + // suffix elision policy: no stripping of suffixes at all. + // Here we expect to see all variants in the profile. + StringMap expected; + expected["foo"] = uint64_t(20301); + expected["foo.bar"] = uint64_t(20303); + expected["foo.llvm.2465"] = uint64_t(20305); + testSuffixElisionPolicy(SampleProfileFormat::SPF_Text, + "none", expected); +} + +TEST_F(SampleProfTest, none_suffix_elision_compact_binary) { + // Profile is created and searched using the "none" + // suffix elision policy: no stripping of suffixes at all. + // Here we expect to see all variants in the profile. + StringMap expected; + expected["foo"] = uint64_t(20301); + expected["foo.bar"] = uint64_t(20303); + expected["foo.llvm.2465"] = uint64_t(20305); + testSuffixElisionPolicy(SampleProfileFormat::SPF_Compact_Binary, + "none", expected); +} + } // end anonymous namespace