Skip to content

Commit 49fdbbc

Browse files
authored
[BOLT] Match functions with exact hash (llvm#96572)
Added flag '--match-profile-with-function-hash' to match functions based on exact hash. After identical and LTO name matching, more functions can be recovered for inference with exact hash, in the case of function renaming with no functional changes. Collisions are possible in the unlikely case where multiple functions share the same exact hash. The flag is off by default as it requires the processing of all binary functions and subsequently is expensive. Test Plan: added hashing-based-function-matching.test.
1 parent aec7670 commit 49fdbbc

File tree

6 files changed

+148
-13
lines changed

6 files changed

+148
-13
lines changed

bolt/docs/CommandLineArgumentReference.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,10 @@
259259

260260
Always use long jumps/nops for Linux kernel static keys
261261

262+
- `--match-profile-with-function-hash`
263+
264+
Match profile with function hash
265+
262266
- `--max-data-relocations=<uint>`
263267

264268
Maximum number of data relocations to process

bolt/lib/Profile/YAMLProfileReader.cpp

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,18 @@ namespace opts {
2222
extern cl::opt<unsigned> Verbosity;
2323
extern cl::OptionCategory BoltOptCategory;
2424
extern cl::opt<bool> InferStaleProfile;
25+
extern cl::opt<bool> Lite;
2526

2627
static llvm::cl::opt<bool>
2728
IgnoreHash("profile-ignore-hash",
2829
cl::desc("ignore hash while reading function profile"),
2930
cl::Hidden, cl::cat(BoltOptCategory));
3031

32+
llvm::cl::opt<bool>
33+
MatchProfileWithFunctionHash("match-profile-with-function-hash",
34+
cl::desc("Match profile with function hash"),
35+
cl::Hidden, cl::cat(BoltOptCategory));
36+
3137
llvm::cl::opt<bool> ProfileUseDFS("profile-use-dfs",
3238
cl::desc("use DFS order for YAML profile"),
3339
cl::Hidden, cl::cat(BoltOptCategory));
@@ -329,6 +335,8 @@ Error YAMLProfileReader::preprocessProfile(BinaryContext &BC) {
329335
}
330336

331337
bool YAMLProfileReader::mayHaveProfileData(const BinaryFunction &BF) {
338+
if (opts::MatchProfileWithFunctionHash)
339+
return true;
332340
for (StringRef Name : BF.getNames())
333341
if (ProfileFunctionNames.contains(Name))
334342
return true;
@@ -363,9 +371,24 @@ Error YAMLProfileReader::readProfile(BinaryContext &BC) {
363371
return Profile.Hash == static_cast<uint64_t>(BF.getHash());
364372
};
365373

366-
// We have to do 2 passes since LTO introduces an ambiguity in function
367-
// names. The first pass assigns profiles that match 100% by name and
368-
// by hash. The second pass allows name ambiguity for LTO private functions.
374+
uint64_t MatchedWithExactName = 0;
375+
uint64_t MatchedWithHash = 0;
376+
uint64_t MatchedWithLTOCommonName = 0;
377+
378+
// Computes hash for binary functions.
379+
if (opts::MatchProfileWithFunctionHash) {
380+
for (auto &[_, BF] : BC.getBinaryFunctions()) {
381+
BF.computeHash(YamlBP.Header.IsDFSOrder, YamlBP.Header.HashFunction);
382+
}
383+
} else if (!opts::IgnoreHash) {
384+
for (BinaryFunction *BF : ProfileBFs) {
385+
if (!BF)
386+
continue;
387+
BF->computeHash(YamlBP.Header.IsDFSOrder, YamlBP.Header.HashFunction);
388+
}
389+
}
390+
391+
// This first pass assigns profiles that match 100% by name and by hash.
369392
for (auto [YamlBF, BF] : llvm::zip_equal(YamlBP.Functions, ProfileBFs)) {
370393
if (!BF)
371394
continue;
@@ -374,15 +397,35 @@ Error YAMLProfileReader::readProfile(BinaryContext &BC) {
374397
// the profile.
375398
Function.setExecutionCount(BinaryFunction::COUNT_NO_PROFILE);
376399

377-
// Recompute hash once per function.
378-
if (!opts::IgnoreHash)
379-
Function.computeHash(YamlBP.Header.IsDFSOrder,
380-
YamlBP.Header.HashFunction);
381-
382-
if (profileMatches(YamlBF, Function))
400+
if (profileMatches(YamlBF, Function)) {
383401
matchProfileToFunction(YamlBF, Function);
402+
++MatchedWithExactName;
403+
}
384404
}
385405

406+
// Iterates through profiled functions to match the first binary function with
407+
// the same exact hash. Serves to match identical, renamed functions.
408+
// Collisions are possible where multiple functions share the same exact hash.
409+
if (opts::MatchProfileWithFunctionHash) {
410+
DenseMap<size_t, BinaryFunction *> StrictHashToBF;
411+
StrictHashToBF.reserve(BC.getBinaryFunctions().size());
412+
413+
for (auto &[_, BF] : BC.getBinaryFunctions())
414+
StrictHashToBF[BF.getHash()] = &BF;
415+
416+
for (yaml::bolt::BinaryFunctionProfile &YamlBF : YamlBP.Functions) {
417+
if (YamlBF.Used)
418+
continue;
419+
auto It = StrictHashToBF.find(YamlBF.Hash);
420+
if (It != StrictHashToBF.end() && !ProfiledFunctions.count(It->second)) {
421+
BinaryFunction *BF = It->second;
422+
matchProfileToFunction(YamlBF, *BF);
423+
++MatchedWithHash;
424+
}
425+
}
426+
}
427+
428+
// This second pass allows name ambiguity for LTO private functions.
386429
for (const auto &[CommonName, LTOProfiles] : LTOCommonNameMap) {
387430
if (!LTOCommonNameFunctionMap.contains(CommonName))
388431
continue;
@@ -396,6 +439,7 @@ Error YAMLProfileReader::readProfile(BinaryContext &BC) {
396439
for (BinaryFunction *BF : Functions) {
397440
if (!ProfiledFunctions.count(BF) && profileMatches(*YamlBF, *BF)) {
398441
matchProfileToFunction(*YamlBF, *BF);
442+
++MatchedWithLTOCommonName;
399443
return true;
400444
}
401445
}
@@ -407,8 +451,10 @@ Error YAMLProfileReader::readProfile(BinaryContext &BC) {
407451
// partially.
408452
if (!ProfileMatched && LTOProfiles.size() == 1 && Functions.size() == 1 &&
409453
!LTOProfiles.front()->Used &&
410-
!ProfiledFunctions.count(*Functions.begin()))
454+
!ProfiledFunctions.count(*Functions.begin())) {
411455
matchProfileToFunction(*LTOProfiles.front(), **Functions.begin());
456+
++MatchedWithLTOCommonName;
457+
}
412458
}
413459

414460
for (auto [YamlBF, BF] : llvm::zip_equal(YamlBP.Functions, ProfileBFs))
@@ -420,6 +466,15 @@ Error YAMLProfileReader::readProfile(BinaryContext &BC) {
420466
errs() << "BOLT-WARNING: profile ignored for function " << YamlBF.Name
421467
<< '\n';
422468

469+
if (opts::Verbosity >= 1) {
470+
outs() << "BOLT-INFO: matched " << MatchedWithExactName
471+
<< " functions with identical names\n";
472+
outs() << "BOLT-INFO: matched " << MatchedWithHash
473+
<< " functions with hash\n";
474+
outs() << "BOLT-INFO: matched " << MatchedWithLTOCommonName
475+
<< " functions with matching LTO common names\n";
476+
}
477+
423478
// Set for parseFunctionProfile().
424479
NormalizeByInsnCount = usesEvent("cycles") || usesEvent("instructions");
425480
NormalizeByCalls = usesEvent("branches");
@@ -439,6 +494,12 @@ Error YAMLProfileReader::readProfile(BinaryContext &BC) {
439494

440495
BC.setNumUnusedProfiledObjects(NumUnused);
441496

497+
if (opts::Lite && opts::MatchProfileWithFunctionHash) {
498+
for (BinaryFunction *BF : BC.getAllBinaryFunctions())
499+
if (!BF->hasProfile())
500+
BF->setIgnored();
501+
}
502+
442503
return Error::success();
443504
}
444505

bolt/lib/Rewrite/RewriteInstance.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ extern cl::opt<bool> Hugify;
8282
extern cl::opt<bool> Instrument;
8383
extern cl::opt<JumpTableSupportLevel> JumpTables;
8484
extern cl::opt<bool> KeepNops;
85+
extern cl::opt<bool> Lite;
8586
extern cl::list<std::string> ReorderData;
8687
extern cl::opt<bolt::ReorderFunctions::ReorderType> ReorderFunctions;
8788
extern cl::opt<bool> TerminalTrap;
@@ -140,9 +141,6 @@ KeepTmp("keep-tmp",
140141
cl::Hidden,
141142
cl::cat(BoltCategory));
142143

143-
cl::opt<bool> Lite("lite", cl::desc("skip processing of cold functions"),
144-
cl::cat(BoltCategory));
145-
146144
static cl::opt<unsigned>
147145
LiteThresholdPct("lite-threshold-pct",
148146
cl::desc("threshold (in percent) for selecting functions to process in lite "

bolt/lib/Utils/CommandLineOpts.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ cl::opt<bool>
128128
cl::desc("instrument code to generate accurate profile data"),
129129
cl::cat(BoltOptCategory));
130130

131+
cl::opt<bool> Lite("lite", cl::desc("skip processing of cold functions"),
132+
cl::cat(BoltCategory));
133+
131134
cl::opt<std::string>
132135
OutputFilename("o",
133136
cl::desc("<output file>"),
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
## Tests function matching in YAMLProfileReader by function hash.
2+
3+
# REQUIRES: system-linux
4+
# RUN: split-file %s %t
5+
# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-unknown %t/main.s -o %t.o
6+
# RUN: %clang %cflags %t.o -o %t.exe -Wl,-q -nostdlib
7+
# RUN: llvm-bolt %t.exe -o %t.out --data %t/yaml -v=2 \
8+
# RUN: --print-cfg --match-profile-with-function-hash --profile-ignore-hash=0 2>&1 | FileCheck %s
9+
10+
# CHECK: BOLT-INFO: matched 1 functions with hash
11+
12+
#--- main.s
13+
.globl main
14+
.type main, @function
15+
main:
16+
.cfi_startproc
17+
.LBB00:
18+
pushq %rbp
19+
movq %rsp, %rbp
20+
subq $16, %rsp
21+
testq %rax, %rax
22+
js .LBB03
23+
.LBB01:
24+
jne .LBB04
25+
.LBB02:
26+
nop
27+
.LBB03:
28+
xorl %eax, %eax
29+
addq $16, %rsp
30+
popq %rbp
31+
retq
32+
.LBB04:
33+
xorl %eax, %eax
34+
addq $16, %rsp
35+
popq %rbp
36+
retq
37+
## For relocations against .text
38+
.reloc 0, R_X86_64_NONE
39+
.cfi_endproc
40+
.size main, .-main
41+
42+
#--- yaml
43+
---
44+
header:
45+
profile-version: 1
46+
binary-name: 'hashing-based-function-matching.s.tmp.exe'
47+
binary-build-id: '<unknown>'
48+
profile-flags: [ lbr ]
49+
profile-origin: branch profile reader
50+
profile-events: ''
51+
dfs-order: false
52+
hash-func: xxh3
53+
functions:
54+
- name: main2
55+
fid: 0
56+
hash: 0x6E7F15254DE2478
57+
exec: 1
58+
nblocks: 6
59+
blocks:
60+
- bid: 1
61+
insns: 1
62+
succ: [ { bid: 3, cnt: 1} ]
63+
...

llvm/docs/ReleaseNotes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,12 @@ Changes to the LLVM tools
372372
Changes to LLDB
373373
---------------------------------
374374

375+
Changes to BOLT
376+
---------------------------------
377+
* Now supports ``--match-profile-with-function-hash`` to match profiled and
378+
binary functions with exact hash, allowing for the matching of renamed but
379+
identical functions.
380+
375381
Changes to Sanitizers
376382
---------------------
377383

0 commit comments

Comments
 (0)