-
Notifications
You must be signed in to change notification settings - Fork 15.4k
[CodeGen][StaticDataPartitioning]Place local-linkage global variables in hot or unlikely prefixed sections based on profile information #125756
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
8eea1ea
93d9881
5cbe8f8
8f21570
f07d34d
4e096e9
4a2a881
1f50494
967dc03
9302b2b
97103c6
38c8a03
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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" | ||
|
|
@@ -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; | ||
|
|
||
|
|
@@ -46,6 +49,20 @@ class StaticDataSplitter : public MachineFunctionPass { | |
| const MachineBlockFrequencyInfo *MBFI = nullptr; | ||
| const ProfileSummaryInfo *PSI = nullptr; | ||
|
|
||
| // 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 | ||
snehasish marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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. | ||
| bool updateGlobalVariableSectionPrefix(MachineFunction &MF); | ||
|
|
||
| // Accummulated data profile count across machine functions in the module. | ||
| DenseMap<const GlobalVariable *, APInt> DataProfileCounts; | ||
snehasish marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // Update LLVM statistics for a machine function without profiles. | ||
| void updateStatsWithoutProfiles(const MachineFunction &MF); | ||
| // Update LLVM statistics for a machine function with profiles. | ||
|
|
@@ -88,13 +105,16 @@ bool StaticDataSplitter::runOnMachineFunction(MachineFunction &MF) { | |
|
|
||
| bool Changed = partitionStaticDataWithProfiles(MF); | ||
|
|
||
| Changed |= updateGlobalVariableSectionPrefix(MF); | ||
|
|
||
| updateStatsWithProfiles(MF); | ||
| return Changed; | ||
| } | ||
|
|
||
| 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 | ||
|
|
@@ -105,6 +125,11 @@ bool StaticDataSplitter::partitionStaticDataWithProfiles(MachineFunction &MF) { | |
| for (const auto &MBB : MF) { | ||
| for (const MachineInstr &I : MBB) { | ||
| for (const MachineOperand &Op : I.operands()) { | ||
| if (!Op.isJTI() && !Op.isGlobal()) | ||
| continue; | ||
|
|
||
| std::optional<uint64_t> Count = MBFI->getBlockProfileCount(&MBB); | ||
|
|
||
| if (Op.isJTI()) { | ||
| assert(MJTI != nullptr && "Jump table info is not available."); | ||
| const int JTI = Op.getIndex(); | ||
|
|
@@ -117,18 +142,89 @@ bool StaticDataSplitter::partitionStaticDataWithProfiles(MachineFunction &MF) { | |
| // 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)) | ||
| if (Count && PSI->isColdCount(*Count)) | ||
| Hotness = MachineFunctionDataHotness::Cold; | ||
|
|
||
| if (MJTI->updateJumpTableEntryHotness(JTI, Hotness)) | ||
| ++NumChangedJumpTables; | ||
| } else if (Op.isGlobal()) { | ||
snehasish marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // Find global variables with local linkage | ||
| const GlobalVariable *GV = | ||
| getLocalLinkageGlobalVariable(Op.getGlobal()); | ||
| if (!GV || !inStaticDataSection(GV, TM)) | ||
| continue; | ||
|
|
||
| // 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; | ||
| } | ||
|
|
||
| const GlobalVariable * | ||
| StaticDataSplitter::getLocalLinkageGlobalVariable(const GlobalValue *GV) { | ||
| if (!GV || GV->isDeclarationForLinker()) | ||
|
||
| return nullptr; | ||
|
|
||
| return GV->hasLocalLinkage() ? dyn_cast<GlobalVariable>(GV) : nullptr; | ||
| } | ||
|
|
||
| bool StaticDataSplitter::inStaticDataSection(const GlobalVariable *GV, | ||
| const TargetMachine &TM) { | ||
| assert(GV && "Caller guaranteed"); | ||
snehasish marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Skip LLVM reserved symbols. | ||
| if (GV->getName().starts_with("llvm.")) | ||
|
||
| return false; | ||
|
|
||
| SectionKind Kind = TargetLoweringObjectFile::getKindForGlobal(GV, TM); | ||
| return Kind.isData() || Kind.isReadOnly() || Kind.isReadOnlyWithRel() || | ||
| Kind.isBSS(); | ||
| } | ||
|
|
||
| bool StaticDataSplitter::updateGlobalVariableSectionPrefix( | ||
| MachineFunction &MF) { | ||
| bool Changed = false; | ||
| 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())) { | ||
| Changed |= GV.updateSectionPrefix("unlikely", | ||
|
||
| std::make_optional(StringRef("hot"))); | ||
| } else if (PSI->isHotCount(Iter->second.getZExtValue())) { | ||
| Changed |= GV.updateSectionPrefix("hot"); | ||
| } | ||
| } | ||
| return Changed; | ||
| } | ||
|
|
||
| void StaticDataSplitter::updateStatsWithProfiles(const MachineFunction &MF) { | ||
| if (!AreStatisticsEnabled()) | ||
| return; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| ; RUN: llc -mtriple x86_64-linux-gnu -data-sections %s -o - | FileCheck %s --check-prefix=ELF | ||
| ; RUN: llc -mtriple x86_64-linux-gnu -unique-section-names=0 -data-sections %s -o - | FileCheck %s --check-prefix=ELF-NOUNIQ | ||
|
|
||
| ; RUN: llc -mtriple x86_64-windows-msvc -data-sections %s -o - | FileCheck %s --check-prefix=COFF-MSVC | ||
|
|
||
| ; ELF: .section .data.hot.foo, | ||
| ; ELF: .section .data.bar, | ||
| ; ELF: .section .bss.unlikely.baz, | ||
| ; ELF: .section .bss.quz, | ||
|
|
||
| ; ELF-NOUNIQ: .section .data.hot.,"aw",@progbits,unique,1 | ||
| ; ELF-NOUNIQ: .section .data,"aw",@progbits,unique,2 | ||
| ; ELF-NOUNIQ: .section .bss.unlikely.,"aw",@nobits,unique,3 | ||
| ; ELF-NOUNIQ: .section .bss,"aw",@nobits,unique,4 | ||
|
|
||
| ; COFF-MSVC: .section .data,"dw",one_only,foo | ||
| ; COFF-MSVC: .section .data,"dw",one_only,bar | ||
| ; COFF-MSVC: .section .bss,"bw",one_only,baz | ||
| ; COFF-MSVC: .section .bss,"bw",one_only,quz | ||
|
|
||
| @foo = global i32 1, !section_prefix !0 | ||
| @bar = global i32 2 | ||
| @baz = global i32 0, !section_prefix !1 | ||
| @quz = global i32 0 | ||
|
|
||
| !0 = !{!"section_prefix", !"hot"} | ||
| !1 = !{!"section_prefix", !"unlikely"} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if data_section_prefix would make sense.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Function and global variables are derived classes of GlobalObject, and currently function section prefix metadata has To me,
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes. I get it. |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't forget to drop this after merging #125757.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
It occurred to me that counters will increase monotonically (before it becomes max), so I asserted a global variable should not have
.hotprefix underif (PSI->isColdCount(Iter->second)) {around StaticDataSplitter.cpp L 224.