Skip to content

Commit 36ed0a9

Browse files
committed
[BOLT] Provide backwards compatibility for YAML profile with std::hash
Summary: xxh3 hash is the default for newly produced profile (sets `hash-func: xxh3`), whereas the profile that doesn't specify `hash-func` will be treated as `hash-func: std-hash`, preserving old behavior.
1 parent e315bf2 commit 36ed0a9

10 files changed

+209
-17
lines changed

bolt/include/bolt/Core/BinaryFunction.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,14 @@ enum IndirectCallPromotionType : char {
7575
ICP_ALL /// Perform ICP on calls and jump tables.
7676
};
7777

78+
/// Hash functions supported for BF/BB hashing.
79+
enum class HashFunction : char {
80+
StdHash, /// std::hash, implementation is platform-dependent. Provided for
81+
/// backwards compatibility.
82+
XXH3, /// llvm::xxh3_64bits, the default.
83+
Default = XXH3,
84+
};
85+
7886
/// Information on a single indirect call to a particular callee.
7987
struct IndirectCallProfile {
8088
MCSymbol *Symbol;
@@ -2234,18 +2242,21 @@ class BinaryFunction {
22342242
///
22352243
/// If \p UseDFS is set, process basic blocks in DFS order. Otherwise, use
22362244
/// the existing layout order.
2245+
/// \p HashFunction specifies which function is used for BF hashing.
22372246
///
22382247
/// By default, instruction operands are ignored while calculating the hash.
22392248
/// The caller can change this via passing \p OperandHashFunc function.
22402249
/// The return result of this function will be mixed with internal hash.
22412250
size_t computeHash(
2242-
bool UseDFS = false,
2251+
bool UseDFS = false, HashFunction HashFunction = HashFunction::Default,
22432252
OperandHashFuncTy OperandHashFunc = [](const MCOperand &) {
22442253
return std::string();
22452254
}) const;
22462255

22472256
/// Compute hash values for each block of the function.
2248-
void computeBlockHashes() const;
2257+
/// \p HashFunction specifies which function is used for BB hashing.
2258+
void
2259+
computeBlockHashes(HashFunction HashFunction = HashFunction::Default) const;
22492260

22502261
void setDWARFUnit(DWARFUnit *Unit) { DwarfUnit = Unit; }
22512262

bolt/include/bolt/Profile/ProfileYAMLMapping.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,14 @@ template <> struct ScalarBitSetTraits<PROFILE_PF> {
178178
}
179179
};
180180

181+
template <> struct ScalarEnumerationTraits<llvm::bolt::HashFunction> {
182+
using HashFunction = llvm::bolt::HashFunction;
183+
static void enumeration(IO &io, HashFunction &value) {
184+
io.enumCase(value, "std-hash", HashFunction::StdHash);
185+
io.enumCase(value, "xxh3", HashFunction::XXH3);
186+
}
187+
};
188+
181189
namespace bolt {
182190
struct BinaryProfileHeader {
183191
uint32_t Version{1};
@@ -188,6 +196,7 @@ struct BinaryProfileHeader {
188196
std::string Origin; // How the profile was obtained.
189197
std::string EventNames; // Events used for sample profile.
190198
bool IsDFSOrder{true}; // Whether using DFS block order in function profile
199+
llvm::bolt::HashFunction HashFunction; // Hash used for BB/BF hashing
191200
};
192201
} // end namespace bolt
193202

@@ -200,6 +209,8 @@ template <> struct MappingTraits<bolt::BinaryProfileHeader> {
200209
YamlIO.mapOptional("profile-origin", Header.Origin);
201210
YamlIO.mapOptional("profile-events", Header.EventNames);
202211
YamlIO.mapOptional("dfs-order", Header.IsDFSOrder);
212+
YamlIO.mapOptional("hash-func", Header.HashFunction,
213+
llvm::bolt::HashFunction::StdHash);
203214
}
204215
};
205216

bolt/lib/Core/BinaryFunction.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3633,7 +3633,7 @@ BinaryFunction::BasicBlockListType BinaryFunction::dfs() const {
36333633
return DFS;
36343634
}
36353635

3636-
size_t BinaryFunction::computeHash(bool UseDFS,
3636+
size_t BinaryFunction::computeHash(bool UseDFS, HashFunction HashFunction,
36373637
OperandHashFuncTy OperandHashFunc) const {
36383638
if (size() == 0)
36393639
return 0;
@@ -3652,7 +3652,13 @@ size_t BinaryFunction::computeHash(bool UseDFS,
36523652
for (const BinaryBasicBlock *BB : Order)
36533653
HashString.append(hashBlock(BC, *BB, OperandHashFunc));
36543654

3655-
return Hash = llvm::xxh3_64bits(HashString);
3655+
switch (HashFunction) {
3656+
case HashFunction::StdHash:
3657+
return Hash = std::hash<std::string>{}(HashString);
3658+
case HashFunction::XXH3:
3659+
return Hash = llvm::xxh3_64bits(HashString);
3660+
}
3661+
llvm_unreachable("Unhandled HashFunction");
36563662
}
36573663

36583664
void BinaryFunction::insertBasicBlocks(

bolt/lib/Passes/IdenticalCodeFolding.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -360,9 +360,9 @@ void IdenticalCodeFolding::runOnFunctions(BinaryContext &BC) {
360360

361361
// Pre-compute hash before pushing into hashtable.
362362
// Hash instruction operands to minimize hash collisions.
363-
BF.computeHash(opts::ICFUseDFS, [&BC](const MCOperand &Op) {
364-
return hashInstOperand(BC, Op);
365-
});
363+
BF.computeHash(
364+
opts::ICFUseDFS, HashFunction::Default,
365+
[&BC](const MCOperand &Op) { return hashInstOperand(BC, Op); });
366366
};
367367

368368
ParallelUtilities::PredicateTy SkipFunc = [&](const BinaryFunction &BF) {

bolt/lib/Profile/StaleProfileMatching.cpp

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ class StaleMatcher {
225225
std::unordered_map<uint16_t, std::vector<HashBlockPairType>> OpHashToBlocks;
226226
};
227227

228-
void BinaryFunction::computeBlockHashes() const {
228+
void BinaryFunction::computeBlockHashes(HashFunction HashFunction) const {
229229
if (size() == 0)
230230
return;
231231

@@ -241,12 +241,26 @@ void BinaryFunction::computeBlockHashes() const {
241241
// Hashing complete instructions.
242242
std::string InstrHashStr = hashBlock(
243243
BC, *BB, [&](const MCOperand &Op) { return hashInstOperand(BC, Op); });
244-
uint64_t InstrHash = llvm::xxh3_64bits(InstrHashStr);
245-
BlendedHashes[I].InstrHash = (uint16_t)InstrHash;
244+
if (HashFunction == HashFunction::StdHash) {
245+
uint64_t InstrHash = std::hash<std::string>{}(InstrHashStr);
246+
BlendedHashes[I].InstrHash = (uint16_t)hash_value(InstrHash);
247+
} else if (HashFunction == HashFunction::XXH3) {
248+
uint64_t InstrHash = llvm::xxh3_64bits(InstrHashStr);
249+
BlendedHashes[I].InstrHash = (uint16_t)InstrHash;
250+
} else {
251+
llvm_unreachable("Unhandled HashFunction");
252+
}
246253
// Hashing opcodes.
247254
std::string OpcodeHashStr = hashBlockLoose(BC, *BB);
248-
OpcodeHashes[I] = llvm::xxh3_64bits(OpcodeHashStr);
249-
BlendedHashes[I].OpcodeHash = (uint16_t)OpcodeHashes[I];
255+
if (HashFunction == HashFunction::StdHash) {
256+
OpcodeHashes[I] = std::hash<std::string>{}(OpcodeHashStr);
257+
BlendedHashes[I].OpcodeHash = (uint16_t)hash_value(OpcodeHashes[I]);
258+
} else if (HashFunction == HashFunction::XXH3) {
259+
OpcodeHashes[I] = llvm::xxh3_64bits(OpcodeHashStr);
260+
BlendedHashes[I].OpcodeHash = (uint16_t)OpcodeHashes[I];
261+
} else {
262+
llvm_unreachable("Unhandled HashFunction");
263+
}
250264
}
251265

252266
// Initialize neighbor hash.
@@ -258,15 +272,25 @@ void BinaryFunction::computeBlockHashes() const {
258272
uint64_t SuccHash = OpcodeHashes[SuccBB->getIndex()];
259273
Hash = hashing::detail::hash_16_bytes(Hash, SuccHash);
260274
}
261-
BlendedHashes[I].SuccHash = (uint8_t)Hash;
275+
if (HashFunction == HashFunction::StdHash) {
276+
// Compatibility with old behavior.
277+
BlendedHashes[I].SuccHash = (uint8_t)hash_value(Hash);
278+
} else {
279+
BlendedHashes[I].SuccHash = (uint8_t)Hash;
280+
}
262281

263282
// Append hashes of predecessors.
264283
Hash = 0;
265284
for (BinaryBasicBlock *PredBB : BB->predecessors()) {
266285
uint64_t PredHash = OpcodeHashes[PredBB->getIndex()];
267286
Hash = hashing::detail::hash_16_bytes(Hash, PredHash);
268287
}
269-
BlendedHashes[I].PredHash = (uint8_t)Hash;
288+
if (HashFunction == HashFunction::StdHash) {
289+
// Compatibility with old behavior.
290+
BlendedHashes[I].PredHash = (uint8_t)hash_value(Hash);
291+
} else {
292+
BlendedHashes[I].PredHash = (uint8_t)Hash;
293+
}
270294
}
271295

272296
// Assign hashes.
@@ -682,7 +706,7 @@ bool YAMLProfileReader::inferStaleProfile(
682706
<< "\"" << BF.getPrintName() << "\"\n");
683707

684708
// Make sure that block hashes are up to date.
685-
BF.computeBlockHashes();
709+
BF.computeBlockHashes(YamlBP.Header.HashFunction);
686710

687711
const BinaryFunction::BasicBlockOrderType BlockOrder(
688712
BF.getLayout().block_begin(), BF.getLayout().block_end());

bolt/lib/Profile/YAMLProfileReader.cpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ bool YAMLProfileReader::parseFunctionProfile(
8383
BinaryContext &BC = BF.getBinaryContext();
8484

8585
const bool IsDFSOrder = YamlBP.Header.IsDFSOrder;
86+
const HashFunction HashFunction = YamlBP.Header.HashFunction;
8687
bool ProfileMatched = true;
8788
uint64_t MismatchedBlocks = 0;
8889
uint64_t MismatchedCalls = 0;
@@ -98,7 +99,8 @@ bool YAMLProfileReader::parseFunctionProfile(
9899
FuncRawBranchCount += YamlSI.Count;
99100
BF.setRawBranchCount(FuncRawBranchCount);
100101

101-
if (!opts::IgnoreHash && YamlBF.Hash != BF.computeHash(IsDFSOrder)) {
102+
if (!opts::IgnoreHash &&
103+
YamlBF.Hash != BF.computeHash(IsDFSOrder, HashFunction)) {
102104
if (opts::Verbosity >= 1)
103105
errs() << "BOLT-WARNING: function hash mismatch\n";
104106
ProfileMatched = false;
@@ -326,6 +328,17 @@ bool YAMLProfileReader::mayHaveProfileData(const BinaryFunction &BF) {
326328
}
327329

328330
Error YAMLProfileReader::readProfile(BinaryContext &BC) {
331+
if (opts::Verbosity >= 1) {
332+
outs() << "BOLT-INFO: YAML profile with hash: ";
333+
switch (YamlBP.Header.HashFunction) {
334+
case HashFunction::StdHash:
335+
outs() << "std::hash\n";
336+
break;
337+
case HashFunction::XXH3:
338+
outs() << "xxh3\n";
339+
break;
340+
}
341+
}
329342
YamlProfileToFunction.resize(YamlBP.Functions.size() + 1);
330343

331344
auto profileMatches = [](const yaml::bolt::BinaryFunctionProfile &Profile,
@@ -348,7 +361,8 @@ Error YAMLProfileReader::readProfile(BinaryContext &BC) {
348361

349362
// Recompute hash once per function.
350363
if (!opts::IgnoreHash)
351-
Function.computeHash(YamlBP.Header.IsDFSOrder);
364+
Function.computeHash(YamlBP.Header.IsDFSOrder,
365+
YamlBP.Header.HashFunction);
352366

353367
if (profileMatches(YamlBF, Function))
354368
matchProfileToFunction(YamlBF, Function);

bolt/lib/Profile/YAMLProfileWriter.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ std::error_code YAMLProfileWriter::writeProfile(const RewriteInstance &RI) {
189189
BP.Header.Id = BuildID ? std::string(*BuildID) : "<unknown>";
190190
BP.Header.Origin = std::string(RI.getProfileReader()->getReaderName());
191191
BP.Header.IsDFSOrder = opts::ProfileUseDFS;
192+
BP.Header.HashFunction = HashFunction::Default;
192193

193194
StringSet<> EventNames = RI.getProfileReader()->getEventNames();
194195
if (!EventNames.empty()) {
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
---
2+
header:
3+
profile-version: 1
4+
binary-name: 'reader-yaml.test.tmp.exe'
5+
binary-build-id: '<unknown>'
6+
profile-flags: [ lbr ]
7+
profile-origin: branch profile reader
8+
profile-events: ''
9+
dfs-order: false
10+
functions:
11+
- name: SolveCubic
12+
fid: 6
13+
hash: 0xC6E9098E973BBE19
14+
exec: 151
15+
nblocks: 18
16+
blocks:
17+
- bid: 0
18+
insns: 43
19+
hash: 0xed4db287e71c0000
20+
exec: 151
21+
succ: [ { bid: 1, cnt: 151, mis: 2 }, { bid: 7, cnt: 0 } ]
22+
- bid: 1
23+
insns: 7
24+
hash: 0x39330000e4560088
25+
succ: [ { bid: 13, cnt: 151 }, { bid: 2, cnt: 0 } ]
26+
- bid: 13
27+
insns: 26
28+
hash: 0xa9700000fe202a7
29+
succ: [ { bid: 3, cnt: 89 }, { bid: 2, cnt: 10 } ]
30+
- bid: 3
31+
insns: 9
32+
hash: 0x62391dad18a700a0
33+
succ: [ { bid: 5, cnt: 151 } ]
34+
- bid: 5
35+
insns: 9
36+
hash: 0x4d906d19ecec0111
37+
- name: usqrt
38+
fid: 7
39+
hash: 0x8B62B1F9AD81EA35
40+
exec: 20
41+
nblocks: 6
42+
blocks:
43+
- bid: 0
44+
insns: 4
45+
hash: 0x1111111111111111
46+
exec: 20
47+
succ: [ { bid: 1, cnt: 0 } ]
48+
- bid: 1
49+
insns: 9
50+
hash: 0x27e43a5e10cd0010
51+
succ: [ { bid: 3, cnt: 320, mis: 171 }, { bid: 2, cnt: 0 } ]
52+
- bid: 3
53+
insns: 2
54+
hash: 0x4db935b6471e0039
55+
succ: [ { bid: 1, cnt: 300, mis: 33 }, { bid: 4, cnt: 20 } ]
56+
...

bolt/test/X86/Inputs/blarge_profile_stale.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ header:
77
profile-origin: branch profile reader
88
profile-events: ''
99
dfs-order: false
10+
hash-func: xxh3
1011
functions:
1112
- name: SolveCubic
1213
fid: 6
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# This script checks that YamlProfileReader in llvm-bolt is reading data
2+
# correctly and stale data is corrected by profile inference.
3+
4+
RUN: yaml2obj %p/Inputs/blarge.yaml &> %t.exe
5+
RUN: llvm-bolt %t.exe -o %t.null -b %p/Inputs/blarge_profile_stale.std-hash.yaml \
6+
RUN: --print-cfg --print-only=usqrt,SolveCubic --infer-stale-profile=1 -v=1 \
7+
RUN: 2>&1 | FileCheck %s
8+
9+
# Verify that yaml reader works as expected.
10+
CHECK: pre-processing profile using YAML profile reader
11+
CHECK: BOLT-INFO: YAML profile with hash: std::hash
12+
13+
# Function "SolveCubic" has stale profile, since there is one jump in the
14+
# profile (from bid=13 to bid=2) which is not in the CFG in the binary. The test
15+
# verifies that the inference is able to match two blocks (bid=1 and bid=13)
16+
# using "loose" hashes and then correctly propagate the counts.
17+
18+
CHECK: Binary Function "SolveCubic" after building cfg {
19+
CHECK: State : CFG constructed
20+
CHECK: Address : 0x400e00
21+
CHECK: Size : 0x368
22+
CHECK: Section : .text
23+
CHECK: IsSimple : 1
24+
CHECK: BB Count : 18
25+
CHECK: Exec Count : 151
26+
CHECK: Branch Count: 552
27+
CHECK: }
28+
# Verify block counts.
29+
CHECK: .LBB00 (43 instructions, align : 1)
30+
CHECK: Successors: .Ltmp[[#BB07:]] (mispreds: 0, count: 0), .LFT[[#BB01:]] (mispreds: 0, count: 151)
31+
CHECK: .LFT[[#BB01:]] (5 instructions, align : 1)
32+
CHECK: Successors: .Ltmp[[#BB013:]] (mispreds: 0, count: 151), .LFT[[#BB02:]] (mispreds: 0, count: 0)
33+
CHECK: .Ltmp[[#BB03:]] (26 instructions, align : 1)
34+
CHECK: Successors: .Ltmp[[#BB05:]] (mispreds: 0, count: 151), .LFT[[#BB04:]] (mispreds: 0, count: 0)
35+
CHECK: .Ltmp[[#BB05:]] (9 instructions, align : 1)
36+
CHECK: .Ltmp[[#BB013:]] (12 instructions, align : 1)
37+
CHECK: Successors: .Ltmp[[#BB03:]] (mispreds: 0, count: 151)
38+
CHECK: End of Function "SolveCubic"
39+
40+
# Function "usqrt" has stale profile, since the number of blocks in the profile
41+
# (nblocks=6) does not match the size of the CFG in the binary. The entry
42+
# block (bid=0) has an incorrect (missing) count, which should be inferred by
43+
# the algorithm.
44+
45+
CHECK: Binary Function "usqrt" after building cfg {
46+
CHECK: State : CFG constructed
47+
CHECK: Address : 0x401170
48+
CHECK: Size : 0x43
49+
CHECK: Section : .text
50+
CHECK: IsSimple : 1
51+
CHECK: BB Count : 5
52+
CHECK: Exec Count : 20
53+
CHECK: Branch Count: 640
54+
CHECK: }
55+
# Verify block counts.
56+
CHECK: .LBB01 (4 instructions, align : 1)
57+
CHECK: Successors: .Ltmp[[#BB113:]] (mispreds: 0, count: 20)
58+
CHECK: .Ltmp[[#BB113:]] (9 instructions, align : 1)
59+
CHECK: Successors: .Ltmp[[#BB112:]] (mispreds: 0, count: 320), .LFT[[#BB10:]] (mispreds: 0, count: 0)
60+
CHECK: .LFT[[#BB10:]] (2 instructions, align : 1)
61+
CHECK: Successors: .Ltmp[[#BB112:]] (mispreds: 0, count: 0)
62+
CHECK: .Ltmp[[#BB112:]] (2 instructions, align : 1)
63+
CHECK: Successors: .Ltmp[[#BB113:]] (mispreds: 0, count: 300), .LFT[[#BB11:]] (mispreds: 0, count: 20)
64+
CHECK: .LFT[[#BB11:]] (2 instructions, align : 1)
65+
CHECK: End of Function "usqrt"
66+
# Check the overall inference stats.
67+
CHECK: 2 out of 7 functions in the binary (28.6%) have non-empty execution profile
68+
CHECK: inferred profile for 2 (100.00% of profiled, 100.00% of stale) functions responsible for {{.*}} samples ({{.*}} out of {{.*}})

0 commit comments

Comments
 (0)