• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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