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