1 //===-- MissingFrameInferrer.cpp - Missing frame inferrer --------- C++ -*-===//
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 "MissingFrameInferrer.h"
10 #include "PerfReader.h"
11 #include "ProfiledBinary.h"
12 #include "llvm/ADT/SCCIterator.h"
13 #include "llvm/ADT/Statistic.h"
14 #include <algorithm>
15 #include <cstdint>
16 #include <iterator>
17 #include <queue>
18 #include <sys/types.h>
19 
20 #define DEBUG_TYPE "missing-frame-inferrer"
21 
22 using namespace llvm;
23 using namespace sampleprof;
24 
25 STATISTIC(TailCallUniReachable,
26           "Number of frame pairs reachable via a unique tail call path");
27 STATISTIC(TailCallMultiReachable,
28           "Number of frame pairs reachable via a multiple tail call paths");
29 STATISTIC(TailCallUnreachable,
30           "Number of frame pairs unreachable via any tail call path");
31 STATISTIC(TailCallFuncSingleTailCalls,
32           "Number of functions with single tail call site");
33 STATISTIC(TailCallFuncMultipleTailCalls,
34           "Number of functions with multiple tail call sites");
35 STATISTIC(TailCallMaxTailCallPath, "Length of the longest tail call path");
36 
37 static cl::opt<uint32_t>
38     MaximumSearchDepth("max-search-depth", cl::init(UINT32_MAX - 1),
39                        cl::desc("The maximum levels the DFS-based missing "
40                                 "frame search should go with"));
41 
initialize(const ContextSampleCounterMap * SampleCounters)42 void MissingFrameInferrer::initialize(
43     const ContextSampleCounterMap *SampleCounters) {
44   // Refine call edges based on LBR samples.
45   if (SampleCounters) {
46     std::unordered_map<uint64_t, std::unordered_set<uint64_t>> SampledCalls;
47     std::unordered_map<uint64_t, std::unordered_set<uint64_t>> SampledTailCalls;
48 
49     // Populate SampledCalls based on static call sites. Similarly to
50     // SampledTailCalls.
51     for (const auto &CI : *SampleCounters) {
52       for (auto Item : CI.second.BranchCounter) {
53         auto From = Item.first.first;
54         auto To = Item.first.second;
55         if (CallEdges.count(From)) {
56           assert(CallEdges[From].size() == 1 &&
57                  "A callsite should only appear once with either a known or a "
58                  "zero (unknown) target value at this point");
59           SampledCalls[From].insert(To);
60         }
61         if (TailCallEdges.count(From)) {
62           assert(TailCallEdges[From].size() == 1 &&
63                  "A callsite should only appear once with either a known or a "
64                  "zero (unknown) target value at this point");
65           FuncRange *FromFRange = Binary->findFuncRange(From);
66           FuncRange *ToFRange = Binary->findFuncRange(To);
67           if (FromFRange != ToFRange)
68             SampledTailCalls[From].insert(To);
69         }
70       }
71     }
72 
73     // Replace static edges with dynamic edges.
74     CallEdges = SampledCalls;
75     TailCallEdges = SampledTailCalls;
76   }
77 
78   // Populate function-based edges. This is to speed up address to function
79   // translation.
80   for (auto Call : CallEdges)
81     for (auto Target : Call.second)
82       if (FuncRange *ToFRange = Binary->findFuncRange(Target))
83         CallEdgesF[Call.first].insert(ToFRange->Func);
84 
85   for (auto Call : TailCallEdges) {
86     for (auto Target : Call.second) {
87       if (FuncRange *ToFRange = Binary->findFuncRange(Target)) {
88         TailCallEdgesF[Call.first].insert(ToFRange->Func);
89         TailCallTargetFuncs.insert(ToFRange->Func);
90       }
91     }
92     if (FuncRange *FromFRange = Binary->findFuncRange(Call.first))
93       FuncToTailCallMap[FromFRange->Func].push_back(Call.first);
94   }
95 
96 #if LLVM_ENABLE_STATS
97   for (auto F : FuncToTailCallMap) {
98     assert(F.second.size() > 0 && "");
99     if (F.second.size() > 1)
100       TailCallFuncMultipleTailCalls++;
101     else
102       TailCallFuncSingleTailCalls++;
103   }
104 #endif
105 
106 #ifndef NDEBUG
107   auto PrintCallTargets =
108       [&](const std::unordered_map<uint64_t, std::unordered_set<uint64_t>>
109               &CallTargets,
110           bool IsTailCall) {
111         for (const auto &Targets : CallTargets) {
112           for (const auto &Target : Targets.second) {
113             dbgs() << (IsTailCall ? "TailCall" : "Call");
114             dbgs() << " From " << format("%8" PRIx64, Targets.first) << " to "
115                    << format("%8" PRIx64, Target) << "\n";
116           }
117         }
118       };
119 
120   LLVM_DEBUG(dbgs() << "============================\n ";
121              dbgs() << "Call targets:\n";
122              PrintCallTargets(CallEdges, false);
123              dbgs() << "\nTail call targets:\n";
124              PrintCallTargets(CallEdges, true);
125              dbgs() << "============================\n";);
126 #endif
127 }
128 
computeUniqueTailCallPath(BinaryFunction * From,BinaryFunction * To,SmallVectorImpl<uint64_t> & Path)129 uint64_t MissingFrameInferrer::computeUniqueTailCallPath(
130     BinaryFunction *From, BinaryFunction *To, SmallVectorImpl<uint64_t> &Path) {
131   // Search for a unique path comprised of only tail call edges for a given
132   // source and target frame address on the a tail call graph that consists of
133   // only tail call edges. Note that only a unique path counts. Multiple paths
134   // are treated unreachable.
135   if (From == To)
136     return 1;
137 
138   // Ignore cyclic paths. Since we are doing a recursive DFS walk, if the source
139   // frame being visited is already in the stack, it means we are seeing a
140   // cycle. This is done before querying the cached result because the cached
141   // result may be computed based on the same path. Consider the following case:
142   //     A -> B, B -> A, A -> D
143   // When computing unique reachablity from A to D, the cached result for (B,D)
144   // should not be counted since the unique path B->A->D is basically the same
145   // path as A->D. Counting that with invalidate the uniqueness from A to D.
146   if (Visiting.contains(From))
147     return 0;
148 
149   // If already computed, return the cached result.
150   auto I = UniquePaths.find({From, To});
151   if (I != UniquePaths.end()) {
152     Path.append(I->second.begin(), I->second.end());
153     return 1;
154   }
155 
156   auto J = NonUniquePaths.find({From, To});
157   if (J != NonUniquePaths.end()) {
158     return J->second;
159   }
160 
161   uint64_t Pos = Path.size();
162 
163   // DFS walk each outgoing tail call edges.
164   // Bail out if we are already at the the maximum searching depth.
165   if (CurSearchingDepth == MaximumSearchDepth)
166     return 0;
167 
168 
169   if (!FuncToTailCallMap.count(From))
170     return 0;
171 
172   CurSearchingDepth++;
173   Visiting.insert(From);
174   uint64_t NumPaths = 0;
175   for (auto TailCall : FuncToTailCallMap[From]) {
176     NumPaths += computeUniqueTailCallPath(TailCall, To, Path);
177     // Stop analyzing the remaining if we are already seeing more than one
178     // reachable paths.
179     if (NumPaths > 1)
180       break;
181   }
182   CurSearchingDepth--;
183   Visiting.erase(From);
184 
185   // Undo already-computed path if it is not unique.
186   if (NumPaths != 1) {
187     Path.pop_back_n(Path.size() - Pos);
188   }
189 
190   // Cache the result.
191   if (NumPaths == 1) {
192     UniquePaths[{From, To}].assign(Path.begin() + Pos, Path.end());
193 #if LLVM_ENABLE_STATS
194     auto &LocalPath = UniquePaths[{From, To}];
195     assert((LocalPath.size() <= MaximumSearchDepth + 1) &&
196            "Path should not be longer than the maximum searching depth");
197     TailCallMaxTailCallPath = std::max(uint64_t(LocalPath.size()),
198                                        TailCallMaxTailCallPath.getValue());
199 #endif
200   } else {
201     NonUniquePaths[{From, To}] = NumPaths;
202   }
203 
204   return NumPaths;
205 }
206 
computeUniqueTailCallPath(uint64_t From,BinaryFunction * To,SmallVectorImpl<uint64_t> & Path)207 uint64_t MissingFrameInferrer::computeUniqueTailCallPath(
208     uint64_t From, BinaryFunction *To, SmallVectorImpl<uint64_t> &Path) {
209   if (!TailCallEdgesF.count(From))
210     return 0;
211   Path.push_back(From);
212   uint64_t NumPaths = 0;
213   for (auto Target : TailCallEdgesF[From]) {
214     NumPaths += computeUniqueTailCallPath(Target, To, Path);
215     // Stop analyzing the remaining if we are already seeing more than one
216     // reachable paths.
217     if (NumPaths > 1)
218       break;
219   }
220 
221   // Undo already-computed path if it is not unique.
222   if (NumPaths != 1)
223     Path.pop_back();
224   return NumPaths;
225 }
226 
inferMissingFrames(uint64_t From,uint64_t To,SmallVectorImpl<uint64_t> & UniquePath)227 bool MissingFrameInferrer::inferMissingFrames(
228     uint64_t From, uint64_t To, SmallVectorImpl<uint64_t> &UniquePath) {
229   assert(!TailCallEdgesF.count(From) &&
230          "transition between From and To cannot be via a tailcall otherwise "
231          "they would not show up at the same time");
232   UniquePath.push_back(From);
233   uint64_t Pos = UniquePath.size();
234 
235   FuncRange *ToFRange = Binary->findFuncRange(To);
236   if (!ToFRange)
237     return false;
238 
239   // Bail out if caller has no known outgoing call edges.
240   if (!CallEdgesF.count(From))
241     return false;
242 
243   // Done with the inference if the calle is reachable via a single callsite.
244   // This may not be accurate but it improves the search throughput.
245   for (auto Target : CallEdgesF[From]) {
246     if (Target == ToFRange->Func)
247       return true;
248   }
249 
250   // Bail out if callee is not tailcall reachable at all.
251   if (!TailCallTargetFuncs.contains(ToFRange->Func))
252     return false;
253 
254   Visiting.clear();
255   CurSearchingDepth = 0;
256   uint64_t NumPaths = 0;
257   for (auto Target : CallEdgesF[From]) {
258     NumPaths +=
259         computeUniqueTailCallPath(Target, ToFRange->Func, UniquePath);
260     // Stop analyzing the remaining if we are already seeing more than one
261     // reachable paths.
262     if (NumPaths > 1)
263       break;
264   }
265 
266   // Undo already-computed path if it is not unique.
267   if (NumPaths != 1) {
268     UniquePath.pop_back_n(UniquePath.size() - Pos);
269     assert(UniquePath.back() == From && "broken path");
270   }
271 
272 #if LLVM_ENABLE_STATS
273   if (NumPaths == 1) {
274     if (ReachableViaUniquePaths.insert({From, ToFRange->StartAddress}).second)
275       TailCallUniReachable++;
276   } else if (NumPaths == 0) {
277     if (Unreachables.insert({From, ToFRange->StartAddress}).second) {
278       TailCallUnreachable++;
279       LLVM_DEBUG(dbgs() << "No path found from "
280                         << format("%8" PRIx64 ":", From) << " to "
281                         << format("%8" PRIx64 ":", ToFRange->StartAddress)
282                         << "\n");
283     }
284   } else if (NumPaths > 1) {
285     if (ReachableViaMultiPaths.insert({From, ToFRange->StartAddress})
286             .second) {
287       TailCallMultiReachable++;
288       LLVM_DEBUG(dbgs() << "Multiple paths found from "
289                         << format("%8" PRIx64 ":", From) << " to "
290                         << format("%8" PRIx64 ":", ToFRange->StartAddress)
291                         << "\n");
292     }
293   }
294 #endif
295 
296   return NumPaths == 1;
297 }
298 
inferMissingFrames(const SmallVectorImpl<uint64_t> & Context,SmallVectorImpl<uint64_t> & NewContext)299 void MissingFrameInferrer::inferMissingFrames(
300     const SmallVectorImpl<uint64_t> &Context,
301     SmallVectorImpl<uint64_t> &NewContext) {
302   if (Context.size() == 1) {
303     NewContext = Context;
304     return;
305   }
306 
307   NewContext.clear();
308   for (uint64_t I = 1; I < Context.size(); I++) {
309     inferMissingFrames(Context[I - 1], Context[I], NewContext);
310   }
311   NewContext.push_back(Context.back());
312 
313   assert((NewContext.size() >= Context.size()) &&
314          "Inferred context should include all frames in the original context");
315   assert((NewContext.size() > Context.size() || NewContext == Context) &&
316          "Inferred context should be exactly the same "
317          "with the original context");
318 }
319