MissingFrameInferrer.cpp 11 KB

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