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