[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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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">;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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>">,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
------------------------
|
||||
|
||||
|
||||
48
lld/include/lld/Common/BPSectionOrdererBase.h
Normal file
48
lld/include/lld/Common/BPSectionOrdererBase.h
Normal 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
|
||||
@@ -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, [§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<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
|
||||
|
||||
208
lld/test/ELF/bp-section-orderer-cold.s
Normal file
208
lld/test/ELF/bp-section-orderer-cold.s
Normal 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
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
112
lld/test/MachO/compression-order-sections.s
Normal file
112
lld/test/MachO/compression-order-sections.s
Normal 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
|
||||
Reference in New Issue
Block a user