1 | //===- MLInlineAdvisor.cpp - machine learned InlineAdvisor ----------------===// |
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 | // This file implements the interface between the inliner and a learned model. |
10 | // It delegates model evaluation to either the AOT compiled model (the |
11 | // 'release' mode) or a runtime-loaded model (the 'development' case). |
12 | // |
13 | //===----------------------------------------------------------------------===// |
14 | #include "llvm/Analysis/MLInlineAdvisor.h" |
15 | #include "llvm/ADT/SCCIterator.h" |
16 | #include "llvm/Analysis/AssumptionCache.h" |
17 | #include "llvm/Analysis/BlockFrequencyInfo.h" |
18 | #include "llvm/Analysis/CallGraph.h" |
19 | #include "llvm/Analysis/FunctionPropertiesAnalysis.h" |
20 | #include "llvm/Analysis/InlineCost.h" |
21 | #include "llvm/Analysis/InlineModelFeatureMaps.h" |
22 | #include "llvm/Analysis/InteractiveModelRunner.h" |
23 | #include "llvm/Analysis/LazyCallGraph.h" |
24 | #include "llvm/Analysis/LoopInfo.h" |
25 | #include "llvm/Analysis/MLModelRunner.h" |
26 | #include "llvm/Analysis/OptimizationRemarkEmitter.h" |
27 | #include "llvm/Analysis/ProfileSummaryInfo.h" |
28 | #include "llvm/Analysis/ReleaseModeModelRunner.h" |
29 | #include "llvm/Analysis/TargetTransformInfo.h" |
30 | #include "llvm/IR/Dominators.h" |
31 | #include "llvm/IR/InstIterator.h" |
32 | #include "llvm/IR/Module.h" |
33 | #include "llvm/IR/PassManager.h" |
34 | #include "llvm/Support/CommandLine.h" |
35 | |
36 | using namespace llvm; |
37 | |
38 | static cl::opt<std::string> InteractiveChannelBaseName( |
39 | "inliner-interactive-channel-base" , cl::Hidden, |
40 | cl::desc( |
41 | "Base file path for the interactive mode. The incoming filename should " |
42 | "have the name <inliner-interactive-channel-base>.in, while the " |
43 | "outgoing name should be <inliner-interactive-channel-base>.out" )); |
44 | static const std::string InclDefaultMsg = |
45 | (Twine("In interactive mode, also send the default policy decision: " ) + |
46 | DefaultDecisionName + "." ) |
47 | .str(); |
48 | static cl::opt<bool> |
49 | InteractiveIncludeDefault("inliner-interactive-include-default" , cl::Hidden, |
50 | cl::desc(InclDefaultMsg)); |
51 | |
52 | enum class SkipMLPolicyCriteria { Never, IfCallerIsNotCold }; |
53 | |
54 | static cl::opt<SkipMLPolicyCriteria> SkipPolicy( |
55 | "ml-inliner-skip-policy" , cl::Hidden, cl::init(Val: SkipMLPolicyCriteria::Never), |
56 | cl::values(clEnumValN(SkipMLPolicyCriteria::Never, "never" , "never" ), |
57 | clEnumValN(SkipMLPolicyCriteria::IfCallerIsNotCold, |
58 | "if-caller-not-cold" , "if the caller is not cold" ))); |
59 | |
60 | static cl::opt<std::string> ModelSelector("ml-inliner-model-selector" , |
61 | cl::Hidden, cl::init(Val: "" )); |
62 | |
63 | #if defined(LLVM_HAVE_TF_AOT_INLINERSIZEMODEL) |
64 | // codegen-ed file |
65 | #include "InlinerSizeModel.h" // NOLINT |
66 | using CompiledModelType = llvm::InlinerSizeModel; |
67 | #else |
68 | using CompiledModelType = NoopSavedModelImpl; |
69 | #endif |
70 | |
71 | std::unique_ptr<InlineAdvisor> |
72 | llvm::getReleaseModeAdvisor(Module &M, ModuleAnalysisManager &MAM, |
73 | std::function<bool(CallBase &)> GetDefaultAdvice) { |
74 | if (!llvm::isEmbeddedModelEvaluatorValid<CompiledModelType>() && |
75 | InteractiveChannelBaseName.empty()) |
76 | return nullptr; |
77 | std::unique_ptr<MLModelRunner> AOTRunner; |
78 | if (InteractiveChannelBaseName.empty()) |
79 | AOTRunner = std::make_unique<ReleaseModeModelRunner<CompiledModelType>>( |
80 | args&: M.getContext(), args: FeatureMap, args: DecisionName, |
81 | args&: EmbeddedModelRunnerOptions().setModelSelector(ModelSelector)); |
82 | else { |
83 | auto Features = FeatureMap; |
84 | if (InteractiveIncludeDefault) |
85 | Features.push_back(x: DefaultDecisionSpec); |
86 | AOTRunner = std::make_unique<InteractiveModelRunner>( |
87 | args&: M.getContext(), args&: Features, args: InlineDecisionSpec, |
88 | args: InteractiveChannelBaseName + ".out" , |
89 | args: InteractiveChannelBaseName + ".in" ); |
90 | } |
91 | return std::make_unique<MLInlineAdvisor>(args&: M, args&: MAM, args: std::move(AOTRunner), |
92 | args&: GetDefaultAdvice); |
93 | } |
94 | |
95 | #define DEBUG_TYPE "inline-ml" |
96 | |
97 | static cl::opt<float> SizeIncreaseThreshold( |
98 | "ml-advisor-size-increase-threshold" , cl::Hidden, |
99 | cl::desc("Maximum factor by which expected native size may increase before " |
100 | "blocking any further inlining." ), |
101 | cl::init(Val: 2.0)); |
102 | |
103 | static cl::opt<bool> KeepFPICache( |
104 | "ml-advisor-keep-fpi-cache" , cl::Hidden, |
105 | cl::desc( |
106 | "For test - keep the ML Inline advisor's FunctionPropertiesInfo cache" ), |
107 | cl::init(Val: false)); |
108 | |
109 | // clang-format off |
110 | const std::vector<TensorSpec> llvm::FeatureMap{ |
111 | #define POPULATE_NAMES(DTYPE, SHAPE, NAME, __) TensorSpec::createSpec<DTYPE>(#NAME, SHAPE), |
112 | // InlineCost features - these must come first |
113 | INLINE_COST_FEATURE_ITERATOR(POPULATE_NAMES) |
114 | |
115 | // Non-cost features |
116 | INLINE_FEATURE_ITERATOR(POPULATE_NAMES) |
117 | #undef POPULATE_NAMES |
118 | }; |
119 | // clang-format on |
120 | |
121 | const char *const llvm::DecisionName = "inlining_decision" ; |
122 | const TensorSpec llvm::InlineDecisionSpec = |
123 | TensorSpec::createSpec<int64_t>(Name: DecisionName, Shape: {1}); |
124 | const char *const llvm::DefaultDecisionName = "inlining_default" ; |
125 | const TensorSpec llvm::DefaultDecisionSpec = |
126 | TensorSpec::createSpec<int64_t>(Name: DefaultDecisionName, Shape: {1}); |
127 | const char *const llvm::RewardName = "delta_size" ; |
128 | |
129 | CallBase *getInlinableCS(Instruction &I) { |
130 | if (auto *CS = dyn_cast<CallBase>(Val: &I)) |
131 | if (Function *Callee = CS->getCalledFunction()) { |
132 | if (!Callee->isDeclaration()) { |
133 | return CS; |
134 | } |
135 | } |
136 | return nullptr; |
137 | } |
138 | |
139 | MLInlineAdvisor::MLInlineAdvisor( |
140 | Module &M, ModuleAnalysisManager &MAM, |
141 | std::unique_ptr<MLModelRunner> Runner, |
142 | std::function<bool(CallBase &)> GetDefaultAdvice) |
143 | : InlineAdvisor( |
144 | M, MAM.getResult<FunctionAnalysisManagerModuleProxy>(IR&: M).getManager()), |
145 | ModelRunner(std::move(Runner)), GetDefaultAdvice(GetDefaultAdvice), |
146 | CG(MAM.getResult<LazyCallGraphAnalysis>(IR&: M)), |
147 | InitialIRSize(getModuleIRSize()), CurrentIRSize(InitialIRSize), |
148 | PSI(MAM.getResult<ProfileSummaryAnalysis>(IR&: M)) { |
149 | assert(ModelRunner); |
150 | ModelRunner->switchContext(Name: "" ); |
151 | // Extract the 'call site height' feature - the position of a call site |
152 | // relative to the farthest statically reachable SCC node. We don't mutate |
153 | // this value while inlining happens. Empirically, this feature proved |
154 | // critical in behavioral cloning - i.e. training a model to mimic the manual |
155 | // heuristic's decisions - and, thus, equally important for training for |
156 | // improvement. |
157 | CallGraph CGraph(M); |
158 | for (auto I = scc_begin(G: &CGraph); !I.isAtEnd(); ++I) { |
159 | const std::vector<CallGraphNode *> &CGNodes = *I; |
160 | unsigned Level = 0; |
161 | for (auto *CGNode : CGNodes) { |
162 | Function *F = CGNode->getFunction(); |
163 | if (!F || F->isDeclaration()) |
164 | continue; |
165 | for (auto &I : instructions(F)) { |
166 | if (auto *CS = getInlinableCS(I)) { |
167 | auto *Called = CS->getCalledFunction(); |
168 | auto Pos = FunctionLevels.find(x: &CG.get(F&: *Called)); |
169 | // In bottom up traversal, an inlinable callee is either in the |
170 | // same SCC, or to a function in a visited SCC. So not finding its |
171 | // level means we haven't visited it yet, meaning it's in this SCC. |
172 | if (Pos == FunctionLevels.end()) |
173 | continue; |
174 | Level = std::max(a: Level, b: Pos->second + 1); |
175 | } |
176 | } |
177 | } |
178 | for (auto *CGNode : CGNodes) { |
179 | Function *F = CGNode->getFunction(); |
180 | if (F && !F->isDeclaration()) |
181 | FunctionLevels[&CG.get(F&: *F)] = Level; |
182 | } |
183 | } |
184 | for (auto KVP : FunctionLevels) { |
185 | AllNodes.insert(V: KVP.first); |
186 | EdgeCount += getLocalCalls(F&: KVP.first->getFunction()); |
187 | } |
188 | NodeCount = AllNodes.size(); |
189 | } |
190 | |
191 | unsigned MLInlineAdvisor::getInitialFunctionLevel(const Function &F) const { |
192 | return CG.lookup(F) ? FunctionLevels.at(k: CG.lookup(F)) : 0; |
193 | } |
194 | |
195 | void MLInlineAdvisor::onPassEntry(LazyCallGraph::SCC *CurSCC) { |
196 | if (!CurSCC || ForceStop) |
197 | return; |
198 | FPICache.clear(); |
199 | // Function passes executed between InlinerPass runs may have changed the |
200 | // module-wide features. |
201 | // The cgscc pass manager rules are such that: |
202 | // - if a pass leads to merging SCCs, then the pipeline is restarted on the |
203 | // merged SCC |
204 | // - if a pass leads to splitting the SCC, then we continue with one of the |
205 | // splits |
206 | // This means that the NodesInLastSCC is a superset (not strict) of the nodes |
207 | // that subsequent passes would have processed |
208 | // - in addition, if new Nodes were created by a pass (e.g. CoroSplit), |
209 | // they'd be adjacent to Nodes in the last SCC. So we just need to check the |
210 | // boundary of Nodes in NodesInLastSCC for Nodes we haven't seen. We don't |
211 | // care about the nature of the Edge (call or ref). `FunctionLevels`-wise, we |
212 | // record them at the same level as the original node (this is a choice, may |
213 | // need revisiting). |
214 | // - nodes are only deleted at the end of a call graph walk where they are |
215 | // batch deleted, so we shouldn't see any dead nodes here. |
216 | while (!NodesInLastSCC.empty()) { |
217 | const auto *N = *NodesInLastSCC.begin(); |
218 | assert(!N->isDead()); |
219 | NodesInLastSCC.erase(Ptr: N); |
220 | EdgeCount += getLocalCalls(F&: N->getFunction()); |
221 | const auto NLevel = FunctionLevels.at(k: N); |
222 | for (const auto &E : *(*N)) { |
223 | const auto *AdjNode = &E.getNode(); |
224 | assert(!AdjNode->isDead() && !AdjNode->getFunction().isDeclaration()); |
225 | auto I = AllNodes.insert(V: AdjNode); |
226 | // We've discovered a new function. |
227 | if (I.second) { |
228 | ++NodeCount; |
229 | NodesInLastSCC.insert(Ptr: AdjNode); |
230 | FunctionLevels[AdjNode] = NLevel; |
231 | } |
232 | } |
233 | } |
234 | |
235 | EdgeCount -= EdgesOfLastSeenNodes; |
236 | EdgesOfLastSeenNodes = 0; |
237 | |
238 | // (Re)use NodesInLastSCC to remember the nodes in the SCC right now, |
239 | // in case the SCC is split before onPassExit and some nodes are split out |
240 | assert(NodesInLastSCC.empty()); |
241 | for (const auto &N : *CurSCC) |
242 | NodesInLastSCC.insert(Ptr: &N); |
243 | } |
244 | |
245 | void MLInlineAdvisor::onPassExit(LazyCallGraph::SCC *CurSCC) { |
246 | // No need to keep this around - function passes will invalidate it. |
247 | if (!KeepFPICache) |
248 | FPICache.clear(); |
249 | if (!CurSCC || ForceStop) |
250 | return; |
251 | // Keep track of the nodes and edges we last saw. Then, in onPassEntry, |
252 | // we update the node count and edge count from the subset of these nodes that |
253 | // survived. |
254 | EdgesOfLastSeenNodes = 0; |
255 | |
256 | // Check on nodes that were in SCC onPassEntry |
257 | for (const LazyCallGraph::Node *N : NodesInLastSCC) { |
258 | assert(!N->isDead()); |
259 | EdgesOfLastSeenNodes += getLocalCalls(F&: N->getFunction()); |
260 | } |
261 | |
262 | // Check on nodes that may have got added to SCC |
263 | for (const auto &N : *CurSCC) { |
264 | assert(!N.isDead()); |
265 | auto I = NodesInLastSCC.insert(Ptr: &N); |
266 | if (I.second) |
267 | EdgesOfLastSeenNodes += getLocalCalls(F&: N.getFunction()); |
268 | } |
269 | assert(NodeCount >= NodesInLastSCC.size()); |
270 | assert(EdgeCount >= EdgesOfLastSeenNodes); |
271 | } |
272 | |
273 | int64_t MLInlineAdvisor::getLocalCalls(Function &F) { |
274 | return getCachedFPI(F).DirectCallsToDefinedFunctions; |
275 | } |
276 | |
277 | // Update the internal state of the advisor, and force invalidate feature |
278 | // analysis. Currently, we maintain minimal (and very simple) global state - the |
279 | // number of functions and the number of static calls. We also keep track of the |
280 | // total IR size in this module, to stop misbehaving policies at a certain bloat |
281 | // factor (SizeIncreaseThreshold) |
282 | void MLInlineAdvisor::onSuccessfulInlining(const MLInlineAdvice &Advice, |
283 | bool CalleeWasDeleted) { |
284 | assert(!ForceStop); |
285 | Function *Caller = Advice.getCaller(); |
286 | Function *Callee = Advice.getCallee(); |
287 | // The caller features aren't valid anymore. |
288 | { |
289 | PreservedAnalyses PA = PreservedAnalyses::all(); |
290 | PA.abandon<FunctionPropertiesAnalysis>(); |
291 | PA.abandon<DominatorTreeAnalysis>(); |
292 | PA.abandon<LoopAnalysis>(); |
293 | FAM.invalidate(IR&: *Caller, PA); |
294 | } |
295 | Advice.updateCachedCallerFPI(FAM); |
296 | int64_t IRSizeAfter = |
297 | getIRSize(F&: *Caller) + (CalleeWasDeleted ? 0 : Advice.CalleeIRSize); |
298 | CurrentIRSize += IRSizeAfter - (Advice.CallerIRSize + Advice.CalleeIRSize); |
299 | if (CurrentIRSize > SizeIncreaseThreshold * InitialIRSize) |
300 | ForceStop = true; |
301 | |
302 | // We can delta-update module-wide features. We know the inlining only changed |
303 | // the caller, and maybe the callee (by deleting the latter). |
304 | // Nodes are simple to update. |
305 | // For edges, we 'forget' the edges that the caller and callee used to have |
306 | // before inlining, and add back what they currently have together. |
307 | int64_t NewCallerAndCalleeEdges = |
308 | getCachedFPI(*Caller).DirectCallsToDefinedFunctions; |
309 | |
310 | // A dead function's node is not actually removed from the call graph until |
311 | // the end of the call graph walk, but the node no longer belongs to any valid |
312 | // SCC. |
313 | if (CalleeWasDeleted) { |
314 | --NodeCount; |
315 | NodesInLastSCC.erase(Ptr: CG.lookup(F: *Callee)); |
316 | DeadFunctions.insert(V: Callee); |
317 | } else { |
318 | NewCallerAndCalleeEdges += |
319 | getCachedFPI(*Callee).DirectCallsToDefinedFunctions; |
320 | } |
321 | EdgeCount += (NewCallerAndCalleeEdges - Advice.CallerAndCalleeEdges); |
322 | assert(CurrentIRSize >= 0 && EdgeCount >= 0 && NodeCount >= 0); |
323 | } |
324 | |
325 | int64_t MLInlineAdvisor::getModuleIRSize() const { |
326 | int64_t Ret = 0; |
327 | for (auto &F : M) |
328 | if (!F.isDeclaration()) |
329 | Ret += getIRSize(F); |
330 | return Ret; |
331 | } |
332 | |
333 | FunctionPropertiesInfo &MLInlineAdvisor::getCachedFPI(Function &F) const { |
334 | auto InsertPair = |
335 | FPICache.insert(x: std::make_pair(x: &F, y: FunctionPropertiesInfo())); |
336 | if (!InsertPair.second) |
337 | return InsertPair.first->second; |
338 | InsertPair.first->second = FAM.getResult<FunctionPropertiesAnalysis>(IR&: F); |
339 | return InsertPair.first->second; |
340 | } |
341 | |
342 | std::unique_ptr<InlineAdvice> MLInlineAdvisor::getAdviceImpl(CallBase &CB) { |
343 | if (auto Skip = getSkipAdviceIfUnreachableCallsite(CB)) |
344 | return Skip; |
345 | |
346 | auto &Caller = *CB.getCaller(); |
347 | auto &Callee = *CB.getCalledFunction(); |
348 | |
349 | auto GetAssumptionCache = [&](Function &F) -> AssumptionCache & { |
350 | return FAM.getResult<AssumptionAnalysis>(IR&: F); |
351 | }; |
352 | auto &TIR = FAM.getResult<TargetIRAnalysis>(IR&: Callee); |
353 | auto &ORE = FAM.getResult<OptimizationRemarkEmitterAnalysis>(IR&: Caller); |
354 | |
355 | if (SkipPolicy == SkipMLPolicyCriteria::IfCallerIsNotCold) { |
356 | if (!PSI.isFunctionEntryCold(F: &Caller)) |
357 | return std::make_unique<InlineAdvice>(args: this, args&: CB, args&: ORE, |
358 | args: GetDefaultAdvice(CB)); |
359 | } |
360 | auto MandatoryKind = InlineAdvisor::getMandatoryKind(CB, FAM, ORE); |
361 | // If this is a "never inline" case, there won't be any changes to internal |
362 | // state we need to track, so we can just return the base InlineAdvice, which |
363 | // will do nothing interesting. |
364 | // Same thing if this is a recursive case. |
365 | if (MandatoryKind == InlineAdvisor::MandatoryInliningKind::Never || |
366 | &Caller == &Callee) |
367 | return getMandatoryAdvice(CB, Advice: false); |
368 | |
369 | bool Mandatory = |
370 | MandatoryKind == InlineAdvisor::MandatoryInliningKind::Always; |
371 | |
372 | // If we need to stop, we won't want to track anymore any state changes, so |
373 | // we just return the base InlineAdvice, which acts as a noop. |
374 | if (ForceStop) { |
375 | ORE.emit(RemarkBuilder: [&] { |
376 | return OptimizationRemarkMissed(DEBUG_TYPE, "ForceStop" , &CB) |
377 | << "Won't attempt inlining because module size grew too much." ; |
378 | }); |
379 | return std::make_unique<InlineAdvice>(args: this, args&: CB, args&: ORE, args&: Mandatory); |
380 | } |
381 | |
382 | int CostEstimate = 0; |
383 | if (!Mandatory) { |
384 | auto IsCallSiteInlinable = |
385 | llvm::getInliningCostEstimate(Call&: CB, CalleeTTI&: TIR, GetAssumptionCache); |
386 | if (!IsCallSiteInlinable) { |
387 | // We can't inline this for correctness reasons, so return the base |
388 | // InlineAdvice, as we don't care about tracking any state changes (which |
389 | // won't happen). |
390 | return std::make_unique<InlineAdvice>(args: this, args&: CB, args&: ORE, args: false); |
391 | } |
392 | CostEstimate = *IsCallSiteInlinable; |
393 | } |
394 | |
395 | const auto CostFeatures = |
396 | llvm::getInliningCostFeatures(Call&: CB, CalleeTTI&: TIR, GetAssumptionCache); |
397 | if (!CostFeatures) { |
398 | return std::make_unique<InlineAdvice>(args: this, args&: CB, args&: ORE, args: false); |
399 | } |
400 | |
401 | if (Mandatory) |
402 | return getMandatoryAdvice(CB, Advice: true); |
403 | |
404 | auto NrCtantParams = 0; |
405 | for (auto I = CB.arg_begin(), E = CB.arg_end(); I != E; ++I) { |
406 | NrCtantParams += (isa<Constant>(Val: *I)); |
407 | } |
408 | |
409 | auto &CallerBefore = getCachedFPI(F&: Caller); |
410 | auto &CalleeBefore = getCachedFPI(F&: Callee); |
411 | |
412 | *ModelRunner->getTensor<int64_t>(FeatureID: FeatureIndex::callee_basic_block_count) = |
413 | CalleeBefore.BasicBlockCount; |
414 | *ModelRunner->getTensor<int64_t>(FeatureID: FeatureIndex::callsite_height) = |
415 | getInitialFunctionLevel(F: Caller); |
416 | *ModelRunner->getTensor<int64_t>(FeatureID: FeatureIndex::node_count) = NodeCount; |
417 | *ModelRunner->getTensor<int64_t>(FeatureID: FeatureIndex::nr_ctant_params) = |
418 | NrCtantParams; |
419 | *ModelRunner->getTensor<int64_t>(FeatureID: FeatureIndex::edge_count) = EdgeCount; |
420 | *ModelRunner->getTensor<int64_t>(FeatureID: FeatureIndex::caller_users) = |
421 | CallerBefore.Uses; |
422 | *ModelRunner->getTensor<int64_t>( |
423 | FeatureID: FeatureIndex::caller_conditionally_executed_blocks) = |
424 | CallerBefore.BlocksReachedFromConditionalInstruction; |
425 | *ModelRunner->getTensor<int64_t>(FeatureID: FeatureIndex::caller_basic_block_count) = |
426 | CallerBefore.BasicBlockCount; |
427 | *ModelRunner->getTensor<int64_t>( |
428 | FeatureID: FeatureIndex::callee_conditionally_executed_blocks) = |
429 | CalleeBefore.BlocksReachedFromConditionalInstruction; |
430 | *ModelRunner->getTensor<int64_t>(FeatureID: FeatureIndex::callee_users) = |
431 | CalleeBefore.Uses; |
432 | *ModelRunner->getTensor<int64_t>(FeatureID: FeatureIndex::cost_estimate) = CostEstimate; |
433 | *ModelRunner->getTensor<int64_t>(FeatureID: FeatureIndex::is_callee_avail_external) = |
434 | Callee.hasAvailableExternallyLinkage(); |
435 | *ModelRunner->getTensor<int64_t>(FeatureID: FeatureIndex::is_caller_avail_external) = |
436 | Caller.hasAvailableExternallyLinkage(); |
437 | |
438 | // Add the cost features |
439 | for (size_t I = 0; |
440 | I < static_cast<size_t>(InlineCostFeatureIndex::NumberOfFeatures); ++I) { |
441 | *ModelRunner->getTensor<int64_t>(FeatureID: inlineCostFeatureToMlFeature( |
442 | Feature: static_cast<InlineCostFeatureIndex>(I))) = CostFeatures->at(n: I); |
443 | } |
444 | // This one would have been set up to be right at the end. |
445 | if (!InteractiveChannelBaseName.empty() && InteractiveIncludeDefault) |
446 | *ModelRunner->getTensor<int64_t>(FeatureID: InlineCostFeatureIndex::NumberOfFeatures) = |
447 | GetDefaultAdvice(CB); |
448 | return getAdviceFromModel(CB, ORE); |
449 | } |
450 | |
451 | std::unique_ptr<MLInlineAdvice> |
452 | MLInlineAdvisor::(CallBase &CB, |
453 | OptimizationRemarkEmitter &ORE) { |
454 | return std::make_unique<MLInlineAdvice>( |
455 | args: this, args&: CB, args&: ORE, args: static_cast<bool>(ModelRunner->evaluate<int64_t>())); |
456 | } |
457 | |
458 | std::unique_ptr<InlineAdvice> |
459 | MLInlineAdvisor::getSkipAdviceIfUnreachableCallsite(CallBase &CB) { |
460 | if (!FAM.getResult<DominatorTreeAnalysis>(IR&: *CB.getCaller()) |
461 | .isReachableFromEntry(A: CB.getParent())) |
462 | return std::make_unique<InlineAdvice>(args: this, args&: CB, args&: getCallerORE(CB), args: false); |
463 | return nullptr; |
464 | } |
465 | |
466 | std::unique_ptr<InlineAdvice> MLInlineAdvisor::getMandatoryAdvice(CallBase &CB, |
467 | bool Advice) { |
468 | // Make sure we track inlinings in all cases - mandatory or not. |
469 | if (auto Skip = getSkipAdviceIfUnreachableCallsite(CB)) |
470 | return Skip; |
471 | if (Advice && !ForceStop) |
472 | return getMandatoryAdviceImpl(CB); |
473 | |
474 | // If this is a "never inline" case, there won't be any changes to internal |
475 | // state we need to track, so we can just return the base InlineAdvice, which |
476 | // will do nothing interesting. |
477 | // Same if we are forced to stop - we don't track anymore. |
478 | return std::make_unique<InlineAdvice>(args: this, args&: CB, args&: getCallerORE(CB), args&: Advice); |
479 | } |
480 | |
481 | std::unique_ptr<MLInlineAdvice> |
482 | MLInlineAdvisor::getMandatoryAdviceImpl(CallBase &CB) { |
483 | return std::make_unique<MLInlineAdvice>(args: this, args&: CB, args&: getCallerORE(CB), args: true); |
484 | } |
485 | |
486 | void MLInlineAdvisor::print(raw_ostream &OS) const { |
487 | OS << "[MLInlineAdvisor] Nodes: " << NodeCount << " Edges: " << EdgeCount |
488 | << " EdgesOfLastSeenNodes: " << EdgesOfLastSeenNodes << "\n" ; |
489 | OS << "[MLInlineAdvisor] FPI:\n" ; |
490 | for (auto I : FPICache) { |
491 | OS << I.first->getName() << ":\n" ; |
492 | I.second.print(OS); |
493 | OS << "\n" ; |
494 | } |
495 | OS << "\n" ; |
496 | OS << "[MLInlineAdvisor] FuncLevels:\n" ; |
497 | for (auto I : FunctionLevels) |
498 | OS << (DeadFunctions.contains(V: &I.first->getFunction()) |
499 | ? "<deleted>" |
500 | : I.first->getFunction().getName()) |
501 | << " : " << I.second << "\n" ; |
502 | |
503 | OS << "\n" ; |
504 | } |
505 | |
506 | MLInlineAdvice::(MLInlineAdvisor *Advisor, CallBase &CB, |
507 | OptimizationRemarkEmitter &ORE, |
508 | bool Recommendation) |
509 | : InlineAdvice(Advisor, CB, ORE, Recommendation), |
510 | CallerIRSize(Advisor->isForcedToStop() ? 0 : Advisor->getIRSize(F&: *Caller)), |
511 | CalleeIRSize(Advisor->isForcedToStop() ? 0 : Advisor->getIRSize(F&: *Callee)), |
512 | CallerAndCalleeEdges(Advisor->isForcedToStop() |
513 | ? 0 |
514 | : (Advisor->getLocalCalls(F&: *Caller) + |
515 | Advisor->getLocalCalls(F&: *Callee))), |
516 | PreInlineCallerFPI(Advisor->getCachedFPI(F&: *Caller)) { |
517 | if (Recommendation) |
518 | FPU.emplace(args&: Advisor->getCachedFPI(F&: *getCaller()), args&: CB); |
519 | } |
520 | |
521 | void MLInlineAdvice::( |
522 | DiagnosticInfoOptimizationBase &OR) { |
523 | using namespace ore; |
524 | OR << NV("Callee" , Callee->getName()); |
525 | for (size_t I = 0; I < NumberOfFeatures; ++I) |
526 | OR << NV(FeatureMap[I].name(), |
527 | *getAdvisor()->getModelRunner().getTensor<int64_t>(FeatureID: I)); |
528 | OR << NV("ShouldInline" , isInliningRecommended()); |
529 | } |
530 | |
531 | void MLInlineAdvice::updateCachedCallerFPI(FunctionAnalysisManager &FAM) const { |
532 | FPU->finish(FAM); |
533 | } |
534 | |
535 | void MLInlineAdvice::recordInliningImpl() { |
536 | ORE.emit(RemarkBuilder: [&]() { |
537 | OptimizationRemark R(DEBUG_TYPE, "InliningSuccess" , DLoc, Block); |
538 | reportContextForRemark(OR&: R); |
539 | return R; |
540 | }); |
541 | getAdvisor()->onSuccessfulInlining(Advice: *this, /*CalleeWasDeleted*/ false); |
542 | } |
543 | |
544 | void MLInlineAdvice::recordInliningWithCalleeDeletedImpl() { |
545 | ORE.emit(RemarkBuilder: [&]() { |
546 | OptimizationRemark R(DEBUG_TYPE, "InliningSuccessWithCalleeDeleted" , DLoc, |
547 | Block); |
548 | reportContextForRemark(OR&: R); |
549 | return R; |
550 | }); |
551 | getAdvisor()->onSuccessfulInlining(Advice: *this, /*CalleeWasDeleted*/ true); |
552 | } |
553 | |
554 | void MLInlineAdvice::recordUnsuccessfulInliningImpl( |
555 | const InlineResult &Result) { |
556 | getAdvisor()->getCachedFPI(F&: *Caller) = PreInlineCallerFPI; |
557 | ORE.emit(RemarkBuilder: [&]() { |
558 | OptimizationRemarkMissed R(DEBUG_TYPE, "InliningAttemptedAndUnsuccessful" , |
559 | DLoc, Block); |
560 | reportContextForRemark(OR&: R); |
561 | return R; |
562 | }); |
563 | } |
564 | void MLInlineAdvice::recordUnattemptedInliningImpl() { |
565 | assert(!FPU); |
566 | ORE.emit(RemarkBuilder: [&]() { |
567 | OptimizationRemarkMissed R(DEBUG_TYPE, "IniningNotAttempted" , DLoc, Block); |
568 | reportContextForRemark(OR&: R); |
569 | return R; |
570 | }); |
571 | } |
572 | |