forked from OSchip/llvm-project
[SampleFDO] add suffix elision control for fcn names
Summary: Add hooks for determining the policy used to decide whether/how to chop off symbol 'suffixes' when locating a given function in a sample profile. Prior to this change, any function symbols of the form "X.Y" were elided/truncated into just "X" when looking up things in a sample profile data file. With this change, the policy on suffixes can be changed by adding a new attribute "sample-profile-suffix-elision-policy" to the function: this attribute can have the value "all" (the default), "selected", or "none". A value of "all" preserves the previous behavior (chop off everything after the first "." character, then treat that as the symbol name). A value of "selected" chops off only the rightmost ".llvm.XXXX" suffix (where "XXX" is any string not containing a "." char). A value of "none" indicates that names should be left as is. Subscribers: jdoerfert, wmi, mtrofin, danielcdh, llvm-commits Tags: #llvm Differential Revision: https://reviews.llvm.org/D58832 llvm-svn: 356146
This commit is contained in:
parent
9678e8d576
commit
9f96f1f17a
|
@ -410,6 +410,34 @@ public:
|
|||
return getNameInModule(Name, M);
|
||||
}
|
||||
|
||||
/// Return the canonical name for a function, taking into account
|
||||
/// suffix elision policy attributes.
|
||||
static StringRef getCanonicalFnName(const Function &F) {
|
||||
static const char *knownSuffixes[] = { ".llvm.", ".part." };
|
||||
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 Cand(F.getName());
|
||||
for (const auto &Suf : knownSuffixes) {
|
||||
StringRef Suffix(Suf);
|
||||
auto It = Cand.rfind(Suffix);
|
||||
if (It == StringRef::npos)
|
||||
return Cand;
|
||||
auto Dit = Cand.rfind('.');
|
||||
if (Dit == It + Suffix.size() - 1)
|
||||
Cand = Cand.substr(0, It);
|
||||
}
|
||||
return Cand;
|
||||
} else if (Attr == "none") {
|
||||
return F.getName();
|
||||
} else {
|
||||
assert(false && "internal error: unknown suffix elision policy");
|
||||
}
|
||||
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 +493,9 @@ public:
|
|||
/// 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 = getCanonicalFnName(F);
|
||||
if (CanonName != OrigName)
|
||||
GUIDToFuncNameMap.insert({Function::getGUID(CanonName), CanonName});
|
||||
}
|
||||
CurrentModule = &M;
|
||||
}
|
||||
|
|
|
@ -286,10 +286,11 @@ public:
|
|||
|
||||
/// Return the samples collected for function \p F.
|
||||
FunctionSamples *getSamplesFor(const Function &F) {
|
||||
// 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);
|
||||
// The function name may have been updated by adding suffix. Call
|
||||
// a helper to (optionally) strip off suffixes so that we can
|
||||
// match against the original function name in the profile.
|
||||
StringRef CanonName = FunctionSamples::getCanonicalFnName(F);
|
||||
return getSamplesFor(CanonName);
|
||||
}
|
||||
|
||||
/// Return the samples collected for function \p F.
|
||||
|
|
|
@ -593,8 +593,8 @@ std::error_code SampleProfileReaderCompactBinary::readFuncOffsetTable() {
|
|||
void SampleProfileReaderCompactBinary::collectFuncsToUse(const Module &M) {
|
||||
FuncsToUse.clear();
|
||||
for (auto &F : M) {
|
||||
StringRef Fname = F.getName().split('.').first;
|
||||
FuncsToUse.insert(Fname);
|
||||
StringRef CanonName = FunctionSamples::getCanonicalFnName(F);
|
||||
FuncsToUse.insert(CanonName);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -199,6 +199,78 @@ struct SampleProfTest : ::testing::Test {
|
|||
VerifySummary(*PS);
|
||||
delete PS;
|
||||
}
|
||||
|
||||
void addFunctionSamples(StringMap<FunctionSamples> *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<FunctionSamples> setupFcnSamplesForElisionTest(StringRef Policy) {
|
||||
StringMap<FunctionSamples> Smap;
|
||||
addFunctionSamples(&Smap, "foo", uint64_t(20301), uint64_t(1437));
|
||||
if (Policy == "" || Policy == "all")
|
||||
return Smap;
|
||||
addFunctionSamples(&Smap, "foo.bar", uint64_t(20303), uint64_t(1439));
|
||||
if (Policy == "selected")
|
||||
return Smap;
|
||||
addFunctionSamples(&Smap, "foo.llvm.2465", uint64_t(20305), uint64_t(1441));
|
||||
return Smap;
|
||||
}
|
||||
|
||||
void createFunctionWithSampleProfileElisionPolicy(Module *M,
|
||||
const char *Fname,
|
||||
StringRef Policy) {
|
||||
FunctionType *FnType =
|
||||
FunctionType::get(Type::getVoidTy(Context), {}, false);
|
||||
auto Inserted = M->getOrInsertFunction(Fname, FnType);
|
||||
auto Fcn = cast<Function>(Inserted.getCallee());
|
||||
if (Policy != "")
|
||||
Fcn->addFnAttr("sample-profile-suffix-elision-policy", Policy);
|
||||
}
|
||||
|
||||
void setupModuleForElisionTest(Module *M, StringRef Policy) {
|
||||
createFunctionWithSampleProfileElisionPolicy(M, "foo", Policy);
|
||||
createFunctionWithSampleProfileElisionPolicy(M, "foo.bar", Policy);
|
||||
createFunctionWithSampleProfileElisionPolicy(M, "foo.llvm.2465", Policy);
|
||||
}
|
||||
|
||||
void testSuffixElisionPolicy(SampleProfileFormat Format, StringRef Policy,
|
||||
const StringMap<uint64_t> &Expected) {
|
||||
SmallVector<char, 128> 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<FunctionSamples> 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 +323,77 @@ TEST_F(SampleProfTest, sample_overflow_saturation) {
|
|||
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<uint64_t> 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<uint64_t> 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.<digits>").
|
||||
StringMap<uint64_t> 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.<digits>").
|
||||
StringMap<uint64_t> 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<uint64_t> 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<uint64_t> 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
|
||||
|
|
Loading…
Reference in New Issue