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