Skip to content
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

[LTO] Turn ImportListsTy into a proper class (NFC) #106427

Conversation

kazutakahirata
Copy link
Contributor

This patch turns ImportListsTy into a class that wraps
DenseMap<StringRef, ImportMapTy>.

Here is the background. I'm planning to reduce the memory footprint
of ThinLTO indexing. Specifically, ImportMapTy, the list of imports
for a given destination module, will be a hash set of integer IDs
indexing into a deduplication table of pairs (SourceModule, GUID),
which is a lot like string interning. I'm planning to put this
deduplication table as part of ImportListsTy and have each instance of
ImportMapTy hold a reference to the deduplication table.

Another reason to wrap the DenseMap is that I need to intercept
operator so that I can construct an instance of ImportMapTy with a
reference to the deduplication table. Note that the default
implementation of operator would default-construct ImportMapTy,
which I am going to disable.

This patch turns ImportListsTy into a class that wraps
DenseMap<StringRef, ImportMapTy>.

Here is the background.  I'm planning to reduce the memory footprint
of ThinLTO indexing.  Specifically, ImportMapTy, the list of imports
for a given destination module, will be a hash set of integer IDs
indexing into a deduplication table of pairs (SourceModule, GUID),
which is a lot like string interning.  I'm planning to put this
deduplication table as part of ImportListsTy and have each instance of
ImportMapTy hold a reference to the deduplication table.

Another reason to wrap the DenseMap is that I need to intercept
operator[]() so that I can construct an instance of ImportMapTy with a
reference to the deduplication table.  Note that the default
implementation of operator[]() would default-construct ImportMapTy,
which I am going to disable.
@llvmbot
Copy link
Collaborator

llvmbot commented Aug 28, 2024

@llvm/pr-subscribers-llvm-transforms

Author: Kazu Hirata (kazutakahirata)

Changes

This patch turns ImportListsTy into a class that wraps
DenseMap<StringRef, ImportMapTy>.

Here is the background. I'm planning to reduce the memory footprint
of ThinLTO indexing. Specifically, ImportMapTy, the list of imports
for a given destination module, will be a hash set of integer IDs
indexing into a deduplication table of pairs (SourceModule, GUID),
which is a lot like string interning. I'm planning to put this
deduplication table as part of ImportListsTy and have each instance of
ImportMapTy hold a reference to the deduplication table.

Another reason to wrap the DenseMap is that I need to intercept
operator so that I can construct an instance of ImportMapTy with a
reference to the deduplication table. Note that the default
implementation of operator would default-construct ImportMapTy,
which I am going to disable.


Full diff: https://github.com/llvm/llvm-project/pull/106427.diff

1 Files Affected:

  • (modified) llvm/include/llvm/Transforms/IPO/FunctionImport.h (+18-1)
diff --git a/llvm/include/llvm/Transforms/IPO/FunctionImport.h b/llvm/include/llvm/Transforms/IPO/FunctionImport.h
index c06d96bbe62e22..78932c12e76ff8 100644
--- a/llvm/include/llvm/Transforms/IPO/FunctionImport.h
+++ b/llvm/include/llvm/Transforms/IPO/FunctionImport.h
@@ -150,7 +150,24 @@ class FunctionImporter {
   };
 
   // A map from destination modules to lists of imports.
-  using ImportListsTy = DenseMap<StringRef, ImportMapTy>;
+  class ImportListsTy {
+  public:
+    ImportListsTy() = default;
+    ImportListsTy(size_t Size) : ListsImpl(Size) {}
+
+    ImportMapTy &operator[](StringRef DestMod) {
+      return ListsImpl.try_emplace(DestMod).first->second;
+    }
+
+    size_t size() const { return ListsImpl.size(); }
+
+    using const_iterator = DenseMap<StringRef, ImportMapTy>::const_iterator;
+    const_iterator begin() const { return ListsImpl.begin(); }
+    const_iterator end() const { return ListsImpl.end(); }
+
+  private:
+    DenseMap<StringRef, ImportMapTy> ListsImpl;
+  };
 
   /// The set contains an entry for every global value that the module exports.
   /// Depending on the user context, this container is allowed to contain

