[lld] 84afdb4 - [lld][MachO] Avoid quadratic iteration over already-folded symbols during ICF (#213339)

via llvm-commits llvm-commits at lists.llvm.org
Sat Aug 1 12:44:29 PDT 2026


Author: Peter Rong
Date: 2026-08-01T12:44:24-07:00
New Revision: 84afdb477133cb04cf842d2d57d4e5e1461ba924

URL: https://github.com/llvm/llvm-project/commit/84afdb477133cb04cf842d2d57d4e5e1461ba924
DIFF: https://github.com/llvm/llvm-project/commit/84afdb477133cb04cf842d2d57d4e5e1461ba924.diff

LOG: [lld][MachO] Avoid quadratic iteration over already-folded symbols during ICF (#213339)

`ConcatInputSection::foldIdentical()` clears the folded functions
`originalUnwindEntry`.
However, it cleared every symbol: with N members, the repeated clearing
is **O(N²)**.
This is redundant: we should only remove the incoming `copy->symbols`.

This patch moves the removing loop ahead and adds more clear comments on
why we need to skip the first element.
Also added a new `lld/test/MachO/icf-scale-same-class.s` with 500K
identical functions as a stress test, which would've taken minutes to
link, and less than a second with the patch.

Testing on real-world app (IRPGO instrumentation + ICF) find that we
achieved a 19x speed up (1:14:42 -> 3:55)

Added: 
    lld/test/MachO/icf-scale-same-class.s

Modified: 
    lld/MachO/InputSection.cpp

Removed: 
    


################################################################################
diff  --git a/lld/MachO/InputSection.cpp b/lld/MachO/InputSection.cpp
index 4c4f644889d5f..d977830161a8e 100644
--- a/lld/MachO/InputSection.cpp
+++ b/lld/MachO/InputSection.cpp
@@ -204,16 +204,20 @@ void ConcatInputSection::foldIdentical(ConcatInputSection *copy,
   for (auto &copySym : copy->symbols)
     copySym->identicalCodeFoldingKind = foldKind;
 
-  symbols.insert(symbols.end(), copy->symbols.begin(), copy->symbols.end());
-  copy->symbols.clear();
-
-  // Remove duplicate compact unwind info for symbols at the same address.
-  if (symbols.empty())
+  if (copy->symbols.empty())
     return;
-  for (auto it = symbols.begin() + 1; it != symbols.end(); ++it) {
+  auto *it = copy->symbols.begin();
+  // The first symbol in the merged section (symbols.front()) must keep its
+  // unwind entry. If this section is empty, the copy's first symbol becomes the
+  // new front, so we skip clearing it.
+  if (symbols.empty())
+    ++it;
+  for (; it != copy->symbols.end(); ++it) {
     assert((*it)->value == 0);
     (*it)->originalUnwindEntry = nullptr;
   }
+  symbols.insert(symbols.end(), copy->symbols.begin(), copy->symbols.end());
+  copy->symbols.clear();
 }
 
 void ConcatInputSection::writeTo(uint8_t *buf) {

diff  --git a/lld/test/MachO/icf-scale-same-class.s b/lld/test/MachO/icf-scale-same-class.s
new file mode 100644
index 0000000000000..7dc19bae6dd60
--- /dev/null
+++ b/lld/test/MachO/icf-scale-same-class.s
@@ -0,0 +1,37 @@
+# REQUIRES: x86
+# RUN: rm -rf %t*
+
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t.o
+# RUN: %lld -lSystem --icf=all -o %t %t.o
+# RUN: llvm-objdump --macho --section-headers %t | FileCheck %s --check-prefix=SECT
+# RUN: llvm-objdump --macho --syms %t | FileCheck %s --check-prefix=SYMS
+
+## Every body folds into one, so __text holds a single 6-byte body plus _main
+## rather than the 3 MiB (0x30000d) the unfolded copies occupy.
+# SECT:      Idx Name          Size
+# SECT-NEXT: 0 __text        00000009
+
+## The sentinels bracket the group, so checking that the first and last members
+## of the class share an address covers the whole run of folds.
+# SYMS-DAG: [[#%.16x,F:]] g     F __TEXT,__text _f_first
+# SYMS-DAG: [[#F]]        g     F __TEXT,__text _f_last
+
+.subsections_via_symbols
+.text
+.p2align 2
+
+.globl _f_first
+_f_first:; movl $7, %eax; ret
+
+## 64 Ki identical function landing in one equivalence class.
+.rept 65536
+  .globl _f\+
+  _f\+:; movl $$7, %eax; ret
+.endr
+
+.globl _f_last
+_f_last:; movl $7, %eax; ret
+
+.globl _main
+_main:
+  ret


        


More information about the llvm-commits mailing list