[lld] [lld][MachO] Order objc stubs by caller priority (PR #216373)
Kyungwoo Lee via llvm-commits
llvm-commits at lists.llvm.org
Fri Aug 14 11:14:16 PDT 2026
https://github.com/kyulee-com created https://github.com/llvm/llvm-project/pull/216373
__objc_stubs is synthetic, so normal input-section sorting does not affect its entries. Record branch-reloc callers, inherit the earliest live caller section priority, stable-sort objc stub symbols before address assignment, and update their symbol values.
This intentionally handles __objc_stubs only. General __stubs ordering is separate because stubsIndex also drives lazy pointers, indirect symbols, and bind/rebase offsets.
>From 12c7971d3a1347919d2b4e391347cd3e6c63377c Mon Sep 17 00:00:00 2001
From: Kyungwoo Lee <kyulee at meta.com>
Date: Fri, 14 Aug 2026 10:19:03 -0700
Subject: [PATCH] [lld][MachO] Order objc stubs by caller priority
__objc_stubs is synthetic, so normal input-section sorting does not affect its entries. Record branch-reloc callers, inherit the earliest live caller section priority, stable-sort objc stub symbols before address assignment, and update their symbol values.
This intentionally handles __objc_stubs only. General __stubs ordering is separate because stubsIndex also drives lazy pointers, indirect symbols, and bind/rebase offsets.
---
lld/MachO/SectionPriorities.h | 7 ++++
lld/MachO/SyntheticSections.cpp | 10 ++++++
lld/MachO/SyntheticSections.h | 3 ++
lld/MachO/Writer.cpp | 45 ++++++++++++++++++++++++-
lld/test/MachO/objc-stubs-order-file.s | 46 ++++++++++++++++++++++++++
5 files changed, 110 insertions(+), 1 deletion(-)
create mode 100644 lld/test/MachO/objc-stubs-order-file.s
diff --git a/lld/MachO/SectionPriorities.h b/lld/MachO/SectionPriorities.h
index 24d2dbc47e498..35b4fb51d4b79 100644
--- a/lld/MachO/SectionPriorities.h
+++ b/lld/MachO/SectionPriorities.h
@@ -12,10 +12,12 @@
#include "InputSection.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/MapVector.h"
+#include "llvm/ADT/SetVector.h"
namespace lld::macho {
using SectionPair = std::pair<const InputSection *, const InputSection *>;
+class Symbol;
class PriorityBuilder {
public:
@@ -76,6 +78,11 @@ class PriorityBuilder {
// contains.
llvm::DenseMap<const InputSection *, int> buildInputSectionPriorities();
+ /// Sections that branch to each objc stub. Objc stubs are synthetic and carry
+ /// no profile data, so they are ordered from the priority of their callers.
+ llvm::MapVector<const Symbol *, llvm::SetVector<const InputSection *>>
+ objcStubCallers;
+
private:
// The symbol with the smallest priority should be ordered first in the output
// section (modulo input section contiguity constraints).
diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp
index ba06a95bb753c..c7459b6af3abb 100644
--- a/lld/MachO/SyntheticSections.cpp
+++ b/lld/MachO/SyntheticSections.cpp
@@ -944,6 +944,16 @@ uint64_t ObjCStubsSection::getSize() const {
return stubSize * symbols.size();
}
+void ObjCStubsSection::reorderSymbols(ArrayRef<Defined *> newOrder) {
+ assert(newOrder.size() == symbols.size() && "must be a permutation");
+ auto stubSize = config->objcStubsMode == ObjCStubsMode::fast
+ ? target->objcStubsFastSize
+ : target->objcStubsSmallSize;
+ symbols.assign(newOrder.begin(), newOrder.end());
+ for (auto [idx, sym] : llvm::enumerate(symbols))
+ sym->value = idx * stubSize;
+}
+
void ObjCStubsSection::writeTo(uint8_t *buf) const {
uint64_t stubOffset = 0;
for (Defined *sym : symbols) {
diff --git a/lld/MachO/SyntheticSections.h b/lld/MachO/SyntheticSections.h
index e649d1275f821..0a72d4814a105 100644
--- a/lld/MachO/SyntheticSections.h
+++ b/lld/MachO/SyntheticSections.h
@@ -346,6 +346,9 @@ class ObjCStubsSection final : public SyntheticSection {
static bool isObjCStubSymbol(Symbol *sym);
static StringRef getMethname(Symbol *sym);
+ ArrayRef<Defined *> getSymbols() const { return symbols; }
+ void reorderSymbols(ArrayRef<Defined *> newOrder);
+
private:
std::vector<Defined *> symbols;
Symbol *objcMsgSend = nullptr;
diff --git a/lld/MachO/Writer.cpp b/lld/MachO/Writer.cpp
index 89b6d467d0d44..4cde030a2cf92 100644
--- a/lld/MachO/Writer.cpp
+++ b/lld/MachO/Writer.cpp
@@ -729,8 +729,12 @@ void Writer::scanRelocations() {
if (auto *undefined = dyn_cast<Undefined>(sym))
treatUndefinedSymbol(*undefined, isec, r.offset);
// treatUndefinedSymbol() can replace sym with a DylibSymbol; re-check.
- if (!isa<Undefined>(sym) && validateSymbolRelocation(sym, isec, r))
+ if (!isa<Undefined>(sym) && validateSymbolRelocation(sym, isec, r)) {
+ if (target->hasAttr(r.type, RelocAttrBits::BRANCH) &&
+ ObjCStubsSection::isObjCStubSymbol(sym))
+ priorityBuilder.objcStubCallers[sym].insert(isec);
prepareSymbolRelocation(sym, isec, r);
+ }
} else {
if (!r.pcrel) {
if (config->emitChainedFixups)
@@ -974,6 +978,43 @@ template <class LP> void Writer::createLoadCommands() {
// Sorting only can happen once all outputs have been collected. Here we sort
// segments, output sections within each segment, and input sections within each
// output segment.
+static void orderObjCStubsByCallerPriority(
+ const DenseMap<const InputSection *, int> &prios) {
+ if (prios.empty() || !in.objcStubs->isNeeded())
+ return;
+
+ DenseMap<const Symbol *, int> stubPriority;
+ for (const auto &[stub, callers] : priorityBuilder.objcStubCallers) {
+ for (const InputSection *caller : callers) {
+ if (!caller->isLive(0))
+ continue;
+ auto prio = prios.find(caller);
+ if (prio == prios.end())
+ continue;
+ auto existing = stubPriority.find(stub);
+ if (existing == stubPriority.end() || prio->second < existing->second)
+ stubPriority[stub] = prio->second;
+ }
+ }
+ if (stubPriority.empty())
+ return;
+
+ ArrayRef<Defined *> symbols = in.objcStubs->getSymbols();
+ SmallVector<Defined *> order(symbols.begin(), symbols.end());
+ llvm::stable_sort(order, [&](const Defined *a, const Defined *b) {
+ auto ia = stubPriority.find(a);
+ auto ib = stubPriority.find(b);
+ bool hasA = ia != stubPriority.end();
+ bool hasB = ib != stubPriority.end();
+ if (hasA != hasB)
+ return hasA;
+ if (!hasA)
+ return false;
+ return ia->second < ib->second;
+ });
+ in.objcStubs->reorderSymbols(order);
+}
+
static void sortSegmentsAndSections() {
TimeTraceScope timeScope("Sort segments and sections");
sortOutputSegments();
@@ -981,6 +1022,8 @@ static void sortSegmentsAndSections() {
DenseMap<const InputSection *, int> isecPriorities =
priorityBuilder.buildInputSectionPriorities();
+ orderObjCStubsByCallerPriority(isecPriorities);
+
uint32_t sectionIndex = 0;
for (OutputSegment *seg : outputSegments) {
seg->sortOutputSections();
diff --git a/lld/test/MachO/objc-stubs-order-file.s b/lld/test/MachO/objc-stubs-order-file.s
new file mode 100644
index 0000000000000..22168d35f0eda
--- /dev/null
+++ b/lld/test/MachO/objc-stubs-order-file.s
@@ -0,0 +1,46 @@
+# REQUIRES: aarch64
+
+# RUN: rm -rf %t && split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/a.s -o %t/a.o
+# RUN: echo _hot > %t/order
+
+# RUN: %lld -arch arm64 -e _main -U _objc_msgSend -o %t/base.out %t/a.o \
+# RUN: -objc_stubs_small
+# RUN: llvm-objdump --no-show-raw-insn --section=__TEXT,__objc_stubs --macho \
+# RUN: %t/base.out | FileCheck %s --check-prefix=BASE
+
+# RUN: %lld -arch arm64 -e _main -U _objc_msgSend -o %t/ordered.out %t/a.o \
+# RUN: -objc_stubs_small -order_file %t/order
+# RUN: llvm-objdump --no-show-raw-insn --section=__TEXT,__objc_stubs --macho \
+# RUN: %t/ordered.out | FileCheck %s --check-prefix=ORDERED
+
+# BASE: Contents of (__TEXT,__objc_stubs) section
+# BASE-NEXT: _objc_msgSend$cold:
+# BASE: _objc_msgSend$hot:
+
+# ORDERED: Contents of (__TEXT,__objc_stubs) section
+# ORDERED-NEXT: _objc_msgSend$hot:
+# ORDERED: _objc_msgSend$cold:
+
+#--- a.s
+.text
+.globl _cold
+.p2align 2
+_cold:
+ bl _objc_msgSend$cold
+ ret
+
+.globl _hot
+.p2align 2
+_hot:
+ bl _objc_msgSend$hot
+ ret
+
+.globl _main
+.p2align 2
+_main:
+ bl _cold
+ bl _hot
+ ret
+
+.subsections_via_symbols
More information about the llvm-commits
mailing list