@kazutakahirata kazutakahirata merged commit e61d606 into llvm:main Aug 28, 2024
8 of 10 checks passed
@kazutakahirata kazutakahirata deleted the cleanup_thinlto_ImportMapTy_ImportListsTy_class branch August 28, 2024 20:06
5c4lar pushed a commit to 5c4lar/llvm-project that referenced this pull request Aug 29, 2024
This patch turns ImportListsTy into a class that wraps
DenseMap<StringRef, ImportMapTy>.

Here is the background.  I'm planning to reduce the memory footprint
of ThinLTO indexing.  Specifically, ImportMapTy, the list of imports
for a given destination module, will be a hash set of integer IDs
indexing into a deduplication table of pairs (SourceModule, GUID),
which is a lot like string interning.  I'm planning to put this
deduplication table as part of ImportListsTy and have each instance of
ImportMapTy hold a reference to the deduplication table.

Another reason to wrap the DenseMap is that I need to intercept
operator[]() so that I can construct an instance of ImportMapTy with a
reference to the deduplication table.  Note that the default
implementation of operator[]() would default-construct ImportMapTy,
which I am going to disable.
@nikic
Copy link
Contributor

nikic commented Sep 17, 2024

We've encountered an issue when integrating this change into Rust. The problem is that this commit removed the possibility to fetch the import list without potentially modifying the DenseMap. Rust can read the ImportLists concurrently from multiple threads, and this may result in crashes, as reported at rust-lang/rust#129749 (comment). At least that's my current theory.

Rust was previously using lookup() instead of operator[]. Would it be possible to restore some form of read-only access to this data structure, whether via lookup or some other method?

@kazutakahirata
Copy link
Contributor Author

We've encountered an issue when integrating this change into Rust. The problem is that this commit removed the possibility to fetch the import list without potentially modifying the DenseMap. Rust can read the ImportLists concurrently from multiple threads, and this may result in crashes, as reported at rust-lang/rust#129749 (comment). At least that's my current theory.

Rust was previously using lookup() instead of operator[]. Would it be possible to restore some form of read-only access to this data structure, whether via lookup or some other method?

So, are there two problems here?

  • Preserving const for the import list if we are accessing it in a read-only manner
  • Crashes from concurrent accesses

I'm happy to switch to lookup or find to preserve const, but I don't see how that fixes crashes. In LLVM, we build the import lists for different destination modules in a single thread and then we do actual imports in parallel. In Rust, do you do something a bit different like building import lists in parallel?

matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Sep 17, 2024
Revert rust-lang#129749 to fix segfault in LLVM

This reverts commit 8c7a7e3, reversing changes made to a00bd75.

Reported in rust-lang#129749 (comment). `@nikic's` theory is that the LLVM API changed in a way that makes it impossible to use concurrently from multiple threads (llvm/llvm-project#106427 (comment)). I pinged `@krasimirgg` who was fine with reverting.

r? `@rust-lang/wg-llvm`
@nikic
Copy link
Contributor

nikic commented Sep 17, 2024

I'm happy to switch to lookup or find to preserve const, but I don't see how that fixes crashes. In LLVM, we build the import lists for different destination modules in a single thread and then we do actual imports in parallel. In Rust, do you do something a bit different like building import lists in parallel?

It's the same in Rust. It's just that previously the part doing the concurrent accesses was using read-only accesses, while it's now doing potential read-write accesses. From an API perspective this is definitely wrong.

Of course, it should only actually be an issue if one of the module identifiers is not part of ImportLists when doing the actual imports, so maybe something is wrong on the Rust side...

@kazutakahirata
Copy link
Contributor Author

