[lld] [lld-macho] Include ICF safe thunks in balanced partitioning (PR #212096)

Karim Alweheshy via llvm-commits llvm-commits at lists.llvm.org
Tue Jul 28 16:59:52 PDT 2026


https://github.com/karim-alweheshy updated https://github.com/llvm/llvm-project/pull/212096

>From 693f3f76939e1ce6456fca650f285043b4e20614 Mon Sep 17 00:00:00 2001
From: Karim Alweheshy <karim.alweheshy at gmail.com>
Date: Sun, 26 Jul 2026 09:29:46 +0200
Subject: [PATCH 1/5] [lld-macho] Include ICF safe thunks in balanced
 partitioning

---
 lld/MachO/BPSectionOrderer.cpp                | 55 +++++++++-------
 .../MachO/bp-section-orderer-safe-thunks.s    | 62 +++++++++++++++++++
 2 files changed, 94 insertions(+), 23 deletions(-)
 create mode 100644 lld/test/MachO/bp-section-orderer-safe-thunks.s

diff --git a/lld/MachO/BPSectionOrderer.cpp b/lld/MachO/BPSectionOrderer.cpp
index edc92d40f2a96..4289045b1ff05 100644
--- a/lld/MachO/BPSectionOrderer.cpp
+++ b/lld/MachO/BPSectionOrderer.cpp
@@ -13,6 +13,7 @@
 #include "Symbols.h"
 #include "lld/Common/BPSectionOrdererBase.inc"
 #include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/StableHashing.h"
 #include "llvm/Support/Endian.h"
 #include "llvm/Support/xxhash.h"
@@ -120,36 +121,44 @@ DenseMap<const InputSection *, int> lld::macho::runBalancedPartitioning(
   // Collect candidate sections and associated symbols.
   SmallVector<InputSection *> sections;
   DenseMap<CachedHashStringRef, std::set<unsigned>> rootSymbolToSectionIdxs;
+  DenseSet<InputSection *> seenSections;
+  auto addSection = [&](InputSection *isec) {
+    if (!isec || isec->data.empty() || !isec->data.data())
+      return;
+    // CString section order is handled by
+    // {Deduplicated}CStringSection::finalizeContents()
+    if (isa<CStringInputSection>(isec) || isec->isFinal)
+      return;
+    // ConcatInputSections are entirely live or dead, so the offset is
+    // irrelevant.
+    if (isa<ConcatInputSection>(isec) && !isec->isLive(0))
+      return;
+    if (!seenSections.insert(isec).second)
+      return;
+    size_t idx = sections.size();
+    sections.emplace_back(isec);
+    for (auto *sym : BPOrdererMachO::getSymbols(*isec)) {
+      auto rootName = lld::utils::getRootSymbol(sym->getName());
+      rootSymbolToSectionIdxs[CachedHashStringRef(rootName)].insert(idx);
+      if (auto linkageName =
+              BPOrdererMachO::getResolvedLinkageName(rootName))
+        rootSymbolToSectionIdxs[CachedHashStringRef(*linkageName)].insert(idx);
+    }
+  };
   for (const auto *file : inputFiles) {
     for (auto *sec : file->sections) {
       if (sec->name == section_names::ehFrame &&
           sec->segname == segment_names::text)
         continue;
-      for (auto &subsec : sec->subsections) {
-        auto *isec = subsec.isec;
-        if (!isec || isec->data.empty() || !isec->data.data())
-          continue;
-        // CString section order is handled by
-        // {Deduplicated}CStringSection::finalizeContents()
-        if (isa<CStringInputSection>(isec) || isec->isFinal)
-          continue;
-        // ConcatInputSections are entirely live or dead, so the offset is
-        // irrelevant.
-        if (isa<ConcatInputSection>(isec) && !isec->isLive(0))
-          continue;
-        size_t idx = sections.size();
-        sections.emplace_back(isec);
-        for (auto *sym : BPOrdererMachO::getSymbols(*isec)) {
-          auto rootName = lld::utils::getRootSymbol(sym->getName());
-          rootSymbolToSectionIdxs[CachedHashStringRef(rootName)].insert(idx);
-          if (auto linkageName =
-                  BPOrdererMachO::getResolvedLinkageName(rootName))
-            rootSymbolToSectionIdxs[CachedHashStringRef(*linkageName)].insert(
-                idx);
-        }
-      }
+      for (auto &subsec : sec->subsections)
+        addSection(subsec.isec);
     }
   }
+  // ICF safe thunks are linker-created after the input-file section graph is
+  // built. Include them (and any other synthetic concat sections) so temporal
+  // profile names resolve to the sections that are actually emitted.
+  for (auto *isec : inputSections)
+    addSection(isec);
 
   auto result = BPOrdererMachO().computeOrder(
       profilePath, compressionSortSpecs, forFunctionCompression,
diff --git a/lld/test/MachO/bp-section-orderer-safe-thunks.s b/lld/test/MachO/bp-section-orderer-safe-thunks.s
new file mode 100644
index 0000000000000..70c279c23a8a4
--- /dev/null
+++ b/lld/test/MachO/bp-section-orderer-safe-thunks.s
@@ -0,0 +1,62 @@
+# REQUIRES: aarch64
+
+# RUN: rm -rf %t && split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/input.s -o %t/input.o
+# RUN: llvm-profdata merge %t/profile.proftext -o %t/profile.profdata
+
+## The temporal profile names _hot_b, whose input section becomes a
+## linker-created ICF thunk. Balanced partitioning must order that thunk.
+# RUN: %lld -arch arm64 -lSystem -e _main -o %t/out %t/input.o --icf=safe_thunks --irpgo-profile=%t/profile.profdata --bp-startup-sort=function --bp-compression-sort=none --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=VERBOSE
+# VERBOSE: Ordered 1 sections (4 bytes) using balanced partitioning
+# VERBOSE: Functions for startup: 1 (4 bytes)
+
+# RUN: %lld -arch arm64 -lSystem -e _main -o - %t/input.o --icf=safe_thunks --irpgo-profile=%t/profile.profdata --bp-startup-sort=function --bp-compression-sort=none | llvm-nm --numeric-sort --format=just-symbols - | FileCheck %s --check-prefix=ORDER
+# ORDER: _hot_b
+# ORDER-NEXT: _main
+# ORDER-NEXT: _hot_a
+# ORDER-NEXT: _cold
+
+#--- input.s
+.subsections_via_symbols
+
+.addrsig
+.addrsig_sym _hot_a
+.addrsig_sym _hot_b
+
+.text
+.globl _main
+_main:
+  ret
+
+.globl _hot_a
+_hot_a:
+  mov w0, #42
+  ret
+
+.globl _hot_b
+_hot_b:
+  mov w0, #42
+  ret
+
+.globl _cold
+_cold:
+  mov w0, #1
+  ret
+
+#--- profile.proftext
+:ir
+:temporal_prof_traces
+# Num Traces
+1
+# Trace Stream Size:
+1
+# Weight
+1
+hot_b
+
+hot_b
+# Func Hash:
+1111
+# Num Counters:
+1
+# Counter Values:

>From fcbe33bd5ff149d2d0c5c72abe112a37fa6a5885 Mon Sep 17 00:00:00 2001
From: Karim Alweheshy <karim.alweheshy at gmail.com>
Date: Sun, 26 Jul 2026 09:34:02 +0200
Subject: [PATCH 2/5] Apply clang-format

---
 lld/MachO/BPSectionOrderer.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/lld/MachO/BPSectionOrderer.cpp b/lld/MachO/BPSectionOrderer.cpp
index 4289045b1ff05..1b86ca7f16755 100644
--- a/lld/MachO/BPSectionOrderer.cpp
+++ b/lld/MachO/BPSectionOrderer.cpp
@@ -140,8 +140,7 @@ DenseMap<const InputSection *, int> lld::macho::runBalancedPartitioning(
     for (auto *sym : BPOrdererMachO::getSymbols(*isec)) {
       auto rootName = lld::utils::getRootSymbol(sym->getName());
       rootSymbolToSectionIdxs[CachedHashStringRef(rootName)].insert(idx);
-      if (auto linkageName =
-              BPOrdererMachO::getResolvedLinkageName(rootName))
+      if (auto linkageName = BPOrdererMachO::getResolvedLinkageName(rootName))
         rootSymbolToSectionIdxs[CachedHashStringRef(*linkageName)].insert(idx);
     }
   };

>From 32b4d16f53bb21a1f8bf602327e28575f64cd741 Mon Sep 17 00:00:00 2001
From: Karim Alweheshy <karim.alweheshy at gmail.com>
Date: Sun, 26 Jul 2026 10:16:19 +0200
Subject: [PATCH 3/5] [lld-macho] Fix safe-thunk BP profile fixture

---
 lld/test/MachO/bp-section-orderer-safe-thunks.s | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lld/test/MachO/bp-section-orderer-safe-thunks.s b/lld/test/MachO/bp-section-orderer-safe-thunks.s
index 70c279c23a8a4..4debcc6451cbf 100644
--- a/lld/test/MachO/bp-section-orderer-safe-thunks.s
+++ b/lld/test/MachO/bp-section-orderer-safe-thunks.s
@@ -60,3 +60,4 @@ hot_b
 # Num Counters:
 1
 # Counter Values:
+1

>From cc7cffac943487a091190081f060c24c4383f517 Mon Sep 17 00:00:00 2001
From: Karim Alweheshy <karim.alweheshy at gmail.com>
Date: Sun, 26 Jul 2026 10:25:24 +0200
Subject: [PATCH 4/5] [lld-macho] Restrict BP discovery to ICF thunks

---
 lld/MachO/BPSectionOrderer.cpp | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/lld/MachO/BPSectionOrderer.cpp b/lld/MachO/BPSectionOrderer.cpp
index 1b86ca7f16755..6135c7c22a5d9 100644
--- a/lld/MachO/BPSectionOrderer.cpp
+++ b/lld/MachO/BPSectionOrderer.cpp
@@ -13,7 +13,7 @@
 #include "Symbols.h"
 #include "lld/Common/BPSectionOrdererBase.inc"
 #include "llvm/ADT/DenseMap.h"
-#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/StableHashing.h"
 #include "llvm/Support/Endian.h"
 #include "llvm/Support/xxhash.h"
@@ -121,7 +121,6 @@ DenseMap<const InputSection *, int> lld::macho::runBalancedPartitioning(
   // Collect candidate sections and associated symbols.
   SmallVector<InputSection *> sections;
   DenseMap<CachedHashStringRef, std::set<unsigned>> rootSymbolToSectionIdxs;
-  DenseSet<InputSection *> seenSections;
   auto addSection = [&](InputSection *isec) {
     if (!isec || isec->data.empty() || !isec->data.data())
       return;
@@ -133,8 +132,6 @@ DenseMap<const InputSection *, int> lld::macho::runBalancedPartitioning(
     // irrelevant.
     if (isa<ConcatInputSection>(isec) && !isec->isLive(0))
       return;
-    if (!seenSections.insert(isec).second)
-      return;
     size_t idx = sections.size();
     sections.emplace_back(isec);
     for (auto *sym : BPOrdererMachO::getSymbols(*isec)) {
@@ -154,10 +151,13 @@ DenseMap<const InputSection *, int> lld::macho::runBalancedPartitioning(
     }
   }
   // ICF safe thunks are linker-created after the input-file section graph is
-  // built. Include them (and any other synthetic concat sections) so temporal
-  // profile names resolve to the sections that are actually emitted.
-  for (auto *isec : inputSections)
-    addSection(isec);
+  // built. Include them so profile names resolve to the emitted thunks.
+  for (auto *isec : inputSections) {
+    if (llvm::any_of(isec->symbols, [](Defined *sym) {
+          return sym->identicalCodeFoldingKind == Symbol::ICFFoldKind::Thunk;
+        }))
+      addSection(isec);
+  }
 
   auto result = BPOrdererMachO().computeOrder(
       profilePath, compressionSortSpecs, forFunctionCompression,

>From 89c9d72b4534a37e0ca002f0faefa0e75344fc43 Mon Sep 17 00:00:00 2001
From: Karim Alweheshy <karim.alweheshy at gmail.com>
Date: Wed, 29 Jul 2026 01:59:26 +0200
Subject: [PATCH 5/5] [lld-macho] Order shared ICF bodies with profiled thunks

---
 lld/MachO/BPSectionOrderer.cpp                | 36 +++++++++++++------
 .../MachO/bp-section-orderer-safe-thunks.s    | 19 +++++++---
 2 files changed, 40 insertions(+), 15 deletions(-)

diff --git a/lld/MachO/BPSectionOrderer.cpp b/lld/MachO/BPSectionOrderer.cpp
index 6135c7c22a5d9..fd667452c9ebf 100644
--- a/lld/MachO/BPSectionOrderer.cpp
+++ b/lld/MachO/BPSectionOrderer.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "BPSectionOrderer.h"
+#include "ICF.h"
 #include "InputSection.h"
 #include "OutputSegment.h"
 #include "Relocations.h"
@@ -120,7 +121,14 @@ DenseMap<const InputSection *, int> lld::macho::runBalancedPartitioning(
     bool compressionSortStartupFunctions, bool verbose) {
   // Collect candidate sections and associated symbols.
   SmallVector<InputSection *> sections;
+  DenseMap<const InputSection *, unsigned> sectionToIdx;
   DenseMap<CachedHashStringRef, std::set<unsigned>> rootSymbolToSectionIdxs;
+  auto addSectionForName = [&](StringRef name, unsigned idx) {
+    auto rootName = lld::utils::getRootSymbol(name);
+    rootSymbolToSectionIdxs[CachedHashStringRef(rootName)].insert(idx);
+    if (auto linkageName = BPOrdererMachO::getResolvedLinkageName(rootName))
+      rootSymbolToSectionIdxs[CachedHashStringRef(*linkageName)].insert(idx);
+  };
   auto addSection = [&](InputSection *isec) {
     if (!isec || isec->data.empty() || !isec->data.data())
       return;
@@ -132,14 +140,11 @@ DenseMap<const InputSection *, int> lld::macho::runBalancedPartitioning(
     // irrelevant.
     if (isa<ConcatInputSection>(isec) && !isec->isLive(0))
       return;
-    size_t idx = sections.size();
+    unsigned idx = sections.size();
     sections.emplace_back(isec);
-    for (auto *sym : BPOrdererMachO::getSymbols(*isec)) {
-      auto rootName = lld::utils::getRootSymbol(sym->getName());
-      rootSymbolToSectionIdxs[CachedHashStringRef(rootName)].insert(idx);
-      if (auto linkageName = BPOrdererMachO::getResolvedLinkageName(rootName))
-        rootSymbolToSectionIdxs[CachedHashStringRef(*linkageName)].insert(idx);
-    }
+    sectionToIdx.try_emplace(isec, idx);
+    for (auto *sym : BPOrdererMachO::getSymbols(*isec))
+      addSectionForName(sym->getName(), idx);
   };
   for (const auto *file : inputFiles) {
     for (auto *sec : file->sections) {
@@ -151,12 +156,23 @@ DenseMap<const InputSection *, int> lld::macho::runBalancedPartitioning(
     }
   }
   // ICF safe thunks are linker-created after the input-file section graph is
-  // built. Include them so profile names resolve to the emitted thunks.
+  // built. Include them so profile names resolve to the emitted thunks and the
+  // shared bodies they immediately branch to.
   for (auto *isec : inputSections) {
-    if (llvm::any_of(isec->symbols, [](Defined *sym) {
+    if (!llvm::any_of(isec->symbols, [](Defined *sym) {
           return sym->identicalCodeFoldingKind == Symbol::ICFFoldKind::Thunk;
         }))
-      addSection(isec);
+      continue;
+    addSection(isec);
+    for (auto *sym : isec->symbols) {
+      if (sym->identicalCodeFoldingKind != Symbol::ICFFoldKind::Thunk)
+        continue;
+      InputSection *bodyIsec = getBodyForThunkFoldedSym(sym)->isec();
+      auto bodyIdx = sectionToIdx.find(bodyIsec);
+      assert(bodyIdx != sectionToIdx.end() &&
+             "ICF thunk body must be a BP candidate");
+      addSectionForName(sym->getName(), bodyIdx->second);
+    }
   }
 
   auto result = BPOrdererMachO().computeOrder(
diff --git a/lld/test/MachO/bp-section-orderer-safe-thunks.s b/lld/test/MachO/bp-section-orderer-safe-thunks.s
index 4debcc6451cbf..ddab9fa878ed1 100644
--- a/lld/test/MachO/bp-section-orderer-safe-thunks.s
+++ b/lld/test/MachO/bp-section-orderer-safe-thunks.s
@@ -5,16 +5,19 @@
 # RUN: llvm-profdata merge %t/profile.proftext -o %t/profile.profdata
 
 ## The temporal profile names _hot_b, whose input section becomes a
-## linker-created ICF thunk. Balanced partitioning must order that thunk.
+## linker-created ICF thunk. Balanced partitioning must order both that thunk
+## and the shared _hot_a body that it immediately branches to. _hot_c folds to
+## another thunk for the same body but is not profiled and must not be promoted.
 # RUN: %lld -arch arm64 -lSystem -e _main -o %t/out %t/input.o --icf=safe_thunks --irpgo-profile=%t/profile.profdata --bp-startup-sort=function --bp-compression-sort=none --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=VERBOSE
-# VERBOSE: Ordered 1 sections (4 bytes) using balanced partitioning
-# VERBOSE: Functions for startup: 1 (4 bytes)
+# VERBOSE: Ordered 2 sections (12 bytes) using balanced partitioning
+# VERBOSE: Functions for startup: 2 (12 bytes)
 
 # RUN: %lld -arch arm64 -lSystem -e _main -o - %t/input.o --icf=safe_thunks --irpgo-profile=%t/profile.profdata --bp-startup-sort=function --bp-compression-sort=none | llvm-nm --numeric-sort --format=just-symbols - | FileCheck %s --check-prefix=ORDER
-# ORDER: _hot_b
+# ORDER: _hot_a
+# ORDER-NEXT: _hot_b
 # ORDER-NEXT: _main
-# ORDER-NEXT: _hot_a
 # ORDER-NEXT: _cold
+# ORDER-NEXT: _hot_c
 
 #--- input.s
 .subsections_via_symbols
@@ -22,6 +25,7 @@
 .addrsig
 .addrsig_sym _hot_a
 .addrsig_sym _hot_b
+.addrsig_sym _hot_c
 
 .text
 .globl _main
@@ -38,6 +42,11 @@ _hot_b:
   mov w0, #42
   ret
 
+.globl _hot_c
+_hot_c:
+  mov w0, #42
+  ret
+
 .globl _cold
 _cold:
   mov w0, #1



More information about the llvm-commits mailing list