[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