Skip to content

Commit 40933fd

Browse files
SharonXSharonSharon Xu
andauthored
[lld][macho] Support order cstrings with -order_file (#140307)
Expand the `-order_file` also accept cstrings to order. The purpose is to order hot cstrings for performance (implemented in this diff), and then later on we can also order cold cstrings for compression size win. Due to the speciality of cstrings, there's no way to pass in symbol names in the order file as the existing -order_file, so we expect `<hash of cstring literal content>` to represent/identify each cstring. ``` // An order file has one entry per line, in the following format: // // <cpu>:<object file>:[<symbol name> | CStringEntryPrefix <cstring hash>] // // <cpu> and <object file> are optional. // If not specified, then that entry tries to match either, // // 1) any symbol of the <symbol 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.) // // or 2) any cstring literal with the given hash, if the entry has the // CStringEntryPrefix prefix defined below in the file. <cstring hash> is the // hash of cstring literal content. // // Cstring literals are not symbolized, we can't identify them by name // However, cstrings are deduplicated, hence unique, so we use the hash of // the content of cstring literals to identify them and assign priority to it. // We use the same hash as used in StringPiece, i.e. 31 bit: // xxh3_64bits(string) & 0x7fffffff // ``` The ordering of cstring has to happen during/before the finalizing of the cstring section content in the `finalizeContents()` function, which happens before the writer is run --------- Co-authored-by: Sharon Xu <[email protected]>
1 parent 5990624 commit 40933fd

File tree

4 files changed

+331
-33
lines changed

4 files changed

+331
-33
lines changed

lld/MachO/SectionPriorities.cpp

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -247,15 +247,13 @@ DenseMap<const InputSection *, int> CallGraphSort::run() {
247247
}
248248

249249
std::optional<int>
250-
macho::PriorityBuilder::getSymbolPriority(const Defined *sym) {
251-
if (sym->isAbsolute())
252-
return std::nullopt;
250+
macho::PriorityBuilder::getSymbolOrCStringPriority(const StringRef key,
251+
InputFile *f) {
253252

254-
auto it = priorities.find(utils::getRootSymbol(sym->getName()));
253+
auto it = priorities.find(key);
255254
if (it == priorities.end())
256255
return std::nullopt;
257256
const SymbolPriorityEntry &entry = it->second;
258-
const InputFile *f = sym->isec()->getFile();
259257
if (!f)
260258
return entry.anyObjectFile;
261259
// We don't use toString(InputFile *) here because it returns the full path
@@ -269,6 +267,14 @@ macho::PriorityBuilder::getSymbolPriority(const Defined *sym) {
269267
return std::min(entry.objectFiles.lookup(filename), entry.anyObjectFile);
270268
}
271269

270+
std::optional<int>
271+
macho::PriorityBuilder::getSymbolPriority(const Defined *sym) {
272+
if (sym->isAbsolute())
273+
return std::nullopt;
274+
return getSymbolOrCStringPriority(utils::getRootSymbol(sym->getName()),
275+
sym->isec()->getFile());
276+
}
277+
272278
void macho::PriorityBuilder::extractCallGraphProfile() {
273279
TimeTraceScope timeScope("Extract call graph profile");
274280
bool hasOrderFile = !priorities.empty();
@@ -301,7 +307,7 @@ void macho::PriorityBuilder::parseOrderFile(StringRef path) {
301307
int prio = std::numeric_limits<int>::min();
302308
MemoryBufferRef mbref = *buffer;
303309
for (StringRef line : args::getLines(mbref)) {
304-
StringRef objectFile, symbol;
310+
StringRef objectFile, symbolOrCStrHash;
305311
line = line.take_until([](char c) { return c == '#'; }); // ignore comments
306312
line = line.ltrim();
307313

@@ -316,7 +322,6 @@ void macho::PriorityBuilder::parseOrderFile(StringRef path) {
316322

317323
if (cpuType != CPU_TYPE_ANY && cpuType != target->cpuType)
318324
continue;
319-
320325
// Drop the CPU type as well as the colon
321326
if (cpuType != CPU_TYPE_ANY)
322327
line = line.drop_until([](char c) { return c == ':'; }).drop_front();
@@ -331,10 +336,20 @@ void macho::PriorityBuilder::parseOrderFile(StringRef path) {
331336
break;
332337
}
333338
}
334-
symbol = utils::getRootSymbol(line.trim());
335339

336-
if (!symbol.empty()) {
337-
SymbolPriorityEntry &entry = priorities[symbol];
340+
// The rest of the line is either <symbol name> or
341+
// CStringEntryPrefix<cstring hash>
342+
line = line.trim();
343+
if (line.starts_with(CStringEntryPrefix)) {
344+
StringRef possibleHash = line.drop_front(CStringEntryPrefix.size());
345+
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];
338353
if (!objectFile.empty())
339354
entry.objectFiles.insert(std::make_pair(objectFile, prio));
340355
else
@@ -389,3 +404,41 @@ macho::PriorityBuilder::buildInputSectionPriorities() {
389404

390405
return sectionPriorities;
391406
}
407+
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+
418+
for (CStringInputSection *isec : inputs) {
419+
for (const auto &[stringPieceIdx, piece] : llvm::enumerate(isec->pieces)) {
420+
if (!piece.live)
421+
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);
427+
else
428+
hotStringPrioritiesAndStringPieces.emplace_back(
429+
*priority, std::make_pair(isec, stringPieceIdx));
430+
}
431+
}
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;
444+
}

lld/MachO/SectionPriorities.h

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

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

2021
class PriorityBuilder {
2122
public:
@@ -28,17 +29,28 @@ class PriorityBuilder {
2829
//
2930
// An order file has one entry per line, in the following format:
3031
//
31-
// <cpu>:<object file>:<symbol name>
32+
// <cpu>:<object file>:[<symbol name> | CStringEntryPrefix <cstring hash>]
3233
//
33-
// <cpu> and <object file> are optional. If not specified, then that entry
34-
// matches any symbol of that name. Parsing this format is not quite
35-
// straightforward because the symbol name itself can contain colons, so when
36-
// encountering a colon, we consider the preceding characters to decide if it
37-
// can be a valid CPU type or file path.
34+
// <cpu> and <object file> are optional.
35+
// If not specified, then that entry tries to match either,
3836
//
37+
// 1) any symbol of the <symbol name>;
38+
// Parsing this format is not quite straightforward because the symbol name
39+
// itself can contain colons, so when encountering a colon, we consider the
40+
// preceding characters to decide if it can be a valid CPU type or file path.
3941
// If a symbol is matched by multiple entries, then it takes the
4042
// lowest-ordered entry (the one nearest to the front of the list.)
4143
//
44+
// 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
46+
// hash of cstring literal content.
47+
//
48+
// Cstring literals are not symbolized, we can't identify them by name
49+
// However, cstrings are deduplicated, hence unique, so we use the hash of
50+
// the content of cstring literals to identify them and assign priority to it.
51+
// We use the same hash as used in StringPiece, i.e. 31 bit:
52+
// xxh3_64bits(string) & 0x7fffffff
53+
//
4254
// The file can also have line comments that start with '#'.
4355
void parseOrderFile(StringRef path);
4456

@@ -54,6 +66,8 @@ class PriorityBuilder {
5466
// Each section gets assigned the priority of the highest-priority symbol it
5567
// contains.
5668
llvm::DenseMap<const InputSection *, int> buildInputSectionPriorities();
69+
std::vector<StringPiecePair>
70+
buildCStringPriorities(ArrayRef<CStringInputSection *>);
5771

5872
private:
5973
// The symbol with the smallest priority should be ordered first in the output
@@ -65,8 +79,11 @@ class PriorityBuilder {
6579
// The priority given to a matching symbol from a particular object file.
6680
llvm::DenseMap<llvm::StringRef, int> objectFiles;
6781
};
82+
const llvm::StringRef CStringEntryPrefix = "CSTR;";
6883

6984
std::optional<int> getSymbolPriority(const Defined *sym);
85+
std::optional<int> getSymbolOrCStringPriority(const StringRef key,
86+
InputFile *f);
7087
llvm::DenseMap<llvm::StringRef, SymbolPriorityEntry> priorities;
7188
llvm::MapVector<SectionPair, uint64_t> callGraphProfile;
7289
};

lld/MachO/SyntheticSections.cpp

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "InputFiles.h"
1515
#include "ObjC.h"
1616
#include "OutputSegment.h"
17+
#include "SectionPriorities.h"
1718
#include "SymbolTable.h"
1819
#include "Symbols.h"
1920

@@ -1760,26 +1761,25 @@ void DeduplicatedCStringSection::finalizeContents() {
17601761
}
17611762
}
17621763

1763-
// Assign an offset for each string and save it to the corresponding
1764+
// Sort the strings for performance and compression size win, and then
1765+
// assign an offset for each string and save it to the corresponding
17641766
// StringPieces for easy access.
1765-
for (CStringInputSection *isec : inputs) {
1766-
for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
1767-
if (!piece.live)
1768-
continue;
1769-
auto s = isec->getCachedHashStringRef(i);
1770-
auto it = stringOffsetMap.find(s);
1771-
assert(it != stringOffsetMap.end());
1772-
StringOffset &offsetInfo = it->second;
1773-
if (offsetInfo.outSecOff == UINT64_MAX) {
1774-
offsetInfo.outSecOff =
1775-
alignToPowerOf2(size, 1ULL << offsetInfo.trailingZeros);
1776-
size =
1777-
offsetInfo.outSecOff + s.size() + 1; // account for null terminator
1778-
}
1779-
piece.outSecOff = offsetInfo.outSecOff;
1767+
for (auto &[isec, i] : priorityBuilder.buildCStringPriorities(inputs)) {
1768+
auto &piece = isec->pieces[i];
1769+
auto s = isec->getCachedHashStringRef(i);
1770+
auto it = stringOffsetMap.find(s);
1771+
assert(it != stringOffsetMap.end());
1772+
lld::macho::DeduplicatedCStringSection::StringOffset &offsetInfo =
1773+
it->second;
1774+
if (offsetInfo.outSecOff == UINT64_MAX) {
1775+
offsetInfo.outSecOff =
1776+
alignToPowerOf2(size, 1ULL << offsetInfo.trailingZeros);
1777+
size = offsetInfo.outSecOff + s.size() + 1; // account for null terminator
17801778
}
1781-
isec->isFinal = true;
1779+
piece.outSecOff = offsetInfo.outSecOff;
17821780
}
1781+
for (CStringInputSection *isec : inputs)
1782+
isec->isFinal = true;
17831783
}
17841784

17851785
void DeduplicatedCStringSection::writeTo(uint8_t *buf) const {

0 commit comments

Comments
 (0)