Skip to content

Conversation

vitalybuka
Copy link
Collaborator

@vitalybuka vitalybuka commented Sep 5, 2025

After #146020 is_contained does linear search trough
all previously inserted operands.

On large binaries it can take up to 30% for ThinLTO linking with CFI, which
uses long lists of cfi.functions metadata.

Created using spr 1.3.6
@llvmbot llvmbot added the LTO Link time optimization (regular/full LTO or ThinLTO) label Sep 5, 2025
@llvmbot
Copy link
Member

llvmbot commented Sep 5, 2025

@llvm/pr-subscribers-lto

Author: Vitaly Buka (vitalybuka)

Changes

is_contained does linear search trough
all previously inserted operands.

On large binaries it can take up to 30%
for ThinLTO linking with CFI, which has
large cfi.function metadata.


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

1 Files Affected:

  • (modified) llvm/lib/Linker/IRMover.cpp (+13-1)
diff --git a/llvm/lib/Linker/IRMover.cpp b/llvm/lib/Linker/IRMover.cpp
index d6c15de4c4cdc..8903f6e9775d8 100644
--- a/llvm/lib/Linker/IRMover.cpp
+++ b/llvm/lib/Linker/IRMover.cpp
@@ -8,6 +8,7 @@
 
 #include "llvm/Linker/IRMover.h"
 #include "LinkDiagnosticInfo.h"
+#include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/SetVector.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/IR/AutoUpgrade.h"
@@ -290,6 +291,9 @@ class IRLinker {
   Module &DstM;
   std::unique_ptr<Module> SrcM;
 
+  // Lookup table to optimize IRMover::linkNamedMDNodes().
+  DenseMap<StringRef, DenseSet<MDNode *>> NamedMDNodes;
+
   /// See IRMover::move().
   IRMover::LazyCallback AddLazyFor;
 
@@ -1132,12 +1136,20 @@ void IRLinker::linkNamedMDNodes() {
       continue;
 
     NamedMDNode *DestNMD = DstM.getOrInsertNamedMetadata(NMD.getName());
+
+    auto &Inserted = NamedMDNodes[DestNMD->getName()];
+    if (Inserted.size() < DestNMD->getNumOperands()) {
+      // Must be the first module, copy everything from DestNMD.
+      Inserted.insert(DestNMD->operands().begin(), DestNMD->operands().end());
+    }
+
     // Add Src elements into Dest node.
     for (const MDNode *Op : NMD.operands()) {
       MDNode *MD = Mapper.mapMDNode(*Op);
-      if (!is_contained(DestNMD->operands(), MD))
+      if (Inserted.insert(MD).second)
         DestNMD->addOperand(MD);
     }
+    assert(Inserted.size() == DestNMD->getNumOperands());
   }
 }
 

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR optimizes the IRLinker to avoid quadratic behavior during ThinLTO linking by replacing a linear search with a hash-based lookup. The change addresses performance issues where is_contained performs linear searches through previously inserted operands, causing up to 30% overhead on large binaries with CFI metadata.

Key changes:

  • Introduces a DenseMap<StringRef, DenseSet<MDNode *>> lookup table to track inserted operands
  • Replaces is_contained linear search with O(1) hash-based duplicate detection
  • Adds initialization logic to populate the lookup table from existing destination operands

@arsenm arsenm changed the title IRLinker: avoid quardatic behavior IRLinker: avoid quadratic behavior Sep 5, 2025
Created using spr 1.3.6
@vitalybuka vitalybuka enabled auto-merge (squash) September 5, 2025 08:25
@vitalybuka vitalybuka merged commit 7115a0b into main Sep 5, 2025
9 checks passed
@vitalybuka vitalybuka deleted the users/vitalybuka/spr/irlinker-avoid-quardatic-behavior branch September 5, 2025 08:55
@llvm-ci
Copy link
Collaborator

llvm-ci commented Sep 5, 2025

