-
Notifications
You must be signed in to change notification settings - Fork 15.1k
[CGData] Lazy loading support for stable function map #151660
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 all commits
b983de3
50faf51
eae8525
adc9d32
4df6e3e
d11f8f4
f4e071d
f8e2b2a
a09da73
9cf76af
be2489f
e0b06a4
146e3b5
207f7b6
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 |
|---|---|---|
|
|
@@ -20,6 +20,8 @@ | |
| #include "llvm/ADT/StringMap.h" | ||
| #include "llvm/IR/StructuralHash.h" | ||
| #include "llvm/Support/Compiler.h" | ||
| #include "llvm/Support/MemoryBuffer.h" | ||
| #include <mutex> | ||
|
|
||
| namespace llvm { | ||
|
|
||
|
|
@@ -72,11 +74,37 @@ struct StableFunctionMap { | |
| IndexOperandHashMap(std::move(IndexOperandHashMap)) {} | ||
| }; | ||
|
|
||
| using HashFuncsMapType = | ||
| DenseMap<stable_hash, SmallVector<std::unique_ptr<StableFunctionEntry>>>; | ||
| using StableFunctionEntries = | ||
| SmallVector<std::unique_ptr<StableFunctionEntry>>; | ||
|
|
||
| /// In addition to the deserialized StableFunctionEntry, the struct stores | ||
| /// the offsets of corresponding serialized stable function entries, and a | ||
| /// once flag for safe lazy loading in a multithreaded environment. | ||
| struct EntryStorage { | ||
| /// The actual storage of deserialized stable function entries. If the map | ||
| /// is lazily loaded, this will be empty until the first access by the | ||
| /// corresponding function hash. | ||
| StableFunctionEntries Entries; | ||
|
|
||
| private: | ||
| /// This is used to deserialize the entry lazily. Each element is the | ||
| /// corresponding serialized stable function entry's offset in the memory | ||
| /// buffer (StableFunctionMap::Buffer). | ||
| /// The offsets are only populated when loading the map lazily, otherwise | ||
| /// it is empty. | ||
| SmallVector<uint64_t> Offsets; | ||
| std::once_flag LazyLoadFlag; | ||
| friend struct StableFunctionMap; | ||
| friend struct StableFunctionMapRecord; | ||
| }; | ||
|
|
||
| // Note: DenseMap requires value type to be copyable even if only using | ||
| // in-place insertion. Use STL instead. This also affects the | ||
| // deletion-while-iteration in finalize(). | ||
| using HashFuncsMapType = std::unordered_map<stable_hash, EntryStorage>; | ||
|
|
||
| /// Get the HashToFuncs map for serialization. | ||
| const HashFuncsMapType &getFunctionMap() const { return HashToFuncs; } | ||
| const HashFuncsMapType &getFunctionMap() const; | ||
|
|
||
| /// Get the NameToId vector for serialization. | ||
| ArrayRef<std::string> getNames() const { return IdToName; } | ||
|
|
@@ -99,6 +127,19 @@ struct StableFunctionMap { | |
| /// \returns true if there is no stable function entry. | ||
| bool empty() const { return size() == 0; } | ||
|
|
||
| /// \returns true if there is an entry for the given function hash. | ||
| /// This does not trigger lazy loading. | ||
| bool contains(HashFuncsMapType::key_type FunctionHash) const { | ||
| return HashToFuncs.count(FunctionHash) > 0; | ||
| } | ||
|
|
||
| /// \returns the stable function entries for the given function hash. If the | ||
| /// map is lazily loaded, it will deserialize the entries if it is not already | ||
| /// done, other requests to the same hash at the same time will be blocked | ||
| /// until the entries are deserialized. | ||
| const StableFunctionEntries & | ||
| at(HashFuncsMapType::key_type FunctionHash) const; | ||
|
|
||
| enum SizeType { | ||
| UniqueHashCount, // The number of unique hashes in HashToFuncs. | ||
| TotalFunctionCount, // The number of total functions in HashToFuncs. | ||
|
|
@@ -119,17 +160,31 @@ struct StableFunctionMap { | |
| /// `StableFunctionEntry` is ready for insertion. | ||
| void insert(std::unique_ptr<StableFunctionEntry> FuncEntry) { | ||
| assert(!Finalized && "Cannot insert after finalization"); | ||
| HashToFuncs[FuncEntry->Hash].emplace_back(std::move(FuncEntry)); | ||
| HashToFuncs[FuncEntry->Hash].Entries.emplace_back(std::move(FuncEntry)); | ||
|
||
| } | ||
|
|
||
| void deserializeLazyLoadingEntry(HashFuncsMapType::iterator It) const; | ||
|
|
||
| /// Eagerly deserialize all the unloaded entries in the lazy loading map. | ||
| void deserializeLazyLoadingEntries() const; | ||
|
|
||
| bool isLazilyLoaded() const { return (bool)Buffer; } | ||
|
|
||
| /// A map from a stable_hash to a vector of functions with that hash. | ||
| HashFuncsMapType HashToFuncs; | ||
| mutable HashFuncsMapType HashToFuncs; | ||
| /// A vector of strings to hold names. | ||
| SmallVector<std::string> IdToName; | ||
| /// A map from StringRef (name) to an ID. | ||
| StringMap<unsigned> NameToId; | ||
| /// True if the function map is finalized with minimal content. | ||
| bool Finalized = false; | ||
| /// The memory buffer that contains the serialized stable function map for | ||
| /// lazy loading. | ||
| /// Non-empty only if this StableFunctionMap is created from a MemoryBuffer | ||
| /// (i.e. by IndexedCodeGenDataReader::read()) and lazily deserialized. | ||
| std::shared_ptr<MemoryBuffer> Buffer; | ||
| /// Whether to read stable function names from the buffer. | ||
| bool ReadStableFunctionMapNames = true; | ||
|
|
||
| friend struct StableFunctionMapRecord; | ||
| }; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,8 +15,10 @@ | |
|
|
||
| #include "llvm/CGData/StableFunctionMap.h" | ||
| #include "llvm/ADT/SmallSet.h" | ||
| #include "llvm/CGData/StableFunctionMapRecord.h" | ||
| #include "llvm/Support/CommandLine.h" | ||
| #include "llvm/Support/Debug.h" | ||
| #include <mutex> | ||
|
|
||
| #define DEBUG_TYPE "stable-function-map" | ||
|
|
||
|
|
@@ -93,9 +95,10 @@ void StableFunctionMap::insert(const StableFunction &Func) { | |
|
|
||
| void StableFunctionMap::merge(const StableFunctionMap &OtherMap) { | ||
| assert(!Finalized && "Cannot merge after finalization"); | ||
| deserializeLazyLoadingEntries(); | ||
| for (auto &[Hash, Funcs] : OtherMap.HashToFuncs) { | ||
| auto &ThisFuncs = HashToFuncs[Hash]; | ||
| for (auto &Func : Funcs) { | ||
| auto &ThisFuncs = HashToFuncs[Hash].Entries; | ||
| for (auto &Func : Funcs.Entries) { | ||
| auto FuncNameId = | ||
| getIdOrCreateForName(*OtherMap.getNameForId(Func->FunctionNameId)); | ||
| auto ModuleNameId = | ||
|
|
@@ -114,25 +117,63 @@ size_t StableFunctionMap::size(SizeType Type) const { | |
| case UniqueHashCount: | ||
| return HashToFuncs.size(); | ||
| case TotalFunctionCount: { | ||
| deserializeLazyLoadingEntries(); | ||
| size_t Count = 0; | ||
| for (auto &Funcs : HashToFuncs) | ||
| Count += Funcs.second.size(); | ||
| Count += Funcs.second.Entries.size(); | ||
| return Count; | ||
| } | ||
| case MergeableFunctionCount: { | ||
| deserializeLazyLoadingEntries(); | ||
| size_t Count = 0; | ||
| for (auto &[Hash, Funcs] : HashToFuncs) | ||
| if (Funcs.size() >= 2) | ||
| Count += Funcs.size(); | ||
| if (Funcs.Entries.size() >= 2) | ||
| Count += Funcs.Entries.size(); | ||
| return Count; | ||
| } | ||
| } | ||
| llvm_unreachable("Unhandled size type"); | ||
| } | ||
|
|
||
| const StableFunctionMap::StableFunctionEntries & | ||
| StableFunctionMap::at(HashFuncsMapType::key_type FunctionHash) const { | ||
| auto It = HashToFuncs.find(FunctionHash); | ||
| if (isLazilyLoaded()) | ||
| deserializeLazyLoadingEntry(It); | ||
| return It->second.Entries; | ||
| } | ||
|
|
||
| void StableFunctionMap::deserializeLazyLoadingEntry( | ||
| HashFuncsMapType::iterator It) const { | ||
| assert(isLazilyLoaded() && "Cannot deserialize non-lazily-loaded map"); | ||
| auto &[Hash, Storage] = *It; | ||
| std::call_once(Storage.LazyLoadFlag, | ||
| [this, HashArg = Hash, &StorageArg = Storage]() { | ||
| for (auto Offset : StorageArg.Offsets) | ||
| StableFunctionMapRecord::deserializeEntry( | ||
| reinterpret_cast<const unsigned char *>(Offset), | ||
| HashArg, const_cast<StableFunctionMap *>(this)); | ||
|
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. Is this const_cast still necessary? 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.
Edit: I found a way to eliminate 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. Yeah I agree that would be weird |
||
| }); | ||
| } | ||
|
|
||
| void StableFunctionMap::deserializeLazyLoadingEntries() const { | ||
| if (!isLazilyLoaded()) | ||
| return; | ||
| for (auto It = HashToFuncs.begin(); It != HashToFuncs.end(); ++It) | ||
| deserializeLazyLoadingEntry(It); | ||
| } | ||
|
|
||
| const StableFunctionMap::HashFuncsMapType & | ||
| StableFunctionMap::getFunctionMap() const { | ||
| // Ensure all entries are deserialized before returning the raw map. | ||
| if (isLazilyLoaded()) | ||
| deserializeLazyLoadingEntries(); | ||
| return HashToFuncs; | ||
| } | ||
|
|
||
| using ParamLocs = SmallVector<IndexPair>; | ||
| static void removeIdenticalIndexPair( | ||
| SmallVector<std::unique_ptr<StableFunctionMap::StableFunctionEntry>> &SFS) { | ||
| static void | ||
| removeIdenticalIndexPair(StableFunctionMap::StableFunctionEntries &SFS) { | ||
| auto &RSF = SFS[0]; | ||
| unsigned StableFunctionCount = SFS.size(); | ||
|
|
||
|
|
@@ -159,9 +200,7 @@ static void removeIdenticalIndexPair( | |
| SF->IndexOperandHashMap->erase(Pair); | ||
| } | ||
|
|
||
| static bool isProfitable( | ||
| const SmallVector<std::unique_ptr<StableFunctionMap::StableFunctionEntry>> | ||
| &SFS) { | ||
| static bool isProfitable(const StableFunctionMap::StableFunctionEntries &SFS) { | ||
| unsigned StableFunctionCount = SFS.size(); | ||
| if (StableFunctionCount < GlobalMergingMinMerges) | ||
| return false; | ||
|
|
@@ -202,8 +241,11 @@ static bool isProfitable( | |
| } | ||
|
|
||
| void StableFunctionMap::finalize(bool SkipTrim) { | ||
| deserializeLazyLoadingEntries(); | ||
| SmallVector<HashFuncsMapType::iterator> ToDelete; | ||
| for (auto It = HashToFuncs.begin(); It != HashToFuncs.end(); ++It) { | ||
|
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. Can we use 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. With 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. Ah that makes sense. Thanks for checking. |
||
| auto &[StableHash, SFS] = *It; | ||
| auto &[StableHash, Storage] = *It; | ||
| auto &SFS = Storage.Entries; | ||
|
|
||
| // Group stable functions by ModuleIdentifier. | ||
| llvm::stable_sort(SFS, [&](const std::unique_ptr<StableFunctionEntry> &L, | ||
|
|
@@ -236,7 +278,7 @@ void StableFunctionMap::finalize(bool SkipTrim) { | |
| } | ||
| } | ||
| if (Invalid) { | ||
| HashToFuncs.erase(It); | ||
| ToDelete.push_back(It); | ||
| continue; | ||
| } | ||
|
|
||
|
|
@@ -248,8 +290,10 @@ void StableFunctionMap::finalize(bool SkipTrim) { | |
| removeIdenticalIndexPair(SFS); | ||
|
|
||
| if (!isProfitable(SFS)) | ||
| HashToFuncs.erase(It); | ||
| ToDelete.push_back(It); | ||
| } | ||
| for (auto It : ToDelete) | ||
| HashToFuncs.erase(It); | ||
|
|
||
| Finalized = true; | ||
| } | ||
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.
Add a comment with ///.