Skip to content

Commit a9a4515

Browse files
authored
[lld][MachO] Read cstring order for non deduped sections (#161879)
llvm/llvm-project#140307 added support for cstring hashes in the orderfile to layout cstrings in a specific order, but only when `--deduplicate-strings` is used. This PR supports cstring ordering when `--no-deduplicate-strings` is used. 1. Create `cStringPriorities`, separate from `priorities`, to hold only priorities for cstring pieces. This allows us to lookup by hash directly, instead of first converting to a string. It also fixes a contrived bug where we want to order a symbol named `CSTR;12345` rather than a cstring. 2. Rather than calling `buildCStringPriorities()` which always constructs and returns a vector, we use `forEachStringPiece()` to efficiently iterate over cstring pieces without creating a new vector if no cstring is ordered. 3. Create `SymbolPriorityEntry::{get,set}Priority()` helper functions to simplify code.
1 parent be6296e commit a9a4515

File tree

4 files changed

+129
-113
lines changed

4 files changed

+129
-113
lines changed

lld/MachO/SectionPriorities.cpp

Lines changed: 68 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "llvm/Support/Path.h"
2828
#include "llvm/Support/TimeProfiler.h"
2929
#include "llvm/Support/raw_ostream.h"
30+
#include "llvm/Support/xxhash.h"
3031

3132
#include <numeric>
3233

@@ -246,33 +247,45 @@ DenseMap<const InputSection *, int> CallGraphSort::run() {
246247
return orderMap;
247248
}
248249

249-
std::optional<int>
250-
macho::PriorityBuilder::getSymbolOrCStringPriority(const StringRef key,
251-
InputFile *f) {
250+
void macho::PriorityBuilder::SymbolPriorityEntry::setPriority(
251+
int priority, StringRef objectFile) {
252+
if (!objectFile.empty())
253+
objectFiles.try_emplace(objectFile, priority);
254+
else
255+
anyObjectFile = std::min(anyObjectFile, priority);
256+
}
252257

253-
auto it = priorities.find(key);
254-
if (it == priorities.end())
255-
return std::nullopt;
256-
const SymbolPriorityEntry &entry = it->second;
258+
int macho::PriorityBuilder::SymbolPriorityEntry::getPriority(
259+
const InputFile *f) const {
257260
if (!f)
258-
return entry.anyObjectFile;
261+
return anyObjectFile;
259262
// We don't use toString(InputFile *) here because it returns the full path
260263
// for object files, and we only want the basename.
261-
StringRef filename;
262-
if (f->archiveName.empty())
263-
filename = path::filename(f->getName());
264-
else
265-
filename = saver().save(path::filename(f->archiveName) + "(" +
266-
path::filename(f->getName()) + ")");
267-
return std::min(entry.objectFiles.lookup(filename), entry.anyObjectFile);
264+
StringRef basename = path::filename(f->getName());
265+
StringRef filename =
266+
f->archiveName.empty()
267+
? basename
268+
: saver().save(path::filename(f->archiveName) + "(" + basename + ")");
269+
return std::min(objectFiles.lookup(filename), anyObjectFile);
268270
}
269271

270272
std::optional<int>
271-
macho::PriorityBuilder::getSymbolPriority(const Defined *sym) {
273+
macho::PriorityBuilder::getCStringPriority(uint32_t hash,
274+
const InputFile *f) const {
275+
auto it = cStringPriorities.find(hash);
276+
if (it == cStringPriorities.end())
277+
return std::nullopt;
278+
return it->second.getPriority(f);
279+
}
280+
281+
std::optional<int>
282+
macho::PriorityBuilder::getSymbolPriority(const Defined *sym) const {
272283
if (sym->isAbsolute())
273284
return std::nullopt;
274-
return getSymbolOrCStringPriority(utils::getRootSymbol(sym->getName()),
275-
sym->isec()->getFile());
285+
auto it = priorities.find(utils::getRootSymbol(sym->getName()));
286+
if (it == priorities.end())
287+
return std::nullopt;
288+
return it->second.getPriority(sym->isec()->getFile());
276289
}
277290

278291
void macho::PriorityBuilder::extractCallGraphProfile() {
@@ -307,7 +320,7 @@ void macho::PriorityBuilder::parseOrderFile(StringRef path) {
307320
int prio = std::numeric_limits<int>::min();
308321
MemoryBufferRef mbref = *buffer;
309322
for (StringRef line : args::getLines(mbref)) {
310-
StringRef objectFile, symbolOrCStrHash;
323+
StringRef objectFile;
311324
line = line.take_until([](char c) { return c == '#'; }); // ignore comments
312325
line = line.ltrim();
313326

@@ -338,22 +351,16 @@ void macho::PriorityBuilder::parseOrderFile(StringRef path) {
338351
}
339352

340353
// The rest of the line is either <symbol name> or
341-
// CStringEntryPrefix<cstring hash>
354+
// cStringEntryPrefix<cstring hash>
342355
line = line.trim();
343-
if (line.starts_with(CStringEntryPrefix)) {
344-
StringRef possibleHash = line.drop_front(CStringEntryPrefix.size());
356+
if (line.consume_front(cStringEntryPrefix)) {
345357
uint32_t hash = 0;
346-
if (to_integer(possibleHash, hash))
347-
symbolOrCStrHash = possibleHash;
348-
} else
349-
symbolOrCStrHash = utils::getRootSymbol(line);
350-
351-
if (!symbolOrCStrHash.empty()) {
352-
SymbolPriorityEntry &entry = priorities[symbolOrCStrHash];
353-
if (!objectFile.empty())
354-
entry.objectFiles.insert(std::make_pair(objectFile, prio));
355-
else
356-
entry.anyObjectFile = std::min(entry.anyObjectFile, prio);
358+
if (to_integer(line, hash))
359+
cStringPriorities[hash].setPriority(prio, objectFile);
360+
} else {
361+
StringRef symbol = utils::getRootSymbol(line);
362+
if (!symbol.empty())
363+
priorities[symbol].setPriority(prio, objectFile);
357364
}
358365

359366
++prio;
@@ -405,40 +412,39 @@ macho::PriorityBuilder::buildInputSectionPriorities() {
405412
return sectionPriorities;
406413
}
407414

408-
std::vector<StringPiecePair> macho::PriorityBuilder::buildCStringPriorities(
409-
ArrayRef<CStringInputSection *> inputs) {
410-
// Split the input strings into hold and cold sets.
411-
// Order hot set based on -order_file_cstring for performance improvement;
412-
// TODO: Order cold set of cstrings for compression via BP.
413-
std::vector<std::pair<int, StringPiecePair>>
414-
hotStringPrioritiesAndStringPieces;
415-
std::vector<StringPiecePair> coldStringPieces;
416-
std::vector<StringPiecePair> orderedStringPieces;
417-
415+
void macho::PriorityBuilder::forEachStringPiece(
416+
ArrayRef<CStringInputSection *> inputs,
417+
std::function<void(CStringInputSection &, StringPiece &, size_t)> f,
418+
bool forceInputOrder, bool computeHash) const {
419+
std::vector<std::tuple<int, CStringInputSection *, size_t>> orderedPieces;
420+
std::vector<std::pair<CStringInputSection *, size_t>> unorderedPieces;
418421
for (CStringInputSection *isec : inputs) {
419422
for (const auto &[stringPieceIdx, piece] : llvm::enumerate(isec->pieces)) {
420423
if (!piece.live)
421424
continue;
422-
423-
std::optional<int> priority = getSymbolOrCStringPriority(
424-
std::to_string(piece.hash), isec->getFile());
425-
if (!priority)
426-
coldStringPieces.emplace_back(isec, stringPieceIdx);
425+
// Process pieces in input order if we have no cstrings in our orderfile
426+
if (forceInputOrder || cStringPriorities.empty()) {
427+
f(*isec, piece, stringPieceIdx);
428+
continue;
429+
}
430+
uint32_t hash =
431+
computeHash
432+
? (xxh3_64bits(isec->getStringRef(stringPieceIdx)) & 0x7fffffff)
433+
: piece.hash;
434+
if (auto priority = getCStringPriority(hash, isec->getFile()))
435+
orderedPieces.emplace_back(*priority, isec, stringPieceIdx);
427436
else
428-
hotStringPrioritiesAndStringPieces.emplace_back(
429-
*priority, std::make_pair(isec, stringPieceIdx));
437+
unorderedPieces.emplace_back(isec, stringPieceIdx);
430438
}
431439
}
432-
433-
// Order hot set for perf
434-
llvm::stable_sort(hotStringPrioritiesAndStringPieces);
435-
for (auto &[priority, stringPiecePair] : hotStringPrioritiesAndStringPieces)
436-
orderedStringPieces.push_back(stringPiecePair);
437-
438-
// TODO: Order cold set for compression
439-
440-
orderedStringPieces.insert(orderedStringPieces.end(),
441-
coldStringPieces.begin(), coldStringPieces.end());
442-
443-
return orderedStringPieces;
440+
if (orderedPieces.empty() && unorderedPieces.empty())
441+
return;
442+
llvm::stable_sort(orderedPieces, [](const auto &left, const auto &right) {
443+
return std::get<0>(left) < std::get<0>(right);
444+
});
445+
for (auto &[priority, isec, pieceIdx] : orderedPieces)
446+
f(*isec, isec->pieces[pieceIdx], pieceIdx);
447+
// TODO: Add option to order the remaining cstrings for compression
448+
for (auto &[isec, pieceIdx] : unorderedPieces)
449+
f(*isec, isec->pieces[pieceIdx], pieceIdx);
444450
}

lld/MachO/SectionPriorities.h

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
namespace lld::macho {
1717

1818
using SectionPair = std::pair<const InputSection *, const InputSection *>;
19-
using StringPiecePair = std::pair<CStringInputSection *, size_t>;
2019

2120
class PriorityBuilder {
2221
public:
@@ -29,7 +28,7 @@ class PriorityBuilder {
2928
//
3029
// An order file has one entry per line, in the following format:
3130
//
32-
// <cpu>:<object file>:[<symbol name> | CStringEntryPrefix <cstring hash>]
31+
// <cpu>:<object file>:[<symbol name> | cStringEntryPrefix <cstring hash>]
3332
//
3433
// <cpu> and <object file> are optional.
3534
// If not specified, then that entry tries to match either,
@@ -42,7 +41,7 @@ class PriorityBuilder {
4241
// lowest-ordered entry (the one nearest to the front of the list.)
4342
//
4443
// or 2) any cstring literal with the given hash, if the entry has the
45-
// CStringEntryPrefix prefix defined below in the file. <cstring hash> is the
44+
// cStringEntryPrefix prefix defined below in the file. <cstring hash> is the
4645
// hash of cstring literal content.
4746
//
4847
// Cstring literals are not symbolized, we can't identify them by name
@@ -54,6 +53,16 @@ class PriorityBuilder {
5453
// The file can also have line comments that start with '#'.
5554
void parseOrderFile(StringRef path);
5655

56+
/// Call \p f for each string piece in \p inputs. If there are any cstring
57+
/// literals in the orderfile (and \p forceInputOrder is false) then string
58+
/// pieces are ordered by the orderfile. \p computeHash must be set when
59+
/// \p deduplicateLiterals is false because then the string piece hash is not
60+
/// set.
61+
void forEachStringPiece(
62+
ArrayRef<CStringInputSection *> inputs,
63+
std::function<void(CStringInputSection &, StringPiece &, size_t)> f,
64+
bool forceInputOrder = false, bool computeHash = false) const;
65+
5766
// Returns layout priorities for some or all input sections. Sections are laid
5867
// out in decreasing order; that is, a higher priority section will be closer
5968
// to the beginning of its output section.
@@ -66,8 +75,6 @@ class PriorityBuilder {
6675
// Each section gets assigned the priority of the highest-priority symbol it
6776
// contains.
6877
llvm::DenseMap<const InputSection *, int> buildInputSectionPriorities();
69-
std::vector<StringPiecePair>
70-
buildCStringPriorities(ArrayRef<CStringInputSection *>);
7178

7279
private:
7380
// The symbol with the smallest priority should be ordered first in the output
@@ -78,13 +85,16 @@ class PriorityBuilder {
7885
int anyObjectFile = 0;
7986
// The priority given to a matching symbol from a particular object file.
8087
llvm::DenseMap<llvm::StringRef, int> objectFiles;
88+
void setPriority(int priority, StringRef objectFile);
89+
int getPriority(const InputFile *f) const;
8190
};
82-
const llvm::StringRef CStringEntryPrefix = "CSTR;";
91+
const llvm::StringRef cStringEntryPrefix = "CSTR;";
8392

84-
std::optional<int> getSymbolPriority(const Defined *sym);
85-
std::optional<int> getSymbolOrCStringPriority(const StringRef key,
86-
InputFile *f);
93+
std::optional<int> getSymbolPriority(const Defined *sym) const;
94+
std::optional<int> getCStringPriority(uint32_t hash,
95+
const InputFile *f) const;
8796
llvm::DenseMap<llvm::StringRef, SymbolPriorityEntry> priorities;
97+
llvm::DenseMap<uint32_t, SymbolPriorityEntry> cStringPriorities;
8898
llvm::MapVector<SectionPair, uint64_t> callGraphProfile;
8999
};
90100

lld/MachO/SyntheticSections.cpp

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1721,47 +1721,44 @@ void CStringSection::writeTo(uint8_t *buf) const {
17211721
// and don't need this alignment. They will be emitted at some arbitrary address
17221722
// `A`, but ld64 will treat them as being 16-byte aligned with an offset of
17231723
// `16 % A`.
1724-
static Align getStringPieceAlignment(const CStringInputSection *isec,
1724+
static Align getStringPieceAlignment(const CStringInputSection &isec,
17251725
const StringPiece &piece) {
1726-
return llvm::Align(1ULL << llvm::countr_zero(isec->align | piece.inSecOff));
1726+
return llvm::Align(1ULL << llvm::countr_zero(isec.align | piece.inSecOff));
17271727
}
17281728

17291729
void CStringSection::finalizeContents() {
17301730
size = 0;
1731-
// TODO: Call buildCStringPriorities() to support cstring ordering when
1732-
// deduplication is off, although this may negatively impact build
1733-
// performance.
1734-
for (CStringInputSection *isec : inputs) {
1735-
for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
1736-
if (!piece.live)
1737-
continue;
1738-
piece.outSecOff = alignTo(size, getStringPieceAlignment(isec, piece));
1739-
StringRef string = isec->getStringRef(i);
1740-
size = piece.outSecOff + string.size() + 1; // account for null terminator
1741-
}
1731+
priorityBuilder.forEachStringPiece(
1732+
inputs,
1733+
[&](CStringInputSection &isec, StringPiece &piece, size_t pieceIdx) {
1734+
piece.outSecOff = alignTo(size, getStringPieceAlignment(isec, piece));
1735+
StringRef string = isec.getStringRef(pieceIdx);
1736+
size =
1737+
piece.outSecOff + string.size() + 1; // account for null terminator
1738+
},
1739+
/*forceInputOrder=*/false, /*computeHash=*/true);
1740+
for (CStringInputSection *isec : inputs)
17421741
isec->isFinal = true;
1743-
}
17441742
}
17451743

17461744
void DeduplicatedCStringSection::finalizeContents() {
17471745
// Find the largest alignment required for each string.
17481746
DenseMap<CachedHashStringRef, Align> strToAlignment;
17491747
// Used for tail merging only
17501748
std::vector<CachedHashStringRef> deduplicatedStrs;
1751-
for (const CStringInputSection *isec : inputs) {
1752-
for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
1753-
if (!piece.live)
1754-
continue;
1755-
auto s = isec->getCachedHashStringRef(i);
1756-
assert(isec->align != 0);
1757-
auto align = getStringPieceAlignment(isec, piece);
1758-
auto [it, wasInserted] = strToAlignment.try_emplace(s, align);
1759-
if (config->tailMergeStrings && wasInserted)
1760-
deduplicatedStrs.push_back(s);
1761-
if (!wasInserted && it->second < align)
1762-
it->second = align;
1763-
}
1764-
}
1749+
priorityBuilder.forEachStringPiece(
1750+
inputs,
1751+
[&](CStringInputSection &isec, StringPiece &piece, size_t pieceIdx) {
1752+
auto s = isec.getCachedHashStringRef(pieceIdx);
1753+
assert(isec.align != 0);
1754+
auto align = getStringPieceAlignment(isec, piece);
1755+
auto [it, wasInserted] = strToAlignment.try_emplace(s, align);
1756+
if (config->tailMergeStrings && wasInserted)
1757+
deduplicatedStrs.push_back(s);
1758+
if (!wasInserted && it->second < align)
1759+
it->second = align;
1760+
},
1761+
/*forceInputOrder=*/true);
17651762

17661763
// Like lexigraphical sort, except we read strings in reverse and take the
17671764
// longest string first
@@ -1801,9 +1798,10 @@ void DeduplicatedCStringSection::finalizeContents() {
18011798
// Sort the strings for performance and compression size win, and then
18021799
// assign an offset for each string and save it to the corresponding
18031800
// StringPieces for easy access.
1804-
for (auto &[isec, i] : priorityBuilder.buildCStringPriorities(inputs)) {
1805-
auto &piece = isec->pieces[i];
1806-
auto s = isec->getCachedHashStringRef(i);
1801+
priorityBuilder.forEachStringPiece(inputs, [&](CStringInputSection &isec,
1802+
StringPiece &piece,
1803+
size_t pieceIdx) {
1804+
auto s = isec.getCachedHashStringRef(pieceIdx);
18071805
// Any string can be tail merged with itself with an offset of zero
18081806
uint64_t tailMergeOffset = 0;
18091807
auto mergeIt =
@@ -1829,7 +1827,7 @@ void DeduplicatedCStringSection::finalizeContents() {
18291827
stringOffsetMap[tailMergedString] = piece.outSecOff;
18301828
assert(isAligned(strToAlignment.at(tailMergedString), piece.outSecOff));
18311829
}
1832-
}
1830+
});
18331831
for (CStringInputSection *isec : inputs)
18341832
isec->isFinal = true;
18351833
}

0 commit comments

Comments
 (0)