[lld] [lld][MachO] Add initializer and +load roots to temporal BP sorting (PR #212110)
Karim Alweheshy via llvm-commits
llvm-commits at lists.llvm.org
Tue Aug 4 22:34:42 PDT 2026
https://github.com/karim-alweheshy updated https://github.com/llvm/llvm-project/pull/212110
>From ea8022b2b9679281dc5cd5c1addf01ae699b54ca Mon Sep 17 00:00:00 2001
From: Karim Alweheshy <karim.alweheshy at gmail.com>
Date: Sun, 26 Jul 2026 12:44:08 +0200
Subject: [PATCH 1/3] [lld][MachO] Add initializers to temporal BP startup
sorting
---
lld/ELF/BPSectionOrderer.cpp | 3 +-
lld/MachO/BPSectionOrderer.cpp | 30 +++++++-
lld/MachO/BPSectionOrderer.h | 2 +-
lld/MachO/Config.h | 1 +
lld/MachO/Driver.cpp | 6 ++
lld/MachO/Options.td | 6 ++
lld/MachO/SectionPriorities.cpp | 2 +-
.../lld/Common/BPSectionOrdererBase.inc | 11 ++-
.../MachO/bp-section-orderer-initializers.s | 70 +++++++++++++++++++
9 files changed, 124 insertions(+), 7 deletions(-)
create mode 100644 lld/test/MachO/bp-section-orderer-initializers.s
diff --git a/lld/ELF/BPSectionOrderer.cpp b/lld/ELF/BPSectionOrderer.cpp
index 0ce7ceff4348b..c9bdc80c3d093 100644
--- a/lld/ELF/BPSectionOrderer.cpp
+++ b/lld/ELF/BPSectionOrderer.cpp
@@ -98,5 +98,6 @@ DenseMap<const InputSectionBase *, int> elf::runBalancedPartitioning(
return orderer.computeOrder(profilePath, compressionSortSpecs,
forFunctionCompression, forDataCompression,
compressionSortStartupFunctions, verbose,
- sections, rootSymbolToSectionIdxs);
+ sections, rootSymbolToSectionIdxs,
+ /*initialStartupSectionIdxs=*/{});
}
diff --git a/lld/MachO/BPSectionOrderer.cpp b/lld/MachO/BPSectionOrderer.cpp
index 6da9985006247..5c9f5e93cc2dc 100644
--- a/lld/MachO/BPSectionOrderer.cpp
+++ b/lld/MachO/BPSectionOrderer.cpp
@@ -12,10 +12,12 @@
#include "Relocations.h"
#include "Symbols.h"
#include "Target.h"
+#include "SyntheticSections.h"
#include "lld/Common/BPSectionOrdererBase.inc"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StableHashing.h"
+#include "llvm/BinaryFormat/MachO.h"
#include "llvm/Support/Endian.h"
#include "llvm/Support/xxhash.h"
@@ -118,7 +120,7 @@ struct BPOrdererMachO : lld::BPOrderer<BPOrdererMachO> {
DenseMap<const InputSection *, int> lld::macho::runBalancedPartitioning(
StringRef profilePath, ArrayRef<BPCompressionSortSpec> compressionSortSpecs,
bool forFunctionCompression, bool forDataCompression,
- bool compressionSortStartupFunctions, bool verbose) {
+ bool compressionSortStartupFunctions, bool sortInitializers, bool verbose) {
// Collect candidate sections and associated symbols.
SmallVector<InputSection *> sections;
DenseMap<const InputSection *, unsigned> sectionToIdx;
@@ -182,10 +184,34 @@ DenseMap<const InputSection *, int> lld::macho::runBalancedPartitioning(
}
}
+ SetVector<unsigned> initialStartupSectionIdxs;
+ if (sortInitializers) {
+ const auto &initOffsetInputs = in.initOffsets->inputs();
+ SmallVector<ConcatInputSection *> initializerSections(
+ initOffsetInputs.begin(), initOffsetInputs.end());
+ for (InputSection *isec : inputSections)
+ if (sectionType(isec->getFlags()) ==
+ llvm::MachO::S_MOD_INIT_FUNC_POINTERS)
+ initializerSections.push_back(cast<ConcatInputSection>(isec));
+ for (const ConcatInputSection *isec : initializerSections) {
+ for (const Relocation &reloc : isec->relocs) {
+ InputSection *referent = reloc.getReferentInputSection();
+ if (!referent)
+ continue;
+ auto it = sectionToIdx.find(referent->canonical());
+ if (it != sectionToIdx.end())
+ initialStartupSectionIdxs.insert(it->second);
+ }
+ }
+ if (verbose)
+ dbgs() << "Initializer functions for startup: "
+ << initialStartupSectionIdxs.size() << "\n";
+ }
+
auto result = BPOrdererMachO().computeOrder(
profilePath, compressionSortSpecs, forFunctionCompression,
forDataCompression, compressionSortStartupFunctions, verbose, sections,
- rootSymbolToSectionIdxs);
+ rootSymbolToSectionIdxs, initialStartupSectionIdxs.getArrayRef());
// BP already orders cold sections after non-cold via separate buckets.
// Unset isCold on sections that received a BP priority so Writer.cpp's
// stable_partition doesn't re-partition them. Sections without a BP priority
diff --git a/lld/MachO/BPSectionOrderer.h b/lld/MachO/BPSectionOrderer.h
index cc63c85cd32d3..e4b8dd0575913 100644
--- a/lld/MachO/BPSectionOrderer.h
+++ b/lld/MachO/BPSectionOrderer.h
@@ -31,7 +31,7 @@ llvm::DenseMap<const InputSection *, int> runBalancedPartitioning(
llvm::StringRef profilePath,
llvm::ArrayRef<BPCompressionSortSpec> compressionSortSpecs,
bool forFunctionCompression, bool forDataCompression,
- bool compressionSortStartupFunctions, bool verbose);
+ bool compressionSortStartupFunctions, bool sortInitializers, bool verbose);
} // namespace lld::macho
diff --git a/lld/MachO/Config.h b/lld/MachO/Config.h
index 9767cc5e5b6e4..6048b8ea3365c 100644
--- a/lld/MachO/Config.h
+++ b/lld/MachO/Config.h
@@ -233,6 +233,7 @@ struct Configuration {
llvm::StringRef irpgoProfilePath;
bool bpStartupFunctionSort = false;
+ bool bpStartupSortInitializers = false;
bool bpCompressionSortStartupFunctions = false;
bool bpFunctionOrderForCompression = false;
bool bpDataOrderForCompression = false;
diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp
index 2864c6d28fa49..a75576af51fc6 100644
--- a/lld/MachO/Driver.cpp
+++ b/lld/MachO/Driver.cpp
@@ -2102,6 +2102,9 @@ bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
config->bpCompressionSortStartupFunctions =
args.hasFlag(OPT_bp_compression_sort_startup_functions,
OPT_no_bp_compression_sort_startup_functions, false);
+ config->bpStartupSortInitializers =
+ args.hasFlag(OPT_bp_startup_sort_initializers,
+ OPT_no_bp_startup_sort_initializers, false);
if (const Arg *arg = args.getLastArg(OPT_bp_startup_sort)) {
StringRef startupSortStr = arg->getValue();
if (startupSortStr == "function") {
@@ -2116,6 +2119,9 @@ bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
config->bpCompressionSortStartupFunctions)
error("--bp-compression-sort-startup-functions must be used with "
"--bp-startup-sort=function");
+ if (!config->bpStartupFunctionSort && config->bpStartupSortInitializers)
+ error("--bp-startup-sort-initializers must be used with "
+ "--bp-startup-sort=function");
if (config->irpgoProfilePath.empty() && config->bpStartupFunctionSort)
error("--bp-startup-sort=function must be used with "
"--irpgo-profile");
diff --git a/lld/MachO/Options.td b/lld/MachO/Options.td
index b7686d66a258e..72da037f2d8d4 100644
--- a/lld/MachO/Options.td
+++ b/lld/MachO/Options.td
@@ -142,6 +142,12 @@ def bp_startup_sort: Joined<["--"], "bp-startup-sort=">,
MetaVarName<"[none,function]">,
HelpText<"Order sections based on profile data to improve startup time">,
Group<grp_lld>;
+def bp_startup_sort_initializers: Flag<["--"], "bp-startup-sort-initializers">,
+ HelpText<"Include Mach-O initializer functions in balanced-partitioning startup sorting">,
+ Group<grp_lld>;
+def no_bp_startup_sort_initializers: Flag<["--"], "no-bp-startup-sort-initializers">,
+ HelpText<"Do not include Mach-O initializer functions in balanced-partitioning startup sorting">,
+ Group<grp_lld>;
def bp_compression_sort_startup_functions: Flag<["--"], "bp-compression-sort-startup-functions">,
HelpText<"Order startup functions to improve compressed size in addition to startup time">,
Group<grp_lld>;
diff --git a/lld/MachO/SectionPriorities.cpp b/lld/MachO/SectionPriorities.cpp
index dea47c76a385e..481d69eca52f1 100644
--- a/lld/MachO/SectionPriorities.cpp
+++ b/lld/MachO/SectionPriorities.cpp
@@ -381,7 +381,7 @@ macho::PriorityBuilder::buildInputSectionPriorities() {
config->bpCompressionSortSpecs, config->bpFunctionOrderForCompression,
config->bpDataOrderForCompression,
config->bpCompressionSortStartupFunctions,
- config->bpVerboseSectionOrderer);
+ config->bpStartupSortInitializers, config->bpVerboseSectionOrderer);
} else if (config->callGraphProfileSort) {
// Sort sections by the profile data provided by __LLVM,__cg_profile
// sections.
diff --git a/lld/include/lld/Common/BPSectionOrdererBase.inc b/lld/include/lld/Common/BPSectionOrdererBase.inc
index b2e87d6c240fc..53f45dd7fbb75 100644
--- a/lld/include/lld/Common/BPSectionOrdererBase.inc
+++ b/lld/include/lld/Common/BPSectionOrdererBase.inc
@@ -68,7 +68,8 @@ template <class D> struct BPOrderer {
bool compressionSortStartupFunctions, bool verbose,
llvm::ArrayRef<Section *> sections,
const DenseMap<CachedHashStringRef, std::set<unsigned>>
- &rootSymbolToSectionIdxs)
+ &rootSymbolToSectionIdxs,
+ llvm::ArrayRef<unsigned> initialStartupSectionIdxs)
-> llvm::DenseMap<const Section *, int>;
std::optional<StringRef> static getResolvedLinkageName(StringRef name) {
@@ -164,7 +165,9 @@ auto BPOrderer<D>::computeOrder(
bool compressionSortStartupFunctions, bool verbose,
ArrayRef<Section *> sections,
const DenseMap<CachedHashStringRef, std::set<unsigned>>
- &rootSymbolToSectionIdxs) -> DenseMap<const Section *, int> {
+ &rootSymbolToSectionIdxs,
+ ArrayRef<unsigned> initialStartupSectionIdxs)
+ -> DenseMap<const Section *, int> {
TimeTraceScope timeScope("Setup Balanced Partitioning");
DenseMap<const void *, uint64_t> sectionToIdx;
for (auto [i, isec] : llvm::enumerate(sections))
@@ -197,6 +200,10 @@ auto BPOrderer<D>::computeOrder(
for (size_t traceIdx = 0; traceIdx < traces.size(); traceIdx++) {
uint64_t currentSize = 0, cutoffSize = 1;
size_t cutoffTimestamp = 1;
+ for (unsigned sectionIdx : initialStartupSectionIdxs) {
+ sectionIdxToTimestamp.try_emplace(sectionIdx, 0);
+ sectionIdxToFirstUN.try_emplace(sectionIdx, maxUN);
+ }
auto &trace = traces[traceIdx].FunctionNameRefs;
for (size_t timestamp = 0; timestamp < trace.size(); timestamp++) {
auto [_, parsedFuncName] = getParsedIRPGOName(
diff --git a/lld/test/MachO/bp-section-orderer-initializers.s b/lld/test/MachO/bp-section-orderer-initializers.s
new file mode 100644
index 0000000000000..9ae5cc5dde2f4
--- /dev/null
+++ b/lld/test/MachO/bp-section-orderer-initializers.s
@@ -0,0 +1,70 @@
+# 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 initializer is absent from the temporal profile, but dyld executes it
+## before main. The opt-in initializer seed includes it in the startup order.
+# RUN: %lld -arch arm64 -lSystem -e _main -o %t/baseline %t/input.o --irpgo-profile=%t/profile.profdata --bp-startup-sort=function --bp-compression-sort=none --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=BASELINE
+# BASELINE: Ordered 1 sections (8 bytes) using balanced partitioning
+# BASELINE: Functions for startup: 1 (8 bytes)
+
+# RUN: %lld -arch arm64 -lSystem -e _main -o - %t/input.o --irpgo-profile=%t/profile.profdata --bp-startup-sort=function --bp-startup-sort-initializers --bp-compression-sort=none --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=INITIALIZERS
+# INITIALIZERS: Initializer functions for startup: 1
+# INITIALIZERS: Ordered 2 sections (16 bytes) using balanced partitioning
+# INITIALIZERS: Functions for startup: 2 (16 bytes)
+
+# RUN: %lld -arch arm64 -lSystem -e _main -o - %t/input.o --irpgo-profile=%t/profile.profdata --bp-startup-sort=function --bp-startup-sort-initializers --bp-compression-sort=none | llvm-nm --numeric-sort --format=just-symbols - | FileCheck %s --check-prefix=ORDER
+# ORDER: _hot
+# ORDER-NEXT: _initializer
+# ORDER-NEXT: _main
+# ORDER-NEXT: _cold
+
+# RUN: not %lld -arch arm64 -lSystem -e _main -o /dev/null %t/input.o --bp-startup-sort-initializers 2>&1 | FileCheck %s --check-prefix=ERROR
+# ERROR: --bp-startup-sort-initializers must be used with --bp-startup-sort=function
+
+#--- input.s
+.subsections_via_symbols
+
+.text
+.globl _main
+_main:
+ ret
+
+.globl _cold
+_cold:
+ mov w0, #1
+ ret
+
+.globl _hot
+_hot:
+ mov w0, #2
+ ret
+
+.globl _initializer
+_initializer:
+ mov w0, #3
+ ret
+
+.section __DATA,__mod_init_func,mod_init_funcs
+.quad _initializer
+
+#--- profile.proftext
+:ir
+:temporal_prof_traces
+# Num Traces
+1
+# Trace Stream Size:
+1
+# Weight
+1
+hot,
+
+hot
+# Func Hash:
+1111
+# Num Counters:
+1
+# Counter Values:
+1
>From 76d0d371c4fde5f08b308ac5ff073468651d87dc Mon Sep 17 00:00:00 2001
From: Karim Alweheshy <karim.alweheshy at gmail.com>
Date: Mon, 27 Jul 2026 00:23:33 +0200
Subject: [PATCH 2/3] [lld][MachO] Add Objective-C +load roots to temporal BP
---
lld/ELF/BPSectionOrderer.cpp | 3 +-
lld/MachO/BPSectionOrderer.cpp | 24 ++-
lld/MachO/BPSectionOrderer.h | 3 +-
lld/MachO/Config.h | 1 +
lld/MachO/Driver.cpp | 6 +
lld/MachO/ObjC.cpp | 131 ++++++++++++++
lld/MachO/ObjC.h | 4 +
lld/MachO/Options.td | 6 +
lld/MachO/SectionPriorities.cpp | 3 +-
.../lld/Common/BPSectionOrdererBase.inc | 13 +-
.../MachO/bp-startup-sort-objc-load-methods.s | 162 ++++++++++++++++++
11 files changed, 349 insertions(+), 7 deletions(-)
create mode 100644 lld/test/MachO/bp-startup-sort-objc-load-methods.s
diff --git a/lld/ELF/BPSectionOrderer.cpp b/lld/ELF/BPSectionOrderer.cpp
index c9bdc80c3d093..671c63ced97a8 100644
--- a/lld/ELF/BPSectionOrderer.cpp
+++ b/lld/ELF/BPSectionOrderer.cpp
@@ -99,5 +99,6 @@ DenseMap<const InputSectionBase *, int> elf::runBalancedPartitioning(
forFunctionCompression, forDataCompression,
compressionSortStartupFunctions, verbose,
sections, rootSymbolToSectionIdxs,
- /*initialStartupSectionIdxs=*/{});
+ /*initialStartupSectionIdxs=*/{},
+ /*prefixStartupSectionIdxs=*/{});
}
diff --git a/lld/MachO/BPSectionOrderer.cpp b/lld/MachO/BPSectionOrderer.cpp
index 5c9f5e93cc2dc..a3daeec9b9b70 100644
--- a/lld/MachO/BPSectionOrderer.cpp
+++ b/lld/MachO/BPSectionOrderer.cpp
@@ -8,6 +8,7 @@
#include "BPSectionOrderer.h"
#include "InputSection.h"
+#include "ObjC.h"
#include "OutputSegment.h"
#include "Relocations.h"
#include "Symbols.h"
@@ -120,7 +121,8 @@ struct BPOrdererMachO : lld::BPOrderer<BPOrdererMachO> {
DenseMap<const InputSection *, int> lld::macho::runBalancedPartitioning(
StringRef profilePath, ArrayRef<BPCompressionSortSpec> compressionSortSpecs,
bool forFunctionCompression, bool forDataCompression,
- bool compressionSortStartupFunctions, bool sortInitializers, bool verbose) {
+ bool compressionSortStartupFunctions, bool sortInitializers,
+ bool sortObjCLoadMethods, bool verbose) {
// Collect candidate sections and associated symbols.
SmallVector<InputSection *> sections;
DenseMap<const InputSection *, unsigned> sectionToIdx;
@@ -208,10 +210,28 @@ DenseMap<const InputSection *, int> lld::macho::runBalancedPartitioning(
<< initialStartupSectionIdxs.size() << "\n";
}
+ SmallVector<unsigned> objcLoadSectionIdxs;
+ if (sortObjCLoadMethods) {
+ for (InputSection *isec : objc::getLoadMethodSections()) {
+ auto it = sectionToIdx.find(isec);
+ if (it != sectionToIdx.end()) {
+ initialStartupSectionIdxs.insert(it->second);
+ objcLoadSectionIdxs.push_back(it->second);
+ }
+ }
+ llvm::sort(objcLoadSectionIdxs);
+ objcLoadSectionIdxs.erase(llvm::unique(objcLoadSectionIdxs),
+ objcLoadSectionIdxs.end());
+ if (verbose)
+ dbgs() << "Objective-C +load functions for startup: "
+ << objcLoadSectionIdxs.size() << "\n";
+ }
+
auto result = BPOrdererMachO().computeOrder(
profilePath, compressionSortSpecs, forFunctionCompression,
forDataCompression, compressionSortStartupFunctions, verbose, sections,
- rootSymbolToSectionIdxs, initialStartupSectionIdxs.getArrayRef());
+ rootSymbolToSectionIdxs, initialStartupSectionIdxs.getArrayRef(),
+ objcLoadSectionIdxs);
// BP already orders cold sections after non-cold via separate buckets.
// Unset isCold on sections that received a BP priority so Writer.cpp's
// stable_partition doesn't re-partition them. Sections without a BP priority
diff --git a/lld/MachO/BPSectionOrderer.h b/lld/MachO/BPSectionOrderer.h
index e4b8dd0575913..69dc8b9acb47f 100644
--- a/lld/MachO/BPSectionOrderer.h
+++ b/lld/MachO/BPSectionOrderer.h
@@ -31,7 +31,8 @@ llvm::DenseMap<const InputSection *, int> runBalancedPartitioning(
llvm::StringRef profilePath,
llvm::ArrayRef<BPCompressionSortSpec> compressionSortSpecs,
bool forFunctionCompression, bool forDataCompression,
- bool compressionSortStartupFunctions, bool sortInitializers, bool verbose);
+ bool compressionSortStartupFunctions, bool sortInitializers,
+ bool sortObjCLoadMethods, bool verbose);
} // namespace lld::macho
diff --git a/lld/MachO/Config.h b/lld/MachO/Config.h
index 6048b8ea3365c..a66f193ef68ac 100644
--- a/lld/MachO/Config.h
+++ b/lld/MachO/Config.h
@@ -234,6 +234,7 @@ struct Configuration {
llvm::StringRef irpgoProfilePath;
bool bpStartupFunctionSort = false;
bool bpStartupSortInitializers = false;
+ bool bpStartupSortObjCLoadMethods = false;
bool bpCompressionSortStartupFunctions = false;
bool bpFunctionOrderForCompression = false;
bool bpDataOrderForCompression = false;
diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp
index a75576af51fc6..f7bb70d0daec4 100644
--- a/lld/MachO/Driver.cpp
+++ b/lld/MachO/Driver.cpp
@@ -2105,6 +2105,9 @@ bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
config->bpStartupSortInitializers =
args.hasFlag(OPT_bp_startup_sort_initializers,
OPT_no_bp_startup_sort_initializers, false);
+ config->bpStartupSortObjCLoadMethods =
+ args.hasFlag(OPT_bp_startup_sort_objc_load_methods,
+ OPT_no_bp_startup_sort_objc_load_methods, false);
if (const Arg *arg = args.getLastArg(OPT_bp_startup_sort)) {
StringRef startupSortStr = arg->getValue();
if (startupSortStr == "function") {
@@ -2122,6 +2125,9 @@ bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
if (!config->bpStartupFunctionSort && config->bpStartupSortInitializers)
error("--bp-startup-sort-initializers must be used with "
"--bp-startup-sort=function");
+ if (!config->bpStartupFunctionSort && config->bpStartupSortObjCLoadMethods)
+ error("--bp-startup-sort-objc-load-methods must be used with "
+ "--bp-startup-sort=function");
if (config->irpgoProfilePath.empty() && config->bpStartupFunctionSort)
error("--bp-startup-sort=function must be used with "
"--irpgo-profile");
diff --git a/lld/MachO/ObjC.cpp b/lld/MachO/ObjC.cpp
index 6a0f2f6347991..25e5536c0f322 100644
--- a/lld/MachO/ObjC.cpp
+++ b/lld/MachO/ObjC.cpp
@@ -18,6 +18,8 @@
#include "lld/Common/ErrorHandler.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/Bitcode/BitcodeReader.h"
+#include "llvm/Support/Endian.h"
+#include "llvm/Support/MathExtras.h"
#include "llvm/Support/TimeProfiler.h"
using namespace llvm;
@@ -323,6 +325,135 @@ void objc::checkCategories() {
}
}
+SmallVector<InputSection *> objc::getLoadMethodSections() {
+ CategoryLayout catLayout(target->wordSize);
+ ClassLayout classLayout(target->wordSize);
+ ROClassLayout roClassLayout(target->wordSize);
+ ListHeaderLayout listHeaderLayout(target->wordSize);
+ MethodLayout methodLayout(target->wordSize);
+ SetVector<InputSection *> loadMethods;
+
+ struct SectionLocation {
+ InputSection *isec = nullptr;
+ uint64_t offset = 0;
+ };
+
+ auto getRelocationTarget = [](const Relocation *reloc,
+ int64_t extraAddend = 0) -> SectionLocation {
+ if (!reloc)
+ return {};
+ InputSection *isec = reloc->getReferentInputSection();
+ if (!isec)
+ return {};
+ auto [offset, overflow] =
+ llvm::AddOverflow<int64_t>(reloc->addend, extraAddend);
+ if (overflow)
+ return {};
+ if (auto *sym = reloc->referent.dyn_cast<Symbol *>()) {
+ auto *defined = dyn_cast<Defined>(sym);
+ if (!defined || defined->value > INT64_MAX)
+ return {};
+ auto added = llvm::AddOverflow<int64_t>(
+ offset, static_cast<int64_t>(defined->value));
+ if (added.second)
+ return {};
+ offset = added.first;
+ }
+ if (offset < 0 || static_cast<uint64_t>(offset) > isec->data.size())
+ return {};
+ return {isec, static_cast<uint64_t>(offset)};
+ };
+
+ auto getRelocationTargetAt = [&](InputSection *isec,
+ uint32_t offset) -> SectionLocation {
+ const Relocation *reloc = isec->getRelocAt(offset);
+ if (!reloc || !target->hasAttr(reloc->type, RelocAttrBits::SUBTRAHEND))
+ return getRelocationTarget(reloc);
+
+ SectionLocation subtrahend = getRelocationTarget(reloc);
+ // parseRelocations stores the paired UNSIGNED minuend immediately after
+ // the SUBTRACTOR; the suffix does not carry the relocation field offset.
+ const Relocation *minuend = reloc + 1;
+ if (!subtrahend.isec || subtrahend.isec != isec ||
+ subtrahend.offset > INT64_MAX)
+ return {};
+ int64_t extraAddend =
+ static_cast<int64_t>(offset) - static_cast<int64_t>(subtrahend.offset);
+ return getRelocationTarget(minuend, extraAddend);
+ };
+
+ auto getReferent = [&](SectionLocation location,
+ uint32_t fieldOffset) -> SectionLocation {
+ if (!location.isec || location.offset + fieldOffset > UINT32_MAX ||
+ location.offset + fieldOffset >= location.isec->data.size())
+ return {};
+ return getRelocationTargetAt(
+ location.isec, static_cast<uint32_t>(location.offset + fieldOffset));
+ };
+
+ auto getString = [](SectionLocation location) -> StringRef {
+ if (!location.isec || location.offset >= location.isec->data.size())
+ return {};
+ if (auto *strings = dyn_cast<CStringInputSection>(location.isec))
+ return strings->getStringRefAtOffset(location.offset);
+ ArrayRef<uint8_t> data = location.isec->data.slice(location.offset);
+ const char *string = reinterpret_cast<const char *>(data.data());
+ return StringRef(string, strnlen(string, data.size()));
+ };
+
+ auto collectLoadMethod = [&](SectionLocation methods) {
+ if (!methods.isec ||
+ methods.offset + listHeaderLayout.totalSize > methods.isec->data.size())
+ return;
+ const uint8_t *header = methods.isec->data.data() + methods.offset;
+ uint32_t sizeAndFlags = support::endian::read32le(header);
+ uint32_t entrySize = sizeAndFlags & 0xffff;
+ uint32_t entryCount = support::endian::read32le(header + sizeof(uint32_t));
+ bool relative = sizeAndFlags & 0x80000000;
+ uint32_t nameOffset = relative ? 0 : methodLayout.nameOffset;
+ uint32_t implOffset =
+ relative ? 2 * sizeof(uint32_t) : methodLayout.implOffset;
+ uint64_t entriesOffset = methods.offset + listHeaderLayout.totalSize;
+ uint64_t entriesSize = static_cast<uint64_t>(entrySize) * entryCount;
+ if (entrySize <= std::max(nameOffset, implOffset) ||
+ entriesOffset + entriesSize > methods.isec->data.size())
+ return;
+
+ for (uint32_t index = 0; index < entryCount; ++index) {
+ uint64_t entryOffset =
+ entriesOffset + static_cast<uint64_t>(index) * entrySize;
+ SectionLocation name = getRelocationTargetAt(
+ methods.isec, static_cast<uint32_t>(entryOffset + nameOffset));
+ if (getString(name) != "load")
+ continue;
+ InputSection *impl =
+ getRelocationTargetAt(methods.isec,
+ static_cast<uint32_t>(entryOffset + implOffset))
+ .isec;
+ if (impl && isCodeSection(impl))
+ loadMethods.insert(impl->canonical());
+ }
+ };
+
+ auto getClassMethods = [&](SectionLocation klass) {
+ SectionLocation metaClass = getReferent(klass, classLayout.metaClassOffset);
+ SectionLocation roClass = getReferent(metaClass, classLayout.roDataOffset);
+ return getReferent(roClass, roClassLayout.baseMethodsOffset);
+ };
+
+ for (InputSection *listIsec : inputSections) {
+ if (listIsec->getName() == section_names::objcNonLazyClassList) {
+ for (const Relocation &reloc : listIsec->relocs)
+ collectLoadMethod(getClassMethods(getRelocationTarget(&reloc)));
+ } else if (listIsec->getName() == section_names::objcNonLazyCatList) {
+ for (const Relocation &reloc : listIsec->relocs)
+ collectLoadMethod(getReferent(getRelocationTarget(&reloc),
+ catLayout.classMethodsOffset));
+ }
+ }
+ return SmallVector<InputSection *>(loadMethods.begin(), loadMethods.end());
+}
+
namespace {
class ObjcCategoryMerger {
diff --git a/lld/MachO/ObjC.h b/lld/MachO/ObjC.h
index db259a82fbbdb..b9aafeb0cd9f6 100644
--- a/lld/MachO/ObjC.h
+++ b/lld/MachO/ObjC.h
@@ -9,10 +9,13 @@
#ifndef LLD_MACHO_OBJC_H
#define LLD_MACHO_OBJC_H
+#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/MemoryBuffer.h"
namespace lld::macho {
+class InputSection;
+
namespace objc {
namespace symbol_names {
@@ -40,6 +43,7 @@ constexpr const char swift_objc_klass[] = "_$s";
// Check for duplicate method names within related categories / classes.
void checkCategories();
void mergeCategories();
+llvm::SmallVector<InputSection *> getLoadMethodSections();
void doCleanup();
} // namespace objc
diff --git a/lld/MachO/Options.td b/lld/MachO/Options.td
index 72da037f2d8d4..696bf2649707d 100644
--- a/lld/MachO/Options.td
+++ b/lld/MachO/Options.td
@@ -148,6 +148,12 @@ def bp_startup_sort_initializers: Flag<["--"], "bp-startup-sort-initializers">,
def no_bp_startup_sort_initializers: Flag<["--"], "no-bp-startup-sort-initializers">,
HelpText<"Do not include Mach-O initializer functions in balanced-partitioning startup sorting">,
Group<grp_lld>;
+def bp_startup_sort_objc_load_methods: Flag<["--"], "bp-startup-sort-objc-load-methods">,
+ HelpText<"Include Objective-C +load implementations as a balanced-partitioning startup prefix">,
+ Group<grp_lld>;
+def no_bp_startup_sort_objc_load_methods: Flag<["--"], "no-bp-startup-sort-objc-load-methods">,
+ HelpText<"Do not include Objective-C +load implementations as a balanced-partitioning startup prefix">,
+ Group<grp_lld>;
def bp_compression_sort_startup_functions: Flag<["--"], "bp-compression-sort-startup-functions">,
HelpText<"Order startup functions to improve compressed size in addition to startup time">,
Group<grp_lld>;
diff --git a/lld/MachO/SectionPriorities.cpp b/lld/MachO/SectionPriorities.cpp
index 481d69eca52f1..24d36d763e7c8 100644
--- a/lld/MachO/SectionPriorities.cpp
+++ b/lld/MachO/SectionPriorities.cpp
@@ -381,7 +381,8 @@ macho::PriorityBuilder::buildInputSectionPriorities() {
config->bpCompressionSortSpecs, config->bpFunctionOrderForCompression,
config->bpDataOrderForCompression,
config->bpCompressionSortStartupFunctions,
- config->bpStartupSortInitializers, config->bpVerboseSectionOrderer);
+ config->bpStartupSortInitializers, config->bpStartupSortObjCLoadMethods,
+ config->bpVerboseSectionOrderer);
} else if (config->callGraphProfileSort) {
// Sort sections by the profile data provided by __LLVM,__cg_profile
// sections.
diff --git a/lld/include/lld/Common/BPSectionOrdererBase.inc b/lld/include/lld/Common/BPSectionOrdererBase.inc
index 53f45dd7fbb75..7231e2c1db5cc 100644
--- a/lld/include/lld/Common/BPSectionOrdererBase.inc
+++ b/lld/include/lld/Common/BPSectionOrdererBase.inc
@@ -69,7 +69,8 @@ template <class D> struct BPOrderer {
llvm::ArrayRef<Section *> sections,
const DenseMap<CachedHashStringRef, std::set<unsigned>>
&rootSymbolToSectionIdxs,
- llvm::ArrayRef<unsigned> initialStartupSectionIdxs)
+ llvm::ArrayRef<unsigned> initialStartupSectionIdxs,
+ llvm::ArrayRef<unsigned> prefixStartupSectionIdxs)
-> llvm::DenseMap<const Section *, int>;
std::optional<StringRef> static getResolvedLinkageName(StringRef name) {
@@ -166,7 +167,8 @@ auto BPOrderer<D>::computeOrder(
ArrayRef<Section *> sections,
const DenseMap<CachedHashStringRef, std::set<unsigned>>
&rootSymbolToSectionIdxs,
- ArrayRef<unsigned> initialStartupSectionIdxs)
+ ArrayRef<unsigned> initialStartupSectionIdxs,
+ ArrayRef<unsigned> prefixStartupSectionIdxs)
-> DenseMap<const Section *, int> {
TimeTraceScope timeScope("Setup Balanced Partitioning");
DenseMap<const void *, uint64_t> sectionToIdx;
@@ -385,6 +387,13 @@ auto BPOrderer<D>::computeOrder(
unsigned numCompressionSections = 0, compressionSize = 0;
unsigned numDuplicateCompressionSections = 0, duplicateCompressionSize = 0;
SetVector<const Section *> orderedSections;
+ for (unsigned sectionIdx : prefixStartupSectionIdxs) {
+ const auto *isec = sections[sectionIdx];
+ if (orderedSections.insert(isec)) {
+ startupSize += D::getSize(*isec);
+ ++numStartupSections;
+ }
+ }
// Order startup functions,
for (auto &node : nodesForStartup) {
const auto *isec = sections[node.Id];
diff --git a/lld/test/MachO/bp-startup-sort-objc-load-methods.s b/lld/test/MachO/bp-startup-sort-objc-load-methods.s
new file mode 100644
index 0000000000000..2c202f3b7ce32
--- /dev/null
+++ b/lld/test/MachO/bp-startup-sort-objc-load-methods.s
@@ -0,0 +1,162 @@
+# REQUIRES: aarch64
+
+# RUN: rm -rf %t && split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos11.0 %t/input.s -o %t/input.o
+# RUN: llvm-profdata merge %t/profile.proftext -o %t/profile.profdata
+# RUN: not %lld -arch arm64 -lSystem -e _main -o /dev/null %t/input.o --irpgo-profile=%t/profile.profdata --bp-startup-sort-objc-load-methods 2>&1 | FileCheck %s --check-prefix=ERROR
+# RUN: %lld -arch arm64 -lSystem -e _main -o %t/out %t/input.o --irpgo-profile=%t/profile.profdata --bp-startup-sort=function --bp-startup-sort-objc-load-methods --verbose-bp-section-orderer 2> %t/verbose
+# RUN: FileCheck %s --input-file=%t/verbose --check-prefix=VERBOSE
+# RUN: llvm-nm --numeric-sort --format=just-symbols %t/out | FileCheck %s --check-prefix=ORDER
+
+# ERROR: --bp-startup-sort-objc-load-methods must be used with --bp-startup-sort=function
+# VERBOSE: Objective-C +load functions for startup: 2
+# VERBOSE: Functions for startup: 3
+# ORDER: _class_load
+# ORDER-NEXT: _category_load
+# ORDER-NEXT: _profile_hot
+# ORDER-NEXT: _main
+
+#--- input.s
+ .subsections_via_symbols
+
+ .text
+ .globl _main
+_main:
+ ret
+
+ .globl _profile_hot
+_profile_hot:
+ mov w0, #1
+ ret
+
+ .globl _class_load
+_class_load:
+ mov w0, #2
+ ret
+
+ .globl _category_load
+_category_load:
+ mov w0, #3
+ ret
+
+ .section __TEXT,__objc_methname,cstring_literals
+L_load_name:
+ .asciz "load"
+
+ .section __TEXT,__objc_methtype,cstring_literals
+L_method_type:
+ .asciz "v16 at 0:8"
+
+ .section __TEXT,__objc_classname,cstring_literals
+L_class_name:
+ .asciz "Foo"
+L_category_name:
+ .asciz "Category"
+
+ .section __DATA,__objc_data
+ .p2align 3
+L_class_container:
+ .space 8
+ .quad L_metaclass_container + 8
+ .quad 0
+ .quad 0
+ .quad 0
+ .quad L_class_ro_container + 8
+
+L_metaclass_container:
+ .space 8
+ .quad L_metaclass_container + 8
+ .quad L_class_container + 8
+ .quad 0
+ .quad 0
+ .quad L_metaclass_ro_container + 8
+
+ .section __DATA,__objc_const
+ .p2align 3
+L_class_ro_container:
+ .space 8
+ .long 2
+ .long 0
+ .long 0
+ .space 4
+ .quad 0
+ .quad L_class_name
+ .quad 0
+ .quad 0
+ .quad 0
+ .quad 0
+ .quad 0
+
+L_metaclass_ro_container:
+ .space 8
+ .long 3
+ .long 40
+ .long 40
+ .space 4
+ .quad 0
+ .quad L_class_name
+ .quad L_class_methods_container + 8
+ .quad 0
+ .quad 0
+ .quad 0
+ .quad 0
+
+L_class_methods_container:
+ .space 8
+ .long 24
+ .long 1
+ .quad L_load_name
+ .quad L_method_type
+ .quad _class_load
+
+L_category_container:
+ .space 8
+ .quad L_category_name
+ .quad 0
+ .quad 0
+ .quad L_category_methods_container + 8
+ .quad 0
+ .quad 0
+ .quad 0
+ .long 64
+ .space 4
+
+L_category_methods_container:
+ .space 8
+ .long 0x8000000c
+ .long 1
+L_category_method_name:
+ .long L_load_name - L_category_method_name
+L_category_method_type:
+ .long L_method_type - L_category_method_type
+L_category_method_impl:
+ .long _category_load - L_category_method_impl
+
+ .section __DATA,__objc_nlclslist,regular,no_dead_strip
+ .quad L_class_container + 8
+
+ .section __DATA,__objc_nlcatlist,regular,no_dead_strip
+ .quad L_category_container + 8
+
+ .section __DATA,__objc_imageinfo,regular,no_dead_strip
+ .long 0
+ .long 64
+
+#--- profile.proftext
+:ir
+:temporal_prof_traces
+# Num Traces
+1
+# Trace Stream Size:
+1
+# Weight
+1
+profile_hot
+
+profile_hot
+# Func Hash:
+1111
+# Num Counters:
+1
+# Counter Values:
+1
>From a73a645fdddfa8534f17d3756119571d3f729789 Mon Sep 17 00:00:00 2001
From: Karim Alweheshy <karim.alweheshy at gmail.com>
Date: Wed, 5 Aug 2026 07:34:26 +0200
Subject: [PATCH 3/3] [lld][MachO] Sort BP section orderer includes
---
lld/MachO/BPSectionOrderer.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lld/MachO/BPSectionOrderer.cpp b/lld/MachO/BPSectionOrderer.cpp
index a3daeec9b9b70..32ead5ed21251 100644
--- a/lld/MachO/BPSectionOrderer.cpp
+++ b/lld/MachO/BPSectionOrderer.cpp
@@ -12,8 +12,8 @@
#include "OutputSegment.h"
#include "Relocations.h"
#include "Symbols.h"
-#include "Target.h"
#include "SyntheticSections.h"
+#include "Target.h"
#include "lld/Common/BPSectionOrdererBase.inc"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
More information about the llvm-commits
mailing list