[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