• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021-2025 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "inlining.h"
17 #include <cstddef>
18 #include "compiler_logger.h"
19 #include "compiler_options.h"
20 #include "events_gen.h"
21 #include "optimizer/ir/analysis.h"
22 #include "optimizer/ir/graph.h"
23 #include "optimizer/ir/basicblock.h"
24 #include "optimizer/ir_builder/ir_builder.h"
25 #include "optimizer/analysis/alias_analysis.h"
26 #include "optimizer/analysis/rpo.h"
27 #include "optimizer/analysis/dominators_tree.h"
28 #include "optimizer/optimizations/cleanup.h"
29 #include "optimizer/optimizations/branch_elimination.h"
30 #include "optimizer/optimizations/object_type_check_elimination.h"
31 #include "optimizer/analysis/object_type_propagation.h"
32 #include "optimizer/optimizations/optimize_string_concat.h"
33 #include "optimizer/optimizations/peepholes.h"
34 #include "optimizer/optimizations/simplify_string_builder.h"
35 #include "runtime/include/class.h"
36 #include "events/events.h"
37 
38 namespace ark::compiler {
39 using MethodPtr = RuntimeInterface::MethodPtr;
40 
41 // Explicitly instantiate both versions of CheckMethodCanBeInlined since they can be used in subclasses
42 template bool Inlining::CheckMethodCanBeInlined<false, true>(const CallInst *, InlineContext *);
43 template bool Inlining::CheckMethodCanBeInlined<true, true>(const CallInst *, InlineContext *);
44 template bool Inlining::CheckMethodCanBeInlined<false, false>(const CallInst *, InlineContext *);
45 template bool Inlining::CheckMethodCanBeInlined<true, false>(const CallInst *, InlineContext *);
46 
CanReplaceWithCallStatic(Opcode opcode)47 inline bool CanReplaceWithCallStatic(Opcode opcode)
48 {
49     switch (opcode) {
50         case Opcode::CallResolvedVirtual:
51         case Opcode::CallVirtual:
52             return true;
53         default:
54             return false;
55     }
56 }
57 
CalculateInstructionsCount(Graph * graph)58 size_t Inlining::CalculateInstructionsCount(Graph *graph)
59 {
60     size_t count = 0;
61     for (auto bb : *graph) {
62         if (bb == nullptr || bb->IsStartBlock() || bb->IsEndBlock()) {
63             continue;
64         }
65         for (auto inst : bb->Insts()) {
66             if (inst->IsSaveState() || inst->IsPhi()) {
67                 continue;
68             }
69             switch (inst->GetOpcode()) {
70                 case Opcode::Return:
71                 case Opcode::ReturnI:
72                 case Opcode::ReturnVoid:
73                     break;
74                 default:
75                     count++;
76             }
77         }
78     }
79     return count;
80 }
81 
Inlining(Graph * graph,uint32_t instructionsCount,uint32_t methodsInlined,const ArenaVector<RuntimeInterface::MethodPtr> * inlinedStack)82 Inlining::Inlining(Graph *graph, uint32_t instructionsCount, uint32_t methodsInlined,
83                    const ArenaVector<RuntimeInterface::MethodPtr> *inlinedStack)
84     : Optimization(graph),
85       methodsInlined_(methodsInlined),
86       instructionsCount_(instructionsCount != 0 ? instructionsCount : CalculateInstructionsCount(graph)),
87       instructionsLimit_(g_options.GetCompilerInliningMaxInsts()),
88       returnBlocks_(graph->GetLocalAllocator()->Adapter()),
89       blacklist_(graph->GetLocalAllocator()->Adapter()),
90       inlinedStack_(graph->GetLocalAllocator()->Adapter()),
91       vregsCount_(graph->GetVRegsCount()),
92       cha_(graph->GetRuntime()->GetCha())
93 {
94     if (inlinedStack != nullptr) {
95         inlinedStack_.reserve(inlinedStack->size() + 1U);
96         inlinedStack_ = *inlinedStack;
97     } else {
98         inlinedStack_.reserve(1U);
99     }
100     inlinedStack_.push_back(graph->GetMethod());
101 }
102 
IsInlineCachesEnabled() const103 bool Inlining::IsInlineCachesEnabled() const
104 {
105     return DoesArchSupportDeoptimization(GetGraph()->GetArch()) && !g_options.IsCompilerNoPicInlining();
106 }
107 
108 #ifdef PANDA_EVENTS_ENABLED
EmitEvent(const Graph * graph,const CallInst * callInst,const InlineContext & ctx,events::InlineResult res)109 static void EmitEvent(const Graph *graph, const CallInst *callInst, const InlineContext &ctx, events::InlineResult res)
110 {
111     auto runtime = graph->GetRuntime();
112     events::InlineKind kind;
113     if (ctx.chaDevirtualize) {
114         kind = events::InlineKind::VIRTUAL_CHA;
115     } else if (CanReplaceWithCallStatic(callInst->GetOpcode()) || ctx.replaceToStatic) {
116         kind = events::InlineKind::VIRTUAL;
117     } else {
118         kind = events::InlineKind::STATIC;
119     }
120     EVENT_INLINE(runtime->GetMethodFullName(graph->GetMethod()),
121                  ctx.method != nullptr ? runtime->GetMethodFullName(ctx.method) : "null", callInst->GetId(), kind, res);
122 }
123 #else
124 // NOLINTNEXTLINE(readability-named-parameter)
EmitEvent(const Graph *,const CallInst *,const InlineContext &,events::InlineResult)125 static void EmitEvent(const Graph *, const CallInst *, const InlineContext &, events::InlineResult) {}
126 #endif
127 
RunImpl()128 bool Inlining::RunImpl()
129 {
130     GetGraph()->RunPass<LoopAnalyzer>();
131 
132     auto blacklistNames = g_options.GetCompilerInliningBlacklist();
133     blacklist_.reserve(blacklistNames.size());
134 
135     for (const auto &methodName : blacklistNames) {
136         blacklist_.insert(methodName);
137     }
138     return Do();
139 }
140 
RunOptimizations() const141 void Inlining::RunOptimizations() const
142 {
143     if (GetGraph()->GetParentGraph() == nullptr && GetGraph()->RunPass<ObjectTypeCheckElimination>() &&
144         GetGraph()->RunPass<Peepholes>()) {
145         GetGraph()->RunPass<BranchElimination>();
146     }
147 }
148 
Do()149 bool Inlining::Do()
150 {
151     bool inlined = false;
152     RunOptimizations();
153 
154     ArenaVector<BasicBlock *> hotBlocks(GetGraph()->GetLocalAllocator()->Adapter());
155 
156     hotBlocks = GetGraph()->GetVectorBlocks();
157     if (GetGraph()->IsAotMode()) {
158         std::stable_sort(hotBlocks.begin(), hotBlocks.end(), [](BasicBlock *a, BasicBlock *b) {
159             auto ad = (a == nullptr) ? 0 : a->GetLoop()->GetDepth();
160             auto bd = (b == nullptr) ? 0 : b->GetLoop()->GetDepth();
161             return ad > bd;
162         });
163     } else {
164         std::stable_sort(hotBlocks.begin(), hotBlocks.end(), [](BasicBlock *a, BasicBlock *b) {
165             auto ahtn = (a == nullptr) ? 0 : a->GetHotness();
166             auto bhtn = (b == nullptr) ? 0 : b->GetHotness();
167             return ahtn > bhtn;
168         });
169     }
170 
171     LOG_INLINING(DEBUG) << "Process blocks (" << hotBlocks.size() << " blocks):";
172     auto lastId = hotBlocks.size();
173     for (size_t i = 0; i < lastId; i++) {
174         if (SkipBlock(hotBlocks[i])) {
175             LOG_INLINING(DEBUG) << "-skip (" << i << '/' << hotBlocks.size() << ")";
176             continue;
177         }
178 
179         [[maybe_unused]] auto hotIdx = std::find(hotBlocks.begin(), hotBlocks.end(), hotBlocks[i]) - hotBlocks.begin();
180         [[maybe_unused]] auto bbId = hotBlocks[i]->GetId();
181         LOG_INLINING(DEBUG) << "-process BB" << bbId << " (" << i << '/' << hotBlocks.size() << "): htn_"
182                             << hotBlocks[i]->GetHotness() << " (" << hotIdx << '/' << hotBlocks.size() << ")";
183 
184         for (auto inst : hotBlocks[i]->InstsSafe()) {
185             if (GetGraph()->GetVectorBlocks()[inst->GetBasicBlock()->GetId()] == nullptr) {
186                 break;
187             }
188 
189             if (!IsInstSuitableForInline(inst)) {
190                 continue;
191             }
192 
193             inlined |= TryInline(static_cast<CallInst *>(inst));
194         }
195     }
196 
197 #ifndef NDEBUG
198     GetGraph()->SetInliningComplete();
199 #endif  // NDEBUG
200 
201     return inlined;
202 }
203 
IsInstSuitableForInline(Inst * inst) const204 bool Inlining::IsInstSuitableForInline(Inst *inst) const
205 {
206     if (!inst->IsCall()) {
207         return false;
208     }
209     auto callInst = static_cast<CallInst *>(inst);
210     ASSERT(!callInst->IsDynamicCall());
211     if (callInst->IsInlined()) {
212         return false;
213     }
214     if (callInst->IsUnresolved() || callInst->GetCallMethod() == nullptr) {
215         LOG_INLINING(DEBUG) << "Unknown method " << callInst->GetCallMethodId();
216         return false;
217     }
218     ASSERT(callInst->GetCallMethod() != nullptr);
219     return true;
220 }
221 
InvalidateAnalyses()222 void Inlining::InvalidateAnalyses()
223 {
224     GetGraph()->InvalidateAnalysis<BoundsAnalysis>();
225     GetGraph()->InvalidateAnalysis<AliasAnalysis>();
226     GetGraph()->InvalidateAnalysis<LoopAnalyzer>();
227     GetGraph()->InvalidateAnalysis<ObjectTypePropagation>();
228     InvalidateBlocksOrderAnalyzes(GetGraph());
229 }
230 
231 /**
232  * Get next Parameter instruction.
233  * NOTE(msherstennikov): this is temporary solution, need to find out better approach
234  * @param inst current param instruction
235  * @return next Parameter instruction, nullptr if there no more Parameter instructions
236  */
GetNextParam(Inst * inst)237 Inst *GetNextParam(Inst *inst)
238 {
239     for (auto nextInst = inst->GetNext(); nextInst != nullptr; nextInst = nextInst->GetNext()) {
240         if (nextInst->GetOpcode() == Opcode::Parameter) {
241             return nextInst;
242         }
243     }
244     return nullptr;
245 }
246 
TryInline(CallInst * callInst)247 bool Inlining::TryInline(CallInst *callInst)
248 {
249     InlineContext ctx;
250     if (!ResolveTarget(callInst, &ctx)) {
251         if (IsInlineCachesEnabled()) {
252             return TryInlineWithInlineCaches(callInst);
253         }
254         return false;
255     }
256 
257     ASSERT(!callInst->IsInlined());
258 
259     LOG_INLINING(DEBUG) << "Try to inline(id=" << callInst->GetId() << (callInst->IsVirtualCall() ? ", virtual" : "")
260                         << ", size=" << GetGraph()->GetRuntime()->GetMethodCodeSize(ctx.method) << ", vregs="
261                         << GetGraph()->GetRuntime()->GetMethodArgumentsCount(ctx.method) +
262                                GetGraph()->GetRuntime()->GetMethodRegistersCount(ctx.method) + 1
263                         << (ctx.chaDevirtualize ? ", CHA" : "")
264                         << "): " << GetGraph()->GetRuntime()->GetMethodFullName(ctx.method, true);
265 
266     if (DoInline(callInst, &ctx)) {
267         if (IsIntrinsic(&ctx)) {
268             callInst->GetBasicBlock()->RemoveInst(callInst);
269         }
270         EmitEvent(GetGraph(), callInst, ctx, events::InlineResult::SUCCESS);
271         return true;
272     }
273     if (ctx.replaceToStatic && !ctx.chaDevirtualize) {
274         ASSERT(ctx.method != nullptr);
275         if (callInst->GetCallMethod() != ctx.method) {
276             // Replace method id only if the methods are different.
277             // Otherwise, leave the method id as is because:
278             // 1. In aot mode the new method id can refer to a method from a different file
279             // 2. In jit mode the method id does not matter
280             callInst->SetCallMethodId(GetGraph()->GetRuntime()->GetMethodId(ctx.method));
281         }
282         callInst->SetCallMethod(ctx.method);
283         if (callInst->GetOpcode() == Opcode::CallResolvedVirtual) {
284             // Drop the first argument - the resolved method
285             ASSERT(!callInst->GetInputs().empty());
286             for (size_t i = 0; i < callInst->GetInputsCount() - 1; i++) {
287                 callInst->SetInput(i, callInst->GetInput(i + 1).GetInst());
288             }
289             callInst->RemoveInput(callInst->GetInputsCount() - 1);
290             ASSERT(!callInst->GetInputTypes()->empty());
291             callInst->GetInputTypes()->erase(callInst->GetInputTypes()->begin());
292         }
293         callInst->SetOpcode(Opcode::CallStatic);
294         EmitEvent(GetGraph(), callInst, ctx, events::InlineResult::DEVIRTUALIZED);
295         return true;
296     }
297 
298     return false;
299 }
300 
TryInlineWithInlineCaches(CallInst * callInst)301 bool Inlining::TryInlineWithInlineCaches(CallInst *callInst)
302 {
303     auto runtime = GetGraph()->GetRuntime();
304     auto pic = runtime->GetInlineCaches();
305     if (pic == nullptr) {
306         return false;
307     }
308 
309     ArenaVector<RuntimeInterface::ClassPtr> receivers(GetGraph()->GetLocalAllocator()->Adapter());
310     auto callKind = pic->GetClasses(GetGraph()->GetMethod(), callInst->GetPc(), &receivers);
311 
312     if (GetGraph()->IsAotMode()) {
313         for (auto receiver : receivers) {
314             if (runtime->IsClassExternal(GetGraph()->GetMethod(), receiver)) {
315                 LOG_INLINING(INFO) << "AOT inline caches external inlining is not supported";
316                 EVENT_INLINE(runtime->GetMethodFullName(GetGraph()->GetMethod()), runtime->GetClassName(receiver),
317                              callInst->GetId(), events::InlineKind::VIRTUAL, events::InlineResult::SKIP_EXTERNAL);
318                 return false;
319                 // We don't support offline external inline caches yet
320             }
321         }
322     }
323 
324     switch (callKind) {
325         case InlineCachesInterface::CallKind::MEGAMORPHIC:
326             EVENT_INLINE(runtime->GetMethodFullName(GetGraph()->GetMethod()), "-", callInst->GetId(),
327                          events::InlineKind::VIRTUAL_POLYMORPHIC, events::InlineResult::FAIL_MEGAMORPHIC);
328             return false;
329         case InlineCachesInterface::CallKind::UNKNOWN:
330             return false;
331         case InlineCachesInterface::CallKind::MONOMORPHIC:
332             return DoInlineMonomorphic(callInst, receivers[0]);
333         case InlineCachesInterface::CallKind::POLYMORPHIC:
334             ASSERT(callKind == InlineCachesInterface::CallKind::POLYMORPHIC);
335             return DoInlinePolymorphic(callInst, &receivers);
336         default:
337             break;
338     }
339     return false;
340 }
341 
DoInlineMonomorphic(CallInst * callInst,RuntimeInterface::ClassPtr receiver)342 bool Inlining::DoInlineMonomorphic(CallInst *callInst, RuntimeInterface::ClassPtr receiver)
343 {
344     auto runtime = GetGraph()->GetRuntime();
345     auto currMethod = GetGraph()->GetMethod();
346     ASSERT(!GetGraph()->IsAotMode() || !runtime->IsClassExternal(currMethod, receiver));
347 
348     InlineContext ctx;
349     ctx.method = runtime->ResolveVirtualMethod(receiver, callInst->GetCallMethod());
350 
351     auto callBb = callInst->GetBasicBlock();
352     auto saveState = callInst->GetSaveState();
353     auto objInst = callInst->GetObjectInst();
354 
355     LOG_INLINING(DEBUG) << "Try to inline monomorphic(size=" << runtime->GetMethodCodeSize(ctx.method)
356                         << "): " << GetMethodFullName(GetGraph(), ctx.method);
357 
358     if (!DoInline(callInst, &ctx)) {
359         return false;
360     }
361 
362     // Add type guard
363     auto getClsInst = GetGraph()->CreateInstGetInstanceClass(DataType::REFERENCE, callInst->GetPc(), objInst);
364 
365     Inst *loadClsInst = nullptr;
366     if (GetGraph()->IsAotMode()) {
367         loadClsInst = GetGraph()->CreateInstLoadClass(
368             DataType::REFERENCE, callInst->GetPc(), saveState,
369             TypeIdMixin {runtime->GetClassIdWithinFile(currMethod, receiver), currMethod}, receiver);
370     } else {
371         loadClsInst = GetGraph()->CreateInstLoadImmediate(DataType::REFERENCE, callInst->GetPc(), receiver);
372     }
373 
374     auto cmpInst = GetGraph()->CreateInstCompare(DataType::BOOL, callInst->GetPc(), getClsInst, loadClsInst,
375                                                  DataType::REFERENCE, ConditionCode::CC_NE);
376     auto deoptInst =
377         GetGraph()->CreateInstDeoptimizeIf(callInst->GetPc(), cmpInst, saveState, DeoptimizeType::INLINE_IC);
378     if (IsIntrinsic(&ctx)) {
379         callInst->InsertBefore(loadClsInst);
380         callInst->InsertBefore(getClsInst);
381         callInst->InsertBefore(cmpInst);
382         callInst->InsertBefore(deoptInst);
383         callInst->GetBasicBlock()->RemoveInst(callInst);
384     } else {
385         callBb->AppendInst(loadClsInst);
386         callBb->AppendInst(getClsInst);
387         callBb->AppendInst(cmpInst);
388         callBb->AppendInst(deoptInst);
389     }
390 
391     EVENT_INLINE(runtime->GetMethodFullName(currMethod), runtime->GetMethodFullName(ctx.method), callInst->GetId(),
392                  events::InlineKind::VIRTUAL_MONOMORPHIC, events::InlineResult::SUCCESS);
393     return true;
394 }
395 
GetOrCloneSaveState(CallInst * callInst,BasicBlock * callBb)396 SaveStateInst *Inlining::GetOrCloneSaveState(CallInst *callInst, BasicBlock *callBb)
397 {
398     auto saveState = callInst->GetSaveState();
399     ASSERT(saveState != nullptr);
400 
401     if (callBb == callInst->GetBasicBlock()) {
402         return saveState;
403     }
404 
405     auto clonedSS = CopySaveState(GetGraph(), saveState);
406     callBb->AppendInst(clonedSS);
407 
408     return clonedSS;
409 }
410 
CreateCompareClass(CallInst * callInst,Inst * getClsInst,RuntimeInterface::ClassPtr receiver,BasicBlock * callBb)411 void Inlining::CreateCompareClass(CallInst *callInst, Inst *getClsInst, RuntimeInterface::ClassPtr receiver,
412                                   BasicBlock *callBb)
413 {
414     auto runtime = GetGraph()->GetRuntime();
415     auto currMethod = GetGraph()->GetMethod();
416     ASSERT(!GetGraph()->IsAotMode() || !runtime->IsClassExternal(currMethod, receiver));
417 
418     Inst *loadClsInst = nullptr;
419     if (GetGraph()->IsAotMode()) {
420         auto saveState = GetOrCloneSaveState(callInst, callBb);
421         loadClsInst = GetGraph()->CreateInstLoadClass(
422             DataType::REFERENCE, callInst->GetPc(), saveState,
423             TypeIdMixin {runtime->GetClassIdWithinFile(currMethod, receiver), currMethod}, receiver);
424     } else {
425         loadClsInst = GetGraph()->CreateInstLoadImmediate(DataType::REFERENCE, callInst->GetPc(), receiver);
426     }
427 
428     auto cmpInst = GetGraph()->CreateInstCompare(DataType::BOOL, callInst->GetPc(), loadClsInst, getClsInst,
429                                                  DataType::REFERENCE, ConditionCode::CC_EQ);
430     auto ifInst = GetGraph()->CreateInstIfImm(DataType::BOOL, callInst->GetPc(), cmpInst, 0, DataType::BOOL,
431                                               ConditionCode::CC_NE);
432     callBb->AppendInst(loadClsInst);
433     callBb->AppendInst(cmpInst);
434     callBb->AppendInst(ifInst);
435 }
436 
InsertDeoptimizeInst(CallInst * callInst,BasicBlock * callBb,DeoptimizeType deoptType)437 void Inlining::InsertDeoptimizeInst(CallInst *callInst, BasicBlock *callBb, DeoptimizeType deoptType)
438 {
439     // If last class compare returns false we need to deoptimize the method.
440     // So we construct instruction DeoptimizeIf and insert instead of IfImm inst.
441     auto ifInst = callBb->GetLastInst();
442     ASSERT(ifInst != nullptr && ifInst->GetOpcode() == Opcode::IfImm);
443     ASSERT(ifInst->CastToIfImm()->GetImm() == 0 && ifInst->CastToIfImm()->GetCc() == ConditionCode::CC_NE);
444 
445     auto compareInst = ifInst->GetInput(0).GetInst()->CastToCompare();
446     ASSERT(compareInst != nullptr && compareInst->GetCc() == ConditionCode::CC_EQ);
447     compareInst->SetCc(ConditionCode::CC_NE);
448 
449     SaveStateInst *saveState = nullptr;
450     if (GetGraph()->IsAotMode()) {
451         auto loadClsInst = compareInst->GetInput(0).GetInst();
452         ASSERT(loadClsInst != nullptr && loadClsInst->GetOpcode() == Opcode::LoadClass);
453         saveState = loadClsInst->GetSaveState();
454     } else {
455         saveState = GetOrCloneSaveState(callInst, callBb);
456     }
457     ASSERT(saveState != nullptr);
458 
459     auto deoptInst = GetGraph()->CreateInstDeoptimizeIf(callInst->GetPc(), compareInst, saveState, deoptType);
460 
461     callBb->RemoveInst(ifInst);
462     callBb->AppendInst(deoptInst);
463 }
464 
InsertCallInst(CallInst * callInst,BasicBlock * callBb,BasicBlock * retBb,Inst * phiInst)465 void Inlining::InsertCallInst(CallInst *callInst, BasicBlock *callBb, BasicBlock *retBb, Inst *phiInst)
466 {
467     ASSERT(phiInst == nullptr || phiInst->GetBasicBlock() == retBb);
468     // Insert new BB
469     auto newCallBb = GetGraph()->CreateEmptyBlock(callBb);
470     callBb->GetLoop()->AppendBlock(newCallBb);
471     callBb->AddSucc(newCallBb);
472     newCallBb->AddSucc(retBb);
473 
474     // Copy SaveState inst
475     auto ss = callInst->GetSaveState();
476     auto cloneSs = CopySaveState(GetGraph(), ss);
477     newCallBb->AppendInst(cloneSs);
478 
479     // Copy Call inst
480     auto cloneCall = callInst->Clone(GetGraph());
481     for (auto input : callInst->GetInputs()) {
482         cloneCall->AppendInput(input.GetInst());
483     }
484     cloneCall->SetSaveState(cloneSs);
485     newCallBb->AppendInst(cloneCall);
486 
487     // Set return value in phi inst
488     if (phiInst != nullptr) {
489         phiInst->AppendInput(cloneCall);
490     }
491 }
492 
UpdateParameterDataflow(Graph * graphInl,Inst * callInst)493 void Inlining::UpdateParameterDataflow(Graph *graphInl, Inst *callInst)
494 {
495     // Replace inlined graph incoming dataflow edges
496     auto startBb = graphInl->GetStartBlock();
497     // Last input is SaveState
498     if (callInst->GetInputsCount() > 1) {
499         Inst *paramInst = *startBb->Insts().begin();
500         if (paramInst != nullptr && paramInst->GetOpcode() != Opcode::Parameter) {
501             paramInst = GetNextParam(paramInst);
502         }
503         while (paramInst != nullptr) {
504             ASSERT(paramInst);
505             auto argNum = paramInst->CastToParameter()->GetArgNumber();
506             if (callInst->GetOpcode() == Opcode::CallResolvedVirtual ||
507                 callInst->GetOpcode() == Opcode::CallResolvedStatic) {
508                 ASSERT(argNum != ParameterInst::DYNAMIC_NUM_ARGS);
509                 argNum += 1;  // skip method_reg
510             }
511             Inst *input = nullptr;
512             if (argNum < callInst->GetInputsCount() - 1) {
513                 input = callInst->GetInput(argNum).GetInst();
514             } else if (argNum == ParameterInst::DYNAMIC_NUM_ARGS) {
515                 input = GetGraph()->FindOrCreateConstant(callInst->GetInputsCount() - 1);
516             } else {
517                 input = GetGraph()->FindOrCreateConstant(DataType::Any(coretypes::TaggedValue::VALUE_UNDEFINED));
518             }
519             paramInst->ReplaceUsers(input);
520             paramInst = GetNextParam(paramInst);
521         }
522     }
523 }
524 
UpdateExternalParameterDataflow(Graph * graphInl,Inst * callInst)525 void UpdateExternalParameterDataflow(Graph *graphInl, Inst *callInst)
526 {
527     // Replace inlined graph incoming dataflow edges
528     auto startBb = graphInl->GetStartBlock();
529     // Last input is SaveState
530     if (callInst->GetInputsCount() <= 1) {
531         return;
532     }
533     Inst *paramInst = *startBb->Insts().begin();
534     if (paramInst != nullptr && paramInst->GetOpcode() != Opcode::Parameter) {
535         paramInst = GetNextParam(paramInst);
536     }
537     ArenaVector<Inst *> worklist {graphInl->GetLocalAllocator()->Adapter()};
538     while (paramInst != nullptr) {
539         auto argNum = paramInst->CastToParameter()->GetArgNumber();
540         ASSERT(argNum != ParameterInst::DYNAMIC_NUM_ARGS);
541         if (callInst->GetOpcode() == Opcode::CallResolvedVirtual ||
542             callInst->GetOpcode() == Opcode::CallResolvedStatic) {
543             argNum += 1;  // skip method_reg
544         }
545         ASSERT(argNum < callInst->GetInputsCount() - 1);
546         auto input = callInst->GetInput(argNum);
547         for (auto &user : paramInst->GetUsers()) {
548             if (user.GetInst()->GetOpcode() == Opcode::NullCheck) {
549                 user.GetInst()->ReplaceUsers(input.GetInst());
550                 worklist.push_back(user.GetInst());
551             }
552         }
553         paramInst->ReplaceUsers(input.GetInst());
554         paramInst = GetNextParam(paramInst);
555     }
556     for (auto inst : worklist) {
557         auto ss = inst->GetInput(1).GetInst();
558         inst->RemoveInputs();
559         inst->GetBasicBlock()->EraseInst(inst);
560         ss->RemoveInputs();
561         ss->GetBasicBlock()->EraseInst(ss);
562     }
563 }
564 
CloneVirtualCallInst(CallInst * call,Graph * graph)565 static inline CallInst *CloneVirtualCallInst(CallInst *call, Graph *graph)
566 {
567     if (call->GetOpcode() == Opcode::CallVirtual) {
568         return call->Clone(graph)->CastToCallVirtual();
569     }
570     if (call->GetOpcode() == Opcode::CallResolvedVirtual) {
571         return call->Clone(graph)->CastToCallResolvedVirtual();
572     }
573     UNREACHABLE();
574 }
575 
DoInlinePolymorphic(CallInst * callInst,ArenaVector<RuntimeInterface::ClassPtr> * receivers)576 bool Inlining::DoInlinePolymorphic(CallInst *callInst, ArenaVector<RuntimeInterface::ClassPtr> *receivers)
577 {
578     LOG_INLINING(DEBUG) << "Try inline polymorphic call(" << receivers->size() << " receivers):";
579     LOG_INLINING(DEBUG) << "  instruction: " << *callInst;
580 
581     bool hasUnreachableBlocks = false;
582     bool hasRuntimeCalls = false;
583     auto runtime = GetGraph()->GetRuntime();
584     auto getClsInst = GetGraph()->CreateInstGetInstanceClass(DataType::REFERENCE, callInst->GetPc());
585     PhiInst *phiInst = nullptr;
586     BasicBlock *callBb = nullptr;
587     BasicBlock *callContBb = nullptr;
588     auto inlinedMethods = methodsInlined_;
589 
590     // For each receiver we construct BB for CallVirtual inlined, and BB for Return.Inlined
591     // Inlined graph we inserts between the blocks:
592     // BEFORE:
593     //     call_bb:
594     //         call_inst
595     //         succs [call_cont_bb]
596     //     call_cont_bb:
597     //
598     // AFTER:
599     //     call_bb:
600     //         compare_classes
601     //     succs [new_call_bb, call_inlined_block]
602     //
603     //     call_inlined_block:
604     //         call_inst.inlined
605     //     succs [inlined_graph]
606     //
607     //     inlined graph:
608     //     succs [return_inlined_block]
609     //
610     //     return_inlined_block:
611     //         return.inlined
612     //     succs [call_cont_bb]
613     //
614     //     new_call_bb:
615     //     succs [call_cont_bb]
616     //
617     //     call_cont_bb
618     //         phi(new_call_bb, return_inlined_block)
619     for (auto receiver : *receivers) {
620         InlineContext ctx;
621         ctx.method = runtime->ResolveVirtualMethod(receiver, callInst->GetCallMethod());
622         ASSERT(ctx.method != nullptr && !runtime->IsMethodAbstract(ctx.method));
623         LOG_INLINING(DEBUG) << "Inline receiver " << runtime->GetMethodFullName(ctx.method);
624         if (!CheckMethodCanBeInlined<true, false>(callInst, &ctx)) {
625             continue;
626         }
627         ASSERT(ctx.intrinsicId == RuntimeInterface::IntrinsicId::INVALID);
628 
629         // Create Call.inlined
630         CallInst *newCallInst = CloneVirtualCallInst(callInst, GetGraph());
631         newCallInst->SetCallMethodId(runtime->GetMethodId(ctx.method));
632         newCallInst->SetCallMethod(ctx.method);
633         auto inlGraph = BuildGraph(&ctx, callInst, newCallInst);
634         if (inlGraph.graph == nullptr) {
635             continue;
636         }
637         vregsCount_ += inlGraph.graph->GetVRegsCount();
638         auto blocks = MakeCallBbs({callInst, getClsInst}, {callBb, callContBb}, &phiInst, receivers->size());
639         callBb = blocks.first;
640         callContBb = blocks.second;
641 
642         CreateCompareClass(callInst, getClsInst, receiver, callBb);
643         InlineReceiver({callInst, newCallInst, phiInst}, {callBb, callContBb},
644                        {&hasUnreachableBlocks, &hasRuntimeCalls}, receivers->size(), inlGraph);
645 
646         GetGraph()->GetPassManager()->GetStatistics()->AddInlinedMethods(1);
647         EVENT_INLINE(runtime->GetMethodFullName(GetGraph()->GetMethod()), runtime->GetMethodFullName(ctx.method),
648                      callInst->GetId(), events::InlineKind::VIRTUAL_POLYMORPHIC, events::InlineResult::SUCCESS);
649         LOG_INLINING(DEBUG) << "Successfully inlined: " << GetMethodFullName(GetGraph(), ctx.method);
650         methodsInlined_++;
651     }
652 
653     bool needToDeoptimize = (methodsInlined_ - inlinedMethods == receivers->size());
654     return FinalizeInlineReceiver({callInst, getClsInst, phiInst}, {callBb, callContBb},
655                                   {&hasUnreachableBlocks, &hasRuntimeCalls}, needToDeoptimize);
656 }
657 
MakeCallBbs(InstPair insts,BasicBlockPair bbs,PhiInst ** phiInst,size_t receiversSize)658 Inlining::BasicBlockPair Inlining::MakeCallBbs(InstPair insts, BasicBlockPair bbs, [[maybe_unused]] PhiInst **phiInst,
659                                                [[maybe_unused]] size_t receiversSize)
660 {
661     [[maybe_unused]] auto *callInst = static_cast<CallInst *>(insts.first);
662     [[maybe_unused]] auto *getClsInst = insts.second;
663     auto [callBb, callContBb] = bbs;
664     if (callBb == nullptr) {
665         // Split block by call instruction
666         callBb = callInst->GetBasicBlock();
667         callContBb = callBb->SplitBlockAfterInstruction(callInst, false);
668         callBb->AppendInst(getClsInst);
669         if (callInst->GetType() != DataType::VOID) {
670             *phiInst = GetGraph()->CreateInstPhi(callInst->GetType(), callInst->GetPc());
671             (*phiInst)->ReserveInputs(receiversSize << 1U);
672             callContBb->AppendPhi(*phiInst);
673         }
674     } else {
675         auto newCallBb = GetGraph()->CreateEmptyBlock(callBb);
676         callBb->GetLoop()->AppendBlock(newCallBb);
677         callBb->AddSucc(newCallBb);
678         callBb = newCallBb;
679     }
680     return std::make_pair(callBb, callContBb);
681 }
682 
InlineReceiver(InstTriple insts,BasicBlockPair bbs,FlagPair flags,size_t receiversSize,InlinedGraph inlGraph)683 void Inlining::InlineReceiver(InstTriple insts, BasicBlockPair bbs, FlagPair flags, size_t receiversSize,
684                               InlinedGraph inlGraph)
685 {
686     auto *callInst = static_cast<CallInst *>(std::get<0>(insts));
687     auto *newCallInst = static_cast<CallInst *>(std::get<1>(insts));
688     auto *phiInst = static_cast<PhiInst *>(std::get<2>(insts));
689     auto [callBb, callContBb] = bbs;
690     auto [hasUnreachableBlocks, hasRuntimeCalls] = flags;
691     // Create call_inlined_block
692     auto callInlinedBlock = GetGraph()->CreateEmptyBlock(callBb);
693     callBb->GetLoop()->AppendBlock(callInlinedBlock);
694     callBb->AddSucc(callInlinedBlock);
695 
696     // Insert Call.inlined in call_inlined_block
697     newCallInst->AppendInput(callInst->GetObjectInst());
698     newCallInst->AppendInput(callInst->GetSaveState());
699     newCallInst->SetInlined(true);
700     newCallInst->SetFlag(inst_flags::NO_DST);
701     // Set NO_DCE flag, since some call instructions might not have one after inlining
702     newCallInst->SetFlag(inst_flags::NO_DCE);
703     callInlinedBlock->PrependInst(newCallInst);
704 
705     // Create return_inlined_block and inster PHI for non void functions
706     auto returnInlinedBlock = GetGraph()->CreateEmptyBlock(callBb);
707     callBb->GetLoop()->AppendBlock(returnInlinedBlock);
708     PhiInst *localPhiInst = nullptr;
709     if (callInst->GetType() != DataType::VOID) {
710         localPhiInst = GetGraph()->CreateInstPhi(callInst->GetType(), callInst->GetPc());
711         localPhiInst->ReserveInputs(receiversSize << 1U);
712         returnInlinedBlock->AppendPhi(localPhiInst);
713     }
714 
715     // Inlined graph between call_inlined_block and return_inlined_block
716     GetGraph()->SetMaxMarkerIdx(inlGraph.graph->GetCurrentMarkerIdx());
717     UpdateParameterDataflow(inlGraph.graph, callInst);
718     UpdateDataflow(inlGraph.graph, callInst, localPhiInst, phiInst);
719     MoveConstants(inlGraph.graph);
720     UpdateControlflow(inlGraph.graph, callInlinedBlock, returnInlinedBlock);
721 
722     if (!returnInlinedBlock->GetPredsBlocks().empty()) {
723         if (inlGraph.hasRuntimeCalls) {
724             auto inlinedReturn =
725                 GetGraph()->CreateInstReturnInlined(DataType::VOID, INVALID_PC, newCallInst->GetSaveState());
726             returnInlinedBlock->PrependInst(inlinedReturn);
727         }
728         if (callInst->GetType() != DataType::VOID) {
729             ASSERT(phiInst);
730             // clang-tidy think that phi_inst can be nullptr
731             phiInst->AppendInput(localPhiInst);  // NOLINT
732         }
733         returnInlinedBlock->AddSucc(callContBb);
734     } else {
735         // We need remove return_inlined_block if inlined graph doesn't have Return inst(only Throw or Deoptimize)
736         *hasUnreachableBlocks = true;
737     }
738 
739     if (inlGraph.hasRuntimeCalls) {
740         *hasRuntimeCalls = true;
741     } else {
742         newCallInst->GetBasicBlock()->RemoveInst(newCallInst);
743     }
744 }
745 
FinalizeInlineReceiver(InstTriple insts,BasicBlockPair bbs,FlagPair flags,bool needToDeoptimize)746 bool Inlining::FinalizeInlineReceiver(InstTriple insts, BasicBlockPair bbs, FlagPair flags, bool needToDeoptimize)
747 {
748     auto *callInst = static_cast<CallInst *>(std::get<0>(insts));
749     auto *getClsInst = std::get<1>(insts);
750     auto *phiInst = static_cast<PhiInst *>(std::get<2>(insts));
751     auto [callBb, callContBb] = bbs;
752     auto [hasUnreachableBlocks, hasRuntimeCalls] = flags;
753     if (callBb == nullptr) {
754         // Nothing was inlined
755         return false;
756     }
757     if (callContBb->GetPredsBlocks().empty() || *hasUnreachableBlocks) {
758         GetGraph()->RemoveUnreachableBlocks();
759     }
760 
761     getClsInst->SetInput(0, callInst->GetObjectInst());
762 
763     if (needToDeoptimize) {
764         InsertDeoptimizeInst(callInst, callBb);
765     } else {
766         InsertCallInst(callInst, callBb, callContBb, phiInst);
767     }
768     if (callInst->GetType() != DataType::VOID) {
769         callInst->ReplaceUsers(phiInst);
770     }
771 
772     ProcessCallReturnInstructions(callInst, callContBb, *hasRuntimeCalls);
773 
774     if (*hasRuntimeCalls) {
775         callInst->GetBasicBlock()->RemoveInst(callInst);
776     }
777     return true;
778 }
779 
780 #ifndef NDEBUG
CheckExternalGraph(Graph * graph)781 void CheckExternalGraph(Graph *graph)
782 {
783     for (auto bb : graph->GetVectorBlocks()) {
784         if (bb != nullptr) {
785             for (auto inst : bb->AllInstsSafe()) {
786                 ASSERT(!inst->RequireState());
787             }
788         }
789     }
790 }
791 #endif
792 
GetNewDefAndCorrectDF(Inst * callInst,Inst * oldDef)793 Inst *Inlining::GetNewDefAndCorrectDF(Inst *callInst, Inst *oldDef)
794 {
795     if (oldDef->IsConst()) {
796         auto constant = oldDef->CastToConstant();
797         auto exisingConstant = GetGraph()->FindOrAddConstant(constant);
798         return exisingConstant;
799     }
800     if (oldDef->IsParameter()) {
801         auto argNum = oldDef->CastToParameter()->GetArgNumber();
802         ASSERT(argNum < callInst->GetInputsCount() - 1);
803         auto input = callInst->GetInput(argNum).GetInst();
804         return input;
805     }
806     ASSERT(oldDef->GetOpcode() == Opcode::NullPtr);
807     auto exisingNullptr = GetGraph()->GetOrCreateNullPtr();
808     return exisingNullptr;
809 }
810 
TryInlineExternal(CallInst * callInst,InlineContext * ctx)811 bool Inlining::TryInlineExternal(CallInst *callInst, InlineContext *ctx)
812 {
813     if (TryInlineExternalAot(callInst, ctx)) {
814         return true;
815     }
816     // Skip external methods
817     EmitEvent(GetGraph(), callInst, *ctx, events::InlineResult::SKIP_EXTERNAL);
818     LOG_INLINING(DEBUG) << "We can't inline external method: " << GetMethodFullName(GetGraph(), ctx->method);
819     return false;
820 }
821 
822 /*
823  * External methods could be inlined only if there are no instructions requiring state.
824  * The only exception are NullChecks that check parameters and used by LoadObject/StoreObject.
825  */
CheckExternalMethodInstructions(Graph * graph,CallInst * callInst)826 bool CheckExternalMethodInstructions(Graph *graph, CallInst *callInst)
827 {
828     ArenaUnorderedSet<Inst *> suspiciousInstructions(graph->GetLocalAllocator()->Adapter());
829     for (auto bb : graph->GetVectorBlocks()) {
830         if (bb == nullptr) {
831             continue;
832         }
833         for (auto inst : bb->InstsSafe()) {
834             bool isRtCall = inst->RequireState() || inst->IsRuntimeCall();
835             auto opcode = inst->GetOpcode();
836             if (isRtCall && opcode == Opcode::NullCheck) {
837                 suspiciousInstructions.insert(inst);
838             } else if (isRtCall && opcode != Opcode::NullCheck) {
839                 return false;
840             }
841             if (opcode != Opcode::LoadObject && opcode != Opcode::StoreObject) {
842                 continue;
843             }
844             auto nc = inst->GetInput(0).GetInst();
845             if (nc->GetOpcode() == Opcode::NullCheck && nc->HasSingleUser()) {
846                 suspiciousInstructions.erase(nc);
847             }
848             // If LoadObject/StoreObject first input (i.e. object to load data from / store data to)
849             // is a method parameter and corresponding call instruction's input is either NullCheck
850             // or NewObject then the NullCheck could be removed from external method's body because
851             // the parameter is known to be not null at the time load/store will be executed.
852             // If we can't prove that the input is not-null then NullCheck could not be eliminated
853             // and a method could not be inlined.
854             auto objInput = inst->GetDataFlowInput(0);
855             if (objInput->GetOpcode() != Opcode::Parameter) {
856                 return false;
857             }
858             auto paramId = objInput->CastToParameter()->GetArgNumber() + callInst->GetObjectIndex();
859             if (callInst->GetInput(paramId).GetInst()->GetOpcode() != Opcode::NullCheck &&
860                 callInst->GetDataFlowInput(paramId)->GetOpcode() != Opcode::NewObject) {
861                 return false;
862             }
863         }
864     }
865     return suspiciousInstructions.empty();
866 }
867 
868 /*
869  * We can only inline external methods that don't have runtime calls.
870  * The only exception from this rule are methods performing LoadObject/StoreObject
871  * to/from a parameter for which we can prove that it can't be null. In that case
872  * NullChecks preceding LoadObject/StoreObject are removed from inlined graph.
873  */
TryInlineExternalAot(CallInst * callInst,InlineContext * ctx)874 bool Inlining::TryInlineExternalAot(CallInst *callInst, InlineContext *ctx)
875 {
876     // We can't guarantee without cha that runtime will use this external file.
877     if (!GetGraph()->GetAotData()->GetUseCha()) {
878         return false;
879     }
880     IrBuilderExternalInliningAnalysis bytecodeAnalysis(GetGraph(), ctx->method);
881     if (!GetGraph()->RunPass(&bytecodeAnalysis)) {
882         EmitEvent(GetGraph(), callInst, *ctx, events::InlineResult::UNSUITABLE);
883         LOG_INLINING(DEBUG) << "We can't inline external method: " << GetMethodFullName(GetGraph(), ctx->method);
884         return false;
885     }
886     auto graphInl = GetGraph()->CreateChildGraph(ctx->method);
887     graphInl->SetCurrentInstructionId(GetGraph()->GetCurrentInstructionId());
888 
889     auto stats = GetGraph()->GetPassManager()->GetStatistics();
890     auto savedPbcInstNum = stats->GetPbcInstNum();
891     if (!TryBuildGraph(*ctx, graphInl, callInst, nullptr)) {
892         stats->SetPbcInstNum(savedPbcInstNum);
893         return false;
894     }
895 
896     graphInl->RunPass<Cleanup>();
897 
898     // External method could be inlined only if there are no instructions requiring state
899     // because compiler saves method id into stack map's inline info and there is no way
900     // to distinguish id of an external method from id of some method from the same translation unit.
901     // Following check ensures that there are no instructions requiring state within parsed
902     // external method except NullChecks used by LoadObject/StoreObject that checks nullness
903     // of parameters that known to be non-null at the call time. In that case NullChecks
904     // will be eliminated and there will no instruction requiring state.
905     if (!CheckExternalMethodInstructions(graphInl, callInst)) {
906         stats->SetPbcInstNum(savedPbcInstNum);
907         return false;
908     }
909 
910     vregsCount_ += graphInl->GetVRegsCount();
911 
912     auto method = ctx->method;
913     auto runtime = GetGraph()->GetRuntime();
914     // Call instruction is already inlined, so change its call id to the resolved method.
915     callInst->SetCallMethodId(runtime->GetMethodId(method));
916     callInst->SetCallMethod(method);
917 
918     auto callBb = callInst->GetBasicBlock();
919     auto callContBb = callBb->SplitBlockAfterInstruction(callInst, false);
920 
921     GetGraph()->SetMaxMarkerIdx(graphInl->GetCurrentMarkerIdx());
922     // Adjust instruction id counter for parent graph, thereby avoid situation when two instructions have same id.
923     GetGraph()->SetCurrentInstructionId(graphInl->GetCurrentInstructionId());
924 
925     UpdateExternalParameterDataflow(graphInl, callInst);
926     UpdateDataflow(graphInl, callInst, callContBb);
927     MoveConstants(graphInl);
928     UpdateControlflow(graphInl, callBb, callContBb);
929 
930     if (callContBb->GetPredsBlocks().empty()) {
931         GetGraph()->RemoveUnreachableBlocks();
932     } else {
933         returnBlocks_.push_back(callContBb);
934     }
935 
936     bool needBarriers = runtime->IsMemoryBarrierRequired(method);
937     ProcessCallReturnInstructions(callInst, callContBb, false, needBarriers);
938 
939 #ifndef NDEBUG
940     CheckExternalGraph(graphInl);
941 #endif
942 
943     LOG_INLINING(DEBUG) << "Successfully inlined external method: " << GetMethodFullName(GetGraph(), ctx->method);
944     methodsInlined_++;
945     return true;
946 }
947 
DoInlineIntrinsicByExpansion(CallInst * callInst,InlineContext * ctx)948 bool Inlining::DoInlineIntrinsicByExpansion([[maybe_unused]] CallInst *callInst, InlineContext *ctx)
949 {
950     // CC-OFFNXT(C_RULE_SWITCH_BRANCH_CHECKER) autogenerated code
951     switch (ctx->intrinsicId) {
952 #include "intrinsics_inlining_expansion_switch_case.inl"
953         default:
954             return false;
955     }
956     LOG_INLINING(DEBUG) << "The method '" << GetMethodFullName(GetGraph(), ctx->method)
957                         << "' is inlined by expanding intrinsic '" << GetIntrinsicName(ctx->intrinsicId) << "'";
958     return true;
959 }
960 
DoInlineIntrinsicByEncoding(CallInst * callInst,InlineContext * ctx)961 bool Inlining::DoInlineIntrinsicByEncoding(CallInst *callInst, InlineContext *ctx)
962 {
963     auto intrinsicId = ctx->intrinsicId;
964     if (!EncodesBuiltin(GetGraph()->GetRuntime(), intrinsicId, GetGraph()->GetArch())) {
965         return false;
966     }
967     IntrinsicInst *inst = GetGraph()->CreateInstIntrinsic(callInst->GetType(), callInst->GetPc(), intrinsicId);
968     bool needSaveState = inst->RequireState();
969 
970     size_t inputsCount = callInst->GetInputsCount() - (needSaveState ? 0 : 1);
971 
972     inst->ReserveInputs(inputsCount);
973     inst->AllocateInputTypes(GetGraph()->GetAllocator(), inputsCount);
974 
975     auto inputs = callInst->GetInputs();
976     for (size_t i = 0; i < inputsCount; ++i) {
977         inst->AppendInput(inputs[i].GetInst(), callInst->GetInputType(i));
978     }
979 
980     callInst->InsertAfter(inst);
981     callInst->ReplaceUsers(inst);
982     LOG_INLINING(DEBUG) << "The method: " << GetMethodFullName(GetGraph(), ctx->method) << "replaced to the intrinsic"
983                         << GetIntrinsicName(intrinsicId);
984     return true;
985 }
986 
DoInlineIntrinsic(CallInst * callInst,InlineContext * ctx)987 bool Inlining::DoInlineIntrinsic(CallInst *callInst, InlineContext *ctx)
988 {
989     ASSERT(ctx->intrinsicId != RuntimeInterface::IntrinsicId::INVALID);
990     ASSERT(callInst != nullptr);
991     if (!DoInlineIntrinsicByExpansion(callInst, ctx) && !DoInlineIntrinsicByEncoding(callInst, ctx)) {
992         return false;
993     }
994     if (ctx->chaDevirtualize) {
995         InsertChaGuard(callInst, ctx);
996     }
997     return true;
998 }
999 
DoInlineMethod(CallInst * callInst,InlineContext * ctx)1000 bool Inlining::DoInlineMethod(CallInst *callInst, InlineContext *ctx)
1001 {
1002     ASSERT(ctx->intrinsicId == RuntimeInterface::IntrinsicId::INVALID);
1003     auto method = ctx->method;
1004 
1005     auto runtime = GetGraph()->GetRuntime();
1006 
1007     if (resolveWoInline_) {
1008         // Return, don't inline anything
1009         // At this point we:
1010         // 1. Gave a chance to inline external method
1011         // 2. Set replace_to_static to true where possible
1012         return false;
1013     }
1014 
1015     ASSERT(!runtime->IsMethodAbstract(method));
1016 
1017     // Split block by call instruction
1018     auto callBb = callInst->GetBasicBlock();
1019     // NOTE (a.popov) Support inlining to the catch blocks
1020     if (callBb->IsCatch()) {
1021         return false;
1022     }
1023 
1024     auto graphInl = BuildGraph(ctx, callInst);
1025     if (graphInl.graph == nullptr) {
1026         return false;
1027     }
1028 
1029     vregsCount_ += graphInl.graph->GetVRegsCount();
1030 
1031     // Call instruction is already inlined, so change its call id to the resolved method.
1032     callInst->SetCallMethodId(runtime->GetMethodId(method));
1033     callInst->SetCallMethod(method);
1034 
1035     auto callContBb = callBb->SplitBlockAfterInstruction(callInst, false);
1036 
1037     GetGraph()->SetMaxMarkerIdx(graphInl.graph->GetCurrentMarkerIdx());
1038     UpdateParameterDataflow(graphInl.graph, callInst);
1039     UpdateDataflow(graphInl.graph, callInst, callContBb);
1040 
1041     MoveConstants(graphInl.graph);
1042 
1043     UpdateControlflow(graphInl.graph, callBb, callContBb);
1044 
1045     if (callContBb->GetPredsBlocks().empty()) {
1046         GetGraph()->RemoveUnreachableBlocks();
1047     } else {
1048         returnBlocks_.push_back(callContBb);
1049     }
1050 
1051     if (ctx->chaDevirtualize) {
1052         InsertChaGuard(callInst, ctx);
1053     }
1054 
1055     bool needBarriers = runtime->IsMemoryBarrierRequired(method);
1056     ProcessCallReturnInstructions(callInst, callContBb, graphInl.hasRuntimeCalls, needBarriers);
1057 
1058     LOG_INLINING(DEBUG) << "Successfully inlined: " << GetMethodFullName(GetGraph(), method);
1059     GetGraph()->GetPassManager()->GetStatistics()->AddInlinedMethods(1);
1060     methodsInlined_++;
1061 
1062     return true;
1063 }
1064 
DoInline(CallInst * callInst,InlineContext * ctx)1065 bool Inlining::DoInline(CallInst *callInst, InlineContext *ctx)
1066 {
1067     ASSERT(!callInst->IsInlined());
1068 
1069     auto method = ctx->method;
1070 
1071     auto runtime = GetGraph()->GetRuntime();
1072 
1073     if (!CheckMethodCanBeInlined<false, true>(callInst, ctx)) {
1074         return false;
1075     }
1076     if (runtime->IsMethodExternal(GetGraph()->GetMethod(), method) && !IsIntrinsic(ctx)) {
1077         if (!g_options.IsCompilerInlineExternalMethods()) {
1078             // Skip external methods
1079             EmitEvent(GetGraph(), callInst, *ctx, events::InlineResult::SKIP_EXTERNAL);
1080             LOG_INLINING(DEBUG) << "We can't inline external method: " << GetMethodFullName(GetGraph(), ctx->method);
1081             return false;
1082         }
1083         if (GetGraph()->IsAotMode()) {
1084             return TryInlineExternal(callInst, ctx);
1085         }
1086     }
1087 
1088     if (IsIntrinsic(ctx)) {
1089         return DoInlineIntrinsic(callInst, ctx);
1090     }
1091     return DoInlineMethod(callInst, ctx);
1092 }
1093 
ProcessCallReturnInstructions(CallInst * callInst,BasicBlock * callContBb,bool hasRuntimeCalls,bool needBarriers)1094 void Inlining::ProcessCallReturnInstructions(CallInst *callInst, BasicBlock *callContBb, bool hasRuntimeCalls,
1095                                              bool needBarriers)
1096 {
1097     if (hasRuntimeCalls || needBarriers) {
1098         // In case if inlined graph contains call to runtime we need to preserve call instruction with special `Inlined`
1099         // flag and create new `ReturnInlined` instruction, hereby codegen can properly handle method frames.
1100         callInst->SetInlined(true);
1101         callInst->SetFlag(inst_flags::NO_DST);
1102         // Set NO_DCE flag, since some call instructions might not have one after inlining
1103         callInst->SetFlag(inst_flags::NO_DCE);
1104         // Remove callInst's all inputs except SaveState and NullCheck(if exist)
1105         // Do not remove function (first) input for dynamic calls
1106         auto saveState = callInst->GetSaveState();
1107         ASSERT(saveState->GetOpcode() == Opcode::SaveState);
1108         if (callInst->GetOpcode() != Opcode::CallDynamic) {
1109             auto nullcheckInst = callInst->GetObjectInst();
1110             callInst->RemoveInputs();
1111             if (nullcheckInst->GetOpcode() == Opcode::NullCheck) {
1112                 callInst->AppendInput(nullcheckInst);
1113             }
1114         } else {
1115             auto func = callInst->GetInput(0).GetInst();
1116             callInst->RemoveInputs();
1117             callInst->AppendInput(func);
1118         }
1119         callInst->AppendInput(saveState);
1120         callInst->SetType(DataType::VOID);
1121         for (auto bb : returnBlocks_) {
1122             auto inlinedReturn =
1123                 GetGraph()->CreateInstReturnInlined(DataType::VOID, INVALID_PC, callInst->GetSaveState());
1124             if (bb != callContBb && (bb->IsEndWithThrowOrDeoptimize() ||
1125                                      (bb->IsEmpty() && bb->GetPredsBlocks()[0]->IsEndWithThrowOrDeoptimize()))) {
1126                 auto lastInst = !bb->IsEmpty() ? bb->GetLastInst() : bb->GetPredsBlocks()[0]->GetLastInst();
1127                 lastInst->InsertBefore(inlinedReturn);
1128                 inlinedReturn->SetExtendedLiveness();
1129             } else {
1130                 bb->PrependInst(inlinedReturn);
1131             }
1132             if (needBarriers) {
1133                 inlinedReturn->SetFlag(inst_flags::MEM_BARRIER);
1134             }
1135         }
1136     } else {
1137         // Otherwise we remove call instruction
1138         auto saveState = callInst->GetSaveState();
1139         // Remove SaveState if it has only Call instruction in the users
1140         ASSERT(saveState != nullptr);
1141         if (saveState->GetUsers().Front().GetNext() == nullptr) {
1142             saveState->GetBasicBlock()->RemoveInst(saveState);
1143         }
1144         callInst->GetBasicBlock()->RemoveInst(callInst);
1145     }
1146     returnBlocks_.clear();
1147 }
1148 
CheckBytecode(CallInst * callInst,const InlineContext & ctx,bool * calleeCallRuntime)1149 bool Inlining::CheckBytecode(CallInst *callInst, const InlineContext &ctx, bool *calleeCallRuntime)
1150 {
1151     auto vregsNum = GetGraph()->GetRuntime()->GetMethodArgumentsCount(ctx.method) +
1152                     GetGraph()->GetRuntime()->GetMethodRegistersCount(ctx.method) + 1;
1153     if ((vregsCount_ + vregsNum) >= g_options.GetCompilerMaxVregsNum()) {
1154         EmitEvent(GetGraph(), callInst, ctx, events::InlineResult::LIMIT);
1155         LOG_INLINING(DEBUG) << "Reached vregs limit: current=" << vregsCount_ << ", inlined=" << vregsNum;
1156         return false;
1157     }
1158     IrBuilderInliningAnalysis bytecodeAnalysis(GetGraph(), ctx.method);
1159     if (!GetGraph()->RunPass(&bytecodeAnalysis)) {
1160         EmitEvent(GetGraph(), callInst, ctx, events::InlineResult::UNSUITABLE);
1161         LOG_INLINING(DEBUG) << "Method contains unsuitable bytecode";
1162         return false;
1163     }
1164 
1165     if (bytecodeAnalysis.HasRuntimeCalls() && g_options.IsCompilerInlineSimpleOnly()) {
1166         EmitEvent(GetGraph(), callInst, ctx, events::InlineResult::UNSUITABLE);
1167         return false;
1168     }
1169     if (calleeCallRuntime != nullptr) {
1170         *calleeCallRuntime = bytecodeAnalysis.HasRuntimeCalls();
1171     }
1172     return true;
1173 }
1174 
TryBuildGraph(const InlineContext & ctx,Graph * graphInl,CallInst * callInst,CallInst * polyCallInst)1175 bool Inlining::TryBuildGraph(const InlineContext &ctx, Graph *graphInl, CallInst *callInst, CallInst *polyCallInst)
1176 {
1177     if (!graphInl->RunPass<IrBuilder>(ctx.method, polyCallInst != nullptr ? polyCallInst : callInst,
1178                                       GetCurrentDepth() + 1)) {
1179         EmitEvent(GetGraph(), callInst, ctx, events::InlineResult::FAIL);
1180         LOG_INLINING(WARNING) << "Graph building failed";
1181         return false;
1182     }
1183 
1184     if (graphInl->HasInfiniteLoop()) {
1185         EmitEvent(GetGraph(), callInst, ctx, events::InlineResult::INF_LOOP);
1186         COMPILER_LOG(INFO, INLINING) << "Inlining of the methods with infinite loop is not supported";
1187         return false;
1188     }
1189 
1190     if (!g_options.IsCompilerInliningSkipAlwaysThrowMethods()) {
1191         return true;
1192     }
1193 
1194     bool alwaysThrow = true;
1195     // check that end block could be reached only through throw-blocks
1196     for (auto pred : graphInl->GetEndBlock()->GetPredsBlocks()) {
1197         auto returnInst = pred->GetLastInst();
1198         if (returnInst == nullptr) {
1199             ASSERT(pred->IsTryEnd());
1200             ASSERT(pred->GetPredsBlocks().size() == 1);
1201             pred = pred->GetPredBlockByIndex(0);
1202         }
1203         if (!pred->IsEndWithThrowOrDeoptimize()) {
1204             alwaysThrow = false;
1205             break;
1206         }
1207     }
1208     if (!alwaysThrow) {
1209         return true;
1210     }
1211     EmitEvent(GetGraph(), callInst, ctx, events::InlineResult::UNSUITABLE);
1212     LOG_INLINING(DEBUG) << "Method always throw an expection, skip inlining: "
1213                         << GetMethodFullName(GetGraph(), ctx.method);
1214     return false;
1215 }
1216 
RemoveDeadSafePoints(Graph * graphInl)1217 void RemoveDeadSafePoints(Graph *graphInl)
1218 {
1219     for (auto bb : *graphInl) {
1220         if (bb == nullptr || bb->IsStartBlock() || bb->IsEndBlock()) {
1221             continue;
1222         }
1223         for (auto inst : bb->InstsSafe()) {
1224             if (!inst->IsSaveState()) {
1225                 continue;
1226             }
1227             ASSERT(inst->GetOpcode() == Opcode::SafePoint || inst->GetOpcode() == Opcode::SaveStateDeoptimize);
1228             ASSERT(inst->GetUsers().Empty());
1229             bb->RemoveInst(inst);
1230         }
1231     }
1232 }
1233 
CheckLoops(bool * calleeCallRuntime,Graph * graphInl)1234 bool Inlining::CheckLoops(bool *calleeCallRuntime, Graph *graphInl)
1235 {
1236     // Check that inlined graph hasn't loops
1237     graphInl->RunPass<LoopAnalyzer>();
1238     if (graphInl->HasLoop()) {
1239         if (g_options.IsCompilerInlineSimpleOnly()) {
1240             LOG_INLINING(INFO) << "Inlining of the methods with loops is disabled";
1241             return false;
1242         }
1243         *calleeCallRuntime = true;
1244     } else if (!*calleeCallRuntime) {
1245         RemoveDeadSafePoints(graphInl);
1246     }
1247     return true;
1248 }
1249 
1250 /* static */
PropagateObjectInfo(Graph * graphInl,CallInst * callInst)1251 void Inlining::PropagateObjectInfo(Graph *graphInl, CallInst *callInst)
1252 {
1253     // Propagate object type information to the parameters of the inlined graph
1254     auto index = callInst->GetObjectIndex();
1255     // NOLINTNEXTLINE(readability-static-accessed-through-instance)
1256     for (auto paramInst : graphInl->GetParameters()) {
1257         auto inputInst = callInst->GetDataFlowInput(index);
1258         paramInst->SetObjectTypeInfo(inputInst->GetObjectTypeInfo());
1259         index++;
1260     }
1261 }
1262 
BuildGraph(InlineContext * ctx,CallInst * callInst,CallInst * polyCallInst)1263 InlinedGraph Inlining::BuildGraph(InlineContext *ctx, CallInst *callInst, CallInst *polyCallInst)
1264 {
1265     bool calleeCallRuntime = false;
1266     if (!CheckBytecode(callInst, *ctx, &calleeCallRuntime)) {
1267         return InlinedGraph();
1268     }
1269 
1270     auto graphInl = GetGraph()->CreateChildGraph(ctx->method);
1271 
1272     // Propagate instruction id counter to inlined graph, thereby avoid instructions id duplication
1273     ASSERT(graphInl != nullptr);
1274     graphInl->SetCurrentInstructionId(GetGraph()->GetCurrentInstructionId());
1275 
1276     auto stats = GetGraph()->GetPassManager()->GetStatistics();
1277     auto savedPbcInstNum = stats->GetPbcInstNum();
1278     if (!TryBuildGraph(*ctx, graphInl, callInst, polyCallInst)) {
1279         stats->SetPbcInstNum(savedPbcInstNum);
1280         return InlinedGraph();
1281     }
1282 
1283     PropagateObjectInfo(graphInl, callInst);
1284 
1285     // Run basic optimizations
1286     graphInl->RunPass<Cleanup>(false);
1287     auto peepholeApplied = graphInl->RunPass<Peepholes>();
1288     auto objectTypeApplied = graphInl->RunPass<ObjectTypeCheckElimination>();
1289     if (peepholeApplied || objectTypeApplied) {
1290         graphInl->RunPass<BranchElimination>();
1291     }
1292     graphInl->RunPass<Cleanup>(false);
1293     graphInl->RunPass<OptimizeStringConcat>();
1294     graphInl->RunPass<SimplifyStringBuilder>();
1295 
1296     auto inlinedInstsCount = CalculateInstructionsCount(graphInl);
1297     LOG_INLINING(DEBUG) << "Actual insts-bc ratio: (" << inlinedInstsCount << " insts) / ("
1298                         << GetGraph()->GetRuntime()->GetMethodCodeSize(ctx->method) << ") = "
1299                         << (double)inlinedInstsCount / GetGraph()->GetRuntime()->GetMethodCodeSize(ctx->method);
1300 
1301     graphInl->RunPass<Inlining>(instructionsCount_ + inlinedInstsCount, methodsInlined_ + 1, &inlinedStack_);
1302 
1303     instructionsCount_ += CalculateInstructionsCount(graphInl);
1304 
1305     GetGraph()->SetMaxMarkerIdx(graphInl->GetCurrentMarkerIdx());
1306 
1307     // Adjust instruction id counter for parent graph, thereby avoid situation when two instructions have same id.
1308     GetGraph()->SetCurrentInstructionId(graphInl->GetCurrentInstructionId());
1309 
1310     if (ctx->chaDevirtualize && !GetCha()->IsSingleImplementation(ctx->method)) {
1311         EmitEvent(GetGraph(), callInst, *ctx, events::InlineResult::LOST_SINGLE_IMPL);
1312         LOG_INLINING(WARNING) << "Method lost single implementation property while we build IR for it";
1313         stats->SetPbcInstNum(savedPbcInstNum);
1314         return InlinedGraph();
1315     }
1316 
1317     if (!CheckLoops(&calleeCallRuntime, graphInl)) {
1318         stats->SetPbcInstNum(savedPbcInstNum);
1319         return InlinedGraph();
1320     }
1321     return {graphInl, calleeCallRuntime};
1322 }
1323 
1324 template <bool CHECK_EXTERNAL>
CheckMethodSize(const CallInst * callInst,InlineContext * ctx)1325 bool Inlining::CheckMethodSize(const CallInst *callInst, InlineContext *ctx)
1326 {
1327     size_t methodSize = GetGraph()->GetRuntime()->GetMethodCodeSize(ctx->method);
1328     size_t expectedInlinedInstsCount = g_options.GetCompilerInliningInstsBcRatio() * methodSize;
1329     bool methodIsTooBig = (expectedInlinedInstsCount + instructionsCount_) > instructionsLimit_;
1330     methodIsTooBig |= methodSize >= g_options.GetCompilerInliningMaxBcSize();
1331     if (methodIsTooBig) {
1332         if (methodSize <= g_options.GetCompilerInliningAlwaysInlineBcSize()) {
1333             methodIsTooBig = false;
1334             EmitEvent(GetGraph(), callInst, *ctx, events::InlineResult::IGNORE_LIMIT);
1335             LOG_INLINING(DEBUG) << "Ignore instructions limit: ";
1336         } else {
1337             EmitEvent(GetGraph(), callInst, *ctx, events::InlineResult::LIMIT);
1338             LOG_INLINING(DEBUG) << "Method is too big (d_" << inlinedStack_.size() << "):";
1339         }
1340         LOG_INLINING(DEBUG) << "instructions_count_ = " << instructionsCount_
1341                             << ", expected_inlined_insts_count = " << expectedInlinedInstsCount
1342                             << ", instructions_limit_ = " << instructionsLimit_
1343                             << ", (method = " << GetMethodFullName(GetGraph(), ctx->method) << ")";
1344     }
1345 
1346     if (methodIsTooBig || resolveWoInline_) {
1347         return CheckTooBigMethodCanBeInlined<CHECK_EXTERNAL>(callInst, ctx, methodIsTooBig);
1348     }
1349     return true;
1350 }
1351 
1352 template <bool CHECK_EXTERNAL>
CheckTooBigMethodCanBeInlined(const CallInst * callInst,InlineContext * ctx,bool methodIsTooBig)1353 bool Inlining::CheckTooBigMethodCanBeInlined(const CallInst *callInst, InlineContext *ctx, bool methodIsTooBig)
1354 {
1355     ctx->replaceToStatic = CanReplaceWithCallStatic(callInst->GetOpcode());
1356     if constexpr (!CHECK_EXTERNAL) {
1357         if (GetGraph()->GetRuntime()->IsMethodExternal(GetGraph()->GetMethod(), ctx->method)) {
1358             // Do not replace to call static if --compiler-inline-external-methods=false
1359             ctx->replaceToStatic &= g_options.IsCompilerInlineExternalMethods();
1360             ASSERT(ctx->method != nullptr);
1361             // Allow to replace CallVirtual with CallStatic if the resolved method is same as the called method
1362             // In AOT mode the resolved method id can be different from the method id in the callInst,
1363             // but we'll keep the method id from the callInst because the resolved method id can be not correct
1364             // for aot compiled method
1365             ctx->replaceToStatic &= ctx->method == callInst->GetCallMethod()
1366                                     // Or if it's not aot mode. That is, just replace in other modes
1367                                     || !GetGraph()->IsAotMode();
1368         }
1369     }
1370     if (methodIsTooBig) {
1371         return false;
1372     }
1373     ASSERT(resolveWoInline_);
1374     // Continue and return true to give a change to TryInlineExternalAot
1375     return true;
1376 }
1377 
CheckDepthLimit(InlineContext * ctx)1378 bool Inlining::CheckDepthLimit(InlineContext *ctx)
1379 {
1380     size_t recInlinedCount = std::count(inlinedStack_.begin(), inlinedStack_.end(), ctx->method);
1381     if ((recInlinedCount >= g_options.GetCompilerInliningRecursiveCallsLimit()) ||
1382         (inlinedStack_.size() >= MAX_CALL_DEPTH)) {
1383         LOG_INLINING(DEBUG) << "Recursive-calls-depth limit reached, method: '"
1384                             << GetMethodFullName(GetGraph(), ctx->method) << "', depth: " << recInlinedCount;
1385         return false;
1386     }
1387     bool isDepthLimitIgnored = GetCurrentDepth() >= g_options.GetCompilerInliningMaxDepth();
1388     bool isSmallMethod =
1389         GetGraph()->GetRuntime()->GetMethodCodeSize(ctx->method) <= g_options.GetCompilerInliningAlwaysInlineBcSize();
1390     if (isDepthLimitIgnored && !isSmallMethod) {
1391         LOG_INLINING(DEBUG) << "Small-method-depth limit reached, method: '"
1392                             << GetMethodFullName(GetGraph(), ctx->method)
1393                             << "', size: " << GetGraph()->GetRuntime()->GetMethodCodeSize(ctx->method);
1394         return false;
1395     }
1396     return true;
1397 }
1398 
1399 template <bool CHECK_EXTERNAL, bool CHECK_INTRINSICS>
CheckMethodCanBeInlined(const CallInst * callInst,InlineContext * ctx)1400 bool Inlining::CheckMethodCanBeInlined(const CallInst *callInst, InlineContext *ctx)
1401 {
1402     if (ctx->method == nullptr) {
1403         return false;
1404     }
1405 
1406     if (!CheckDepthLimit(ctx)) {
1407         return false;
1408     }
1409 
1410     if constexpr (CHECK_EXTERNAL) {
1411         if ((!g_options.IsCompilerInlineExternalMethods() || GetGraph()->IsAotMode()) &&
1412             GetGraph()->GetRuntime()->IsMethodExternal(GetGraph()->GetMethod(), ctx->method)) {
1413             // Skip external methods
1414             EmitEvent(GetGraph(), callInst, *ctx, events::InlineResult::SKIP_EXTERNAL);
1415             LOG_INLINING(DEBUG) << "We can't inline external method: " << GetMethodFullName(GetGraph(), ctx->method);
1416             return false;
1417         }
1418     }
1419 
1420     if (!blacklist_.empty()) {
1421         std::string methodName = GetGraph()->GetRuntime()->GetMethodFullName(ctx->method);
1422         if (blacklist_.find(methodName) != blacklist_.end()) {
1423             EmitEvent(GetGraph(), callInst, *ctx, events::InlineResult::NOINLINE);
1424             LOG_INLINING(DEBUG) << "Method is in the blacklist: " << GetMethodFullName(GetGraph(), ctx->method);
1425             return false;
1426         }
1427     }
1428 
1429     if (!GetGraph()->GetRuntime()->IsMethodCanBeInlined(ctx->method)) {
1430         if constexpr (CHECK_INTRINSICS) {
1431             if (GetGraph()->GetRuntime()->IsMethodIntrinsic(ctx->method)) {
1432                 ctx->intrinsicId = GetGraph()->GetRuntime()->GetIntrinsicId(ctx->method);
1433                 return true;
1434             }
1435         }
1436         EmitEvent(GetGraph(), callInst, *ctx, events::InlineResult::UNSUITABLE);
1437         return false;
1438     }
1439 
1440     if (GetGraph()->GetRuntime()->GetMethodName(ctx->method).find("__noinline__") != std::string::npos) {
1441         EmitEvent(GetGraph(), callInst, *ctx, events::InlineResult::NOINLINE);
1442         return false;
1443     }
1444     return CheckMethodSize<CHECK_EXTERNAL>(callInst, ctx);
1445 }
1446 
RemoveReturnVoidInst(BasicBlock * endBlock)1447 void RemoveReturnVoidInst(BasicBlock *endBlock)
1448 {
1449     for (auto &pred : endBlock->GetPredsBlocks()) {
1450         auto returnInst = pred->GetLastInst();
1451         if (returnInst->GetOpcode() == Opcode::Throw || returnInst->GetOpcode() == Opcode::Deoptimize) {
1452             continue;
1453         }
1454         ASSERT(returnInst->GetOpcode() == Opcode::ReturnVoid);
1455         pred->RemoveInst(returnInst);
1456     }
1457 }
1458 
1459 /// Embed inlined dataflow graph into the caller graph. A special case where the graph is empty
UpdateDataflowForEmptyGraph(Inst * callInst,std::variant<BasicBlock *,PhiInst * > use,BasicBlock * endBlock)1460 void Inlining::UpdateDataflowForEmptyGraph(Inst *callInst, std::variant<BasicBlock *, PhiInst *> use,
1461                                            BasicBlock *endBlock)
1462 {
1463     auto predBlock = endBlock->GetPredsBlocks().front();
1464     auto returnInst = predBlock->GetLastInst();
1465     ASSERT(returnInst->GetOpcode() == Opcode::Return || returnInst->GetOpcode() == Opcode::ReturnVoid ||
1466            predBlock->IsEndWithThrowOrDeoptimize());
1467     if (returnInst->GetOpcode() == Opcode::Return) {
1468         ASSERT(returnInst->GetInputsCount() == 1);
1469         auto inputInst = returnInst->GetInput(0).GetInst();
1470         if (std::holds_alternative<PhiInst *>(use)) {
1471             auto phiInst = std::get<PhiInst *>(use);
1472             phiInst->AppendInput(inputInst);
1473         } else {
1474             callInst->ReplaceUsers(inputInst);
1475         }
1476     }
1477     if (!predBlock->IsEndWithThrowOrDeoptimize()) {
1478         predBlock->RemoveInst(returnInst);
1479     }
1480 }
1481 
1482 /// Embed inlined dataflow graph into the caller graph.
UpdateDataflow(Graph * graphInl,Inst * callInst,std::variant<BasicBlock *,PhiInst * > use,Inst * newDef)1483 void Inlining::UpdateDataflow(Graph *graphInl, Inst *callInst, std::variant<BasicBlock *, PhiInst *> use, Inst *newDef)
1484 {
1485     // Replace inlined graph outcoming dataflow edges
1486     auto endBlock = graphInl->GetEndBlock();
1487     if (endBlock->GetPredsBlocks().size() > 1) {
1488         if (callInst->GetType() == DataType::VOID) {
1489             RemoveReturnVoidInst(endBlock);
1490             return;
1491         }
1492         PhiInst *phiInst = nullptr;
1493         if (std::holds_alternative<BasicBlock *>(use)) {
1494             phiInst = GetGraph()->CreateInstPhi(GetGraph()->GetRuntime()->GetMethodReturnType(graphInl->GetMethod()),
1495                                                 INVALID_PC);
1496             phiInst->ReserveInputs(endBlock->GetPredsBlocks().size());
1497             std::get<BasicBlock *>(use)->AppendPhi(phiInst);
1498         } else {
1499             phiInst = std::get<PhiInst *>(use);
1500             ASSERT(phiInst != nullptr);
1501         }
1502         for (auto pred : endBlock->GetPredsBlocks()) {
1503             auto returnInst = pred->GetLastInst();
1504             if (returnInst == nullptr) {
1505                 ASSERT(pred->IsTryEnd());
1506                 ASSERT(pred->GetPredsBlocks().size() == 1);
1507                 pred = pred->GetPredBlockByIndex(0);
1508                 returnInst = pred->GetLastInst();
1509             }
1510             if (pred->IsEndWithThrowOrDeoptimize()) {
1511                 continue;
1512             }
1513             ASSERT(returnInst->GetOpcode() == Opcode::Return);
1514             ASSERT(returnInst->GetInputsCount() == 1);
1515             phiInst->AppendInput(returnInst->GetInput(0).GetInst());
1516             pred->RemoveInst(returnInst);
1517         }
1518         if (newDef == nullptr) {
1519             newDef = phiInst;
1520         }
1521         callInst->ReplaceUsers(newDef);
1522     } else {
1523         UpdateDataflowForEmptyGraph(callInst, use, endBlock);
1524     }
1525 }
1526 
1527 /// Embed inlined controlflow graph into the caller graph.
UpdateControlflow(Graph * graphInl,BasicBlock * callBb,BasicBlock * callContBb)1528 void Inlining::UpdateControlflow(Graph *graphInl, BasicBlock *callBb, BasicBlock *callContBb)
1529 {
1530     // Move all blocks from inlined graph to parent
1531     auto currentLoop = callBb->GetLoop();
1532     for (auto bb : graphInl->GetVectorBlocks()) {
1533         if (bb != nullptr && !bb->IsStartBlock() && !bb->IsEndBlock()) {
1534             bb->ClearMarkers();
1535             GetGraph()->AddBlock(bb);
1536             bb->CopyTryCatchProps(callBb);
1537         }
1538     }
1539     callContBb->CopyTryCatchProps(callBb);
1540 
1541     // Fix loop tree
1542     for (auto loop : graphInl->GetRootLoop()->GetInnerLoops()) {
1543         currentLoop->AppendInnerLoop(loop);
1544         loop->SetOuterLoop(currentLoop);
1545     }
1546     for (auto bb : graphInl->GetRootLoop()->GetBlocks()) {
1547         bb->SetLoop(currentLoop);
1548         currentLoop->AppendBlock(bb);
1549     }
1550 
1551     // Connect inlined graph as successor of the first part of call continuation block
1552     auto startBb = graphInl->GetStartBlock();
1553     ASSERT(startBb->GetSuccsBlocks().size() == 1);
1554     auto succ = startBb->GetSuccessor(0);
1555     succ->ReplacePred(startBb, callBb);
1556     startBb->GetSuccsBlocks().clear();
1557 
1558     ASSERT(graphInl->HasEndBlock());
1559     auto endBlock = graphInl->GetEndBlock();
1560     for (auto pred : endBlock->GetPredsBlocks()) {
1561         endBlock->RemovePred(pred);
1562         if (pred->IsEndWithThrowOrDeoptimize() ||
1563             (pred->IsEmpty() && pred->GetPredsBlocks()[0]->IsEndWithThrowOrDeoptimize())) {
1564             if (!GetGraph()->HasEndBlock()) {
1565                 GetGraph()->CreateEndBlock();
1566             }
1567             returnBlocks_.push_back(pred);
1568             pred->ReplaceSucc(endBlock, GetGraph()->GetEndBlock());
1569         } else {
1570             pred->ReplaceSucc(endBlock, callContBb);
1571         }
1572     }
1573 }
1574 
1575 /**
1576  * Move constants of the inlined graph to the current one if same constant doesn't already exist.
1577  * If constant exists just fix callee graph's dataflow to use existing constants.
1578  */
MoveConstants(Graph * graphInl)1579 void Inlining::MoveConstants(Graph *graphInl)
1580 {
1581     auto startBb = graphInl->GetStartBlock();
1582     for (ConstantInst *constant = graphInl->GetFirstConstInst(), *nextConstant = nullptr; constant != nullptr;
1583          constant = nextConstant) {
1584         nextConstant = constant->GetNextConst();
1585         startBb->EraseInst(constant);
1586         auto exisingConstant = GetGraph()->FindOrAddConstant(constant);
1587         if (exisingConstant != constant) {
1588             constant->ReplaceUsers(exisingConstant);
1589         }
1590     }
1591 
1592     // Move NullPtr instruction
1593     if (graphInl->HasNullPtrInst()) {
1594         startBb->EraseInst(graphInl->GetNullPtrInst());
1595         auto exisingNullptr = GetGraph()->GetOrCreateNullPtr();
1596         graphInl->GetNullPtrInst()->ReplaceUsers(exisingNullptr);
1597     }
1598     // Move LoadUniqueObject instruction
1599     if (graphInl->HasUniqueObjectInst()) {
1600         startBb->EraseInst(graphInl->GetUniqueObjectInst());
1601         auto exisingUniqueObject = GetGraph()->GetOrCreateUniqueObjectInst();
1602         graphInl->GetUniqueObjectInst()->ReplaceUsers(exisingUniqueObject);
1603     }
1604 }
1605 
ResolveTarget(CallInst * callInst,InlineContext * ctx)1606 bool Inlining::ResolveTarget(CallInst *callInst, InlineContext *ctx)
1607 {
1608     auto runtime = GetGraph()->GetRuntime();
1609     auto method = callInst->GetCallMethod();
1610     if (callInst->GetOpcode() == Opcode::CallStatic) {
1611         ctx->method = method;
1612         return true;
1613     }
1614 
1615     if (g_options.IsCompilerNoVirtualInlining()) {
1616         return false;
1617     }
1618 
1619     // If class or method are final we can resolve the method
1620     if (runtime->IsMethodFinal(method) || runtime->IsClassFinal(runtime->GetClass(method))) {
1621         ctx->method = method;
1622         return true;
1623     }
1624 
1625     auto objectInst = callInst->GetDataFlowInput(callInst->GetObjectIndex());
1626     auto typeInfo = objectInst->GetObjectTypeInfo();
1627     if (CanUseTypeInfo(typeInfo, method)) {
1628         auto receiver = typeInfo.GetClass();
1629         MethodPtr resolvedMethod;
1630         if (runtime->IsInterfaceMethod(method)) {
1631             resolvedMethod = runtime->ResolveInterfaceMethod(receiver, method);
1632         } else {
1633             resolvedMethod = runtime->ResolveVirtualMethod(receiver, method);
1634         }
1635         if (resolvedMethod != nullptr && (typeInfo.IsExact() || runtime->IsMethodFinal(resolvedMethod))) {
1636             ctx->method = resolvedMethod;
1637             return true;
1638         }
1639         if (typeInfo.IsExact()) {
1640             LOG_INLINING(WARNING) << "Runtime failed to resolve method";
1641             return false;
1642         }
1643     }
1644 
1645     if (ArchTraits<RUNTIME_ARCH>::SUPPORT_DEOPTIMIZATION && !g_options.IsCompilerNoChaInlining() &&
1646         !GetGraph()->IsAotMode()) {
1647         // Try resolve via CHA
1648         auto cha = GetCha();
1649         if (cha != nullptr && cha->IsSingleImplementation(method)) {
1650             auto klass = runtime->GetClass(method);
1651             ctx->method = runtime->ResolveVirtualMethod(klass, callInst->GetCallMethod());
1652             if (ctx->method == nullptr) {
1653                 return false;
1654             }
1655             ctx->chaDevirtualize = true;
1656             return true;
1657         }
1658     }
1659 
1660     return false;
1661 }
1662 
CanUseTypeInfo(ObjectTypeInfo typeInfo,RuntimeInterface::MethodPtr method)1663 bool Inlining::CanUseTypeInfo(ObjectTypeInfo typeInfo, RuntimeInterface::MethodPtr method)
1664 {
1665     auto runtime = GetGraph()->GetRuntime();
1666     if (!typeInfo || runtime->IsInterface(typeInfo.GetClass())) {
1667         return false;
1668     }
1669     return runtime->IsAssignableFrom(runtime->GetClass(method), typeInfo.GetClass());
1670 }
1671 
InsertChaGuard(CallInst * callInst,InlineContext * ctx)1672 void Inlining::InsertChaGuard(CallInst *callInst, InlineContext *ctx)
1673 {
1674     ASSERT(ctx->chaDevirtualize);
1675 
1676     auto saveState = callInst->GetSaveState();
1677     auto checkDeopt = GetGraph()->CreateInstIsMustDeoptimize(DataType::BOOL, callInst->GetPc());
1678     auto deopt =
1679         GetGraph()->CreateInstDeoptimizeIf(callInst->GetPc(), checkDeopt, saveState, DeoptimizeType::INLINE_CHA);
1680     callInst->InsertBefore(deopt);
1681     deopt->InsertBefore(checkDeopt);
1682 
1683     GetCha()->AddDependency(ctx->method, GetGraph()->GetOutermostParentGraph()->GetMethod());
1684     GetGraph()->GetOutermostParentGraph()->AddSingleImplementationMethod(ctx->method);
1685 }
1686 
SkipBlock(const BasicBlock * block) const1687 bool Inlining::SkipBlock(const BasicBlock *block) const
1688 {
1689     if (block == nullptr || block->IsEmpty()) {
1690         return true;
1691     }
1692     if (!g_options.IsCompilerInliningSkipThrowBlocks() || (GetGraph()->GetThrowCounter(block) > 0)) {
1693         return false;
1694     }
1695     return block->IsEndWithThrowOrDeoptimize();
1696 }
1697 }  // namespace ark::compiler
1698