[lld] Glob-based BP compression sort groups (#185661)

Add
--bp-compression-sort-section=<glob>[=<layout_priority>[=<match_priority>]]
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.
This commit is contained in:
Zhaoxuan Jiang
2026-04-02 08:53:08 +08:00
committed by GitHub
parent 3d7eedce56
commit fd609e5d33
19 changed files with 687 additions and 110 deletions

View File

@@ -32,6 +32,7 @@ struct BPOrdererELF : lld::BPOrderer<BPOrdererELF> {
static bool isCodeSection(const Section &sec) {
return sec.flags & ELF::SHF_EXECINSTR;
}
static StringRef getSectionName(const Section &sec) { return sec.name; }
ArrayRef<Defined *> getSymbols(const Section &sec) {
auto it = secToSym.find(&sec);
if (it == secToSym.end())
@@ -63,9 +64,10 @@ struct BPOrdererELF : lld::BPOrderer<BPOrdererELF> {
} // namespace
DenseMap<const InputSectionBase *, int> elf::runBalancedPartitioning(
Ctx &ctx, StringRef profilePath, bool forFunctionCompression,
bool forDataCompression, bool compressionSortStartupFunctions,
bool verbose) {
Ctx &ctx, StringRef profilePath,
ArrayRef<BPCompressionSortSpec> compressionSortSpecs,
bool forFunctionCompression, bool forDataCompression,
bool compressionSortStartupFunctions, bool verbose) {
// Collect candidate sections and associated symbols.
SmallVector<InputSectionBase *> sections;
DenseMap<CachedHashStringRef, std::set<unsigned>> rootSymbolToSectionIdxs;
@@ -93,8 +95,8 @@ DenseMap<const InputSectionBase *, int> 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);
}

View File

@@ -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<const InputSectionBase *, int>
runBalancedPartitioning(Ctx &ctx, llvm::StringRef profilePath,
bool forFunctionCompression, bool forDataCompression,
bool compressionSortStartupFunctions, bool verbose);
llvm::DenseMap<const InputSectionBase *, int> runBalancedPartitioning(
Ctx &ctx, llvm::StringRef profilePath,
llvm::ArrayRef<BPCompressionSortSpec> compressionSortSpecs,
bool forFunctionCompression, bool forDataCompression,
bool compressionSortStartupFunctions, bool verbose);
} // namespace lld::elf

View File

@@ -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<BPCompressionSortSpec> bpCompressionSortSpecs;
bool bpVerboseSectionOrderer = false;
bool branchToBranch = false;
bool checkSections;

View File

@@ -1209,6 +1209,54 @@ static CGProfileSortKind getCGProfileSortKind(Ctx &ctx,
}
static void parseBPOrdererOptions(Ctx &ctx, opt::InputArgList &args) {
auto addCompressionSortSpec = [&](StringRef value) {
SmallVector<StringRef, 3> parts;
value.split(parts, '=');
StringRef globString = parts[0];
unsigned layoutPriority = 0;
std::optional<unsigned> 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") {

View File

@@ -148,7 +148,10 @@ def : FF<"no-call-graph-profile-sort">, Alias<call_graph_profile_sort>, 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<"<glob>[=<layout_priority>[=<match_priority>]]">,
HelpText<"Reorder input sections whose name matches <glob> 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">;

View File

@@ -1109,11 +1109,12 @@ static void maybeShuffle(Ctx &ctx,
static DenseMap<const InputSectionBase *, int> buildSectionOrder(Ctx &ctx) {
DenseMap<const InputSectionBase *, int> 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);

View File

@@ -34,6 +34,13 @@ struct BPOrdererMachO : lld::BPOrderer<BPOrdererMachO> {
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<Defined *> getSymbols(const Section &sec) {
return sec.symbols;
}
@@ -107,7 +114,8 @@ private:
} // namespace
DenseMap<const InputSection *, int> lld::macho::runBalancedPartitioning(
StringRef profilePath, bool forFunctionCompression, bool forDataCompression,
StringRef profilePath, ArrayRef<BPCompressionSortSpec> compressionSortSpecs,
bool forFunctionCompression, bool forDataCompression,
bool compressionSortStartupFunctions, bool verbose) {
// Collect candidate sections and associated symbols.
SmallVector<InputSection *> sections;
@@ -140,8 +148,8 @@ DenseMap<const InputSection *, int> lld::macho::runBalancedPartitioning(
}
}
return BPOrdererMachO().computeOrder(profilePath, forFunctionCompression,
forDataCompression,
compressionSortStartupFunctions, verbose,
sections, rootSymbolToSectionIdxs);
return BPOrdererMachO().computeOrder(
profilePath, compressionSortSpecs, forFunctionCompression,
forDataCompression, compressionSortStartupFunctions, verbose, sections,
rootSymbolToSectionIdxs);
}

View File

@@ -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<const InputSection *, int>
runBalancedPartitioning(llvm::StringRef profilePath,
bool forFunctionCompression, bool forDataCompression,
bool compressionSortStartupFunctions, bool verbose);
llvm::DenseMap<const InputSection *, int> runBalancedPartitioning(
llvm::StringRef profilePath,
llvm::ArrayRef<BPCompressionSortSpec> compressionSortSpecs,
bool forFunctionCompression, bool forDataCompression,
bool compressionSortStartupFunctions, bool verbose);
} // namespace lld::macho

View File

@@ -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<BPCompressionSortSpec> bpCompressionSortSpecs;
bool bpVerboseSectionOrderer = false;
SectionRenameMap sectionRenameMap;

View File

@@ -2062,6 +2062,50 @@ bool link(ArrayRef<const char *> 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<StringRef, 3> parts;
value.split(parts, '=');
StringRef globString = parts[0];
unsigned layoutPriority = 0;
std::optional<unsigned> 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") {

View File

@@ -147,9 +147,17 @@ def bp_compression_sort_startup_functions: Flag<["--"], "bp-compression-sort-sta
Group<grp_lld>;
def no_bp_compression_sort_startup_functions: Flag<["--"], "no-bp-compression-sort-startup-functions">,
HelpText<"Do not order startup function for compression">, Group<grp_lld>;
def bp_compression_sort: Joined<["--"], "bp-compression-sort=">,
MetaVarName<"[none,function,data,both]">,
HelpText<"Order sections to improve compressed size">, Group<grp_lld>;
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<grp_lld>;
def bp_compression_sort_section
: Joined<["--"], "bp-compression-sort-section=">,
MetaVarName<"<glob>[=<layout_priority>[=<match_priority>]]">,
HelpText<"Reorder sections whose segment+section name (e.g. __TEXT__text) matches <glob> 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<grp_lld>;
def irpgo_profile_sort: Separate<["--"], "irpgo-profile-sort">, Group<grp_lld>;
def irpgo_profile_sort_eq: Joined<["--"], "irpgo-profile-sort=">,
Alias<!cast<Separate>(irpgo_profile_sort)>, MetaVarName<"<profile>">,

View File

@@ -373,11 +373,12 @@ DenseMap<const InputSection *, int>
macho::PriorityBuilder::buildInputSectionPriorities() {
DenseMap<const InputSection *, int> 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);

View File

@@ -29,6 +29,17 @@ Non-comprehensive list of changes in this release
ELF Improvements
----------------
* Added ``--bp-compression-sort-section=<glob>[=<layout_priority>[=<match_priority>]]``,
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
------------------------

View File

@@ -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 <optional>
namespace lld {
/// Specifies a glob-based compression sort group for balanced partitioning.
struct BPCompressionSortSpec {
static llvm::Expected<BPCompressionSortSpec>
create(llvm::StringRef globString, unsigned layoutPriority,
std::optional<unsigned> 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<unsigned> matchPriority;
private:
BPCompressionSortSpec(llvm::GlobPattern glob, llvm::StringRef globString,
unsigned layoutPriority,
std::optional<unsigned> matchPriority)
: glob(std::move(glob)), globString(globString),
layoutPriority(layoutPriority), matchPriority(matchPriority) {}
};
} // namespace lld
#endif

View File

@@ -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 <class D> 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<BPCompressionSortSpec> compressionSortSpecs,
bool forFunctionCompression, bool forDataCompression,
bool compressionSortStartupFunctions, bool verbose,
llvm::ArrayRef<Section *> sections,
const DenseMap<CachedHashStringRef, std::set<unsigned>>
@@ -69,6 +73,10 @@ template <class D> struct BPOrderer {
std::optional<StringRef> static getResolvedLinkageName(StringRef name) {
return {};
}
std::string static getCompressionSubgroupKey(const Section &sec) {
return "";
}
};
} // namespace lld
@@ -150,7 +158,8 @@ static SmallVector<std::pair<unsigned, UtilityNodes>> getUnsForCompression(
template <class D>
auto BPOrderer<D>::computeOrder(
StringRef profilePath, bool forFunctionCompression, bool forDataCompression,
StringRef profilePath, ArrayRef<BPCompressionSortSpec> compressionSortSpecs,
bool forFunctionCompression, bool forDataCompression,
bool compressionSortStartupFunctions, bool verbose,
ArrayRef<Section *> sections,
const DenseMap<CachedHashStringRef, std::set<unsigned>>
@@ -221,20 +230,82 @@ auto BPOrderer<D>::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<std::string, SmallVector<unsigned>> subgroups;
};
StringMap<GlobGroup> globGroups;
SmallVector<unsigned> 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<unsigned> sectionIdxs;
};
SmallVector<CompressionGroup> groupsForCompression;
{
struct SortedCompressionGroup {
std::tuple<unsigned, StringRef, StringRef> sortKey;
SmallVector<unsigned> *sectionIdxs;
};
SmallVector<SortedCompressionGroup> 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<unsigned> startupIdxs;
@@ -252,51 +323,44 @@ auto BPOrderer<D>::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<unsigned, SmallVector<unsigned, 0>> duplicateSectionIdxs;
auto unsForFunctionCompression = getUnsForCompression<D>(
sections, sectionToIdx, sectionIdxsForFunctionCompression,
&duplicateSectionIdxs, maxUN);
auto unsForDataCompression = getUnsForCompression<D>(
sections, sectionToIdx, sectionIdxsForDataCompression,
&duplicateSectionIdxs, maxUN);
SmallVector<std::vector<BPFunctionNode>> nodesForCompression;
for (auto &group : groupsForCompression) {
auto &nodes = nodesForCompression.emplace_back();
auto uns =
getUnsForCompression<D>(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<BPFunctionNode> nodesForStartup, nodesForFunctionCompression,
nodesForDataCompression;
std::vector<BPFunctionNode> 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, [&sectionIdxToTimestamp](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<const Section *> orderedSections;
// Order startup functions,
for (auto &node : nodesForStartup) {
@@ -306,63 +370,46 @@ auto BPOrderer<D>::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

View File

@@ -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

View File

@@ -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]

View File

@@ -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

View File

@@ -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