From fd609e5d33eae0df48d37a215f13a78bab3f8ebb Mon Sep 17 00:00:00 2001 From: Zhaoxuan Jiang Date: Thu, 2 Apr 2026 08:53:08 +0800 Subject: [PATCH] [lld] Glob-based BP compression sort groups (#185661) Add --bp-compression-sort-section=[=[=]] to let users split input sections into multiple compression groups, run balanced partitioning independently per group, and leave out sections that are poor candidates for BP. This replaces the old coarse --bp-compression-sort with a more explicit, user-controlled one. In ELF, the glob matches input section names (.text.unlikely.cold1). In Mach-O, it matches the concatenated segment+section name (__TEXT__text). layout_priority controls group placement in the final layout. match_priority resolves conflicts when multiple globs match the same section: explicit priority beats positional matching, and among positional specs the last match wins. A CRTP hook getCompressionSubgroupKey() allows backends to further subdivide glob groups into independent BP instances. This allows Mach-O backend to separate cold functions via N_COLD_FUNC in the future. The deprecated --bp-compression-sort option keeps its existing function/data behavior by assigning sections to fixed legacy groups. --- lld/ELF/BPSectionOrderer.cpp | 12 +- lld/ELF/BPSectionOrderer.h | 11 +- lld/ELF/Config.h | 2 + lld/ELF/Driver.cpp | 48 ++++ lld/ELF/Options.td | 5 +- lld/ELF/Writer.cpp | 5 +- lld/MachO/BPSectionOrderer.cpp | 18 +- lld/MachO/BPSectionOrderer.h | 11 +- lld/MachO/Config.h | 2 + lld/MachO/Driver.cpp | 44 ++++ lld/MachO/Options.td | 14 +- lld/MachO/SectionPriorities.cpp | 5 +- lld/docs/ReleaseNotes.rst | 15 ++ lld/include/lld/Common/BPSectionOrdererBase.h | 48 ++++ .../lld/Common/BPSectionOrdererBase.inc | 215 +++++++++++------- lld/test/ELF/bp-section-orderer-cold.s | 208 +++++++++++++++++ lld/test/ELF/bp-section-orderer.s | 10 + lld/test/MachO/bp-section-orderer-errs.s | 12 + lld/test/MachO/compression-order-sections.s | 112 +++++++++ 19 files changed, 687 insertions(+), 110 deletions(-) create mode 100644 lld/include/lld/Common/BPSectionOrdererBase.h create mode 100644 lld/test/ELF/bp-section-orderer-cold.s create mode 100644 lld/test/MachO/compression-order-sections.s diff --git a/lld/ELF/BPSectionOrderer.cpp b/lld/ELF/BPSectionOrderer.cpp index 966108ffb0df..0ce7ceff4348 100644 --- a/lld/ELF/BPSectionOrderer.cpp +++ b/lld/ELF/BPSectionOrderer.cpp @@ -32,6 +32,7 @@ struct BPOrdererELF : lld::BPOrderer { static bool isCodeSection(const Section &sec) { return sec.flags & ELF::SHF_EXECINSTR; } + static StringRef getSectionName(const Section &sec) { return sec.name; } ArrayRef getSymbols(const Section &sec) { auto it = secToSym.find(&sec); if (it == secToSym.end()) @@ -63,9 +64,10 @@ struct BPOrdererELF : lld::BPOrderer { } // namespace DenseMap elf::runBalancedPartitioning( - Ctx &ctx, StringRef profilePath, bool forFunctionCompression, - bool forDataCompression, bool compressionSortStartupFunctions, - bool verbose) { + Ctx &ctx, StringRef profilePath, + ArrayRef compressionSortSpecs, + bool forFunctionCompression, bool forDataCompression, + bool compressionSortStartupFunctions, bool verbose) { // Collect candidate sections and associated symbols. SmallVector sections; DenseMap> rootSymbolToSectionIdxs; @@ -93,8 +95,8 @@ DenseMap elf::runBalancedPartitioning( for (ELFFileBase *file : ctx.objectFiles) for (Symbol *sym : file->getLocalSymbols()) addSection(*sym); - return orderer.computeOrder(profilePath, forFunctionCompression, - forDataCompression, + return orderer.computeOrder(profilePath, compressionSortSpecs, + forFunctionCompression, forDataCompression, compressionSortStartupFunctions, verbose, sections, rootSymbolToSectionIdxs); } diff --git a/lld/ELF/BPSectionOrderer.h b/lld/ELF/BPSectionOrderer.h index a0cb1360005a..ed212550ed55 100644 --- a/lld/ELF/BPSectionOrderer.h +++ b/lld/ELF/BPSectionOrderer.h @@ -14,6 +14,8 @@ #ifndef LLD_ELF_BPSECTION_ORDERER_H #define LLD_ELF_BPSECTION_ORDERER_H +#include "lld/Common/BPSectionOrdererBase.h" +#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/StringRef.h" @@ -27,10 +29,11 @@ class InputSectionBase; /// It is important that -ffunction-sections and -fdata-sections compiler flags /// are used to ensure functions and data are in their own sections and thus /// can be reordered. -llvm::DenseMap -runBalancedPartitioning(Ctx &ctx, llvm::StringRef profilePath, - bool forFunctionCompression, bool forDataCompression, - bool compressionSortStartupFunctions, bool verbose); +llvm::DenseMap runBalancedPartitioning( + Ctx &ctx, llvm::StringRef profilePath, + llvm::ArrayRef compressionSortSpecs, + bool forFunctionCompression, bool forDataCompression, + bool compressionSortStartupFunctions, bool verbose); } // namespace lld::elf diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h index 8df13a61ea3d..cf3cde1cf269 100644 --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -9,6 +9,7 @@ #ifndef LLD_ELF_CONFIG_H #define LLD_ELF_CONFIG_H +#include "lld/Common/BPSectionOrdererBase.h" #include "lld/Common/CommonLinkerContext.h" #include "lld/Common/ErrorHandler.h" #include "llvm/ADT/CachedHashString.h" @@ -305,6 +306,7 @@ struct Config { bool bpCompressionSortStartupFunctions = false; bool bpFunctionOrderForCompression = false; bool bpDataOrderForCompression = false; + llvm::SmallVector bpCompressionSortSpecs; bool bpVerboseSectionOrderer = false; bool branchToBranch = false; bool checkSections; diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp index f7f880ae9bc7..949314fd70ed 100644 --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -1209,6 +1209,54 @@ static CGProfileSortKind getCGProfileSortKind(Ctx &ctx, } static void parseBPOrdererOptions(Ctx &ctx, opt::InputArgList &args) { + auto addCompressionSortSpec = [&](StringRef value) { + SmallVector parts; + value.split(parts, '='); + + StringRef globString = parts[0]; + unsigned layoutPriority = 0; + std::optional matchPriority; + + if (parts.size() > 1 && !parts[1].empty()) { + if (!to_integer(parts[1], layoutPriority)) { + ErrAlways(ctx) << "--bp-compression-sort-section: expected integer " + "for layout_priority, got '" + << parts[1] << "'"; + return; + } + } + if (parts.size() > 2 && !parts[2].empty()) { + unsigned mp; + if (!to_integer(parts[2], mp)) { + ErrAlways(ctx) << "--bp-compression-sort-section: expected integer " + "for match_priority, got '" + << parts[2] << "'"; + return; + } + matchPriority = mp; + } + if (parts.size() > 3) { + ErrAlways(ctx) << "--bp-compression-sort-section: too many '=' in '" + << value << "'"; + return; + } + + auto spec = BPCompressionSortSpec::create(globString, layoutPriority, + matchPriority); + if (!spec) { + ErrAlways(ctx) << "--bp-compression-sort-section: " + << toString(spec.takeError()); + return; + } + ctx.arg.bpCompressionSortSpecs.emplace_back(std::move(*spec)); + }; + + for (auto *arg : args.filtered(OPT_bp_compression_sort_section)) + addCompressionSortSpec(arg->getValue()); + if (!ctx.arg.bpCompressionSortSpecs.empty() && + args.hasArg(OPT_call_graph_ordering_file)) + ErrAlways(ctx) << "--bp-compression-sort-section is incompatible with " + "--call-graph-ordering-file"; if (auto *arg = args.getLastArg(OPT_bp_compression_sort)) { StringRef s = arg->getValue(); if (s == "function") { diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td index d262f34c9d1d..64c42eb49607 100644 --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -148,7 +148,10 @@ def : FF<"no-call-graph-profile-sort">, Alias, AliasArg defm irpgo_profile: EEq<"irpgo-profile", "Read a temporary profile file for use with --bp-startup-sort=">; def bp_compression_sort: JJ<"bp-compression-sort=">, MetaVarName<"[none,function,data,both]">, - HelpText<"Improve Lempel-Ziv compression by grouping similar sections together, resulting in a smaller compressed app size">; + HelpText<"Improve Lempel-Ziv compression by grouping similar sections together. Deprecated. Please use --bp-compression-sort-section">; +def bp_compression_sort_section: JJ<"bp-compression-sort-section=">, + MetaVarName<"[=[=]]">, + HelpText<"Reorder input sections whose name matches to improve Lempel-Ziv compression, while letting users split sections into groups, run BP independently per group, and exclude unsuitable sections. layout_priority controls group placement order (default 0). match_priority resolves conflicts when multiple globs match (default: last match wins). May be specified multiple times">; def bp_startup_sort: JJ<"bp-startup-sort=">, MetaVarName<"[none,function]">, HelpText<"Utilize a temporal profile file to reduce page faults during program startup">; diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp index 6b0382090f78..ccc7aaec5aa4 100644 --- a/lld/ELF/Writer.cpp +++ b/lld/ELF/Writer.cpp @@ -1109,11 +1109,12 @@ static void maybeShuffle(Ctx &ctx, static DenseMap buildSectionOrder(Ctx &ctx) { DenseMap sectionOrder; if (ctx.arg.bpStartupFunctionSort || ctx.arg.bpFunctionOrderForCompression || - ctx.arg.bpDataOrderForCompression) { + ctx.arg.bpDataOrderForCompression || + !ctx.arg.bpCompressionSortSpecs.empty()) { TimeTraceScope timeScope("Balanced Partitioning Section Orderer"); sectionOrder = runBalancedPartitioning( ctx, ctx.arg.bpStartupFunctionSort ? ctx.arg.irpgoProfilePath : "", - ctx.arg.bpFunctionOrderForCompression, + ctx.arg.bpCompressionSortSpecs, ctx.arg.bpFunctionOrderForCompression, ctx.arg.bpDataOrderForCompression, ctx.arg.bpCompressionSortStartupFunctions, ctx.arg.bpVerboseSectionOrderer); diff --git a/lld/MachO/BPSectionOrderer.cpp b/lld/MachO/BPSectionOrderer.cpp index c9c6c1c62bdf..a9b5d07ac55e 100644 --- a/lld/MachO/BPSectionOrderer.cpp +++ b/lld/MachO/BPSectionOrderer.cpp @@ -34,6 +34,13 @@ struct BPOrdererMachO : lld::BPOrderer { static bool isCodeSection(const Section &sec) { return macho::isCodeSection(&sec); } + static std::string getSectionName(const Section &sec) { + return (sec.getSegName() + sec.getName()).str(); + } + // TODO: Use N_COLD_FUNC to separate cold code into a different subgroup. + static std::string getCompressionSubgroupKey(const Section &sec) { + return ""; + } static ArrayRef getSymbols(const Section &sec) { return sec.symbols; } @@ -107,7 +114,8 @@ private: } // namespace DenseMap lld::macho::runBalancedPartitioning( - StringRef profilePath, bool forFunctionCompression, bool forDataCompression, + StringRef profilePath, ArrayRef compressionSortSpecs, + bool forFunctionCompression, bool forDataCompression, bool compressionSortStartupFunctions, bool verbose) { // Collect candidate sections and associated symbols. SmallVector sections; @@ -140,8 +148,8 @@ DenseMap lld::macho::runBalancedPartitioning( } } - return BPOrdererMachO().computeOrder(profilePath, forFunctionCompression, - forDataCompression, - compressionSortStartupFunctions, verbose, - sections, rootSymbolToSectionIdxs); + return BPOrdererMachO().computeOrder( + profilePath, compressionSortSpecs, forFunctionCompression, + forDataCompression, compressionSortStartupFunctions, verbose, sections, + rootSymbolToSectionIdxs); } diff --git a/lld/MachO/BPSectionOrderer.h b/lld/MachO/BPSectionOrderer.h index a27f605cb118..cc63c85cd32d 100644 --- a/lld/MachO/BPSectionOrderer.h +++ b/lld/MachO/BPSectionOrderer.h @@ -14,6 +14,8 @@ #ifndef LLD_MACHO_BPSECTION_ORDERER_H #define LLD_MACHO_BPSECTION_ORDERER_H +#include "lld/Common/BPSectionOrdererBase.h" +#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/StringRef.h" @@ -25,10 +27,11 @@ class InputSection; /// /// It is important that .subsections_via_symbols is used to ensure functions /// and data are in their own sections and thus can be reordered. -llvm::DenseMap -runBalancedPartitioning(llvm::StringRef profilePath, - bool forFunctionCompression, bool forDataCompression, - bool compressionSortStartupFunctions, bool verbose); +llvm::DenseMap runBalancedPartitioning( + llvm::StringRef profilePath, + llvm::ArrayRef compressionSortSpecs, + bool forFunctionCompression, bool forDataCompression, + bool compressionSortStartupFunctions, bool verbose); } // namespace lld::macho diff --git a/lld/MachO/Config.h b/lld/MachO/Config.h index 814ba1016849..9767cc5e5b6e 100644 --- a/lld/MachO/Config.h +++ b/lld/MachO/Config.h @@ -9,6 +9,7 @@ #ifndef LLD_MACHO_CONFIG_H #define LLD_MACHO_CONFIG_H +#include "lld/Common/BPSectionOrdererBase.h" #include "llvm/ADT/CachedHashString.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" @@ -235,6 +236,7 @@ struct Configuration { bool bpCompressionSortStartupFunctions = false; bool bpFunctionOrderForCompression = false; bool bpDataOrderForCompression = false; + llvm::SmallVector bpCompressionSortSpecs; bool bpVerboseSectionOrderer = false; SectionRenameMap sectionRenameMap; diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp index 58fbe64c2d1f..26b39f7a28d0 100644 --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -2062,6 +2062,50 @@ bool link(ArrayRef argsArr, llvm::raw_ostream &stdoutOS, if (config->irpgoProfilePath.empty() && config->bpStartupFunctionSort) error("--bp-startup-sort=function must be used with " "--irpgo-profile"); + auto addCompressionSortSpec = [&](StringRef value) { + SmallVector parts; + value.split(parts, '='); + + StringRef globString = parts[0]; + unsigned layoutPriority = 0; + std::optional matchPriority; + + if (parts.size() > 1 && !parts[1].empty()) { + if (!to_integer(parts[1], layoutPriority)) { + error("--bp-compression-sort-section: expected integer " + "for layout_priority, got '" + + parts[1] + "'"); + return; + } + } + if (parts.size() > 2 && !parts[2].empty()) { + unsigned mp; + if (!to_integer(parts[2], mp)) { + error("--bp-compression-sort-section: expected integer " + "for match_priority, got '" + + parts[2] + "'"); + return; + } + matchPriority = mp; + } + if (parts.size() > 3) { + error("--bp-compression-sort-section: too many '=' in '" + value + "'"); + return; + } + + auto spec = BPCompressionSortSpec::create(globString, layoutPriority, + matchPriority); + if (!spec) { + error("--bp-compression-sort-section: " + toString(spec.takeError())); + return; + } + config->bpCompressionSortSpecs.emplace_back(std::move(*spec)); + }; + + for (const Arg *arg : args.filtered(OPT_bp_compression_sort_section)) + addCompressionSortSpec(arg->getValue()); + if (!config->bpCompressionSortSpecs.empty()) + IncompatWithCGSort("--bp-compression-sort-section"); if (const Arg *arg = args.getLastArg(OPT_bp_compression_sort)) { StringRef compressionSortStr = arg->getValue(); if (compressionSortStr == "function") { diff --git a/lld/MachO/Options.td b/lld/MachO/Options.td index 5bd220b3c196..b7686d66a258 100644 --- a/lld/MachO/Options.td +++ b/lld/MachO/Options.td @@ -147,9 +147,17 @@ def bp_compression_sort_startup_functions: Flag<["--"], "bp-compression-sort-sta Group; def no_bp_compression_sort_startup_functions: Flag<["--"], "no-bp-compression-sort-startup-functions">, HelpText<"Do not order startup function for compression">, Group; -def bp_compression_sort: Joined<["--"], "bp-compression-sort=">, - MetaVarName<"[none,function,data,both]">, - HelpText<"Order sections to improve compressed size">, Group; +def bp_compression_sort + : Joined<["--"], "bp-compression-sort=">, + MetaVarName<"[none,function,data,both]">, + HelpText<"Order sections to improve compressed size. Deprecated. Please " + "use --bp-compression-sort-section">, + Group; +def bp_compression_sort_section + : Joined<["--"], "bp-compression-sort-section=">, + MetaVarName<"[=[=]]">, + HelpText<"Reorder sections whose segment+section name (e.g. __TEXT__text) matches to improve Lempel-Ziv compression, while letting users split sections into groups, run BP independently per group, and exclude unsuitable sections. layout_priority controls group placement order (default 0). match_priority resolves conflicts when multiple globs match (default: last match wins). May be specified multiple times">, + Group; def irpgo_profile_sort: Separate<["--"], "irpgo-profile-sort">, Group; def irpgo_profile_sort_eq: Joined<["--"], "irpgo-profile-sort=">, Alias(irpgo_profile_sort)>, MetaVarName<"">, diff --git a/lld/MachO/SectionPriorities.cpp b/lld/MachO/SectionPriorities.cpp index 818418e8aa29..b9353bfcb282 100644 --- a/lld/MachO/SectionPriorities.cpp +++ b/lld/MachO/SectionPriorities.cpp @@ -373,11 +373,12 @@ DenseMap macho::PriorityBuilder::buildInputSectionPriorities() { DenseMap sectionPriorities; if (config->bpStartupFunctionSort || config->bpFunctionOrderForCompression || - config->bpDataOrderForCompression) { + config->bpDataOrderForCompression || + !config->bpCompressionSortSpecs.empty()) { TimeTraceScope timeScope("Balanced Partitioning Section Orderer"); sectionPriorities = runBalancedPartitioning( config->bpStartupFunctionSort ? config->irpgoProfilePath : "", - config->bpFunctionOrderForCompression, + config->bpCompressionSortSpecs, config->bpFunctionOrderForCompression, config->bpDataOrderForCompression, config->bpCompressionSortStartupFunctions, config->bpVerboseSectionOrderer); diff --git a/lld/docs/ReleaseNotes.rst b/lld/docs/ReleaseNotes.rst index cc1628f48bb0..a4270c4f9907 100644 --- a/lld/docs/ReleaseNotes.rst +++ b/lld/docs/ReleaseNotes.rst @@ -29,6 +29,17 @@ Non-comprehensive list of changes in this release ELF Improvements ---------------- +* Added ``--bp-compression-sort-section=[=[=]]``, + replacing the old coarse ``--bp-compression-sort`` modes with a way to split + input sections into multiple compression groups, run balanced partitioning + independently per group, and leave out sections that are poor candidates for + BP. + ``layout_priority`` controls group placement order (lower value = placed + first, default 0). ``match_priority`` resolves conflicts when multiple globs + match the same section (lower value = higher priority; explicit priority + beats positional last-match-wins; default: positional). In ELF, the glob + matches input section names (e.g. ``.text.unlikely.code1``). + Breaking changes ---------------- @@ -41,6 +52,10 @@ MinGW Improvements MachO Improvements ------------------ +* ``--bp-compression-sort-section`` now accepts optional layout and match + priorities (same syntax as ELF). In Mach-O, the glob matches the + concatenated segment+section name (e.g. ``__TEXT__text``). + WebAssembly Improvements ------------------------ diff --git a/lld/include/lld/Common/BPSectionOrdererBase.h b/lld/include/lld/Common/BPSectionOrdererBase.h new file mode 100644 index 000000000000..18024944b60c --- /dev/null +++ b/lld/include/lld/Common/BPSectionOrdererBase.h @@ -0,0 +1,48 @@ +//===- BPSectionOrdererBase.h -----------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLD_COMMON_BPSECTION_ORDERER_BASE_H +#define LLD_COMMON_BPSECTION_ORDERER_BASE_H + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/GlobPattern.h" +#include + +namespace lld { + +/// Specifies a glob-based compression sort group for balanced partitioning. +struct BPCompressionSortSpec { + static llvm::Expected + create(llvm::StringRef globString, unsigned layoutPriority, + std::optional matchPriority) { + auto glob = llvm::GlobPattern::create(globString); + if (!glob) + return glob.takeError(); + return BPCompressionSortSpec(std::move(*glob), globString, layoutPriority, + matchPriority); + } + + const llvm::GlobPattern glob; + const llvm::StringRef globString; + const unsigned layoutPriority; + // nullopt means positional priority (last match wins among positional specs). + // Explicit matchPriority always beats positional. + const std::optional matchPriority; + +private: + BPCompressionSortSpec(llvm::GlobPattern glob, llvm::StringRef globString, + unsigned layoutPriority, + std::optional matchPriority) + : glob(std::move(glob)), globString(globString), + layoutPriority(layoutPriority), matchPriority(matchPriority) {} +}; + +} // namespace lld + +#endif diff --git a/lld/include/lld/Common/BPSectionOrdererBase.inc b/lld/include/lld/Common/BPSectionOrdererBase.inc index 7d13cd25c0e7..daac8785f8da 100644 --- a/lld/include/lld/Common/BPSectionOrdererBase.inc +++ b/lld/include/lld/Common/BPSectionOrdererBase.inc @@ -19,6 +19,7 @@ // //===----------------------------------------------------------------------===// +#include "lld/Common/BPSectionOrdererBase.h" #include "lld/Common/ErrorHandler.h" #include "lld/Common/Utils.h" #include "llvm/ADT/CachedHashString.h" @@ -52,14 +53,17 @@ template struct BPOrderer { // Compute a section order using the Balanced Partitioning algorithm. // - // * for*Compresion: Improve Lempel-Ziv compression by grouping - // similar sections together. + // * compressionSortSpecs: Improve Lempel-Ziv compression by grouping + // similar sections together. The specs select which sections participate, + // divide them into distinct groups, and run BP independently for each + // group, then place the groups according to their layout priority. // * profilePath: Utilize a temporal profile file to reduce page faults during // program startup. // * compressionSortStartupFunctions: if profilePath is specified, allocate // extra utility vertices to prioritize nearby function similarity. - auto computeOrder(llvm::StringRef profilePath, bool forFunctionCompression, - bool forDataCompression, + auto computeOrder(llvm::StringRef profilePath, + llvm::ArrayRef compressionSortSpecs, + bool forFunctionCompression, bool forDataCompression, bool compressionSortStartupFunctions, bool verbose, llvm::ArrayRef
sections, const DenseMap> @@ -69,6 +73,10 @@ template struct BPOrderer { std::optional static getResolvedLinkageName(StringRef name) { return {}; } + + std::string static getCompressionSubgroupKey(const Section &sec) { + return ""; + } }; } // namespace lld @@ -150,7 +158,8 @@ static SmallVector> getUnsForCompression( template auto BPOrderer::computeOrder( - StringRef profilePath, bool forFunctionCompression, bool forDataCompression, + StringRef profilePath, ArrayRef compressionSortSpecs, + bool forFunctionCompression, bool forDataCompression, bool compressionSortStartupFunctions, bool verbose, ArrayRef
sections, const DenseMap> @@ -221,20 +230,82 @@ auto BPOrderer::computeOrder( } } + // Compression grouping: for each section, find the winning spec among + // matching --bp-compression-sort-section globs. Match resolution: + // - Explicit matchPriority always beats positional. + // - Among explicit: lowest matchPriority wins. + // - Among positional: last match wins. + // Sections matched by the same winning globString are grouped together and + // may be further subdivided by getCompressionSubgroupKey(). Layout order is + // (layoutPriority, globString, subgroupKey). + struct GlobGroup { + unsigned layoutPriority; + std::map> subgroups; + }; + StringMap globGroups; SmallVector sectionIdxsForFunctionCompression, sectionIdxsForDataCompression; for (unsigned sectionIdx = 0; sectionIdx < sections.size(); sectionIdx++) { if (startupSectionIdxUNs.contains(sectionIdx)) continue; const auto *isec = sections[sectionIdx]; - if (D::isCodeSection(*isec)) { - if (forFunctionCompression) - sectionIdxsForFunctionCompression.push_back(sectionIdx); - } else { - if (forDataCompression) - sectionIdxsForDataCompression.push_back(sectionIdx); + auto sectionName = D::getSectionName(*isec); + + const BPCompressionSortSpec *winner = nullptr; + for (const auto &spec : compressionSortSpecs) { + if (!spec.glob.match(sectionName)) + continue; + if (!winner || spec.matchPriority.value_or(UINT_MAX) <= + winner->matchPriority.value_or(UINT_MAX)) + winner = &spec; + } + if (winner) { + auto &group = globGroups[winner->globString]; + group.layoutPriority = winner->layoutPriority; + auto subgroupKey = D::getCompressionSubgroupKey(*isec); + group.subgroups[subgroupKey].push_back(sectionIdx); + continue; + } + bool isCode = D::isCodeSection(*isec); + if (forFunctionCompression && isCode) + sectionIdxsForFunctionCompression.push_back(sectionIdx); + if (forDataCompression && !isCode) + sectionIdxsForDataCompression.push_back(sectionIdx); + } + + // Flatten groups sorted by (layoutPriority, globString, subgroupKey). + struct CompressionGroup { + std::string key; + SmallVector sectionIdxs; + }; + SmallVector groupsForCompression; + { + struct SortedCompressionGroup { + std::tuple sortKey; + SmallVector *sectionIdxs; + }; + SmallVector sortedGroups; + for (auto &[globString, group] : globGroups) + for (auto &[subKey, idxs] : group.subgroups) + sortedGroups.push_back( + {{group.layoutPriority, globString, subKey}, &idxs}); + llvm::sort(sortedGroups, [](const auto &a, const auto &b) { + return a.sortKey < b.sortKey; + }); + for (auto &group : sortedGroups) { + auto &[prio, globString, subKey] = group.sortKey; + std::string fullKey = + subKey.empty() ? globString.str() : (globString + ":" + subKey).str(); + groupsForCompression.push_back( + {std::move(fullKey), std::move(*group.sectionIdxs)}); } } + if (forFunctionCompression) + groupsForCompression.push_back( + {"legacy:function", std::move(sectionIdxsForFunctionCompression)}); + if (forDataCompression) + groupsForCompression.push_back( + {"legacy:data", std::move(sectionIdxsForDataCompression)}); if (compressionSortStartupFunctions) { SmallVector startupIdxs; @@ -252,51 +323,44 @@ auto BPOrderer::computeOrder( } } - // Map a section index (order directly) to a list of duplicate section indices - // (not ordered directly). + // Map a section index (ordered directly) to a list of duplicate section + // indices (not ordered directly). DenseMap> duplicateSectionIdxs; - auto unsForFunctionCompression = getUnsForCompression( - sections, sectionToIdx, sectionIdxsForFunctionCompression, - &duplicateSectionIdxs, maxUN); - auto unsForDataCompression = getUnsForCompression( - sections, sectionToIdx, sectionIdxsForDataCompression, - &duplicateSectionIdxs, maxUN); + SmallVector> nodesForCompression; + for (auto &group : groupsForCompression) { + auto &nodes = nodesForCompression.emplace_back(); + auto uns = + getUnsForCompression(sections, sectionToIdx, group.sectionIdxs, + &duplicateSectionIdxs, maxUN); + for (auto &[sectionIdx, uns] : uns) + nodes.emplace_back(sectionIdx, uns); + // Sort compression nodes by their Id (which is the section index) because + // the input linker order tends to be not bad. + llvm::sort(nodes, [](auto &L, auto &R) { return L.Id < R.Id; }); + } - std::vector nodesForStartup, nodesForFunctionCompression, - nodesForDataCompression; + std::vector nodesForStartup; for (auto &[sectionIdx, uns] : startupSectionIdxUNs) nodesForStartup.emplace_back(sectionIdx, uns); - for (auto &[sectionIdx, uns] : unsForFunctionCompression) - nodesForFunctionCompression.emplace_back(sectionIdx, uns); - for (auto &[sectionIdx, uns] : unsForDataCompression) - nodesForDataCompression.emplace_back(sectionIdx, uns); // Use the first timestamp to define the initial order for startup nodes. llvm::sort(nodesForStartup, [§ionIdxToTimestamp](auto &L, auto &R) { return std::make_pair(sectionIdxToTimestamp[L.Id], L.Id) < std::make_pair(sectionIdxToTimestamp[R.Id], R.Id); }); - // Sort compression nodes by their Id (which is the section index) because the - // input linker order tends to be not bad. - llvm::sort(nodesForFunctionCompression, - [](auto &L, auto &R) { return L.Id < R.Id; }); - llvm::sort(nodesForDataCompression, - [](auto &L, auto &R) { return L.Id < R.Id; }); { TimeTraceScope timeScope("Balanced Partitioning"); BalancedPartitioningConfig config; BalancedPartitioning bp(config); bp.run(nodesForStartup); - bp.run(nodesForFunctionCompression); - bp.run(nodesForDataCompression); + for (auto &nodes : nodesForCompression) + bp.run(nodes); } unsigned numStartupSections = 0, startupSize = 0; - unsigned numCodeCompressionSections = 0, codeCompressionSize = 0; - unsigned numDuplicateCodeSections = 0, duplicateCodeSize = 0; - unsigned numDataCompressionSections = 0, dataCompressionSize = 0; - unsigned numDuplicateDataSections = 0, duplicateDataSize = 0; + unsigned numCompressionSections = 0, compressionSize = 0; + unsigned numDuplicateCompressionSections = 0, duplicateCompressionSize = 0; SetVector orderedSections; // Order startup functions, for (auto &node : nodesForStartup) { @@ -306,63 +370,46 @@ auto BPOrderer::computeOrder( ++numStartupSections; } } - // then functions for compression, - for (auto &node : nodesForFunctionCompression) { - const auto *isec = sections[node.Id]; - if (orderedSections.insert(isec)) { - codeCompressionSize += D::getSize(*isec); - ++numCodeCompressionSections; - } - auto It = duplicateSectionIdxs.find(node.Id); - if (It == duplicateSectionIdxs.end()) - continue; - for (auto dupSecIdx : It->getSecond()) { - const auto *dupIsec = sections[dupSecIdx]; - if (orderedSections.insert(dupIsec)) { - duplicateCodeSize += D::getSize(*dupIsec); - ++numDuplicateCodeSections; + // then sections for compression. + for (const auto &nodes : nodesForCompression) { + for (auto &node : nodes) { + const auto *isec = sections[node.Id]; + if (orderedSections.insert(isec)) { + compressionSize += D::getSize(*isec); + ++numCompressionSections; } - } - } - // then data for compression. - for (auto &node : nodesForDataCompression) { - const auto *isec = sections[node.Id]; - if (orderedSections.insert(isec)) { - dataCompressionSize += D::getSize(*isec); - ++numDataCompressionSections; - } - auto It = duplicateSectionIdxs.find(node.Id); - if (It == duplicateSectionIdxs.end()) - continue; - for (auto dupSecIdx : It->getSecond()) { - const auto *dupIsec = sections[dupSecIdx]; - if (orderedSections.insert(dupIsec)) { - duplicateDataSize += D::getSize(*dupIsec); - ++numDuplicateDataSections; + auto It = duplicateSectionIdxs.find(node.Id); + if (It == duplicateSectionIdxs.end()) + continue; + for (auto dupSecIdx : It->getSecond()) { + const auto *dupIsec = sections[dupSecIdx]; + if (orderedSections.insert(dupIsec)) { + duplicateCompressionSize += D::getSize(*dupIsec); + ++numDuplicateCompressionSections; + } } } } if (verbose) { - unsigned numTotalOrderedSections = - numStartupSections + numCodeCompressionSections + - numDuplicateCodeSections + numDataCompressionSections + - numDuplicateDataSections; - unsigned totalOrderedSize = startupSize + codeCompressionSize + - duplicateCodeSize + dataCompressionSize + - duplicateDataSize; + unsigned numTotalOrderedSections = numStartupSections + + numCompressionSections + + numDuplicateCompressionSections; + unsigned totalOrderedSize = + startupSize + compressionSize + duplicateCompressionSize; dbgs() << "Ordered " << numTotalOrderedSections << " sections (" << totalOrderedSize << " bytes) using balanced partitioning:\n"; dbgs() << " Functions for startup: " << numStartupSections << " (" << startupSize << " bytes)\n"; - dbgs() << " Functions for compression: " << numCodeCompressionSections - << " (" << codeCompressionSize << " bytes)\n"; - dbgs() << " Duplicate functions: " << numDuplicateCodeSections << " (" - << duplicateCodeSize << " bytes)\n"; - dbgs() << " Data for compression: " << numDataCompressionSections << " (" - << dataCompressionSize << " bytes)\n"; - dbgs() << " Duplicate data: " << numDuplicateDataSections << " (" - << duplicateDataSize << " bytes)\n"; + dbgs() << " Sections for compression: " << numCompressionSections << " (" + << compressionSize << " bytes)\n"; + dbgs() << " Compression groups: " << groupsForCompression.size() << "\n"; + for (const auto &group : groupsForCompression) + dbgs() << " " << group.key << ": " << group.sectionIdxs.size() + << " sections\n"; + dbgs() << " Duplicate compression sections: " + << numDuplicateCompressionSections << " (" + << duplicateCompressionSize << " bytes)\n"; if (!profilePath.empty()) { // Evaluate this function order for startup diff --git a/lld/test/ELF/bp-section-orderer-cold.s b/lld/test/ELF/bp-section-orderer-cold.s new file mode 100644 index 000000000000..8bc11b83df28 --- /dev/null +++ b/lld/test/ELF/bp-section-orderer-cold.s @@ -0,0 +1,208 @@ +# REQUIRES: aarch64 +# RUN: rm -rf %t && split-file %s %t && cd %t +# RUN: llvm-mc -filetype=obj -triple=aarch64 a.s -o a.o +# RUN: llvm-profdata merge a.proftext -o a.profdata + +## Simple glob: all .text* sections in one group. +# RUN: ld.lld a.o -o simple.out \ +# RUN: --bp-compression-sort-section=".text*" \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=SIMPLE + +## Deprecated --bp-compression-sort=function still works. +# RUN: ld.lld a.o -o deprecated.out \ +# RUN: --bp-compression-sort=function \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=LEGACY-FUNCTION + +# SIMPLE: Sections for compression: 7 +# SIMPLE: Compression groups: 1 +# SIMPLE: .text*: 7 sections + +# LEGACY-FUNCTION: Sections for compression: 7 +# LEGACY-FUNCTION: Compression groups: 1 +# LEGACY-FUNCTION: legacy:function: 7 sections + +## Layout priority: .text.unlikely*, .text*, .text.split* +# RUN: ld.lld a.o -o layout.out \ +# RUN: --bp-compression-sort-section=".text*=1" \ +# RUN: --bp-compression-sort-section=".text.unlikely*=0" \ +# RUN: --bp-compression-sort-section=".text.split*=2" \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=LAYOUT +# RUN: llvm-nm -jn layout.out | FileCheck %s --check-prefix=LAYOUT-ORDER + +# LAYOUT: Sections for compression: 7 +# LAYOUT: Compression groups: 3 +# LAYOUT: .text.unlikely*: 2 sections +# LAYOUT: .text*: 4 sections +# LAYOUT: .text.split*: 1 sections + +# LAYOUT-ORDER: cold1 +# LAYOUT-ORDER: hot1 +# LAYOUT-ORDER: cold_split1 + +## Match priority: explicit match_priority wins +# RUN: ld.lld a.o -o match.out \ +# RUN: --bp-compression-sort-section=".text.unlikely*=2=0" \ +# RUN: --bp-compression-sort-section=".text*=0" \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=MATCH +# RUN: llvm-nm -jn match.out | FileCheck %s --check-prefix=MATCH-ORDER + +## Match priority: lower match_priority wins +# RUN: ld.lld a.o -o match-prio.out \ +# RUN: --bp-compression-sort-section=".text*=0=1" \ +# RUN: --bp-compression-sort-section=".text.unlikely*=2=0" \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=MATCH +# RUN: llvm-nm -jn match-prio.out | FileCheck %s --check-prefix=MATCH-ORDER + +# MATCH: Sections for compression: 7 +# MATCH: Compression groups: 2 +# MATCH: .text*: 5 sections +# MATCH: .text.unlikely*: 2 sections +# MATCH-ORDER: main +# MATCH-ORDER: cold1 + +## Match priority tie: last match wins +# RUN: ld.lld a.o -o match-tie.out \ +# RUN: --bp-compression-sort-section=".text.unlikely*=0=0" \ +# RUN: --bp-compression-sort-section=".text*=0=0" \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=MATCH-TIE + +# MATCH-TIE: Compression groups: 1 +# MATCH-TIE: .text*: 7 sections + +## Layout priority tie: groups ordered by glob string +# RUN: ld.lld a.o -o layout-tie.out \ +# RUN: --bp-compression-sort-section=".text.unlikely*=0=1" \ +# RUN: --bp-compression-sort-section=".text.split*=0=2" \ +# RUN: --bp-compression-sort-section=".text*=0" \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=LAYOUT-TIE +# RUN: llvm-nm -jn layout-tie.out | FileCheck %s --check-prefix=LAYOUT-TIE-ORDER + +# LAYOUT-TIE: Compression groups: 3 +# LAYOUT-TIE: .text*: 4 sections +# LAYOUT-TIE: .text.split*: 1 sections +# LAYOUT-TIE: .text.unlikely*: 2 sections +# LAYOUT-TIE-ORDER: main +# LAYOUT-TIE-ORDER: cold_split1 +# LAYOUT-TIE-ORDER: cold1 + +## Startup sort + compression: startup, .text*, .text.unlikely*, .text.split* +# RUN: ld.lld a.o -o startup-compr.out --irpgo-profile=a.profdata \ +# RUN: --bp-startup-sort=function \ +# RUN: --bp-compression-sort-section=".text*=0" \ +# RUN: --bp-compression-sort-section=".text.unlikely*=1=1" \ +# RUN: --bp-compression-sort-section=".text.split*=2=1" \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=STARTUP +# RUN: llvm-nm -jn startup-compr.out | FileCheck %s --check-prefix=STARTUP-ORDER + +# STARTUP: Functions for startup: 2 +# STARTUP: Sections for compression: 5 +# STARTUP: Compression groups: 3 +# STARTUP-ORDER: hot1 +# STARTUP-ORDER: main +# STARTUP-ORDER: cold2 +# STARTUP-ORDER: cold_split1 + +#--- a.proftext +:ir +:temporal_prof_traces +# Num Traces +1 +# Trace Stream Size: +1 +# Weight +1 +hot1, cold1 + +hot1 +# Func Hash: +1111 +# Num Counters: +1 +# Counter Values: +1 + +cold1 +# Func Hash: +2222 +# Num Counters: +1 +# Counter Values: +1 + +#--- a.s + .section .text.split.cold_split1,"ax",@progbits + .globl cold_split1 + .type cold_split1,@function +cold_split1: + add w0, w0, #30 + add w1, w1, #31 + bl main + ret +.Lfunc_end_cold_split1: + .size cold_split1, .Lfunc_end_cold_split1-cold_split1 + + .section .text.hot1,"ax",@progbits + .globl hot1 + .type hot1,@function +hot1: + add w0, w0, #1 + add w1, w1, #2 + bl main + ret +.Lfunc_end_hot1: + .size hot1, .Lfunc_end_hot1-hot1 + + .section .text.unlikely.cold1,"ax",@progbits + .globl cold1 + .type cold1,@function +cold1: + add w0, w0, #10 + add w1, w1, #11 + bl main + ret +.Lfunc_end_cold1: + .size cold1, .Lfunc_end_cold1-cold1 + + .section .text.hot2,"ax",@progbits + .globl hot2 + .type hot2,@function +hot2: + add w0, w0, #2 + add w1, w1, #3 + bl hot1 + ret +.Lfunc_end_hot2: + .size hot2, .Lfunc_end_hot2-hot2 + + .section .text.unlikely.cold2,"ax",@progbits + .globl cold2 + .type cold2,@function +cold2: + add w0, w0, #20 + add w1, w1, #21 + bl hot1 + ret +.Lfunc_end_cold2: + .size cold2, .Lfunc_end_cold2-cold2 + + .section .text.hot3,"ax",@progbits + .globl hot3 + .type hot3,@function +hot3: + add w0, w0, #3 + add w1, w1, #4 + bl cold1 + ret +.Lfunc_end_hot3: + .size hot3, .Lfunc_end_hot3-hot3 + + .section .text.main,"ax",@progbits + .globl main + .type main,@function +main: + mov w0, wzr + ret +.Lfunc_end_main: + .size main, .Lfunc_end_main-main + + .section ".note.GNU-stack","",@progbits diff --git a/lld/test/ELF/bp-section-orderer.s b/lld/test/ELF/bp-section-orderer.s index 9a83c70dba31..b1a2bccbfbee 100644 --- a/lld/test/ELF/bp-section-orderer.s +++ b/lld/test/ELF/bp-section-orderer.s @@ -5,14 +5,24 @@ ## Check for incompatible cases # RUN: not ld.lld %t --irpgo-profile=/dev/null --bp-startup-sort=function --call-graph-ordering-file=/dev/null 2>&1 | FileCheck %s --check-prefix=BP-STARTUP-CALLGRAPH-ERR # RUN: not ld.lld --bp-compression-sort=function --call-graph-ordering-file /dev/null 2>&1 | FileCheck %s --check-prefix=BP-COMPRESSION-CALLGRAPH-ERR +# RUN: not ld.lld '--bp-compression-sort-section=.text*' --call-graph-ordering-file /dev/null 2>&1 | FileCheck %s --check-prefix=BP-SECTION-CALLGRAPH-ERR # RUN: not ld.lld --bp-startup-sort=function 2>&1 | FileCheck %s --check-prefix=BP-STARTUP-ERR # RUN: not ld.lld --bp-compression-sort-startup-functions 2>&1 | FileCheck %s --check-prefix=BP-STARTUP-COMPRESSION-ERR +# RUN: not ld.lld '--bp-compression-sort-section=.text*=x' 2>&1 | FileCheck %s --check-prefix=BP-SECTION-LAYOUT-ERR +# RUN: not ld.lld '--bp-compression-sort-section=.text*=0=x' 2>&1 | FileCheck %s --check-prefix=BP-SECTION-MATCH-ERR +# RUN: not ld.lld '--bp-compression-sort-section=.text*=0=0=0' 2>&1 | FileCheck %s --check-prefix=BP-SECTION-EQ-ERR +# RUN: not ld.lld '--bp-compression-sort-section=[' 2>&1 | FileCheck %s --check-prefix=BP-SECTION-GLOB-ERR # RUN: not ld.lld --bp-startup-sort=invalid --bp-compression-sort=invalid 2>&1 | FileCheck %s --check-prefix=BP-INVALID # BP-STARTUP-CALLGRAPH-ERR: error: --bp-startup-sort=function is incompatible with --call-graph-ordering-file # BP-COMPRESSION-CALLGRAPH-ERR: error: --bp-compression-sort is incompatible with --call-graph-ordering-file +# BP-SECTION-CALLGRAPH-ERR: error: --bp-compression-sort-section is incompatible with --call-graph-ordering-file # BP-STARTUP-ERR: error: --bp-startup-sort=function must be used with --irpgo-profile # BP-STARTUP-COMPRESSION-ERR: error: --bp-compression-sort-startup-functions must be used with --irpgo-profile +# BP-SECTION-LAYOUT-ERR: error: --bp-compression-sort-section: expected integer for layout_priority, got 'x' +# BP-SECTION-MATCH-ERR: error: --bp-compression-sort-section: expected integer for match_priority, got 'x' +# BP-SECTION-EQ-ERR: error: --bp-compression-sort-section: too many '=' in '.text*=0=0=0' +# BP-SECTION-GLOB-ERR: error: --bp-compression-sort-section: invalid glob pattern, unmatched '[' # BP-INVALID: error: --bp-compression-sort=: expected [none|function|data|both] # BP-INVALID: error: --bp-startup-sort=: expected [none|function] diff --git a/lld/test/MachO/bp-section-orderer-errs.s b/lld/test/MachO/bp-section-orderer-errs.s index abeb25122a92..b7b2868a4ac3 100644 --- a/lld/test/MachO/bp-section-orderer-errs.s +++ b/lld/test/MachO/bp-section-orderer-errs.s @@ -6,10 +6,22 @@ # RUN: not %lld -o /dev/null --bp-compression-sort=function --call-graph-profile-sort %s 2>&1 | FileCheck %s --check-prefix=COMPRESSION-ERR # COMPRESSION-ERR: --bp-compression-sort= is incompatible with --call-graph-profile-sort +# RUN: not %lld -o /dev/null '--bp-compression-sort-section=__TEXT*' --call-graph-profile-sort %s 2>&1 | FileCheck %s --check-prefix=SECTION-COMPRESSION-ERR +# SECTION-COMPRESSION-ERR: --bp-compression-sort-section is incompatible with --call-graph-profile-sort + # RUN: not %lld -o /dev/null --compression-sort=malformed 2>&1 | FileCheck %s --check-prefix=COMPRESSION-MALFORM # RUN: not %lld -o /dev/null --bp-compression-sort=malformed 2>&1 | FileCheck %s --check-prefix=COMPRESSION-MALFORM # COMPRESSION-MALFORM: unknown value `malformed` for --bp-compression-sort= +# RUN: not %lld -o /dev/null '--bp-compression-sort-section=__TEXT*=x' 2>&1 | FileCheck %s --check-prefix=SECTION-LAYOUT-ERR +# RUN: not %lld -o /dev/null '--bp-compression-sort-section=__TEXT*=0=x' 2>&1 | FileCheck %s --check-prefix=SECTION-MATCH-ERR +# RUN: not %lld -o /dev/null '--bp-compression-sort-section=__TEXT*=0=0=0' 2>&1 | FileCheck %s --check-prefix=SECTION-EQ-ERR +# RUN: not %lld -o /dev/null '--bp-compression-sort-section=[' 2>&1 | FileCheck %s --check-prefix=SECTION-GLOB-ERR +# SECTION-LAYOUT-ERR: --bp-compression-sort-section: expected integer for layout_priority, got 'x' +# SECTION-MATCH-ERR: --bp-compression-sort-section: expected integer for match_priority, got 'x' +# SECTION-EQ-ERR: --bp-compression-sort-section: too many '=' in '__TEXT*=0=0=0' +# SECTION-GLOB-ERR: --bp-compression-sort-section: invalid glob pattern, unmatched '[' + # RUN: not %lld -o /dev/null --compression-sort-startup-functions 2>&1 | FileCheck %s --check-prefix=STARTUP # RUN: not %lld -o /dev/null --bp-compression-sort-startup-functions 2>&1 | FileCheck %s --check-prefix=STARTUP # STARTUP: --bp-compression-sort-startup-functions must be used with --bp-startup-sort=function diff --git a/lld/test/MachO/compression-order-sections.s b/lld/test/MachO/compression-order-sections.s new file mode 100644 index 000000000000..40ceaf7cc4ca --- /dev/null +++ b/lld/test/MachO/compression-order-sections.s @@ -0,0 +1,112 @@ +# 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: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/b.s -o %t/b.o + +## Wildcard glob: all sections go to a single group +# RUN: %lld -arch arm64 -e _main -o %t/a.out %t/a.o %t/b.o \ +# RUN: --bp-compression-sort-section="*" \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=WILDCARD + +# WILDCARD: Sections for compression: 7 +# WILDCARD: Compression groups: 1 +# WILDCARD: *: 7 sections + +## Two globs: sections are grouped by the winning glob +# RUN: %lld -arch arm64 -e _main -o %t/a.out %t/a.o %t/b.o \ +# RUN: --bp-compression-sort-section="__DATA*" \ +# RUN: --bp-compression-sort-section="__TEXT*" \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=TWO-GLOBS + +## Deprecated --bp-compression-sort=both still works +# RUN: %lld -arch arm64 -e _main -o %t/a.out %t/a.o %t/b.o \ +# RUN: --bp-compression-sort=both \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=LEGACY-BOTH + +## Deprecated function/data modes still use the legacy buckets. +# RUN: %lld -arch arm64 -e _main -o %t/a.out %t/a.o %t/b.o \ +# RUN: --bp-compression-sort=function \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=LEGACY-FUNCTION +# RUN: %lld -arch arm64 -e _main -o %t/a.out %t/a.o %t/b.o \ +# RUN: --bp-compression-sort=data \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=LEGACY-DATA + +# TWO-GLOBS: Sections for compression: 7 +# TWO-GLOBS: Compression groups: 2 +# TWO-GLOBS: __DATA*: 6 sections +# TWO-GLOBS: __TEXT*: 1 sections + +# LEGACY-BOTH: Sections for compression: 7 +# LEGACY-BOTH: Compression groups: 2 +# LEGACY-BOTH: legacy:function: 1 sections +# LEGACY-BOTH: legacy:data: 6 sections + +# LEGACY-FUNCTION: Sections for compression: 1 +# LEGACY-FUNCTION: Compression groups: 1 +# LEGACY-FUNCTION: legacy:function: 1 sections + +# LEGACY-DATA: Sections for compression: 6 +# LEGACY-DATA: Compression groups: 1 +# LEGACY-DATA: legacy:data: 6 sections + +## Single glob matching only TEXT +# RUN: %lld -arch arm64 -e _main -o %t/a.out %t/a.o %t/b.o \ +# RUN: --bp-compression-sort-section="__TEXT*" \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=TEXT + +# TEXT: Sections for compression: 1 +# TEXT: Compression groups: 1 +# TEXT: __TEXT*: 1 sections + +## Exact section name glob +# RUN: %lld -arch arm64 -e _main -o %t/a.out %t/a.o %t/b.o \ +# RUN: --bp-compression-sort-section="__DATA__custom" \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=DATA + +# DATA: Sections for compression: 2 +# DATA: Compression groups: 1 +# DATA: __DATA__custom: 2 sections + +## Match priority: explicit match_priority wins +# RUN: %lld -arch arm64 -e _main -o %t/match.out %t/a.o %t/b.o \ +# RUN: --bp-compression-sort-section="__DATA*" \ +# RUN: --bp-compression-sort-section="__DATA__custom=0=1" \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=MATCH + +# MATCH: Compression groups: 2 +# MATCH: __DATA*: 4 sections +# MATCH: __DATA__custom: 2 sections + +#--- a.s + .text + .globl _main +_main: + ret + + .data +data_01: + .ascii "data_01" +data_02: + .ascii "data_02" +data_03: + .ascii "data_03" + + .section __DATA,__custom +custom_06: + .ascii "custom_06" +custom_07: + .ascii "custom_07" + + .bss +bss0: + .zero 10 + +.subsections_via_symbols + +#--- b.s + .data +data_11: + .ascii "data_11" + +.subsections_via_symbols