https://github.com/llvm/llvm-project/pull/178333 updates the memprof pass to annotate string literal section prefix. The StaticDataProfileInfo.cpp provides an analysis pass to reconcile global variable hotness. It's used by StaticDataAnnotator and AsmPrinter to look up global variable hotness. This PR updates the analysis pass to compute the hotness of string literals. * When both data access profiles and pgo counters provide a hotness attribute, use the hotter one. * Otherwise, use the hotness attribute that's available. Implementation-wise, the option `AnnotateStringLiteralSectionPrefix` is moved from MemProf (a transform pass) to StaticDataProfileInfo (an Analysis pass). Otherwise, there might be errors like caught by CI. Note https://github.com/llvm/llvm-project/pull/178336#issuecomment-3808537817 is an edited message, and its history shows the intermediate failures like below. ~My understanding is~ Preliminary LLM study (:)) shows that the error manifests in PowerPC but not X86 due to cmake variable differences. ``` FAILED: unittests/Target/PowerPC/PowerPCTests ... >>> referenced by CommandLine.h:1437 (/home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/include/llvm/Support/CommandLine.h:1437) >>> StaticDataProfileInfo.cpp.o:(llvm::StaticDataProfileInfo::getConstantSectionPrefix(llvm::Constant const*, llvm::ProfileSummaryInfo const*) const) in archive lib/libLLVMAnalysis.a clang++: error: linker command failed with exit code 1 (use -v to see invocation) ```
212 lines
8.2 KiB
C++
212 lines
8.2 KiB
C++
#include "llvm/Analysis/StaticDataProfileInfo.h"
|
|
#include "llvm/Analysis/ProfileSummaryInfo.h"
|
|
#include "llvm/IR/Constant.h"
|
|
#include "llvm/IR/Constants.h"
|
|
#include "llvm/IR/GlobalVariable.h"
|
|
#include "llvm/IR/Module.h"
|
|
#include "llvm/InitializePasses.h"
|
|
#include "llvm/ProfileData/InstrProf.h"
|
|
|
|
#define DEBUG_TYPE "static-data-profile-info"
|
|
|
|
using namespace llvm;
|
|
|
|
namespace llvm {
|
|
// FIXME: This option is added for incremental rollout purposes.
|
|
// After the option, string literal partitioning should be implied by
|
|
// AnnotateStaticDataSectionPrefix in MemProfUse.cpp and this option should be
|
|
// cleaned up.
|
|
cl::opt<bool> AnnotateStringLiteralSectionPrefix(
|
|
"memprof-annotate-string-literal-section-prefix", cl::init(false),
|
|
cl::Hidden,
|
|
cl::desc("If true, annotate the string literal data section prefix"));
|
|
namespace memprof {
|
|
// Returns true iff the global variable has custom section either by
|
|
// __attribute__((section("name")))
|
|
// (https://clang.llvm.org/docs/AttributeReference.html#section-declspec-allocate)
|
|
// or #pragma clang section directives
|
|
// (https://clang.llvm.org/docs/LanguageExtensions.html#specifying-section-names-for-global-objects-pragma-clang-section).
|
|
static bool hasExplicitSectionName(const GlobalVariable &GVar) {
|
|
if (GVar.hasSection())
|
|
return true;
|
|
|
|
auto Attrs = GVar.getAttributes();
|
|
if (Attrs.hasAttribute("bss-section") || Attrs.hasAttribute("data-section") ||
|
|
Attrs.hasAttribute("relro-section") ||
|
|
Attrs.hasAttribute("rodata-section"))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
AnnotationKind getAnnotationKind(const GlobalVariable &GV) {
|
|
if (GV.isDeclarationForLinker())
|
|
return AnnotationKind::DeclForLinker;
|
|
// Skip 'llvm.'-prefixed global variables conservatively because they are
|
|
// often handled specially,
|
|
StringRef Name = GV.getName();
|
|
if (Name.starts_with("llvm."))
|
|
return AnnotationKind::ReservedName;
|
|
// Respect user-specified custom data sections.
|
|
if (hasExplicitSectionName(GV))
|
|
return AnnotationKind::ExplicitSection;
|
|
return AnnotationKind::AnnotationOK;
|
|
}
|
|
|
|
bool IsAnnotationOK(const GlobalVariable &GV) {
|
|
return getAnnotationKind(GV) == AnnotationKind::AnnotationOK;
|
|
}
|
|
} // namespace memprof
|
|
} // namespace llvm
|
|
|
|
void StaticDataProfileInfo::addConstantProfileCount(
|
|
const Constant *C, std::optional<uint64_t> Count) {
|
|
if (!Count) {
|
|
ConstantWithoutCounts.insert(C);
|
|
return;
|
|
}
|
|
uint64_t &OriginalCount = ConstantProfileCounts[C];
|
|
OriginalCount = llvm::SaturatingAdd(*Count, OriginalCount);
|
|
// Clamp the count to getInstrMaxCountValue. InstrFDO reserves a few
|
|
// large values for special use.
|
|
if (OriginalCount > getInstrMaxCountValue())
|
|
OriginalCount = getInstrMaxCountValue();
|
|
}
|
|
|
|
StaticDataProfileInfo::StaticDataHotness
|
|
StaticDataProfileInfo::getConstantHotnessUsingProfileCount(
|
|
const Constant *C, const ProfileSummaryInfo *PSI, uint64_t Count) const {
|
|
// The accummulated counter shows the constant is hot. Return enum 'hot'
|
|
// whether this variable is seen by unprofiled functions or not.
|
|
if (PSI->isHotCount(Count))
|
|
return StaticDataHotness::Hot;
|
|
// The constant is not hot, and seen by unprofiled functions. We don't want to
|
|
// assign it to unlikely sections, even if the counter says 'cold'. So return
|
|
// enum 'LukewarmOrUnknown'.
|
|
if (ConstantWithoutCounts.count(C))
|
|
return StaticDataHotness::LukewarmOrUnknown;
|
|
// The accummulated counter shows the constant is cold so return enum 'cold'.
|
|
if (PSI->isColdCount(Count))
|
|
return StaticDataHotness::Cold;
|
|
|
|
return StaticDataHotness::LukewarmOrUnknown;
|
|
}
|
|
|
|
StaticDataProfileInfo::StaticDataHotness
|
|
StaticDataProfileInfo::getSectionHotnessUsingDataAccessProfile(
|
|
std::optional<StringRef> MaybeSectionPrefix) const {
|
|
if (!MaybeSectionPrefix)
|
|
return StaticDataHotness::LukewarmOrUnknown;
|
|
StringRef Prefix = *MaybeSectionPrefix;
|
|
assert((Prefix == "hot" || Prefix == "unlikely") &&
|
|
"Expect section_prefix to be one of hot or unlikely");
|
|
return Prefix == "hot" ? StaticDataHotness::Hot : StaticDataHotness::Cold;
|
|
}
|
|
|
|
StringRef StaticDataProfileInfo::hotnessToStr(StaticDataHotness Hotness) const {
|
|
switch (Hotness) {
|
|
case StaticDataHotness::Cold:
|
|
return "unlikely";
|
|
case StaticDataHotness::Hot:
|
|
return "hot";
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
std::optional<uint64_t>
|
|
StaticDataProfileInfo::getConstantProfileCount(const Constant *C) const {
|
|
auto I = ConstantProfileCounts.find(C);
|
|
if (I == ConstantProfileCounts.end())
|
|
return std::nullopt;
|
|
return I->second;
|
|
}
|
|
|
|
StringRef StaticDataProfileInfo::getConstantSectionPrefix(
|
|
const Constant *C, const ProfileSummaryInfo *PSI) const {
|
|
std::optional<uint64_t> Count = getConstantProfileCount(C);
|
|
|
|
#ifndef NDEBUG
|
|
auto DbgPrintPrefix = [](StringRef Prefix) {
|
|
return Prefix.empty() ? "<empty>" : Prefix;
|
|
};
|
|
#endif
|
|
|
|
if (EnableDataAccessProf) {
|
|
// Both data access profiles and PGO counters are available. Use the
|
|
// hotter one to be conservative. Basically, we want the non-unlikely
|
|
// sections to have max coverage of accessed symbols and meanwhile can
|
|
// tolerant some cold symbols in it, and the unlikely section variant to not
|
|
// have potentially hot symbols if possible, to avoid the penalty of access
|
|
// cold pages.
|
|
if (const GlobalVariable *GV = dyn_cast<GlobalVariable>(C);
|
|
GV && llvm::memprof::IsAnnotationOK(*GV) &&
|
|
(AnnotateStringLiteralSectionPrefix ||
|
|
!GV->getName().starts_with(".str"))) {
|
|
// Note a global var is covered by data access profiles iff the
|
|
// symbol name is preserved in the symbol table; most notably, a string
|
|
// literal with private linkage (e.g., those not externalized by ThinLTO
|
|
// and with insignificant address) won't have an entry in the symbol
|
|
// table (unless there is another string with identical content that
|
|
// gets a symbol table entry). For the private-linkage string literals,
|
|
// their hotness will be at least lukewarm (i.e., empty prefix).
|
|
auto HotnessFromDataAccessProf =
|
|
getSectionHotnessUsingDataAccessProfile(GV->getSectionPrefix());
|
|
|
|
if (!Count) {
|
|
StringRef Prefix = hotnessToStr(HotnessFromDataAccessProf);
|
|
LLVM_DEBUG(dbgs() << GV->getName() << " has section prefix "
|
|
<< DbgPrintPrefix(Prefix)
|
|
<< ", solely from data access profiles\n");
|
|
return Prefix;
|
|
}
|
|
|
|
auto HotnessFromPGO = getConstantHotnessUsingProfileCount(C, PSI, *Count);
|
|
StaticDataHotness GlobalVarHotness = StaticDataHotness::LukewarmOrUnknown;
|
|
if (HotnessFromDataAccessProf == StaticDataHotness::Hot ||
|
|
HotnessFromPGO == StaticDataHotness::Hot) {
|
|
GlobalVarHotness = StaticDataHotness::Hot;
|
|
} else if (HotnessFromDataAccessProf ==
|
|
StaticDataHotness::LukewarmOrUnknown ||
|
|
HotnessFromPGO == StaticDataHotness::LukewarmOrUnknown) {
|
|
GlobalVarHotness = StaticDataHotness::LukewarmOrUnknown;
|
|
} else {
|
|
GlobalVarHotness = StaticDataHotness::Cold;
|
|
}
|
|
StringRef Prefix = hotnessToStr(GlobalVarHotness);
|
|
LLVM_DEBUG(
|
|
dbgs() << GV->getName() << " has section prefix "
|
|
<< DbgPrintPrefix(Prefix)
|
|
<< ", the max from data access profiles as "
|
|
<< DbgPrintPrefix(hotnessToStr(HotnessFromDataAccessProf))
|
|
<< " and PGO counters as "
|
|
<< DbgPrintPrefix(hotnessToStr(HotnessFromPGO)) << "\n");
|
|
return Prefix;
|
|
}
|
|
}
|
|
if (!Count)
|
|
return "";
|
|
return hotnessToStr(getConstantHotnessUsingProfileCount(C, PSI, *Count));
|
|
}
|
|
|
|
bool StaticDataProfileInfoWrapperPass::doInitialization(Module &M) {
|
|
bool EnableDataAccessProf = false;
|
|
if (auto *MD = mdconst::extract_or_null<ConstantInt>(
|
|
M.getModuleFlag("EnableDataAccessProf")))
|
|
EnableDataAccessProf = MD->getZExtValue();
|
|
Info.reset(new StaticDataProfileInfo(EnableDataAccessProf));
|
|
return false;
|
|
}
|
|
|
|
bool StaticDataProfileInfoWrapperPass::doFinalization(Module &M) {
|
|
Info.reset();
|
|
return false;
|
|
}
|
|
|
|
INITIALIZE_PASS(StaticDataProfileInfoWrapperPass, "static-data-profile-info",
|
|
"Static Data Profile Info", false, true)
|
|
|
|
StaticDataProfileInfoWrapperPass::StaticDataProfileInfoWrapperPass()
|
|
: ImmutablePass(ID) {}
|
|
|
|
char StaticDataProfileInfoWrapperPass::ID = 0;
|