Skip to content

Commit af784a5

Browse files
[ThinLTO] Use a set rather than a map to track exported ValueInfos. (#97360)
#95482 is a reland of #88024. #95482 keeps indexing memory usage reasonable by using unordered_map and doesn't make other changes to originally reviewed code. While discussing possible ways to minimize indexing memory usage, Teresa asked whether I need `ExportSetTy` as a map or a set is sufficient. This PR implements the idea. It uses a set rather than a map to track exposed ValueInfos. Currently, `ExportLists` has two use cases, and neither needs to track a ValueInfo's import/export status. So using a set is sufficient and correct. 1) In both in-process and distributed ThinLTO, it's used to decide if a function or global variable is visible [1] from another module after importing creates additional cross-module references. * If a cross-module call edge is seen today, the callee must be visible to another module without keeping track of its export status already. For instance, this [2] is how callees of direct calls get exported. 2) For in-process ThinLTO [3], it's used to compute lto cache key. * The cache key computation already hashes [4] 'ImportList' , and 'ExportList' is determined by 'ImportList'. So it's fine to not track 'import type' for export list. [1] https://github.com/llvm/llvm-project/blob/66cd8ec4c08252ebc73c82e4883a8da247ed146b/llvm/lib/LTO/LTO.cpp#L1815-L1819 [2] https://github.com/llvm/llvm-project/blob/66cd8ec4c08252ebc73c82e4883a8da247ed146b/llvm/lib/LTO/LTO.cpp#L1783-L1794 [3] https://github.com/llvm/llvm-project/blob/66cd8ec4c08252ebc73c82e4883a8da247ed146b/llvm/lib/LTO/LTO.cpp#L1494-L1496 [4] https://github.com/llvm/llvm-project/blob/b76100e220591fab2bf0a4917b216439f7aa4b09/llvm/lib/LTO/LTO.cpp#L194-L222
1 parent 873c3f7 commit af784a5

File tree

5 files changed

+61
-80
lines changed

5 files changed

+61
-80
lines changed

llvm/include/llvm/Transforms/IPO/FunctionImport.h

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,10 @@ class FunctionImporter {
104104
/// index's module path string table).
105105
using ImportMapTy = DenseMap<StringRef, FunctionsToImportTy>;
106106

107-
/// The map contains an entry for every global value the module exports.
108-
/// The key is ValueInfo, and the value indicates whether the definition
109-
/// or declaration is visible to another module. If a function's definition is
110-
/// visible to other modules, the global values this function referenced are
111-
/// visible and shouldn't be internalized.
112-
/// TODO: Rename to `ExportMapTy`.
113-
using ExportSetTy = DenseMap<ValueInfo, GlobalValueSummary::ImportKind>;
107+
/// The set contains an entry for every global value that the module exports.
108+
/// Depending on the user context, this container is allowed to contain
109+
/// definitions, declarations or a mix of both.
110+
using ExportSetTy = DenseSet<ValueInfo>;
114111

115112
/// A function of this type is used to load modules referenced by the index.
116113
using ModuleLoaderTy =

llvm/lib/LTO/LTO.cpp

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,19 +161,17 @@ void llvm::computeLTOCacheKey(
161161
auto ModHash = Index.getModuleHash(ModuleID);
162162
Hasher.update(ArrayRef<uint8_t>((uint8_t *)&ModHash[0], sizeof(ModHash)));
163163

164-
std::vector<std::pair<uint64_t, uint8_t>> ExportsGUID;
164+
// TODO: `ExportList` is determined by `ImportList`. Since `ImportList` is
165+
// used to compute cache key, we could omit hashing `ExportList` here.
166+
std::vector<uint64_t> ExportsGUID;
165167
ExportsGUID.reserve(ExportList.size());
166-
for (const auto &[VI, ExportType] : ExportList)
167-
ExportsGUID.push_back(
168-
std::make_pair(VI.getGUID(), static_cast<uint8_t>(ExportType)));
168+
for (const auto &VI : ExportList)
169+
ExportsGUID.push_back(VI.getGUID());
169170

170171
// Sort the export list elements GUIDs.
171172
llvm::sort(ExportsGUID);
172-
for (auto [GUID, ExportType] : ExportsGUID) {
173-
// The export list can impact the internalization, be conservative here
173+
for (auto GUID : ExportsGUID)
174174
Hasher.update(ArrayRef<uint8_t>((uint8_t *)&GUID, sizeof(GUID)));
175-
AddUint8(ExportType);
176-
}
177175

178176
// Include the hash for every module we import functions from. The set of
179177
// imported symbols for each module may affect code generation and is

llvm/lib/Transforms/IPO/FunctionImport.cpp

Lines changed: 49 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -400,8 +400,7 @@ class GlobalsImporter final {
400400
// later, in ComputeCrossModuleImport, after import decisions are
401401
// complete, which is more efficient than adding them here.
402402
if (ExportLists)
403-
(*ExportLists)[RefSummary->modulePath()][VI] =
404-
GlobalValueSummary::Definition;
403+
(*ExportLists)[RefSummary->modulePath()].insert(VI);
405404

406405
// If variable is not writeonly we attempt to recursively analyze
407406
// its references in order to import referenced constants.
@@ -582,7 +581,7 @@ class WorkloadImportsManager : public ModuleImportsManager {
582581
GlobalValueSummary::Definition;
583582
GVI.onImportingSummary(*GVS);
584583
if (ExportLists)
585-
(*ExportLists)[ExportingModule][VI] = GlobalValueSummary::Definition;
584+
(*ExportLists)[ExportingModule].insert(VI);
586585
}
587586
LLVM_DEBUG(dbgs() << "[Workload] Done\n");
588587
}
@@ -818,10 +817,8 @@ static void computeImportForFunction(
818817
// Since definition takes precedence over declaration for the same VI,
819818
// try emplace <VI, declaration> pair without checking insert result.
820819
// If insert doesn't happen, there must be an existing entry keyed by
821-
// VI.
822-
if (ExportLists)
823-
(*ExportLists)[DeclSourceModule].try_emplace(
824-
VI, GlobalValueSummary::Declaration);
820+
// VI. Note `ExportLists` only keeps track of exports due to imported
821+
// definitions.
825822
ImportList[DeclSourceModule].try_emplace(
826823
VI.getGUID(), GlobalValueSummary::Declaration);
827824
}
@@ -892,7 +889,7 @@ static void computeImportForFunction(
892889
// later, in ComputeCrossModuleImport, after import decisions are
893890
// complete, which is more efficient than adding them here.
894891
if (ExportLists)
895-
(*ExportLists)[ExportModulePath][VI] = GlobalValueSummary::Definition;
892+
(*ExportLists)[ExportModulePath].insert(VI);
896893
}
897894

898895
auto GetAdjustedThreshold = [](unsigned Threshold, bool IsHotCallsite) {
@@ -998,19 +995,29 @@ static bool isGlobalVarSummary(const ModuleSummaryIndex &Index,
998995
return false;
999996
}
1000997

1001-
template <class T>
1002-
static unsigned numGlobalVarSummaries(const ModuleSummaryIndex &Index, T &Cont,
1003-
unsigned &DefinedGVS,
1004-
unsigned &DefinedFS) {
998+
// Return the number of global variable summaries in ExportSet.
999+
static unsigned
1000+
numGlobalVarSummaries(const ModuleSummaryIndex &Index,
1001+
FunctionImporter::ExportSetTy &ExportSet) {
1002+
unsigned NumGVS = 0;
1003+
for (auto &VI : ExportSet)
1004+
if (isGlobalVarSummary(Index, VI.getGUID()))
1005+
++NumGVS;
1006+
return NumGVS;
1007+
}
1008+
1009+
// Given ImportMap, return the number of global variable summaries and record
1010+
// the number of defined function summaries as output parameter.
1011+
static unsigned
1012+
numGlobalVarSummaries(const ModuleSummaryIndex &Index,
1013+
FunctionImporter::FunctionsToImportTy &ImportMap,
1014+
unsigned &DefinedFS) {
10051015
unsigned NumGVS = 0;
1006-
DefinedGVS = 0;
10071016
DefinedFS = 0;
1008-
for (auto &[GUID, Type] : Cont) {
1009-
if (isGlobalVarSummary(Index, GUID)) {
1010-
if (Type == GlobalValueSummary::Definition)
1011-
++DefinedGVS;
1017+
for (auto &[GUID, Type] : ImportMap) {
1018+
if (isGlobalVarSummary(Index, GUID))
10121019
++NumGVS;
1013-
} else if (Type == GlobalValueSummary::Definition)
1020+
else if (Type == GlobalValueSummary::Definition)
10141021
++DefinedFS;
10151022
}
10161023
return NumGVS;
@@ -1046,7 +1053,7 @@ static bool checkVariableImport(
10461053
};
10471054

10481055
for (auto &ExportPerModule : ExportLists)
1049-
for (auto &[VI, Unused] : ExportPerModule.second)
1056+
for (auto &VI : ExportPerModule.second)
10501057
if (!FlattenedImports.count(VI.getGUID()) &&
10511058
IsReadOrWriteOnlyVarNeedingImporting(ExportPerModule.first, VI))
10521059
return false;
@@ -1079,14 +1086,12 @@ void llvm::ComputeCrossModuleImport(
10791086
// since we may import the same values multiple times into different modules
10801087
// during the import computation.
10811088
for (auto &ELI : ExportLists) {
1089+
// `NewExports` tracks the VI that gets exported because the full definition
1090+
// of its user/referencer gets exported.
10821091
FunctionImporter::ExportSetTy NewExports;
10831092
const auto &DefinedGVSummaries =
10841093
ModuleToDefinedGVSummaries.lookup(ELI.first);
1085-
for (auto &[EI, Type] : ELI.second) {
1086-
// If a variable is exported as a declaration, its 'refs' and 'calls' are
1087-
// not further exported.
1088-
if (Type == GlobalValueSummary::Declaration)
1089-
continue;
1094+
for (auto &EI : ELI.second) {
10901095
// Find the copy defined in the exporting module so that we can mark the
10911096
// values it references in that specific definition as exported.
10921097
// Below we will add all references and called values, without regard to
@@ -1105,31 +1110,22 @@ void llvm::ComputeCrossModuleImport(
11051110
// we convert such variables initializers to "zeroinitializer".
11061111
// See processGlobalForThinLTO.
11071112
if (!Index.isWriteOnly(GVS))
1108-
for (const auto &VI : GVS->refs()) {
1109-
// Try to emplace the declaration entry. If a definition entry
1110-
// already exists for key `VI`, this is a no-op.
1111-
NewExports.try_emplace(VI, GlobalValueSummary::Declaration);
1112-
}
1113+
for (const auto &VI : GVS->refs())
1114+
NewExports.insert(VI);
11131115
} else {
11141116
auto *FS = cast<FunctionSummary>(S);
1115-
for (const auto &Edge : FS->calls()) {
1116-
// Try to emplace the declaration entry. If a definition entry
1117-
// already exists for key `VI`, this is a no-op.
1118-
NewExports.try_emplace(Edge.first, GlobalValueSummary::Declaration);
1119-
}
1120-
for (const auto &Ref : FS->refs()) {
1121-
// Try to emplace the declaration entry. If a definition entry
1122-
// already exists for key `VI`, this is a no-op.
1123-
NewExports.try_emplace(Ref, GlobalValueSummary::Declaration);
1124-
}
1117+
for (const auto &Edge : FS->calls())
1118+
NewExports.insert(Edge.first);
1119+
for (const auto &Ref : FS->refs())
1120+
NewExports.insert(Ref);
11251121
}
11261122
}
11271123
// Prune list computed above to only include values defined in the
11281124
// exporting module. We do this after the above insertion since we may hit
11291125
// the same ref/call target multiple times in above loop, and it is more
11301126
// efficient to avoid a set lookup each time.
11311127
for (auto EI = NewExports.begin(); EI != NewExports.end();) {
1132-
if (!DefinedGVSummaries.count(EI->first.getGUID()))
1128+
if (!DefinedGVSummaries.count(EI->getGUID()))
11331129
NewExports.erase(EI++);
11341130
else
11351131
++EI;
@@ -1144,29 +1140,22 @@ void llvm::ComputeCrossModuleImport(
11441140
for (auto &ModuleImports : ImportLists) {
11451141
auto ModName = ModuleImports.first;
11461142
auto &Exports = ExportLists[ModName];
1147-
unsigned DefinedGVS = 0, DefinedFS = 0;
1148-
unsigned NumGVS =
1149-
numGlobalVarSummaries(Index, Exports, DefinedGVS, DefinedFS);
1150-
LLVM_DEBUG(dbgs() << "* Module " << ModName << " exports " << DefinedFS
1151-
<< " function as definitions, "
1152-
<< Exports.size() - NumGVS - DefinedFS
1153-
<< " functions as declarations, " << DefinedGVS
1154-
<< " var definitions and " << NumGVS - DefinedGVS
1155-
<< " var declarations. Imports from "
1156-
<< ModuleImports.second.size() << " modules.\n");
1143+
unsigned NumGVS = numGlobalVarSummaries(Index, Exports);
1144+
LLVM_DEBUG(dbgs() << "* Module " << ModName << " exports "
1145+
<< Exports.size() - NumGVS << " functions and " << NumGVS
1146+
<< " vars. Imports from " << ModuleImports.second.size()
1147+
<< " modules.\n");
11571148
for (auto &Src : ModuleImports.second) {
11581149
auto SrcModName = Src.first;
1159-
unsigned DefinedGVS = 0, DefinedFS = 0;
1150+
unsigned DefinedFS = 0;
11601151
unsigned NumGVSPerMod =
1161-
numGlobalVarSummaries(Index, Src.second, DefinedGVS, DefinedFS);
1152+
numGlobalVarSummaries(Index, Src.second, DefinedFS);
11621153
LLVM_DEBUG(dbgs() << " - " << DefinedFS << " function definitions and "
11631154
<< Src.second.size() - NumGVSPerMod - DefinedFS
11641155
<< " function declarations imported from " << SrcModName
11651156
<< "\n");
1166-
LLVM_DEBUG(dbgs() << " - " << DefinedGVS << " global vars definition and "
1167-
<< NumGVSPerMod - DefinedGVS
1168-
<< " global vars declaration imported from "
1169-
<< SrcModName << "\n");
1157+
LLVM_DEBUG(dbgs() << " - " << NumGVSPerMod
1158+
<< " global vars imported from " << SrcModName << "\n");
11701159
}
11711160
}
11721161
#endif
@@ -1180,17 +1169,14 @@ static void dumpImportListForModule(const ModuleSummaryIndex &Index,
11801169
<< ImportList.size() << " modules.\n");
11811170
for (auto &Src : ImportList) {
11821171
auto SrcModName = Src.first;
1183-
unsigned DefinedGVS = 0, DefinedFS = 0;
1184-
unsigned NumGVSPerMod =
1185-
numGlobalVarSummaries(Index, Src.second, DefinedGVS, DefinedFS);
1172+
unsigned DefinedFS = 0;
1173+
unsigned NumGVSPerMod = numGlobalVarSummaries(Index, Src.second, DefinedFS);
11861174
LLVM_DEBUG(dbgs() << " - " << DefinedFS << " function definitions and "
11871175
<< Src.second.size() - DefinedFS - NumGVSPerMod
11881176
<< " function declarations imported from " << SrcModName
11891177
<< "\n");
1190-
LLVM_DEBUG(dbgs() << " - " << DefinedGVS << " var definitions and "
1191-
<< NumGVSPerMod - DefinedGVS
1192-
<< " var declarations imported from " << SrcModName
1193-
<< "\n");
1178+
LLVM_DEBUG(dbgs() << " - " << NumGVSPerMod << " vars imported from "
1179+
<< SrcModName << "\n");
11941180
}
11951181
}
11961182
#endif

llvm/test/ThinLTO/X86/funcimport-stats.ll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
; RUN: cat %t4 | FileCheck %s
1111

1212
; CHECK: - [[NUM_FUNCS:[0-9]+]] function definitions and 0 function declarations imported from
13-
; CHECK-NEXT: - [[NUM_VARS:[0-9]+]] global vars definition and 0 global vars declaration imported from
13+
; CHECK-NEXT: - [[NUM_VARS:[0-9]+]] global vars imported from
1414

1515
; CHECK: [[NUM_FUNCS]] function-import - Number of functions imported in backend
1616
; CHECK-NEXT: [[NUM_FUNCS]] function-import - Number of functions thin link decided to import

llvm/test/Transforms/FunctionImport/funcimport.ll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ declare void @variadic_va_start(...)
167167

168168
; DUMP: Module [[M1:.*]] imports from 1 module
169169
; DUMP-NEXT: 15 function definitions and 0 function declarations imported from [[M2:.*]]
170-
; DUMP-NEXT: 4 var definitions and 0 var declarations imported from [[M2]]
170+
; DUMP-NEXT: 4 vars imported from [[M2]]
171171

172172
; DUMP: Imported 15 functions for Module [[M1]]
173173
; DUMP-NEXT: Imported 4 global variables for Module [[M1]]

0 commit comments

Comments
 (0)