1 | //===-- ImportedFunctionsInliningStats.cpp ----------------------*- 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 | // Generating inliner statistics for imported functions, mostly useful for |
9 | // ThinLTO. |
10 | //===----------------------------------------------------------------------===// |
11 | |
12 | #include "llvm/Analysis/Utils/ImportedFunctionsInliningStatistics.h" |
13 | #include "llvm/ADT/STLExtras.h" |
14 | #include "llvm/IR/Function.h" |
15 | #include "llvm/IR/Module.h" |
16 | #include "llvm/Support/CommandLine.h" |
17 | #include "llvm/Support/Debug.h" |
18 | #include "llvm/Support/raw_ostream.h" |
19 | #include <iomanip> |
20 | #include <sstream> |
21 | #include <string> |
22 | |
23 | using namespace llvm; |
24 | |
25 | namespace llvm { |
26 | cl::opt<InlinerFunctionImportStatsOpts> InlinerFunctionImportStats( |
27 | "inliner-function-import-stats" , |
28 | cl::init(Val: InlinerFunctionImportStatsOpts::No), |
29 | cl::values(clEnumValN(InlinerFunctionImportStatsOpts::Basic, "basic" , |
30 | "basic statistics" ), |
31 | clEnumValN(InlinerFunctionImportStatsOpts::Verbose, "verbose" , |
32 | "printing of statistics for each inlined function" )), |
33 | cl::Hidden, cl::desc("Enable inliner stats for imported functions" )); |
34 | } // namespace llvm |
35 | |
36 | ImportedFunctionsInliningStatistics::InlineGraphNode & |
37 | ImportedFunctionsInliningStatistics::createInlineGraphNode(const Function &F) { |
38 | |
39 | auto &ValueLookup = NodesMap[F.getName()]; |
40 | if (!ValueLookup) { |
41 | ValueLookup = std::make_unique<InlineGraphNode>(); |
42 | ValueLookup->Imported = F.hasMetadata(Kind: "thinlto_src_module" ); |
43 | } |
44 | return *ValueLookup; |
45 | } |
46 | |
47 | void ImportedFunctionsInliningStatistics::recordInline(const Function &Caller, |
48 | const Function &Callee) { |
49 | |
50 | InlineGraphNode &CallerNode = createInlineGraphNode(F: Caller); |
51 | InlineGraphNode &CalleeNode = createInlineGraphNode(F: Callee); |
52 | CalleeNode.NumberOfInlines++; |
53 | |
54 | if (!CallerNode.Imported && !CalleeNode.Imported) { |
55 | // Direct inline from not imported callee to not imported caller, so we |
56 | // don't have to add this to graph. It might be very helpful if you wanna |
57 | // get the inliner statistics in compile step where there are no imported |
58 | // functions. In this case the graph would be empty. |
59 | CalleeNode.NumberOfRealInlines++; |
60 | return; |
61 | } |
62 | |
63 | CallerNode.InlinedCallees.push_back(Elt: &CalleeNode); |
64 | if (!CallerNode.Imported) { |
65 | // We could avoid second lookup, but it would make the code ultra ugly. |
66 | auto It = NodesMap.find(Key: Caller.getName()); |
67 | assert(It != NodesMap.end() && "The node should be already there." ); |
68 | // Save Caller as a starting node for traversal. The string has to be one |
69 | // from map because Caller can disappear (and function name with it). |
70 | NonImportedCallers.push_back(x: It->first()); |
71 | } |
72 | } |
73 | |
74 | void ImportedFunctionsInliningStatistics::setModuleInfo(const Module &M) { |
75 | ModuleName = M.getName(); |
76 | for (const auto &F : M.functions()) { |
77 | if (F.isDeclaration()) |
78 | continue; |
79 | AllFunctions++; |
80 | ImportedFunctions += int(F.hasMetadata(Kind: "thinlto_src_module" )); |
81 | } |
82 | } |
83 | static std::string getStatString(const char *Msg, int32_t Fraction, int32_t All, |
84 | const char *PercentageOfMsg, |
85 | bool LineEnd = true) { |
86 | double Result = 0; |
87 | if (All != 0) |
88 | Result = 100 * static_cast<double>(Fraction) / All; |
89 | |
90 | std::stringstream Str; |
91 | Str << std::setprecision(4) << Msg << ": " << Fraction << " [" << Result |
92 | << "% of " << PercentageOfMsg << "]" ; |
93 | if (LineEnd) |
94 | Str << "\n" ; |
95 | return Str.str(); |
96 | } |
97 | |
98 | void ImportedFunctionsInliningStatistics::dump(const bool Verbose) { |
99 | calculateRealInlines(); |
100 | NonImportedCallers.clear(); |
101 | |
102 | int32_t InlinedImportedFunctionsCount = 0; |
103 | int32_t InlinedNotImportedFunctionsCount = 0; |
104 | |
105 | int32_t InlinedImportedFunctionsToImportingModuleCount = 0; |
106 | int32_t InlinedNotImportedFunctionsToImportingModuleCount = 0; |
107 | |
108 | const auto SortedNodes = getSortedNodes(); |
109 | std::string Out; |
110 | Out.reserve(res_arg: 5000); |
111 | raw_string_ostream Ostream(Out); |
112 | |
113 | Ostream << "------- Dumping inliner stats for [" << ModuleName |
114 | << "] -------\n" ; |
115 | |
116 | if (Verbose) |
117 | Ostream << "-- List of inlined functions:\n" ; |
118 | |
119 | for (const auto &Node : SortedNodes) { |
120 | assert(Node->second->NumberOfInlines >= Node->second->NumberOfRealInlines); |
121 | if (Node->second->NumberOfInlines == 0) |
122 | continue; |
123 | |
124 | if (Node->second->Imported) { |
125 | InlinedImportedFunctionsCount++; |
126 | InlinedImportedFunctionsToImportingModuleCount += |
127 | int(Node->second->NumberOfRealInlines > 0); |
128 | } else { |
129 | InlinedNotImportedFunctionsCount++; |
130 | InlinedNotImportedFunctionsToImportingModuleCount += |
131 | int(Node->second->NumberOfRealInlines > 0); |
132 | } |
133 | |
134 | if (Verbose) |
135 | Ostream << "Inlined " |
136 | << (Node->second->Imported ? "imported " : "not imported " ) |
137 | << "function [" << Node->first() << "]" |
138 | << ": #inlines = " << Node->second->NumberOfInlines |
139 | << ", #inlines_to_importing_module = " |
140 | << Node->second->NumberOfRealInlines << "\n" ; |
141 | } |
142 | |
143 | auto InlinedFunctionsCount = |
144 | InlinedImportedFunctionsCount + InlinedNotImportedFunctionsCount; |
145 | auto NotImportedFuncCount = AllFunctions - ImportedFunctions; |
146 | auto ImportedNotInlinedIntoModule = |
147 | ImportedFunctions - InlinedImportedFunctionsToImportingModuleCount; |
148 | |
149 | Ostream << "-- Summary:\n" |
150 | << "All functions: " << AllFunctions |
151 | << ", imported functions: " << ImportedFunctions << "\n" |
152 | << getStatString(Msg: "inlined functions" , Fraction: InlinedFunctionsCount, |
153 | All: AllFunctions, PercentageOfMsg: "all functions" ) |
154 | << getStatString(Msg: "imported functions inlined anywhere" , |
155 | Fraction: InlinedImportedFunctionsCount, All: ImportedFunctions, |
156 | PercentageOfMsg: "imported functions" ) |
157 | << getStatString(Msg: "imported functions inlined into importing module" , |
158 | Fraction: InlinedImportedFunctionsToImportingModuleCount, |
159 | All: ImportedFunctions, PercentageOfMsg: "imported functions" , |
160 | /*LineEnd=*/false) |
161 | << getStatString(Msg: ", remaining" , Fraction: ImportedNotInlinedIntoModule, |
162 | All: ImportedFunctions, PercentageOfMsg: "imported functions" ) |
163 | << getStatString(Msg: "non-imported functions inlined anywhere" , |
164 | Fraction: InlinedNotImportedFunctionsCount, |
165 | All: NotImportedFuncCount, PercentageOfMsg: "non-imported functions" ) |
166 | << getStatString( |
167 | Msg: "non-imported functions inlined into importing module" , |
168 | Fraction: InlinedNotImportedFunctionsToImportingModuleCount, |
169 | All: NotImportedFuncCount, PercentageOfMsg: "non-imported functions" ); |
170 | Ostream.flush(); |
171 | dbgs() << Out; |
172 | } |
173 | |
174 | void ImportedFunctionsInliningStatistics::calculateRealInlines() { |
175 | // Removing duplicated Callers. |
176 | llvm::sort(C&: NonImportedCallers); |
177 | NonImportedCallers.erase(first: llvm::unique(R&: NonImportedCallers), |
178 | last: NonImportedCallers.end()); |
179 | |
180 | for (const auto &Name : NonImportedCallers) { |
181 | auto &Node = *NodesMap[Name]; |
182 | if (!Node.Visited) |
183 | dfs(GraphNode&: Node); |
184 | } |
185 | } |
186 | |
187 | void ImportedFunctionsInliningStatistics::dfs(InlineGraphNode &GraphNode) { |
188 | assert(!GraphNode.Visited); |
189 | GraphNode.Visited = true; |
190 | for (auto *const InlinedFunctionNode : GraphNode.InlinedCallees) { |
191 | InlinedFunctionNode->NumberOfRealInlines++; |
192 | if (!InlinedFunctionNode->Visited) |
193 | dfs(GraphNode&: *InlinedFunctionNode); |
194 | } |
195 | } |
196 | |
197 | ImportedFunctionsInliningStatistics::SortedNodesTy |
198 | ImportedFunctionsInliningStatistics::getSortedNodes() { |
199 | SortedNodesTy SortedNodes; |
200 | SortedNodes.reserve(n: NodesMap.size()); |
201 | for (const NodesMapTy::value_type &Node : NodesMap) |
202 | SortedNodes.push_back(x: &Node); |
203 | |
204 | llvm::sort(C&: SortedNodes, Comp: [&](const SortedNodesTy::value_type &Lhs, |
205 | const SortedNodesTy::value_type &Rhs) { |
206 | if (Lhs->second->NumberOfInlines != Rhs->second->NumberOfInlines) |
207 | return Lhs->second->NumberOfInlines > Rhs->second->NumberOfInlines; |
208 | if (Lhs->second->NumberOfRealInlines != Rhs->second->NumberOfRealInlines) |
209 | return Lhs->second->NumberOfRealInlines > |
210 | Rhs->second->NumberOfRealInlines; |
211 | return Lhs->first() < Rhs->first(); |
212 | }); |
213 | return SortedNodes; |
214 | } |
215 | |