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