Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions llvm/include/llvm/IR/Function.h
Original file line number Diff line number Diff line change
Expand Up @@ -346,12 +346,6 @@ class LLVM_ABI Function : public GlobalObject, public ilist_node<Function> {
/// sample PGO, to enable the same inlines as the profiled optimized binary.
DenseSet<GlobalValue::GUID> getImportGUIDs() const;

/// Set the section prefix for this function.
void setSectionPrefix(StringRef Prefix);

/// Get the section prefix for this function.
std::optional<StringRef> getSectionPrefix() const;

/// hasGC/getGC/setGC/clearGC - The name of the garbage collection algorithm
/// to use during code generation.
bool hasGC() const {
Expand Down
11 changes: 11 additions & 0 deletions llvm/include/llvm/IR/GlobalObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,17 @@ class GlobalObject : public GlobalValue {
/// appropriate default object file section.
void setSection(StringRef S);

/// Set the section prefix for this global object.
void setSectionPrefix(StringRef Prefix);

/// Update the section prefix, unless the existing prefix is the same as
/// `KeepPrefix`.
void updateSectionPrefix(StringRef Prefix,
std::optional<StringRef> KeepPrefix = std::nullopt);

/// Get the section prefix for this global object.
std::optional<StringRef> getSectionPrefix() const;

bool hasComdat() const { return getComdat() != nullptr; }
const Comdat *getComdat() const { return ObjComdat; }
Comdat *getComdat() { return ObjComdat; }
Expand Down
4 changes: 2 additions & 2 deletions llvm/include/llvm/IR/MDBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ class MDBuilder {
MDNode *createFunctionEntryCount(uint64_t Count, bool Synthetic,
const DenseSet<GlobalValue::GUID> *Imports);

/// Return metadata containing the section prefix for a function.
MDNode *createFunctionSectionPrefix(StringRef Prefix);
/// Return metadata containing the section prefix for a global object.
MDNode *createGlobalObjectSectionPrefix(StringRef Prefix);

/// Return metadata containing the pseudo probe descriptor for a function.
MDNode *createPseudoProbeDesc(uint64_t GUID, uint64_t Hash, StringRef FName);
Expand Down
201 changes: 149 additions & 52 deletions llvm/lib/CodeGen/StaticDataSplitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
// The pass uses branch profile data to assign hotness based section qualifiers
// for the following types of static data:
// - Jump tables
// - Module-internal global variables
// - Constant pools (TODO)
// - Other module-internal data (TODO)
//
// For the original RFC of this pass please see
// https://discourse.llvm.org/t/rfc-profile-guided-static-data-partitioning/83744

#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/Analysis/ProfileSummaryInfo.h"
#include "llvm/CodeGen/MBFIWrapper.h"
Expand All @@ -27,9 +27,12 @@
#include "llvm/CodeGen/MachineFunctionPass.h"
#include "llvm/CodeGen/MachineJumpTableInfo.h"
#include "llvm/CodeGen/Passes.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/Module.h"
#include "llvm/InitializePasses.h"
#include "llvm/Pass.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Target/TargetLoweringObjectFile.h"

using namespace llvm;

Expand All @@ -46,12 +49,27 @@ class StaticDataSplitter : public MachineFunctionPass {
const MachineBlockFrequencyInfo *MBFI = nullptr;
const ProfileSummaryInfo *PSI = nullptr;

// Returns true iff any jump table is hot-cold categorized.
bool splitJumpTables(MachineFunction &MF);
void updateStats(bool ProfileAvailable, const MachineJumpTableInfo *MJTI);
void updateJumpTableStats(bool ProfileAvailable,
const MachineJumpTableInfo &MJTI);

// Same as above but works on functions with profile information.
bool splitJumpTablesWithProfiles(const MachineFunction &MF,
MachineJumpTableInfo &MJTI);
// Use profiles to partition static data.
bool partitionStaticDataWithProfiles(MachineFunction &MF);

// If the global value is a local linkage global variable, return it.
// Otherwise, return nullptr.
const GlobalVariable *getLocalLinkageGlobalVariable(const GlobalValue *GV);

// Returns true if the global variable is in one of {.rodata, .bss, .data,
// .data.rel.ro} sections
bool inStaticDataSection(const GlobalVariable *GV, const TargetMachine &TM);

// Iterate all global variables in the module and update the section prefix
// of the module-internal data.
void updateGlobalVariableSectionPrefix(MachineFunction &MF);

// Accummulated data profile count across machine functions in the module.
DenseMap<const GlobalVariable *, APInt> DataProfileCounts;

public:
static char ID;
Expand All @@ -77,13 +95,24 @@ bool StaticDataSplitter::runOnMachineFunction(MachineFunction &MF) {
MBFI = &getAnalysis<MachineBlockFrequencyInfoWrapperPass>().getMBFI();
PSI = &getAnalysis<ProfileSummaryInfoWrapperPass>().getPSI();

return splitJumpTables(MF);
const bool ProfileAvailable = PSI && PSI->hasProfileSummary() && MBFI &&
MF.getFunction().hasProfileData();
bool Changed = false;

if (ProfileAvailable)
Changed |= partitionStaticDataWithProfiles(MF);

updateGlobalVariableSectionPrefix(MF);
updateStats(ProfileAvailable, MF.getJumpTableInfo());
return Changed;
}

bool StaticDataSplitter::splitJumpTablesWithProfiles(
const MachineFunction &MF, MachineJumpTableInfo &MJTI) {
bool StaticDataSplitter::partitionStaticDataWithProfiles(MachineFunction &MF) {
int NumChangedJumpTables = 0;

const TargetMachine &TM = MF.getTarget();
MachineJumpTableInfo *MJTI = MF.getJumpTableInfo();

// Jump table could be used by either terminating instructions or
// non-terminating ones, so we walk all instructions and use
// `MachineOperand::isJTI()` to identify jump table operands.
Expand All @@ -92,63 +121,131 @@ bool StaticDataSplitter::splitJumpTablesWithProfiles(
for (const auto &MBB : MF) {
for (const MachineInstr &I : MBB) {
for (const MachineOperand &Op : I.operands()) {
if (!Op.isJTI())
continue;
const int JTI = Op.getIndex();
// This is not a source block of jump table.
if (JTI == -1)
std::optional<uint64_t> Count = std::nullopt;
if (!Op.isJTI() && !Op.isGlobal())
continue;

auto Hotness = MachineFunctionDataHotness::Hot;
Count = MBFI->getBlockProfileCount(&MBB);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::optional<uint64_t> Count = std::nullopt;
if (!Op.isJTI() && !Op.isGlobal())
continue;
auto Hotness = MachineFunctionDataHotness::Hot;
Count = MBFI->getBlockProfileCount(&MBB);
if (!Op.isJTI() && !Op.isGlobal())
continue;
std::optional<uint64_t> Count = MBFI->getBlockProfileCount(&MBB);

is that OK?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes the change is ok.

I kept getting "Applying suggestions on deleted lines is currently not supported." error when trying to commit the suggestion. I'll make the change in the local client..


if (Op.isJTI()) {
assert(MJTI != nullptr && "Jump table info is not available.");
const int JTI = Op.getIndex();
// This is not a source block of jump table.
if (JTI == -1)
continue;

auto Hotness = MachineFunctionDataHotness::Hot;

// Hotness is based on source basic block hotness.
// TODO: PSI APIs are about instruction hotness. Introduce API for
// data access hotness.
if (Count && PSI->isColdCount(*Count))
Hotness = MachineFunctionDataHotness::Cold;

// Hotness is based on source basic block hotness.
// TODO: PSI APIs are about instruction hotness. Introduce API for data
// access hotness.
if (PSI->isColdBlock(&MBB, MBFI))
Hotness = MachineFunctionDataHotness::Cold;
if (MJTI->updateJumpTableEntryHotness(JTI, Hotness))
++NumChangedJumpTables;
} else if (Op.isGlobal()) {
// Find global variables with local linkage
const GlobalVariable *GV =
getLocalLinkageGlobalVariable(Op.getGlobal());
if (!GV || !inStaticDataSection(GV, TM))
continue;

if (MJTI.updateJumpTableEntryHotness(JTI, Hotness))
++NumChangedJumpTables;
// Acccumulate data profile count across machine function
// instructions.
// TODO: Analyze global variable's initializers.
if (Count) {
auto [It, Inserted] =
DataProfileCounts.try_emplace(GV, APInt(128, 0));
It->second += *Count;
}
}
}
}
}
return NumChangedJumpTables > 0;
}

bool StaticDataSplitter::splitJumpTables(MachineFunction &MF) {
MachineJumpTableInfo *MJTI = MF.getJumpTableInfo();
if (!MJTI || MJTI->getJumpTables().empty())
return false;

const bool ProfileAvailable = PSI && PSI->hasProfileSummary() && MBFI &&
MF.getFunction().hasProfileData();
auto statOnExit = llvm::make_scope_exit([&] {
if (!AreStatisticsEnabled())
return;
void StaticDataSplitter::updateJumpTableStats(
bool ProfileAvailable, const MachineJumpTableInfo &MJTI) {
if (!ProfileAvailable) {
NumUnknownJumpTables += MJTI.getJumpTables().size();
return;
}

if (!ProfileAvailable) {
NumUnknownJumpTables += MJTI->getJumpTables().size();
return;
for (size_t JTI = 0; JTI < MJTI.getJumpTables().size(); JTI++) {
auto Hotness = MJTI.getJumpTables()[JTI].Hotness;
if (Hotness == MachineFunctionDataHotness::Hot) {
++NumHotJumpTables;
} else {
assert(Hotness == MachineFunctionDataHotness::Cold &&
"A jump table is either hot or cold when profile information is "
"available.");
++NumColdJumpTables;
}
}
}

for (size_t JTI = 0; JTI < MJTI->getJumpTables().size(); JTI++) {
auto Hotness = MJTI->getJumpTables()[JTI].Hotness;
if (Hotness == MachineFunctionDataHotness::Hot) {
++NumHotJumpTables;
} else {
assert(Hotness == MachineFunctionDataHotness::Cold &&
"A jump table is either hot or cold when profile information is "
"available.");
++NumColdJumpTables;
}
}
});
void StaticDataSplitter::updateStats(bool ProfileAvailable,
const MachineJumpTableInfo *MJTI) {
if (!AreStatisticsEnabled())
return;

// Place jump tables according to block hotness if function has profile data.
if (ProfileAvailable)
return splitJumpTablesWithProfiles(MF, *MJTI);
if (MJTI)
updateJumpTableStats(ProfileAvailable, *MJTI);
}

return true;
const GlobalVariable *
StaticDataSplitter::getLocalLinkageGlobalVariable(const GlobalValue *GV) {
if (!GV || GV->isDeclarationForLinker())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can "GV->hasLocalLinkage()" cover "GV->isDeclarationForLinker()"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I haven't thought about it before.

It turns out IR verifier requires a declaration to have one of 'valid' decl linkages, and valid decl linkages must be at least external according to isValidDeclarationLinkage.

I removed GV->isDeclarationForLinker() here and added a comment.

return nullptr;

return GV->hasLocalLinkage() ? dyn_cast<GlobalVariable>(GV) : nullptr;
}

bool StaticDataSplitter::inStaticDataSection(const GlobalVariable *GV,
const TargetMachine &TM) {
assert(GV && "Caller guaranteed");

// Skip LLVM reserved symbols.
if (GV->getName().starts_with("llvm."))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you please explain why need to skip LLVM reserved symbols?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. The globals with llvm. as a name prefix are usually handled specially, by many (middle-end and back-end) passes (e.g., AsmPrinter emits special LLVM values specially), and they are skipped in this pass mostly out of conservativeness.

I moved this check before calling inStaticDataSection helper though for readability, and added a comment around the check. What do you think about this?

return false;

SectionKind Kind = TargetLoweringObjectFile::getKindForGlobal(GV, TM);
return Kind.isData() || Kind.isReadOnly() || Kind.isReadOnlyWithRel() ||
Kind.isBSS();
}

void StaticDataSplitter::updateGlobalVariableSectionPrefix(
MachineFunction &MF) {
for (GlobalVariable &GV : MF.getFunction().getParent()->globals()) {
if (GV.isDeclarationForLinker())
continue;
// DataProfileCounts accumulates data profile count across all machine
// function instructions, and it can't model the indirect accesses through
// other global variables' initializers.
// TODO: Analyze the users of module-internal global variables and see
// through the users' initializers. Do not place a global variable into
// unlikely section if any of its users are potentially hot.
auto Iter = DataProfileCounts.find(&GV);
if (Iter == DataProfileCounts.end())
continue;

// StaticDataSplitter is made a machine function pass rather than a module
// pass because (Lazy)MachineBlockFrequencyInfo is a machine-function
// analysis pass and cannot be used for a legacy module pass.
// As a result, we use `DataProfileCounts` to accumulate data
// profile count across machine functions and update global variable section
// prefix once per machine function.
// FIXME: Make StaticDataSplitter a module pass under new pass manager
// framework, and set global variable section prefix once per module after
// analyzing all machine functions.
if (PSI->isColdCount(Iter->second.getZExtValue())) {
GV.updateSectionPrefix("unlikely", std::make_optional(StringRef("hot")));
} else if (PSI->isHotCount(Iter->second.getZExtValue())) {
GV.updateSectionPrefix("hot");
}
}
}

char StaticDataSplitter::ID = 0;
Expand Down
6 changes: 6 additions & 0 deletions llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,7 @@ getELFSectionNameForGlobal(const GlobalObject *GO, SectionKind Kind,
}

bool HasPrefix = false;

if (const auto *F = dyn_cast<Function>(GO)) {
// Jump table hotness takes precedence over its enclosing function's hotness
// if it's known. The function's section prefix is used if jump table entry
Expand All @@ -687,6 +688,11 @@ getELFSectionNameForGlobal(const GlobalObject *GO, SectionKind Kind,
raw_svector_ostream(Name) << '.' << *Prefix;
HasPrefix = true;
}
} else if (const auto *GV = dyn_cast<GlobalVariable>(GO)) {
if (std::optional<StringRef> Prefix = GV->getSectionPrefix()) {
raw_svector_ostream(Name) << '.' << *Prefix;
HasPrefix = true;
}
}

if (UniqueSectionName) {
Expand Down
16 changes: 0 additions & 16 deletions llvm/lib/IR/Function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1164,22 +1164,6 @@ DenseSet<GlobalValue::GUID> Function::getImportGUIDs() const {
return R;
}

void Function::setSectionPrefix(StringRef Prefix) {
MDBuilder MDB(getContext());
setMetadata(LLVMContext::MD_section_prefix,
MDB.createFunctionSectionPrefix(Prefix));
}

std::optional<StringRef> Function::getSectionPrefix() const {
if (MDNode *MD = getMetadata(LLVMContext::MD_section_prefix)) {
assert(cast<MDString>(MD->getOperand(0))->getString() ==
"function_section_prefix" &&
"Metadata not match");
return cast<MDString>(MD->getOperand(1))->getString();
}
return std::nullopt;
}

bool Function::nullPointerIsDefined() const {
return hasFnAttribute(Attribute::NullPointerIsValid);
}
Expand Down
30 changes: 30 additions & 0 deletions llvm/lib/IR/Globals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "llvm/IR/GlobalAlias.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/MDBuilder.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
Expand Down Expand Up @@ -286,6 +287,35 @@ void GlobalObject::setSection(StringRef S) {
setGlobalObjectFlag(HasSectionHashEntryBit, !S.empty());
}

void GlobalObject::setSectionPrefix(StringRef Prefix) {
MDBuilder MDB(getContext());
setMetadata(LLVMContext::MD_section_prefix,
MDB.createGlobalObjectSectionPrefix(Prefix));
}

void GlobalObject::updateSectionPrefix(StringRef Prefix,
std::optional<StringRef> KeepPrefix) {
auto SectionPrefix = getSectionPrefix();
if (SectionPrefix && (*SectionPrefix == Prefix ||
(KeepPrefix && *SectionPrefix == *KeepPrefix)))
return;

setSectionPrefix(Prefix);
return;
}

std::optional<StringRef> GlobalObject::getSectionPrefix() const {
if (MDNode *MD = getMetadata(LLVMContext::MD_section_prefix)) {
[[maybe_unused]] StringRef MDName =
cast<MDString>(MD->getOperand(0))->getString();
assert((MDName == "section_prefix" ||
(isa<Function>(this) && MDName == "function_section_prefix")) &&
"Metadata not match");
return cast<MDString>(MD->getOperand(1))->getString();
}
return std::nullopt;
}

bool GlobalValue::isNobuiltinFnDef() const {
const Function *F = dyn_cast<Function>(this);
if (!F || F->empty())
Expand Down
Loading