From 84afdb477133cb04cf842d2d57d4e5e1461ba924 Mon Sep 17 00:00:00 2001 From: Peter Rong Date: Sat, 1 Aug 2026 12:44:24 -0700 Subject: [PATCH] [lld][MachO] Avoid quadratic iteration over already-folded symbols during ICF (#213339) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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) --- lld/MachO/InputSection.cpp | 16 +++++++----- lld/test/MachO/icf-scale-same-class.s | 37 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 6 deletions(-) create mode 100644 lld/test/MachO/icf-scale-same-class.s diff --git a/lld/MachO/InputSection.cpp b/lld/MachO/InputSection.cpp index 4c4f644889d5..d977830161a8 100644 --- a/lld/MachO/InputSection.cpp +++ b/lld/MachO/InputSection.cpp @@ -204,16 +204,20 @@ void ConcatInputSection::foldIdentical(ConcatInputSection *copy, for (auto ©Sym : 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 000000000000..7dc19bae6dd6 --- /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