1 | //===- bolt/Profile/YAMLProfileReader.cpp - YAML profile de-serializer ----===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "bolt/Profile/YAMLProfileReader.h" |
10 | #include "bolt/Core/BinaryBasicBlock.h" |
11 | #include "bolt/Core/BinaryFunction.h" |
12 | #include "bolt/Passes/MCF.h" |
13 | #include "bolt/Profile/ProfileYAMLMapping.h" |
14 | #include "bolt/Utils/NameResolver.h" |
15 | #include "bolt/Utils/Utils.h" |
16 | #include "llvm/ADT/STLExtras.h" |
17 | #include "llvm/ADT/edit_distance.h" |
18 | #include "llvm/Demangle/Demangle.h" |
19 | #include "llvm/MC/MCPseudoProbe.h" |
20 | #include "llvm/Support/CommandLine.h" |
21 | |
22 | using namespace llvm; |
23 | |
24 | namespace opts { |
25 | |
26 | extern cl::opt<unsigned> Verbosity; |
27 | extern cl::OptionCategory BoltOptCategory; |
28 | extern cl::opt<bool> InferStaleProfile; |
29 | extern cl::opt<bool> Lite; |
30 | |
31 | static cl::opt<unsigned> NameSimilarityFunctionMatchingThreshold( |
32 | "name-similarity-function-matching-threshold" , |
33 | cl::desc("Match functions using namespace and edit distance" ), cl::init(Val: 0), |
34 | cl::Hidden, cl::cat(BoltOptCategory)); |
35 | |
36 | static llvm::cl::opt<bool> |
37 | IgnoreHash("profile-ignore-hash" , |
38 | cl::desc("ignore hash while reading function profile" ), |
39 | cl::Hidden, cl::cat(BoltOptCategory)); |
40 | |
41 | static llvm::cl::opt<bool> |
42 | MatchProfileWithFunctionHash("match-profile-with-function-hash" , |
43 | cl::desc("Match profile with function hash" ), |
44 | cl::Hidden, cl::cat(BoltOptCategory)); |
45 | static llvm::cl::opt<bool> |
46 | MatchWithCallGraph("match-with-call-graph" , |
47 | cl::desc("Match functions with call graph" ), cl::Hidden, |
48 | cl::cat(BoltOptCategory)); |
49 | |
50 | llvm::cl::opt<bool> ProfileUseDFS("profile-use-dfs" , |
51 | cl::desc("use DFS order for YAML profile" ), |
52 | cl::Hidden, cl::cat(BoltOptCategory)); |
53 | |
54 | extern llvm::cl::opt<bool> StaleMatchingWithPseudoProbes; |
55 | } // namespace opts |
56 | |
57 | namespace llvm { |
58 | namespace bolt { |
59 | |
60 | YAMLProfileReader::CallGraphMatcher::CallGraphMatcher( |
61 | BinaryContext &BC, yaml::bolt::BinaryProfile &YamlBP, |
62 | ProfileLookupMap &IdToYAMLBF) { |
63 | constructBFCG(BC, YamlBP); |
64 | constructYAMLFCG(YamlBP, IdToYAMLBF); |
65 | computeBFNeighborHashes(BC); |
66 | } |
67 | |
68 | void YAMLProfileReader::CallGraphMatcher::constructBFCG( |
69 | BinaryContext &BC, yaml::bolt::BinaryProfile &YamlBP) { |
70 | for (BinaryFunction *BF : BC.getAllBinaryFunctions()) { |
71 | for (const BinaryBasicBlock &BB : BF->blocks()) { |
72 | for (const MCInst &Instr : BB) { |
73 | if (!BC.MIB->isCall(Inst: Instr)) |
74 | continue; |
75 | const MCSymbol *CallSymbol = BC.MIB->getTargetSymbol(Inst: Instr); |
76 | if (!CallSymbol) |
77 | continue; |
78 | BinaryData *BD = BC.getBinaryDataByName(Name: CallSymbol->getName()); |
79 | if (!BD) |
80 | continue; |
81 | BinaryFunction *CalleeBF = BC.getFunctionForSymbol(Symbol: BD->getSymbol()); |
82 | if (!CalleeBF) |
83 | continue; |
84 | |
85 | BFAdjacencyMap[CalleeBF].insert(x: BF); |
86 | BFAdjacencyMap[BF].insert(x: CalleeBF); |
87 | } |
88 | } |
89 | } |
90 | } |
91 | |
92 | void YAMLProfileReader::CallGraphMatcher::computeBFNeighborHashes( |
93 | BinaryContext &BC) { |
94 | for (BinaryFunction *BF : BC.getAllBinaryFunctions()) { |
95 | auto It = BFAdjacencyMap.find(Val: BF); |
96 | if (It == BFAdjacencyMap.end()) |
97 | continue; |
98 | auto &AdjacentBFs = It->second; |
99 | std::string HashStr; |
100 | for (BinaryFunction *BF : AdjacentBFs) |
101 | HashStr += BF->getOneName(); |
102 | uint64_t Hash = std::hash<std::string>{}(HashStr); |
103 | NeighborHashToBFs[Hash].push_back(x: BF); |
104 | } |
105 | } |
106 | |
107 | void YAMLProfileReader::CallGraphMatcher::constructYAMLFCG( |
108 | yaml::bolt::BinaryProfile &YamlBP, ProfileLookupMap &IdToYAMLBF) { |
109 | |
110 | for (auto &CallerYamlBF : YamlBP.Functions) { |
111 | for (auto &YamlBB : CallerYamlBF.Blocks) { |
112 | for (auto &CallSite : YamlBB.CallSites) { |
113 | auto IdToYAMLBFIt = IdToYAMLBF.find(Val: CallSite.DestId); |
114 | if (IdToYAMLBFIt == IdToYAMLBF.end()) |
115 | continue; |
116 | YamlBFAdjacencyMap[&CallerYamlBF].insert(x: IdToYAMLBFIt->second); |
117 | YamlBFAdjacencyMap[IdToYAMLBFIt->second].insert(x: &CallerYamlBF); |
118 | } |
119 | } |
120 | } |
121 | } |
122 | |
123 | bool YAMLProfileReader::isYAML(const StringRef Filename) { |
124 | if (auto MB = MemoryBuffer::getFileOrSTDIN(Filename)) { |
125 | StringRef Buffer = (*MB)->getBuffer(); |
126 | return Buffer.starts_with(Prefix: "---\n" ); |
127 | } else { |
128 | report_error(Message: Filename, EC: MB.getError()); |
129 | } |
130 | return false; |
131 | } |
132 | |
133 | void YAMLProfileReader::buildNameMaps(BinaryContext &BC) { |
134 | auto lookupFunction = [&](StringRef Name) -> BinaryFunction * { |
135 | if (BinaryData *BD = BC.getBinaryDataByName(Name)) |
136 | return BC.getFunctionForSymbol(Symbol: BD->getSymbol()); |
137 | return nullptr; |
138 | }; |
139 | |
140 | ProfileBFs.reserve(n: YamlBP.Functions.size()); |
141 | |
142 | for (yaml::bolt::BinaryFunctionProfile &YamlBF : YamlBP.Functions) { |
143 | StringRef Name = YamlBF.Name; |
144 | const size_t Pos = Name.find(Str: "(*" ); |
145 | if (Pos != StringRef::npos) |
146 | Name = Name.substr(Start: 0, N: Pos); |
147 | ProfileFunctionNames.insert(key: Name); |
148 | ProfileBFs.push_back(x: lookupFunction(Name)); |
149 | if (const std::optional<StringRef> CommonName = getLTOCommonName(Name)) |
150 | LTOCommonNameMap[*CommonName].push_back(x: &YamlBF); |
151 | } |
152 | for (auto &[Symbol, BF] : BC.SymbolToFunctionMap) { |
153 | StringRef Name = Symbol->getName(); |
154 | if (const std::optional<StringRef> CommonName = getLTOCommonName(Name)) |
155 | LTOCommonNameFunctionMap[*CommonName].insert(x: BF); |
156 | } |
157 | } |
158 | |
159 | bool YAMLProfileReader::hasLocalsWithFileName() const { |
160 | return llvm::any_of(Range: ProfileFunctionNames.keys(), P: [](StringRef FuncName) { |
161 | return FuncName.count(C: '/') == 2 && FuncName[0] != '/'; |
162 | }); |
163 | } |
164 | |
165 | bool YAMLProfileReader::parseFunctionProfile( |
166 | BinaryFunction &BF, const yaml::bolt::BinaryFunctionProfile &YamlBF) { |
167 | BinaryContext &BC = BF.getBinaryContext(); |
168 | |
169 | const bool IsDFSOrder = YamlBP.Header.IsDFSOrder; |
170 | const HashFunction HashFunction = YamlBP.Header.HashFunction; |
171 | bool ProfileMatched = true; |
172 | uint64_t MismatchedBlocks = 0; |
173 | uint64_t MismatchedCalls = 0; |
174 | uint64_t MismatchedEdges = 0; |
175 | |
176 | uint64_t FunctionExecutionCount = 0; |
177 | |
178 | BF.setExecutionCount(YamlBF.ExecCount); |
179 | BF.setExternEntryCount(YamlBF.ExternEntryCount); |
180 | |
181 | uint64_t FuncRawBranchCount = 0; |
182 | for (const yaml::bolt::BinaryBasicBlockProfile &YamlBB : YamlBF.Blocks) |
183 | for (const yaml::bolt::SuccessorInfo &YamlSI : YamlBB.Successors) |
184 | FuncRawBranchCount += YamlSI.Count; |
185 | BF.setRawSampleCount(FuncRawBranchCount); |
186 | |
187 | if (BF.empty()) |
188 | return true; |
189 | |
190 | if (!opts::IgnoreHash) { |
191 | if (!BF.getHash()) |
192 | BF.computeHash(UseDFS: IsDFSOrder, HashFunction); |
193 | if (YamlBF.Hash != BF.getHash()) { |
194 | if (opts::Verbosity >= 1) |
195 | errs() << "BOLT-WARNING: function hash mismatch\n" ; |
196 | ProfileMatched = false; |
197 | } |
198 | } |
199 | |
200 | if (YamlBF.NumBasicBlocks != BF.size()) { |
201 | if (opts::Verbosity >= 1) |
202 | errs() << "BOLT-WARNING: number of basic blocks mismatch\n" ; |
203 | ProfileMatched = false; |
204 | } |
205 | |
206 | BinaryFunction::BasicBlockOrderType Order; |
207 | if (IsDFSOrder) |
208 | llvm::copy(Range: BF.dfs(), Out: std::back_inserter(x&: Order)); |
209 | else |
210 | llvm::copy(Range: BF.getLayout().blocks(), Out: std::back_inserter(x&: Order)); |
211 | |
212 | for (const yaml::bolt::BinaryBasicBlockProfile &YamlBB : YamlBF.Blocks) { |
213 | if (YamlBB.Index >= Order.size()) { |
214 | if (opts::Verbosity >= 2) |
215 | errs() << "BOLT-WARNING: index " << YamlBB.Index |
216 | << " is out of bounds\n" ; |
217 | ++MismatchedBlocks; |
218 | continue; |
219 | } |
220 | |
221 | BinaryBasicBlock &BB = *Order[YamlBB.Index]; |
222 | |
223 | // Basic samples profile (without LBR) does not have branches information |
224 | // and needs a special processing. |
225 | if (YamlBP.Header.Flags & BinaryFunction::PF_BASIC) { |
226 | if (!YamlBB.EventCount) { |
227 | BB.setExecutionCount(0); |
228 | continue; |
229 | } |
230 | uint64_t NumSamples = YamlBB.EventCount * 1000; |
231 | if (NormalizeByInsnCount && BB.getNumNonPseudos()) |
232 | NumSamples /= BB.getNumNonPseudos(); |
233 | else if (NormalizeByCalls) |
234 | NumSamples /= BB.getNumCalls() + 1; |
235 | |
236 | BB.setExecutionCount(NumSamples); |
237 | if (BB.isEntryPoint()) |
238 | FunctionExecutionCount += NumSamples; |
239 | continue; |
240 | } |
241 | |
242 | BB.setExecutionCount(YamlBB.ExecCount); |
243 | |
244 | for (const yaml::bolt::CallSiteInfo &YamlCSI : YamlBB.CallSites) { |
245 | BinaryFunction *Callee = YamlProfileToFunction.lookup(Val: YamlCSI.DestId); |
246 | bool IsFunction = Callee ? true : false; |
247 | MCSymbol *CalleeSymbol = nullptr; |
248 | if (IsFunction) |
249 | CalleeSymbol = Callee->getSymbolForEntryID(EntryNum: YamlCSI.EntryDiscriminator); |
250 | |
251 | BF.getAllCallSites().emplace_back(Args&: CalleeSymbol, Args: YamlCSI.Count, |
252 | Args: YamlCSI.Mispreds, Args: YamlCSI.Offset); |
253 | |
254 | if (YamlCSI.Offset >= BB.getOriginalSize()) { |
255 | if (opts::Verbosity >= 2) |
256 | errs() << "BOLT-WARNING: offset " << YamlCSI.Offset |
257 | << " out of bounds in block " << BB.getName() << '\n'; |
258 | ++MismatchedCalls; |
259 | continue; |
260 | } |
261 | |
262 | MCInst *Instr = |
263 | BF.getInstructionAtOffset(Offset: BB.getInputOffset() + YamlCSI.Offset); |
264 | if (!Instr) { |
265 | if (opts::Verbosity >= 2) |
266 | errs() << "BOLT-WARNING: no instruction at offset " << YamlCSI.Offset |
267 | << " in block " << BB.getName() << '\n'; |
268 | ++MismatchedCalls; |
269 | continue; |
270 | } |
271 | if (!BC.MIB->isCall(Inst: *Instr) && !BC.MIB->isIndirectBranch(Inst: *Instr)) { |
272 | if (opts::Verbosity >= 2) |
273 | errs() << "BOLT-WARNING: expected call at offset " << YamlCSI.Offset |
274 | << " in block " << BB.getName() << '\n'; |
275 | ++MismatchedCalls; |
276 | continue; |
277 | } |
278 | |
279 | auto setAnnotation = [&](StringRef Name, uint64_t Count) { |
280 | if (BC.MIB->hasAnnotation(Inst: *Instr, Name)) { |
281 | if (opts::Verbosity >= 1) |
282 | errs() << "BOLT-WARNING: ignoring duplicate " << Name |
283 | << " info for offset 0x" << Twine::utohexstr(Val: YamlCSI.Offset) |
284 | << " in function " << BF << '\n'; |
285 | return; |
286 | } |
287 | BC.MIB->addAnnotation(Inst&: *Instr, Name, Val: Count); |
288 | }; |
289 | |
290 | if (BC.MIB->isIndirectCall(Inst: *Instr) || BC.MIB->isIndirectBranch(Inst: *Instr)) { |
291 | auto &CSP = BC.MIB->getOrCreateAnnotationAs<IndirectCallSiteProfile>( |
292 | Inst&: *Instr, Name: "CallProfile" ); |
293 | CSP.emplace_back(Args&: CalleeSymbol, Args: YamlCSI.Count, Args: YamlCSI.Mispreds); |
294 | } else if (BC.MIB->getConditionalTailCall(Inst: *Instr)) { |
295 | setAnnotation("CTCTakenCount" , YamlCSI.Count); |
296 | setAnnotation("CTCMispredCount" , YamlCSI.Mispreds); |
297 | } else { |
298 | setAnnotation("Count" , YamlCSI.Count); |
299 | } |
300 | } |
301 | |
302 | for (const yaml::bolt::SuccessorInfo &YamlSI : YamlBB.Successors) { |
303 | if (YamlSI.Index >= Order.size()) { |
304 | if (opts::Verbosity >= 1) |
305 | errs() << "BOLT-WARNING: index out of bounds for profiled block\n" ; |
306 | ++MismatchedEdges; |
307 | continue; |
308 | } |
309 | |
310 | BinaryBasicBlock *ToBB = Order[YamlSI.Index]; |
311 | if (!BB.getSuccessor(Label: ToBB->getLabel())) { |
312 | // Allow passthrough blocks. |
313 | BinaryBasicBlock *FTSuccessor = BB.getConditionalSuccessor(Condition: false); |
314 | if (FTSuccessor && FTSuccessor->succ_size() == 1 && |
315 | FTSuccessor->getSuccessor(Label: ToBB->getLabel())) { |
316 | BinaryBasicBlock::BinaryBranchInfo &FTBI = |
317 | FTSuccessor->getBranchInfo(Succ: *ToBB); |
318 | FTBI.Count += YamlSI.Count; |
319 | FTBI.MispredictedCount += YamlSI.Mispreds; |
320 | ToBB = FTSuccessor; |
321 | } else { |
322 | if (opts::Verbosity >= 1) |
323 | errs() << "BOLT-WARNING: no successor for block " << BB.getName() |
324 | << " that matches index " << YamlSI.Index << " or block " |
325 | << ToBB->getName() << '\n'; |
326 | ++MismatchedEdges; |
327 | continue; |
328 | } |
329 | } |
330 | |
331 | BinaryBasicBlock::BinaryBranchInfo &BI = BB.getBranchInfo(Succ: *ToBB); |
332 | BI.Count += YamlSI.Count; |
333 | BI.MispredictedCount += YamlSI.Mispreds; |
334 | } |
335 | } |
336 | |
337 | // If basic block profile wasn't read it should be 0. |
338 | for (BinaryBasicBlock &BB : BF) |
339 | if (BB.getExecutionCount() == BinaryBasicBlock::COUNT_NO_PROFILE) |
340 | BB.setExecutionCount(0); |
341 | |
342 | if (YamlBP.Header.Flags & BinaryFunction::PF_BASIC) |
343 | BF.setExecutionCount(FunctionExecutionCount); |
344 | |
345 | ProfileMatched &= !MismatchedBlocks && !MismatchedCalls && !MismatchedEdges; |
346 | |
347 | if (!ProfileMatched) { |
348 | if (opts::Verbosity >= 1) |
349 | errs() << "BOLT-WARNING: " << MismatchedBlocks << " blocks, " |
350 | << MismatchedCalls << " calls, and " << MismatchedEdges |
351 | << " edges in profile did not match function " << BF << '\n'; |
352 | |
353 | if (YamlBF.NumBasicBlocks != BF.size()) |
354 | ++BC.Stats.NumStaleFuncsWithEqualBlockCount; |
355 | |
356 | if (!opts::InferStaleProfile) |
357 | return false; |
358 | ArrayRef<ProbeMatchSpec> ProbeMatchSpecs; |
359 | auto BFIt = BFToProbeMatchSpecs.find(Val: &BF); |
360 | if (BFIt != BFToProbeMatchSpecs.end()) |
361 | ProbeMatchSpecs = BFIt->second; |
362 | ProfileMatched = inferStaleProfile(Function&: BF, YamlBF, ProbeMatchSpecs); |
363 | } |
364 | if (ProfileMatched) |
365 | BF.markProfiled(Flags: YamlBP.Header.Flags); |
366 | |
367 | return ProfileMatched; |
368 | } |
369 | |
370 | Error YAMLProfileReader::preprocessProfile(BinaryContext &BC) { |
371 | ErrorOr<std::unique_ptr<MemoryBuffer>> MB = |
372 | MemoryBuffer::getFileOrSTDIN(Filename); |
373 | if (std::error_code EC = MB.getError()) { |
374 | errs() << "ERROR: cannot open " << Filename << ": " << EC.message() << "\n" ; |
375 | return errorCodeToError(EC); |
376 | } |
377 | yaml::Input YamlInput(MB.get()->getBuffer()); |
378 | YamlInput.setAllowUnknownKeys(true); |
379 | |
380 | // Consume YAML file. |
381 | YamlInput >> YamlBP; |
382 | if (YamlInput.error()) { |
383 | errs() << "BOLT-ERROR: syntax error parsing profile in " << Filename |
384 | << " : " << YamlInput.error().message() << '\n'; |
385 | return errorCodeToError(EC: YamlInput.error()); |
386 | } |
387 | |
388 | // Sanity check. |
389 | if (YamlBP.Header.Version != 1) |
390 | return make_error<StringError>( |
391 | Args: Twine("cannot read profile : unsupported version" ), |
392 | Args: inconvertibleErrorCode()); |
393 | |
394 | if (YamlBP.Header.EventNames.find(c: ',') != StringRef::npos) |
395 | return make_error<StringError>( |
396 | Args: Twine("multiple events in profile are not supported" ), |
397 | Args: inconvertibleErrorCode()); |
398 | |
399 | // Match profile to function based on a function name. |
400 | buildNameMaps(BC); |
401 | |
402 | // Preliminary assign function execution count. |
403 | for (auto [YamlBF, BF] : llvm::zip_equal(t&: YamlBP.Functions, u&: ProfileBFs)) { |
404 | if (!BF) |
405 | continue; |
406 | if (!BF->hasProfile()) { |
407 | BF->setExecutionCount(YamlBF.ExecCount); |
408 | } else { |
409 | if (opts::Verbosity >= 1) { |
410 | errs() << "BOLT-WARNING: dropping duplicate profile for " << YamlBF.Name |
411 | << '\n'; |
412 | } |
413 | BF = nullptr; |
414 | } |
415 | } |
416 | |
417 | return Error::success(); |
418 | } |
419 | |
420 | bool YAMLProfileReader::profileMatches( |
421 | const yaml::bolt::BinaryFunctionProfile &Profile, const BinaryFunction &BF) { |
422 | if (opts::IgnoreHash) |
423 | return Profile.NumBasicBlocks == BF.size(); |
424 | return Profile.Hash == static_cast<uint64_t>(BF.getHash()); |
425 | } |
426 | |
427 | bool YAMLProfileReader::mayHaveProfileData(const BinaryFunction &BF) { |
428 | if (opts::MatchProfileWithFunctionHash || opts::MatchWithCallGraph) |
429 | return true; |
430 | for (StringRef Name : BF.getNames()) |
431 | if (ProfileFunctionNames.contains(key: Name)) |
432 | return true; |
433 | for (StringRef Name : BF.getNames()) { |
434 | if (const std::optional<StringRef> CommonName = getLTOCommonName(Name)) { |
435 | if (LTOCommonNameMap.contains(Key: *CommonName)) |
436 | return true; |
437 | } |
438 | } |
439 | |
440 | return false; |
441 | } |
442 | |
443 | size_t YAMLProfileReader::matchWithExactName() { |
444 | size_t MatchedWithExactName = 0; |
445 | // This first pass assigns profiles that match 100% by name and by hash. |
446 | for (auto [YamlBF, BF] : llvm::zip_equal(t&: YamlBP.Functions, u&: ProfileBFs)) { |
447 | if (!BF) |
448 | continue; |
449 | BinaryFunction &Function = *BF; |
450 | // Clear function call count that may have been set while pre-processing |
451 | // the profile. |
452 | Function.setExecutionCount(BinaryFunction::COUNT_NO_PROFILE); |
453 | |
454 | if (profileMatches(Profile: YamlBF, BF: Function)) { |
455 | matchProfileToFunction(YamlBF, BF&: Function); |
456 | ++MatchedWithExactName; |
457 | } |
458 | } |
459 | return MatchedWithExactName; |
460 | } |
461 | |
462 | size_t YAMLProfileReader::matchWithHash(BinaryContext &BC) { |
463 | // Iterates through profiled functions to match the first binary function with |
464 | // the same exact hash. Serves to match identical, renamed functions. |
465 | // Collisions are possible where multiple functions share the same exact hash. |
466 | size_t MatchedWithHash = 0; |
467 | if (opts::MatchProfileWithFunctionHash) { |
468 | DenseMap<size_t, BinaryFunction *> StrictHashToBF; |
469 | StrictHashToBF.reserve(NumEntries: BC.getBinaryFunctions().size()); |
470 | |
471 | for (auto &[_, BF] : BC.getBinaryFunctions()) |
472 | StrictHashToBF[BF.getHash()] = &BF; |
473 | |
474 | for (yaml::bolt::BinaryFunctionProfile &YamlBF : YamlBP.Functions) { |
475 | if (YamlBF.Used) |
476 | continue; |
477 | auto It = StrictHashToBF.find(Val: YamlBF.Hash); |
478 | if (It != StrictHashToBF.end() && !ProfiledFunctions.count(x: It->second)) { |
479 | BinaryFunction *BF = It->second; |
480 | matchProfileToFunction(YamlBF, BF&: *BF); |
481 | ++MatchedWithHash; |
482 | } |
483 | } |
484 | } |
485 | return MatchedWithHash; |
486 | } |
487 | |
488 | size_t YAMLProfileReader::matchWithLTOCommonName() { |
489 | // This second pass allows name ambiguity for LTO private functions. |
490 | size_t MatchedWithLTOCommonName = 0; |
491 | for (const auto &[CommonName, LTOProfiles] : LTOCommonNameMap) { |
492 | if (!LTOCommonNameFunctionMap.contains(Key: CommonName)) |
493 | continue; |
494 | std::unordered_set<BinaryFunction *> &Functions = |
495 | LTOCommonNameFunctionMap[CommonName]; |
496 | // Return true if a given profile is matched to one of BinaryFunctions with |
497 | // matching LTO common name. |
498 | auto matchProfile = [&](yaml::bolt::BinaryFunctionProfile *YamlBF) { |
499 | if (YamlBF->Used) |
500 | return false; |
501 | for (BinaryFunction *BF : Functions) { |
502 | if (!ProfiledFunctions.count(x: BF) && profileMatches(Profile: *YamlBF, BF: *BF)) { |
503 | matchProfileToFunction(YamlBF&: *YamlBF, BF&: *BF); |
504 | ++MatchedWithLTOCommonName; |
505 | return true; |
506 | } |
507 | } |
508 | return false; |
509 | }; |
510 | bool ProfileMatched = llvm::any_of(Range: LTOProfiles, P: matchProfile); |
511 | |
512 | // If there's only one function with a given name, try to match it |
513 | // partially. |
514 | if (!ProfileMatched && LTOProfiles.size() == 1 && Functions.size() == 1 && |
515 | !LTOProfiles.front()->Used && |
516 | !ProfiledFunctions.count(x: *Functions.begin())) { |
517 | matchProfileToFunction(YamlBF&: *LTOProfiles.front(), BF&: **Functions.begin()); |
518 | ++MatchedWithLTOCommonName; |
519 | } |
520 | } |
521 | return MatchedWithLTOCommonName; |
522 | } |
523 | |
524 | size_t YAMLProfileReader::matchWithCallGraph(BinaryContext &BC) { |
525 | if (!opts::MatchWithCallGraph) |
526 | return 0; |
527 | |
528 | size_t MatchedWithCallGraph = 0; |
529 | CallGraphMatcher CGMatcher(BC, YamlBP, IdToYamLBF); |
530 | |
531 | ItaniumPartialDemangler Demangler; |
532 | auto GetBaseName = [&](std::string &FunctionName) { |
533 | if (Demangler.partialDemangle(MangledName: FunctionName.c_str())) |
534 | return std::string("" ); |
535 | size_t BufferSize = 1; |
536 | char *Buffer = static_cast<char *>(std::malloc(size: BufferSize)); |
537 | char *BaseName = Demangler.getFunctionBaseName(Buf: Buffer, N: &BufferSize); |
538 | if (!BaseName) { |
539 | std::free(ptr: Buffer); |
540 | return std::string("" ); |
541 | } |
542 | if (Buffer != BaseName) |
543 | Buffer = BaseName; |
544 | std::string BaseNameStr(Buffer, BufferSize); |
545 | std::free(ptr: Buffer); |
546 | return BaseNameStr; |
547 | }; |
548 | |
549 | // Matches YAMLBF to BFs with neighbor hashes. |
550 | for (yaml::bolt::BinaryFunctionProfile &YamlBF : YamlBP.Functions) { |
551 | if (YamlBF.Used) |
552 | continue; |
553 | auto AdjacentYamlBFsOpt = CGMatcher.getAdjacentYamlBFs(YamlBF); |
554 | if (!AdjacentYamlBFsOpt) |
555 | continue; |
556 | std::set<yaml::bolt::BinaryFunctionProfile *> AdjacentYamlBFs = |
557 | AdjacentYamlBFsOpt.value(); |
558 | std::string AdjacentYamlBFsHashStr; |
559 | for (auto *AdjacentYamlBF : AdjacentYamlBFs) |
560 | AdjacentYamlBFsHashStr += AdjacentYamlBF->Name; |
561 | uint64_t Hash = std::hash<std::string>{}(AdjacentYamlBFsHashStr); |
562 | auto BFsWithSameHashOpt = CGMatcher.getBFsWithNeighborHash(NeighborHash: Hash); |
563 | if (!BFsWithSameHashOpt) |
564 | continue; |
565 | std::vector<BinaryFunction *> BFsWithSameHash = BFsWithSameHashOpt.value(); |
566 | // Finds the binary function with the longest common prefix to the profiled |
567 | // function and matches. |
568 | BinaryFunction *ClosestBF = nullptr; |
569 | size_t LCP = 0; |
570 | std::string YamlBFBaseName = GetBaseName(YamlBF.Name); |
571 | for (BinaryFunction *BF : BFsWithSameHash) { |
572 | if (ProfiledFunctions.count(x: BF)) |
573 | continue; |
574 | std::string BFName = std::string(BF->getOneName()); |
575 | std::string BFBaseName = GetBaseName(BFName); |
576 | size_t PrefixLength = 0; |
577 | size_t N = std::min(a: YamlBFBaseName.size(), b: BFBaseName.size()); |
578 | for (size_t I = 0; I < N; ++I) { |
579 | if (YamlBFBaseName[I] != BFBaseName[I]) |
580 | break; |
581 | ++PrefixLength; |
582 | } |
583 | if (PrefixLength >= LCP) { |
584 | LCP = PrefixLength; |
585 | ClosestBF = BF; |
586 | } |
587 | } |
588 | if (ClosestBF) { |
589 | matchProfileToFunction(YamlBF, BF&: *ClosestBF); |
590 | ++MatchedWithCallGraph; |
591 | } |
592 | } |
593 | |
594 | return MatchedWithCallGraph; |
595 | } |
596 | |
597 | size_t YAMLProfileReader::InlineTreeNodeMapTy::matchInlineTrees( |
598 | const MCPseudoProbeDecoder &Decoder, |
599 | const std::vector<yaml::bolt::InlineTreeNode> &DecodedInlineTree, |
600 | const MCDecodedPseudoProbeInlineTree *Root) { |
601 | // Match inline tree nodes by GUID, checksum, parent, and call site. |
602 | for (const auto &[InlineTreeNodeId, InlineTreeNode] : |
603 | llvm::enumerate(First: DecodedInlineTree)) { |
604 | uint64_t GUID = InlineTreeNode.GUID; |
605 | uint64_t Hash = InlineTreeNode.Hash; |
606 | uint32_t ParentId = InlineTreeNode.ParentIndexDelta; |
607 | uint32_t CallSiteProbe = InlineTreeNode.CallSiteProbe; |
608 | const MCDecodedPseudoProbeInlineTree *Cur = nullptr; |
609 | if (!InlineTreeNodeId) { |
610 | Cur = Root; |
611 | } else if (const MCDecodedPseudoProbeInlineTree *Parent = |
612 | getInlineTreeNode(ProfileInlineTreeNodeId: ParentId)) { |
613 | for (const MCDecodedPseudoProbeInlineTree &Child : |
614 | Parent->getChildren()) { |
615 | if (Child.Guid == GUID) { |
616 | if (std::get<1>(t: Child.getInlineSite()) == CallSiteProbe) |
617 | Cur = &Child; |
618 | break; |
619 | } |
620 | } |
621 | } |
622 | // Don't match nodes if the profile is stale (mismatching binary FuncHash |
623 | // and YAML Hash) |
624 | if (Cur && Decoder.getFuncDescForGUID(GUID: Cur->Guid)->FuncHash == Hash) |
625 | mapInlineTreeNode(ProfileNodeIdx: InlineTreeNodeId, BinaryNode: Cur); |
626 | } |
627 | return Map.size(); |
628 | } |
629 | |
630 | // Decode index deltas and indirection through \p YamlPD. Return modified copy |
631 | // of \p YamlInlineTree with populated decoded fields (GUID, Hash, ParentIndex). |
632 | static std::vector<yaml::bolt::InlineTreeNode> |
633 | decodeYamlInlineTree(const yaml::bolt::ProfilePseudoProbeDesc &YamlPD, |
634 | std::vector<yaml::bolt::InlineTreeNode> YamlInlineTree) { |
635 | uint32_t ParentId = 0; |
636 | uint32_t PrevGUIDIdx = 0; |
637 | for (yaml::bolt::InlineTreeNode &InlineTreeNode : YamlInlineTree) { |
638 | uint32_t GUIDIdx = InlineTreeNode.GUIDIndex; |
639 | if (GUIDIdx != UINT32_MAX) |
640 | PrevGUIDIdx = GUIDIdx; |
641 | else |
642 | GUIDIdx = PrevGUIDIdx; |
643 | uint32_t HashIdx = YamlPD.GUIDHashIdx[GUIDIdx]; |
644 | ParentId += InlineTreeNode.ParentIndexDelta; |
645 | InlineTreeNode.GUID = YamlPD.GUID[GUIDIdx]; |
646 | InlineTreeNode.Hash = YamlPD.Hash[HashIdx]; |
647 | InlineTreeNode.ParentIndexDelta = ParentId; |
648 | } |
649 | return YamlInlineTree; |
650 | } |
651 | |
652 | size_t YAMLProfileReader::matchWithPseudoProbes(BinaryContext &BC) { |
653 | if (!opts::StaleMatchingWithPseudoProbes) |
654 | return 0; |
655 | |
656 | const MCPseudoProbeDecoder *Decoder = BC.getPseudoProbeDecoder(); |
657 | const yaml::bolt::ProfilePseudoProbeDesc &YamlPD = YamlBP.PseudoProbeDesc; |
658 | |
659 | // Set existing BF->YamlBF match into ProbeMatchSpecs for (local) probe |
660 | // matching. |
661 | assert(Decoder && |
662 | "If pseudo probes are in use, pseudo probe decoder should exist" ); |
663 | for (auto [YamlBF, BF] : llvm::zip_equal(t&: YamlBP.Functions, u&: ProfileBFs)) { |
664 | // BF is preliminary name-matched function to YamlBF |
665 | // MatchedBF is final matched function |
666 | BinaryFunction *MatchedBF = YamlProfileToFunction.lookup(Val: YamlBF.Id); |
667 | if (!BF) |
668 | BF = MatchedBF; |
669 | if (!BF) |
670 | continue; |
671 | uint64_t GUID = BF->getGUID(); |
672 | if (!GUID) |
673 | continue; |
674 | auto It = TopLevelGUIDToInlineTree.find(x: GUID); |
675 | if (It == TopLevelGUIDToInlineTree.end()) |
676 | continue; |
677 | const MCDecodedPseudoProbeInlineTree *Node = It->second; |
678 | assert(Node && "Malformed TopLevelGUIDToInlineTree" ); |
679 | auto &MatchSpecs = BFToProbeMatchSpecs[BF]; |
680 | auto &InlineTreeMap = |
681 | MatchSpecs.emplace_back(args: InlineTreeNodeMapTy(), args&: YamlBF).first; |
682 | std::vector<yaml::bolt::InlineTreeNode> ProfileInlineTree = |
683 | decodeYamlInlineTree(YamlPD, YamlInlineTree: YamlBF.InlineTree); |
684 | // Erase unsuccessful match |
685 | if (!InlineTreeMap.matchInlineTrees(Decoder: *Decoder, DecodedInlineTree: ProfileInlineTree, Root: Node)) |
686 | MatchSpecs.pop_back(); |
687 | } |
688 | |
689 | return 0; |
690 | } |
691 | |
692 | size_t YAMLProfileReader::matchWithNameSimilarity(BinaryContext &BC) { |
693 | if (opts::NameSimilarityFunctionMatchingThreshold == 0) |
694 | return 0; |
695 | |
696 | size_t MatchedWithNameSimilarity = 0; |
697 | ItaniumPartialDemangler Demangler; |
698 | |
699 | // Demangle and derive namespace from function name. |
700 | auto DemangleName = [&](std::string &FunctionName) { |
701 | StringRef RestoredName = NameResolver::restore(Name: FunctionName); |
702 | return demangle(MangledName: RestoredName); |
703 | }; |
704 | auto DeriveNameSpace = [&](std::string &DemangledName) { |
705 | if (Demangler.partialDemangle(MangledName: DemangledName.c_str())) |
706 | return std::string("" ); |
707 | std::vector<char> Buffer(DemangledName.begin(), DemangledName.end()); |
708 | size_t BufferSize; |
709 | char *NameSpace = |
710 | Demangler.getFunctionDeclContextName(Buf: &Buffer[0], N: &BufferSize); |
711 | return std::string(NameSpace, BufferSize); |
712 | }; |
713 | |
714 | // Maps namespaces to associated function block counts and gets profile |
715 | // function names and namespaces to minimize the number of BFs to process and |
716 | // avoid repeated name demangling/namespace derivation. |
717 | StringMap<std::set<uint32_t>> NamespaceToProfiledBFSizes; |
718 | std::vector<std::string> ProfileBFDemangledNames; |
719 | ProfileBFDemangledNames.reserve(n: YamlBP.Functions.size()); |
720 | std::vector<std::string> ProfiledBFNamespaces; |
721 | ProfiledBFNamespaces.reserve(n: YamlBP.Functions.size()); |
722 | |
723 | for (auto &YamlBF : YamlBP.Functions) { |
724 | std::string YamlBFDemangledName = DemangleName(YamlBF.Name); |
725 | ProfileBFDemangledNames.push_back(x: YamlBFDemangledName); |
726 | std::string YamlBFNamespace = DeriveNameSpace(YamlBFDemangledName); |
727 | ProfiledBFNamespaces.push_back(x: YamlBFNamespace); |
728 | NamespaceToProfiledBFSizes[YamlBFNamespace].insert(x: YamlBF.NumBasicBlocks); |
729 | } |
730 | |
731 | StringMap<std::vector<BinaryFunction *>> NamespaceToBFs; |
732 | |
733 | // Maps namespaces to BFs excluding binary functions with no equal sized |
734 | // profiled functions belonging to the same namespace. |
735 | for (BinaryFunction *BF : BC.getAllBinaryFunctions()) { |
736 | std::string DemangledName = BF->getDemangledName(); |
737 | std::string Namespace = DeriveNameSpace(DemangledName); |
738 | |
739 | auto NamespaceToProfiledBFSizesIt = |
740 | NamespaceToProfiledBFSizes.find(Key: Namespace); |
741 | // Skip if there are no ProfileBFs with a given \p Namespace. |
742 | if (NamespaceToProfiledBFSizesIt == NamespaceToProfiledBFSizes.end()) |
743 | continue; |
744 | // Skip if there are no ProfileBFs in a given \p Namespace with |
745 | // equal number of blocks. |
746 | if (NamespaceToProfiledBFSizesIt->second.count(x: BF->size()) == 0) |
747 | continue; |
748 | NamespaceToBFs[Namespace].push_back(x: BF); |
749 | } |
750 | |
751 | // Iterates through all profiled functions and binary functions belonging to |
752 | // the same namespace and matches based on edit distance threshold. |
753 | assert(YamlBP.Functions.size() == ProfiledBFNamespaces.size() && |
754 | ProfiledBFNamespaces.size() == ProfileBFDemangledNames.size()); |
755 | for (size_t I = 0; I < YamlBP.Functions.size(); ++I) { |
756 | yaml::bolt::BinaryFunctionProfile &YamlBF = YamlBP.Functions[I]; |
757 | std::string &YamlBFNamespace = ProfiledBFNamespaces[I]; |
758 | if (YamlBF.Used) |
759 | continue; |
760 | // Skip if there are no BFs in a given \p Namespace. |
761 | auto It = NamespaceToBFs.find(Key: YamlBFNamespace); |
762 | if (It == NamespaceToBFs.end()) |
763 | continue; |
764 | |
765 | std::string &YamlBFDemangledName = ProfileBFDemangledNames[I]; |
766 | std::vector<BinaryFunction *> BFs = It->second; |
767 | unsigned MinEditDistance = UINT_MAX; |
768 | BinaryFunction *ClosestNameBF = nullptr; |
769 | |
770 | // Determines BF the closest to the profiled function, in the |
771 | // same namespace. |
772 | for (BinaryFunction *BF : BFs) { |
773 | if (ProfiledFunctions.count(x: BF)) |
774 | continue; |
775 | if (BF->size() != YamlBF.NumBasicBlocks) |
776 | continue; |
777 | std::string BFDemangledName = BF->getDemangledName(); |
778 | unsigned BFEditDistance = |
779 | StringRef(BFDemangledName).edit_distance(Other: YamlBFDemangledName); |
780 | if (BFEditDistance < MinEditDistance) { |
781 | MinEditDistance = BFEditDistance; |
782 | ClosestNameBF = BF; |
783 | } |
784 | } |
785 | |
786 | if (ClosestNameBF && |
787 | MinEditDistance <= opts::NameSimilarityFunctionMatchingThreshold) { |
788 | matchProfileToFunction(YamlBF, BF&: *ClosestNameBF); |
789 | ++MatchedWithNameSimilarity; |
790 | } |
791 | } |
792 | |
793 | return MatchedWithNameSimilarity; |
794 | } |
795 | |
796 | Error YAMLProfileReader::readProfile(BinaryContext &BC) { |
797 | if (opts::Verbosity >= 1) { |
798 | outs() << "BOLT-INFO: YAML profile with hash: " ; |
799 | switch (YamlBP.Header.HashFunction) { |
800 | case HashFunction::StdHash: |
801 | outs() << "std::hash\n" ; |
802 | break; |
803 | case HashFunction::XXH3: |
804 | outs() << "xxh3\n" ; |
805 | break; |
806 | } |
807 | } |
808 | YamlProfileToFunction.reserve(NumEntries: YamlBP.Functions.size()); |
809 | |
810 | // Computes hash for binary functions. |
811 | if (opts::MatchProfileWithFunctionHash) { |
812 | for (auto &[_, BF] : BC.getBinaryFunctions()) { |
813 | BF.computeHash(UseDFS: YamlBP.Header.IsDFSOrder, HashFunction: YamlBP.Header.HashFunction); |
814 | } |
815 | } else if (!opts::IgnoreHash) { |
816 | for (BinaryFunction *BF : ProfileBFs) { |
817 | if (!BF) |
818 | continue; |
819 | BF->computeHash(UseDFS: YamlBP.Header.IsDFSOrder, HashFunction: YamlBP.Header.HashFunction); |
820 | } |
821 | } |
822 | |
823 | if (opts::StaleMatchingWithPseudoProbes) { |
824 | const MCPseudoProbeDecoder *Decoder = BC.getPseudoProbeDecoder(); |
825 | assert(Decoder && |
826 | "If pseudo probes are in use, pseudo probe decoder should exist" ); |
827 | for (const MCDecodedPseudoProbeInlineTree &TopLev : |
828 | Decoder->getDummyInlineRoot().getChildren()) |
829 | TopLevelGUIDToInlineTree[TopLev.Guid] = &TopLev; |
830 | } |
831 | |
832 | // Map profiled function ids to names. |
833 | for (yaml::bolt::BinaryFunctionProfile &YamlBF : YamlBP.Functions) |
834 | IdToYamLBF[YamlBF.Id] = &YamlBF; |
835 | |
836 | const size_t MatchedWithExactName = matchWithExactName(); |
837 | const size_t MatchedWithHash = matchWithHash(BC); |
838 | const size_t MatchedWithLTOCommonName = matchWithLTOCommonName(); |
839 | const size_t MatchedWithCallGraph = matchWithCallGraph(BC); |
840 | const size_t MatchedWithNameSimilarity = matchWithNameSimilarity(BC); |
841 | [[maybe_unused]] const size_t MatchedWithPseudoProbes = |
842 | matchWithPseudoProbes(BC); |
843 | |
844 | for (auto [YamlBF, BF] : llvm::zip_equal(t&: YamlBP.Functions, u&: ProfileBFs)) |
845 | if (!YamlBF.Used && BF && !ProfiledFunctions.count(x: BF)) |
846 | matchProfileToFunction(YamlBF, BF&: *BF); |
847 | |
848 | |
849 | for (yaml::bolt::BinaryFunctionProfile &YamlBF : YamlBP.Functions) |
850 | if (!YamlBF.Used && opts::Verbosity >= 1) |
851 | errs() << "BOLT-WARNING: profile ignored for function " << YamlBF.Name |
852 | << '\n'; |
853 | |
854 | if (opts::Verbosity >= 1) { |
855 | outs() << "BOLT-INFO: matched " << MatchedWithExactName |
856 | << " functions with identical names\n" ; |
857 | outs() << "BOLT-INFO: matched " << MatchedWithHash |
858 | << " functions with hash\n" ; |
859 | outs() << "BOLT-INFO: matched " << MatchedWithLTOCommonName |
860 | << " functions with matching LTO common names\n" ; |
861 | outs() << "BOLT-INFO: matched " << MatchedWithCallGraph |
862 | << " functions with call graph\n" ; |
863 | outs() << "BOLT-INFO: matched " << MatchedWithNameSimilarity |
864 | << " functions with similar names\n" ; |
865 | } |
866 | |
867 | // Set for parseFunctionProfile(). |
868 | NormalizeByInsnCount = usesEvent(Name: "cycles" ) || usesEvent(Name: "instructions" ); |
869 | NormalizeByCalls = usesEvent(Name: "branches" ); |
870 | uint64_t NumUnused = 0; |
871 | for (yaml::bolt::BinaryFunctionProfile &YamlBF : YamlBP.Functions) { |
872 | if (BinaryFunction *BF = YamlProfileToFunction.lookup(Val: YamlBF.Id)) |
873 | parseFunctionProfile(BF&: *BF, YamlBF); |
874 | else |
875 | ++NumUnused; |
876 | } |
877 | |
878 | BC.setNumUnusedProfiledObjects(NumUnused); |
879 | |
880 | if (opts::Lite && |
881 | (opts::MatchProfileWithFunctionHash || opts::MatchWithCallGraph)) { |
882 | for (BinaryFunction *BF : BC.getAllBinaryFunctions()) |
883 | if (!BF->hasProfile()) |
884 | BF->setIgnored(); |
885 | } |
886 | |
887 | return Error::success(); |
888 | } |
889 | |
890 | bool YAMLProfileReader::usesEvent(StringRef Name) const { |
891 | return StringRef(YamlBP.Header.EventNames).contains(Other: Name); |
892 | } |
893 | |
894 | } // end namespace bolt |
895 | } // end namespace llvm |
896 | |