[lld][Macho][NFC] Encapsulate priorities map in a priority class

`config->priorities` has been used to hold the intermediate state during the construction of the order in which sections should be laid out. This is not a good place to hold this state since the intermediate state is not a "configuration" for LLD. It should be encapsulated in a class for building a mapping from section to priority (which I created in this diff as the `PriorityBuilder` class).

The same thing is being done for `config->callGraphProfile`.

Reviewed By: #lld-macho, int3

Differential Revision: https://reviews.llvm.org/D122156
This commit is contained in:
Roger Kim 2022-03-23 13:21:34 -04:00
parent 31dc248ffc
commit f858fba631
5 changed files with 84 additions and 79 deletions

View file

@ -170,10 +170,6 @@ struct Configuration {
std::vector<SectionAlign> sectionAlignments; std::vector<SectionAlign> sectionAlignments;
std::vector<SegmentProtection> segmentProtections; std::vector<SegmentProtection> segmentProtections;
llvm::DenseMap<llvm::StringRef, SymbolPriorityEntry> priorities;
llvm::MapVector<std::pair<const InputSection *, const InputSection *>,
uint64_t>
callGraphProfile;
bool callGraphProfileSort = false; bool callGraphProfileSort = false;
llvm::StringRef printSymbolOrder; llvm::StringRef printSymbolOrder;

View file

@ -1453,7 +1453,7 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
StringRef orderFile = args.getLastArgValue(OPT_order_file); StringRef orderFile = args.getLastArgValue(OPT_order_file);
if (!orderFile.empty()) if (!orderFile.empty())
parseOrderFile(orderFile); priorityBuilder.parseOrderFile(orderFile);
referenceStubBinder(); referenceStubBinder();
@ -1510,7 +1510,7 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
gatherInputSections(); gatherInputSections();
if (config->callGraphProfileSort) if (config->callGraphProfileSort)
extractCallGraphProfile(); priorityBuilder.extractCallGraphProfile();
if (config->deadStrip) if (config->deadStrip)
markLive(); markLive();

View file

@ -34,9 +34,11 @@ using namespace llvm::sys;
using namespace lld; using namespace lld;
using namespace lld::macho; using namespace lld::macho;
PriorityBuilder macho::priorityBuilder;
namespace { namespace {
size_t lowestPriority = std::numeric_limits<size_t>::max(); size_t highestAvailablePriority = std::numeric_limits<size_t>::max();
struct Edge { struct Edge {
int from; int from;
@ -62,7 +64,7 @@ struct Cluster {
class CallGraphSort { class CallGraphSort {
public: public:
CallGraphSort(); CallGraphSort(const MapVector<SectionPair, uint64_t> &profile);
DenseMap<const InputSection *, size_t> run(); DenseMap<const InputSection *, size_t> run();
@ -75,13 +77,9 @@ private:
constexpr int MAX_DENSITY_DEGRADATION = 8; constexpr int MAX_DENSITY_DEGRADATION = 8;
} // end anonymous namespace } // end anonymous namespace
using SectionPair = std::pair<const InputSection *, const InputSection *>; // Take the edge list in callGraphProfile, resolve symbol names to Symbols, and
// generate a graph between InputSections with the provided weights.
// Take the edge list in config->callGraphProfile, resolve symbol names to CallGraphSort::CallGraphSort(const MapVector<SectionPair, uint64_t> &profile) {
// Symbols, and generate a graph between InputSections with the provided
// weights.
CallGraphSort::CallGraphSort() {
MapVector<SectionPair, uint64_t> &profile = config->callGraphProfile;
DenseMap<const InputSection *, int> secToCluster; DenseMap<const InputSection *, int> secToCluster;
auto getOrCreateCluster = [&](const InputSection *isec) -> int { auto getOrCreateCluster = [&](const InputSection *isec) -> int {
@ -94,7 +92,7 @@ CallGraphSort::CallGraphSort() {
}; };
// Create the graph // Create the graph
for (std::pair<SectionPair, uint64_t> &c : profile) { for (const std::pair<SectionPair, uint64_t> &c : profile) {
const auto fromSec = c.first.first->canonical(); const auto fromSec = c.first.first->canonical();
const auto toSec = c.first.second->canonical(); const auto toSec = c.first.second->canonical();
uint64_t weight = c.second; uint64_t weight = c.second;
@ -212,7 +210,7 @@ DenseMap<const InputSection *, size_t> CallGraphSort::run() {
// priority 0 and be placed at the end of sections. // priority 0 and be placed at the end of sections.
// NB: This is opposite from COFF/ELF to be compatible with the existing // NB: This is opposite from COFF/ELF to be compatible with the existing
// order-file code. // order-file code.
int curOrder = lowestPriority; int curOrder = highestAvailablePriority;
for (int leader : sorted) { for (int leader : sorted) {
for (int i = leader;;) { for (int i = leader;;) {
orderMap[sections[i]] = curOrder--; orderMap[sections[i]] = curOrder--;
@ -251,12 +249,12 @@ DenseMap<const InputSection *, size_t> CallGraphSort::run() {
return orderMap; return orderMap;
} }
static Optional<size_t> getSymbolPriority(const Defined *sym) { Optional<size_t> macho::PriorityBuilder::getSymbolPriority(const Defined *sym) {
if (sym->isAbsolute()) if (sym->isAbsolute())
return None; return None;
auto it = config->priorities.find(sym->getName()); auto it = priorities.find(sym->getName());
if (it == config->priorities.end()) if (it == priorities.end())
return None; return None;
const SymbolPriorityEntry &entry = it->second; const SymbolPriorityEntry &entry = it->second;
const InputFile *f = sym->isec->getFile(); const InputFile *f = sym->isec->getFile();
@ -273,9 +271,9 @@ static Optional<size_t> getSymbolPriority(const Defined *sym) {
return std::max(entry.objectFiles.lookup(filename), entry.anyObjectFile); return std::max(entry.objectFiles.lookup(filename), entry.anyObjectFile);
} }
void macho::extractCallGraphProfile() { void macho::PriorityBuilder::extractCallGraphProfile() {
TimeTraceScope timeScope("Extract call graph profile"); TimeTraceScope timeScope("Extract call graph profile");
bool hasOrderFile = !config->priorities.empty(); bool hasOrderFile = !priorities.empty();
for (const InputFile *file : inputFiles) { for (const InputFile *file : inputFiles) {
auto *obj = dyn_cast_or_null<ObjFile>(file); auto *obj = dyn_cast_or_null<ObjFile>(file);
if (!obj) if (!obj)
@ -289,13 +287,13 @@ void macho::extractCallGraphProfile() {
(hasOrderFile && (hasOrderFile &&
(getSymbolPriority(fromSym) || getSymbolPriority(toSym)))) (getSymbolPriority(fromSym) || getSymbolPriority(toSym))))
continue; continue;
config->callGraphProfile[{fromSym->isec, toSym->isec}] += entry.count; callGraphProfile[{fromSym->isec, toSym->isec}] += entry.count;
} }
} }
} }
void macho::parseOrderFile(StringRef path) { void macho::PriorityBuilder::parseOrderFile(StringRef path) {
assert(config->callGraphProfile.empty() && assert(callGraphProfile.empty() &&
"Order file must be parsed before call graph profile is processed"); "Order file must be parsed before call graph profile is processed");
Optional<MemoryBufferRef> buffer = readFile(path); Optional<MemoryBufferRef> buffer = readFile(path);
if (!buffer) { if (!buffer) {
@ -304,7 +302,6 @@ void macho::parseOrderFile(StringRef path) {
} }
MemoryBufferRef mbref = *buffer; MemoryBufferRef mbref = *buffer;
size_t priority = std::numeric_limits<size_t>::max();
for (StringRef line : args::getLines(mbref)) { for (StringRef line : args::getLines(mbref)) {
StringRef objectFile, symbol; StringRef objectFile, symbol;
line = line.take_until([](char c) { return c == '#'; }); // ignore comments line = line.take_until([](char c) { return c == '#'; }); // ignore comments
@ -339,34 +336,34 @@ void macho::parseOrderFile(StringRef path) {
symbol = line.trim(); symbol = line.trim();
if (!symbol.empty()) { if (!symbol.empty()) {
SymbolPriorityEntry &entry = config->priorities[symbol]; SymbolPriorityEntry &entry = priorities[symbol];
if (!objectFile.empty()) if (!objectFile.empty())
entry.objectFiles.insert(std::make_pair(objectFile, priority)); entry.objectFiles.insert(
std::make_pair(objectFile, highestAvailablePriority));
else else
entry.anyObjectFile = std::max(entry.anyObjectFile, priority); entry.anyObjectFile =
std::max(entry.anyObjectFile, highestAvailablePriority);
} }
--priority; --highestAvailablePriority;
} }
lowestPriority = priority;
} }
// Sort sections by the profile data provided by __LLVM,__cg_profile sections. DenseMap<const InputSection *, size_t>
// macho::PriorityBuilder::buildInputSectionPriorities() {
// This first builds a call graph based on the profile data then merges sections
// according to the C³ heuristic. All clusters are then sorted by a density
// metric to further improve locality.
static DenseMap<const InputSection *, size_t> computeCallGraphProfileOrder() {
TimeTraceScope timeScope("Call graph profile sort");
return CallGraphSort().run();
}
DenseMap<const InputSection *, size_t> macho::buildInputSectionPriorities() {
DenseMap<const InputSection *, size_t> sectionPriorities; DenseMap<const InputSection *, size_t> sectionPriorities;
if (config->callGraphProfileSort) if (config->callGraphProfileSort) {
sectionPriorities = computeCallGraphProfileOrder(); // Sort sections by the profile data provided by __LLVM,__cg_profile
// sections.
//
// This first builds a call graph based on the profile data then merges
// sections according to the C³ heuristic. All clusters are then sorted by a
// density metric to further improve locality.
TimeTraceScope timeScope("Call graph profile sort");
sectionPriorities = CallGraphSort(callGraphProfile).run();
}
if (config->priorities.empty()) if (priorities.empty())
return sectionPriorities; return sectionPriorities;
auto addSym = [&](const Defined *sym) { auto addSym = [&](const Defined *sym) {

View file

@ -15,41 +15,53 @@
namespace lld { namespace lld {
namespace macho { namespace macho {
// Reads every input section's call graph profile, and combines them into using SectionPair = std::pair<const InputSection *, const InputSection *>;
// config->callGraphProfile. If an order file is present, any edges where one
// or both of the vertices are specified in the order file are discarded.
void extractCallGraphProfile();
// Reads the order file at `path` into config->priorities. class PriorityBuilder {
// public:
// An order file has one entry per line, in the following format: // Reads every input section's call graph profile, and combines them into
// // callGraphProfile. If an order file is present, any edges where one or both
// <cpu>:<object file>:<symbol name> // of the vertices are specified in the order file are discarded.
// void extractCallGraphProfile();
// <cpu> and <object file> are optional. If not specified, then that entry
// matches any symbol of that name. Parsing this format is not quite
// straightforward because the symbol name itself can contain colons, so when
// encountering a colon, we consider the preceding characters to decide if it
// can be a valid CPU type or file path.
//
// If a symbol is matched by multiple entries, then it takes the lowest-ordered
// entry (the one nearest to the front of the list.)
//
// The file can also have line comments that start with '#'.
void parseOrderFile(StringRef path);
// Returns layout priorities for some or all input sections. Sections are laid // Reads the order file at `path` into config->priorities.
// out in decreasing order; that is, a higher priority section will be closer //
// to the beginning of its output section. // An order file has one entry per line, in the following format:
// //
// If either an order file or a call graph profile are present, this is used // <cpu>:<object file>:<symbol name>
// as the source of priorities. If both are present, the order file takes //
// precedence, but the call graph profile is still used for symbols that don't // <cpu> and <object file> are optional. If not specified, then that entry
// appear in the order file. If neither is present, an empty map is returned. // matches any symbol of that name. Parsing this format is not quite
// // straightforward because the symbol name itself can contain colons, so when
// Each section gets assigned the priority of the highest-priority symbol it // encountering a colon, we consider the preceding characters to decide if it
// contains. // can be a valid CPU type or file path.
llvm::DenseMap<const InputSection *, size_t> buildInputSectionPriorities(); //
// If a symbol is matched by multiple entries, then it takes the
// lowest-ordered entry (the one nearest to the front of the list.)
//
// The file can also have line comments that start with '#'.
void parseOrderFile(StringRef path);
// Returns layout priorities for some or all input sections. Sections are laid
// out in decreasing order; that is, a higher priority section will be closer
// to the beginning of its output section.
//
// If either an order file or a call graph profile are present, this is used
// as the source of priorities. If both are present, the order file takes
// precedence, but the call graph profile is still used for symbols that don't
// appear in the order file. If neither is present, an empty map is returned.
//
// Each section gets assigned the priority of the highest-priority symbol it
// contains.
llvm::DenseMap<const InputSection *, size_t> buildInputSectionPriorities();
private:
llvm::Optional<size_t> getSymbolPriority(const Defined *sym);
llvm::DenseMap<llvm::StringRef, SymbolPriorityEntry> priorities;
llvm::MapVector<SectionPair, uint64_t> callGraphProfile;
};
extern PriorityBuilder priorityBuilder;
} // namespace macho } // namespace macho
} // namespace lld } // namespace lld

View file

@ -860,7 +860,7 @@ static void sortSegmentsAndSections() {
sortOutputSegments(); sortOutputSegments();
DenseMap<const InputSection *, size_t> isecPriorities = DenseMap<const InputSection *, size_t> isecPriorities =
buildInputSectionPriorities(); priorityBuilder.buildInputSectionPriorities();
uint32_t sectionIndex = 0; uint32_t sectionIndex = 0;
for (OutputSegment *seg : outputSegments) { for (OutputSegment *seg : outputSegments) {