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