1 /**
2 * Copyright (c) 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 <fstream>
17 #include "bytecode_inst.h"
18 #include "static_core/compiler/optimizer/analysis/dominators_tree.h"
19 #include "static_core/compiler/optimizer/analysis/loop_analyzer.h"
20 #include "libabckit/src/irbuilder_dynamic/ir_builder_dyn.h"
21
22 namespace libabckit {
23
RunImpl()24 bool IrBuilderDynamic::RunImpl()
25 {
26 auto instructionsBuf = GetGraph()->GetRuntime()->GetMethodCode(GetMethod());
27 BytecodeInstructions pbcInstructions(instructionsBuf, GetGraph()->GetRuntime()->GetMethodCodeSize(GetMethod()));
28 size_t vregsCount = GetGraph()->GetRuntime()->GetMethodRegistersCount(GetMethod()) +
29 GetGraph()->GetRuntime()->GetMethodTotalArgumentsCount(GetMethod()) + 1;
30 if (!CheckMethodLimitations(pbcInstructions, vregsCount)) {
31 return false;
32 }
33 GetGraph()->SetVRegsCount(vregsCount);
34 BuildBasicBlocks(pbcInstructions);
35 GetGraph()->RunPass<compiler::DominatorsTree>();
36 GetGraph()->RunPass<compiler::LoopAnalyzer>();
37
38 InstBuilder instBuilder(GetGraph(), GetMethod());
39 instBuilder.Prepare();
40 instDefs_.resize(vregsCount);
41
42 for (auto bb : GetGraph()->GetBlocksRPO()) {
43 if (!BuildBasicBlock(bb, &instBuilder, instructionsBuf)) {
44 return false;
45 }
46 }
47
48 GetGraph()->RunPass<compiler::DominatorsTree>();
49 GetGraph()->InvalidateAnalysis<compiler::LoopAnalyzer>();
50 GetGraph()->RunPass<compiler::LoopAnalyzer>();
51 instBuilder.FixInstructions();
52 return true;
53 }
54
CheckMethodLimitations(const BytecodeInstructions &,size_t)55 bool IrBuilderDynamic::CheckMethodLimitations(const BytecodeInstructions & /*instructions*/, size_t /*vregsCount*/)
56 {
57 return true;
58 }
59
BuildBasicBlock(compiler::BasicBlock * bb,InstBuilder * instBuilder,const uint8_t * instructionsBuf)60 bool IrBuilderDynamic::BuildBasicBlock(compiler::BasicBlock *bb, InstBuilder *instBuilder,
61 const uint8_t *instructionsBuf)
62 {
63 instBuilder->SetCurrentBlock(bb);
64 instBuilder->UpdateDefs();
65 ASSERT(bb->GetGuestPc() != ark::compiler::INVALID_PC);
66 // If block is not in the `blocks_` vector, it's auxiliary block without instructions
67 if (bb == blocks_[bb->GetGuestPc()]) {
68 return BuildInstructionsForBB(bb, instBuilder, instructionsBuf);
69 }
70
71 return true;
72 }
73
BuildInstructionsForBB(compiler::BasicBlock * bb,InstBuilder * instBuilder,const uint8_t * instructionsBuf)74 bool IrBuilderDynamic::BuildInstructionsForBB(compiler::BasicBlock *bb, InstBuilder *instBuilder,
75 const uint8_t *instructionsBuf)
76 {
77 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
78 BytecodeInstructions instructions(instructionsBuf + bb->GetGuestPc(), std::numeric_limits<int>::max());
79 for (auto inst : instructions) {
80 auto pc = instBuilder->GetPc(inst.GetAddress());
81 // Break if current pc is pc of some basic block, that means that it is the end of the current block.
82 if (pc != bb->GetGuestPc() && GetBlockForPc(pc) != nullptr) {
83 break;
84 }
85 // Copy current defs for assigning them to catch-phi if current inst is throwable
86 ASSERT(instBuilder->GetCurrentDefs().size() == instDefs_.size());
87 std::copy(instBuilder->GetCurrentDefs().begin(), instBuilder->GetCurrentDefs().end(), instDefs_.begin());
88 auto currentLastInst = bb->GetLastInst();
89 auto bbCount = GetGraph()->GetVectorBlocks().size();
90 instBuilder->BuildInstruction(&inst);
91
92 if (instBuilder->IsFailed()) {
93 std::cerr << "Unsupported instruction\n";
94 return false;
95 }
96
97 // One PBC instruction can be expanded to the group of IR's instructions, find first built instruction in
98 // this group, and then mark all instructions as throwable; All instructions should be marked, since some of
99 // them can be deleted during optimizations, unnecessary catch-phi moves will be resolved before Register
100 // Allocator
101 auto throwableInst = (currentLastInst == nullptr) ? bb->GetFirstInst() : currentLastInst->GetNext();
102 ProcessThrowableInstructions(instBuilder, throwableInst);
103
104 auto &vb = GetGraph()->GetVectorBlocks();
105 for (size_t i = bbCount; i < vb.size(); i++) {
106 ProcessThrowableInstructions(instBuilder, vb[i]->GetFirstInst());
107 }
108
109 // Break if we meet terminator instruction. If instruction in the middle of basic block we don't create
110 // further dead instructions.
111 if (inst.IsTerminator() && !inst.IsSuspend()) {
112 break;
113 }
114 }
115 return true;
116 }
117
ProcessThrowableInstructions(InstBuilder * instBuilder,compiler::Inst * throwableInst)118 void IrBuilderDynamic::ProcessThrowableInstructions(InstBuilder *instBuilder, compiler::Inst *throwableInst)
119 {
120 for (; throwableInst != nullptr; throwableInst = throwableInst->GetNext()) {
121 if (throwableInst->IsSaveState()) {
122 continue;
123 }
124 catchHandlers_.clear();
125 EnumerateTryBlocksCoveredPc(throwableInst->GetPc(), [this](const TryCodeBlock &tryBlock) {
126 auto tbb = tryBlock.beginBb;
127 tbb->EnumerateCatchHandlers([this](compiler::BasicBlock *catchHandler, [[maybe_unused]] size_t typeId) {
128 catchHandlers_.insert(catchHandler);
129 return true;
130 });
131 });
132 if (!catchHandlers_.empty()) {
133 instBuilder->AddCatchPhiInputs(catchHandlers_, instDefs_, throwableInst);
134 }
135 }
136 }
137
InstNotJump(BytecodeInst * inst)138 static inline bool InstNotJump(BytecodeInst *inst)
139 {
140 return inst->GetAddress() != nullptr && InstBuilder::GetInstructionJumpOffset(inst) == INVALID_OFFSET &&
141 !inst->HasFlag(BytecodeInst::RETURN);
142 }
143
BuildBasicBlocks(const BytecodeInstructions & instructions)144 void IrBuilderDynamic::BuildBasicBlocks(const BytecodeInstructions &instructions)
145 {
146 blocks_.resize(instructions.GetSize() + 1);
147 bool fallthrough = false;
148
149 CreateBlock(0);
150 // Create basic blocks
151 for (auto inst : instructions) {
152 auto pc = instructions.GetPc(inst);
153
154 if (fallthrough) {
155 CreateBlock(pc);
156 fallthrough = false;
157 }
158 auto offset = InstBuilder::GetInstructionJumpOffset(&inst);
159 if (offset != INVALID_OFFSET) {
160 auto targetPc = pc + static_cast<size_t>(offset);
161 CreateBlock(targetPc);
162 if (inst.HasFlag(BytecodeInst::CONDITIONAL)) {
163 fallthrough = true;
164 }
165 }
166 }
167 CreateTryCatchBoundariesBlocks();
168 GetGraph()->CreateStartBlock();
169 GetGraph()->CreateEndBlock(instructions.GetSize());
170 ConnectBasicBlocks(instructions);
171 ResolveTryCatchBlocks();
172 }
173
174 template <class Callback>
EnumerateTryBlocksCoveredPc(uint32_t pc,const Callback & callback)175 void IrBuilderDynamic::EnumerateTryBlocksCoveredPc(uint32_t pc, const Callback &callback)
176 {
177 for (const auto &[begin_pc, try_block] : tryBlocks_) {
178 if (begin_pc <= pc && pc < try_block.boundaries.endPc) {
179 callback(try_block);
180 }
181 }
182 }
183
184 /**
185 * Return `TryCodeBlock` and flag if was created a new one
186 */
InsertTryBlockInfo(const Boundaries & tryBoundaries)187 IrBuilderDynamic::TryCodeBlock *IrBuilderDynamic::InsertTryBlockInfo(const Boundaries &tryBoundaries)
188 {
189 auto tryId = static_cast<uint32_t>(tryBlocks_.size());
190 auto range = tryBlocks_.equal_range(tryBoundaries.beginPc);
191 for (auto iter = range.first; iter != range.second; ++iter) {
192 // use try-block with the same boundaries
193 if (tryBoundaries.endPc == iter->second.boundaries.endPc) {
194 return &iter->second;
195 }
196 // insert in the increasing `end_pc` order
197 if (tryBoundaries.endPc > iter->second.boundaries.endPc) {
198 auto it = tryBlocks_.emplace_hint(iter, tryBoundaries.beginPc, TryCodeBlock {tryBoundaries});
199 it->second.Init(GetGraph(), tryId);
200 return &it->second;
201 }
202 }
203 auto it = tryBlocks_.emplace(tryBoundaries.beginPc, TryCodeBlock {tryBoundaries});
204 it->second.Init(GetGraph(), tryId);
205 return &it->second;
206 }
207
CreateTryCatchBoundariesBlocks()208 void IrBuilderDynamic::CreateTryCatchBoundariesBlocks()
209 {
210 auto *pfw = static_cast<FileWrapper *>(GetGraph()->GetRuntime()->GetBinaryFileForMethod(GetMethod()));
211 pfw->EnumerateTryBlocks(GetMethod(), [pfw, this](void *tryBlock) {
212 auto [start, end] = pfw->GetTryBlockBoundaries(tryBlock);
213 auto *tryInfo = InsertTryBlockInfo({start, end});
214 pfw->EnumerateCatchBlocksForTryBlock(tryBlock, [pfw, this, tryInfo](void *catchBlock) {
215 auto pc = pfw->GetCatchBlockHandlerPc(catchBlock);
216 catchesPc_.insert(pc);
217 tryInfo->catches->emplace_back(CatchCodeBlock {pc, 0U});
218 });
219 });
220
221 for (const auto &[pc, try_block] : tryBlocks_) {
222 CreateBlock(pc);
223 CreateBlock(try_block.boundaries.endPc);
224 }
225 for (auto pc : catchesPc_) {
226 CreateBlock(pc);
227 }
228 }
229
AddSuccs(BlocksConnectorInfo * info,compiler::BasicBlock * currBb,compiler::BasicBlock * targetBlock,size_t & pc,BytecodeInst & inst)230 compiler::BasicBlock *IrBuilderDynamic::AddSuccs(BlocksConnectorInfo *info, compiler::BasicBlock *currBb,
231 compiler::BasicBlock *targetBlock, size_t &pc, BytecodeInst &inst)
232 {
233 if (info->fallthrough) {
234 ASSERT(targetBlock != nullptr);
235 // May be the second edge between same blocks
236 currBb->AddSucc(targetBlock, true);
237 info->fallthrough = false;
238 currBb = targetBlock;
239 } else if (targetBlock != nullptr) {
240 if (catchesPc_.count(pc) == 0) {
241 if (InstNotJump(&info->prevInst) && !info->deadInstructions) {
242 currBb->AddSucc(targetBlock);
243 }
244 }
245 currBb = targetBlock;
246 info->deadInstructions = false;
247 } else if (info->deadInstructions) {
248 // We are processing dead instructions now, skipping them until we meet the next block.
249 return currBb;
250 }
251 if (auto jmpTargetBlock = GetBlockToJump(&inst, pc); jmpTargetBlock != nullptr) {
252 currBb->AddSucc(jmpTargetBlock);
253 // In case of unconditional branch, we reset curr_bb, so if next instruction won't start new block, then
254 // we'll skip further dead instructions.
255 info->fallthrough = inst.HasFlag(BytecodeInst::CONDITIONAL);
256 if (!info->fallthrough) {
257 info->deadInstructions = true;
258 }
259 }
260 info->prevInst = inst;
261 return currBb;
262 }
263
ConnectBasicBlocks(const BytecodeInstructions & instructions)264 void IrBuilderDynamic::ConnectBasicBlocks(const BytecodeInstructions &instructions)
265 {
266 BlocksConnectorInfo info;
267 compiler::BasicBlock *currBb = blocks_[0];
268 GetGraph()->GetStartBlock()->AddSucc(currBb);
269 for (auto inst : instructions) {
270 auto pc = instructions.GetPc(inst);
271 auto targetBlock = blocks_[pc];
272 TrackTryBoundaries(pc);
273 currBb = AddSuccs(&info, currBb, targetBlock, pc, inst);
274 }
275
276 // Erase end block if it wasn't connected, should be infinite loop in the graph
277 if (GetGraph()->GetEndBlock()->GetPredsBlocks().empty()) {
278 GetGraph()->EraseBlock(GetGraph()->GetEndBlock());
279 GetGraph()->SetEndBlock(nullptr);
280 }
281 }
282
TrackTryBoundaries(size_t pc)283 void IrBuilderDynamic::TrackTryBoundaries(size_t pc)
284 {
285 openedTryBlocks_.remove_if([pc](TryCodeBlock *tryBlock) { return tryBlock->boundaries.endPc == pc; });
286
287 if (tryBlocks_.count(pc) > 0) {
288 auto range = tryBlocks_.equal_range(pc);
289 for (auto it = range.first; it != range.second; ++it) {
290 auto &tryBlock = it->second;
291 if (tryBlock.boundaries.endPc > pc) {
292 openedTryBlocks_.push_back(&tryBlock);
293 auto allocator = GetGraph()->GetLocalAllocator();
294 tryBlock.basicBlocks = allocator->New<ArenaVector<compiler::BasicBlock *>>(allocator->Adapter());
295 } else {
296 // Empty try-block
297 ASSERT(tryBlock.boundaries.endPc == pc);
298 }
299 }
300 }
301
302 if (openedTryBlocks_.empty()) {
303 return;
304 }
305
306 if (auto bb = blocks_[pc]; bb != nullptr) {
307 for (auto tryBlock : openedTryBlocks_) {
308 tryBlock->basicBlocks->push_back(bb);
309 }
310 }
311
312 for (auto &tryBlock : openedTryBlocks_) {
313 tryBlock->containsThrowableInst = true;
314 }
315 }
316
GetBlockToJump(BytecodeInst * inst,size_t pc)317 compiler::BasicBlock *IrBuilderDynamic::GetBlockToJump(BytecodeInst *inst, size_t pc)
318 {
319 if ((inst->HasFlag(BytecodeInst::RETURN) && !inst->HasFlag(BytecodeInst::SUSPEND)) ||
320 inst->IsThrow(BytecodeInst::Exceptions::X_THROW)) {
321 return GetGraph()->GetEndBlock();
322 }
323
324 #ifdef ENABLE_BYTECODE_OPT
325 if (inst->GetOpcode() == BytecodeInst::Opcode::RETURNUNDEFINED) {
326 return GetGraph()->GetEndBlock();
327 }
328 #endif
329
330 if (auto offset = InstBuilder::GetInstructionJumpOffset(inst); offset != INVALID_OFFSET) {
331 ASSERT(blocks_[pc + static_cast<size_t>(offset)] != nullptr);
332 return blocks_[pc + static_cast<size_t>(offset)];
333 }
334 return nullptr;
335 }
336
337 /**
338 * Mark blocks which were connected to the graph.
339 * Catch-handlers will not be marked, since they have not been connected yet.
340 */
MarkNormalControlFlow(compiler::BasicBlock * block,compiler::Marker marker)341 static void MarkNormalControlFlow(compiler::BasicBlock *block, compiler::Marker marker)
342 {
343 block->SetMarker(marker);
344 for (auto succ : block->GetSuccsBlocks()) {
345 if (!succ->IsMarked(marker)) {
346 MarkNormalControlFlow(succ, marker);
347 }
348 }
349 }
350
MarkTryCatchBlocks(compiler::Marker marker)351 void IrBuilderDynamic::MarkTryCatchBlocks(compiler::Marker marker)
352 {
353 // All blocks without `normal` mark are considered as catch-blocks
354 for (auto bb : GetGraph()->GetBlocksRPO()) {
355 if (bb->IsMarked(marker)) {
356 continue;
357 }
358 if (bb->IsTryBegin()) {
359 bb->SetCatch(bb->GetSuccessor(0)->IsCatch());
360 } else if (bb->IsTryEnd()) {
361 bb->SetCatch(bb->GetPredecessor(0)->IsCatch());
362 } else {
363 bb->SetCatch(true);
364 }
365 }
366
367 // Nested try-blocks can be removed, but referring to them basic blocks can be placed in the external try-blocks.
368 // So `try` marks are added after removing unreachable blocks
369 for (auto it : tryBlocks_) {
370 const auto &tryBlock = it.second;
371 if (tryBlock.beginBb->GetGraph() != tryBlock.endBb->GetGraph()) {
372 RestoreTryEnd(tryBlock);
373 }
374 tryBlock.beginBb->SetTryId(tryBlock.id);
375 tryBlock.endBb->SetTryId(tryBlock.id);
376 if (tryBlock.basicBlocks == nullptr) {
377 continue;
378 }
379 for (auto bb : *tryBlock.basicBlocks) {
380 bb->SetTryId(tryBlock.id);
381 bb->SetTry(true);
382 }
383 }
384 }
385
386 /*
387 * Connect catch-blocks to the graph.
388 */
ResolveTryCatchBlocks()389 void IrBuilderDynamic::ResolveTryCatchBlocks()
390 {
391 auto markerHolder = compiler::MarkerHolder(GetGraph());
392 auto marker = markerHolder.GetMarker();
393 MarkNormalControlFlow(GetGraph()->GetStartBlock(), marker);
394 ConnectTryCatchBlocks();
395 GetGraph()->RemoveUnreachableBlocks();
396 MarkTryCatchBlocks(marker);
397 }
398
ConnectTryCatchBlocks()399 void IrBuilderDynamic::ConnectTryCatchBlocks()
400 {
401 ArenaMap<uint32_t, compiler::BasicBlock *> catchBlocks(GetGraph()->GetLocalAllocator()->Adapter());
402 // Firstly create catch_begin blocks, as they should precede try_begin blocks
403 for (auto pc : catchesPc_) {
404 auto catchBegin = GetGraph()->CreateEmptyBlock();
405 catchBegin->SetGuestPc(pc);
406 catchBegin->SetCatch(true);
407 catchBegin->SetCatchBegin(true);
408 auto firstCatchBb = GetBlockForPc(pc);
409 catchBegin->AddSucc(firstCatchBb);
410 catchBlocks.emplace(pc, catchBegin);
411 }
412
413 // Connect try_begin and catch_begin blocks
414 for (auto it : tryBlocks_) {
415 const auto &tryBlock = it.second;
416 if (tryBlock.containsThrowableInst) {
417 ConnectTryCodeBlock(tryBlock, catchBlocks);
418 } else if (tryBlock.basicBlocks != nullptr) {
419 tryBlock.basicBlocks->clear();
420 }
421 }
422 }
423
ConnectTryCodeBlock(const TryCodeBlock & tryBlock,const ArenaMap<uint32_t,compiler::BasicBlock * > & catchBlocks)424 void IrBuilderDynamic::ConnectTryCodeBlock(const TryCodeBlock &tryBlock,
425 const ArenaMap<uint32_t, compiler::BasicBlock *> &catchBlocks)
426 {
427 auto tryBegin = tryBlock.beginBb;
428 ASSERT(tryBegin != nullptr);
429 auto tryEnd = tryBlock.endBb;
430 ASSERT(tryEnd != nullptr);
431 // Create auxiliary `Try` instruction
432 auto tryInst = GetGraph()->CreateInstTry();
433 tryInst->SetTryEndBlock(tryEnd);
434 tryBegin->AppendInst(tryInst);
435 // Insert `try_begin` and `try_end`
436 auto firstTryBb = GetBlockForPc(tryBlock.boundaries.beginPc);
437 auto lastTryBb = GetPrevBlockForPc(tryBlock.boundaries.endPc);
438 firstTryBb->InsertBlockBefore(tryBegin);
439 lastTryBb->InsertBlockBeforeSucc(tryEnd, lastTryBb->GetSuccessor(0));
440 // Connect catch-handlers
441 for (auto catchBlock : *tryBlock.catches) {
442 auto catchBegin = catchBlocks.at(catchBlock.pc);
443 if (!tryBegin->HasSucc(catchBegin)) {
444 tryBegin->AddSucc(catchBegin, true);
445 tryEnd->AddSucc(catchBegin, true);
446 }
447 tryInst->AppendCatchTypeId(catchBlock.typeId, tryBegin->GetSuccBlockIndex(catchBegin));
448 }
449 }
450
451 /**
452 * `try_end` restoring is required in the following case:
453 * try {
454 * try { a++;}
455 * catch { a++; }
456 * }
457 *
458 * Nested try doesn't contain throwable instructions and related catch-handler will not be connected to the graph.
459 * As a result all `catch` basic blocks will be eliminated together with outer's `try_end`, since it was inserted just
460 * after `catch`
461 */
RestoreTryEnd(const TryCodeBlock & tryBlock)462 void IrBuilderDynamic::RestoreTryEnd(const TryCodeBlock &tryBlock)
463 {
464 ASSERT(tryBlock.endBb->GetGraph() == nullptr);
465 ASSERT(tryBlock.endBb->GetSuccsBlocks().empty());
466 ASSERT(tryBlock.endBb->GetPredsBlocks().empty());
467
468 GetGraph()->RestoreBlock(tryBlock.endBb);
469 auto lastTryBb = GetPrevBlockForPc(tryBlock.boundaries.endPc);
470 lastTryBb->InsertBlockBeforeSucc(tryBlock.endBb, lastTryBb->GetSuccessor(0));
471 for (auto succ : tryBlock.beginBb->GetSuccsBlocks()) {
472 if (succ->IsCatchBegin()) {
473 tryBlock.endBb->AddSucc(succ);
474 }
475 }
476 }
477
478 } // namespace libabckit
479