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 "compiler_logger.h"
17 #include "optimizer/analysis/dominators_tree.h"
18 #include "optimizer/analysis/loop_analyzer.h"
19 #include "optimizer/ir/basicblock.h"
20 #include "optimizer/ir/inst.h"
21 #include "optimizer/optimizations/cleanup.h"
22 #include "optimizer/optimizations/try_catch_resolving.h"
23
24 namespace ark::compiler {
TryCatchResolving(Graph * graph)25 TryCatchResolving::TryCatchResolving(Graph *graph)
26 : Optimization(graph),
27 tryBlocks_(graph->GetLocalAllocator()->Adapter()),
28 throwInsts_(graph->GetLocalAllocator()->Adapter()),
29 throwInsts0_(graph->GetLocalAllocator()->Adapter()),
30 catchBlocks_(graph->GetLocalAllocator()->Adapter()),
31 phiInsts_(graph->GetLocalAllocator()->Adapter()),
32 cphi2phi_(graph->GetLocalAllocator()->Adapter()),
33 catch2cphis_(graph->GetLocalAllocator()->Adapter())
34 {
35 }
36
RunImpl()37 bool TryCatchResolving::RunImpl()
38 {
39 COMPILER_LOG(DEBUG, TRY_CATCH_RESOLVING) << "Running try-catch-resolving";
40 for (auto bb : GetGraph()->GetBlocksRPO()) {
41 if (bb->IsTryBegin()) {
42 tryBlocks_.emplace_back(bb);
43 }
44 }
45 if (!g_options.IsCompilerNonOptimizing()) {
46 CollectCandidates();
47 if (!catchBlocks_.empty() && !throwInsts_.empty()) {
48 ConnectThrowCatch();
49 }
50 }
51 for (auto bb : tryBlocks_) {
52 COMPILER_LOG(DEBUG, TRY_CATCH_RESOLVING) << "Visit try-begin BB " << bb->GetId();
53 VisitTryInst(GetTryBeginInst(bb));
54 }
55 if (!g_options.IsCompilerNonOptimizing() && !GetGraph()->IsAotMode()) {
56 if (!throwInsts0_.empty()) {
57 DeoptimizeIfs();
58 }
59 }
60 GetGraph()->RemoveUnreachableBlocks();
61 GetGraph()->ClearTryCatchInfo();
62 InvalidateAnalyses();
63 // Cleanup should be done inside pass, to satisfy GraphChecker
64 GetGraph()->RunPass<Cleanup>();
65 COMPILER_LOG(DEBUG, TRY_CATCH_RESOLVING) << "Finishing try-catch-resolving";
66 return true;
67 }
68
DeoptimizeIfs()69 void TryCatchResolving::DeoptimizeIfs()
70 {
71 for (auto thr0w : throwInsts0_) {
72 auto *tryBB = thr0w->GetBasicBlock();
73 if (tryBB->GetPredsBlocks().size() != 1) {
74 continue;
75 }
76 auto pred = tryBB->GetPredBlockByIndex(0);
77 auto lastInst = pred->GetLastInst();
78 if (lastInst == nullptr || lastInst->GetOpcode() != Opcode::IfImm) {
79 continue;
80 }
81 auto ifImm = lastInst->CastToIfImm();
82 ASSERT(ifImm->GetCc() == ConditionCode::CC_NE || ifImm->GetCc() == ConditionCode::CC_EQ);
83 ASSERT(ifImm->GetImm() == 0);
84 auto saveState = tryBB->GetFirstInst();
85 if (saveState == nullptr || saveState->GetOpcode() != Opcode::SaveState) {
86 continue;
87 }
88 auto cc = ifImm->GetCc();
89 if (tryBB == pred->GetFalseSuccessor()) {
90 cc = GetInverseConditionCode(cc);
91 }
92 auto compInst = GetGraph()->CreateInstCompare(DataType::BOOL, ifImm->GetPc(), ifImm->GetInput(0).GetInst(),
93 GetGraph()->FindOrCreateConstant(0), DataType::BOOL, cc);
94 #ifdef PANDA_COMPILER_DEBUG_INFO
95 compInst->SetCurrentMethod(ifImm->GetCurrentMethod());
96 #endif
97 saveState->RemoveUsers<false>();
98 tryBB->EraseInst(saveState, true);
99 auto deoptimizeIf =
100 GetGraph()->CreateInstDeoptimizeIf(saveState->GetPc(), compInst, saveState, DeoptimizeType::IFIMM_TRY);
101 deoptimizeIf->SetInput(0, compInst);
102 deoptimizeIf->SetInput(1, saveState);
103 lastInst->InsertBefore(compInst);
104 lastInst->InsertBefore(saveState);
105 lastInst->InsertBefore(deoptimizeIf);
106 COMPILER_LOG(DEBUG, TRY_CATCH_RESOLVING) << "IfImm " << lastInst->GetId() << " BB " << pred->GetId()
107 << " is replaced by DeoptimizeIf " << deoptimizeIf->GetId();
108 pred->RemoveInst(lastInst);
109 pred->RemoveSucc(tryBB);
110 tryBB->RemovePred(pred);
111 }
112 }
113
FindCatchBeginBlock(BasicBlock * bb)114 BasicBlock *TryCatchResolving::FindCatchBeginBlock(BasicBlock *bb)
115 {
116 for (auto pred : bb->GetPredsBlocks()) {
117 if (pred->IsCatchBegin()) {
118 return pred;
119 }
120 }
121 return nullptr;
122 }
123
CollectCandidates()124 void TryCatchResolving::CollectCandidates()
125 {
126 for (auto bb : GetGraph()->GetBlocksRPO()) {
127 if (bb->IsCatch() && !(bb->IsCatchBegin() || bb->IsTryBegin() || bb->IsTryEnd())) {
128 catchBlocks_.emplace(bb->GetGuestPc(), bb);
129 BasicBlock *cblPred = FindCatchBeginBlock(bb);
130 if (cblPred != nullptr) {
131 cblPred->RemoveSucc(bb);
132 bb->RemovePred(cblPred);
133 catch2cphis_.emplace(bb, cblPred);
134 }
135 } else if (bb->IsTry()) {
136 auto throwInst = bb->GetLastInst();
137 if (throwInst != nullptr && throwInst->GetOpcode() != Opcode::Throw) {
138 throwInst = nullptr;
139 }
140 if (GetGraph()->GetThrowCounter(bb) > 0) {
141 ASSERT(throwInst != nullptr);
142 throwInsts_.emplace_back(throwInst);
143 } else if (throwInst != nullptr) {
144 throwInsts0_.emplace_back(throwInst);
145 }
146 }
147 }
148 }
149
ConnectThrowCatchImpl(BasicBlock * catchBlock,BasicBlock * throwBlock,uint32_t catchPc,Inst * newObj,Inst * thr0w)150 void TryCatchResolving::ConnectThrowCatchImpl(BasicBlock *catchBlock, BasicBlock *throwBlock, uint32_t catchPc,
151 Inst *newObj, Inst *thr0w)
152 {
153 auto throwBlockSucc = throwBlock->GetSuccessor(0);
154 throwBlock->RemoveSucc(throwBlockSucc);
155 throwBlockSucc->RemovePred(throwBlock);
156 throwBlock->AddSucc(catchBlock);
157 PhiInst *phiInst = nullptr;
158 auto pit = phiInsts_.find(catchPc);
159 if (pit == phiInsts_.end()) {
160 phiInst = GetGraph()->CreateInstPhi(newObj->GetType(), catchPc);
161 catchBlock->AppendPhi(phiInst);
162 phiInsts_.emplace(catchPc, phiInst);
163 } else {
164 phiInst = pit->second;
165 }
166 phiInst->AppendInput(newObj);
167 auto cpit = catch2cphis_.find(catchBlock);
168 ASSERT(cpit != catch2cphis_.end());
169 auto cphisBlock = cpit->second;
170 RemoveCatchPhis(cphisBlock, catchBlock, thr0w, phiInst);
171 COMPILER_LOG(DEBUG, TRY_CATCH_RESOLVING) << "throw I " << thr0w->GetId() << " BB " << throwBlock->GetId()
172 << " is connected with catch BB " << catchBlock->GetId() << " and removed";
173 throwBlock->RemoveInst(thr0w);
174 }
175
ConnectThrowCatch()176 void TryCatchResolving::ConnectThrowCatch()
177 {
178 auto *graph = GetGraph();
179 auto *runtime = graph->GetRuntime();
180 auto *method = graph->GetMethod();
181 for (auto thr0w : throwInsts_) {
182 auto throwBlock = thr0w->GetBasicBlock();
183 auto throwInst = thr0w->CastToThrow();
184 // Inlined throws generate the problem with matching calls and returns now. NOTE Should be fixed.
185 if (GetGraph()->GetThrowCounter(throwBlock) == 0 || throwInst->IsInlined()) {
186 continue;
187 }
188 auto newObj = thr0w->GetInput(0).GetInst();
189 RuntimeInterface::ClassPtr cls = nullptr;
190 if (newObj->GetOpcode() != Opcode::NewObject) {
191 continue;
192 }
193 auto initClass = newObj->GetInput(0).GetInst();
194 if (initClass->GetOpcode() == Opcode::LoadAndInitClass) {
195 cls = initClass->CastToLoadAndInitClass()->GetClass();
196 } else {
197 ASSERT(initClass->GetOpcode() == Opcode::LoadImmediate);
198 cls = initClass->CastToLoadImmediate()->GetClass();
199 }
200 if (cls == nullptr) {
201 continue;
202 }
203 auto catchPc = runtime->FindCatchBlock(method, cls, thr0w->GetPc());
204 if (catchPc == panda_file::INVALID_OFFSET) {
205 continue;
206 }
207 auto cit = catchBlocks_.find(catchPc);
208 if (cit == catchBlocks_.end()) {
209 continue;
210 }
211 ConnectThrowCatchImpl(cit->second, throwBlock, catchPc, newObj, thr0w);
212 }
213 }
214
215 /**
216 * Search throw instruction with known at compile-time `object_id`
217 * and directly connect catch-handler for this `object_id` if it exists in the current graph
218 */
VisitTryInst(TryInst * tryInst)219 void TryCatchResolving::VisitTryInst(TryInst *tryInst)
220 {
221 auto tryBegin = tryInst->GetBasicBlock();
222 auto tryEnd = tryInst->GetTryEndBlock();
223 ASSERT(tryBegin != nullptr && tryBegin->IsTryBegin());
224 ASSERT(tryEnd != nullptr && tryEnd->IsTryEnd());
225
226 // Now, when catch-handler was searched - remove all edges from `try_begin` and `try_end` blocks
227 DeleteTryCatchEdges(tryBegin, tryEnd);
228 // Clean-up labels and `try_inst`
229 COMPILER_LOG(DEBUG, TRY_CATCH_RESOLVING) << "Erase try-inst I " << tryInst->GetId();
230 tryBegin->EraseInst(tryInst);
231 COMPILER_LOG(DEBUG, TRY_CATCH_RESOLVING) << "Unset try-begin BB " << tryBegin->GetId();
232 tryBegin->SetTryBegin(false);
233 COMPILER_LOG(DEBUG, TRY_CATCH_RESOLVING) << "Unset try-end BB " << tryEnd->GetId();
234 tryEnd->SetTryEnd(false);
235 }
236
237 /// Disconnect auxiliary `try_begin` and `try_end`. That means all related catch-handlers become unreachable
DeleteTryCatchEdges(BasicBlock * tryBegin,BasicBlock * tryEnd)238 void TryCatchResolving::DeleteTryCatchEdges(BasicBlock *tryBegin, BasicBlock *tryEnd)
239 {
240 while (tryBegin->GetSuccsBlocks().size() > 1U) {
241 auto catchSucc = tryBegin->GetSuccessor(1U);
242 ASSERT(catchSucc->IsCatchBegin());
243 tryBegin->RemoveSucc(catchSucc);
244 catchSucc->RemovePred(tryBegin);
245 COMPILER_LOG(DEBUG, TRY_CATCH_RESOLVING)
246 << "Remove edge between try_begin BB " << tryBegin->GetId() << " and catch-begin BB " << catchSucc->GetId();
247 if (tryEnd->GetGraph() != nullptr) {
248 ASSERT(tryEnd->GetSuccessor(1) == catchSucc);
249 tryEnd->RemoveSucc(catchSucc);
250 catchSucc->RemovePred(tryEnd);
251 COMPILER_LOG(DEBUG, TRY_CATCH_RESOLVING)
252 << "Remove edge between try_end BB " << tryEnd->GetId() << " and catch-begin BB " << catchSucc->GetId();
253 }
254 }
255 }
256
RemoveCatchPhisImpl(CatchPhiInst * catchPhi,BasicBlock * catchBlock,Inst * throwInst)257 void TryCatchResolving::RemoveCatchPhisImpl(CatchPhiInst *catchPhi, BasicBlock *catchBlock, Inst *throwInst)
258 {
259 auto throwInsts = catchPhi->GetThrowableInsts();
260 auto it = std::find(throwInsts->begin(), throwInsts->end(), throwInst);
261 if (it != throwInsts->end()) {
262 auto inputIndex = std::distance(throwInsts->begin(), it);
263 auto inputInst = catchPhi->GetInput(inputIndex).GetInst();
264 PhiInst *phi = nullptr;
265 auto cit = cphi2phi_.find(catchPhi);
266 if (cit == cphi2phi_.end()) {
267 phi = GetGraph()->CreateInstPhi(catchPhi->GetType(), catchBlock->GetGuestPc())->CastToPhi();
268 catchBlock->AppendPhi(phi);
269 catchPhi->ReplaceUsers(phi);
270 cphi2phi_.emplace(catchPhi, phi);
271 } else {
272 phi = cit->second;
273 }
274 phi->AppendInput(inputInst);
275 } else {
276 while (!catchPhi->GetUsers().Empty()) {
277 auto &user = catchPhi->GetUsers().Front();
278 auto userInst = user.GetInst();
279 if (userInst->IsSaveState() || userInst->IsCatchPhi()) {
280 userInst->RemoveInput(user.GetIndex());
281 } else {
282 auto inputInst = catchPhi->GetInput(0).GetInst();
283 userInst->ReplaceInput(catchPhi, inputInst);
284 }
285 }
286 }
287 }
288
289 /**
290 * Replace all catch-phi instructions with their inputs
291 * Replace accumulator's catch-phi with exception's object
292 */
RemoveCatchPhis(BasicBlock * cphisBlock,BasicBlock * catchBlock,Inst * throwInst,Inst * phiInst)293 void TryCatchResolving::RemoveCatchPhis(BasicBlock *cphisBlock, BasicBlock *catchBlock, Inst *throwInst, Inst *phiInst)
294 {
295 ASSERT(cphisBlock->IsCatchBegin());
296 for (auto inst : cphisBlock->AllInstsSafe()) {
297 if (!inst->IsCatchPhi()) {
298 break;
299 }
300 auto catchPhi = inst->CastToCatchPhi();
301 if (catchPhi->IsAcc()) {
302 catchPhi->ReplaceUsers(phiInst);
303 } else {
304 RemoveCatchPhisImpl(catchPhi, catchBlock, throwInst);
305 }
306 }
307 }
308
InvalidateAnalyses()309 void TryCatchResolving::InvalidateAnalyses()
310 {
311 GetGraph()->InvalidateAnalysis<DominatorsTree>();
312 GetGraph()->InvalidateAnalysis<LoopAnalyzer>();
313 InvalidateBlocksOrderAnalyzes(GetGraph());
314 }
315 } // namespace ark::compiler
316