[lld] [lld][MachO] Add initializer and +load roots to temporal BP sorting (PR #212110)

via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 4 23:07:37 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lld-macho

Author: Karim Alweheshy (karim-alweheshy)

<details>
<summary>Changes</summary>

## Summary

Add two opt-in sources of linker-known pre-`main` code to Mach-O temporal
Balanced Partitioning:

- `--bp-startup-sort-initializers` adds functions referenced by the emitted
  Mach-O initializer table to the startup objective.
- `--bp-startup-sort-objc-load-methods` discovers Objective-C class and
  category `+load` implementations from non-lazy metadata, adds them to the
  startup objective, and places that eager set in a deterministic contiguous
  prefix.

These functions are guaranteed to run before `main`, but may be absent from a
compiler temporal profile. Both options are disabled by default and require
`--bp-startup-sort=function`.

## Implementation

Initializer discovery uses the inputs to `InitOffsetsSection` when lld emits
`__TEXT,__init_offsets`, and live `S_MOD_INIT_FUNC_POINTERS` inputs otherwise.
Undefined and external targets are ignored because they have no local input
section to order.

Objective-C discovery starts at `__objc_nlclslist` and `__objc_nlcatlist`,
follows the referenced class, metaclass, read-only class, category, and method
list metadata, and selects methods whose selector is `load`. The parser:

- supports absolute and relative method-list encodings, including paired
  Mach-O `SUBTRACTOR`/`UNSIGNED` relocations;
- preserves relocation addends for references to interior metadata;
- canonicalizes ICF-folded implementation sections; and
- bounds method-list parsing using the encoded entry size and count.

Only the discovered `+load` implementations receive the contiguous prefix.

## Testing

- `bp-section-orderer-initializers.s` checks default behavior, opt-in startup
  accounting and symbol order, and rejection without function startup sorting.
- `bp-startup-sort-objc-load-methods.s` covers class and category `+load`
  discovery, interior metadata addends, absolute and relative method lists,
  paired relocations, startup accounting, final symbol order, and flag
  validation.
- The LLVM pre-merge matrix passes on Linux, Linux AArch64, macOS arm64, and
  Windows, together with formatting and ABI checks.


---

Patch is 24.44 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/212110.diff


12 Files Affected:

- (modified) lld/ELF/BPSectionOrderer.cpp (+3-1) 
- (modified) lld/MachO/BPSectionOrderer.cpp (+48-2) 
- (modified) lld/MachO/BPSectionOrderer.h (+2-1) 
- (modified) lld/MachO/Config.h (+2) 
- (modified) lld/MachO/Driver.cpp (+12) 
- (modified) lld/MachO/ObjC.cpp (+131) 
- (modified) lld/MachO/ObjC.h (+4) 
- (modified) lld/MachO/Options.td (+12) 
- (modified) lld/MachO/SectionPriorities.cpp (+1) 
- (modified) lld/include/lld/Common/BPSectionOrdererBase.inc (+18-2) 
- (added) lld/test/MachO/bp-section-orderer-initializers.s (+70) 
- (added) lld/test/MachO/bp-startup-sort-objc-load-methods.s (+162) 


``````````diff
diff --git a/lld/ELF/BPSectionOrderer.cpp b/lld/ELF/BPSectionOrderer.cpp
index 0ce7ceff4348b..671c63ced97a8 100644
--- a/lld/ELF/BPSectionOrderer.cpp
+++ b/lld/ELF/BPSectionOrderer.cpp
@@ -98,5 +98,7 @@ DenseMap<const InputSectionBase *, int> elf::runBalancedPartitioning(
   return orderer.computeOrder(profilePath, compressionSortSpecs,
                               forFunctionCompression, forDataCompression,
                               compressionSortStartupFunctions, verbose,
-                              sections, rootSymbolToSectionIdxs);
+                              sections, rootSymbolToSectionIdxs,
+                              /*initialStartupSectionIdxs=*/{},
+                              /*prefixStartupSectionIdxs=*/{});
 }
diff --git a/lld/MachO/BPSectionOrderer.cpp b/lld/MachO/BPSectionOrderer.cpp
index 6da9985006247..32ead5ed21251 100644
--- a/lld/MachO/BPSectionOrderer.cpp
+++ b/lld/MachO/BPSectionOrderer.cpp
@@ -8,14 +8,17 @@
 
 #include "BPSectionOrderer.h"
 #include "InputSection.h"
+#include "ObjC.h"
 #include "OutputSegment.h"
 #include "Relocations.h"
 #include "Symbols.h"
+#include "SyntheticSections.h"
 #include "Target.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 +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 verbose) {
+    bool compressionSortStartupFunctions, bool sortInitializers,
+    bool sortObjCLoadMethods, bool verbose) {
   // Collect candidate sections and associated symbols.
   SmallVector<InputSection *> sections;
   DenseMap<const InputSection *, unsigned> sectionToIdx;
@@ -182,10 +186,52 @@ 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";
+  }
+
+  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);
+      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 cc63c85cd32d3..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 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 9767cc5e5b6e4..a66f193ef68ac 100644
--- a/lld/MachO/Config.h
+++ b/lld/MachO/Config.h
@@ -233,6 +233,8 @@ 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 2864c6d28fa49..f7bb70d0daec4 100644
--- a/lld/MachO/Driver.cpp
+++ b/lld/MachO/Driver.cpp
@@ -2102,6 +2102,12 @@ 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);
+  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") {
@@ -2116,6 +2122,12 @@ 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->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 b7686d66a258e..696bf2649707d 100644
--- a/lld/MachO/Options.td
+++ b/lld/MachO/Options.td
@@ -142,6 +142,18 @@ 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_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 dea47c76a385e..24d36d763e7c8 100644
--- a/lld/MachO/SectionPriorities.cpp
+++ b/lld/MachO/SectionPriorities.cpp
@@ -381,6 +381,7 @@ macho::PriorityBuilder::buildInputSectionPriorities() {
         config->bpCompressionSortSpecs, config->bpFunctionOrderForCompression,
         config->bpDataOrderForCompression,
         config->bpCompressionSortStartupFunctions,
+        config->bpStartupSortInitializers, config->bpStartupSortObjCLoadMethods,
         config->bpVerboseSectionOrderer);
   } else if (config->callGraphProfileSort) {
     // Sort sections by the profile data provided by __LLVM,__cg_profile
diff --git a/lld/include/lld/Common/BPSectionOrdererBase.inc b/lld/include/lld/Common/BPSectionOrdererBase.inc
index b2e87d6c240fc..7231e2c1db5cc 100644
--- a/lld/include/lld/Common/BPSectionOrdererBase.inc
+++ b/lld/include/lld/Common/BPSectionOrdererBase.inc
@@ -68,7 +68,9 @@ 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::ArrayRef<unsigned> prefixStartupSectionIdxs)
       -> llvm::DenseMap<const Section *, int>;
 
   std::optional<StringRef> static getResolvedLinkageName(StringRef name) {
@@ -164,7 +166,10 @@ 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,
+    ArrayRef<unsigned> prefixStartupSectionIdxs)
+    -> DenseMap<const Section *, int> {
   TimeTraceScope timeScope("Setup Balanced Partitioning");
   DenseMap<const void *, uint64_t> sectionToIdx;
   for (auto [i, isec] : llvm::enumerate(sections))
@@ -197,6 +202,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(
@@ -378,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-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 - ...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/212110


More information about the llvm-commits mailing list