• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "file_items.h"
17 #include "ir_builder.h"
18 #include "compiler_logger.h"
19 #include "macros.h"
20 #include "optimizer/ir/basicblock.h"
21 #include "optimizer/ir/runtime_interface.h"
22 #include "pbc_iterator.h"
23 #include "bytecode_instruction.h"
24 #include "code_data_accessor-inl.h"
25 #include "optimizer/analysis/dominators_tree.h"
26 #include "optimizer/analysis/loop_analyzer.h"
27 #include "optimizer/analysis/hotness_propagation.h"
28 #include "method_data_accessor-inl.h"
29 
30 namespace ark::compiler {
31 
RunImpl()32 bool IrBuilder::RunImpl()
33 {
34     COMPILER_LOG(INFO, IR_BUILDER) << "Start building ir for method: "
35                                    << GetGraph()->GetRuntime()->GetClassNameFromMethod(GetMethod()) << "."
36                                    << GetGraph()->GetRuntime()->GetMethodName(GetMethod())
37                                    << "(args=" << GetGraph()->GetRuntime()->GetMethodTotalArgumentsCount(GetMethod())
38                                    << ", regs=" << GetGraph()->GetRuntime()->GetMethodRegistersCount(GetMethod())
39                                    << ")";
40 
41     auto instructionsBuf = GetGraph()->GetRuntime()->GetMethodCode(GetMethod());
42     BytecodeInstructions pbcInstructions(instructionsBuf, GetGraph()->GetRuntime()->GetMethodCodeSize(GetMethod()));
43     size_t vregsCount = GetGraph()->GetRuntime()->GetMethodRegistersCount(GetMethod()) +
44                         GetGraph()->GetRuntime()->GetMethodTotalArgumentsCount(GetMethod()) + 1;
45     if (!CheckMethodLimitations(pbcInstructions, vregsCount)) {
46         return false;
47     }
48     GetGraph()->SetVRegsCount(vregsCount);
49     BuildBasicBlocks(pbcInstructions);
50     GetGraph()->RunPass<DominatorsTree>();
51     GetGraph()->RunPass<LoopAnalyzer>();
52 
53     if (!BuildIr(vregsCount)) {
54         return false;
55     }
56 
57     if (g_options.IsCompilerPrintStats() || g_options.WasSetCompilerDumpStatsCsv()) {
58         uint64_t pbcInstNum = 0;
59         for ([[maybe_unused]] auto i : pbcInstructions) {
60             pbcInstNum++;
61         }
62         GetGraph()->GetPassManager()->GetStatistics()->AddPbcInstNum(pbcInstNum);
63     }
64 
65     COMPILER_LOG(INFO, IR_BUILDER) << "IR successfully built: " << GetGraph()->GetVectorBlocks().size()
66                                    << " basic blocks, " << GetGraph()->GetCurrentInstructionId() << " instructions";
67 
68     HotnessPropagation(GetGraph()).Run();
69 
70     return true;
71 }
72 
BuildIr(size_t vregsCount)73 bool IrBuilder::BuildIr(size_t vregsCount)
74 {
75     auto instructionsBuf = GetGraph()->GetRuntime()->GetMethodCode(GetMethod());
76     InstBuilder instBuilder(GetGraph(), GetMethod(), callerInst_, inliningDepth_);
77     SetInstBuilder(&instBuilder);
78     instDefs_.resize(vregsCount + GetGraph()->GetEnvCount());
79     COMPILER_LOG(INFO, IR_BUILDER) << "Start instructions building...";
80     for (auto bb : GetGraph()->GetBlocksRPO()) {
81         if (!BuildBasicBlock(bb, instructionsBuf)) {
82             return false;
83         }
84     }
85     if (GetGraph()->IsBytecodeOptimizer() && !g_options.IsCompilerNonOptimizing() && !GetGraph()->IsDynamicMethod()) {
86         ConnectThrowBlocks();
87         if (GetGraph()->IsThrowApplied()) {
88             InvalidateBlocksOrderAnalyzes(GetGraph());
89         }
90     }
91     GetGraph()->RunPass<DominatorsTree>();
92     GetGraph()->InvalidateAnalysis<LoopAnalyzer>();
93     GetGraph()->RunPass<LoopAnalyzer>();
94     instBuilder.FixInstructions();
95     if (GetGraph()->GetRuntime()->IsMemoryBarrierRequired(GetMethod())) {
96         SetMemoryBarrierFlag();
97     }
98     return true;
99 }
100 
SetMemoryBarrierFlag()101 void IrBuilder::SetMemoryBarrierFlag()
102 {
103     COMPILER_LOG(INFO, IR_BUILDER) << "Setting memory barrier flag";
104     for (auto preEnd : GetGraph()->GetEndBlock()->GetPredsBlocks()) {
105         if (preEnd->IsTryEnd()) {
106             ASSERT(preEnd->GetPredsBlocks().size() == 1U);
107             preEnd = preEnd->GetPredecessor(0);
108         }
109         auto lastInst = preEnd->GetLastInst();
110         ASSERT(lastInst != nullptr);
111         if (lastInst->IsReturn()) {
112             ASSERT(GetGraph()->GetRuntime()->IsInstanceConstructor(GetMethod()));
113             lastInst->SetFlag(inst_flags::MEM_BARRIER);
114             COMPILER_LOG(INFO, IR_BUILDER) << "Set memory barrier flag to " << *lastInst;
115         }
116     }
117 }
118 
CheckMethodLimitations(const BytecodeInstructions & instructions,size_t vregsCount)119 bool IrBuilder::CheckMethodLimitations(const BytecodeInstructions &instructions, size_t vregsCount)
120 {
121     // NOTE(a.popov) Optimize catch-phi's memory consumption and get rid of this limitation
122     static constexpr auto TRY_BLOCKS_LIMIT = 128U;
123 
124     size_t bytecodeSizeLimit = g_options.GetCompilerMaxBytecodeSize();
125 
126     // The option CompilerInlineFullIntrinsics increases the size of the code several times.
127     // So the limit for this option is reduced
128     if (g_options.IsCompilerInlineFullIntrinsics()) {
129         ASSERT(GetGraph()->IsDynamicMethod());
130         bytecodeSizeLimit >>= 2U;
131     }
132 
133     if (g_options.GetCompilerMaxVregsNum() > VirtualRegister::MAX_NUM_VIRT_REGS) {
134         COMPILER_LOG(INFO, IR_BUILDER) << "Big limit for virtual registers from program options: "
135                                        << g_options.GetCompilerMaxVregsNum()
136                                        << " max value:" << VirtualRegister::MAX_NUM_VIRT_REGS;
137         ASSERT(g_options.GetCompilerMaxVregsNum() <= VirtualRegister::MAX_NUM_VIRT_REGS);
138         return false;
139     }
140 
141     if (instructions.GetSize() > bytecodeSizeLimit) {
142         COMPILER_LOG(INFO, IR_BUILDER) << "Method is too big: size=" << instructions.GetSize()
143                                        << ", limit=" << bytecodeSizeLimit;
144         return false;
145     }
146     if (vregsCount >= g_options.GetCompilerMaxVregsNum()) {
147         COMPILER_LOG(INFO, IR_BUILDER) << "Method has too many virtual registers: " << vregsCount
148                                        << ", limit=" << g_options.GetCompilerMaxVregsNum();
149         return false;
150     }
151 
152     auto pandaFile = static_cast<panda_file::File *>(GetGraph()->GetRuntime()->GetBinaryFileForMethod(GetMethod()));
153     panda_file::MethodDataAccessor mda(*pandaFile,
154                                        panda_file::File::EntityId(GetGraph()->GetRuntime()->GetMethodId(GetMethod())));
155     panda_file::CodeDataAccessor cda(*pandaFile, mda.GetCodeId().value());
156     if (cda.GetTriesSize() > TRY_BLOCKS_LIMIT) {
157         COMPILER_LOG(INFO, IR_BUILDER) << "Method has too many try blocks: " << cda.GetTriesSize()
158                                        << ", limit=" << TRY_BLOCKS_LIMIT;
159         return false;
160     }
161     return true;
162 }
163 
CreateSaveStateForLoopBlocks(BasicBlock * bb)164 bool IrBuilder::CreateSaveStateForLoopBlocks(BasicBlock *bb)
165 {
166     auto instBuilder = GetInstBuilder();
167     if (GetGraph()->GetEndBlock() != bb && !GetGraph()->IsOsrMode() &&
168         (bb->IsLoopPostExit() || bb->IsLoopPreHeader())) {
169         // Prepend SaveState as a first instruction in the loop pre header and post exit
170         // Set NO_DCE flag, so Cleanup pass won't delete yet unused instruction
171         auto *ss = instBuilder->CreateSaveState(Opcode::SaveState, bb->GetGuestPc());
172         bb->AppendInst(ss);
173         ss->SetFlag(inst_flags::NO_DCE);
174     }
175 
176     if (bb->IsLoopHeader() && !bb->GetLoop()->IsTryCatchLoop()) {
177         // Prepend SaveSateOSR as a first instruction in the loop header
178         // NOTE (a.popov) Support osr-entry for loops with catch-block back-edge
179         if (GetGraph()->IsOsrMode()) {
180             auto backedges = bb->GetLoop()->GetBackEdges();
181             auto isCatch = [](BasicBlock *basicBlock) { return basicBlock->IsCatch(); };
182             bool hasCatchBackedge = std::find_if(backedges.begin(), backedges.end(), isCatch) != backedges.end();
183             if (hasCatchBackedge) {
184                 COMPILER_LOG(WARNING, IR_BUILDER)
185                     << "Osr-entry for loops with catch-handler as back-edge is not supported";
186                 return false;
187             }
188             bb->SetOsrEntry(true);
189             auto ss = instBuilder->CreateSaveStateOsr(bb);
190             bb->AppendInst(ss);
191             COMPILER_LOG(DEBUG, IR_BUILDER) << "create save state OSR: " << *ss;
192 #ifndef NDEBUG
193             instBuilder->TryInsertSafepoint(bb, true);
194 #endif
195         }
196 
197         if (g_options.IsCompilerUseSafepoint()) {
198             auto sp = instBuilder->CreateSafePoint(bb);
199             bb->AppendInst(sp);
200             COMPILER_LOG(DEBUG, IR_BUILDER) << "create safepoint: " << *sp;
201         }
202     }
203 
204     return true;
205 }
206 
BuildBasicBlock(BasicBlock * bb,const uint8_t * instructionsBuf)207 bool IrBuilder::BuildBasicBlock(BasicBlock *bb, const uint8_t *instructionsBuf)
208 {
209     auto instBuilder = GetInstBuilder();
210     instBuilder->SetCurrentBlock(bb);
211     instBuilder->UpdateDefs();
212     if (GetGraph()->IsDynamicMethod() && !GetGraph()->IsBytecodeOptimizer() &&
213         bb == GetGraph()->GetStartBlock()->GetSuccessor(0)) {
214         instBuilder->InitEnv(bb);
215     }
216 
217     // OSR needs additional design for try-catch processing.
218     if ((bb->IsTryBegin() || bb->IsTryEnd()) && GetGraph()->IsOsrMode()) {
219         return false;
220     }
221 
222     if (!CreateSaveStateForLoopBlocks(bb)) {
223         return false;
224     }
225 
226     ASSERT(bb->GetGuestPc() != INVALID_PC);
227     // If block is not in the `blocks_` vector, it's auxiliary block without instructions
228     if (bb == blocks_[bb->GetGuestPc()]) {
229         if (bb->IsLoopPreHeader() && !GetGraph()->IsOsrMode()) {
230             return BuildInstructionsForBB<true>(bb, instructionsBuf);
231         }
232         return BuildInstructionsForBB<false>(bb, instructionsBuf);
233     }
234     if (bb->IsLoopPreHeader() && !GetGraph()->IsOsrMode()) {
235         auto ss = instBuilder->CreateSaveStateDeoptimize(bb->GetGuestPc());
236         bb->AppendInst(ss);
237     }
238     COMPILER_LOG(DEBUG, IR_BUILDER) << "Auxiliary block, skipping";
239     return true;
240 }
241 
242 template <bool NEED_SS_DEOPT>
AddInstructionToBB(BasicBlock * bb,BytecodeInstruction & inst,size_t pc,bool * ssDeoptWasBuilded)243 bool IrBuilder::AddInstructionToBB(BasicBlock *bb, BytecodeInstruction &inst, [[maybe_unused]] size_t pc,
244                                    [[maybe_unused]] bool *ssDeoptWasBuilded)
245 {
246     auto instBuilder = GetInstBuilder();
247     // Copy current defs for assigning them to catch-phi if current inst is throwable
248     ASSERT(instBuilder->GetCurrentDefs().size() == instDefs_.size());
249     std::copy(instBuilder->GetCurrentDefs().begin(), instBuilder->GetCurrentDefs().end(), instDefs_.begin());
250     auto currentBb = instBuilder->GetCurrentBlock();
251     auto currentLastInst = currentBb->GetLastInst();
252     auto bbCount = GetGraph()->GetVectorBlocks().size();
253     if constexpr (NEED_SS_DEOPT) {
254         if (inst.IsJump()) {
255             auto ss = instBuilder->CreateSaveStateDeoptimize(pc);
256             bb->AppendInst(ss);
257             *ssDeoptWasBuilded = true;
258         }
259     }
260     instBuilder->BuildInstruction(&inst);
261     if (instBuilder->IsFailed()) {
262         COMPILER_LOG(WARNING, IR_BUILDER) << "Unsupported instruction";
263         return false;
264     }
265     if (inst.CanThrow()) {
266         // One PBC instruction can be expanded to the group of IR's instructions, find first built instruction
267         // in this group, and then mark all instructions as throwable; All instructions should be marked, since
268         // some of them can be deleted during optimizations, unnecessary catch-phi moves will be resolved before
269         // Register Allocator
270         auto throwableInst = (currentLastInst == nullptr) ? currentBb->GetFirstInst() : currentLastInst->GetNext();
271         ProcessThrowableInstructions(throwableInst);
272 
273         auto &vb = GetGraph()->GetVectorBlocks();
274         for (size_t i = bbCount; i < vb.size(); i++) {
275             ProcessThrowableInstructions(vb[i]->GetFirstInst());
276         }
277     }
278     return true;
279 }
280 
281 template <bool NEED_SS_DEOPT>
BuildInstructionsForBB(BasicBlock * bb,const uint8_t * instructionsBuf)282 bool IrBuilder::BuildInstructionsForBB(BasicBlock *bb, const uint8_t *instructionsBuf)
283 {
284     auto instBuilder = GetInstBuilder();
285     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
286     BytecodeInstructions instructions(instructionsBuf + bb->GetGuestPc(), std::numeric_limits<int>::max());
287     bool ssDeoptWasBuilded = false;
288 #ifndef NDEBUG
289     instBuilder->TryInsertSafepoint(nullptr, true);
290 #endif
291     for (auto inst : instructions) {
292 #ifndef NDEBUG
293         instBuilder->TryInsertSafepoint();
294 #endif
295         auto pc = instBuilder->GetPc(inst.GetAddress());
296         // Break if current pc is pc of some basic block, that means that it is the end of the current block.
297         if (pc != bb->GetGuestPc() && GetBlockForPc(pc) != nullptr) {
298             break;
299         }
300         COMPILER_LOG(DEBUG, IR_BUILDER) << "[PBC] " << inst << "  # "
301                                         << reinterpret_cast<void *>(inst.GetAddress() - instructionsBuf);
302         if (!AddInstructionToBB<NEED_SS_DEOPT>(bb, inst, pc, &ssDeoptWasBuilded)) {
303             return false;
304         }
305         // Break if we meet terminator instruction. If instruction in the middle of basic block we don't create
306         // further dead instructions.
307         if (inst.IsTerminator() && !inst.IsSuspend()) {
308             break;
309         }
310     }
311     if (NEED_SS_DEOPT && !ssDeoptWasBuilded) {
312         ASSERT(bb->GetSuccsBlocks().size() == 1);
313         auto pc = bb->GetSuccsBlocks()[0]->GetGuestPc();
314         auto ss = instBuilder->CreateSaveStateDeoptimize(pc);
315         bb->AppendInst(ss);
316     }
317     return true;
318 }
319 
ProcessThrowableInstructions(Inst * throwableInst)320 void IrBuilder::ProcessThrowableInstructions(Inst *throwableInst)
321 {
322     auto instBuilder = GetInstBuilder();
323     for (; throwableInst != nullptr; throwableInst = throwableInst->GetNext()) {
324         if (throwableInst->IsSaveState()) {
325             continue;
326         }
327         COMPILER_LOG(DEBUG, IR_BUILDER) << "Throwable inst, Id = " << throwableInst->GetId();
328         catchHandlers_.clear();
329         EnumerateTryBlocksCoveredPc(throwableInst->GetPc(), [this](const TryCodeBlock &tryBlock) {
330             auto tbb = tryBlock.beginBb;
331             tbb->EnumerateCatchHandlers([this](BasicBlock *catchHandler, [[maybe_unused]] size_t typeId) {
332                 catchHandlers_.insert(catchHandler);
333                 return true;
334             });
335         });
336         if (!catchHandlers_.empty()) {
337             instBuilder->AddCatchPhiInputs(catchHandlers_, instDefs_, throwableInst);
338         }
339     }
340 }
341 
InstNotJump(BytecodeInstruction * inst)342 static inline bool InstNotJump(BytecodeInstruction *inst)
343 {
344     return inst->GetAddress() != nullptr && InstBuilder::GetInstructionJumpOffset(inst) == INVALID_OFFSET &&
345            !inst->HasFlag(BytecodeInstruction::RETURN);
346 }
347 
BuildBasicBlocks(const BytecodeInstructions & instructions)348 void IrBuilder::BuildBasicBlocks(const BytecodeInstructions &instructions)
349 {
350     blocks_.resize(instructions.GetSize() + 1);
351     bool fallthrough = false;
352 
353     CreateBlock(0);
354     // Create basic blocks
355     for (auto inst : instructions) {
356         auto pc = instructions.GetPc(inst);
357 
358         if (fallthrough) {
359             CreateBlock(pc);
360             fallthrough = false;
361         }
362         auto offset = InstBuilder::GetInstructionJumpOffset(&inst);
363         if (offset != INVALID_OFFSET) {
364             auto targetPc = static_cast<uint64_t>(static_cast<int64_t>(pc) + offset);
365             CreateBlock(targetPc);
366             if (inst.HasFlag(BytecodeInstruction::CONDITIONAL)) {
367                 fallthrough = true;
368             }
369         }
370     }
371     CreateTryCatchBoundariesBlocks();
372     GetGraph()->CreateStartBlock();
373     GetGraph()->CreateEndBlock(instructions.GetSize());
374     ConnectBasicBlocks(instructions);
375     ResolveTryCatchBlocks();
376     COMPILER_LOG(DEBUG, IR_BUILDER) << "Created " << GetGraph()->GetVectorBlocks().size() << " basic blocks";
377 }
378 
379 template <class Callback>
EnumerateTryBlocksCoveredPc(uint32_t pc,const Callback & callback)380 void IrBuilder::EnumerateTryBlocksCoveredPc(uint32_t pc, const Callback &callback)
381 {
382     for (const auto &[begin_pc, try_block] : tryBlocks_) {
383         if (begin_pc <= pc && pc < try_block.boundaries.endPc) {
384             callback(try_block);
385         }
386     }
387 }
388 
389 /// Return `TryCodeBlock` and flag if was created a new one
InsertTryBlockInfo(const Boundaries & tryBoundaries)390 IrBuilder::TryCodeBlock *IrBuilder::InsertTryBlockInfo(const Boundaries &tryBoundaries)
391 {
392     auto tryId = static_cast<uint32_t>(tryBlocks_.size());
393     auto range = tryBlocks_.equal_range(tryBoundaries.beginPc);
394     for (auto iter = range.first; iter != range.second; ++iter) {
395         // use try-block with the same boundaries
396         if (tryBoundaries.endPc == iter->second.boundaries.endPc) {
397             return &iter->second;
398         }
399         // insert in the increasing `end_pc` order
400         if (tryBoundaries.endPc > iter->second.boundaries.endPc) {
401             auto it = tryBlocks_.emplace_hint(iter, tryBoundaries.beginPc, TryCodeBlock {tryBoundaries});
402             it->second.Init(GetGraph(), tryId);
403             return &it->second;
404         }
405     }
406     auto it = tryBlocks_.emplace(tryBoundaries.beginPc, TryCodeBlock {tryBoundaries});
407     it->second.Init(GetGraph(), tryId);
408     return &it->second;
409 }
410 
CreateTryCatchBoundariesBlocks()411 void IrBuilder::CreateTryCatchBoundariesBlocks()
412 {
413     auto pandaFile = static_cast<panda_file::File *>(GetGraph()->GetRuntime()->GetBinaryFileForMethod(GetMethod()));
414     panda_file::MethodDataAccessor mda(*pandaFile,
415                                        panda_file::File::EntityId(GetGraph()->GetRuntime()->GetMethodId(GetMethod())));
416     panda_file::CodeDataAccessor cda(*pandaFile, mda.GetCodeId().value());
417 
418     cda.EnumerateTryBlocks([this](panda_file::CodeDataAccessor::TryBlock &tryBlock) {
419         auto startPc = tryBlock.GetStartPc();
420         auto endPc = startPc + tryBlock.GetLength();
421         auto tryInfo = InsertTryBlockInfo({startPc, endPc});
422         tryBlock.EnumerateCatchBlocks([this, tryInfo](panda_file::CodeDataAccessor::CatchBlock &catchBlock) {
423             auto pc = catchBlock.GetHandlerPc();
424             catchesPc_.insert(pc);
425             auto typeIdx = catchBlock.GetTypeIdx();
426             auto typeId = typeIdx == panda_file::INVALID_INDEX
427                               ? 0
428                               : GetGraph()->GetRuntime()->ResolveTypeIndex(GetMethod(), typeIdx);
429             tryInfo->catches->emplace_back(CatchCodeBlock {pc, typeId});
430             return true;
431         });
432 
433         return true;
434     });
435 
436     COMPILER_LOG(INFO, IR_BUILDER) << "There are: " << tryBlocks_.size() << " try-blocks in the method";
437     COMPILER_LOG(INFO, IR_BUILDER) << "There are: " << catchesPc_.size() << " catch-handlers in the method";
438 
439     for (const auto &[pc, try_block] : tryBlocks_) {
440         CreateBlock(pc);
441         CreateBlock(try_block.boundaries.endPc);
442     }
443     for (auto pc : catchesPc_) {
444         CreateBlock(pc);
445     }
446 }
447 
ConnectBasicBlocks(const BytecodeInstructions & instructions)448 void IrBuilder::ConnectBasicBlocks(const BytecodeInstructions &instructions)
449 {
450     BlocksConnectorInfo info;
451     BasicBlock *currBb = blocks_[0];
452     GetGraph()->GetStartBlock()->AddSucc(currBb);
453     for (auto inst : instructions) {
454         auto pc = instructions.GetPc(inst);
455         auto targetBlock = blocks_[pc];
456         TrackTryBoundaries(pc, inst, targetBlock != nullptr ? targetBlock : currBb, info);
457         if (info.fallthrough) {
458             ASSERT(targetBlock != nullptr);
459             // May be the second edge between same blocks
460             currBb->AddSucc(targetBlock, true);
461             info.fallthrough = false;
462             currBb = targetBlock;
463         } else if (targetBlock != nullptr) {
464             if (catchesPc_.count(pc) == 0 && InstNotJump(&info.prevInst) && !info.deadInstructions) {
465                 currBb->AddSucc(targetBlock);
466             }
467             currBb = targetBlock;
468             info.deadInstructions = false;
469         } else if (info.deadInstructions) {
470             // We are processing dead instructions now, skipping them until we meet the next block.
471             continue;
472         }
473         if (auto jmpTargetBlock = GetBlockToJump(&inst, pc); jmpTargetBlock != nullptr) {
474             currBb->AddSucc(jmpTargetBlock);
475             // In case of unconditional branch, we reset curr_bb, so if next instruction won't start new block, then
476             // we'll skip further dead instructions.
477             info.fallthrough = inst.HasFlag(BytecodeInstruction::CONDITIONAL);
478             if (!info.fallthrough) {
479                 info.deadInstructions = true;
480             }
481         }
482         info.prevInst = inst;
483     }
484 
485     // Erase end block if it wasn't connected, should be infinite loop in the graph
486     if (GetGraph()->GetEndBlock()->GetPredsBlocks().empty()) {
487         GetGraph()->EraseBlock(GetGraph()->GetEndBlock());
488         GetGraph()->SetEndBlock(nullptr);
489         COMPILER_LOG(INFO, IR_BUILDER) << "Builded graph without end block";
490     }
491 }
492 
TrackTryBoundaries(size_t pc,const BytecodeInstruction & inst,BasicBlock * targetBb,BlocksConnectorInfo & info)493 void IrBuilder::TrackTryBoundaries(size_t pc, const BytecodeInstruction &inst, BasicBlock *targetBb,
494                                    BlocksConnectorInfo &info)
495 {
496     openedTryBlocks_.remove_if([pc](TryCodeBlock *tryBlock) { return tryBlock->boundaries.endPc == pc; });
497 
498     if (tryBlocks_.count(pc) > 0) {
499         auto range = tryBlocks_.equal_range(pc);
500         for (auto it = range.first; it != range.second; ++it) {
501             auto &tryBlock = it->second;
502             if (tryBlock.boundaries.endPc > pc) {
503                 openedTryBlocks_.push_back(&tryBlock);
504                 auto allocator = GetGraph()->GetLocalAllocator();
505                 tryBlock.basicBlocks = allocator->New<ArenaVector<BasicBlock *>>(allocator->Adapter());
506                 tryBlock.throwBlocks = allocator->New<ArenaSet<BasicBlock *>>(allocator->Adapter());
507             } else {
508                 // Empty try-block
509                 ASSERT(tryBlock.boundaries.endPc == pc);
510             }
511         }
512     }
513 
514     if (openedTryBlocks_.empty()) {
515         return;
516     }
517 
518     if (auto bb = blocks_[pc]; bb != nullptr) {
519         for (auto tryBlock : openedTryBlocks_) {
520             tryBlock->basicBlocks->push_back(bb);
521         }
522     }
523 
524     if (inst.CanThrow()) {
525         for (auto &tryBlock : openedTryBlocks_) {
526             tryBlock->containsThrowableInst = true;
527         }
528     }
529     if (GetGraph()->IsBytecodeOptimizer() && !g_options.IsCompilerNonOptimizing()) {
530         if (!info.deadInstructions && inst.IsThrow(BytecodeInstruction::Exceptions::X_THROW) &&
531             !openedTryBlocks_.empty()) {
532             auto &tryBlock = *openedTryBlocks_.rbegin();
533             tryBlock->throwBlocks->insert(targetBb);
534         }
535     }
536 }
537 
GetBlockToJump(BytecodeInstruction * inst,size_t pc)538 BasicBlock *IrBuilder::GetBlockToJump(BytecodeInstruction *inst, size_t pc)
539 {
540     if ((inst->HasFlag(BytecodeInstruction::RETURN) && !inst->HasFlag(BytecodeInstruction::SUSPEND)) ||
541         inst->IsThrow(BytecodeInstruction::Exceptions::X_THROW)) {
542         return GetGraph()->GetEndBlock();
543     }
544 
545     if (auto offset = InstBuilder::GetInstructionJumpOffset(inst); offset != INVALID_OFFSET) {
546         ASSERT(blocks_[static_cast<uint64_t>(static_cast<int64_t>(pc) + offset)] != nullptr);
547         return blocks_[static_cast<uint64_t>(static_cast<int64_t>(pc) + offset)];
548     }
549     return nullptr;
550 }
551 
552 /**
553  * Mark blocks which were connected to the graph.
554  * Catch-handlers will not be marked, since they have not been connected yet.
555  */
MarkNormalControlFlow(BasicBlock * block,Marker marker)556 static void MarkNormalControlFlow(BasicBlock *block, Marker marker)
557 {
558     block->SetMarker(marker);
559     for (auto succ : block->GetSuccsBlocks()) {
560         if (!succ->IsMarked(marker)) {
561             MarkNormalControlFlow(succ, marker);
562         }
563     }
564 }
565 
MarkTryCatchBlocks(Marker marker)566 void IrBuilder::MarkTryCatchBlocks(Marker marker)
567 {
568     // All blocks without `normal` mark are considered as catch-blocks
569     for (auto bb : GetGraph()->GetBlocksRPO()) {
570         if (bb->IsMarked(marker)) {
571             continue;
572         }
573         if (bb->IsTryBegin()) {
574             bb->SetCatch(bb->GetSuccessor(0)->IsCatch());
575         } else if (bb->IsTryEnd()) {
576             bb->SetCatch(bb->GetPredecessor(0)->IsCatch());
577         } else {
578             bb->SetCatch(true);
579         }
580     }
581 
582     // Nested try-blocks can be removed, but referring to them basic blocks can be placed in the external
583     // try-blocks. So `try` marks are added after removing unreachable blocks
584     for (auto it : tryBlocks_) {
585         const auto &tryBlock = it.second;
586         if (tryBlock.beginBb->GetGraph() != tryBlock.endBb->GetGraph()) {
587             RestoreTryEnd(tryBlock);
588         }
589         tryBlock.beginBb->SetTryId(tryBlock.id);
590         tryBlock.endBb->SetTryId(tryBlock.id);
591         if (tryBlock.basicBlocks == nullptr) {
592             continue;
593         }
594         for (auto bb : *tryBlock.basicBlocks) {
595             bb->SetTryId(tryBlock.id);
596             bb->SetTry(true);
597         }
598     }
599 }
600 
601 /*
602  * Connect catch-blocks to the graph.
603  */
ResolveTryCatchBlocks()604 void IrBuilder::ResolveTryCatchBlocks()
605 {
606     auto markerHolder = MarkerHolder(GetGraph());
607     auto marker = markerHolder.GetMarker();
608     MarkNormalControlFlow(GetGraph()->GetStartBlock(), marker);
609     ConnectTryCatchBlocks();
610     GetGraph()->RemoveUnreachableBlocks();
611     MarkTryCatchBlocks(marker);
612 }
613 
ConnectTryCatchBlocks()614 void IrBuilder::ConnectTryCatchBlocks()
615 {
616     ArenaMap<uint32_t, BasicBlock *> catchBlocks(GetGraph()->GetLocalAllocator()->Adapter());
617     // Firstly create catch_begin blocks, as they should precede try_begin blocks
618     for (auto pc : catchesPc_) {
619         auto catchBegin = GetGraph()->CreateEmptyBlock();
620         catchBegin->SetGuestPc(pc);
621         catchBegin->SetCatch(true);
622         catchBegin->SetCatchBegin(true);
623         auto firstCatchBb = GetBlockForPc(pc);
624         catchBegin->AddSucc(firstCatchBb);
625         catchBlocks.emplace(pc, catchBegin);
626     }
627 
628     // Connect try_begin and catch_begin blocks
629     for (auto it : tryBlocks_) {
630         const auto &tryBlock = it.second;
631         if (tryBlock.containsThrowableInst) {
632             ConnectTryCodeBlock(tryBlock, catchBlocks);
633         } else if (tryBlock.basicBlocks != nullptr) {
634             tryBlock.basicBlocks->clear();
635         }
636     }
637 }
638 
ConnectTryCodeBlock(const TryCodeBlock & tryBlock,const ArenaMap<uint32_t,BasicBlock * > & catchBlocks)639 void IrBuilder::ConnectTryCodeBlock(const TryCodeBlock &tryBlock, const ArenaMap<uint32_t, BasicBlock *> &catchBlocks)
640 {
641     auto tryBegin = tryBlock.beginBb;
642     ASSERT(tryBegin != nullptr);
643     auto tryEnd = tryBlock.endBb;
644     ASSERT(tryEnd != nullptr);
645     // Create auxiliary `Try` instruction
646     auto tryInst = GetGraph()->CreateInstTry(tryEnd);
647     tryBegin->AppendInst(tryInst);
648     // Insert `try_begin` and `try_end`
649     auto firstTryBb = GetBlockForPc(tryBlock.boundaries.beginPc);
650     auto lastTryBb = GetPrevBlockForPc(tryBlock.boundaries.endPc);
651     firstTryBb->InsertBlockBefore(tryBegin);
652     lastTryBb->InsertBlockBeforeSucc(tryEnd, lastTryBb->GetSuccessor(0));
653     // Connect catch-handlers
654     for (auto catchBlock : *tryBlock.catches) {
655         auto catchBegin = catchBlocks.at(catchBlock.pc);
656         if (!tryBegin->HasSucc(catchBegin)) {
657             tryBegin->AddSucc(catchBegin, true);
658             tryEnd->AddSucc(catchBegin, true);
659         }
660         tryInst->AppendCatchTypeId(catchBlock.typeId, tryBegin->GetSuccBlockIndex(catchBegin));
661     }
662 }
663 
664 /**
665  * `try_end` restoring is required in the following case:
666  * try {
667  *       try { a++;}
668  *       catch { a++; }
669  * }
670  *
671  * Nested try doesn't contain throwable instructions and related catch-handler will not be connected to the graph.
672  * As a result all `catch` basic blocks will be eliminated together with outer's `try_end`, since it was inserted
673  * just after `catch`
674  */
RestoreTryEnd(const TryCodeBlock & tryBlock)675 void IrBuilder::RestoreTryEnd(const TryCodeBlock &tryBlock)
676 {
677     ASSERT(tryBlock.endBb->GetGraph() == nullptr);
678     ASSERT(tryBlock.endBb->GetSuccsBlocks().empty());
679     ASSERT(tryBlock.endBb->GetPredsBlocks().empty());
680 
681     GetGraph()->RestoreBlock(tryBlock.endBb);
682     auto lastTryBb = GetPrevBlockForPc(tryBlock.boundaries.endPc);
683     lastTryBb->InsertBlockBeforeSucc(tryBlock.endBb, lastTryBb->GetSuccessor(0));
684     for (auto succ : tryBlock.beginBb->GetSuccsBlocks()) {
685         if (succ->IsCatchBegin()) {
686             tryBlock.endBb->AddSucc(succ);
687         }
688     }
689 }
690 
RunImpl()691 bool IrBuilderInliningAnalysis::RunImpl()
692 {
693     auto pandaFile = static_cast<panda_file::File *>(GetGraph()->GetRuntime()->GetBinaryFileForMethod(GetMethod()));
694     panda_file::MethodDataAccessor mda(*pandaFile,
695                                        panda_file::File::EntityId(GetGraph()->GetRuntime()->GetMethodId(GetMethod())));
696     auto codeId = mda.GetCodeId();
697     if (!codeId.has_value()) {
698         return false;
699     }
700     panda_file::CodeDataAccessor cda(*pandaFile, codeId.value());
701 
702     // NOTE(msherstennikov): Support inlining methods with try/catch
703     if (cda.GetTriesSize() != 0) {
704         return false;
705     }
706 
707     BytecodeInstructions instructions(GetGraph()->GetRuntime()->GetMethodCode(GetMethod()),
708                                       GetGraph()->GetRuntime()->GetMethodCodeSize(GetMethod()));
709 
710     for (auto inst : instructions) {
711         if (!IsSuitableForInline(&inst)) {
712             return false;
713         }
714     }
715     return true;
716 }
717 
FindCatchBlockInPandaFile(Class * cls,uint32_t pc) const718 uint32_t IrBuilder::FindCatchBlockInPandaFile(Class *cls, uint32_t pc) const
719 {
720     if (cls == nullptr) {
721         return panda_file::INVALID_OFFSET;
722     }
723     auto pandaFile = static_cast<panda_file::File *>(GetGraph()->GetRuntime()->GetBinaryFileForMethod(GetMethod()));
724     auto *rta = GetGraph()->GetRuntime();
725     panda_file::MethodDataAccessor mda(*(pandaFile),
726                                        panda_file::File::EntityId(GetGraph()->GetRuntime()->GetMethodId(GetMethod())));
727     panda_file::CodeDataAccessor cda(*(pandaFile), mda.GetCodeId().value());
728 
729     auto method = GetMethod();
730 
731     auto *descriptorStdCoreObject = reinterpret_cast<const uint8_t *>("Lstd/core/Object;");
732     ark::panda_file::File::EntityId stdCoreObjectId = pandaFile->GetClassId(descriptorStdCoreObject);
733     auto *stdCoreObject = rta->ResolveType(method, stdCoreObjectId.GetOffset());
734 
735     uint32_t pcOffset = panda_file::INVALID_OFFSET;
736 
737     cda.EnumerateTryBlocks(
738         [&pcOffset, cls, pc, rta, method, stdCoreObject](panda_file::CodeDataAccessor::TryBlock &tryBlock) {
739             if ((tryBlock.GetStartPc() > pc) || ((tryBlock.GetStartPc() + tryBlock.GetLength()) <= pc)) {
740                 return pcOffset == panda_file::INVALID_OFFSET;
741             }
742             tryBlock.EnumerateCatchBlocks(
743                 [&pcOffset, &cls, &rta, &method, &stdCoreObject](panda_file::CodeDataAccessor::CatchBlock &catchBlock) {
744                     auto typeIdx = catchBlock.GetTypeIdx();
745                     if (typeIdx == panda_file::INVALID_INDEX) {
746                         return true;
747                     }
748 
749                     auto typeId = rta->ResolveTypeIndex(method, typeIdx);
750                     auto *handlerClass = rta->ResolveType(method, typeId);
751 
752                     if (handlerClass == stdCoreObject) {
753                         pcOffset = catchBlock.GetHandlerPc();
754                         return false;
755                     }
756 
757                     // bytecode_optimizer should have class linker to determine subclass
758                     // then the following condition could be replaced by cls->IsSubClassOf(handlerClass)
759                     if (cls == handlerClass) {
760                         pcOffset = catchBlock.GetHandlerPc();
761                         return false;
762                     }
763                     return true;
764                 });
765             return false;
766         });
767     return pcOffset;
768 }
769 
FindExceptionClass(BasicBlock * throwBlock,int32_t * throwPc)770 RuntimeInterface::ClassPtr IrBuilder::FindExceptionClass(BasicBlock *throwBlock, int32_t *throwPc)
771 {
772     if (throwBlock->GetGraph() != GetGraph()) {
773         return nullptr;
774     }
775     if (throwBlock->GetSuccsBlocks().size() > 1 || throwBlock->GetSuccessor(0)->IsTryEnd()) {
776         return nullptr;
777     }
778     auto thr0w = throwBlock->GetLastInst();
779     if (thr0w->GetOpcode() != Opcode::Throw) {
780         return nullptr;
781     }
782     *throwPc = thr0w->GetPc();
783     auto throwInst = thr0w->CastToThrow();
784     auto newObj = throwInst->GetInput(0).GetInst();
785     RuntimeInterface::ClassPtr cls = nullptr;
786     if (newObj->GetOpcode() != Opcode::NewObject) {
787         return nullptr;
788     }
789     auto initClass = newObj->GetInput(0).GetInst();
790     if (initClass->GetOpcode() == Opcode::LoadAndInitClass) {
791         cls = initClass->CastToLoadAndInitClass()->GetClass();
792     } else {
793         ASSERT(initClass->GetOpcode() == Opcode::LoadImmediate);
794         cls = initClass->CastToLoadImmediate()->GetClass();
795     }
796     return cls;
797 }
798 
FindCatchBegin(BasicBlock * bb)799 BasicBlock *IrBuilder::FindCatchBegin(BasicBlock *bb)
800 {
801     if (bb->IsCatchBegin()) {
802         return bb;
803     }
804     for (auto pred : bb->GetPredsBlocks()) {
805         if (pred->IsCatch() || pred->IsTryBegin()) {
806             return FindCatchBegin(pred);
807         }
808     }
809     UNREACHABLE();
810     return nullptr;
811 }
812 
FindAppropriateCatchBlock(const TryCodeBlock & tryBlock,BasicBlock * throwBlock,uint32_t catchPc)813 bool IrBuilder::FindAppropriateCatchBlock(const TryCodeBlock &tryBlock, BasicBlock *throwBlock, uint32_t catchPc)
814 {
815     bool result = false;
816     if (catchPc == panda_file::INVALID_OFFSET) {
817         return result;
818     }
819     for (auto catchBlock : *tryBlock.catches) {
820         if (catchBlock.pc == catchPc) {
821             auto cBb = GetBlockForPc(catchPc);
822             ASSERT(cBb != nullptr);
823             cBb = FindCatchBegin(cBb);
824             ASSERT(cBb != nullptr && cBb->IsCatchBegin() && cBb->GetGuestPc() == catchPc);
825             auto endBlock = GetGraph()->GetEndBlock();
826             endBlock->RemovePred(throwBlock);
827             throwBlock->RemoveSucc(endBlock);
828             COMPILER_LOG(DEBUG, IR_BUILDER)
829                 << "throwBlock " << throwBlock->GetId() << " is disconnected with endBlock " << endBlock->GetId();
830             throwBlock->AddSucc(cBb);
831             COMPILER_LOG(DEBUG, IR_BUILDER)
832                 << "throwBlock " << throwBlock->GetId() << " is connected with catchBeginBlock " << cBb->GetId();
833             [[maybe_unused]] auto res = GetGraph()->AppendThrowBlock(throwBlock);
834             ASSERT(res);
835             GetGraph()->SetThrowApplied();
836             result = true;
837             break;
838         }
839     }
840     return result;
841 }
842 
ConnectThrowBlock(BasicBlock * throwBlock,const TryCodeBlock & tryBlock)843 void IrBuilder::ConnectThrowBlock(BasicBlock *throwBlock, const TryCodeBlock &tryBlock)
844 {
845     if (throwBlock->GetGraph() != GetGraph()) {
846         return;
847     }
848     auto endBlock = GetGraph()->GetEndBlock();
849     if (throwBlock->GetSuccsBlocks().size() != 1 || throwBlock->GetSuccessor(0) != endBlock) {
850         return;
851     }
852     int32_t throwPc;
853     auto cls = FindExceptionClass(throwBlock, &throwPc);
854     if (cls != nullptr &&
855         FindAppropriateCatchBlock(tryBlock, throwBlock,
856                                   FindCatchBlockInPandaFile(static_cast<ark::Class *>(cls), throwPc))) {
857         return;
858     }
859     auto tryEnd = tryBlock.endBb;
860     ASSERT(tryEnd != nullptr);
861     if (tryEnd->GetGraph() != GetGraph()) {
862         return;
863     }
864     BasicBlock *catchBegin = nullptr;
865     if (tryEnd->IsCatch()) {
866         catchBegin = FindCatchBegin(tryEnd);
867     } else {
868         for (auto succ : tryEnd->GetSuccsBlocks()) {
869             if (succ->IsCatchBegin()) {
870                 catchBegin = succ;
871                 break;
872             }
873         }
874     }
875     if (catchBegin != nullptr) {
876         throwBlock->AddSucc(catchBegin);
877         COMPILER_LOG(DEBUG, IR_BUILDER) << "throwBlock " << throwBlock->GetId() << " is connected with catchBegin "
878                                         << catchBegin->GetId();
879         [[maybe_unused]] auto result = GetGraph()->AppendThrowBlock(throwBlock);
880         ASSERT(result);
881         GetGraph()->SetThrowApplied();
882     }
883 }
884 
ConnectThrowBlocks()885 void IrBuilder::ConnectThrowBlocks()
886 {
887     for (auto it : tryBlocks_) {
888         const auto &tryBlock = it.second;
889         if (tryBlock.containsThrowableInst) {
890             for (auto throwBlock : *tryBlock.throwBlocks) {
891                 ConnectThrowBlock(throwBlock, tryBlock);
892             }
893         }
894     }
895 }
896 }  // namespace ark::compiler
897