[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