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