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