Skip to content

Commit 76dd742

Browse files
authored
[CGData] Lazy loading support for stable function map (#151660)
The stable function map could be huge for a large application. Fully loading it is slow and consumes a significant amount of memory, which is unnecessary and drastically slows down compilation especially for non-LTO and distributed-ThinLTO setups. This patch introduces an opt-in lazy loading support for the stable function map. The detailed changes are: - `StableFunctionMap` - The map now stores entries in an `EntryStorage` struct, which includes offsets for serialized entries and a `std::once_flag` for thread-safe lazy loading. - The underlying map type is changed from `DenseMap` to `std::unordered_map` for compatibility with `std::once_flag`. - `contains()`, `size()` and `at()` are implemented to only load requested entries on demand. - Lazy Loading Mechanism - When reading indexed codegen data, if the newly-introduced `-indexed-codegen-data-lazy-loading` flag is set, the stable function map is not fully deserialized up front. The binary format for the stable function map now includes offsets and sizes to support lazy loading. - The safety of lazy loading is guarded by the once flag per function hash. This guarantees that even in a multi-threaded environment, the deserialization for a given function hash will happen exactly once. The first thread to request it performs the load, and subsequent threads will wait for it to complete before using the data. For single-threaded builds, the overhead is negligible (a single check on the once flag). For multi-threaded scenarios, users can omit the flag to retain the previous eager-loading behavior.
1 parent bad3df4 commit 76dd742

20 files changed

+327
-86
lines changed

llvm/include/llvm/CGData/CodeGenData.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,9 @@ enum CGDataVersion {
285285
// Version 3 adds the total size of the Names in the stable function map so
286286
// we can skip reading them into the memory for non-assertion builds.
287287
Version3 = 3,
288+
// Version 4 adjusts the structure of stable function merging map for
289+
// efficient lazy loading support.
290+
Version4 = 4,
288291
CurrentVersion = CG_DATA_INDEX_VERSION
289292
};
290293
const uint64_t Version = CGDataVersion::CurrentVersion;

llvm/include/llvm/CGData/CodeGenData.inc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,4 @@ CG_DATA_SECT_ENTRY(CG_merge, CG_DATA_QUOTE(CG_DATA_MERGE_COMMON),
4949
#endif
5050

5151
/* Indexed codegen data format version (start from 1). */
52-
#define CG_DATA_INDEX_VERSION 3
52+
#define CG_DATA_INDEX_VERSION 4

llvm/include/llvm/CGData/StableFunctionMap.h

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
#include "llvm/ADT/StringMap.h"
2121
#include "llvm/IR/StructuralHash.h"
2222
#include "llvm/Support/Compiler.h"
23+
#include "llvm/Support/MemoryBuffer.h"
24+
#include <mutex>
2325

2426
namespace llvm {
2527

@@ -72,11 +74,37 @@ struct StableFunctionMap {
7274
IndexOperandHashMap(std::move(IndexOperandHashMap)) {}
7375
};
7476

75-
using HashFuncsMapType =
76-
DenseMap<stable_hash, SmallVector<std::unique_ptr<StableFunctionEntry>>>;
77+
using StableFunctionEntries =
78+
SmallVector<std::unique_ptr<StableFunctionEntry>>;
79+
80+
/// In addition to the deserialized StableFunctionEntry, the struct stores
81+
/// the offsets of corresponding serialized stable function entries, and a
82+
/// once flag for safe lazy loading in a multithreaded environment.
83+
struct EntryStorage {
84+
/// The actual storage of deserialized stable function entries. If the map
85+
/// is lazily loaded, this will be empty until the first access by the
86+
/// corresponding function hash.
87+
StableFunctionEntries Entries;
88+
89+
private:
90+
/// This is used to deserialize the entry lazily. Each element is the
91+
/// corresponding serialized stable function entry's offset in the memory
92+
/// buffer (StableFunctionMap::Buffer).
93+
/// The offsets are only populated when loading the map lazily, otherwise
94+
/// it is empty.
95+
SmallVector<uint64_t> Offsets;
96+
std::once_flag LazyLoadFlag;
97+
friend struct StableFunctionMap;
98+
friend struct StableFunctionMapRecord;
99+
};
100+
101+
// Note: DenseMap requires value type to be copyable even if only using
102+
// in-place insertion. Use STL instead. This also affects the
103+
// deletion-while-iteration in finalize().
104+
using HashFuncsMapType = std::unordered_map<stable_hash, EntryStorage>;
77105

78106
/// Get the HashToFuncs map for serialization.
79-
const HashFuncsMapType &getFunctionMap() const { return HashToFuncs; }
107+
const HashFuncsMapType &getFunctionMap() const;
80108

81109
/// Get the NameToId vector for serialization.
82110
ArrayRef<std::string> getNames() const { return IdToName; }
@@ -99,6 +127,19 @@ struct StableFunctionMap {
99127
/// \returns true if there is no stable function entry.
100128
bool empty() const { return size() == 0; }
101129

130+
/// \returns true if there is an entry for the given function hash.
131+
/// This does not trigger lazy loading.
132+
bool contains(HashFuncsMapType::key_type FunctionHash) const {
133+
return HashToFuncs.count(FunctionHash) > 0;
134+
}
135+
136+
/// \returns the stable function entries for the given function hash. If the
137+
/// map is lazily loaded, it will deserialize the entries if it is not already
138+
/// done, other requests to the same hash at the same time will be blocked
139+
/// until the entries are deserialized.
140+
const StableFunctionEntries &
141+
at(HashFuncsMapType::key_type FunctionHash) const;
142+
102143
enum SizeType {
103144
UniqueHashCount, // The number of unique hashes in HashToFuncs.
104145
TotalFunctionCount, // The number of total functions in HashToFuncs.
@@ -119,17 +160,31 @@ struct StableFunctionMap {
119160
/// `StableFunctionEntry` is ready for insertion.
120161
void insert(std::unique_ptr<StableFunctionEntry> FuncEntry) {
121162
assert(!Finalized && "Cannot insert after finalization");
122-
HashToFuncs[FuncEntry->Hash].emplace_back(std::move(FuncEntry));
163+
HashToFuncs[FuncEntry->Hash].Entries.emplace_back(std::move(FuncEntry));
123164
}
124165

166+
void deserializeLazyLoadingEntry(HashFuncsMapType::iterator It) const;
167+
168+
/// Eagerly deserialize all the unloaded entries in the lazy loading map.
169+
void deserializeLazyLoadingEntries() const;
170+
171+
bool isLazilyLoaded() const { return (bool)Buffer; }
172+
125173
/// A map from a stable_hash to a vector of functions with that hash.
126-
HashFuncsMapType HashToFuncs;
174+
mutable HashFuncsMapType HashToFuncs;
127175
/// A vector of strings to hold names.
128176
SmallVector<std::string> IdToName;
129177
/// A map from StringRef (name) to an ID.
130178
StringMap<unsigned> NameToId;
131179
/// True if the function map is finalized with minimal content.
132180
bool Finalized = false;
181+
/// The memory buffer that contains the serialized stable function map for
182+
/// lazy loading.
183+
/// Non-empty only if this StableFunctionMap is created from a MemoryBuffer
184+
/// (i.e. by IndexedCodeGenDataReader::read()) and lazily deserialized.
185+
std::shared_ptr<MemoryBuffer> Buffer;
186+
/// Whether to read stable function names from the buffer.
187+
bool ReadStableFunctionMapNames = true;
133188

134189
friend struct StableFunctionMapRecord;
135190
};

llvm/include/llvm/CGData/StableFunctionMapRecord.h

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,26 @@
2424

2525
namespace llvm {
2626

27+
/// The structure of the serialized stable function map is as follows:
28+
/// - Number of unique function/module names
29+
/// - Total size of unique function/module names for opt-in skipping
30+
/// - Unique function/module names
31+
/// - Padding to align to 4 bytes
32+
/// - Number of StableFunctionEntries
33+
/// - Hashes of each StableFunctionEntry
34+
/// - Fixed-size fields for each StableFunctionEntry (the order is consistent
35+
/// with the hashes above):
36+
/// - FunctionNameId
37+
/// - ModuleNameId
38+
/// - InstCount
39+
/// - Relative offset to the beginning of IndexOperandHashes for this entry
40+
/// - Total size of variable-sized IndexOperandHashes for lazy-loading support
41+
/// - Variable-sized IndexOperandHashes for each StableFunctionEntry:
42+
/// - Number of IndexOperandHashes
43+
/// - Contents of each IndexOperandHashes
44+
/// - InstIndex
45+
/// - OpndIndex
46+
/// - OpndHash
2747
struct StableFunctionMapRecord {
2848
std::unique_ptr<StableFunctionMap> FunctionMap;
2949

@@ -40,13 +60,25 @@ struct StableFunctionMapRecord {
4060
const StableFunctionMap *FunctionMap,
4161
std::vector<CGDataPatchItem> &PatchItems);
4262

63+
/// A static helper function to deserialize the stable function map entry.
64+
/// Ptr should be pointing to the start of the fixed-sized fields of the
65+
/// entry when passed in.
66+
LLVM_ABI static void deserializeEntry(const unsigned char *Ptr,
67+
stable_hash Hash,
68+
StableFunctionMap *FunctionMap);
69+
4370
/// Serialize the stable function map to a raw_ostream.
4471
LLVM_ABI void serialize(raw_ostream &OS,
4572
std::vector<CGDataPatchItem> &PatchItems) const;
4673

4774
/// Deserialize the stable function map from a raw_ostream.
48-
LLVM_ABI void deserialize(const unsigned char *&Ptr,
49-
bool ReadStableFunctionMapNames = true);
75+
LLVM_ABI void deserialize(const unsigned char *&Ptr);
76+
77+
/// Lazily deserialize the stable function map from `Buffer` starting at
78+
/// `Offset`. The individual stable function entry would be read lazily from
79+
/// `Buffer` when the function map is accessed.
80+
LLVM_ABI void lazyDeserialize(std::shared_ptr<MemoryBuffer> Buffer,
81+
uint64_t Offset);
5082

5183
/// Serialize the stable function map to a YAML stream.
5284
LLVM_ABI void serializeYAML(yaml::Output &YOS) const;
@@ -70,6 +102,18 @@ struct StableFunctionMapRecord {
70102
yaml::Output YOS(OS);
71103
serializeYAML(YOS);
72104
}
105+
106+
/// Set whether to read stable function names from the buffer.
107+
/// Has no effect if the function map is read from a YAML stream.
108+
void setReadStableFunctionMapNames(bool Read) {
109+
assert(
110+
FunctionMap->empty() &&
111+
"Cannot change ReadStableFunctionMapNames after the map is populated");
112+
FunctionMap->ReadStableFunctionMapNames = Read;
113+
}
114+
115+
private:
116+
void deserialize(const unsigned char *&Ptr, bool Lazy);
73117
};
74118

75119
} // namespace llvm

llvm/lib/CGData/CodeGenData.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ Expected<Header> Header::readFromBuffer(const unsigned char *Curr) {
186186
return make_error<CGDataError>(cgdata_error::unsupported_version);
187187
H.DataKind = endian::readNext<uint32_t, endianness::little, unaligned>(Curr);
188188

189-
static_assert(IndexedCGData::CGDataVersion::CurrentVersion == Version3,
189+
static_assert(IndexedCGData::CGDataVersion::CurrentVersion == Version4,
190190
"Please update the offset computation below if a new field has "
191191
"been added to the header.");
192192
H.OutlinedHashTreeOffset =

llvm/lib/CGData/CodeGenDataReader.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ static cl::opt<bool> IndexedCodeGenDataReadFunctionMapNames(
2626
"disabled to save memory and time for final consumption of the "
2727
"indexed CodeGenData in production."));
2828

29+
cl::opt<bool> IndexedCodeGenDataLazyLoading(
30+
"indexed-codegen-data-lazy-loading", cl::init(false), cl::Hidden,
31+
cl::desc(
32+
"Lazily load indexed CodeGenData. Enable to save memory and time "
33+
"for final consumption of the indexed CodeGenData in production."));
34+
2935
namespace llvm {
3036

3137
static Expected<std::unique_ptr<MemoryBuffer>>
@@ -109,11 +115,20 @@ Error IndexedCodeGenDataReader::read() {
109115
return error(cgdata_error::eof);
110116
HashTreeRecord.deserialize(Ptr);
111117
}
118+
119+
// TODO: lazy loading support for outlined hash tree.
120+
std::shared_ptr<MemoryBuffer> SharedDataBuffer = std::move(DataBuffer);
112121
if (hasStableFunctionMap()) {
113122
const unsigned char *Ptr = Start + Header.StableFunctionMapOffset;
114123
if (Ptr >= End)
115124
return error(cgdata_error::eof);
116-
FunctionMapRecord.deserialize(Ptr, IndexedCodeGenDataReadFunctionMapNames);
125+
FunctionMapRecord.setReadStableFunctionMapNames(
126+
IndexedCodeGenDataReadFunctionMapNames);
127+
if (IndexedCodeGenDataLazyLoading)
128+
FunctionMapRecord.lazyDeserialize(SharedDataBuffer,
129+
Header.StableFunctionMapOffset);
130+
else
131+
FunctionMapRecord.deserialize(Ptr);
117132
}
118133

119134
return success();

llvm/lib/CGData/StableFunctionMap.cpp

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515

1616
#include "llvm/CGData/StableFunctionMap.h"
1717
#include "llvm/ADT/SmallSet.h"
18+
#include "llvm/CGData/StableFunctionMapRecord.h"
1819
#include "llvm/Support/CommandLine.h"
1920
#include "llvm/Support/Debug.h"
21+
#include <mutex>
2022

2123
#define DEBUG_TYPE "stable-function-map"
2224

@@ -93,9 +95,10 @@ void StableFunctionMap::insert(const StableFunction &Func) {
9395

9496
void StableFunctionMap::merge(const StableFunctionMap &OtherMap) {
9597
assert(!Finalized && "Cannot merge after finalization");
98+
deserializeLazyLoadingEntries();
9699
for (auto &[Hash, Funcs] : OtherMap.HashToFuncs) {
97-
auto &ThisFuncs = HashToFuncs[Hash];
98-
for (auto &Func : Funcs) {
100+
auto &ThisFuncs = HashToFuncs[Hash].Entries;
101+
for (auto &Func : Funcs.Entries) {
99102
auto FuncNameId =
100103
getIdOrCreateForName(*OtherMap.getNameForId(Func->FunctionNameId));
101104
auto ModuleNameId =
@@ -114,25 +117,63 @@ size_t StableFunctionMap::size(SizeType Type) const {
114117
case UniqueHashCount:
115118
return HashToFuncs.size();
116119
case TotalFunctionCount: {
120+
deserializeLazyLoadingEntries();
117121
size_t Count = 0;
118122
for (auto &Funcs : HashToFuncs)
119-
Count += Funcs.second.size();
123+
Count += Funcs.second.Entries.size();
120124
return Count;
121125
}
122126
case MergeableFunctionCount: {
127+
deserializeLazyLoadingEntries();
123128
size_t Count = 0;
124129
for (auto &[Hash, Funcs] : HashToFuncs)
125-
if (Funcs.size() >= 2)
126-
Count += Funcs.size();
130+
if (Funcs.Entries.size() >= 2)
131+
Count += Funcs.Entries.size();
127132
return Count;
128133
}
129134
}
130135
llvm_unreachable("Unhandled size type");
131136
}
132137

138+
const StableFunctionMap::StableFunctionEntries &
139+
StableFunctionMap::at(HashFuncsMapType::key_type FunctionHash) const {
140+
auto It = HashToFuncs.find(FunctionHash);
141+
if (isLazilyLoaded())
142+
deserializeLazyLoadingEntry(It);
143+
return It->second.Entries;
144+
}
145+
146+
void StableFunctionMap::deserializeLazyLoadingEntry(
147+
HashFuncsMapType::iterator It) const {
148+
assert(isLazilyLoaded() && "Cannot deserialize non-lazily-loaded map");
149+
auto &[Hash, Storage] = *It;
150+
std::call_once(Storage.LazyLoadFlag,
151+
[this, HashArg = Hash, &StorageArg = Storage]() {
152+
for (auto Offset : StorageArg.Offsets)
153+
StableFunctionMapRecord::deserializeEntry(
154+
reinterpret_cast<const unsigned char *>(Offset),
155+
HashArg, const_cast<StableFunctionMap *>(this));
156+
});
157+
}
158+
159+
void StableFunctionMap::deserializeLazyLoadingEntries() const {
160+
if (!isLazilyLoaded())
161+
return;
162+
for (auto It = HashToFuncs.begin(); It != HashToFuncs.end(); ++It)
163+
deserializeLazyLoadingEntry(It);
164+
}
165+
166+
const StableFunctionMap::HashFuncsMapType &
167+
StableFunctionMap::getFunctionMap() const {
168+
// Ensure all entries are deserialized before returning the raw map.
169+
if (isLazilyLoaded())
170+
deserializeLazyLoadingEntries();
171+
return HashToFuncs;
172+
}
173+
133174
using ParamLocs = SmallVector<IndexPair>;
134-
static void removeIdenticalIndexPair(
135-
SmallVector<std::unique_ptr<StableFunctionMap::StableFunctionEntry>> &SFS) {
175+
static void
176+
removeIdenticalIndexPair(StableFunctionMap::StableFunctionEntries &SFS) {
136177
auto &RSF = SFS[0];
137178
unsigned StableFunctionCount = SFS.size();
138179

@@ -159,9 +200,7 @@ static void removeIdenticalIndexPair(
159200
SF->IndexOperandHashMap->erase(Pair);
160201
}
161202

162-
static bool isProfitable(
163-
const SmallVector<std::unique_ptr<StableFunctionMap::StableFunctionEntry>>
164-
&SFS) {
203+
static bool isProfitable(const StableFunctionMap::StableFunctionEntries &SFS) {
165204
unsigned StableFunctionCount = SFS.size();
166205
if (StableFunctionCount < GlobalMergingMinMerges)
167206
return false;
@@ -202,8 +241,11 @@ static bool isProfitable(
202241
}
203242

204243
void StableFunctionMap::finalize(bool SkipTrim) {
244+
deserializeLazyLoadingEntries();
245+
SmallVector<HashFuncsMapType::iterator> ToDelete;
205246
for (auto It = HashToFuncs.begin(); It != HashToFuncs.end(); ++It) {
206-
auto &[StableHash, SFS] = *It;
247+
auto &[StableHash, Storage] = *It;
248+
auto &SFS = Storage.Entries;
207249

208250
// Group stable functions by ModuleIdentifier.
209251
llvm::stable_sort(SFS, [&](const std::unique_ptr<StableFunctionEntry> &L,
@@ -236,7 +278,7 @@ void StableFunctionMap::finalize(bool SkipTrim) {
236278
}
237279
}
238280
if (Invalid) {
239-
HashToFuncs.erase(It);
281+
ToDelete.push_back(It);
240282
continue;
241283
}
242284

@@ -248,8 +290,10 @@ void StableFunctionMap::finalize(bool SkipTrim) {
248290
removeIdenticalIndexPair(SFS);
249291

250292
if (!isProfitable(SFS))
251-
HashToFuncs.erase(It);
293+
ToDelete.push_back(It);
252294
}
295+
for (auto It : ToDelete)
296+
HashToFuncs.erase(It);
253297

254298
Finalized = true;
255299
}

0 commit comments

Comments
 (0)