I'm happy to switch to lookup or find to preserve const, but I don't see how that fixes crashes. In LLVM, we build the import lists for different destination modules in a single thread and then we do actual imports in parallel. In Rust, do you do something a bit different like building import lists in parallel?

It's the same in Rust. It's just that previously the part doing the concurrent accesses was using read-only accesses, while it's now doing potential read-write accesses. From an API perspective this is definitely wrong.

Of course, it should only actually be an issue if one of the module identifiers is not part of ImportLists when doing the actual imports, so maybe something is wrong on the Rust side...

I see. I guess it's still handy to have a read-only access to ImportLits. I've posted #109036. Please take a look. Thanks!

rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Sep 17, 2024
Rollup merge of rust-lang#130477 - tmandry:revert-llvm-20-lto, r=tmandry

Revert rust-lang#129749 to fix segfault in LLVM

This reverts commit 8c7a7e3, reversing changes made to a00bd75.

Reported in rust-lang#129749 (comment). `@nikic's` theory is that the LLVM API changed in a way that makes it impossible to use concurrently from multiple threads (llvm/llvm-project#106427 (comment)). I pinged `@krasimirgg` who was fine with reverting.

r? `@rust-lang/wg-llvm`
@nikic
Copy link
Contributor

nikic commented Sep 18, 2024

After some further investigation, this issue occurs when empty or mostly empty (e.g. module inline asm only) modules are part of the thin link.

And the reason why LLVM did not hit this problem is this code:

// Make sure that every module has an entry in the ExportLists, ImportList,
// GVSummary and ResolvedODR maps to enable threaded access to these maps
// below.
for (auto &Module : Modules) {
auto ModuleIdentifier = Module->getName();
ExportLists[ModuleIdentifier];
ImportLists[ModuleIdentifier];
ResolvedODR[ModuleIdentifier];
ModuleToDefinedGVSummaries[ModuleIdentifier];
}

So LLVM's solution here was to force-initialize all map entries, while Rust's solution was to use read-only access to the maps.

@teresajohnson
Copy link
Contributor

After some further investigation, this issue occurs when empty or mostly empty (e.g. module inline asm only) modules are part of the thin link.

And the reason why LLVM did not hit this problem is this code:

// Make sure that every module has an entry in the ExportLists, ImportList,
// GVSummary and ResolvedODR maps to enable threaded access to these maps
// below.
for (auto &Module : Modules) {
auto ModuleIdentifier = Module->getName();
ExportLists[ModuleIdentifier];
ImportLists[ModuleIdentifier];
ResolvedODR[ModuleIdentifier];
ModuleToDefinedGVSummaries[ModuleIdentifier];
}

That is the old LTO API but fyi the new LTO API (used by lld and gold) uses the same approach:

// Create entries for any modules that didn't have any GV summaries
// (either they didn't have any GVs to start with, or we suppressed
// generation of the summaries because they e.g. had inline assembly
// uses that couldn't be promoted/renamed on export). This is so
// InProcessThinBackend::start can still launch a backend thread, which
// is passed the map of summaries for the module, without any special
// handling for this case.
for (auto &Mod : ThinLTO.ModuleMap)
if (!ModuleToDefinedGVSummaries.count(Mod.first))
ModuleToDefinedGVSummaries.try_emplace(Mod.first);

So LLVM's solution here was to force-initialize all map entries, while Rust's solution was to use read-only access to the maps.

RalfJung pushed a commit to RalfJung/miri that referenced this pull request Sep 21, 2024
Revert #129749 to fix segfault in LLVM

This reverts commit 8c7a7e346be4cdf13e77ab4acbfb5ade819a4e60, reversing changes made to a00bd75b6c5c96d0a35afa2dc07ce3155112d278.

Reported in rust-lang/rust#129749 (comment). `@nikic's` theory is that the LLVM API changed in a way that makes it impossible to use concurrently from multiple threads (llvm/llvm-project#106427 (comment)). I pinged `@krasimirgg` who was fine with reverting.

r? `@rust-lang/wg-llvm`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants