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