LLVM Buildbot has detected a new failure on builder cross-project-tests-sie-ubuntu running on doug-worker-1a while building llvm at step 6 "test-build-unified-tree-check-cross-project".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/181/builds/27321

Here is the relevant piece of the build log for the reference
Step 6 (test-build-unified-tree-check-cross-project) failure: test (failure)
******************** TEST 'cross-project-tests :: debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/address_after_ref.cpp' FAILED ********************
Exit Code: 2

Command Output (stderr):
--
clang++ -O0 -glldb -std=gnu++11 /home/buildbot/buildbot-root/cross-project-tests-sie-ubuntu/llvm-project/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/address_after_ref.cpp -o /home/buildbot/buildbot-root/cross-project-tests-sie-ubuntu/build/projects/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/Output/address_after_ref.cpp.tmp # RUN: at line 5
+ clang++ -O0 -glldb -std=gnu++11 /home/buildbot/buildbot-root/cross-project-tests-sie-ubuntu/llvm-project/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/address_after_ref.cpp -o /home/buildbot/buildbot-root/cross-project-tests-sie-ubuntu/build/projects/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/Output/address_after_ref.cpp.tmp
"/usr/bin/python3.8" "/home/buildbot/buildbot-root/cross-project-tests-sie-ubuntu/llvm-project/cross-project-tests/debuginfo-tests/dexter/dexter.py" test --fail-lt 1.0 -w --debugger lldb-dap --lldb-executable "/home/buildbot/buildbot-root/cross-project-tests-sie-ubuntu/build/bin/lldb-dap" --binary /home/buildbot/buildbot-root/cross-project-tests-sie-ubuntu/build/projects/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/Output/address_after_ref.cpp.tmp -- /home/buildbot/buildbot-root/cross-project-tests-sie-ubuntu/llvm-project/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/address_after_ref.cpp | /home/buildbot/buildbot-root/cross-project-tests-sie-ubuntu/build/bin/FileCheck /home/buildbot/buildbot-root/cross-project-tests-sie-ubuntu/llvm-project/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/address_after_ref.cpp # RUN: at line 6
+ /usr/bin/python3.8 /home/buildbot/buildbot-root/cross-project-tests-sie-ubuntu/llvm-project/cross-project-tests/debuginfo-tests/dexter/dexter.py test --fail-lt 1.0 -w --debugger lldb-dap --lldb-executable /home/buildbot/buildbot-root/cross-project-tests-sie-ubuntu/build/bin/lldb-dap --binary /home/buildbot/buildbot-root/cross-project-tests-sie-ubuntu/build/projects/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/Output/address_after_ref.cpp.tmp -- /home/buildbot/buildbot-root/cross-project-tests-sie-ubuntu/llvm-project/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/address_after_ref.cpp
+ /home/buildbot/buildbot-root/cross-project-tests-sie-ubuntu/build/bin/FileCheck /home/buildbot/buildbot-root/cross-project-tests-sie-ubuntu/llvm-project/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/address_after_ref.cpp


****************************************


vitalybuka added a commit that referenced this pull request Sep 6, 2025
In #157045 I didn't realize that IRMover object is
one for entire LTO, but IRLinker is created per
module. We need one chache for combined module.

I timed "IRLinker::linkNamedMDNodes" code on one
of our internal binaries, not very large, but
above average.

Before #146020: 0.4859s
After #146020: 624.4686s
After #157045: 322.3493s
After this patch: 0.5574s
@zmodem
Copy link
Collaborator

zmodem commented Sep 8, 2025

We're hitting the assert when there are duplicated !llvm.linker.options in the destination module. (https://crbug.com/443585789)

I'll go ahead and remove the assert for now and add a test.

zmodem added a commit that referenced this pull request Sep 8, 2025
It was added in #157045, but will fail if there are duplicated nodes in
DstM already, as illustrated by the test. See comments on the PR.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

LTO Link time optimization (regular/full LTO or ThinLTO)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants