Skip to content

Commit 61210d3

Browse files
committed
[Reland] [CGData] Lazy loading support for stable function map
This is an attempt to reland llvm#151660 by including a missing STL header found by a buildbot failure. 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 e3cf967 commit 61210d3

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: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
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>
25+
#include <unordered_map>
2326

2427
namespace llvm {
2528

@@ -72,11 +75,37 @@ struct StableFunctionMap {
7275
IndexOperandHashMap(std::move(IndexOperandHashMap)) {}
7376
};
7477

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

78107
/// Get the HashToFuncs map for serialization.
79-
const HashFuncsMapType &getFunctionMap() const { return HashToFuncs; }
108+
const HashFuncsMapType &getFunctionMap() const;
80109

81110
/// Get the NameToId vector for serialization.
82111
ArrayRef<std::string> getNames() const { return IdToName; }
@@ -99,6 +128,19 @@ struct StableFunctionMap {
99128
/// \returns true if there is no stable function entry.
100129
bool empty() const { return size() == 0; }
101130

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

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

134190
friend struct StableFunctionMapRecord;
135191
};

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: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
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"
2021

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

9495
void StableFunctionMap::merge(const StableFunctionMap &OtherMap) {
9596
assert(!Finalized && "Cannot merge after finalization");
97+
deserializeLazyLoadingEntries();
9698
for (auto &[Hash, Funcs] : OtherMap.HashToFuncs) {
97-
auto &ThisFuncs = HashToFuncs[Hash];
98-
for (auto &Func : Funcs) {
99+
auto &ThisFuncs = HashToFuncs[Hash].Entries;
100+
for (auto &Func : Funcs.Entries) {
99101
auto FuncNameId =
100102
getIdOrCreateForName(*OtherMap.getNameForId(Func->FunctionNameId));
101103
auto ModuleNameId =
@@ -114,25 +116,63 @@ size_t StableFunctionMap::size(SizeType Type) const {
114116
case UniqueHashCount:
115117
return HashToFuncs.size();
116118
case TotalFunctionCount: {
119+
deserializeLazyLoadingEntries();
117120
size_t Count = 0;
118121
for (auto &Funcs : HashToFuncs)
119-
Count += Funcs.second.size();
122+
Count += Funcs.second.Entries.size();
120123
return Count;
121124
}
122125
case MergeableFunctionCount: {
126+
deserializeLazyLoadingEntries();
123127
size_t Count = 0;
124128
for (auto &[Hash, Funcs] : HashToFuncs)
125-
if (Funcs.size() >= 2)
126-
Count += Funcs.size();
129+
if (Funcs.Entries.size() >= 2)
130+
Count += Funcs.Entries.size();
127131
return Count;
128132
}
129133
}
130134
llvm_unreachable("Unhandled size type");
131135
}
132136

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

@@ -159,9 +199,7 @@ static void removeIdenticalIndexPair(
159199
SF->IndexOperandHashMap->erase(Pair);
160200
}
161201

162-
static bool isProfitable(
163-
const SmallVector<std::unique_ptr<StableFunctionMap::StableFunctionEntry>>
164-
&SFS) {
202+
static bool isProfitable(const StableFunctionMap::StableFunctionEntries &SFS) {
165203
unsigned StableFunctionCount = SFS.size();
166204
if (StableFunctionCount < GlobalMergingMinMerges)
167205
return false;
@@ -202,8 +240,11 @@ static bool isProfitable(
202240
}
203241

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

208249
// Group stable functions by ModuleIdentifier.
209250
llvm::stable_sort(SFS, [&](const std::unique_ptr<StableFunctionEntry> &L,
@@ -236,7 +277,7 @@ void StableFunctionMap::finalize(bool SkipTrim) {
236277
}
237278
}
238279
if (Invalid) {
239-
HashToFuncs.erase(It);
280+
ToDelete.push_back(It);
240281
continue;
241282
}
242283

@@ -248,8 +289,10 @@ void StableFunctionMap::finalize(bool SkipTrim) {
248289
removeIdenticalIndexPair(SFS);
249290

250291
if (!isProfitable(SFS))
251-
HashToFuncs.erase(It);
292+
ToDelete.push_back(It);
252293
}
294+
for (auto It : ToDelete)
295+
HashToFuncs.erase(It);
253296

254297
Finalized = true;
255298
}

0 commit comments

Comments
 (0)